@geekmidas/cli 10.0.0-alpha.6 → 10.0.0-alpha.9
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/README.md +12 -8
- package/bin/adjacent-tsconfig.mjs +59 -0
- package/bin/gkm.mjs +7 -1
- package/dist/{config-CNz5KaSR.cjs → config-BZ1F0WuA.cjs} +6 -50
- package/dist/config-BZ1F0WuA.cjs.map +1 -0
- package/dist/{config-CxP7P-qG.mjs → config-DVVph9yz.mjs} +7 -45
- package/dist/config-DVVph9yz.mjs.map +1 -0
- 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/{credentials-DzsXZzkx.cjs → credentials-Xamqe_NJ.cjs} +19 -18
- package/dist/credentials-Xamqe_NJ.cjs.map +1 -0
- package/dist/{credentials-BK3Z93MX.mjs → credentials-dP8vRVRv.mjs} +19 -18
- package/dist/credentials-dP8vRVRv.mjs.map +1 -0
- package/dist/discover-CAlFgYec.mjs.map +1 -1
- package/dist/discover-DTmFeloL.cjs.map +1 -1
- package/dist/{index-BGGdjueF.d.cts → index-C6e-ULLx.d.mts} +2 -2
- package/dist/index-C6e-ULLx.d.mts.map +1 -0
- package/dist/{index-DEbaleny.d.mts → index-CR5urhWX.d.cts} +2 -2
- package/dist/index-CR5urhWX.d.cts.map +1 -0
- package/dist/index.cjs +596 -248
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +596 -248
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-D-dx9Akj.cjs → openapi-SjsgARie.cjs} +4 -4
- package/dist/{openapi-D-dx9Akj.cjs.map → openapi-SjsgARie.cjs.map} +1 -1
- package/dist/{openapi-Bw8DWFWz.mjs → openapi-Xx0A7Wg0.mjs} +4 -4
- package/dist/{openapi-Bw8DWFWz.mjs.map → openapi-Xx0A7Wg0.mjs.map} +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +3 -3
- package/dist/openapi.d.mts +3 -3
- package/dist/openapi.mjs +3 -3
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{setup-DGIPd2fh.mjs → setup-BA8ciTPU.mjs} +4 -4
- package/dist/setup-BsZvZP6f.cjs +11 -0
- package/dist/{setup-BWMhbW-p.mjs → setup-C8KOx8_8.mjs} +5 -5
- package/dist/setup-C8KOx8_8.mjs.map +1 -0
- package/dist/{setup-D7L4BgBT.cjs → setup-hC6hI86f.cjs} +5 -5
- package/dist/setup-hC6hI86f.cjs.map +1 -0
- package/dist/{types-BLSNdrk0.d.cts → types-6pkp2RRj.d.mts} +48 -4
- package/dist/types-6pkp2RRj.d.mts.map +1 -0
- package/dist/{types-C3POXhw3.d.mts → types-Y3fKQSAK.d.cts} +45 -1
- package/dist/types-Y3fKQSAK.d.cts.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-BzrG_2SI.mjs +8 -0
- package/dist/{workspace-LKiNyaDy.cjs → workspace-C6VJQVtG.cjs} +79 -1
- package/dist/workspace-C6VJQVtG.cjs.map +1 -0
- package/dist/{workspace-CGaUAw5A.cjs → workspace-XXtNhqJl.cjs} +3 -3
- package/dist/{workspace-DIuZx-cc.mjs → workspace-zoqRbihZ.mjs} +62 -2
- package/dist/workspace-zoqRbihZ.mjs.map +1 -0
- package/package.json +14 -13
- package/dist/config-CNz5KaSR.cjs.map +0 -1
- package/dist/config-CxP7P-qG.mjs.map +0 -1
- package/dist/credentials-BK3Z93MX.mjs.map +0 -1
- package/dist/credentials-DzsXZzkx.cjs.map +0 -1
- package/dist/index-BGGdjueF.d.cts.map +0 -1
- package/dist/index-DEbaleny.d.mts.map +0 -1
- package/dist/setup-8IWPsRIN.cjs +0 -11
- package/dist/setup-BWMhbW-p.mjs.map +0 -1
- package/dist/setup-D7L4BgBT.cjs.map +0 -1
- package/dist/types-BLSNdrk0.d.cts.map +0 -1
- package/dist/types-C3POXhw3.d.mts.map +0 -1
- package/dist/workspace-CnTqIlB3.mjs +0 -8
- package/dist/workspace-DIuZx-cc.mjs.map +0 -1
- package/dist/workspace-LKiNyaDy.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
3
|
const require_discover = require('./discover-DTmFeloL.cjs');
|
|
4
|
-
const require_config = require('./config-
|
|
5
|
-
const require_workspace = require('./workspace-
|
|
4
|
+
const require_config = require('./config-BZ1F0WuA.cjs');
|
|
5
|
+
const require_workspace = require('./workspace-C6VJQVtG.cjs');
|
|
6
6
|
const require_credentials = require('./credentials-BapzjyV5.cjs');
|
|
7
|
-
const require_credentials$1 = require('./credentials-
|
|
7
|
+
const require_credentials$1 = require('./credentials-Xamqe_NJ.cjs');
|
|
8
8
|
const require_generator = require('./generator-CNzBw7g5.cjs');
|
|
9
9
|
const require_storage = require('./storage-BJmzYxt8.cjs');
|
|
10
|
-
const require_openapi = require('./openapi-
|
|
10
|
+
const require_openapi = require('./openapi-SjsgARie.cjs');
|
|
11
11
|
const require_emit = require('./emit-CbcRolsa.cjs');
|
|
12
12
|
const require_dokploy_api = require('./dokploy-api-BsVH0y_s.cjs');
|
|
13
13
|
const require_encryption = require('./encryption-C6jaU0hE.cjs');
|
|
@@ -15,7 +15,7 @@ const require_CachedStateProvider = require('./CachedStateProvider-CqPdW7CB.cjs'
|
|
|
15
15
|
const require_fullstack_secrets = require('./fullstack-secrets-C-GGohke.cjs');
|
|
16
16
|
const require_openapi_react_query = require('./openapi-react-query-CmJ1xDbn.cjs');
|
|
17
17
|
require('./sync-SizaChmC.cjs');
|
|
18
|
-
const require_setup = require('./setup-
|
|
18
|
+
const require_setup = require('./setup-hC6hI86f.cjs');
|
|
19
19
|
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
20
20
|
const node_path = require_chunk.__toESM(require("node:path"));
|
|
21
21
|
const __geekmidas_manifest = require_chunk.__toESM(require("@geekmidas/manifest"));
|
|
@@ -43,7 +43,7 @@ const prompts = require_chunk.__toESM(require("prompts"));
|
|
|
43
43
|
|
|
44
44
|
//#region package.json
|
|
45
45
|
var name = "@geekmidas/cli";
|
|
46
|
-
var version = "10.0.0-alpha.
|
|
46
|
+
var version = "10.0.0-alpha.9";
|
|
47
47
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
48
48
|
var private$1 = false;
|
|
49
49
|
var type = "module";
|
|
@@ -143,6 +143,7 @@ var dependencies = {
|
|
|
143
143
|
"dotenv": "~18.0.1",
|
|
144
144
|
"esbuild": "~0.28.2",
|
|
145
145
|
"fast-glob": "~3.3.3",
|
|
146
|
+
"get-tsconfig": "~4.10.1",
|
|
146
147
|
"hono": "~4.13.8",
|
|
147
148
|
"lodash.kebabcase": "~4.1.1",
|
|
148
149
|
"openapi-typescript": "~7.13.0",
|
|
@@ -157,8 +158,8 @@ var devDependencies = {
|
|
|
157
158
|
"@types/node": "~24.9.1",
|
|
158
159
|
"@types/pg": "~8.23.1",
|
|
159
160
|
"@types/prompts": "~2.4.9",
|
|
160
|
-
"typescript": "
|
|
161
|
-
"vitest": "
|
|
161
|
+
"typescript": "~5.8.2",
|
|
162
|
+
"vitest": "~3.2.4",
|
|
162
163
|
"zod": "~4.6.5"
|
|
163
164
|
};
|
|
164
165
|
var peerDependencies = { "@geekmidas/telescope": "workspace:~" };
|
|
@@ -1377,9 +1378,9 @@ async function devCommand(options) {
|
|
|
1377
1378
|
if (enableOpenApi) await require_openapi.generateOpenApiFrom(config.constructs, { openapi: config.openapi });
|
|
1378
1379
|
const runtime = config.runtime ?? "node";
|
|
1379
1380
|
let secretsJsonPath;
|
|
1380
|
-
const appSecrets = await require_credentials$1.loadSecretsForApp(secretsRoot, workspaceAppName);
|
|
1381
|
+
const appSecrets = await require_credentials$1.loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
|
|
1381
1382
|
if (workspace && require_credentials$1.usesConstructs(workspace)) {
|
|
1382
|
-
const reconciled = await require_credentials$1.reconcileWorkspace(workspace, { stage:
|
|
1383
|
+
const reconciled = await require_credentials$1.reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
1383
1384
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
1384
1385
|
logger$9.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
1385
1386
|
for (const [container, address] of Object.entries(reconciled.addresses)) logger$9.log(` ${container}: ${address}`);
|
|
@@ -1661,15 +1662,15 @@ async function validateFrontendApps(workspace) {
|
|
|
1661
1662
|
*/
|
|
1662
1663
|
async function loadDevSecrets(workspace) {
|
|
1663
1664
|
if (!workspace.secrets.enabled) return {};
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1665
|
+
const stage = workspace.stages.local;
|
|
1666
|
+
if (require_storage.secretsExist(stage, workspace.root)) {
|
|
1666
1667
|
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
1667
1668
|
if (secrets) {
|
|
1668
1669
|
logger$9.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1669
1670
|
return require_storage.toEmbeddableSecrets(secrets);
|
|
1670
1671
|
}
|
|
1671
1672
|
}
|
|
1672
|
-
logger$9.warn(
|
|
1673
|
+
logger$9.warn(`⚠️ Secrets enabled but no "${stage}" secrets found. Run "gkm setup" to initialize the local stage`);
|
|
1673
1674
|
return {};
|
|
1674
1675
|
}
|
|
1675
1676
|
/**
|
|
@@ -1713,7 +1714,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1713
1714
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
1714
1715
|
let secretsEnv;
|
|
1715
1716
|
if (require_credentials$1.usesConstructs(workspace)) {
|
|
1716
|
-
const reconciled = await require_credentials$1.reconcileWorkspace(workspace, { stage:
|
|
1717
|
+
const reconciled = await require_credentials$1.reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
1717
1718
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
1718
1719
|
logger$9.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
1719
1720
|
for (const [container, address] of Object.entries(reconciled.addresses)) logger$9.log(` ${container}: ${address}`);
|
|
@@ -2458,6 +2459,7 @@ function rootGkmConfig(workspace) {
|
|
|
2458
2459
|
const own = only ? require_workspace.getAppGkmConfig(workspace, only[0]) : void 0;
|
|
2459
2460
|
return own ?? {
|
|
2460
2461
|
services: workspace.services,
|
|
2462
|
+
stages: workspace.stages,
|
|
2461
2463
|
constructs: require_workspace.allConstructGlobs(workspace)
|
|
2462
2464
|
};
|
|
2463
2465
|
}
|
|
@@ -2465,9 +2467,9 @@ async function buildCommand(options) {
|
|
|
2465
2467
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
2466
2468
|
if (loadedConfig.type === "workspace") {
|
|
2467
2469
|
const cwd = (0, node_path.resolve)(process.cwd());
|
|
2468
|
-
const workspaceRoot = (0, node_path.resolve)(loadedConfig.workspace.root);
|
|
2469
|
-
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2470
|
-
const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => (0, node_path.resolve)(workspaceRoot, app.path) !== workspaceRoot);
|
|
2470
|
+
const workspaceRoot$1 = (0, node_path.resolve)(loadedConfig.workspace.root);
|
|
2471
|
+
const isAtWorkspaceRoot = cwd === workspaceRoot$1;
|
|
2472
|
+
const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => (0, node_path.resolve)(workspaceRoot$1, app.path) !== workspaceRoot$1);
|
|
2471
2473
|
if (isAtWorkspaceRoot && someAppIsElsewhere) {
|
|
2472
2474
|
logger$7.log("📦 Detected workspace configuration");
|
|
2473
2475
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
@@ -2506,7 +2508,9 @@ async function buildCommand(options) {
|
|
|
2506
2508
|
sources: constructSources
|
|
2507
2509
|
}) : {};
|
|
2508
2510
|
const surfaces = Object.values(declared).filter((d) => d.kind === "rest-api");
|
|
2509
|
-
const
|
|
2511
|
+
const workspaceRoot = loadedConfig.workspace.root;
|
|
2512
|
+
const served = surfaces.find((d) => (0, node_path.resolve)(workspaceRoot, d.path) === (0, node_path.resolve)(process.cwd()));
|
|
2513
|
+
const primary = served ?? surfaces.find((d) => d.endpoints.length === 0) ?? surfaces[0];
|
|
2510
2514
|
const browsable = Object.entries(declared).find(([, d]) => d.kind === "database");
|
|
2511
2515
|
const browsableSource = browsable ? constructSources[browsable[0]] : void 0;
|
|
2512
2516
|
const owners = {};
|
|
@@ -2557,7 +2561,7 @@ async function buildCommand(options) {
|
|
|
2557
2561
|
const queueGenerator = new QueueGenerator();
|
|
2558
2562
|
const topicGenerator = new TopicGenerator();
|
|
2559
2563
|
const code = constructGlobs$1;
|
|
2560
|
-
const [
|
|
2564
|
+
const [loadedEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics] = await Promise.all([
|
|
2561
2565
|
endpointGenerator.load(code),
|
|
2562
2566
|
functionGenerator.load(code),
|
|
2563
2567
|
cronGenerator.load(code),
|
|
@@ -2565,6 +2569,7 @@ async function buildCommand(options) {
|
|
|
2565
2569
|
queueGenerator.load(code),
|
|
2566
2570
|
topicGenerator.load(code)
|
|
2567
2571
|
]);
|
|
2572
|
+
const allEndpoints = primary ? loadedEndpoints.filter(({ construct }) => !construct.surface || construct.surface.id === primary.id) : loadedEndpoints;
|
|
2568
2573
|
logger$7.log(`Found ${allEndpoints.length} endpoints`);
|
|
2569
2574
|
logger$7.log(`Found ${allFunctions.length} functions`);
|
|
2570
2575
|
logger$7.log(`Found ${allCrons.length} crons`);
|
|
@@ -2572,7 +2577,7 @@ async function buildCommand(options) {
|
|
|
2572
2577
|
logger$7.log(`Found ${allQueues.length} queues`);
|
|
2573
2578
|
logger$7.log(`Found ${allTopics.length} topics`);
|
|
2574
2579
|
if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
2575
|
-
const selfServing =
|
|
2580
|
+
const selfServing = served && served.endpoints.length > 0 && constructSources[served.id] !== void 0 ? [served.id, served] : void 0;
|
|
2576
2581
|
if (selfServing) {
|
|
2577
2582
|
const [id] = selfServing;
|
|
2578
2583
|
await writeSurfaceEntry((0, node_path.join)(process.cwd(), ".gkm", "server"), constructSources[id]);
|
|
@@ -2843,7 +2848,6 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2843
2848
|
});
|
|
2844
2849
|
}
|
|
2845
2850
|
logger$7.log(`\n✅ Workspace build complete!`);
|
|
2846
|
-
await require_openapi.openapiCommand({ cwd: workspace.root });
|
|
2847
2851
|
logger$7.log(`\n📋 Build Summary:`);
|
|
2848
2852
|
for (const result of results) {
|
|
2849
2853
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
@@ -5693,7 +5697,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
5693
5697
|
logger$5.log(`\n Generated: .dockerignore (workspace root)`);
|
|
5694
5698
|
const dockerCompose = generateWorkspaceCompose(workspace, {
|
|
5695
5699
|
registry: options.registry,
|
|
5696
|
-
containers: await require_credentials$1.derivedContainers(workspace,
|
|
5700
|
+
containers: await require_credentials$1.derivedContainers(workspace, workspace.stages.local)
|
|
5697
5701
|
});
|
|
5698
5702
|
const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
|
|
5699
5703
|
await (0, node_fs_promises.writeFile)(composePath, dockerCompose);
|
|
@@ -7139,6 +7143,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7139
7143
|
*/
|
|
7140
7144
|
async function deployCommand(options) {
|
|
7141
7145
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
7146
|
+
require_workspace.assertDeployedStage(loadedConfig.workspace.stages, options.stage);
|
|
7142
7147
|
return workspaceDeployCommand(loadedConfig.workspace, options);
|
|
7143
7148
|
}
|
|
7144
7149
|
|
|
@@ -7514,47 +7519,47 @@ const CONSTRUCTS_GLOB = "./src/constructs/**/*.ts";
|
|
|
7514
7519
|
*/
|
|
7515
7520
|
const WORKSPACE_CONSTRUCTS_GLOB = "./constructs/**/*.ts";
|
|
7516
7521
|
/**
|
|
7517
|
-
*
|
|
7522
|
+
* Where each routes structure puts an app's handlers, relative to the app.
|
|
7518
7523
|
*
|
|
7519
|
-
*
|
|
7520
|
-
*
|
|
7524
|
+
* `.ts` only, never `.tsx`: a TanStack site keeps its own `src/routes/`, and
|
|
7525
|
+
* this glob must not import a frontend's route components.
|
|
7521
7526
|
*/
|
|
7522
|
-
function
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
ownerUrlKey: (0, __geekmidas_manifest.provideKey)(id, "ownerUrl")
|
|
7529
|
-
};
|
|
7530
|
-
}
|
|
7531
|
-
/** The bucket a project named `name` declares. */
|
|
7532
|
-
function storageFor(name$1) {
|
|
7533
|
-
const id = (0, __geekmidas_manifest.canonicalId)(`${name$1}-uploads`);
|
|
7534
|
-
return {
|
|
7535
|
-
id,
|
|
7536
|
-
service: (0, __geekmidas_manifest.serviceKey)(id),
|
|
7537
|
-
urlKey: (0, __geekmidas_manifest.provideKey)(id, "url")
|
|
7538
|
-
};
|
|
7527
|
+
function routesGlob(structure) {
|
|
7528
|
+
switch (structure) {
|
|
7529
|
+
case "centralized-endpoints": return "./src/endpoints/**/*.ts";
|
|
7530
|
+
case "centralized-routes": return "./src/routes/**/*.ts";
|
|
7531
|
+
case "domain-based": return "./src/**/routes/*.ts";
|
|
7532
|
+
}
|
|
7539
7533
|
}
|
|
7540
|
-
/**
|
|
7541
|
-
|
|
7542
|
-
|
|
7534
|
+
/**
|
|
7535
|
+
* Plain ids — `Database`, not `Beetlefit`.
|
|
7536
|
+
*
|
|
7537
|
+
* The workspace `name` already scopes every physical name
|
|
7538
|
+
* (`production-beetlefit-database`), so an id that carried the project too
|
|
7539
|
+
* would say it twice: `production-beetlefit-beetlefit-cache`. Within a
|
|
7540
|
+
* workspace the constructs are declared once, at its root, so there is no
|
|
7541
|
+
* second app's `Database` to collide with.
|
|
7542
|
+
*/
|
|
7543
|
+
function scaffolded(id) {
|
|
7543
7544
|
return {
|
|
7544
7545
|
id,
|
|
7545
7546
|
service: (0, __geekmidas_manifest.serviceKey)(id),
|
|
7546
7547
|
urlKey: (0, __geekmidas_manifest.provideKey)(id, "url")
|
|
7547
7548
|
};
|
|
7548
7549
|
}
|
|
7549
|
-
/** The
|
|
7550
|
-
function
|
|
7551
|
-
const id = (0, __geekmidas_manifest.canonicalId)(`${name$1}-cache`);
|
|
7550
|
+
/** The database a project declares. */
|
|
7551
|
+
function databaseFor() {
|
|
7552
7552
|
return {
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
urlKey: (0, __geekmidas_manifest.provideKey)(id, "url")
|
|
7553
|
+
...scaffolded("Database"),
|
|
7554
|
+
ownerUrlKey: (0, __geekmidas_manifest.provideKey)("Database", "ownerUrl")
|
|
7556
7555
|
};
|
|
7557
7556
|
}
|
|
7557
|
+
/** The bucket a project declares. */
|
|
7558
|
+
const storageFor = () => scaffolded("Uploads");
|
|
7559
|
+
/** The mail sender a project declares. */
|
|
7560
|
+
const emailFor = () => scaffolded("Mail");
|
|
7561
|
+
/** The cache a project declares. */
|
|
7562
|
+
const cacheFor = () => scaffolded("Cache");
|
|
7558
7563
|
/**
|
|
7559
7564
|
* The files a project's declared database needs.
|
|
7560
7565
|
*
|
|
@@ -7562,8 +7567,8 @@ function cacheFor(name$1) {
|
|
|
7562
7567
|
* the same way — and so the schema type, the migration, and the id the test
|
|
7563
7568
|
* setup reads all come from one place.
|
|
7564
7569
|
*/
|
|
7565
|
-
function databaseFiles(
|
|
7566
|
-
const db = databaseFor(
|
|
7570
|
+
function databaseFiles() {
|
|
7571
|
+
const db = databaseFor();
|
|
7567
7572
|
return [{
|
|
7568
7573
|
path: "src/constructs/database.ts",
|
|
7569
7574
|
content: `import { KyselyDatabase } from '@geekmidas/constructs/database/kysely';
|
|
@@ -7592,7 +7597,16 @@ export interface Database {
|
|
|
7592
7597
|
*/
|
|
7593
7598
|
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}');
|
|
7594
7599
|
`
|
|
7595
|
-
},
|
|
7600
|
+
}, usersMigration()];
|
|
7601
|
+
}
|
|
7602
|
+
/**
|
|
7603
|
+
* The table the scaffolded endpoints and factories expect.
|
|
7604
|
+
*
|
|
7605
|
+
* Its own function because a workspace's API needs it without the database
|
|
7606
|
+
* construct beside it — that one lives at the workspace root.
|
|
7607
|
+
*/
|
|
7608
|
+
function usersMigration() {
|
|
7609
|
+
return {
|
|
7596
7610
|
path: "src/db/migrations/001_create_users.ts",
|
|
7597
7611
|
content: `import type { Kysely } from 'kysely';
|
|
7598
7612
|
|
|
@@ -7620,7 +7634,18 @@ export async function down(db: Kysely<unknown>): Promise<void> {
|
|
|
7620
7634
|
await db.schema.dropTable('users').execute();
|
|
7621
7635
|
}
|
|
7622
7636
|
`
|
|
7623
|
-
}
|
|
7637
|
+
};
|
|
7638
|
+
}
|
|
7639
|
+
/**
|
|
7640
|
+
* What a workspace's `constructs` glob reaches: the root constructs, and the
|
|
7641
|
+
* handlers of every app laid out the way `init` was told.
|
|
7642
|
+
*
|
|
7643
|
+
* Every app in `appsDir` (`apps/*`) rather than the API's own directory.
|
|
7644
|
+
* Endpoints are split by the surface they were built from, so naming one app
|
|
7645
|
+
* here would only decide which apps' endpoints are silently never loaded.
|
|
7646
|
+
*/
|
|
7647
|
+
function workspaceConstructsGlobs(structure, appsDir) {
|
|
7648
|
+
return [WORKSPACE_CONSTRUCTS_GLOB, `./${appsDir}/*/${routesGlob(structure).replace(/^\.\//, "")}`];
|
|
7624
7649
|
}
|
|
7625
7650
|
|
|
7626
7651
|
//#endregion
|
|
@@ -7700,6 +7725,7 @@ what the OpenAPI spec says, which env vars a process needs.
|
|
|
7700
7725
|
\`\`\`typescript
|
|
7701
7726
|
export default defineWorkspace({
|
|
7702
7727
|
name: '${name$1}',
|
|
7728
|
+
stages: { local: 'dev', deployed: ['staging', 'prod'], protected: ['prod'] },
|
|
7703
7729
|
constructs: '${constructsGlob}',
|
|
7704
7730
|
secrets: { enabled: true },
|
|
7705
7731
|
});
|
|
@@ -7954,6 +7980,30 @@ ${database ? "gkm test # starts services, injects secrets, then runs vites
|
|
|
7954
7980
|
Run tests through \`gkm test\`, not \`vitest\` directly${database ? " — it is what starts the database and injects the secrets the suite needs" : ""}.`;
|
|
7955
7981
|
}
|
|
7956
7982
|
|
|
7983
|
+
//#endregion
|
|
7984
|
+
//#region src/init/dependencies.ts
|
|
7985
|
+
/**
|
|
7986
|
+
* Third-party ranges a scaffold installs beside the `@geekmidas` packages.
|
|
7987
|
+
*
|
|
7988
|
+
* Held to the peer ranges those packages declare, in one place, because a
|
|
7989
|
+
* template that pinned its own copy drifted below them: `kysely ~0.28` against
|
|
7990
|
+
* a `~0.29.6` peer is two copies of Kysely and a `Kysely<DB>` from one that is
|
|
7991
|
+
* not assignable to the other's.
|
|
7992
|
+
*
|
|
7993
|
+
* Hand-maintained, unlike `versions.ts`, which `sync-versions` rewrites whole.
|
|
7994
|
+
*/
|
|
7995
|
+
const DEPENDENCY_VERSIONS = {
|
|
7996
|
+
"@tanstack/react-query": "~5.80.0",
|
|
7997
|
+
"better-auth": "~1.7.5",
|
|
7998
|
+
hono: "~4.13.8",
|
|
7999
|
+
kysely: "~0.29.6",
|
|
8000
|
+
"kysely-ctl": "~0.21.0",
|
|
8001
|
+
pg: "~8.23.0",
|
|
8002
|
+
pino: "~10.3.1",
|
|
8003
|
+
sst: "~4.17.1",
|
|
8004
|
+
zod: "~4.6.5"
|
|
8005
|
+
};
|
|
8006
|
+
|
|
7957
8007
|
//#endregion
|
|
7958
8008
|
//#region src/init/versions.ts
|
|
7959
8009
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
@@ -7976,26 +8026,26 @@ const CLI_VERSION = `~${pkg.version}`;
|
|
|
7976
8026
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
7977
8027
|
*/
|
|
7978
8028
|
const GEEKMIDAS_VERSIONS = {
|
|
7979
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
7980
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
7981
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
7982
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
7983
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
7984
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
7985
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
7986
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
7987
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
7988
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
7989
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
7990
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
7991
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
7992
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
7993
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
7994
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
7995
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
7996
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
7997
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
7998
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8029
|
+
"@geekmidas/audit": "~10.0.0-alpha.9",
|
|
8030
|
+
"@geekmidas/auth": "~10.0.0-alpha.9",
|
|
8031
|
+
"@geekmidas/cache": "~10.0.0-alpha.9",
|
|
8032
|
+
"@geekmidas/client": "~10.0.0-alpha.9",
|
|
8033
|
+
"@geekmidas/cloud": "~10.0.0-alpha.9",
|
|
8034
|
+
"@geekmidas/constructs": "~10.0.0-alpha.9",
|
|
8035
|
+
"@geekmidas/db": "~10.0.0-alpha.9",
|
|
8036
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.9",
|
|
8037
|
+
"@geekmidas/envkit": "~10.0.0-alpha.9",
|
|
8038
|
+
"@geekmidas/errors": "~10.0.0-alpha.9",
|
|
8039
|
+
"@geekmidas/events": "~10.0.0-alpha.9",
|
|
8040
|
+
"@geekmidas/logger": "~10.0.0-alpha.9",
|
|
8041
|
+
"@geekmidas/manifest": "~10.0.0-alpha.9",
|
|
8042
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.9",
|
|
8043
|
+
"@geekmidas/schema": "~10.0.0-alpha.9",
|
|
8044
|
+
"@geekmidas/services": "~10.0.0-alpha.9",
|
|
8045
|
+
"@geekmidas/storage": "~10.0.0-alpha.9",
|
|
8046
|
+
"@geekmidas/studio": "~10.0.0-alpha.9",
|
|
8047
|
+
"@geekmidas/telescope": "~10.0.0-alpha.9",
|
|
8048
|
+
"@geekmidas/testkit": "~10.0.0-alpha.9",
|
|
7999
8049
|
"@geekmidas/cli": CLI_VERSION
|
|
8000
8050
|
};
|
|
8001
8051
|
|
|
@@ -8015,27 +8065,28 @@ function generateAuthAppFiles(options) {
|
|
|
8015
8065
|
private: true,
|
|
8016
8066
|
type: "module",
|
|
8017
8067
|
scripts: {
|
|
8018
|
-
dev: "gkm dev
|
|
8019
|
-
build: "
|
|
8020
|
-
start: "node dist/index.js",
|
|
8021
|
-
typecheck: "tsc --noEmit",
|
|
8068
|
+
dev: "gkm dev",
|
|
8069
|
+
build: "gkm build",
|
|
8022
8070
|
"db:migrate": "gkm exec -- npx @better-auth/cli migrate",
|
|
8023
8071
|
"db:generate": "gkm exec -- npx @better-auth/cli generate"
|
|
8024
8072
|
},
|
|
8025
8073
|
dependencies: {
|
|
8026
8074
|
[modelsPackage]: "workspace:*",
|
|
8075
|
+
"@geekmidas/auth": GEEKMIDAS_VERSIONS["@geekmidas/auth"],
|
|
8076
|
+
"@geekmidas/constructs": GEEKMIDAS_VERSIONS["@geekmidas/constructs"],
|
|
8077
|
+
"@geekmidas/db": GEEKMIDAS_VERSIONS["@geekmidas/db"],
|
|
8027
8078
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
8028
8079
|
"@geekmidas/logger": GEEKMIDAS_VERSIONS["@geekmidas/logger"],
|
|
8029
|
-
"@hono/node-server": "~1.
|
|
8030
|
-
"better-auth": "
|
|
8031
|
-
hono:
|
|
8032
|
-
kysely:
|
|
8033
|
-
pg:
|
|
8080
|
+
"@hono/node-server": "~1.14.1",
|
|
8081
|
+
"better-auth": DEPENDENCY_VERSIONS["better-auth"],
|
|
8082
|
+
hono: DEPENDENCY_VERSIONS.hono,
|
|
8083
|
+
kysely: DEPENDENCY_VERSIONS.kysely,
|
|
8084
|
+
pg: DEPENDENCY_VERSIONS.pg
|
|
8034
8085
|
},
|
|
8035
8086
|
devDependencies: {
|
|
8036
8087
|
"@geekmidas/cli": GEEKMIDAS_VERSIONS["@geekmidas/cli"],
|
|
8037
8088
|
"@types/node": "~22.0.0",
|
|
8038
|
-
"@types/pg": "~8.
|
|
8089
|
+
"@types/pg": "~8.23.1",
|
|
8039
8090
|
tsx: "~4.20.0",
|
|
8040
8091
|
typescript: "~5.8.2"
|
|
8041
8092
|
}
|
|
@@ -8075,6 +8126,30 @@ dist/
|
|
|
8075
8126
|
];
|
|
8076
8127
|
}
|
|
8077
8128
|
|
|
8129
|
+
//#endregion
|
|
8130
|
+
//#region src/init/generators/stages.ts
|
|
8131
|
+
/** The `stages` block a generated gkm.config.ts declares, indented two spaces. */
|
|
8132
|
+
function stagesBlock(stages) {
|
|
8133
|
+
const list = (names) => `[${names.map((name$1) => `'${name$1}'`).join(", ")}]`;
|
|
8134
|
+
return `
|
|
8135
|
+
// Named by this project and read by every command: \`gkm dev\`, \`exec\` and
|
|
8136
|
+
// \`test\` run as \`local\`, and \`gkm deploy --stage\` accepts only \`deployed\`.
|
|
8137
|
+
stages: {
|
|
8138
|
+
local: '${stages.local}',
|
|
8139
|
+
deployed: ${list(stages.deployed)},${stages.protected?.length ? `
|
|
8140
|
+
// Retained and protected on removal.
|
|
8141
|
+
protected: ${list(stages.protected)},` : ""}
|
|
8142
|
+
},`;
|
|
8143
|
+
}
|
|
8144
|
+
/**
|
|
8145
|
+
* The `STAGE` the scaffolded env config parses: one of the declared stages,
|
|
8146
|
+
* defaulting to the local one.
|
|
8147
|
+
*/
|
|
8148
|
+
function stageEnv(stages) {
|
|
8149
|
+
const names = [stages.local, ...stages.deployed].map((name$1) => `'${name$1}'`).join(", ");
|
|
8150
|
+
return `get('STAGE').enum([${names}]).default('${stages.local}')`;
|
|
8151
|
+
}
|
|
8152
|
+
|
|
8078
8153
|
//#endregion
|
|
8079
8154
|
//#region src/init/generators/config.ts
|
|
8080
8155
|
/**
|
|
@@ -8099,13 +8174,7 @@ function generateConfigFiles(options, template) {
|
|
|
8099
8174
|
const isServerless = template.name === "serverless";
|
|
8100
8175
|
const hasWorker = template.name === "worker";
|
|
8101
8176
|
const isFullstack = options.template === "fullstack";
|
|
8102
|
-
const getRoutesGlob = () =>
|
|
8103
|
-
switch (routesStructure) {
|
|
8104
|
-
case "centralized-endpoints": return "./src/endpoints/**/*.ts";
|
|
8105
|
-
case "centralized-routes": return "./src/routes/**/*.ts";
|
|
8106
|
-
case "domain-based": return "./src/**/routes/*.ts";
|
|
8107
|
-
}
|
|
8108
|
-
};
|
|
8177
|
+
const getRoutesGlob = () => routesGlob(routesStructure);
|
|
8109
8178
|
if (isFullstack) return generateSingleAppConfigFiles(options, template, {
|
|
8110
8179
|
telescope,
|
|
8111
8180
|
studio,
|
|
@@ -8116,7 +8185,7 @@ function generateConfigFiles(options, template) {
|
|
|
8116
8185
|
});
|
|
8117
8186
|
let gkmConfig = `import { defineConfig } from '@geekmidas/cli/config';
|
|
8118
8187
|
|
|
8119
|
-
export default defineConfig({${options.monorepo ? "" : `
|
|
8188
|
+
export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ? "" : `
|
|
8120
8189
|
// One glob, every kind. What reconcile reads to derive the containers this
|
|
8121
8190
|
// app needs: a declared database is why a Postgres exists at all, so nothing
|
|
8122
8191
|
// lists \`postgres\` anywhere.
|
|
@@ -8158,21 +8227,7 @@ export default defineConfig({${options.monorepo ? "" : `
|
|
|
8158
8227
|
gkmConfig += `
|
|
8159
8228
|
});
|
|
8160
8229
|
`;
|
|
8161
|
-
const tsConfig = options.monorepo ? {
|
|
8162
|
-
extends: "../../tsconfig.json",
|
|
8163
|
-
compilerOptions: {
|
|
8164
|
-
noEmit: true,
|
|
8165
|
-
allowImportingTsExtensions: true,
|
|
8166
|
-
baseUrl: ".",
|
|
8167
|
-
paths: {
|
|
8168
|
-
"~/*": ["./src/*"],
|
|
8169
|
-
[`@${options.name}/constructs/*`]: ["../../constructs/*"],
|
|
8170
|
-
[`@${options.name}/*`]: ["../../packages/*/src"]
|
|
8171
|
-
}
|
|
8172
|
-
},
|
|
8173
|
-
include: ["src/**/*.ts"],
|
|
8174
|
-
exclude: ["node_modules", "dist"]
|
|
8175
|
-
} : {
|
|
8230
|
+
const tsConfig = options.monorepo ? workspaceApiTsConfig(options.name) : {
|
|
8176
8231
|
compilerOptions: {
|
|
8177
8232
|
target: "ES2022",
|
|
8178
8233
|
module: "NodeNext",
|
|
@@ -8212,7 +8267,7 @@ export default defineConfig({${options.monorepo ? "" : `
|
|
|
8212
8267
|
clientKind: "git",
|
|
8213
8268
|
useIgnoreFile: true
|
|
8214
8269
|
},
|
|
8215
|
-
|
|
8270
|
+
assist: { actions: { source: { organizeImports: "on" } } },
|
|
8216
8271
|
formatter: {
|
|
8217
8272
|
enabled: true,
|
|
8218
8273
|
indentStyle: "space",
|
|
@@ -8236,11 +8291,13 @@ export default defineConfig({${options.monorepo ? "" : `
|
|
|
8236
8291
|
style: { noNonNullAssertion: "off" }
|
|
8237
8292
|
}
|
|
8238
8293
|
},
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
"
|
|
8242
|
-
"
|
|
8243
|
-
"
|
|
8294
|
+
css: { parser: { tailwindDirectives: true } },
|
|
8295
|
+
files: { includes: [
|
|
8296
|
+
"**",
|
|
8297
|
+
"!**/node_modules",
|
|
8298
|
+
"!**/dist",
|
|
8299
|
+
"!**/.gkm",
|
|
8300
|
+
"!**/coverage"
|
|
8244
8301
|
] }
|
|
8245
8302
|
};
|
|
8246
8303
|
const turboConfig = {
|
|
@@ -8294,8 +8351,16 @@ export default defineConfig({${options.monorepo ? "" : `
|
|
|
8294
8351
|
});
|
|
8295
8352
|
return files$1;
|
|
8296
8353
|
}
|
|
8297
|
-
|
|
8298
|
-
|
|
8354
|
+
/**
|
|
8355
|
+
* The tsconfig of an API inside a workspace.
|
|
8356
|
+
*
|
|
8357
|
+
* One definition for both ways of getting one — the fullstack template and
|
|
8358
|
+
* `--monorepo` — because the two copies had drifted: only one of them mapped
|
|
8359
|
+
* the root constructs, so in the other `@<name>/constructs/api.ts` fell through
|
|
8360
|
+
* to the `packages/` mapping and nothing could load the surface.
|
|
8361
|
+
*/
|
|
8362
|
+
function workspaceApiTsConfig(name$1) {
|
|
8363
|
+
return {
|
|
8299
8364
|
extends: "../../tsconfig.json",
|
|
8300
8365
|
compilerOptions: {
|
|
8301
8366
|
noEmit: true,
|
|
@@ -8303,12 +8368,16 @@ function generateSingleAppConfigFiles(options, _template, _helpers) {
|
|
|
8303
8368
|
baseUrl: ".",
|
|
8304
8369
|
paths: {
|
|
8305
8370
|
"~/*": ["./src/*"],
|
|
8306
|
-
[`@${
|
|
8371
|
+
[`@${name$1}/constructs/*`]: ["../../constructs/*"],
|
|
8372
|
+
[`@${name$1}/*`]: ["../../packages/*/src"]
|
|
8307
8373
|
}
|
|
8308
8374
|
},
|
|
8309
8375
|
include: ["src/**/*.ts"],
|
|
8310
8376
|
exclude: ["node_modules", "dist"]
|
|
8311
8377
|
};
|
|
8378
|
+
}
|
|
8379
|
+
function generateSingleAppConfigFiles(options, _template, _helpers) {
|
|
8380
|
+
const tsConfig = workspaceApiTsConfig(options.name);
|
|
8312
8381
|
const files$1 = [{
|
|
8313
8382
|
path: "tsconfig.json",
|
|
8314
8383
|
content: `${JSON.stringify(tsConfig, null, 2)}\n`
|
|
@@ -8717,12 +8786,12 @@ function generateExpoAppFiles(options) {
|
|
|
8717
8786
|
dependencies: {
|
|
8718
8787
|
[apiPackage]: "workspace:*",
|
|
8719
8788
|
[modelsPackage]: "workspace:*",
|
|
8720
|
-
"@better-auth/expo": "
|
|
8789
|
+
"@better-auth/expo": DEPENDENCY_VERSIONS["better-auth"],
|
|
8721
8790
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
8722
8791
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
8723
8792
|
"@react-navigation/native": "^7.1.0",
|
|
8724
|
-
"@tanstack/react-query": "
|
|
8725
|
-
"better-auth": "
|
|
8793
|
+
"@tanstack/react-query": DEPENDENCY_VERSIONS["@tanstack/react-query"],
|
|
8794
|
+
"better-auth": DEPENDENCY_VERSIONS["better-auth"],
|
|
8726
8795
|
expo: "~55.0.0",
|
|
8727
8796
|
"expo-constants": "~55.0.0",
|
|
8728
8797
|
"expo-dev-client": "~55.0.0",
|
|
@@ -8858,7 +8927,7 @@ module.exports = withNativeWind(config, { input: './global.css' });
|
|
|
8858
8927
|
"@/*": ["./*"],
|
|
8859
8928
|
[modelsPackage]: ["../../packages/models/src"],
|
|
8860
8929
|
[`${modelsPackage}/*`]: ["../../packages/models/src/*"],
|
|
8861
|
-
[`${apiPackage}/client`]: ["../../apps/api/.gkm/openapi.ts"]
|
|
8930
|
+
[`${apiPackage}/client`]: ["../../apps/api/.gkm/openapi/api.ts"]
|
|
8862
8931
|
}
|
|
8863
8932
|
},
|
|
8864
8933
|
include: [
|
|
@@ -9416,14 +9485,142 @@ export type ListUsersResponse = z.infer<typeof ListUsersResponseSchema>;
|
|
|
9416
9485
|
];
|
|
9417
9486
|
}
|
|
9418
9487
|
|
|
9488
|
+
//#endregion
|
|
9489
|
+
//#region src/init/generators/deploy.ts
|
|
9490
|
+
/** One `deploy:<stage>` script per deployed stage. */
|
|
9491
|
+
const perStage = (options, command) => Object.fromEntries(options.stages.deployed.map((stage) => [`deploy:${stage}`, command(stage)]));
|
|
9492
|
+
function deployPackage(options) {
|
|
9493
|
+
switch (options.deployTarget) {
|
|
9494
|
+
case "dokploy": return {
|
|
9495
|
+
scripts: perStage(options, (stage) => `gkm deploy --provider dokploy --stage ${stage}`),
|
|
9496
|
+
dependencies: {},
|
|
9497
|
+
devDependencies: {}
|
|
9498
|
+
};
|
|
9499
|
+
case "sst": {
|
|
9500
|
+
const v = GEEKMIDAS_VERSIONS;
|
|
9501
|
+
return {
|
|
9502
|
+
scripts: perStage(options, (stage) => `gkm build --provider aws && sst deploy --stage ${stage}`),
|
|
9503
|
+
dependencies: {
|
|
9504
|
+
"@geekmidas/cloud": v["@geekmidas/cloud"],
|
|
9505
|
+
"@geekmidas/db": v["@geekmidas/db"],
|
|
9506
|
+
"@geekmidas/envkit": v["@geekmidas/envkit"],
|
|
9507
|
+
"@geekmidas/events": v["@geekmidas/events"],
|
|
9508
|
+
"@geekmidas/manifest": v["@geekmidas/manifest"],
|
|
9509
|
+
"@geekmidas/storage": v["@geekmidas/storage"],
|
|
9510
|
+
pg: DEPENDENCY_VERSIONS.pg
|
|
9511
|
+
},
|
|
9512
|
+
devDependencies: { sst: DEPENDENCY_VERSIONS.sst }
|
|
9513
|
+
};
|
|
9514
|
+
}
|
|
9515
|
+
default: return {
|
|
9516
|
+
scripts: {},
|
|
9517
|
+
dependencies: {},
|
|
9518
|
+
devDependencies: {}
|
|
9519
|
+
};
|
|
9520
|
+
}
|
|
9521
|
+
}
|
|
9522
|
+
function generateDeployFiles(options) {
|
|
9523
|
+
if (options.deployTarget !== "sst") return [];
|
|
9524
|
+
if (!options.region) throw new Error("An SST deploy needs a region.");
|
|
9525
|
+
const db = databaseFor();
|
|
9526
|
+
const mail = emailFor();
|
|
9527
|
+
const hasDatabase = options.template === "fullstack" || options.services.db;
|
|
9528
|
+
const inputs = [...hasDatabase ? [` // RDS needs a network. Created here rather than assumed, because a
|
|
9529
|
+
// VPC with NAT is a monthly cost in an account that may already
|
|
9530
|
+
// have one — replace it with \`sst.aws.Vpc.get(…)\` if so.
|
|
9531
|
+
${db.id}: { vpc },`] : [], ...options.services.mail ? [` // Every provider rejects an unverified sender, so there is no
|
|
9532
|
+
// default; the synth stops with \`EmailNeedsSender\` until it is set.
|
|
9533
|
+
${mail.id}: { from: process.env.MAIL_FROM as string },`] : []];
|
|
9534
|
+
return [{
|
|
9535
|
+
path: "sst.config.ts",
|
|
9536
|
+
content: `/// <reference path="./.sst/platform/config.d.ts" />
|
|
9537
|
+
|
|
9538
|
+
/**
|
|
9539
|
+
* ${options.name} on AWS.
|
|
9540
|
+
*
|
|
9541
|
+
* No bucket, cluster, queue or IAM is listed here: they come from the
|
|
9542
|
+
* constructs the application declares, through \`fromManifest\`. What is here
|
|
9543
|
+
* is what varies by stage, and the inputs a declaration cannot carry.
|
|
9544
|
+
*
|
|
9545
|
+
* Deploy with \`pnpm run deploy:<stage>\`, which builds the manifest first.
|
|
9546
|
+
*/
|
|
9547
|
+
const region = '${options.region}';
|
|
9548
|
+
|
|
9549
|
+
/** From \`stages.protected\` in gkm.config.ts. */
|
|
9550
|
+
const PROTECTED: string[] = ${JSON.stringify(options.stages.protected ?? []).replace(/"/g, "'")};
|
|
9551
|
+
|
|
9552
|
+
export default $config({
|
|
9553
|
+
app(input) {
|
|
9554
|
+
return {
|
|
9555
|
+
name: '${options.name}',
|
|
9556
|
+
// Any other stage is disposable; a protected one's database should not
|
|
9557
|
+
// vanish because somebody removed a stack.
|
|
9558
|
+
removal: PROTECTED.includes(input?.stage) ? 'retain' : 'remove',
|
|
9559
|
+
protect: PROTECTED.includes(input?.stage),
|
|
9560
|
+
home: 'aws',
|
|
9561
|
+
providers: { aws: { region } },
|
|
9562
|
+
};
|
|
9563
|
+
},
|
|
9564
|
+
|
|
9565
|
+
async run() {
|
|
9566
|
+
const { App, fromManifest, Stack } = await import('@geekmidas/cloud/sst');
|
|
9567
|
+
// Written by \`gkm build --provider aws\`. Imported rather than discovered,
|
|
9568
|
+
// so SST's toolchain never evaluates the application's own modules.
|
|
9569
|
+
const { backends, constructs } = await import('./.gkm/manifest/aws.js');
|
|
9570
|
+
|
|
9571
|
+
${hasDatabase ? " const vpc = new sst.aws.Vpc('Vpc', { nat: 'ec2' });\n\n" : ""} const app = new App({
|
|
9572
|
+
name: $app.name,
|
|
9573
|
+
stage: $app.stage,
|
|
9574
|
+
domain: '',
|
|
9575
|
+
region,
|
|
9576
|
+
hostedZoneId: '',
|
|
9577
|
+
});
|
|
9578
|
+
|
|
9579
|
+
return fromManifest(
|
|
9580
|
+
new Stack(app, '${(0, __geekmidas_manifest.canonicalId)(options.name)}'),
|
|
9581
|
+
constructs,
|
|
9582
|
+
{
|
|
9583
|
+
${inputs.join("\n")}
|
|
9584
|
+
},
|
|
9585
|
+
backends,
|
|
9586
|
+
);
|
|
9587
|
+
},
|
|
9588
|
+
});
|
|
9589
|
+
`
|
|
9590
|
+
}];
|
|
9591
|
+
}
|
|
9592
|
+
|
|
9419
9593
|
//#endregion
|
|
9420
9594
|
//#region src/init/generators/monorepo.ts
|
|
9421
9595
|
/**
|
|
9596
|
+
* What the root `constructs/` folder imports, and the peers each construct
|
|
9597
|
+
* needs, for the services this workspace was scaffolded with.
|
|
9598
|
+
*/
|
|
9599
|
+
function rootConstructDependencies(options) {
|
|
9600
|
+
const v = GEEKMIDAS_VERSIONS;
|
|
9601
|
+
return {
|
|
9602
|
+
"@geekmidas/constructs": v["@geekmidas/constructs"],
|
|
9603
|
+
"@geekmidas/logger": v["@geekmidas/logger"],
|
|
9604
|
+
...options.loggerType === "pino" ? { pino: DEPENDENCY_VERSIONS.pino } : {},
|
|
9605
|
+
"@geekmidas/telescope": v["@geekmidas/telescope"],
|
|
9606
|
+
"@geekmidas/auth": v["@geekmidas/auth"],
|
|
9607
|
+
"better-auth": DEPENDENCY_VERSIONS["better-auth"],
|
|
9608
|
+
"@geekmidas/db": v["@geekmidas/db"],
|
|
9609
|
+
kysely: DEPENDENCY_VERSIONS.kysely,
|
|
9610
|
+
pg: DEPENDENCY_VERSIONS.pg,
|
|
9611
|
+
...options.services.storage ? { "@geekmidas/storage": v["@geekmidas/storage"] } : {},
|
|
9612
|
+
...options.services.mail ? { "@geekmidas/emailkit": v["@geekmidas/emailkit"] } : {},
|
|
9613
|
+
...options.services.cache ? { "@geekmidas/cache": v["@geekmidas/cache"] } : {}
|
|
9614
|
+
};
|
|
9615
|
+
}
|
|
9616
|
+
/**
|
|
9422
9617
|
* Generate monorepo root files (pnpm-workspace.yaml, root package.json, etc.)
|
|
9423
9618
|
*/
|
|
9424
9619
|
function generateMonorepoFiles(options, _template) {
|
|
9425
9620
|
if (!options.monorepo) return [];
|
|
9426
9621
|
const isFullstack = options.template === "fullstack";
|
|
9622
|
+
const deploy = deployPackage(options);
|
|
9623
|
+
const isSst = options.deployTarget === "sst";
|
|
9427
9624
|
const rootPackageJson = {
|
|
9428
9625
|
name: options.name,
|
|
9429
9626
|
version: "0.0.1",
|
|
@@ -9440,9 +9637,13 @@ function generateMonorepoFiles(options, _template) {
|
|
|
9440
9637
|
fmt: "biome format . --write",
|
|
9441
9638
|
"fmt:check": "biome format .",
|
|
9442
9639
|
...isFullstack ? { storybook: "pnpm --filter ./packages/ui storybook" } : {},
|
|
9443
|
-
...
|
|
9640
|
+
...deploy.scripts
|
|
9641
|
+
},
|
|
9642
|
+
dependencies: {
|
|
9643
|
+
zod: DEPENDENCY_VERSIONS.zod,
|
|
9644
|
+
...isFullstack ? rootConstructDependencies(options) : {},
|
|
9645
|
+
...deploy.dependencies
|
|
9444
9646
|
},
|
|
9445
|
-
dependencies: { zod: "~4.1.0" },
|
|
9446
9647
|
devDependencies: {
|
|
9447
9648
|
"@biomejs/biome": "~2.3.0",
|
|
9448
9649
|
"@geekmidas/cli": GEEKMIDAS_VERSIONS["@geekmidas/cli"],
|
|
@@ -9450,7 +9651,8 @@ function generateMonorepoFiles(options, _template) {
|
|
|
9450
9651
|
tsx: "~4.20.0",
|
|
9451
9652
|
turbo: "~2.3.0",
|
|
9452
9653
|
typescript: "~5.8.2",
|
|
9453
|
-
vitest: "~4.0.0"
|
|
9654
|
+
vitest: "~4.0.0",
|
|
9655
|
+
...deploy.devDependencies
|
|
9454
9656
|
}
|
|
9455
9657
|
};
|
|
9456
9658
|
const apiPathParts = options.apiPath.split("/");
|
|
@@ -9466,7 +9668,7 @@ function generateMonorepoFiles(options, _template) {
|
|
|
9466
9668
|
clientKind: "git",
|
|
9467
9669
|
useIgnoreFile: true
|
|
9468
9670
|
},
|
|
9469
|
-
|
|
9671
|
+
assist: { actions: { source: { organizeImports: "on" } } },
|
|
9470
9672
|
formatter: {
|
|
9471
9673
|
enabled: true,
|
|
9472
9674
|
indentStyle: "space",
|
|
@@ -9490,11 +9692,14 @@ function generateMonorepoFiles(options, _template) {
|
|
|
9490
9692
|
style: { noNonNullAssertion: "off" }
|
|
9491
9693
|
}
|
|
9492
9694
|
},
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
"
|
|
9496
|
-
"
|
|
9497
|
-
"
|
|
9695
|
+
css: { parser: { tailwindDirectives: true } },
|
|
9696
|
+
files: { includes: [
|
|
9697
|
+
"**",
|
|
9698
|
+
"!**/node_modules",
|
|
9699
|
+
"!**/dist",
|
|
9700
|
+
"!**/.gkm",
|
|
9701
|
+
"!**/coverage",
|
|
9702
|
+
...isSst ? ["!**/.sst", "!sst-env.d.ts"] : []
|
|
9498
9703
|
] }
|
|
9499
9704
|
};
|
|
9500
9705
|
const turboConfig = {
|
|
@@ -9560,12 +9765,14 @@ coverage/
|
|
|
9560
9765
|
|
|
9561
9766
|
# Turbo
|
|
9562
9767
|
.turbo/
|
|
9563
|
-
`;
|
|
9768
|
+
${isSst ? "\n# SST\n.sst/\n" : ""}`;
|
|
9564
9769
|
const tsConfig = {
|
|
9565
9770
|
compilerOptions: {
|
|
9566
9771
|
...isFullstack ? {
|
|
9567
9772
|
baseUrl: ".",
|
|
9568
|
-
paths: { [`@${options.name}/constructs/*`]: ["./constructs/*"] }
|
|
9773
|
+
paths: { [`@${options.name}/constructs/*`]: ["./constructs/*"] },
|
|
9774
|
+
allowImportingTsExtensions: true,
|
|
9775
|
+
noEmit: true
|
|
9569
9776
|
} : {},
|
|
9570
9777
|
target: "ES2022",
|
|
9571
9778
|
module: "NodeNext",
|
|
@@ -9577,16 +9784,21 @@ coverage/
|
|
|
9577
9784
|
forceConsistentCasingInFileNames: true,
|
|
9578
9785
|
resolveJsonModule: true
|
|
9579
9786
|
},
|
|
9580
|
-
exclude: [
|
|
9787
|
+
exclude: [
|
|
9788
|
+
"node_modules",
|
|
9789
|
+
"dist",
|
|
9790
|
+
...isSst ? ["sst.config.ts", ".sst"] : []
|
|
9791
|
+
]
|
|
9581
9792
|
};
|
|
9582
9793
|
const vitestConfig = `import { defineConfig } from 'vitest/config';
|
|
9583
9794
|
|
|
9584
9795
|
export default defineConfig({
|
|
9585
9796
|
test: {
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9797
|
+
// Each app and package is its own project, so its own config — an app's
|
|
9798
|
+
// \`globalSetup\`, its path aliases — applies to its own tests.
|
|
9799
|
+
projects: ['apps/*', 'packages/*'],
|
|
9800
|
+
// The shared packages ship without tests of their own.
|
|
9801
|
+
passWithNoTests: true,
|
|
9590
9802
|
coverage: {
|
|
9591
9803
|
provider: 'v8',
|
|
9592
9804
|
reporter: ['text', 'json', 'html'],
|
|
@@ -9667,7 +9879,8 @@ export default defineConfig({
|
|
|
9667
9879
|
{
|
|
9668
9880
|
path: ".vscode/extensions.json",
|
|
9669
9881
|
content: `${JSON.stringify(vscodeExtensions, null, " ")}\n`
|
|
9670
|
-
}
|
|
9882
|
+
},
|
|
9883
|
+
...generateDeployFiles(options)
|
|
9671
9884
|
];
|
|
9672
9885
|
if (isFullstack) files$1.push({
|
|
9673
9886
|
path: "gkm.config.ts",
|
|
@@ -9694,12 +9907,17 @@ export default defineWorkspace({
|
|
|
9694
9907
|
// The scope every physical name is built from: \`Database\` becomes
|
|
9695
9908
|
// \`production-${options.name}-database\` on Dokploy and on AWS alike.
|
|
9696
9909
|
name: '${options.name}',
|
|
9910
|
+
${stagesBlock(options.stages)}
|
|
9697
9911
|
|
|
9698
|
-
//
|
|
9912
|
+
// Every kind, in every app. A declared database is why a Postgres exists, a
|
|
9699
9913
|
// declared bucket is why MinIO does, a declared topic is why a broker does —
|
|
9700
9914
|
// none of it listed here. It is also where the apps come from: a
|
|
9701
|
-
// \`StaticSite\` is an app, and so is every \`RestApi\`.
|
|
9702
|
-
|
|
9915
|
+
// \`StaticSite\` is an app, and so is every \`RestApi\`. And it finds each
|
|
9916
|
+
// app's endpoints, crons and subscribers: an endpoint belongs to the surface
|
|
9917
|
+
// it was built from, not to the directory it sits in.
|
|
9918
|
+
constructs: [
|
|
9919
|
+
${workspaceConstructsGlobs(options.routesStructure, (0, node_path.dirname)(options.apiPath)).map((glob) => ` '${glob}',`).join("\n")}
|
|
9920
|
+
],
|
|
9703
9921
|
|
|
9704
9922
|
secrets: {
|
|
9705
9923
|
enabled: true,
|
|
@@ -9728,7 +9946,7 @@ export default defineWorkspace({
|
|
|
9728
9946
|
function generateRootConstructs(options) {
|
|
9729
9947
|
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
9730
9948
|
const { name: name$1, services, frontendFramework } = options;
|
|
9731
|
-
const db = databaseFor(
|
|
9949
|
+
const db = databaseFor();
|
|
9732
9950
|
const files$1 = [];
|
|
9733
9951
|
files$1.push({
|
|
9734
9952
|
path: "constructs/logger.ts",
|
|
@@ -9794,6 +10012,7 @@ import { authDb } from './database.ts';
|
|
|
9794
10012
|
* origin down.
|
|
9795
10013
|
*/
|
|
9796
10014
|
export const auth = new BetterAuth('Auth', {
|
|
10015
|
+
path: 'apps/auth',
|
|
9797
10016
|
database: authDb,
|
|
9798
10017
|
basePath: '/api/auth',
|
|
9799
10018
|
});
|
|
@@ -9808,25 +10027,22 @@ import { logger } from './logger.ts';
|
|
|
9808
10027
|
/**
|
|
9809
10028
|
* The application's HTTP surface — one RestApi, one container.
|
|
9810
10029
|
*
|
|
9811
|
-
*
|
|
9812
|
-
* \`
|
|
9813
|
-
* \`src/\`, where the conventional layout has them at the app root.
|
|
10030
|
+
* \`path\` is where its app lives. Its handlers are found by the workspace's
|
|
10031
|
+
* \`constructs\` glob, and belong to it because they were built from it.
|
|
9814
10032
|
*/
|
|
9815
10033
|
export const api = new RestApi('Api', {
|
|
10034
|
+
path: '${options.apiPath}',
|
|
10035
|
+
|
|
9816
10036
|
// Typed out rather than omitted: an API that ships open because a field was
|
|
9817
10037
|
// left off is the one default worth refusing to have.
|
|
9818
10038
|
defaultAuthorizer: 'none',
|
|
9819
10039
|
|
|
9820
10040
|
// The actual logger, not a path to one.
|
|
9821
10041
|
logger,
|
|
9822
|
-
|
|
9823
|
-
app: {
|
|
9824
|
-
code: './src/{endpoints,functions,crons,queues,topics,subscribers}/**/*.ts',
|
|
9825
|
-
},
|
|
9826
10042
|
}).auth(auth);
|
|
9827
10043
|
`
|
|
9828
10044
|
});
|
|
9829
|
-
const variant = frontendFramework === "tanstack-start" ? "{ variant: 'tanstack' }" : frontendFramework === "expo" ? void 0 : "{ variant: 'next' }";
|
|
10045
|
+
const variant = frontendFramework === "tanstack-start" ? "{ path: 'apps/web', variant: 'tanstack' }" : frontendFramework === "expo" ? void 0 : "{ path: 'apps/web', variant: 'next' }";
|
|
9830
10046
|
if (variant !== void 0) files$1.push({
|
|
9831
10047
|
path: "constructs/site.ts",
|
|
9832
10048
|
content: `import { StaticSite } from '@geekmidas/constructs/site';
|
|
@@ -9836,7 +10052,7 @@ import { auth } from './auth.ts';
|
|
|
9836
10052
|
/**
|
|
9837
10053
|
* The frontend — a construct like any other, which is what makes it an app.
|
|
9838
10054
|
*
|
|
9839
|
-
*
|
|
10055
|
+
* \`path\` is where it lives. \`.dependsOn()\` is the single fact
|
|
9840
10056
|
* behind four things that are hand-maintained otherwise: this site's
|
|
9841
10057
|
* build-time API URL, the API's CORS origins, the auth server's trusted
|
|
9842
10058
|
* origins, and which generated client lands here.
|
|
@@ -9845,7 +10061,7 @@ export const web = new StaticSite('Web', ${variant}).dependsOn([api, auth]);
|
|
|
9845
10061
|
`
|
|
9846
10062
|
});
|
|
9847
10063
|
if (services.storage) {
|
|
9848
|
-
const bucket = storageFor(
|
|
10064
|
+
const bucket = storageFor();
|
|
9849
10065
|
files$1.push({
|
|
9850
10066
|
path: "constructs/storage.ts",
|
|
9851
10067
|
content: `import { ObjectStorage } from '@geekmidas/constructs/object-storage';
|
|
@@ -9856,7 +10072,7 @@ export const uploads = new ObjectStorage('${bucket.id}');
|
|
|
9856
10072
|
});
|
|
9857
10073
|
}
|
|
9858
10074
|
if (services.mail) {
|
|
9859
|
-
const mail = emailFor(
|
|
10075
|
+
const mail = emailFor();
|
|
9860
10076
|
files$1.push({
|
|
9861
10077
|
path: "constructs/email.ts",
|
|
9862
10078
|
content: `import { Email } from '@geekmidas/constructs/email';
|
|
@@ -9867,7 +10083,7 @@ export const email = new Email('${mail.id}', { templates: {} });
|
|
|
9867
10083
|
});
|
|
9868
10084
|
}
|
|
9869
10085
|
if (services.cache) {
|
|
9870
|
-
const kv = cacheFor(
|
|
10086
|
+
const kv = cacheFor();
|
|
9871
10087
|
files$1.push({
|
|
9872
10088
|
path: "constructs/cache.ts",
|
|
9873
10089
|
content: `import { Cache } from '@geekmidas/constructs/cache';
|
|
@@ -9900,9 +10116,9 @@ const apiTemplate = {
|
|
|
9900
10116
|
"@geekmidas/errors": GEEKMIDAS_VERSIONS["@geekmidas/errors"],
|
|
9901
10117
|
"@geekmidas/auth": GEEKMIDAS_VERSIONS["@geekmidas/auth"],
|
|
9902
10118
|
"@hono/node-server": "~1.14.1",
|
|
9903
|
-
hono:
|
|
9904
|
-
pino:
|
|
9905
|
-
zod:
|
|
10119
|
+
hono: DEPENDENCY_VERSIONS.hono,
|
|
10120
|
+
pino: DEPENDENCY_VERSIONS.pino,
|
|
10121
|
+
zod: DEPENDENCY_VERSIONS.zod
|
|
9906
10122
|
},
|
|
9907
10123
|
devDependencies: {
|
|
9908
10124
|
"@biomejs/biome": "~2.3.0",
|
|
@@ -9926,10 +10142,10 @@ const apiTemplate = {
|
|
|
9926
10142
|
},
|
|
9927
10143
|
files: (options) => {
|
|
9928
10144
|
const { loggerType, routesStructure, monorepo, name: name$1, services } = options;
|
|
9929
|
-
const bucket = storageFor(
|
|
9930
|
-
const kv = cacheFor(
|
|
9931
|
-
const db = databaseFor(
|
|
9932
|
-
const mail = emailFor(
|
|
10145
|
+
const bucket = storageFor();
|
|
10146
|
+
const kv = cacheFor();
|
|
10147
|
+
const db = databaseFor();
|
|
10148
|
+
const mail = emailFor();
|
|
9933
10149
|
const src = (path) => monorepo ? `~/${path}` : `./${path}`;
|
|
9934
10150
|
const declares = !monorepo;
|
|
9935
10151
|
const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
|
|
@@ -9963,6 +10179,9 @@ export const logger = createLogger();
|
|
|
9963
10179
|
import { logger } from '../config/logger.ts';
|
|
9964
10180
|
|
|
9965
10181
|
export const api = new RestApi('Api', {
|
|
10182
|
+
// The project is the app.
|
|
10183
|
+
path: '.',
|
|
10184
|
+
|
|
9966
10185
|
// Typed out rather than omitted: an API that ships open because a field was
|
|
9967
10186
|
// left off is the one default worth refusing to have.
|
|
9968
10187
|
defaultAuthorizer: 'none',
|
|
@@ -9985,7 +10204,7 @@ export const envParser = new EnvironmentParser({ ...process.env, ...Credentials
|
|
|
9985
10204
|
export const config = envParser
|
|
9986
10205
|
.create((get) => ({
|
|
9987
10206
|
nodeEnv: get('NODE_ENV').enum(['development', 'test', 'production']).default('development'),
|
|
9988
|
-
stage:
|
|
10207
|
+
stage: ${stageEnv(options.stages)},
|
|
9989
10208
|
}))
|
|
9990
10209
|
.parse();
|
|
9991
10210
|
`
|
|
@@ -10137,7 +10356,7 @@ export const authService = {
|
|
|
10137
10356
|
headers: { cookie },
|
|
10138
10357
|
});
|
|
10139
10358
|
if (!res.ok) return null;
|
|
10140
|
-
return res.json();
|
|
10359
|
+
return (await res.json()) as Session | null;
|
|
10141
10360
|
},
|
|
10142
10361
|
};
|
|
10143
10362
|
},
|
|
@@ -10150,7 +10369,6 @@ export const authService = {
|
|
|
10150
10369
|
import { api } from '${constructsImport("api")}';${options.database ? `
|
|
10151
10370
|
import { database } from '${constructsImport("database")}';` : ""}
|
|
10152
10371
|
import { authService, type Session } from './services/auth.ts';
|
|
10153
|
-
import { logger } from './config/logger.ts';
|
|
10154
10372
|
|
|
10155
10373
|
/**
|
|
10156
10374
|
* The shared endpoint factory — no session required.
|
|
@@ -10208,7 +10426,8 @@ import { database } from '${constructsImport("database")}';` : ""}
|
|
|
10208
10426
|
export const router = api.endpoints${options.database ? ".database(database)" : ""};
|
|
10209
10427
|
`
|
|
10210
10428
|
});
|
|
10211
|
-
if (options.database && declares) files$1.push(...databaseFiles(
|
|
10429
|
+
if (options.database && declares) files$1.push(...databaseFiles());
|
|
10430
|
+
else if (options.database) files$1.push(usersMigration());
|
|
10212
10431
|
if (services.storage && declares) files$1.push({
|
|
10213
10432
|
path: "src/constructs/storage.ts",
|
|
10214
10433
|
content: `import { ObjectStorage } from '@geekmidas/constructs/object-storage';
|
|
@@ -10358,9 +10577,9 @@ const minimalTemplate = {
|
|
|
10358
10577
|
"@geekmidas/rate-limit": GEEKMIDAS_VERSIONS["@geekmidas/rate-limit"],
|
|
10359
10578
|
"@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"],
|
|
10360
10579
|
"@hono/node-server": "~1.14.1",
|
|
10361
|
-
hono:
|
|
10362
|
-
pino:
|
|
10363
|
-
zod:
|
|
10580
|
+
hono: DEPENDENCY_VERSIONS.hono,
|
|
10581
|
+
pino: DEPENDENCY_VERSIONS.pino,
|
|
10582
|
+
zod: DEPENDENCY_VERSIONS.zod
|
|
10364
10583
|
},
|
|
10365
10584
|
devDependencies: {
|
|
10366
10585
|
"@biomejs/biome": "~2.3.0",
|
|
@@ -10384,7 +10603,7 @@ const minimalTemplate = {
|
|
|
10384
10603
|
},
|
|
10385
10604
|
files: (options) => {
|
|
10386
10605
|
const { loggerType, routesStructure, name: name$1 } = options;
|
|
10387
|
-
const db = databaseFor(
|
|
10606
|
+
const db = databaseFor();
|
|
10388
10607
|
const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
|
|
10389
10608
|
|
|
10390
10609
|
export const logger = createLogger();
|
|
@@ -10404,6 +10623,9 @@ export const logger = createLogger();
|
|
|
10404
10623
|
import { logger } from '../config/logger.ts';
|
|
10405
10624
|
|
|
10406
10625
|
export const api = new RestApi('Api', {
|
|
10626
|
+
// The project is the app.
|
|
10627
|
+
path: '.',
|
|
10628
|
+
|
|
10407
10629
|
// Typed out rather than omitted: an API that ships open because a field was
|
|
10408
10630
|
// left off is the one default worth refusing to have.
|
|
10409
10631
|
defaultAuthorizer: 'none',
|
|
@@ -10426,7 +10648,7 @@ export const envParser = new EnvironmentParser({ ...process.env, ...Credentials
|
|
|
10426
10648
|
export const config = envParser
|
|
10427
10649
|
.create((get) => ({
|
|
10428
10650
|
nodeEnv: get('NODE_ENV').enum(['development', 'test', 'production']).default('development'),
|
|
10429
|
-
stage:
|
|
10651
|
+
stage: ${stageEnv(options.stages)},
|
|
10430
10652
|
}))
|
|
10431
10653
|
.parse();
|
|
10432
10654
|
`
|
|
@@ -10453,7 +10675,7 @@ export const healthEndpoint = api
|
|
|
10453
10675
|
`
|
|
10454
10676
|
}
|
|
10455
10677
|
];
|
|
10456
|
-
if (options.database) files$1.push(...databaseFiles(
|
|
10678
|
+
if (options.database) files$1.push(...databaseFiles());
|
|
10457
10679
|
if (options.telescope) files$1.push({
|
|
10458
10680
|
path: "src/config/telescope.ts",
|
|
10459
10681
|
content: `import { Telescope } from '@geekmidas/telescope';
|
|
@@ -10517,8 +10739,8 @@ const serverlessTemplate = {
|
|
|
10517
10739
|
"@geekmidas/rate-limit": GEEKMIDAS_VERSIONS["@geekmidas/rate-limit"],
|
|
10518
10740
|
"@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"],
|
|
10519
10741
|
"@hono/node-server": "~1.14.1",
|
|
10520
|
-
hono:
|
|
10521
|
-
pino:
|
|
10742
|
+
hono: DEPENDENCY_VERSIONS.hono,
|
|
10743
|
+
pino: DEPENDENCY_VERSIONS.pino
|
|
10522
10744
|
},
|
|
10523
10745
|
devDependencies: {
|
|
10524
10746
|
"@biomejs/biome": "~2.3.0",
|
|
@@ -10561,6 +10783,9 @@ export const logger = createLogger();
|
|
|
10561
10783
|
import { logger } from '../config/logger.ts';
|
|
10562
10784
|
|
|
10563
10785
|
export const api = new RestApi('Api', {
|
|
10786
|
+
// The project is the app.
|
|
10787
|
+
path: '.',
|
|
10788
|
+
|
|
10564
10789
|
// Typed out rather than omitted: an API that ships open because a field was
|
|
10565
10790
|
// left off is the one default worth refusing to have.
|
|
10566
10791
|
defaultAuthorizer: 'none',
|
|
@@ -10583,7 +10808,7 @@ export const envParser = new EnvironmentParser({ ...process.env, ...Credentials
|
|
|
10583
10808
|
export const config = envParser
|
|
10584
10809
|
.create((get) => ({
|
|
10585
10810
|
nodeEnv: get('NODE_ENV').enum(['development', 'test', 'production']).default('development'),
|
|
10586
|
-
stage:
|
|
10811
|
+
stage: ${stageEnv(options.stages)},
|
|
10587
10812
|
}))
|
|
10588
10813
|
.parse();
|
|
10589
10814
|
`
|
|
@@ -10633,7 +10858,7 @@ export const worker = new Worker('Jobs', { logger });
|
|
|
10633
10858
|
`
|
|
10634
10859
|
}
|
|
10635
10860
|
];
|
|
10636
|
-
if (options.database) files$1.push(...databaseFiles(
|
|
10861
|
+
if (options.database) files$1.push(...databaseFiles());
|
|
10637
10862
|
if (options.telescope) files$1.push({
|
|
10638
10863
|
path: "src/config/telescope.ts",
|
|
10639
10864
|
content: `import { Telescope } from '@geekmidas/telescope';
|
|
@@ -10642,7 +10867,7 @@ import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';
|
|
|
10642
10867
|
// Note: For production Lambda, consider using a persistent storage
|
|
10643
10868
|
export const telescope = new Telescope({
|
|
10644
10869
|
storage: new InMemoryStorage({ maxEntries: 50 }),
|
|
10645
|
-
enabled: process.env.STAGE === '
|
|
10870
|
+
enabled: process.env.STAGE === '${options.stages.local}',
|
|
10646
10871
|
});
|
|
10647
10872
|
`
|
|
10648
10873
|
});
|
|
@@ -10678,7 +10903,7 @@ const workerTemplate = {
|
|
|
10678
10903
|
"@geekmidas/events": GEEKMIDAS_VERSIONS["@geekmidas/events"],
|
|
10679
10904
|
"@geekmidas/logger": GEEKMIDAS_VERSIONS["@geekmidas/logger"],
|
|
10680
10905
|
"@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"],
|
|
10681
|
-
pino:
|
|
10906
|
+
pino: DEPENDENCY_VERSIONS.pino
|
|
10682
10907
|
},
|
|
10683
10908
|
devDependencies: {
|
|
10684
10909
|
"@biomejs/biome": "~2.3.0",
|
|
@@ -10726,7 +10951,7 @@ export const envParser = new EnvironmentParser({ ...process.env, ...Credentials
|
|
|
10726
10951
|
export const config = envParser
|
|
10727
10952
|
.create((get) => ({
|
|
10728
10953
|
nodeEnv: get('NODE_ENV').enum(['development', 'test', 'production']).default('development'),
|
|
10729
|
-
stage:
|
|
10954
|
+
stage: ${stageEnv(options.stages)},
|
|
10730
10955
|
}))
|
|
10731
10956
|
.parse();
|
|
10732
10957
|
`
|
|
@@ -10816,7 +11041,7 @@ export const cleanup = worker
|
|
|
10816
11041
|
});
|
|
10817
11042
|
`
|
|
10818
11043
|
});
|
|
10819
|
-
if (options.database) files$1.push(...databaseFiles(
|
|
11044
|
+
if (options.database) files$1.push(...databaseFiles());
|
|
10820
11045
|
if (options.telescope) files$1.push({
|
|
10821
11046
|
path: "src/config/telescope.ts",
|
|
10822
11047
|
content: `import { Telescope } from '@geekmidas/telescope';
|
|
@@ -10837,7 +11062,7 @@ export const telescope = new Telescope({
|
|
|
10837
11062
|
/**
|
|
10838
11063
|
* OpenAPI output path (fixed, not configurable)
|
|
10839
11064
|
*/
|
|
10840
|
-
const OPENAPI_OUTPUT_PATH$1 = "./.gkm/openapi.ts";
|
|
11065
|
+
const OPENAPI_OUTPUT_PATH$1 = "./.gkm/openapi/api.ts";
|
|
10841
11066
|
/**
|
|
10842
11067
|
* Endpoints output path (generated by gkm dev/build)
|
|
10843
11068
|
*/
|
|
@@ -10943,15 +11168,23 @@ const packageManagerChoices = [
|
|
|
10943
11168
|
/**
|
|
10944
11169
|
* Deploy target choices for prompts
|
|
10945
11170
|
*/
|
|
10946
|
-
const deployTargetChoices = [
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
11171
|
+
const deployTargetChoices = [
|
|
11172
|
+
{
|
|
11173
|
+
title: "Dokploy",
|
|
11174
|
+
value: "dokploy",
|
|
11175
|
+
description: "Deploy to Dokploy (Docker-based hosting)"
|
|
11176
|
+
},
|
|
11177
|
+
{
|
|
11178
|
+
title: "AWS (SST)",
|
|
11179
|
+
value: "sst",
|
|
11180
|
+
description: "Deploy to your AWS account through SST"
|
|
11181
|
+
},
|
|
11182
|
+
{
|
|
11183
|
+
title: "Configure later",
|
|
11184
|
+
value: "none",
|
|
11185
|
+
description: "Skip deployment setup for now"
|
|
11186
|
+
}
|
|
11187
|
+
];
|
|
10955
11188
|
/**
|
|
10956
11189
|
* Services choices for multi-select prompt
|
|
10957
11190
|
*/
|
|
@@ -11044,12 +11277,12 @@ function generatePackageJson(options, template) {
|
|
|
11044
11277
|
if (declares && services.mail) dependencies$1["@geekmidas/emailkit"] = GEEKMIDAS_VERSIONS["@geekmidas/emailkit"];
|
|
11045
11278
|
if (database) {
|
|
11046
11279
|
dependencies$1["@geekmidas/db"] = GEEKMIDAS_VERSIONS["@geekmidas/db"];
|
|
11047
|
-
dependencies$1.kysely =
|
|
11048
|
-
dependencies$1.pg =
|
|
11049
|
-
devDependencies$1["@types/pg"] = "~8.
|
|
11280
|
+
dependencies$1.kysely = DEPENDENCY_VERSIONS.kysely;
|
|
11281
|
+
dependencies$1.pg = DEPENDENCY_VERSIONS.pg;
|
|
11282
|
+
devDependencies$1["@types/pg"] = "~8.23.1";
|
|
11050
11283
|
devDependencies$1["@geekmidas/testkit"] = GEEKMIDAS_VERSIONS["@geekmidas/testkit"];
|
|
11051
11284
|
devDependencies$1["@faker-js/faker"] = "~9.8.0";
|
|
11052
|
-
devDependencies$1["kysely-ctl"] = "
|
|
11285
|
+
devDependencies$1["kysely-ctl"] = DEPENDENCY_VERSIONS["kysely-ctl"];
|
|
11053
11286
|
devDependencies$1["vite-tsconfig-paths"] = "~5.1.0";
|
|
11054
11287
|
}
|
|
11055
11288
|
if (monorepo) {
|
|
@@ -11069,6 +11302,8 @@ function generatePackageJson(options, template) {
|
|
|
11069
11302
|
const appName = pathParts[pathParts.length - 1] || "api";
|
|
11070
11303
|
packageName = `@${name$1}/${appName}`;
|
|
11071
11304
|
}
|
|
11305
|
+
dependencies$1["@geekmidas/client"] = GEEKMIDAS_VERSIONS["@geekmidas/client"];
|
|
11306
|
+
dependencies$1["@tanstack/react-query"] = DEPENDENCY_VERSIONS["@tanstack/react-query"];
|
|
11072
11307
|
const packageJson = {
|
|
11073
11308
|
name: packageName,
|
|
11074
11309
|
version: "0.0.1",
|
|
@@ -11106,7 +11341,7 @@ function generateSourceFiles(options, template) {
|
|
|
11106
11341
|
*/
|
|
11107
11342
|
function generateTestFiles(options, _template) {
|
|
11108
11343
|
if (!options.database) return [];
|
|
11109
|
-
const db = databaseFor(
|
|
11344
|
+
const db = databaseFor();
|
|
11110
11345
|
const declares = !options.monorepo;
|
|
11111
11346
|
const schema = declares ? "../src/constructs/database.ts" : "~/services/database.ts";
|
|
11112
11347
|
const runtimeUrl = declares ? db.urlKey : "DATABASE_URL";
|
|
@@ -11142,14 +11377,16 @@ import pg from 'pg';
|
|
|
11142
11377
|
import { wrapVitestKyselyTransaction } from '@geekmidas/testkit/kysely';
|
|
11143
11378
|
import type { Database } from '${schema}';
|
|
11144
11379
|
|
|
11145
|
-
const
|
|
11380
|
+
const db = new Kysely<Database>({
|
|
11146
11381
|
dialect: new PostgresDialect({
|
|
11147
11382
|
pool: new pg.Pool({ connectionString: process.env.${runtimeUrl} }),
|
|
11148
11383
|
}),
|
|
11149
11384
|
});
|
|
11150
11385
|
|
|
11151
11386
|
export const it = wrapVitestKyselyTransaction<Database>(itVitest, {
|
|
11152
|
-
|
|
11387
|
+
// A function, or a construct: an instance is taken for a construct and has
|
|
11388
|
+
// no service to register.
|
|
11389
|
+
connection: () => db,
|
|
11153
11390
|
});
|
|
11154
11391
|
`
|
|
11155
11392
|
},
|
|
@@ -11234,10 +11471,10 @@ export const usersBuilder = KyselyFactory.createBuilder<Database, 'users'>(
|
|
|
11234
11471
|
import { it } from './config.ts';
|
|
11235
11472
|
|
|
11236
11473
|
describe('example', () => {
|
|
11237
|
-
it('should have a working test setup', async ({
|
|
11238
|
-
//
|
|
11239
|
-
//
|
|
11240
|
-
expect(
|
|
11474
|
+
it('should have a working test setup', async ({ trx }) => {
|
|
11475
|
+
// trx is a Kysely transaction; everything in it is rolled back after the
|
|
11476
|
+
// test
|
|
11477
|
+
expect(trx).toBeDefined();
|
|
11241
11478
|
});
|
|
11242
11479
|
});
|
|
11243
11480
|
`
|
|
@@ -12488,8 +12725,8 @@ export const Alert: Story = {
|
|
|
12488
12725
|
),
|
|
12489
12726
|
};
|
|
12490
12727
|
`;
|
|
12491
|
-
const componentsUiIndex = `export { Button, type ButtonProps, buttonVariants } from './button.tsx';
|
|
12492
|
-
export { Input } from './input.tsx';
|
|
12728
|
+
const componentsUiIndex = `export { Button, type ButtonProps, buttonVariants } from './button/index.tsx';
|
|
12729
|
+
export { Input } from './input/index.tsx';
|
|
12493
12730
|
export {
|
|
12494
12731
|
Card,
|
|
12495
12732
|
CardHeader,
|
|
@@ -12497,17 +12734,17 @@ export {
|
|
|
12497
12734
|
CardTitle,
|
|
12498
12735
|
CardDescription,
|
|
12499
12736
|
CardContent,
|
|
12500
|
-
} from './card.tsx';
|
|
12501
|
-
export { Label } from './label.tsx';
|
|
12502
|
-
export { Badge, type BadgeProps, badgeVariants } from './badge.tsx';
|
|
12503
|
-
export { Separator } from './separator.tsx';
|
|
12504
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs.tsx';
|
|
12737
|
+
} from './card/index.tsx';
|
|
12738
|
+
export { Label } from './label/index.tsx';
|
|
12739
|
+
export { Badge, type BadgeProps, badgeVariants } from './badge/index.tsx';
|
|
12740
|
+
export { Separator } from './separator/index.tsx';
|
|
12741
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs/index.tsx';
|
|
12505
12742
|
export {
|
|
12506
12743
|
Tooltip,
|
|
12507
12744
|
TooltipTrigger,
|
|
12508
12745
|
TooltipContent,
|
|
12509
12746
|
TooltipProvider,
|
|
12510
|
-
} from './tooltip.tsx';
|
|
12747
|
+
} from './tooltip/index.tsx';
|
|
12511
12748
|
export {
|
|
12512
12749
|
Dialog,
|
|
12513
12750
|
DialogPortal,
|
|
@@ -12519,7 +12756,7 @@ export {
|
|
|
12519
12756
|
DialogFooter,
|
|
12520
12757
|
DialogTitle,
|
|
12521
12758
|
DialogDescription,
|
|
12522
|
-
} from './dialog.tsx';
|
|
12759
|
+
} from './dialog/index.tsx';
|
|
12523
12760
|
`;
|
|
12524
12761
|
const buttonIndexTsx = buttonTsx;
|
|
12525
12762
|
const inputIndexTsx = inputTsx;
|
|
@@ -12687,8 +12924,8 @@ function generateWebAppFiles(options) {
|
|
|
12687
12924
|
[uiPackage]: "workspace:*",
|
|
12688
12925
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
12689
12926
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
12690
|
-
"@tanstack/react-query": "
|
|
12691
|
-
"better-auth": "
|
|
12927
|
+
"@tanstack/react-query": DEPENDENCY_VERSIONS["@tanstack/react-query"],
|
|
12928
|
+
"better-auth": DEPENDENCY_VERSIONS["better-auth"],
|
|
12692
12929
|
next: "~16.1.0",
|
|
12693
12930
|
react: "~19.2.0",
|
|
12694
12931
|
"react-dom": "~19.2.0"
|
|
@@ -12744,7 +12981,7 @@ export default nextConfig;
|
|
|
12744
12981
|
baseUrl: ".",
|
|
12745
12982
|
paths: {
|
|
12746
12983
|
"~/*": ["./src/*", "../../packages/ui/src/*"],
|
|
12747
|
-
[`${apiPackage}/client`]: ["../../apps/api/.gkm/openapi.ts"],
|
|
12984
|
+
[`${apiPackage}/client`]: ["../../apps/api/.gkm/openapi/api.ts"],
|
|
12748
12985
|
[`${modelsPackage}`]: ["../../packages/models/src"],
|
|
12749
12986
|
[`${modelsPackage}/*`]: ["../../packages/models/src/*"],
|
|
12750
12987
|
[`${uiPackage}`]: ["../../packages/ui/src"],
|
|
@@ -12757,12 +12994,7 @@ export default nextConfig;
|
|
|
12757
12994
|
"**/*.tsx",
|
|
12758
12995
|
".next/types/**/*.ts"
|
|
12759
12996
|
],
|
|
12760
|
-
exclude: ["node_modules"]
|
|
12761
|
-
references: [
|
|
12762
|
-
{ path: "../../apps/api" },
|
|
12763
|
-
{ path: "../../packages/ui" },
|
|
12764
|
-
{ path: "../../packages/models" }
|
|
12765
|
-
]
|
|
12997
|
+
exclude: ["node_modules"]
|
|
12766
12998
|
};
|
|
12767
12999
|
const queryClientTs = `import { QueryClient } from '@tanstack/react-query';
|
|
12768
13000
|
|
|
@@ -13025,10 +13257,10 @@ function generateTanStackWebFiles(options) {
|
|
|
13025
13257
|
[uiPackage]: "workspace:*",
|
|
13026
13258
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
13027
13259
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
13028
|
-
"@tanstack/react-query": "
|
|
13260
|
+
"@tanstack/react-query": DEPENDENCY_VERSIONS["@tanstack/react-query"],
|
|
13029
13261
|
"@tanstack/react-router": "^1.87.0",
|
|
13030
13262
|
"@tanstack/react-start": "^1.87.0",
|
|
13031
|
-
"better-auth": "
|
|
13263
|
+
"better-auth": DEPENDENCY_VERSIONS["better-auth"],
|
|
13032
13264
|
react: "~19.2.0",
|
|
13033
13265
|
"react-dom": "~19.2.0"
|
|
13034
13266
|
},
|
|
@@ -13096,7 +13328,7 @@ export default defineConfig({
|
|
|
13096
13328
|
[`${modelsPackage}/*`]: ["../../packages/models/src/*"],
|
|
13097
13329
|
[uiPackage]: ["../../packages/ui/src"],
|
|
13098
13330
|
[`${uiPackage}/*`]: ["../../packages/ui/src/*"],
|
|
13099
|
-
[`${apiPackage}/client`]: ["../../apps/api/.gkm/openapi.ts"]
|
|
13331
|
+
[`${apiPackage}/client`]: ["../../apps/api/.gkm/openapi/api.ts"]
|
|
13100
13332
|
}
|
|
13101
13333
|
},
|
|
13102
13334
|
include: [
|
|
@@ -13386,12 +13618,27 @@ function detectPackageManager(cwd = process.cwd()) {
|
|
|
13386
13618
|
if (userAgent.includes("bun")) return "bun";
|
|
13387
13619
|
return "npm";
|
|
13388
13620
|
}
|
|
13621
|
+
/** `gkm init`'s flags, which a name can swallow when a space goes missing. */
|
|
13622
|
+
const INIT_FLAGS = [
|
|
13623
|
+
"yes",
|
|
13624
|
+
"name",
|
|
13625
|
+
"template",
|
|
13626
|
+
"monorepo",
|
|
13627
|
+
"api-path",
|
|
13628
|
+
"pm",
|
|
13629
|
+
"skip-install"
|
|
13630
|
+
];
|
|
13389
13631
|
/**
|
|
13390
13632
|
* Validate project name for npm package naming conventions
|
|
13391
13633
|
*/
|
|
13392
13634
|
function validateProjectName(name$1) {
|
|
13393
13635
|
if (!name$1) return "Project name is required";
|
|
13394
13636
|
if (!/^[a-z0-9-_@/.]+$/i.test(name$1)) return "Project name can only contain letters, numbers, hyphens, underscores, @, /, and .";
|
|
13637
|
+
const flag = name$1.match(/--(\w[\w-]*)$/)?.[1];
|
|
13638
|
+
if (flag && INIT_FLAGS.includes(flag)) {
|
|
13639
|
+
const intended = name$1.slice(0, -(flag.length + 2));
|
|
13640
|
+
return `"${name$1}" ends in the --${flag} flag. Did you mean \`${intended} --${flag}\`?`;
|
|
13641
|
+
}
|
|
13395
13642
|
const reserved = [
|
|
13396
13643
|
"node_modules",
|
|
13397
13644
|
".git",
|
|
@@ -13431,6 +13678,18 @@ function getRunCommand(pkgManager, script) {
|
|
|
13431
13678
|
default: return `npm run ${script}`;
|
|
13432
13679
|
}
|
|
13433
13680
|
}
|
|
13681
|
+
/**
|
|
13682
|
+
* Run a binary the project installed, through its package manager — so it is
|
|
13683
|
+
* the project's own version, not whatever \`npx\` would fetch.
|
|
13684
|
+
*/
|
|
13685
|
+
function getExecCommand(pkgManager, command) {
|
|
13686
|
+
switch (pkgManager) {
|
|
13687
|
+
case "pnpm": return `pnpm exec ${command}`;
|
|
13688
|
+
case "yarn": return `yarn ${command}`;
|
|
13689
|
+
case "bun": return `bunx ${command}`;
|
|
13690
|
+
default: return `npx --no-install ${command}`;
|
|
13691
|
+
}
|
|
13692
|
+
}
|
|
13434
13693
|
const lockfileByPm = {
|
|
13435
13694
|
pnpm: "pnpm-lock.yaml",
|
|
13436
13695
|
yarn: "yarn.lock",
|
|
@@ -13501,12 +13760,31 @@ function findWorkspacePackages(cwd, pm) {
|
|
|
13501
13760
|
|
|
13502
13761
|
//#endregion
|
|
13503
13762
|
//#region src/init/index.ts
|
|
13763
|
+
/** `' staging, prod '` → `['staging', 'prod']`. */
|
|
13764
|
+
const stageList = (value) => value.split(",").map((name$1) => name$1.trim()).filter(Boolean);
|
|
13765
|
+
/** `eu-west-1`, `us-gov-west-1`, `ap-southeast-2`. */
|
|
13766
|
+
const AWS_REGION = /^[a-z]{2}(-gov)?-[a-z]+-\d$/;
|
|
13504
13767
|
/**
|
|
13505
13768
|
* Main init command - scaffolds a new project
|
|
13506
13769
|
*/
|
|
13507
13770
|
async function initCommand(projectName, options = {}) {
|
|
13508
13771
|
const cwd = process.cwd();
|
|
13509
13772
|
const detectedPkgManager = detectPackageManager(cwd);
|
|
13773
|
+
if (options.deploy && !deployTargetChoices.some((choice) => choice.value === options.deploy)) throw new Error(`Unknown deploy target "${options.deploy}". Use ${deployTargetChoices.map((c) => c.value).join(", ")}.`);
|
|
13774
|
+
if (options.region && !AWS_REGION.test(options.region)) throw new Error(`"${options.region}" is not an AWS region. Use one like eu-west-1.`);
|
|
13775
|
+
if (options.stages || options.localStage || options.protectedStage) {
|
|
13776
|
+
const deployed = stageList(options.stages ?? "");
|
|
13777
|
+
const problems = [
|
|
13778
|
+
...require_workspace.deployedProblems(deployed),
|
|
13779
|
+
...options.localStage ? require_workspace.stageProblems({
|
|
13780
|
+
local: options.localStage,
|
|
13781
|
+
deployed
|
|
13782
|
+
}) : [],
|
|
13783
|
+
...options.protectedStage && !deployed.includes(options.protectedStage) ? [`--protected-stage "${options.protectedStage}" is not in --stages`] : []
|
|
13784
|
+
];
|
|
13785
|
+
if (problems.length) throw new Error([...new Set(problems)].join("\n"));
|
|
13786
|
+
}
|
|
13787
|
+
let deployedSoFar = [];
|
|
13510
13788
|
prompts.default.override({});
|
|
13511
13789
|
const onCancel = () => {
|
|
13512
13790
|
process.exit(0);
|
|
@@ -13557,12 +13835,49 @@ async function initCommand(projectName, options = {}) {
|
|
|
13557
13835
|
initial: packageManagerChoices.findIndex((c) => c.value === detectedPkgManager)
|
|
13558
13836
|
},
|
|
13559
13837
|
{
|
|
13560
|
-
type: options.yes ? null : "select",
|
|
13838
|
+
type: options.yes || options.deploy ? null : "select",
|
|
13561
13839
|
name: "deployTarget",
|
|
13562
13840
|
message: "Deployment target:",
|
|
13563
13841
|
choices: deployTargetChoices,
|
|
13564
13842
|
initial: 0
|
|
13565
13843
|
},
|
|
13844
|
+
{
|
|
13845
|
+
type: (_prev, values) => !options.yes && !options.region && (options.deploy ?? values.deployTarget) === "sst" ? "text" : null,
|
|
13846
|
+
name: "region",
|
|
13847
|
+
message: "AWS region (e.g. eu-west-1):",
|
|
13848
|
+
validate: (value) => AWS_REGION.test(value.trim()) || "An AWS region, like eu-west-1"
|
|
13849
|
+
},
|
|
13850
|
+
{
|
|
13851
|
+
type: options.yes || options.stages ? null : "text",
|
|
13852
|
+
name: "deployedStages",
|
|
13853
|
+
message: "Deployed stages, comma-separated (e.g. staging, prod):",
|
|
13854
|
+
validate: (value) => {
|
|
13855
|
+
const deployed = stageList(value);
|
|
13856
|
+
if (deployed.length === 0) return "At least one stage";
|
|
13857
|
+
return require_workspace.deployedProblems(deployed)[0] ?? true;
|
|
13858
|
+
}
|
|
13859
|
+
},
|
|
13860
|
+
{
|
|
13861
|
+
type: (_prev, values) => options.yes || options.protectedStage ? null : stageList(options.stages ?? values.deployedStages ?? "").length > 1 ? "select" : null,
|
|
13862
|
+
name: "protectedStage",
|
|
13863
|
+
message: "Which one is production (retained and protected)?",
|
|
13864
|
+
choices: (_prev, values) => stageList(options.stages ?? values.deployedStages ?? "").map((name$2) => ({
|
|
13865
|
+
title: name$2,
|
|
13866
|
+
value: name$2
|
|
13867
|
+
}))
|
|
13868
|
+
},
|
|
13869
|
+
{
|
|
13870
|
+
type: (_prev, values) => {
|
|
13871
|
+
deployedSoFar = stageList(options.stages ?? values.deployedStages ?? "");
|
|
13872
|
+
return options.yes || options.localStage ? null : "text";
|
|
13873
|
+
},
|
|
13874
|
+
name: "localStage",
|
|
13875
|
+
message: "Local stage, what gkm dev runs as (e.g. dev):",
|
|
13876
|
+
validate: (value) => require_workspace.stageProblems({
|
|
13877
|
+
local: value.trim(),
|
|
13878
|
+
deployed: deployedSoFar
|
|
13879
|
+
})[0] ?? true
|
|
13880
|
+
},
|
|
13566
13881
|
{
|
|
13567
13882
|
type: options.yes ? null : "confirm",
|
|
13568
13883
|
name: "telescope",
|
|
@@ -13628,7 +13943,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
13628
13943
|
};
|
|
13629
13944
|
if (services.events === "pgboss") services.db = true;
|
|
13630
13945
|
const pkgManager = options.pm ? options.pm : options.yes ? "pnpm" : answers.packageManager ?? detectedPkgManager;
|
|
13631
|
-
const deployTarget = options.yes ? "
|
|
13946
|
+
const deployTarget = options.deploy ?? (options.yes ? "none" : answers.deployTarget ?? "none");
|
|
13632
13947
|
const database = services.db;
|
|
13633
13948
|
const frontendFramework = isFullstack ? options.yes ? "nextjs" : answers.frontendFramework ?? "nextjs" : void 0;
|
|
13634
13949
|
const templateOptions = {
|
|
@@ -13643,6 +13958,8 @@ async function initCommand(projectName, options = {}) {
|
|
|
13643
13958
|
apiPath: monorepo ? options.apiPath ?? "apps/api" : "",
|
|
13644
13959
|
packageManager: pkgManager,
|
|
13645
13960
|
deployTarget,
|
|
13961
|
+
stages: resolveStages(options, answers),
|
|
13962
|
+
...deployTarget === "sst" ? { region: options.region ?? (options.yes ? "eu-west-1" : answers.region?.trim()) } : {},
|
|
13646
13963
|
services,
|
|
13647
13964
|
frontendFramework
|
|
13648
13965
|
};
|
|
@@ -13718,12 +14035,12 @@ async function initCommand(projectName, options = {}) {
|
|
|
13718
14035
|
if (services.mail) secretServices.push("mailpit");
|
|
13719
14036
|
if (services.events === "sns") secretServices.push("localstack");
|
|
13720
14037
|
if (services.events === "rabbitmq") secretServices.push("rabbitmq");
|
|
13721
|
-
const
|
|
14038
|
+
const local = templateOptions.stages.local;
|
|
14039
|
+
const devSecrets = require_generator.createStageSecrets(local, secretServices, {
|
|
13722
14040
|
projectName: name$1,
|
|
13723
14041
|
eventsBackend: services.events
|
|
13724
14042
|
});
|
|
13725
14043
|
const customSecrets = {
|
|
13726
|
-
NODE_ENV: "development",
|
|
13727
14044
|
PORT: "3000",
|
|
13728
14045
|
LOG_LEVEL: "debug",
|
|
13729
14046
|
JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
@@ -13743,8 +14060,8 @@ async function initCommand(projectName, options = {}) {
|
|
|
13743
14060
|
}
|
|
13744
14061
|
devSecrets.custom = customSecrets;
|
|
13745
14062
|
await require_storage.writeStageSecrets(devSecrets, targetDir);
|
|
13746
|
-
const keyPath = require_storage.getKeyPath(
|
|
13747
|
-
console.log(` Secrets: .gkm/secrets
|
|
14063
|
+
const keyPath = require_storage.getKeyPath(local, name$1);
|
|
14064
|
+
console.log(` Secrets: .gkm/secrets/${local}.json (encrypted)`);
|
|
13748
14065
|
console.log(` Key: ${keyPath}\n`);
|
|
13749
14066
|
if (!options.skipInstall) {
|
|
13750
14067
|
console.log("\n📦 Installing dependencies...\n");
|
|
@@ -13757,11 +14074,13 @@ async function initCommand(projectName, options = {}) {
|
|
|
13757
14074
|
console.error("Failed to install dependencies");
|
|
13758
14075
|
}
|
|
13759
14076
|
try {
|
|
13760
|
-
(0, node_child_process.execSync)("
|
|
14077
|
+
(0, node_child_process.execSync)(getExecCommand(pkgManager, "biome check --write ."), {
|
|
13761
14078
|
cwd: targetDir,
|
|
13762
14079
|
stdio: "inherit"
|
|
13763
14080
|
});
|
|
13764
|
-
} catch {
|
|
14081
|
+
} catch {
|
|
14082
|
+
console.warn(`\n⚠️ Formatting did not finish cleanly. Run ${getRunCommand(pkgManager, "fmt")} to see why.`);
|
|
14083
|
+
}
|
|
13765
14084
|
}
|
|
13766
14085
|
console.log("\n📦 Initializing git repository...\n");
|
|
13767
14086
|
try {
|
|
@@ -13833,19 +14152,46 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
13833
14152
|
console.log(` └── turbo.json # Turbo config`);
|
|
13834
14153
|
console.log("");
|
|
13835
14154
|
}
|
|
14155
|
+
const { local, deployed } = options.stages;
|
|
13836
14156
|
console.log("🔐 Secrets management:");
|
|
13837
|
-
console.log(` gkm secrets:show --stage
|
|
13838
|
-
console.log(` gkm secrets:set KEY VALUE --stage
|
|
13839
|
-
console.log(` gkm secrets:init --stage
|
|
14157
|
+
console.log(` gkm secrets:show --stage ${local} # View secrets`);
|
|
14158
|
+
console.log(` gkm secrets:set KEY VALUE --stage ${local} # Add secret`);
|
|
14159
|
+
for (const stage of deployed) console.log(` gkm secrets:init --stage ${stage} # Create ${stage} secrets`);
|
|
13840
14160
|
console.log("");
|
|
13841
14161
|
if (options.deployTarget === "dokploy") {
|
|
13842
14162
|
console.log("🚀 Deployment:");
|
|
13843
|
-
console.log(` ${getRunCommand(pkgManager,
|
|
14163
|
+
for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)}`);
|
|
14164
|
+
console.log("");
|
|
14165
|
+
}
|
|
14166
|
+
if (options.deployTarget === "sst") {
|
|
14167
|
+
console.log("🚀 Deployment (AWS, through SST):");
|
|
14168
|
+
for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)} # gkm build --provider aws, then sst deploy`);
|
|
14169
|
+
console.log(` Uses your AWS credentials, in ${options.region}.`);
|
|
14170
|
+
if (options.services.mail) console.log(" Set MAIL_FROM to a sender verified in SES first.");
|
|
13844
14171
|
console.log("");
|
|
13845
14172
|
}
|
|
13846
14173
|
console.log("📚 Documentation: https://geekmidas.github.io/toolbox/");
|
|
13847
14174
|
console.log("");
|
|
13848
14175
|
}
|
|
14176
|
+
/**
|
|
14177
|
+
* The stages the project declares, from the answers or the flags.
|
|
14178
|
+
*
|
|
14179
|
+
* `--yes` with no stage flags has nobody to ask and needs an answer, so it
|
|
14180
|
+
* takes `local`, and one deployed stage, `production`, that is protected.
|
|
14181
|
+
*/
|
|
14182
|
+
function resolveStages(options, answers) {
|
|
14183
|
+
const deployed = stageList(options.stages ?? answers.deployedStages ?? (options.yes ? "production" : ""));
|
|
14184
|
+
const kept = options.protectedStage ?? answers.protectedStage ?? (deployed.length === 1 || options.yes ? deployed[0] : void 0);
|
|
14185
|
+
const local = (options.localStage ?? answers.localStage ?? (options.yes ? "local" : "")).trim();
|
|
14186
|
+
const stages = {
|
|
14187
|
+
local,
|
|
14188
|
+
deployed,
|
|
14189
|
+
...kept ? { protected: [kept] } : {}
|
|
14190
|
+
};
|
|
14191
|
+
const problems = require_workspace.stageProblems(stages);
|
|
14192
|
+
if (problems.length) throw new Error(problems.join("\n"));
|
|
14193
|
+
return stages;
|
|
14194
|
+
}
|
|
13849
14195
|
|
|
13850
14196
|
//#endregion
|
|
13851
14197
|
//#region src/secrets/index.ts
|
|
@@ -14056,19 +14402,20 @@ function maskUrl(url) {
|
|
|
14056
14402
|
* Environment variables are sniffed to inject only what the app needs.
|
|
14057
14403
|
*/
|
|
14058
14404
|
async function testCommand(options = {}) {
|
|
14059
|
-
const stage = options.stage ?? "development";
|
|
14060
14405
|
const cwd = process.cwd();
|
|
14406
|
+
const stage = options.stage ?? await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace.stages.local).catch(() => void 0);
|
|
14407
|
+
if (!stage) throw new Error("No stage to test with: add `stages` to gkm.config.ts, or pass --stage.");
|
|
14061
14408
|
console.log(`\n🧪 Running tests with ${stage} environment...\n`);
|
|
14062
14409
|
const autoSetup = options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP);
|
|
14063
14410
|
if (autoSetup) {
|
|
14064
|
-
const { ensureStageSecrets } = await Promise.resolve().then(() => require("./setup-
|
|
14411
|
+
const { ensureStageSecrets } = await Promise.resolve().then(() => require("./setup-BsZvZP6f.cjs"));
|
|
14065
14412
|
const generated = await ensureStageSecrets(stage, cwd);
|
|
14066
14413
|
if (generated) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
|
|
14067
14414
|
}
|
|
14068
14415
|
const defaultEnv = require_credentials$1.loadEnvFiles(".env");
|
|
14069
14416
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
14070
14417
|
const result = await require_credentials$1.prepareEntryCredentials({
|
|
14071
|
-
|
|
14418
|
+
stage,
|
|
14072
14419
|
startDocker: true,
|
|
14073
14420
|
secretsFileName: "test-secrets.json",
|
|
14074
14421
|
resolveDockerPorts: "full",
|
|
@@ -14296,7 +14643,7 @@ program.name("gkm").description("GeekMidas backend framework CLI").version(packa
|
|
|
14296
14643
|
const opts = program.opts();
|
|
14297
14644
|
if (opts.debug) enableDebug();
|
|
14298
14645
|
});
|
|
14299
|
-
program.command("init").description("Scaffold a new project").argument("[name]", "Project name").option("--template <template>", "Project template (minimal, api, serverless, worker)").option("--skip-install", "Skip dependency installation", false).option("-y, --yes", "Skip prompts, use defaults", false).option("--monorepo", "Setup as monorepo with packages/models", false).option("--api-path <path>", "API app path in monorepo (default: apps/api)").option("--pm <manager>", "Package manager (pnpm, npm, yarn, bun)").action(async (name$1, options) => {
|
|
14646
|
+
program.command("init").description("Scaffold a new project").argument("[name]", "Project name").option("--template <template>", "Project template (minimal, api, serverless, worker)").option("--skip-install", "Skip dependency installation", false).option("-y, --yes", "Skip prompts, use defaults", false).option("--monorepo", "Setup as monorepo with packages/models", false).option("--api-path <path>", "API app path in monorepo (default: apps/api)").option("--pm <manager>", "Package manager (pnpm, npm, yarn, bun)").option("--deploy <target>", "Where it deploys (dokploy, sst, none)").option("--region <region>", "AWS region for an SST deploy (e.g. eu-west-1)").option("--stages <names>", "Deployed stages, comma-separated (e.g. staging,prod)").option("--protected-stage <name>", "Which deployed stage is production").option("--local-stage <name>", "What gkm dev runs as (e.g. dev)").option("--region <region>", "AWS region for an SST deploy (e.g. eu-west-1)").action(async (name$1, options) => {
|
|
14300
14647
|
try {
|
|
14301
14648
|
const globalOptions = program.opts();
|
|
14302
14649
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -14316,7 +14663,7 @@ program.command("trust").description("Trust the local edge's certificate authori
|
|
|
14316
14663
|
process.exit(1);
|
|
14317
14664
|
}
|
|
14318
14665
|
});
|
|
14319
|
-
program.command("setup").description("Reconcile declared constructs — containers, databases, buckets, secrets").option("--stage <stage>", "Stage name
|
|
14666
|
+
program.command("setup").description("Reconcile declared constructs — containers, databases, buckets, secrets").option("--stage <stage>", "Stage name (default: stages.local)").option("--force", "Regenerate secrets even if they exist").option("--skip-docker", "Skip starting Docker services").option("-y, --yes", "Skip prompts").action(async (options) => {
|
|
14320
14667
|
try {
|
|
14321
14668
|
const globalOptions = program.opts();
|
|
14322
14669
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -14388,7 +14735,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
|
|
|
14388
14735
|
process.exit(1);
|
|
14389
14736
|
}
|
|
14390
14737
|
});
|
|
14391
|
-
program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from
|
|
14738
|
+
program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from (default: stages.local)").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").option("--auto-setup", "Generate a fresh stage (secrets + key) from the workspace config when none exists (for CI; also via GKM_AUTO_SETUP)").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
|
|
14392
14739
|
try {
|
|
14393
14740
|
const globalOptions = program.opts();
|
|
14394
14741
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -14536,7 +14883,7 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
14536
14883
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
14537
14884
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
14538
14885
|
if (secrets) {
|
|
14539
|
-
const { derivedContainers: derivedContainers$1 } = await Promise.resolve().then(() => require("./workspace-
|
|
14886
|
+
const { derivedContainers: derivedContainers$1 } = await Promise.resolve().then(() => require("./workspace-XXtNhqJl.cjs"));
|
|
14540
14887
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers$1(workspace, options.stage));
|
|
14541
14888
|
if (result) {
|
|
14542
14889
|
await writeStageSecrets$1(result.secrets, workspace.root);
|
|
@@ -14565,7 +14912,7 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
14565
14912
|
console.error(`No remote secrets found for stage "${options.stage}".`);
|
|
14566
14913
|
process.exit(1);
|
|
14567
14914
|
}
|
|
14568
|
-
const { derivedContainers: derivedContainers$1 } = await Promise.resolve().then(() => require("./workspace-
|
|
14915
|
+
const { derivedContainers: derivedContainers$1 } = await Promise.resolve().then(() => require("./workspace-XXtNhqJl.cjs"));
|
|
14569
14916
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers$1(workspace, options.stage));
|
|
14570
14917
|
if (result) {
|
|
14571
14918
|
secrets = result.secrets;
|
|
@@ -14579,7 +14926,7 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
14579
14926
|
process.exit(1);
|
|
14580
14927
|
}
|
|
14581
14928
|
});
|
|
14582
|
-
program.command("secrets:reconcile").description("Backfill missing custom secrets from workspace config").option("--stage <stage>", "Stage name
|
|
14929
|
+
program.command("secrets:reconcile").description("Backfill missing custom secrets from workspace config").option("--stage <stage>", "Stage name (default: stages.local)").action(async (options) => {
|
|
14583
14930
|
try {
|
|
14584
14931
|
const globalOptions = program.opts();
|
|
14585
14932
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -14587,19 +14934,20 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
14587
14934
|
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-D0pimNiy.cjs"));
|
|
14588
14935
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-BM9hGpgv.cjs"));
|
|
14589
14936
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
14590
|
-
const
|
|
14937
|
+
const stage = options.stage ?? workspace.stages.local;
|
|
14938
|
+
const secrets = await readStageSecrets$1(stage, workspace.root);
|
|
14591
14939
|
if (!secrets) {
|
|
14592
|
-
console.error(`No secrets found for stage "${
|
|
14940
|
+
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14593
14941
|
process.exit(1);
|
|
14594
14942
|
}
|
|
14595
|
-
const { derivedContainers: derivedContainers$1 } = await Promise.resolve().then(() => require("./workspace-
|
|
14596
|
-
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers$1(workspace,
|
|
14943
|
+
const { derivedContainers: derivedContainers$1 } = await Promise.resolve().then(() => require("./workspace-XXtNhqJl.cjs"));
|
|
14944
|
+
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers$1(workspace, stage));
|
|
14597
14945
|
if (!result) {
|
|
14598
|
-
console.log(`\n✓ Secrets for stage "${
|
|
14946
|
+
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
14599
14947
|
return;
|
|
14600
14948
|
}
|
|
14601
14949
|
await writeStageSecrets$1(result.secrets, workspace.root);
|
|
14602
|
-
console.log(`\n✓ Reconciled ${result.addedKeys.length} missing secret(s) for stage "${
|
|
14950
|
+
console.log(`\n✓ Reconciled ${result.addedKeys.length} missing secret(s) for stage "${stage}":`);
|
|
14603
14951
|
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
14604
14952
|
} catch (error) {
|
|
14605
14953
|
console.error(formatError(error));
|