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