@geekmidas/cli 1.2.2 → 1.2.3
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 +6 -0
- package/dist/index.cjs +88 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +89 -162
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-BZ4Qik9w.mjs → openapi-NthphEWK.mjs} +2 -2
- package/dist/{openapi-BZ4Qik9w.mjs.map → openapi-NthphEWK.mjs.map} +1 -1
- package/dist/{openapi-CzfnHlhG.cjs → openapi-ZhO7wwya.cjs} +1 -7
- package/dist/{openapi-CzfnHlhG.cjs.map → openapi-ZhO7wwya.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.mjs +1 -1
- package/package.json +2 -2
- package/src/dev/index.ts +69 -106
- package/src/workspace/__tests__/client-generator.spec.ts +330 -301
- package/src/workspace/client-generator.ts +139 -199
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { __require, getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-CASoZOjs.mjs";
|
|
3
3
|
import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-BQ4a36Rq.mjs";
|
|
4
4
|
import { getCredentialsPath, getDokployCredentials, getDokployRegistryId, getDokployToken, removeDokployCredentials, storeDokployCredentials, storeDokployRegistryId } from "./credentials-DT1dSxIx.mjs";
|
|
5
|
-
import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH,
|
|
5
|
+
import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-NthphEWK.mjs";
|
|
6
6
|
import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-BMW6yLu3.mjs";
|
|
7
7
|
import { DokployApi } from "./dokploy-api-7k3t7_zd.mjs";
|
|
8
8
|
import { encryptSecrets } from "./encryption-JtMsiGNp.mjs";
|
|
@@ -23,7 +23,7 @@ import fg from "fast-glob";
|
|
|
23
23
|
import { Cron } from "@geekmidas/constructs/crons";
|
|
24
24
|
import { Function } from "@geekmidas/constructs/functions";
|
|
25
25
|
import { Subscriber } from "@geekmidas/constructs/subscribers";
|
|
26
|
-
import {
|
|
26
|
+
import { randomBytes } from "node:crypto";
|
|
27
27
|
import { Client } from "pg";
|
|
28
28
|
import { lookup } from "node:dns/promises";
|
|
29
29
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -31,7 +31,7 @@ import prompts from "prompts";
|
|
|
31
31
|
|
|
32
32
|
//#region package.json
|
|
33
33
|
var name = "@geekmidas/cli";
|
|
34
|
-
var version = "1.2.
|
|
34
|
+
var version = "1.2.2";
|
|
35
35
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
36
36
|
var private$1 = false;
|
|
37
37
|
var type = "module";
|
|
@@ -627,99 +627,61 @@ export async function setupSubscribers(
|
|
|
627
627
|
//#region src/workspace/client-generator.ts
|
|
628
628
|
const logger$10 = console;
|
|
629
629
|
/**
|
|
630
|
-
*
|
|
631
|
-
*/
|
|
632
|
-
const specHashCache = /* @__PURE__ */ new Map();
|
|
633
|
-
/**
|
|
634
|
-
* Calculate hash of content for change detection.
|
|
630
|
+
* Get frontend apps that depend on a backend app.
|
|
635
631
|
*/
|
|
636
|
-
function
|
|
637
|
-
|
|
632
|
+
function getDependentFrontends(workspace, backendAppName) {
|
|
633
|
+
const dependentApps = [];
|
|
634
|
+
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.includes(backendAppName)) dependentApps.push(appName);
|
|
635
|
+
return dependentApps;
|
|
638
636
|
}
|
|
639
637
|
/**
|
|
640
|
-
*
|
|
641
|
-
* @internal Exported for use in dev command
|
|
638
|
+
* Get the path to a backend's OpenAPI spec file.
|
|
642
639
|
*/
|
|
643
|
-
function
|
|
644
|
-
|
|
645
|
-
|
|
640
|
+
function getBackendOpenApiPath(workspace, backendAppName) {
|
|
641
|
+
const app = workspace.apps[backendAppName];
|
|
642
|
+
if (!app || app.type !== "backend") return null;
|
|
643
|
+
return join(workspace.root, app.path, ".gkm", "openapi.ts");
|
|
646
644
|
}
|
|
647
645
|
/**
|
|
648
|
-
*
|
|
649
|
-
* Returns the spec content and endpoint count.
|
|
646
|
+
* Count endpoints in an OpenAPI spec content.
|
|
650
647
|
*/
|
|
651
|
-
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
const appPath = join(workspace.root, app.path);
|
|
655
|
-
const routesPatterns = normalizeRoutes(app.routes);
|
|
656
|
-
if (routesPatterns.length === 0) return null;
|
|
657
|
-
const endpointGenerator = new EndpointGenerator();
|
|
658
|
-
const allLoadedEndpoints = [];
|
|
659
|
-
for (const pattern of routesPatterns) {
|
|
660
|
-
const fullPattern = join(appPath, pattern);
|
|
661
|
-
const loaded = await endpointGenerator.load(fullPattern);
|
|
662
|
-
allLoadedEndpoints.push(...loaded);
|
|
663
|
-
}
|
|
664
|
-
const loadedEndpoints = allLoadedEndpoints;
|
|
665
|
-
if (loadedEndpoints.length === 0) return null;
|
|
666
|
-
const endpoints = loadedEndpoints.map(({ construct }) => construct);
|
|
667
|
-
const tsGenerator = new OpenApiTsGenerator();
|
|
668
|
-
const content = await tsGenerator.generate(endpoints, {
|
|
669
|
-
title: `${appName} API`,
|
|
670
|
-
version: "1.0.0",
|
|
671
|
-
description: `Auto-generated API client for ${appName}`
|
|
672
|
-
});
|
|
673
|
-
return {
|
|
674
|
-
content,
|
|
675
|
-
endpointCount: loadedEndpoints.length
|
|
676
|
-
};
|
|
648
|
+
function countEndpoints(content) {
|
|
649
|
+
const endpointMatches = content.match(/'(GET|POST|PUT|PATCH|DELETE)\s+\/[^']+'/g);
|
|
650
|
+
return endpointMatches?.length ?? 0;
|
|
677
651
|
}
|
|
678
652
|
/**
|
|
679
|
-
*
|
|
680
|
-
*
|
|
653
|
+
* Copy the OpenAPI client from a backend to all dependent frontend apps.
|
|
654
|
+
* Called when the backend's .gkm/openapi.ts file changes.
|
|
681
655
|
*/
|
|
682
|
-
async function
|
|
656
|
+
async function copyClientToFrontends(workspace, backendAppName, options = {}) {
|
|
657
|
+
const log = options.silent ? () => {} : logger$10.log.bind(logger$10);
|
|
683
658
|
const results = [];
|
|
684
|
-
const
|
|
685
|
-
if (!
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
659
|
+
const backendApp = workspace.apps[backendAppName];
|
|
660
|
+
if (!backendApp || backendApp.type !== "backend") return results;
|
|
661
|
+
const openApiPath = join(workspace.root, backendApp.path, ".gkm", "openapi.ts");
|
|
662
|
+
if (!existsSync(openApiPath)) return results;
|
|
663
|
+
const content = await readFile(openApiPath, "utf-8");
|
|
664
|
+
const endpointCount = countEndpoints(content);
|
|
665
|
+
const dependentFrontends = getDependentFrontends(workspace, backendAppName);
|
|
666
|
+
for (const frontendAppName of dependentFrontends) {
|
|
667
|
+
const frontendApp = workspace.apps[frontendAppName];
|
|
668
|
+
if (!frontendApp || frontendApp.type !== "frontend") continue;
|
|
669
|
+
const clientOutput = frontendApp.client?.output;
|
|
670
|
+
if (!clientOutput) continue;
|
|
696
671
|
const result = {
|
|
697
672
|
frontendApp: frontendAppName,
|
|
698
673
|
backendApp: backendAppName,
|
|
699
674
|
outputPath: "",
|
|
700
|
-
endpointCount
|
|
701
|
-
|
|
675
|
+
endpointCount,
|
|
676
|
+
success: false
|
|
702
677
|
};
|
|
703
678
|
try {
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
result.reason = "No endpoints found in backend";
|
|
707
|
-
results.push(result);
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
result.endpointCount = spec.endpointCount;
|
|
711
|
-
const cacheKey = `${backendAppName}:${frontendAppName}`;
|
|
712
|
-
const newHash = hashContent(spec.content);
|
|
713
|
-
const oldHash = specHashCache.get(cacheKey);
|
|
714
|
-
if (!options.force && oldHash === newHash) {
|
|
715
|
-
result.reason = "No schema changes detected";
|
|
716
|
-
results.push(result);
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
679
|
+
const frontendPath = join(workspace.root, frontendApp.path);
|
|
680
|
+
const outputDir = join(frontendPath, clientOutput);
|
|
719
681
|
await mkdir(outputDir, { recursive: true });
|
|
720
|
-
const fileName =
|
|
682
|
+
const fileName = `${backendAppName}.ts`;
|
|
721
683
|
const outputPath = join(outputDir, fileName);
|
|
722
|
-
const backendRelPath = relative(dirname(outputPath), join(workspace.root,
|
|
684
|
+
const backendRelPath = relative(dirname(outputPath), join(workspace.root, backendApp.path));
|
|
723
685
|
const clientContent = `/**
|
|
724
686
|
* Auto-generated API client for ${backendAppName}
|
|
725
687
|
* Generated from: ${backendRelPath}
|
|
@@ -727,43 +689,31 @@ async function generateClientForFrontend(workspace, frontendAppName, options = {
|
|
|
727
689
|
* DO NOT EDIT - This file is automatically regenerated when backend schemas change.
|
|
728
690
|
*/
|
|
729
691
|
|
|
730
|
-
${
|
|
692
|
+
${content}
|
|
731
693
|
`;
|
|
732
694
|
await writeFile(outputPath, clientContent);
|
|
733
|
-
specHashCache.set(cacheKey, newHash);
|
|
734
695
|
result.outputPath = outputPath;
|
|
735
|
-
result.
|
|
736
|
-
|
|
696
|
+
result.success = true;
|
|
697
|
+
log(`📦 Copied client to ${frontendAppName} from ${backendAppName} (${endpointCount} endpoints)`);
|
|
737
698
|
} catch (error) {
|
|
738
|
-
result.
|
|
739
|
-
results.push(result);
|
|
699
|
+
result.error = error.message;
|
|
740
700
|
}
|
|
701
|
+
results.push(result);
|
|
741
702
|
}
|
|
742
703
|
return results;
|
|
743
704
|
}
|
|
744
705
|
/**
|
|
745
|
-
*
|
|
706
|
+
* Copy clients from all backends to their dependent frontends.
|
|
707
|
+
* Useful for initial setup or force refresh.
|
|
746
708
|
*/
|
|
747
|
-
async function
|
|
748
|
-
const log = options.silent ? () => {} : logger$10.log.bind(logger$10);
|
|
709
|
+
async function copyAllClients(workspace, options = {}) {
|
|
749
710
|
const allResults = [];
|
|
750
|
-
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "
|
|
751
|
-
const results = await
|
|
752
|
-
|
|
753
|
-
if (result.generated) log(`📦 Generated client for ${result.frontendApp} from ${result.backendApp} (${result.endpointCount} endpoints)`);
|
|
754
|
-
allResults.push(result);
|
|
755
|
-
}
|
|
711
|
+
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "backend" && app.routes) {
|
|
712
|
+
const results = await copyClientToFrontends(workspace, appName, options);
|
|
713
|
+
allResults.push(...results);
|
|
756
714
|
}
|
|
757
715
|
return allResults;
|
|
758
716
|
}
|
|
759
|
-
/**
|
|
760
|
-
* Get frontend apps that depend on a backend app.
|
|
761
|
-
*/
|
|
762
|
-
function getDependentFrontends(workspace, backendAppName) {
|
|
763
|
-
const dependentApps = [];
|
|
764
|
-
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.includes(backendAppName)) dependentApps.push(appName);
|
|
765
|
-
return dependentApps;
|
|
766
|
-
}
|
|
767
717
|
|
|
768
718
|
//#endregion
|
|
769
719
|
//#region src/dev/index.ts
|
|
@@ -1263,10 +1213,10 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1263
1213
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1264
1214
|
logger$9.log("✅ Frontend apps validated");
|
|
1265
1215
|
}
|
|
1266
|
-
if (frontendApps.length > 0) {
|
|
1267
|
-
const clientResults = await
|
|
1268
|
-
const
|
|
1269
|
-
if (
|
|
1216
|
+
if (frontendApps.length > 0 && backendApps.length > 0) {
|
|
1217
|
+
const clientResults = await copyAllClients(workspace);
|
|
1218
|
+
const copiedCount = clientResults.filter((r) => r.success).length;
|
|
1219
|
+
if (copiedCount > 0) logger$9.log(`\n📦 Copied ${copiedCount} API client(s)`);
|
|
1270
1220
|
}
|
|
1271
1221
|
await startWorkspaceServices(workspace);
|
|
1272
1222
|
const secretsEnv = await loadDevSecrets(workspace);
|
|
@@ -1327,65 +1277,42 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1327
1277
|
stdio: "inherit",
|
|
1328
1278
|
env: turboEnv
|
|
1329
1279
|
});
|
|
1330
|
-
let
|
|
1280
|
+
let openApiWatcher = null;
|
|
1331
1281
|
if (frontendApps.length > 0 && backendApps.length > 0) {
|
|
1332
|
-
const
|
|
1333
|
-
const
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
const patternKey = join(app.path, routePattern);
|
|
1340
|
-
const existing = backendRouteMap.get(patternKey) || [];
|
|
1341
|
-
backendRouteMap.set(patternKey, [...existing, appName]);
|
|
1342
|
-
}
|
|
1282
|
+
const openApiPaths = [];
|
|
1283
|
+
for (const [appName] of backendApps) {
|
|
1284
|
+
const openApiPath = getBackendOpenApiPath(workspace, appName);
|
|
1285
|
+
if (openApiPath) openApiPaths.push({
|
|
1286
|
+
path: openApiPath,
|
|
1287
|
+
appName
|
|
1288
|
+
});
|
|
1343
1289
|
}
|
|
1344
|
-
if (
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1290
|
+
if (openApiPaths.length > 0) {
|
|
1291
|
+
logger$9.log(`\n👀 Watching ${openApiPaths.length} backend OpenAPI spec(s) for changes`);
|
|
1292
|
+
const pathToApp = new Map(openApiPaths.map((p) => [p.path, p.appName]));
|
|
1293
|
+
openApiWatcher = chokidar.watch(openApiPaths.map((p) => p.path), {
|
|
1294
|
+
persistent: true,
|
|
1295
|
+
ignoreInitial: true,
|
|
1296
|
+
depth: 0
|
|
1349
1297
|
});
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
break;
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
if (changedBackends.length === 0) return;
|
|
1373
|
-
const affectedFrontends = /* @__PURE__ */ new Set();
|
|
1374
|
-
for (const backend of changedBackends) {
|
|
1375
|
-
const dependents = getDependentFrontends(workspace, backend);
|
|
1376
|
-
for (const frontend of dependents) affectedFrontends.add(frontend);
|
|
1377
|
-
}
|
|
1378
|
-
if (affectedFrontends.size === 0) return;
|
|
1379
|
-
logger$9.log(`\n🔄 Detected schema change in ${changedBackends.join(", ")}`);
|
|
1380
|
-
for (const frontend of affectedFrontends) try {
|
|
1381
|
-
const results = await generateClientForFrontend(workspace, frontend);
|
|
1382
|
-
for (const result of results) if (result.generated) logger$9.log(` 📦 Regenerated client for ${result.frontendApp} (${result.endpointCount} endpoints)`);
|
|
1383
|
-
} catch (error) {
|
|
1384
|
-
logger$9.error(` ❌ Failed to regenerate client for ${frontend}: ${error.message}`);
|
|
1385
|
-
}
|
|
1386
|
-
}, 500);
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1298
|
+
let copyTimeout = null;
|
|
1299
|
+
const handleChange = async (changedPath) => {
|
|
1300
|
+
if (copyTimeout) clearTimeout(copyTimeout);
|
|
1301
|
+
copyTimeout = setTimeout(async () => {
|
|
1302
|
+
const backendAppName = pathToApp.get(changedPath);
|
|
1303
|
+
if (!backendAppName) return;
|
|
1304
|
+
logger$9.log(`\n🔄 OpenAPI spec changed for ${backendAppName}`);
|
|
1305
|
+
try {
|
|
1306
|
+
const results = await copyClientToFrontends(workspace, backendAppName, { silent: true });
|
|
1307
|
+
for (const result of results) if (result.success) logger$9.log(` 📦 Copied client to ${result.frontendApp} (${result.endpointCount} endpoints)`);
|
|
1308
|
+
else if (result.error) logger$9.error(` ❌ Failed to copy client to ${result.frontendApp}: ${result.error}`);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
logger$9.error(` ❌ Failed to copy clients: ${error.message}`);
|
|
1311
|
+
}
|
|
1312
|
+
}, 200);
|
|
1313
|
+
};
|
|
1314
|
+
openApiWatcher.on("change", handleChange);
|
|
1315
|
+
openApiWatcher.on("add", handleChange);
|
|
1389
1316
|
}
|
|
1390
1317
|
}
|
|
1391
1318
|
let isShuttingDown = false;
|
|
@@ -1393,7 +1320,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1393
1320
|
if (isShuttingDown) return;
|
|
1394
1321
|
isShuttingDown = true;
|
|
1395
1322
|
logger$9.log("\n🛑 Shutting down workspace...");
|
|
1396
|
-
if (
|
|
1323
|
+
if (openApiWatcher) openApiWatcher.close().catch(() => {});
|
|
1397
1324
|
if (turboProcess.pid) try {
|
|
1398
1325
|
process.kill(-turboProcess.pid, "SIGTERM");
|
|
1399
1326
|
} catch {
|
|
@@ -1411,7 +1338,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1411
1338
|
reject(error);
|
|
1412
1339
|
});
|
|
1413
1340
|
turboProcess.on("exit", (code) => {
|
|
1414
|
-
if (
|
|
1341
|
+
if (openApiWatcher) openApiWatcher.close().catch(() => {});
|
|
1415
1342
|
if (code !== null && code !== 0) reject(new Error(`Turbo exited with code ${code}`));
|
|
1416
1343
|
else resolve$1();
|
|
1417
1344
|
});
|