@geekmidas/cli 0.54.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +26 -5
- package/dist/CachedStateProvider-D73dCqfH.cjs +60 -0
- package/dist/CachedStateProvider-D73dCqfH.cjs.map +1 -0
- package/dist/CachedStateProvider-DVyKfaMm.mjs +54 -0
- package/dist/CachedStateProvider-DVyKfaMm.mjs.map +1 -0
- package/dist/CachedStateProvider-D_uISMmJ.cjs +3 -0
- package/dist/CachedStateProvider-OiFUGr7p.mjs +3 -0
- package/dist/HostingerProvider-DUV9-Tzg.cjs +210 -0
- package/dist/HostingerProvider-DUV9-Tzg.cjs.map +1 -0
- package/dist/HostingerProvider-DqUq6e9i.mjs +210 -0
- package/dist/HostingerProvider-DqUq6e9i.mjs.map +1 -0
- package/dist/LocalStateProvider-CdspeSVL.cjs +43 -0
- package/dist/LocalStateProvider-CdspeSVL.cjs.map +1 -0
- package/dist/LocalStateProvider-DxoSaWUV.mjs +42 -0
- package/dist/LocalStateProvider-DxoSaWUV.mjs.map +1 -0
- package/dist/Route53Provider-CpRIqu69.cjs +157 -0
- package/dist/Route53Provider-CpRIqu69.cjs.map +1 -0
- package/dist/Route53Provider-KUAX3vz9.mjs +156 -0
- package/dist/Route53Provider-KUAX3vz9.mjs.map +1 -0
- package/dist/SSMStateProvider-BxAPU99a.cjs +53 -0
- package/dist/SSMStateProvider-BxAPU99a.cjs.map +1 -0
- package/dist/SSMStateProvider-C4wp4AZe.mjs +52 -0
- package/dist/SSMStateProvider-C4wp4AZe.mjs.map +1 -0
- package/dist/{bundler-DGry2vaR.mjs → bundler-BqTN5Dj5.mjs} +3 -3
- package/dist/{bundler-DGry2vaR.mjs.map → bundler-BqTN5Dj5.mjs.map} +1 -1
- package/dist/{bundler-BB-kETMd.cjs → bundler-tHLLwYuU.cjs} +3 -3
- package/dist/{bundler-BB-kETMd.cjs.map → bundler-tHLLwYuU.cjs.map} +1 -1
- package/dist/{config-HYiM3iQJ.cjs → config-BGeJsW1r.cjs} +2 -2
- package/dist/{config-HYiM3iQJ.cjs.map → config-BGeJsW1r.cjs.map} +1 -1
- package/dist/{config-C3LSBNSl.mjs → config-C6awcFBx.mjs} +2 -2
- package/dist/{config-C3LSBNSl.mjs.map → config-C6awcFBx.mjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/credentials-C8DWtnMY.cjs +174 -0
- package/dist/credentials-C8DWtnMY.cjs.map +1 -0
- package/dist/credentials-DT1dSxIx.mjs +126 -0
- package/dist/credentials-DT1dSxIx.mjs.map +1 -0
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +1 -1
- package/dist/{dokploy-api-94KzmTVf.mjs → dokploy-api-7k3t7_zd.mjs} +1 -1
- package/dist/{dokploy-api-94KzmTVf.mjs.map → dokploy-api-7k3t7_zd.mjs.map} +1 -1
- package/dist/dokploy-api-CHa8G51l.mjs +3 -0
- package/dist/{dokploy-api-YD8WCQfW.cjs → dokploy-api-CQvhV6Hd.cjs} +1 -1
- package/dist/{dokploy-api-YD8WCQfW.cjs.map → dokploy-api-CQvhV6Hd.cjs.map} +1 -1
- package/dist/dokploy-api-CWc02yyg.cjs +3 -0
- package/dist/{encryption-DaCB_NmS.cjs → encryption-BE0UOb8j.cjs} +1 -1
- package/dist/{encryption-DaCB_NmS.cjs.map → encryption-BE0UOb8j.cjs.map} +1 -1
- package/dist/{encryption-Biq0EZ4m.cjs → encryption-Cv3zips0.cjs} +1 -1
- package/dist/{encryption-BC4MAODn.mjs → encryption-JtMsiGNp.mjs} +1 -1
- package/dist/{encryption-BC4MAODn.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
- package/dist/encryption-UUmaWAmz.mjs +3 -0
- package/dist/{index-pOA56MWT.d.cts → index-B5rGIc4g.d.cts} +553 -196
- package/dist/index-B5rGIc4g.d.cts.map +1 -0
- package/dist/{index-A70abJ1m.d.mts → index-KFEbMIRa.d.mts} +554 -197
- package/dist/index-KFEbMIRa.d.mts.map +1 -0
- package/dist/index.cjs +2265 -658
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2242 -635
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-C3C-BzIZ.mjs → openapi-BMFmLnX6.mjs} +51 -7
- package/dist/openapi-BMFmLnX6.mjs.map +1 -0
- package/dist/{openapi-D7WwlpPF.cjs → openapi-D1KXv2Ml.cjs} +51 -7
- package/dist/openapi-D1KXv2Ml.cjs.map +1 -0
- package/dist/{openapi-react-query-C_MxpBgF.cjs → openapi-react-query-BeXvk-wa.cjs} +1 -1
- package/dist/{openapi-react-query-C_MxpBgF.cjs.map → openapi-react-query-BeXvk-wa.cjs.map} +1 -1
- package/dist/{openapi-react-query-ZoP9DPbY.mjs → openapi-react-query-DGEkD39r.mjs} +1 -1
- package/dist/{openapi-react-query-ZoP9DPbY.mjs.map → openapi-react-query-DGEkD39r.mjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +2 -2
- package/dist/openapi.mjs +3 -3
- package/dist/{storage-Dhst7BhI.mjs → storage-BMW6yLu3.mjs} +1 -1
- package/dist/{storage-Dhst7BhI.mjs.map → storage-BMW6yLu3.mjs.map} +1 -1
- package/dist/{storage-fOR8dMu5.cjs → storage-C7pmBq1u.cjs} +1 -1
- package/dist/{storage-BPRgh3DU.cjs → storage-CoCNe0Pt.cjs} +1 -1
- package/dist/{storage-BPRgh3DU.cjs.map → storage-CoCNe0Pt.cjs.map} +1 -1
- package/dist/{storage-DNj_I11J.mjs → storage-D8XzjVaO.mjs} +1 -1
- package/dist/{types-BtGL-8QS.d.mts → types-BldpmqQX.d.mts} +1 -1
- package/dist/{types-BtGL-8QS.d.mts.map → types-BldpmqQX.d.mts.map} +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-CaVW6j2q.cjs → workspace-BFRUOOrh.cjs} +309 -25
- package/dist/workspace-BFRUOOrh.cjs.map +1 -0
- package/dist/{workspace-DLFRaDc-.mjs → workspace-DAxG3_H2.mjs} +309 -25
- package/dist/workspace-DAxG3_H2.mjs.map +1 -0
- package/package.json +14 -8
- package/scripts/sync-versions.ts +86 -0
- package/src/build/__tests__/handler-templates.spec.ts +115 -47
- package/src/deploy/CachedStateProvider.ts +86 -0
- package/src/deploy/LocalStateProvider.ts +57 -0
- package/src/deploy/SSMStateProvider.ts +93 -0
- package/src/deploy/StateProvider.ts +171 -0
- package/src/deploy/__tests__/CachedStateProvider.spec.ts +228 -0
- package/src/deploy/__tests__/HostingerProvider.spec.ts +347 -0
- package/src/deploy/__tests__/LocalStateProvider.spec.ts +126 -0
- package/src/deploy/__tests__/Route53Provider.spec.ts +402 -0
- package/src/deploy/__tests__/SSMStateProvider.spec.ts +177 -0
- package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +1 -3
- package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +28 -19
- package/src/deploy/__tests__/createDnsProvider.spec.ts +172 -0
- package/src/deploy/__tests__/createStateProvider.spec.ts +116 -0
- package/src/deploy/__tests__/dns-orchestration.spec.ts +192 -0
- package/src/deploy/__tests__/dns-verification.spec.ts +2 -2
- package/src/deploy/__tests__/env-resolver.spec.ts +37 -15
- package/src/deploy/__tests__/sniffer.spec.ts +4 -20
- package/src/deploy/__tests__/state.spec.ts +13 -5
- package/src/deploy/dns/DnsProvider.ts +163 -0
- package/src/deploy/dns/HostingerProvider.ts +100 -0
- package/src/deploy/dns/Route53Provider.ts +256 -0
- package/src/deploy/dns/index.ts +257 -165
- package/src/deploy/env-resolver.ts +12 -5
- package/src/deploy/index.ts +16 -13
- package/src/deploy/sniffer-envkit-patch.ts +3 -1
- package/src/deploy/sniffer-routes-worker.ts +104 -0
- package/src/deploy/sniffer.ts +77 -55
- package/src/deploy/state-commands.ts +274 -0
- package/src/dev/__tests__/entry.spec.ts +8 -2
- package/src/dev/__tests__/index.spec.ts +1 -3
- package/src/dev/index.ts +9 -3
- package/src/docker/__tests__/templates.spec.ts +3 -1
- package/src/index.ts +88 -0
- package/src/init/__tests__/generators.spec.ts +273 -0
- package/src/init/__tests__/init.spec.ts +3 -3
- package/src/init/generators/auth.ts +1 -0
- package/src/init/generators/config.ts +2 -0
- package/src/init/generators/models.ts +6 -1
- package/src/init/generators/monorepo.ts +3 -0
- package/src/init/generators/ui.ts +1472 -0
- package/src/init/generators/web.ts +134 -87
- package/src/init/index.ts +22 -3
- package/src/init/templates/api.ts +109 -3
- package/src/init/versions.ts +25 -53
- package/src/openapi.ts +99 -13
- package/src/workspace/__tests__/schema.spec.ts +107 -0
- package/src/workspace/schema.ts +314 -4
- package/src/workspace/types.ts +22 -36
- package/dist/dokploy-api-CItuaWTq.mjs +0 -3
- package/dist/dokploy-api-DBNE8MDt.cjs +0 -3
- package/dist/encryption-CQXBZGkt.mjs +0 -3
- package/dist/index-A70abJ1m.d.mts.map +0 -1
- package/dist/index-pOA56MWT.d.cts.map +0 -1
- package/dist/openapi-C3C-BzIZ.mjs.map +0 -1
- package/dist/openapi-D7WwlpPF.cjs.map +0 -1
- package/dist/workspace-CaVW6j2q.cjs.map +0 -1
- package/dist/workspace-DLFRaDc-.mjs.map +0 -1
- package/tsconfig.tsbuildinfo +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
|
-
const require_workspace = require('./workspace-
|
|
4
|
-
const require_config = require('./config-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
3
|
+
const require_workspace = require('./workspace-BFRUOOrh.cjs');
|
|
4
|
+
const require_config = require('./config-BGeJsW1r.cjs');
|
|
5
|
+
const require_credentials = require('./credentials-C8DWtnMY.cjs');
|
|
6
|
+
const require_openapi = require('./openapi-D1KXv2Ml.cjs');
|
|
7
|
+
const require_storage = require('./storage-CoCNe0Pt.cjs');
|
|
8
|
+
const require_dokploy_api = require('./dokploy-api-CQvhV6Hd.cjs');
|
|
9
|
+
const require_encryption = require('./encryption-BE0UOb8j.cjs');
|
|
10
|
+
const require_CachedStateProvider = require('./CachedStateProvider-D73dCqfH.cjs');
|
|
11
|
+
const require_openapi_react_query = require('./openapi-react-query-BeXvk-wa.cjs');
|
|
10
12
|
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
11
13
|
const node_path = require_chunk.__toESM(require("node:path"));
|
|
12
14
|
const commander = require_chunk.__toESM(require("commander"));
|
|
13
15
|
const node_process = require_chunk.__toESM(require("node:process"));
|
|
14
16
|
const node_readline_promises = require_chunk.__toESM(require("node:readline/promises"));
|
|
15
17
|
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
16
|
-
const node_os = require_chunk.__toESM(require("node:os"));
|
|
17
18
|
const node_child_process = require_chunk.__toESM(require("node:child_process"));
|
|
18
19
|
const node_net = require_chunk.__toESM(require("node:net"));
|
|
19
20
|
const chokidar = require_chunk.__toESM(require("chokidar"));
|
|
20
21
|
const dotenv = require_chunk.__toESM(require("dotenv"));
|
|
21
22
|
const fast_glob = require_chunk.__toESM(require("fast-glob"));
|
|
22
23
|
const __geekmidas_constructs_crons = require_chunk.__toESM(require("@geekmidas/constructs/crons"));
|
|
23
|
-
const __geekmidas_constructs_endpoints = require_chunk.__toESM(require("@geekmidas/constructs/endpoints"));
|
|
24
24
|
const __geekmidas_constructs_functions = require_chunk.__toESM(require("@geekmidas/constructs/functions"));
|
|
25
25
|
const __geekmidas_constructs_subscribers = require_chunk.__toESM(require("@geekmidas/constructs/subscribers"));
|
|
26
26
|
const node_crypto = require_chunk.__toESM(require("node:crypto"));
|
|
27
27
|
const pg = require_chunk.__toESM(require("pg"));
|
|
28
28
|
const node_dns_promises = require_chunk.__toESM(require("node:dns/promises"));
|
|
29
|
+
const node_module = require_chunk.__toESM(require("node:module"));
|
|
29
30
|
const node_url = require_chunk.__toESM(require("node:url"));
|
|
30
31
|
const prompts = require_chunk.__toESM(require("prompts"));
|
|
31
|
-
const node_module = require_chunk.__toESM(require("node:module"));
|
|
32
32
|
|
|
33
33
|
//#region package.json
|
|
34
34
|
var name = "@geekmidas/cli";
|
|
35
|
-
var version = "0.
|
|
35
|
+
var version = "1.0.0";
|
|
36
36
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
37
37
|
var private$1 = false;
|
|
38
38
|
var type = "module";
|
|
@@ -66,6 +66,8 @@ var exports$1 = {
|
|
|
66
66
|
var bin = { "gkm": "./dist/index.cjs" };
|
|
67
67
|
var scripts = {
|
|
68
68
|
"ts": "tsc --noEmit --skipLibCheck src/**/*.ts",
|
|
69
|
+
"sync-versions": "tsx scripts/sync-versions.ts",
|
|
70
|
+
"prebuild": "pnpm sync-versions",
|
|
69
71
|
"test": "vitest",
|
|
70
72
|
"test:once": "vitest run",
|
|
71
73
|
"test:coverage": "vitest run --coverage"
|
|
@@ -76,6 +78,9 @@ var repository = {
|
|
|
76
78
|
};
|
|
77
79
|
var dependencies = {
|
|
78
80
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
81
|
+
"@aws-sdk/client-route-53": "~3.971.0",
|
|
82
|
+
"@aws-sdk/client-ssm": "~3.971.0",
|
|
83
|
+
"@aws-sdk/credential-providers": "~3.971.0",
|
|
79
84
|
"@geekmidas/constructs": "workspace:~",
|
|
80
85
|
"@geekmidas/envkit": "workspace:~",
|
|
81
86
|
"@geekmidas/errors": "workspace:~",
|
|
@@ -89,7 +94,8 @@ var dependencies = {
|
|
|
89
94
|
"lodash.kebabcase": "^4.1.1",
|
|
90
95
|
"openapi-typescript": "^7.4.2",
|
|
91
96
|
"pg": "~8.17.1",
|
|
92
|
-
"prompts": "~2.4.2"
|
|
97
|
+
"prompts": "~2.4.2",
|
|
98
|
+
"tsx": "~4.20.3"
|
|
93
99
|
};
|
|
94
100
|
var devDependencies = {
|
|
95
101
|
"@geekmidas/testkit": "workspace:*",
|
|
@@ -119,138 +125,6 @@ var package_default = {
|
|
|
119
125
|
peerDependenciesMeta
|
|
120
126
|
};
|
|
121
127
|
|
|
122
|
-
//#endregion
|
|
123
|
-
//#region src/auth/credentials.ts
|
|
124
|
-
/**
|
|
125
|
-
* Get the path to the credentials directory
|
|
126
|
-
*/
|
|
127
|
-
function getCredentialsDir(options) {
|
|
128
|
-
const root = options?.root ?? (0, node_os.homedir)();
|
|
129
|
-
return (0, node_path.join)(root, ".gkm");
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Get the path to the credentials file
|
|
133
|
-
*/
|
|
134
|
-
function getCredentialsPath(options) {
|
|
135
|
-
return (0, node_path.join)(getCredentialsDir(options), "credentials.json");
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Ensure the credentials directory exists
|
|
139
|
-
*/
|
|
140
|
-
function ensureCredentialsDir(options) {
|
|
141
|
-
const dir = getCredentialsDir(options);
|
|
142
|
-
if (!(0, node_fs.existsSync)(dir)) (0, node_fs.mkdirSync)(dir, {
|
|
143
|
-
recursive: true,
|
|
144
|
-
mode: 448
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Read stored credentials from disk
|
|
149
|
-
*/
|
|
150
|
-
async function readCredentials(options) {
|
|
151
|
-
const path = getCredentialsPath(options);
|
|
152
|
-
if (!(0, node_fs.existsSync)(path)) return {};
|
|
153
|
-
try {
|
|
154
|
-
const content = await (0, node_fs_promises.readFile)(path, "utf-8");
|
|
155
|
-
return JSON.parse(content);
|
|
156
|
-
} catch {
|
|
157
|
-
return {};
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Write credentials to disk
|
|
162
|
-
*/
|
|
163
|
-
async function writeCredentials(credentials, options) {
|
|
164
|
-
ensureCredentialsDir(options);
|
|
165
|
-
const path = getCredentialsPath(options);
|
|
166
|
-
await (0, node_fs_promises.writeFile)(path, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Store Dokploy credentials
|
|
170
|
-
*/
|
|
171
|
-
async function storeDokployCredentials(token, endpoint, options) {
|
|
172
|
-
const credentials = await readCredentials(options);
|
|
173
|
-
credentials.dokploy = {
|
|
174
|
-
token,
|
|
175
|
-
endpoint,
|
|
176
|
-
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
177
|
-
};
|
|
178
|
-
await writeCredentials(credentials, options);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Get stored Dokploy credentials
|
|
182
|
-
*/
|
|
183
|
-
async function getDokployCredentials(options) {
|
|
184
|
-
const credentials = await readCredentials(options);
|
|
185
|
-
if (!credentials.dokploy) return null;
|
|
186
|
-
return {
|
|
187
|
-
token: credentials.dokploy.token,
|
|
188
|
-
endpoint: credentials.dokploy.endpoint,
|
|
189
|
-
registryId: credentials.dokploy.registryId
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Remove Dokploy credentials
|
|
194
|
-
*/
|
|
195
|
-
async function removeDokployCredentials(options) {
|
|
196
|
-
const credentials = await readCredentials(options);
|
|
197
|
-
if (!credentials.dokploy) return false;
|
|
198
|
-
delete credentials.dokploy;
|
|
199
|
-
await writeCredentials(credentials, options);
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Get Dokploy API token, checking stored credentials first, then environment
|
|
204
|
-
*/
|
|
205
|
-
async function getDokployToken(options) {
|
|
206
|
-
const envToken = process.env.DOKPLOY_API_TOKEN;
|
|
207
|
-
if (envToken) return envToken;
|
|
208
|
-
const stored = await getDokployCredentials(options);
|
|
209
|
-
if (stored) return stored.token;
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Store Dokploy registry ID
|
|
214
|
-
*/
|
|
215
|
-
async function storeDokployRegistryId(registryId, options) {
|
|
216
|
-
const credentials = await readCredentials(options);
|
|
217
|
-
if (!credentials.dokploy) throw new Error("Dokploy credentials not found. Run \"gkm login --service dokploy\" first.");
|
|
218
|
-
credentials.dokploy.registryId = registryId;
|
|
219
|
-
await writeCredentials(credentials, options);
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Get Dokploy registry ID from stored credentials
|
|
223
|
-
*/
|
|
224
|
-
async function getDokployRegistryId(options) {
|
|
225
|
-
const stored = await getDokployCredentials(options);
|
|
226
|
-
return stored?.registryId ?? void 0;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Store Hostinger API token
|
|
230
|
-
*
|
|
231
|
-
* @param token - API token from hpanel.hostinger.com/profile/api
|
|
232
|
-
*/
|
|
233
|
-
async function storeHostingerToken(token, options) {
|
|
234
|
-
const credentials = await readCredentials(options);
|
|
235
|
-
credentials.hostinger = {
|
|
236
|
-
token,
|
|
237
|
-
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
238
|
-
};
|
|
239
|
-
await writeCredentials(credentials, options);
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Get stored Hostinger API token
|
|
243
|
-
*
|
|
244
|
-
* Checks environment variable first (HOSTINGER_API_TOKEN),
|
|
245
|
-
* then falls back to stored credentials.
|
|
246
|
-
*/
|
|
247
|
-
async function getHostingerToken(options) {
|
|
248
|
-
const envToken = process.env.HOSTINGER_API_TOKEN;
|
|
249
|
-
if (envToken) return envToken;
|
|
250
|
-
const credentials = await readCredentials(options);
|
|
251
|
-
return credentials.hostinger?.token ?? null;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
128
|
//#endregion
|
|
255
129
|
//#region src/auth/index.ts
|
|
256
130
|
const logger$11 = console;
|
|
@@ -258,7 +132,7 @@ const logger$11 = console;
|
|
|
258
132
|
* Validate Dokploy token by making a test API call
|
|
259
133
|
*/
|
|
260
134
|
async function validateDokployToken(endpoint, token) {
|
|
261
|
-
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-
|
|
135
|
+
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-CWc02yyg.cjs"));
|
|
262
136
|
const api = new DokployApi$1({
|
|
263
137
|
baseUrl: endpoint,
|
|
264
138
|
token
|
|
@@ -346,10 +220,10 @@ async function loginCommand(options) {
|
|
|
346
220
|
logger$11.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
347
221
|
process.exit(1);
|
|
348
222
|
}
|
|
349
|
-
await storeDokployCredentials(token, endpoint);
|
|
223
|
+
await require_credentials.storeDokployCredentials(token, endpoint);
|
|
350
224
|
logger$11.log("\n✓ Successfully logged in to Dokploy!");
|
|
351
225
|
logger$11.log(` Endpoint: ${endpoint}`);
|
|
352
|
-
logger$11.log(` Credentials stored in: ${getCredentialsPath()}`);
|
|
226
|
+
logger$11.log(` Credentials stored in: ${require_credentials.getCredentialsPath()}`);
|
|
353
227
|
logger$11.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
354
228
|
}
|
|
355
229
|
}
|
|
@@ -359,13 +233,13 @@ async function loginCommand(options) {
|
|
|
359
233
|
async function logoutCommand(options) {
|
|
360
234
|
const { service = "dokploy" } = options;
|
|
361
235
|
if (service === "all") {
|
|
362
|
-
const dokployRemoved = await removeDokployCredentials();
|
|
236
|
+
const dokployRemoved = await require_credentials.removeDokployCredentials();
|
|
363
237
|
if (dokployRemoved) logger$11.log("\n✓ Logged out from all services");
|
|
364
238
|
else logger$11.log("\nNo stored credentials found");
|
|
365
239
|
return;
|
|
366
240
|
}
|
|
367
241
|
if (service === "dokploy") {
|
|
368
|
-
const removed = await removeDokployCredentials();
|
|
242
|
+
const removed = await require_credentials.removeDokployCredentials();
|
|
369
243
|
if (removed) logger$11.log("\n✓ Logged out from Dokploy");
|
|
370
244
|
else logger$11.log("\nNo Dokploy credentials found");
|
|
371
245
|
}
|
|
@@ -375,13 +249,13 @@ async function logoutCommand(options) {
|
|
|
375
249
|
*/
|
|
376
250
|
async function whoamiCommand() {
|
|
377
251
|
logger$11.log("\n📋 Current credentials:\n");
|
|
378
|
-
const dokploy = await getDokployCredentials();
|
|
252
|
+
const dokploy = await require_credentials.getDokployCredentials();
|
|
379
253
|
if (dokploy) {
|
|
380
254
|
logger$11.log(" Dokploy:");
|
|
381
255
|
logger$11.log(` Endpoint: ${dokploy.endpoint}`);
|
|
382
256
|
logger$11.log(` Token: ${maskToken(dokploy.token)}`);
|
|
383
257
|
} else logger$11.log(" Dokploy: Not logged in");
|
|
384
|
-
logger$11.log(`\n Credentials file: ${getCredentialsPath()}`);
|
|
258
|
+
logger$11.log(`\n Credentials file: ${require_credentials.getCredentialsPath()}`);
|
|
385
259
|
}
|
|
386
260
|
/**
|
|
387
261
|
* Mask a token for display
|
|
@@ -1252,13 +1126,13 @@ async function validateFrontendApp(appName, appPath, workspaceRoot) {
|
|
|
1252
1126
|
if (!hasConfigFile) errors.push(`Next.js config file not found. Expected one of: ${NEXTJS_CONFIG_FILES.join(", ")}`);
|
|
1253
1127
|
const packageJsonPath = (0, node_path.join)(fullPath, "package.json");
|
|
1254
1128
|
if ((0, node_fs.existsSync)(packageJsonPath)) try {
|
|
1255
|
-
const pkg
|
|
1129
|
+
const pkg = require(packageJsonPath);
|
|
1256
1130
|
const deps = {
|
|
1257
|
-
...pkg
|
|
1258
|
-
...pkg
|
|
1131
|
+
...pkg.dependencies,
|
|
1132
|
+
...pkg.devDependencies
|
|
1259
1133
|
};
|
|
1260
1134
|
if (!deps.next) errors.push("Next.js not found in dependencies. Run: pnpm add next react react-dom");
|
|
1261
|
-
if (!pkg
|
|
1135
|
+
if (!pkg.scripts?.dev) warnings.push("No \"dev\" script found in package.json. Turbo expects a \"dev\" script to run.");
|
|
1262
1136
|
} catch {
|
|
1263
1137
|
errors.push(`Failed to read package.json at ${packageJsonPath}`);
|
|
1264
1138
|
}
|
|
@@ -2153,7 +2027,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2153
2027
|
let masterKey;
|
|
2154
2028
|
if (context.production?.bundle && !skipBundle) {
|
|
2155
2029
|
logger$7.log(`\n📦 Bundling production server...`);
|
|
2156
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
2030
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-tHLLwYuU.cjs"));
|
|
2157
2031
|
const allConstructs = [
|
|
2158
2032
|
...endpoints.map((e) => e.construct),
|
|
2159
2033
|
...functions.map((f) => f.construct),
|
|
@@ -2281,37 +2155,6 @@ function getAppOutputPath(workspace, _appName, app) {
|
|
|
2281
2155
|
//#endregion
|
|
2282
2156
|
//#region src/deploy/state.ts
|
|
2283
2157
|
/**
|
|
2284
|
-
* Get the state file path for a stage
|
|
2285
|
-
*/
|
|
2286
|
-
function getStateFilePath(workspaceRoot, stage) {
|
|
2287
|
-
return (0, node_path.join)(workspaceRoot, ".gkm", `deploy-${stage}.json`);
|
|
2288
|
-
}
|
|
2289
|
-
/**
|
|
2290
|
-
* Read the deploy state for a stage
|
|
2291
|
-
* Returns null if state file doesn't exist
|
|
2292
|
-
*/
|
|
2293
|
-
async function readStageState(workspaceRoot, stage) {
|
|
2294
|
-
const filePath = getStateFilePath(workspaceRoot, stage);
|
|
2295
|
-
try {
|
|
2296
|
-
const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
2297
|
-
return JSON.parse(content);
|
|
2298
|
-
} catch (error) {
|
|
2299
|
-
if (error.code === "ENOENT") return null;
|
|
2300
|
-
console.warn(`Warning: Could not read deploy state: ${error}`);
|
|
2301
|
-
return null;
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
/**
|
|
2305
|
-
* Write the deploy state for a stage
|
|
2306
|
-
*/
|
|
2307
|
-
async function writeStageState(workspaceRoot, stage, state) {
|
|
2308
|
-
const filePath = getStateFilePath(workspaceRoot, stage);
|
|
2309
|
-
const dir = (0, node_path.join)(workspaceRoot, ".gkm");
|
|
2310
|
-
await (0, node_fs_promises.mkdir)(dir, { recursive: true });
|
|
2311
|
-
state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2312
|
-
await (0, node_fs_promises.writeFile)(filePath, JSON.stringify(state, null, 2));
|
|
2313
|
-
}
|
|
2314
|
-
/**
|
|
2315
2158
|
* Create a new empty state for a stage
|
|
2316
2159
|
*/
|
|
2317
2160
|
function createEmptyState(stage, environmentId) {
|
|
@@ -2406,155 +2249,90 @@ function isDnsVerified(state, hostname, serverIp) {
|
|
|
2406
2249
|
}
|
|
2407
2250
|
|
|
2408
2251
|
//#endregion
|
|
2409
|
-
//#region src/deploy/dns/
|
|
2252
|
+
//#region src/deploy/dns/DnsProvider.ts
|
|
2253
|
+
/**
|
|
2254
|
+
* Check if value is a DnsProvider implementation.
|
|
2255
|
+
*/
|
|
2256
|
+
function isDnsProvider(value) {
|
|
2257
|
+
return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.getRecords === "function" && typeof value.upsertRecords === "function";
|
|
2258
|
+
}
|
|
2410
2259
|
/**
|
|
2411
|
-
*
|
|
2260
|
+
* Create a DNS provider based on configuration.
|
|
2412
2261
|
*
|
|
2413
|
-
*
|
|
2414
|
-
*
|
|
2262
|
+
* - 'hostinger': HostingerProvider
|
|
2263
|
+
* - 'route53': Route53Provider
|
|
2264
|
+
* - 'manual': Returns null (user handles DNS)
|
|
2265
|
+
* - Custom: Use provided DnsProvider implementation
|
|
2266
|
+
*/
|
|
2267
|
+
async function createDnsProvider(options) {
|
|
2268
|
+
const { config } = options;
|
|
2269
|
+
if (config.provider === "manual") return null;
|
|
2270
|
+
if (isDnsProvider(config.provider)) return config.provider;
|
|
2271
|
+
const provider = config.provider;
|
|
2272
|
+
if (provider === "hostinger") {
|
|
2273
|
+
const { HostingerProvider } = await Promise.resolve().then(() => require("./HostingerProvider-DUV9-Tzg.cjs"));
|
|
2274
|
+
return new HostingerProvider();
|
|
2275
|
+
}
|
|
2276
|
+
if (provider === "route53") {
|
|
2277
|
+
const { Route53Provider } = await Promise.resolve().then(() => require("./Route53Provider-CpRIqu69.cjs"));
|
|
2278
|
+
const route53Config = config;
|
|
2279
|
+
return new Route53Provider({
|
|
2280
|
+
region: route53Config.region,
|
|
2281
|
+
profile: route53Config.profile,
|
|
2282
|
+
hostedZoneId: route53Config.hostedZoneId
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
if (provider === "cloudflare") throw new Error("Cloudflare DNS provider not yet implemented");
|
|
2286
|
+
throw new Error(`Unknown DNS provider: ${JSON.stringify(config)}`);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
//#endregion
|
|
2290
|
+
//#region src/deploy/dns/index.ts
|
|
2291
|
+
const logger$6 = console;
|
|
2292
|
+
/**
|
|
2293
|
+
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
2415
2294
|
*/
|
|
2416
|
-
|
|
2295
|
+
function isLegacyDnsConfig(config) {
|
|
2296
|
+
return typeof config === "object" && config !== null && "provider" in config && "domain" in config;
|
|
2297
|
+
}
|
|
2417
2298
|
/**
|
|
2418
|
-
*
|
|
2299
|
+
* Normalize DNS config to new multi-domain format
|
|
2419
2300
|
*/
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
this.statusText = statusText;
|
|
2425
|
-
this.errors = errors;
|
|
2426
|
-
this.name = "HostingerApiError";
|
|
2301
|
+
function normalizeDnsConfig(config) {
|
|
2302
|
+
if (isLegacyDnsConfig(config)) {
|
|
2303
|
+
const { domain,...providerConfig } = config;
|
|
2304
|
+
return { [domain]: providerConfig };
|
|
2427
2305
|
}
|
|
2428
|
-
|
|
2306
|
+
return config;
|
|
2307
|
+
}
|
|
2429
2308
|
/**
|
|
2430
|
-
*
|
|
2309
|
+
* Find the root domain for a hostname from available DNS configs
|
|
2431
2310
|
*
|
|
2432
2311
|
* @example
|
|
2433
|
-
*
|
|
2434
|
-
*
|
|
2435
|
-
*
|
|
2436
|
-
* // Get all records for a domain
|
|
2437
|
-
* const records = await api.getRecords('traflabs.io');
|
|
2438
|
-
*
|
|
2439
|
-
* // Create/update records
|
|
2440
|
-
* await api.upsertRecords('traflabs.io', [
|
|
2441
|
-
* { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
|
|
2442
|
-
* ]);
|
|
2443
|
-
* ```
|
|
2312
|
+
* findRootDomain('api.geekmidas.com', { 'geekmidas.com': {...}, 'geekmidas.dev': {...} })
|
|
2313
|
+
* // Returns 'geekmidas.com'
|
|
2444
2314
|
*/
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
},
|
|
2461
|
-
body: body ? JSON.stringify(body) : void 0
|
|
2462
|
-
});
|
|
2463
|
-
if (!response.ok) {
|
|
2464
|
-
let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
|
|
2465
|
-
let errors;
|
|
2466
|
-
try {
|
|
2467
|
-
const errorBody = await response.json();
|
|
2468
|
-
if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
|
|
2469
|
-
errors = errorBody.errors;
|
|
2470
|
-
} catch {}
|
|
2471
|
-
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
2315
|
+
function findRootDomain(hostname, dnsConfig) {
|
|
2316
|
+
const domains = Object.keys(dnsConfig).sort((a, b) => b.length - a.length);
|
|
2317
|
+
for (const domain of domains) if (hostname === domain || hostname.endsWith(`.${domain}`)) return domain;
|
|
2318
|
+
return null;
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Group hostnames by their root domain
|
|
2322
|
+
*/
|
|
2323
|
+
function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
2324
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2325
|
+
for (const [appName, hostname] of appHostnames) {
|
|
2326
|
+
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
2327
|
+
if (!rootDomain) {
|
|
2328
|
+
logger$6.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
2329
|
+
continue;
|
|
2472
2330
|
}
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
return JSON.parse(text);
|
|
2476
|
-
}
|
|
2477
|
-
/**
|
|
2478
|
-
* Get all DNS records for a domain
|
|
2479
|
-
*
|
|
2480
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2481
|
-
*/
|
|
2482
|
-
async getRecords(domain) {
|
|
2483
|
-
const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
|
|
2484
|
-
return response.data || [];
|
|
2485
|
-
}
|
|
2486
|
-
/**
|
|
2487
|
-
* Create or update DNS records
|
|
2488
|
-
*
|
|
2489
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2490
|
-
* @param records - Records to create/update
|
|
2491
|
-
* @param overwrite - If true, replaces all existing records. If false, merges with existing.
|
|
2492
|
-
*/
|
|
2493
|
-
async upsertRecords(domain, records, overwrite = false) {
|
|
2494
|
-
await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
|
|
2495
|
-
overwrite,
|
|
2496
|
-
zone: records
|
|
2497
|
-
});
|
|
2498
|
-
}
|
|
2499
|
-
/**
|
|
2500
|
-
* Validate DNS records before applying
|
|
2501
|
-
*
|
|
2502
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2503
|
-
* @param records - Records to validate
|
|
2504
|
-
* @returns true if valid, throws if invalid
|
|
2505
|
-
*/
|
|
2506
|
-
async validateRecords(domain, records) {
|
|
2507
|
-
await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
|
|
2508
|
-
overwrite: false,
|
|
2509
|
-
zone: records
|
|
2510
|
-
});
|
|
2511
|
-
return true;
|
|
2331
|
+
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
2332
|
+
grouped.get(rootDomain).set(appName, hostname);
|
|
2512
2333
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
*
|
|
2516
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2517
|
-
* @param filters - Filters to match records for deletion
|
|
2518
|
-
*/
|
|
2519
|
-
async deleteRecords(domain, filters) {
|
|
2520
|
-
await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
|
|
2521
|
-
}
|
|
2522
|
-
/**
|
|
2523
|
-
* Check if a specific record exists
|
|
2524
|
-
*
|
|
2525
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2526
|
-
* @param name - Subdomain name (e.g., 'api.joemoer')
|
|
2527
|
-
* @param type - Record type (e.g., 'A')
|
|
2528
|
-
*/
|
|
2529
|
-
async recordExists(domain, name$1, type$1 = "A") {
|
|
2530
|
-
const records = await this.getRecords(domain);
|
|
2531
|
-
return records.some((r) => r.name === name$1 && r.type === type$1);
|
|
2532
|
-
}
|
|
2533
|
-
/**
|
|
2534
|
-
* Create a single A record if it doesn't exist
|
|
2535
|
-
*
|
|
2536
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2537
|
-
* @param subdomain - Subdomain name (e.g., 'api.joemoer')
|
|
2538
|
-
* @param ip - IP address to point to
|
|
2539
|
-
* @param ttl - TTL in seconds (default: 300)
|
|
2540
|
-
* @returns true if created, false if already exists
|
|
2541
|
-
*/
|
|
2542
|
-
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
2543
|
-
const exists = await this.recordExists(domain, subdomain, "A");
|
|
2544
|
-
if (exists) return false;
|
|
2545
|
-
await this.upsertRecords(domain, [{
|
|
2546
|
-
name: subdomain,
|
|
2547
|
-
type: "A",
|
|
2548
|
-
ttl,
|
|
2549
|
-
records: [{ content: ip }]
|
|
2550
|
-
}]);
|
|
2551
|
-
return true;
|
|
2552
|
-
}
|
|
2553
|
-
};
|
|
2554
|
-
|
|
2555
|
-
//#endregion
|
|
2556
|
-
//#region src/deploy/dns/index.ts
|
|
2557
|
-
const logger$6 = console;
|
|
2334
|
+
return grouped;
|
|
2335
|
+
}
|
|
2558
2336
|
/**
|
|
2559
2337
|
* Resolve IP address from a hostname
|
|
2560
2338
|
*/
|
|
@@ -2626,127 +2404,91 @@ function printDnsRecordsSimple(records, rootDomain) {
|
|
|
2626
2404
|
logger$6.log("");
|
|
2627
2405
|
}
|
|
2628
2406
|
/**
|
|
2629
|
-
*
|
|
2630
|
-
*/
|
|
2631
|
-
async function promptForToken(message) {
|
|
2632
|
-
const { stdin, stdout } = await import("node:process");
|
|
2633
|
-
if (!stdin.isTTY) throw new Error("Interactive input required for Hostinger token.");
|
|
2634
|
-
stdout.write(message);
|
|
2635
|
-
return new Promise((resolve$3) => {
|
|
2636
|
-
let value = "";
|
|
2637
|
-
const onData = (char) => {
|
|
2638
|
-
const c = char.toString();
|
|
2639
|
-
if (c === "\n" || c === "\r") {
|
|
2640
|
-
stdin.setRawMode(false);
|
|
2641
|
-
stdin.pause();
|
|
2642
|
-
stdin.removeListener("data", onData);
|
|
2643
|
-
stdout.write("\n");
|
|
2644
|
-
resolve$3(value);
|
|
2645
|
-
} else if (c === "") {
|
|
2646
|
-
stdin.setRawMode(false);
|
|
2647
|
-
stdin.pause();
|
|
2648
|
-
stdout.write("\n");
|
|
2649
|
-
process.exit(1);
|
|
2650
|
-
} else if (c === "" || c === "\b") {
|
|
2651
|
-
if (value.length > 0) value = value.slice(0, -1);
|
|
2652
|
-
} else value += c;
|
|
2653
|
-
};
|
|
2654
|
-
stdin.setRawMode(true);
|
|
2655
|
-
stdin.resume();
|
|
2656
|
-
stdin.on("data", onData);
|
|
2657
|
-
});
|
|
2658
|
-
}
|
|
2659
|
-
/**
|
|
2660
|
-
* Create DNS records using the configured provider
|
|
2661
|
-
*/
|
|
2662
|
-
async function createDnsRecords(records, dnsConfig) {
|
|
2663
|
-
const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
|
|
2664
|
-
if (provider === "manual") return records.map((r) => ({
|
|
2665
|
-
...r,
|
|
2666
|
-
created: false,
|
|
2667
|
-
existed: false
|
|
2668
|
-
}));
|
|
2669
|
-
if (provider === "hostinger") return createHostingerRecords(records, rootDomain, ttl);
|
|
2670
|
-
if (provider === "cloudflare") {
|
|
2671
|
-
logger$6.log(" ⚠ Cloudflare DNS integration not yet implemented");
|
|
2672
|
-
return records.map((r) => ({
|
|
2673
|
-
...r,
|
|
2674
|
-
error: "Cloudflare not implemented"
|
|
2675
|
-
}));
|
|
2676
|
-
}
|
|
2677
|
-
return records;
|
|
2678
|
-
}
|
|
2679
|
-
/**
|
|
2680
|
-
* Create DNS records at Hostinger
|
|
2407
|
+
* Create DNS records for a single domain using its configured provider
|
|
2681
2408
|
*/
|
|
2682
|
-
async function
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
logger$6.log("\n 📋 Hostinger API token not found.");
|
|
2686
|
-
logger$6.log(" Get your token from: https://hpanel.hostinger.com/profile/api\n");
|
|
2687
|
-
try {
|
|
2688
|
-
token = await promptForToken(" Hostinger API Token: ");
|
|
2689
|
-
await storeHostingerToken(token);
|
|
2690
|
-
logger$6.log(" ✓ Token saved");
|
|
2691
|
-
} catch {
|
|
2692
|
-
logger$6.log(" ⚠ Could not get token, skipping DNS creation");
|
|
2693
|
-
return records.map((r) => ({
|
|
2694
|
-
...r,
|
|
2695
|
-
error: "No API token"
|
|
2696
|
-
}));
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
const api = new HostingerApi(token);
|
|
2700
|
-
const results = [];
|
|
2701
|
-
let existingRecords = [];
|
|
2409
|
+
async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
2410
|
+
const ttl = "ttl" in providerConfig && providerConfig.ttl ? providerConfig.ttl : 300;
|
|
2411
|
+
let provider;
|
|
2702
2412
|
try {
|
|
2703
|
-
|
|
2413
|
+
provider = await createDnsProvider({ config: providerConfig });
|
|
2704
2414
|
} catch (error) {
|
|
2705
2415
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2706
|
-
logger$6.log(` ⚠ Failed to
|
|
2416
|
+
logger$6.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
2707
2417
|
return records.map((r) => ({
|
|
2708
2418
|
...r,
|
|
2709
2419
|
error: message
|
|
2710
2420
|
}));
|
|
2711
2421
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2422
|
+
if (!provider) return records.map((r) => ({
|
|
2423
|
+
...r,
|
|
2424
|
+
created: false,
|
|
2425
|
+
existed: false
|
|
2426
|
+
}));
|
|
2427
|
+
const results = [];
|
|
2428
|
+
const upsertRecords = records.map((r) => ({
|
|
2429
|
+
name: r.subdomain,
|
|
2430
|
+
type: r.type,
|
|
2431
|
+
ttl,
|
|
2432
|
+
value: r.value
|
|
2433
|
+
}));
|
|
2434
|
+
try {
|
|
2435
|
+
const upsertResults = await provider.upsertRecords(rootDomain, upsertRecords);
|
|
2436
|
+
for (const [i, record] of records.entries()) {
|
|
2437
|
+
const result = upsertResults[i];
|
|
2438
|
+
if (!result) {
|
|
2439
|
+
results.push({
|
|
2440
|
+
hostname: record.hostname,
|
|
2441
|
+
subdomain: record.subdomain,
|
|
2442
|
+
type: record.type,
|
|
2443
|
+
value: record.value,
|
|
2444
|
+
appName: record.appName,
|
|
2445
|
+
error: "No result returned from provider"
|
|
2446
|
+
});
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
if (result.unchanged) results.push({
|
|
2450
|
+
hostname: record.hostname,
|
|
2451
|
+
subdomain: record.subdomain,
|
|
2452
|
+
type: record.type,
|
|
2453
|
+
value: record.value,
|
|
2454
|
+
appName: record.appName,
|
|
2717
2455
|
existed: true,
|
|
2718
2456
|
created: false
|
|
2719
2457
|
});
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
}]);
|
|
2729
|
-
results.push({
|
|
2730
|
-
...record,
|
|
2731
|
-
created: true,
|
|
2732
|
-
existed: false
|
|
2733
|
-
});
|
|
2734
|
-
} catch (error) {
|
|
2735
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2736
|
-
results.push({
|
|
2737
|
-
...record,
|
|
2738
|
-
error: message
|
|
2458
|
+
else results.push({
|
|
2459
|
+
hostname: record.hostname,
|
|
2460
|
+
subdomain: record.subdomain,
|
|
2461
|
+
type: record.type,
|
|
2462
|
+
value: record.value,
|
|
2463
|
+
appName: record.appName,
|
|
2464
|
+
created: result.created,
|
|
2465
|
+
existed: !result.created
|
|
2739
2466
|
});
|
|
2740
2467
|
}
|
|
2468
|
+
} catch (error) {
|
|
2469
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2470
|
+
logger$6.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
2471
|
+
return records.map((r) => ({
|
|
2472
|
+
hostname: r.hostname,
|
|
2473
|
+
subdomain: r.subdomain,
|
|
2474
|
+
type: r.type,
|
|
2475
|
+
value: r.value,
|
|
2476
|
+
appName: r.appName,
|
|
2477
|
+
error: message
|
|
2478
|
+
}));
|
|
2741
2479
|
}
|
|
2742
2480
|
return results;
|
|
2743
2481
|
}
|
|
2744
2482
|
/**
|
|
2745
2483
|
* Main DNS orchestration function for deployments
|
|
2484
|
+
*
|
|
2485
|
+
* Supports both legacy single-domain format and new multi-domain format:
|
|
2486
|
+
* - Legacy: { provider: 'hostinger', domain: 'example.com' }
|
|
2487
|
+
* - Multi: { 'example.com': { provider: 'hostinger' }, 'example.dev': { provider: 'route53' } }
|
|
2746
2488
|
*/
|
|
2747
2489
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
|
|
2748
2490
|
if (!dnsConfig) return null;
|
|
2749
|
-
const
|
|
2491
|
+
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
2750
2492
|
logger$6.log("\n🌐 Setting up DNS records...");
|
|
2751
2493
|
let serverIp;
|
|
2752
2494
|
try {
|
|
@@ -2758,31 +2500,43 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
|
|
|
2758
2500
|
logger$6.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
2759
2501
|
return null;
|
|
2760
2502
|
}
|
|
2761
|
-
const
|
|
2762
|
-
if (
|
|
2763
|
-
logger$6.log(" No DNS records needed");
|
|
2503
|
+
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
2504
|
+
if (groupedHostnames.size === 0) {
|
|
2505
|
+
logger$6.log(" No DNS records needed (no hostnames match configured domains)");
|
|
2764
2506
|
return {
|
|
2765
2507
|
records: [],
|
|
2766
2508
|
success: true,
|
|
2767
2509
|
serverIp
|
|
2768
2510
|
};
|
|
2769
2511
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2512
|
+
const allRecords = [];
|
|
2513
|
+
let hasFailures = false;
|
|
2514
|
+
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
2515
|
+
const providerConfig = normalizedConfig[rootDomain];
|
|
2516
|
+
if (!providerConfig) {
|
|
2517
|
+
logger$6.log(` ⚠ No provider config for ${rootDomain}`);
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
2521
|
+
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
2522
|
+
if (requiredRecords.length === 0) continue;
|
|
2523
|
+
logger$6.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
2524
|
+
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
2525
|
+
allRecords.push(...domainRecords);
|
|
2526
|
+
const created = domainRecords.filter((r) => r.created).length;
|
|
2527
|
+
const existed = domainRecords.filter((r) => r.existed).length;
|
|
2528
|
+
const failed = domainRecords.filter((r) => r.error).length;
|
|
2529
|
+
if (created > 0) logger$6.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
2530
|
+
if (existed > 0) logger$6.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
2531
|
+
if (failed > 0) {
|
|
2532
|
+
logger$6.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
2533
|
+
hasFailures = true;
|
|
2534
|
+
}
|
|
2535
|
+
printDnsRecordsTable(domainRecords, rootDomain);
|
|
2536
|
+
if (providerConfig.provider === "manual" || failed > 0) printDnsRecordsSimple(domainRecords.filter((r) => !r.created && !r.existed), rootDomain);
|
|
2537
|
+
}
|
|
2784
2538
|
return {
|
|
2785
|
-
records:
|
|
2539
|
+
records: allRecords,
|
|
2786
2540
|
success: !hasFailures,
|
|
2787
2541
|
serverIp
|
|
2788
2542
|
};
|
|
@@ -3611,8 +3365,8 @@ function resolveDockerConfig$1(config) {
|
|
|
3611
3365
|
const docker = config.docker ?? {};
|
|
3612
3366
|
let defaultImageName = "api";
|
|
3613
3367
|
try {
|
|
3614
|
-
const pkg
|
|
3615
|
-
if (pkg
|
|
3368
|
+
const pkg = require(`${process.cwd()}/package.json`);
|
|
3369
|
+
if (pkg.name) defaultImageName = pkg.name.replace(/^@[^/]+\//, "");
|
|
3616
3370
|
} catch {}
|
|
3617
3371
|
return {
|
|
3618
3372
|
registry: docker.registry ?? "",
|
|
@@ -3968,9 +3722,9 @@ async function dockerCommand(options) {
|
|
|
3968
3722
|
} else throw new Error("Monorepo detected but turbo.json not found.\n\nDocker builds in monorepos require Turborepo for proper dependency isolation.\n\nTo fix this:\n 1. Install turbo: pnpm add -Dw turbo\n 2. Create turbo.json in your monorepo root\n 3. Run this command again\n\nSee: https://turbo.build/repo/docs/guides/tools/docker");
|
|
3969
3723
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
3970
3724
|
if (useTurbo && !options.turboPackage) try {
|
|
3971
|
-
const pkg
|
|
3972
|
-
if (pkg
|
|
3973
|
-
turboPackage = pkg
|
|
3725
|
+
const pkg = require(`${process.cwd()}/package.json`);
|
|
3726
|
+
if (pkg.name) {
|
|
3727
|
+
turboPackage = pkg.name;
|
|
3974
3728
|
logger$5.log(` Turbo package: ${turboPackage}`);
|
|
3975
3729
|
}
|
|
3976
3730
|
} catch {}
|
|
@@ -4095,8 +3849,8 @@ function getAppPackageName(appPath) {
|
|
|
4095
3849
|
const pkgPath = (0, node_path.join)(appPath, "package.json");
|
|
4096
3850
|
if (!(0, node_fs.existsSync)(pkgPath)) return void 0;
|
|
4097
3851
|
const content = (0, node_fs.readFileSync)(pkgPath, "utf-8");
|
|
4098
|
-
const pkg
|
|
4099
|
-
return pkg
|
|
3852
|
+
const pkg = JSON.parse(content);
|
|
3853
|
+
return pkg.name;
|
|
4100
3854
|
} catch {
|
|
4101
3855
|
return void 0;
|
|
4102
3856
|
}
|
|
@@ -4192,8 +3946,8 @@ function getAppNameFromCwd$1() {
|
|
|
4192
3946
|
const packageJsonPath = (0, node_path.join)(process.cwd(), "package.json");
|
|
4193
3947
|
if (!(0, node_fs.existsSync)(packageJsonPath)) return void 0;
|
|
4194
3948
|
try {
|
|
4195
|
-
const pkg
|
|
4196
|
-
if (pkg
|
|
3949
|
+
const pkg = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8"));
|
|
3950
|
+
if (pkg.name) return pkg.name.replace(/^@[^/]+\//, "");
|
|
4197
3951
|
} catch {}
|
|
4198
3952
|
return void 0;
|
|
4199
3953
|
}
|
|
@@ -4209,8 +3963,8 @@ function getAppNameFromPackageJson() {
|
|
|
4209
3963
|
const packageJsonPath = (0, node_path.join)(projectRoot, "package.json");
|
|
4210
3964
|
if (!(0, node_fs.existsSync)(packageJsonPath)) return void 0;
|
|
4211
3965
|
try {
|
|
4212
|
-
const pkg
|
|
4213
|
-
if (pkg
|
|
3966
|
+
const pkg = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8"));
|
|
3967
|
+
if (pkg.name) return pkg.name.replace(/^@[^/]+\//, "");
|
|
4214
3968
|
} catch {}
|
|
4215
3969
|
return void 0;
|
|
4216
3970
|
}
|
|
@@ -4329,7 +4083,7 @@ const logger$3 = console;
|
|
|
4329
4083
|
* Get the Dokploy API token from stored credentials or environment
|
|
4330
4084
|
*/
|
|
4331
4085
|
async function getApiToken$1() {
|
|
4332
|
-
const token = await getDokployToken();
|
|
4086
|
+
const token = await require_credentials.getDokployToken();
|
|
4333
4087
|
if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
|
|
4334
4088
|
return token;
|
|
4335
4089
|
}
|
|
@@ -4358,7 +4112,7 @@ async function deployDokploy(options) {
|
|
|
4358
4112
|
registryOptions.registryId = config.registryId;
|
|
4359
4113
|
logger$3.log(` Using Dokploy registry: ${config.registryId}`);
|
|
4360
4114
|
} else {
|
|
4361
|
-
const storedRegistryId = await getDokployRegistryId();
|
|
4115
|
+
const storedRegistryId = await require_credentials.getDokployRegistryId();
|
|
4362
4116
|
if (storedRegistryId) {
|
|
4363
4117
|
registryOptions.registryId = storedRegistryId;
|
|
4364
4118
|
logger$3.log(` Using stored Dokploy registry: ${storedRegistryId}`);
|
|
@@ -4591,7 +4345,7 @@ const logger$2 = console;
|
|
|
4591
4345
|
* Get the Dokploy API token from stored credentials or environment
|
|
4592
4346
|
*/
|
|
4593
4347
|
async function getApiToken() {
|
|
4594
|
-
const token = await getDokployToken();
|
|
4348
|
+
const token = await require_credentials.getDokployToken();
|
|
4595
4349
|
if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
|
|
4596
4350
|
return token;
|
|
4597
4351
|
}
|
|
@@ -4600,7 +4354,7 @@ async function getApiToken() {
|
|
|
4600
4354
|
*/
|
|
4601
4355
|
async function getEndpoint(providedEndpoint) {
|
|
4602
4356
|
if (providedEndpoint) return providedEndpoint;
|
|
4603
|
-
const stored = await getDokployCredentials();
|
|
4357
|
+
const stored = await require_credentials.getDokployCredentials();
|
|
4604
4358
|
if (stored) return stored.endpoint;
|
|
4605
4359
|
throw new Error("Dokploy endpoint not specified.\nEither run \"gkm login --service dokploy\" first, or provide --endpoint.");
|
|
4606
4360
|
}
|
|
@@ -4745,7 +4499,7 @@ async function deployListCommand(options) {
|
|
|
4745
4499
|
logger$2.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
4746
4500
|
return;
|
|
4747
4501
|
}
|
|
4748
|
-
const storedRegistryId = await getDokployRegistryId();
|
|
4502
|
+
const storedRegistryId = await require_credentials.getDokployRegistryId();
|
|
4749
4503
|
for (const registry of registries) {
|
|
4750
4504
|
const isDefault = registry.registryId === storedRegistryId;
|
|
4751
4505
|
const marker = isDefault ? " (default)" : "";
|
|
@@ -4757,6 +4511,48 @@ async function deployListCommand(options) {
|
|
|
4757
4511
|
}
|
|
4758
4512
|
}
|
|
4759
4513
|
|
|
4514
|
+
//#endregion
|
|
4515
|
+
//#region src/deploy/StateProvider.ts
|
|
4516
|
+
/**
|
|
4517
|
+
* Check if value is a StateProvider implementation.
|
|
4518
|
+
*/
|
|
4519
|
+
function isStateProvider(value) {
|
|
4520
|
+
return typeof value === "object" && value !== null && typeof value.read === "function" && typeof value.write === "function";
|
|
4521
|
+
}
|
|
4522
|
+
/**
|
|
4523
|
+
* Create a state provider based on configuration.
|
|
4524
|
+
*
|
|
4525
|
+
* - 'local': LocalStateProvider (default)
|
|
4526
|
+
* - 'ssm': CachedStateProvider with SSM as source of truth
|
|
4527
|
+
* - Custom: Use provided StateProvider implementation
|
|
4528
|
+
*/
|
|
4529
|
+
async function createStateProvider(options) {
|
|
4530
|
+
const { config, workspaceRoot, workspaceName } = options;
|
|
4531
|
+
if (!config) {
|
|
4532
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CdspeSVL.cjs"));
|
|
4533
|
+
return new LocalStateProvider(workspaceRoot);
|
|
4534
|
+
}
|
|
4535
|
+
if (isStateProvider(config.provider)) return config.provider;
|
|
4536
|
+
const provider = config.provider;
|
|
4537
|
+
if (provider === "local") {
|
|
4538
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CdspeSVL.cjs"));
|
|
4539
|
+
return new LocalStateProvider(workspaceRoot);
|
|
4540
|
+
}
|
|
4541
|
+
if (provider === "ssm") {
|
|
4542
|
+
if (!workspaceName) throw new Error("Workspace name is required for SSM state provider. Set \"name\" in gkm.config.ts.");
|
|
4543
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CdspeSVL.cjs"));
|
|
4544
|
+
const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-BxAPU99a.cjs"));
|
|
4545
|
+
const { CachedStateProvider: CachedStateProvider$1 } = await Promise.resolve().then(() => require("./CachedStateProvider-D_uISMmJ.cjs"));
|
|
4546
|
+
const local = new LocalStateProvider(workspaceRoot);
|
|
4547
|
+
const ssm = new SSMStateProvider({
|
|
4548
|
+
workspaceName,
|
|
4549
|
+
region: config.region
|
|
4550
|
+
});
|
|
4551
|
+
return new CachedStateProvider$1(ssm, local);
|
|
4552
|
+
}
|
|
4553
|
+
throw new Error(`Unknown state provider: ${JSON.stringify(config)}`);
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4760
4556
|
//#endregion
|
|
4761
4557
|
//#region src/deploy/secrets.ts
|
|
4762
4558
|
/**
|
|
@@ -4859,10 +4655,12 @@ function generateSecretsReport(encryptedApps, sniffedApps) {
|
|
|
4859
4655
|
const __filename$1 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
4860
4656
|
const __dirname$1 = (0, node_path.dirname)(__filename$1);
|
|
4861
4657
|
/**
|
|
4862
|
-
*
|
|
4658
|
+
* Resolve the tsx package path from the CLI package's dependencies.
|
|
4659
|
+
* This ensures tsx is available regardless of whether the target project has it installed.
|
|
4863
4660
|
*/
|
|
4864
|
-
function
|
|
4865
|
-
|
|
4661
|
+
function resolveTsxPath() {
|
|
4662
|
+
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
4663
|
+
return require$1.resolve("tsx");
|
|
4866
4664
|
}
|
|
4867
4665
|
/**
|
|
4868
4666
|
* Resolve the path to a sniffer helper file.
|
|
@@ -5023,7 +4821,9 @@ async function sniffEntryFile(entryPath, appPath, workspacePath) {
|
|
|
5023
4821
|
*
|
|
5024
4822
|
* Route-based apps have endpoints, functions, crons, and subscribers that
|
|
5025
4823
|
* use services. Each service's register() method accesses environment variables.
|
|
5026
|
-
*
|
|
4824
|
+
*
|
|
4825
|
+
* This runs in a subprocess with tsx loader to properly handle TypeScript
|
|
4826
|
+
* compilation and path alias resolution (e.g., `src/...` imports).
|
|
5027
4827
|
*
|
|
5028
4828
|
* @param routes - Glob pattern(s) for route files
|
|
5029
4829
|
* @param appPath - The app's path relative to workspace (e.g., 'apps/api')
|
|
@@ -5032,32 +4832,63 @@ async function sniffEntryFile(entryPath, appPath, workspacePath) {
|
|
|
5032
4832
|
*/
|
|
5033
4833
|
async function sniffRouteFiles(routes, appPath, workspacePath) {
|
|
5034
4834
|
const fullAppPath = (0, node_path.resolve)(workspacePath, appPath);
|
|
5035
|
-
const
|
|
5036
|
-
const
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
4835
|
+
const workerPath = resolveSnifferFile("sniffer-routes-worker");
|
|
4836
|
+
const tsxPath = resolveTsxPath();
|
|
4837
|
+
const routesArray = Array.isArray(routes) ? routes : [routes];
|
|
4838
|
+
const pattern = routesArray[0];
|
|
4839
|
+
if (!pattern) return {
|
|
4840
|
+
envVars: [],
|
|
4841
|
+
error: new Error("No route patterns provided")
|
|
4842
|
+
};
|
|
4843
|
+
return new Promise((resolvePromise) => {
|
|
4844
|
+
const child = (0, node_child_process.spawn)("node", [
|
|
4845
|
+
"--import",
|
|
4846
|
+
tsxPath,
|
|
4847
|
+
workerPath,
|
|
4848
|
+
fullAppPath,
|
|
4849
|
+
pattern
|
|
4850
|
+
], {
|
|
5040
4851
|
cwd: fullAppPath,
|
|
5041
|
-
|
|
4852
|
+
stdio: [
|
|
4853
|
+
"ignore",
|
|
4854
|
+
"pipe",
|
|
4855
|
+
"pipe"
|
|
4856
|
+
],
|
|
4857
|
+
env: { ...process.env }
|
|
5042
4858
|
});
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
4859
|
+
let stdout = "";
|
|
4860
|
+
let stderr = "";
|
|
4861
|
+
child.stdout.on("data", (data) => {
|
|
4862
|
+
stdout += data.toString();
|
|
4863
|
+
});
|
|
4864
|
+
child.stderr.on("data", (data) => {
|
|
4865
|
+
stderr += data.toString();
|
|
4866
|
+
});
|
|
4867
|
+
child.on("close", (code) => {
|
|
4868
|
+
if (stderr) stderr.split("\n").filter((line) => line.trim()).forEach((line) => console.warn(line));
|
|
4869
|
+
try {
|
|
4870
|
+
const jsonMatch = stdout.match(/\{[^{}]*"envVars"[^{}]*\}[^{]*$/);
|
|
4871
|
+
if (jsonMatch) {
|
|
4872
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
4873
|
+
resolvePromise({
|
|
4874
|
+
envVars: result.envVars || [],
|
|
4875
|
+
error: result.error ? new Error(result.error) : void 0
|
|
4876
|
+
});
|
|
4877
|
+
return;
|
|
4878
|
+
}
|
|
4879
|
+
} catch {}
|
|
4880
|
+
resolvePromise({
|
|
4881
|
+
envVars: [],
|
|
4882
|
+
error: new Error(`Failed to sniff route files (exit code ${code}): ${stderr || stdout || "No output"}`)
|
|
4883
|
+
});
|
|
4884
|
+
});
|
|
4885
|
+
child.on("error", (err) => {
|
|
4886
|
+
resolvePromise({
|
|
4887
|
+
envVars: [],
|
|
4888
|
+
error: err
|
|
4889
|
+
});
|
|
4890
|
+
});
|
|
4891
|
+
});
|
|
5061
4892
|
}
|
|
5062
4893
|
/**
|
|
5063
4894
|
* Run the SnifferEnvironmentParser on an envParser module to detect
|
|
@@ -5388,7 +5219,7 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
5388
5219
|
*/
|
|
5389
5220
|
async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
5390
5221
|
logger$1.log("\n🔧 Checking Dokploy setup...");
|
|
5391
|
-
let creds = await getDokployCredentials();
|
|
5222
|
+
let creds = await require_credentials.getDokployCredentials();
|
|
5392
5223
|
if (!creds) {
|
|
5393
5224
|
logger$1.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
5394
5225
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
@@ -5403,7 +5234,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5403
5234
|
logger$1.log("\nValidating credentials...");
|
|
5404
5235
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
5405
5236
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
5406
|
-
await storeDokployCredentials(token, normalizedEndpoint);
|
|
5237
|
+
await require_credentials.storeDokployCredentials(token, normalizedEndpoint);
|
|
5407
5238
|
creds = {
|
|
5408
5239
|
token,
|
|
5409
5240
|
endpoint: normalizedEndpoint
|
|
@@ -5420,7 +5251,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5420
5251
|
try {
|
|
5421
5252
|
const projectDetails = await api.getProject(existingConfig.projectId);
|
|
5422
5253
|
logger$1.log("✓ Project verified");
|
|
5423
|
-
const storedRegistryId = existingConfig.registryId ?? await getDokployRegistryId();
|
|
5254
|
+
const storedRegistryId = existingConfig.registryId ?? await require_credentials.getDokployRegistryId();
|
|
5424
5255
|
const environments = projectDetails.environments ?? [];
|
|
5425
5256
|
let environment = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
5426
5257
|
if (!environment) {
|
|
@@ -5489,14 +5320,14 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5489
5320
|
logger$1.log(` ✓ Created application: ${applicationId}`);
|
|
5490
5321
|
}
|
|
5491
5322
|
logger$1.log("\n🐳 Checking registry...");
|
|
5492
|
-
let registryId = await getDokployRegistryId();
|
|
5323
|
+
let registryId = await require_credentials.getDokployRegistryId();
|
|
5493
5324
|
if (registryId) try {
|
|
5494
5325
|
const registry = await api.getRegistry(registryId);
|
|
5495
5326
|
logger$1.log(` Using registry: ${registry.registryName}`);
|
|
5496
5327
|
} catch {
|
|
5497
5328
|
logger$1.log(" ⚠ Stored registry not found, clearing...");
|
|
5498
5329
|
registryId = void 0;
|
|
5499
|
-
await storeDokployRegistryId("");
|
|
5330
|
+
await require_credentials.storeDokployRegistryId("");
|
|
5500
5331
|
}
|
|
5501
5332
|
if (!registryId) {
|
|
5502
5333
|
const registries = await api.listRegistries();
|
|
@@ -5507,7 +5338,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5507
5338
|
const password = await prompt("Registry password/token: ", true);
|
|
5508
5339
|
const registry = await api.createRegistry("Default Registry", dockerConfig.registry, username, password);
|
|
5509
5340
|
registryId = registry.registryId;
|
|
5510
|
-
await storeDokployRegistryId(registryId);
|
|
5341
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5511
5342
|
logger$1.log(` ✓ Registry created: ${registryId}`);
|
|
5512
5343
|
} else logger$1.log(" ⚠ No registry configured. Set docker.registry in gkm.config.ts");
|
|
5513
5344
|
else {
|
|
@@ -5521,7 +5352,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5521
5352
|
const index = parseInt(selection, 10) - 1;
|
|
5522
5353
|
if (index >= 0 && index < registries.length) {
|
|
5523
5354
|
registryId = registries[index].registryId;
|
|
5524
|
-
await storeDokployRegistryId(registryId);
|
|
5355
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5525
5356
|
logger$1.log(` ✓ Selected: ${registries[index].registryName}`);
|
|
5526
5357
|
} else if (dockerConfig.registry && index === registries.length) {
|
|
5527
5358
|
logger$1.log(`\n Creating new registry...`);
|
|
@@ -5530,7 +5361,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5530
5361
|
const password = await prompt(" Registry password/token: ", true);
|
|
5531
5362
|
const registry = await api.createRegistry(dockerConfig.registry.replace(/^https?:\/\//, ""), dockerConfig.registry, username, password);
|
|
5532
5363
|
registryId = registry.registryId;
|
|
5533
|
-
await storeDokployRegistryId(registryId);
|
|
5364
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5534
5365
|
logger$1.log(` ✓ Registry created: ${registryId}`);
|
|
5535
5366
|
} else logger$1.log(" ⚠ Invalid selection, skipping registry setup");
|
|
5536
5367
|
}
|
|
@@ -5612,7 +5443,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5612
5443
|
if (report.appsWithSecrets.length > 0) logger$1.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
5613
5444
|
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$1.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
5614
5445
|
}
|
|
5615
|
-
let creds = await getDokployCredentials();
|
|
5446
|
+
let creds = await require_credentials.getDokployCredentials();
|
|
5616
5447
|
if (!creds) {
|
|
5617
5448
|
logger$1.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
5618
5449
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
@@ -5627,7 +5458,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5627
5458
|
logger$1.log("\nValidating credentials...");
|
|
5628
5459
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
5629
5460
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
5630
|
-
await storeDokployCredentials(token, normalizedEndpoint);
|
|
5461
|
+
await require_credentials.storeDokployCredentials(token, normalizedEndpoint);
|
|
5631
5462
|
creds = {
|
|
5632
5463
|
token,
|
|
5633
5464
|
endpoint: normalizedEndpoint
|
|
@@ -5669,7 +5500,12 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5669
5500
|
logger$1.log(` ✓ Created project: ${project.projectId}`);
|
|
5670
5501
|
}
|
|
5671
5502
|
logger$1.log("\n📋 Loading deploy state...");
|
|
5672
|
-
|
|
5503
|
+
const stateProvider = await createStateProvider({
|
|
5504
|
+
config: workspace.state,
|
|
5505
|
+
workspaceRoot: workspace.root,
|
|
5506
|
+
workspaceName: workspace.name
|
|
5507
|
+
});
|
|
5508
|
+
let state = await stateProvider.read(stage);
|
|
5673
5509
|
if (state) {
|
|
5674
5510
|
logger$1.log(` Found existing state for stage "${stage}"`);
|
|
5675
5511
|
if (state.environmentId !== environmentId) {
|
|
@@ -5681,7 +5517,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5681
5517
|
state = createEmptyState(stage, environmentId);
|
|
5682
5518
|
}
|
|
5683
5519
|
logger$1.log("\n🐳 Checking registry...");
|
|
5684
|
-
let registryId = await getDokployRegistryId();
|
|
5520
|
+
let registryId = await require_credentials.getDokployRegistryId();
|
|
5685
5521
|
const registry = workspace.deploy.dokploy?.registry;
|
|
5686
5522
|
if (registryId) try {
|
|
5687
5523
|
const reg = await api.getRegistry(registryId);
|
|
@@ -5689,13 +5525,13 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5689
5525
|
} catch {
|
|
5690
5526
|
logger$1.log(" ⚠ Stored registry not found, clearing...");
|
|
5691
5527
|
registryId = void 0;
|
|
5692
|
-
await storeDokployRegistryId("");
|
|
5528
|
+
await require_credentials.storeDokployRegistryId("");
|
|
5693
5529
|
}
|
|
5694
5530
|
if (!registryId) {
|
|
5695
5531
|
const registries = await api.listRegistries();
|
|
5696
5532
|
if (registries.length > 0) {
|
|
5697
5533
|
registryId = registries[0].registryId;
|
|
5698
|
-
await storeDokployRegistryId(registryId);
|
|
5534
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5699
5535
|
logger$1.log(` Using registry: ${registries[0].registryName}`);
|
|
5700
5536
|
} else if (registry) {
|
|
5701
5537
|
logger$1.log(" No registries found in Dokploy. Let's create one.");
|
|
@@ -5704,7 +5540,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5704
5540
|
const password = await prompt("Registry password/token: ", true);
|
|
5705
5541
|
const reg = await api.createRegistry("Default Registry", registry, username, password);
|
|
5706
5542
|
registryId = reg.registryId;
|
|
5707
|
-
await storeDokployRegistryId(registryId);
|
|
5543
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5708
5544
|
logger$1.log(` ✓ Registry created: ${registryId}`);
|
|
5709
5545
|
} else logger$1.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
5710
5546
|
}
|
|
@@ -6004,14 +5840,14 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6004
5840
|
}
|
|
6005
5841
|
}
|
|
6006
5842
|
logger$1.log("\n📋 Saving deploy state...");
|
|
6007
|
-
await
|
|
6008
|
-
logger$1.log(
|
|
5843
|
+
await stateProvider.write(stage, state);
|
|
5844
|
+
logger$1.log(" ✓ State saved");
|
|
6009
5845
|
const dnsConfig = workspace.deploy.dns;
|
|
6010
5846
|
if (dnsConfig && appHostnames.size > 0) {
|
|
6011
5847
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
6012
5848
|
if (dnsResult?.serverIp && appHostnames.size > 0) {
|
|
6013
5849
|
await verifyDnsRecords(appHostnames, dnsResult.serverIp, state);
|
|
6014
|
-
await
|
|
5850
|
+
await stateProvider.write(stage, state);
|
|
6015
5851
|
}
|
|
6016
5852
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
6017
5853
|
logger$1.log("\n🔒 Validating domains for SSL certificates...");
|
|
@@ -6080,7 +5916,7 @@ async function deployCommand(options) {
|
|
|
6080
5916
|
dokployConfig = setupResult.config;
|
|
6081
5917
|
finalRegistry = dokployConfig.registry ?? dockerConfig.registry;
|
|
6082
5918
|
if (setupResult.serviceUrls) {
|
|
6083
|
-
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await Promise.resolve().then(() => require("./storage-
|
|
5919
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
|
|
6084
5920
|
let secrets = await readStageSecrets$1(stage);
|
|
6085
5921
|
if (!secrets) {
|
|
6086
5922
|
logger$1.log(` Creating secrets file for stage "${stage}"...`);
|
|
@@ -6170,34 +6006,202 @@ async function deployCommand(options) {
|
|
|
6170
6006
|
}
|
|
6171
6007
|
|
|
6172
6008
|
//#endregion
|
|
6173
|
-
//#region src/
|
|
6009
|
+
//#region src/deploy/state-commands.ts
|
|
6174
6010
|
/**
|
|
6175
|
-
*
|
|
6176
|
-
*
|
|
6011
|
+
* Pull state from remote to local.
|
|
6012
|
+
* `gkm state:pull --stage=<stage>`
|
|
6177
6013
|
*/
|
|
6178
|
-
function
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
port: 5432,
|
|
6186
|
-
username: "app",
|
|
6187
|
-
database: "app"
|
|
6188
|
-
},
|
|
6189
|
-
redis: {
|
|
6190
|
-
host: "redis",
|
|
6191
|
-
port: 6379,
|
|
6192
|
-
username: "default"
|
|
6193
|
-
},
|
|
6194
|
-
rabbitmq: {
|
|
6195
|
-
host: "rabbitmq",
|
|
6196
|
-
port: 5672,
|
|
6197
|
-
username: "app",
|
|
6198
|
-
vhost: "/"
|
|
6014
|
+
async function statePullCommand(options) {
|
|
6015
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
6016
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
6017
|
+
console.error("No remote state provider configured.");
|
|
6018
|
+
console.error("Add a remote provider in gkm.config.ts:");
|
|
6019
|
+
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
6020
|
+
process.exit(1);
|
|
6199
6021
|
}
|
|
6200
|
-
|
|
6022
|
+
const provider = await createStateProvider({
|
|
6023
|
+
config: workspace.state,
|
|
6024
|
+
workspaceRoot: workspace.root,
|
|
6025
|
+
workspaceName: workspace.name
|
|
6026
|
+
});
|
|
6027
|
+
if (!(provider instanceof require_CachedStateProvider.CachedStateProvider)) {
|
|
6028
|
+
console.error("State provider does not support pull operation.");
|
|
6029
|
+
process.exit(1);
|
|
6030
|
+
}
|
|
6031
|
+
console.log(`Pulling state for stage: ${options.stage}...`);
|
|
6032
|
+
const state = await provider.pull(options.stage);
|
|
6033
|
+
if (state) {
|
|
6034
|
+
console.log("State pulled successfully.");
|
|
6035
|
+
printStateSummary(state);
|
|
6036
|
+
} else console.log("No remote state found for this stage.");
|
|
6037
|
+
}
|
|
6038
|
+
/**
|
|
6039
|
+
* Push local state to remote.
|
|
6040
|
+
* `gkm state:push --stage=<stage>`
|
|
6041
|
+
*/
|
|
6042
|
+
async function statePushCommand(options) {
|
|
6043
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
6044
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
6045
|
+
console.error("No remote state provider configured.");
|
|
6046
|
+
console.error("Add a remote provider in gkm.config.ts:");
|
|
6047
|
+
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
6048
|
+
process.exit(1);
|
|
6049
|
+
}
|
|
6050
|
+
const provider = await createStateProvider({
|
|
6051
|
+
config: workspace.state,
|
|
6052
|
+
workspaceRoot: workspace.root,
|
|
6053
|
+
workspaceName: workspace.name
|
|
6054
|
+
});
|
|
6055
|
+
if (!(provider instanceof require_CachedStateProvider.CachedStateProvider)) {
|
|
6056
|
+
console.error("State provider does not support push operation.");
|
|
6057
|
+
process.exit(1);
|
|
6058
|
+
}
|
|
6059
|
+
console.log(`Pushing state for stage: ${options.stage}...`);
|
|
6060
|
+
const state = await provider.push(options.stage);
|
|
6061
|
+
if (state) {
|
|
6062
|
+
console.log("State pushed successfully.");
|
|
6063
|
+
printStateSummary(state);
|
|
6064
|
+
} else console.log("No local state found for this stage.");
|
|
6065
|
+
}
|
|
6066
|
+
/**
|
|
6067
|
+
* Show current state.
|
|
6068
|
+
* `gkm state:show --stage=<stage>`
|
|
6069
|
+
*/
|
|
6070
|
+
async function stateShowCommand(options) {
|
|
6071
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
6072
|
+
const provider = await createStateProvider({
|
|
6073
|
+
config: workspace.state,
|
|
6074
|
+
workspaceRoot: workspace.root,
|
|
6075
|
+
workspaceName: workspace.name
|
|
6076
|
+
});
|
|
6077
|
+
const state = await provider.read(options.stage);
|
|
6078
|
+
if (!state) {
|
|
6079
|
+
console.log(`No state found for stage: ${options.stage}`);
|
|
6080
|
+
return;
|
|
6081
|
+
}
|
|
6082
|
+
if (options.json) console.log(JSON.stringify(state, null, 2));
|
|
6083
|
+
else printStateDetails(state);
|
|
6084
|
+
}
|
|
6085
|
+
/**
|
|
6086
|
+
* Compare local and remote state.
|
|
6087
|
+
* `gkm state:diff --stage=<stage>`
|
|
6088
|
+
*/
|
|
6089
|
+
async function stateDiffCommand(options) {
|
|
6090
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
6091
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
6092
|
+
console.error("No remote state provider configured.");
|
|
6093
|
+
console.error("Diff requires a remote provider to compare against.");
|
|
6094
|
+
process.exit(1);
|
|
6095
|
+
}
|
|
6096
|
+
const provider = await createStateProvider({
|
|
6097
|
+
config: workspace.state,
|
|
6098
|
+
workspaceRoot: workspace.root,
|
|
6099
|
+
workspaceName: workspace.name
|
|
6100
|
+
});
|
|
6101
|
+
if (!(provider instanceof require_CachedStateProvider.CachedStateProvider)) {
|
|
6102
|
+
console.error("State provider does not support diff operation.");
|
|
6103
|
+
process.exit(1);
|
|
6104
|
+
}
|
|
6105
|
+
console.log(`Comparing state for stage: ${options.stage}...\n`);
|
|
6106
|
+
const { local, remote } = await provider.diff(options.stage);
|
|
6107
|
+
if (!local && !remote) {
|
|
6108
|
+
console.log("No state found (local or remote).");
|
|
6109
|
+
return;
|
|
6110
|
+
}
|
|
6111
|
+
if (!local) console.log("Local: (none)");
|
|
6112
|
+
else console.log(`Local: Last deployed ${local.lastDeployedAt}`);
|
|
6113
|
+
if (!remote) console.log("Remote: (none)");
|
|
6114
|
+
else console.log(`Remote: Last deployed ${remote.lastDeployedAt}`);
|
|
6115
|
+
console.log("");
|
|
6116
|
+
const localApps = local?.applications ?? {};
|
|
6117
|
+
const remoteApps = remote?.applications ?? {};
|
|
6118
|
+
const allApps = new Set([...Object.keys(localApps), ...Object.keys(remoteApps)]);
|
|
6119
|
+
if (allApps.size > 0) {
|
|
6120
|
+
console.log("Applications:");
|
|
6121
|
+
for (const app of allApps) {
|
|
6122
|
+
const localId = localApps[app];
|
|
6123
|
+
const remoteId = remoteApps[app];
|
|
6124
|
+
if (localId === remoteId) console.log(` ${app}: ${localId ?? "(none)"}`);
|
|
6125
|
+
else if (!localId) console.log(` ${app}: (none) -> ${remoteId} [REMOTE ONLY]`);
|
|
6126
|
+
else if (!remoteId) console.log(` ${app}: ${localId} -> (none) [LOCAL ONLY]`);
|
|
6127
|
+
else console.log(` ${app}: ${localId} (local) != ${remoteId} (remote) [MISMATCH]`);
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
const localServices = local?.services ?? {};
|
|
6131
|
+
const remoteServices = remote?.services ?? {};
|
|
6132
|
+
if (Object.keys(localServices).length > 0 || Object.keys(remoteServices).length > 0) {
|
|
6133
|
+
console.log("\nServices:");
|
|
6134
|
+
const serviceKeys = new Set([...Object.keys(localServices), ...Object.keys(remoteServices)]);
|
|
6135
|
+
for (const key of serviceKeys) {
|
|
6136
|
+
const localVal = localServices[key];
|
|
6137
|
+
const remoteVal = remoteServices[key];
|
|
6138
|
+
if (localVal === remoteVal) console.log(` ${key}: ${localVal ?? "(none)"}`);
|
|
6139
|
+
else console.log(` ${key}: ${localVal ?? "(none)"} (local) != ${remoteVal ?? "(none)"} (remote)`);
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
function printStateSummary(state) {
|
|
6144
|
+
const appCount = Object.keys(state.applications).length;
|
|
6145
|
+
const hasPostgres = !!state.services.postgresId;
|
|
6146
|
+
const hasRedis = !!state.services.redisId;
|
|
6147
|
+
console.log(` Stage: ${state.stage}`);
|
|
6148
|
+
console.log(` Applications: ${appCount}`);
|
|
6149
|
+
console.log(` Postgres: ${hasPostgres ? "configured" : "none"}`);
|
|
6150
|
+
console.log(` Redis: ${hasRedis ? "configured" : "none"}`);
|
|
6151
|
+
console.log(` Last deployed: ${state.lastDeployedAt}`);
|
|
6152
|
+
}
|
|
6153
|
+
function printStateDetails(state) {
|
|
6154
|
+
console.log(`Stage: ${state.stage}`);
|
|
6155
|
+
console.log(`Environment ID: ${state.environmentId}`);
|
|
6156
|
+
console.log(`Last Deployed: ${state.lastDeployedAt}`);
|
|
6157
|
+
console.log("");
|
|
6158
|
+
console.log("Applications:");
|
|
6159
|
+
const apps = Object.entries(state.applications);
|
|
6160
|
+
if (apps.length === 0) console.log(" (none)");
|
|
6161
|
+
else for (const [name$1, id] of apps) console.log(` ${name$1}: ${id}`);
|
|
6162
|
+
console.log("");
|
|
6163
|
+
console.log("Services:");
|
|
6164
|
+
if (!state.services.postgresId && !state.services.redisId) console.log(" (none)");
|
|
6165
|
+
else {
|
|
6166
|
+
if (state.services.postgresId) console.log(` Postgres: ${state.services.postgresId}`);
|
|
6167
|
+
if (state.services.redisId) console.log(` Redis: ${state.services.redisId}`);
|
|
6168
|
+
}
|
|
6169
|
+
if (state.dnsVerified && Object.keys(state.dnsVerified).length > 0) {
|
|
6170
|
+
console.log("");
|
|
6171
|
+
console.log("DNS Verified:");
|
|
6172
|
+
for (const [hostname, info] of Object.entries(state.dnsVerified)) console.log(` ${hostname}: ${info.serverIp} (${info.verifiedAt})`);
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
|
|
6176
|
+
//#endregion
|
|
6177
|
+
//#region src/secrets/generator.ts
|
|
6178
|
+
/**
|
|
6179
|
+
* Generate a secure random password using URL-safe base64 characters.
|
|
6180
|
+
* @param length Password length (default: 32)
|
|
6181
|
+
*/
|
|
6182
|
+
function generateSecurePassword(length = 32) {
|
|
6183
|
+
return (0, node_crypto.randomBytes)(Math.ceil(length * 3 / 4)).toString("base64url").slice(0, length);
|
|
6184
|
+
}
|
|
6185
|
+
/** Default service configurations */
|
|
6186
|
+
const SERVICE_DEFAULTS = {
|
|
6187
|
+
postgres: {
|
|
6188
|
+
host: "postgres",
|
|
6189
|
+
port: 5432,
|
|
6190
|
+
username: "app",
|
|
6191
|
+
database: "app"
|
|
6192
|
+
},
|
|
6193
|
+
redis: {
|
|
6194
|
+
host: "redis",
|
|
6195
|
+
port: 6379,
|
|
6196
|
+
username: "default"
|
|
6197
|
+
},
|
|
6198
|
+
rabbitmq: {
|
|
6199
|
+
host: "rabbitmq",
|
|
6200
|
+
port: 5672,
|
|
6201
|
+
username: "app",
|
|
6202
|
+
vhost: "/"
|
|
6203
|
+
}
|
|
6204
|
+
};
|
|
6201
6205
|
/**
|
|
6202
6206
|
* Generate credentials for a specific service.
|
|
6203
6207
|
*/
|
|
@@ -6288,45 +6292,33 @@ function rotateServicePassword(secrets, service) {
|
|
|
6288
6292
|
|
|
6289
6293
|
//#endregion
|
|
6290
6294
|
//#region src/init/versions.ts
|
|
6291
|
-
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
6292
|
-
function loadPackageJson() {
|
|
6293
|
-
try {
|
|
6294
|
-
return require$1("../package.json");
|
|
6295
|
-
} catch {
|
|
6296
|
-
return require$1("../../package.json");
|
|
6297
|
-
}
|
|
6298
|
-
}
|
|
6299
|
-
const pkg = loadPackageJson();
|
|
6300
6295
|
/**
|
|
6301
|
-
*
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
* Current released versions of @geekmidas packages
|
|
6306
|
-
* Update these when publishing new versions
|
|
6307
|
-
* Note: CLI version is read from package.json via CLI_VERSION
|
|
6296
|
+
* Package versions for @geekmidas packages
|
|
6297
|
+
*
|
|
6298
|
+
* AUTO-GENERATED - Do not edit manually
|
|
6299
|
+
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
6308
6300
|
*/
|
|
6309
6301
|
const GEEKMIDAS_VERSIONS = {
|
|
6310
|
-
"@geekmidas/audit": "~0.
|
|
6311
|
-
"@geekmidas/auth": "~0.
|
|
6312
|
-
"@geekmidas/cache": "~0.
|
|
6313
|
-
"@geekmidas/cli":
|
|
6314
|
-
"@geekmidas/client": "~0.
|
|
6315
|
-
"@geekmidas/cloud": "~0.
|
|
6316
|
-
"@geekmidas/constructs": "~0.
|
|
6317
|
-
"@geekmidas/db": "~0.
|
|
6318
|
-
"@geekmidas/emailkit": "~0.
|
|
6319
|
-
"@geekmidas/envkit": "~0.
|
|
6320
|
-
"@geekmidas/errors": "~
|
|
6321
|
-
"@geekmidas/events": "~0.
|
|
6322
|
-
"@geekmidas/logger": "~0.
|
|
6323
|
-
"@geekmidas/rate-limit": "~0.
|
|
6324
|
-
"@geekmidas/schema": "~
|
|
6325
|
-
"@geekmidas/services": "~0.
|
|
6326
|
-
"@geekmidas/storage": "~
|
|
6327
|
-
"@geekmidas/studio": "~0.
|
|
6328
|
-
"@geekmidas/telescope": "~0.
|
|
6329
|
-
"@geekmidas/testkit": "~0.
|
|
6302
|
+
"@geekmidas/audit": "~1.0.0",
|
|
6303
|
+
"@geekmidas/auth": "~1.0.0",
|
|
6304
|
+
"@geekmidas/cache": "~1.0.0",
|
|
6305
|
+
"@geekmidas/cli": "~1.0.0",
|
|
6306
|
+
"@geekmidas/client": "~1.0.0",
|
|
6307
|
+
"@geekmidas/cloud": "~1.0.0",
|
|
6308
|
+
"@geekmidas/constructs": "~1.0.0",
|
|
6309
|
+
"@geekmidas/db": "~1.0.0",
|
|
6310
|
+
"@geekmidas/emailkit": "~1.0.0",
|
|
6311
|
+
"@geekmidas/envkit": "~1.0.0",
|
|
6312
|
+
"@geekmidas/errors": "~1.0.0",
|
|
6313
|
+
"@geekmidas/events": "~1.0.0",
|
|
6314
|
+
"@geekmidas/logger": "~1.0.0",
|
|
6315
|
+
"@geekmidas/rate-limit": "~1.0.0",
|
|
6316
|
+
"@geekmidas/schema": "~1.0.0",
|
|
6317
|
+
"@geekmidas/services": "~1.0.0",
|
|
6318
|
+
"@geekmidas/storage": "~1.0.0",
|
|
6319
|
+
"@geekmidas/studio": "~1.0.0",
|
|
6320
|
+
"@geekmidas/telescope": "~1.0.0",
|
|
6321
|
+
"@geekmidas/testkit": "~1.0.0"
|
|
6330
6322
|
};
|
|
6331
6323
|
|
|
6332
6324
|
//#endregion
|
|
@@ -6375,7 +6367,10 @@ function generateAuthAppFiles(options) {
|
|
|
6375
6367
|
compilerOptions: {
|
|
6376
6368
|
noEmit: true,
|
|
6377
6369
|
baseUrl: ".",
|
|
6378
|
-
paths: {
|
|
6370
|
+
paths: {
|
|
6371
|
+
"~/*": ["./src/*"],
|
|
6372
|
+
[`@${options.name}/*`]: ["../../packages/*/src"]
|
|
6373
|
+
}
|
|
6379
6374
|
},
|
|
6380
6375
|
include: ["src/**/*.ts"],
|
|
6381
6376
|
exclude: ["node_modules", "dist"]
|
|
@@ -6585,7 +6580,10 @@ export default defineConfig({
|
|
|
6585
6580
|
compilerOptions: {
|
|
6586
6581
|
noEmit: true,
|
|
6587
6582
|
baseUrl: ".",
|
|
6588
|
-
paths: {
|
|
6583
|
+
paths: {
|
|
6584
|
+
"~/*": ["./src/*"],
|
|
6585
|
+
[`@${options.name}/*`]: ["../../packages/*/src"]
|
|
6586
|
+
}
|
|
6589
6587
|
},
|
|
6590
6588
|
include: ["src/**/*.ts"],
|
|
6591
6589
|
exclude: ["node_modules", "dist"]
|
|
@@ -6705,7 +6703,10 @@ function generateSingleAppConfigFiles(options, _template, _helpers) {
|
|
|
6705
6703
|
compilerOptions: {
|
|
6706
6704
|
noEmit: true,
|
|
6707
6705
|
baseUrl: ".",
|
|
6708
|
-
paths: {
|
|
6706
|
+
paths: {
|
|
6707
|
+
"~/*": ["./src/*"],
|
|
6708
|
+
[`@${options.name}/*`]: ["../../packages/*/src"]
|
|
6709
|
+
}
|
|
6709
6710
|
},
|
|
6710
6711
|
include: ["src/**/*.ts"],
|
|
6711
6712
|
exclude: ["node_modules", "dist"]
|
|
@@ -7003,7 +7004,11 @@ function generateModelsPackage(options) {
|
|
|
7003
7004
|
// Common Schemas
|
|
7004
7005
|
// ============================================
|
|
7005
7006
|
|
|
7006
|
-
export const IdSchema = z.
|
|
7007
|
+
export const IdSchema = z.uuid();
|
|
7008
|
+
|
|
7009
|
+
export const IdParamsSchema = z.object({
|
|
7010
|
+
id: IdSchema,
|
|
7011
|
+
});
|
|
7007
7012
|
|
|
7008
7013
|
export const TimestampsSchema = z.object({
|
|
7009
7014
|
createdAt: z.coerce.date(),
|
|
@@ -7029,6 +7034,7 @@ export const PaginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =
|
|
|
7029
7034
|
// ============================================
|
|
7030
7035
|
|
|
7031
7036
|
export type Id = z.infer<typeof IdSchema>;
|
|
7037
|
+
export type IdParams = z.infer<typeof IdParamsSchema>;
|
|
7032
7038
|
export type Timestamps = z.infer<typeof TimestampsSchema>;
|
|
7033
7039
|
export type Pagination = z.infer<typeof PaginationSchema>;
|
|
7034
7040
|
`;
|
|
@@ -7121,6 +7127,7 @@ function generateMonorepoFiles(options, _template) {
|
|
|
7121
7127
|
lint: "biome lint .",
|
|
7122
7128
|
fmt: "biome format . --write",
|
|
7123
7129
|
"fmt:check": "biome format .",
|
|
7130
|
+
...isFullstack ? { storybook: "pnpm --filter ./packages/ui storybook" } : {},
|
|
7124
7131
|
...options.deployTarget === "dokploy" ? { deploy: "gkm deploy --provider dokploy --stage production" } : {}
|
|
7125
7132
|
},
|
|
7126
7133
|
dependencies: { zod: "~4.1.0" },
|
|
@@ -7516,7 +7523,20 @@ export const config = envParser
|
|
|
7516
7523
|
},
|
|
7517
7524
|
{
|
|
7518
7525
|
path: getRoutePath("health.ts"),
|
|
7519
|
-
content: `import {
|
|
7526
|
+
content: monorepo ? `import { z } from 'zod';
|
|
7527
|
+
import { publicRouter } from '~/router';
|
|
7528
|
+
|
|
7529
|
+
export const healthEndpoint = publicRouter
|
|
7530
|
+
.get('/health')
|
|
7531
|
+
.output(z.object({
|
|
7532
|
+
status: z.string(),
|
|
7533
|
+
timestamp: z.string(),
|
|
7534
|
+
}))
|
|
7535
|
+
.handle(async () => ({
|
|
7536
|
+
status: 'ok',
|
|
7537
|
+
timestamp: new Date().toISOString(),
|
|
7538
|
+
}));
|
|
7539
|
+
` : `import { e } from '@geekmidas/constructs/endpoints';
|
|
7520
7540
|
import { z } from 'zod';
|
|
7521
7541
|
|
|
7522
7542
|
export const healthEndpoint = e
|
|
@@ -7569,12 +7589,12 @@ export const listUsersEndpoint = e
|
|
|
7569
7589
|
{
|
|
7570
7590
|
path: getRoutePath("users/get.ts"),
|
|
7571
7591
|
content: modelsImport ? `import { e } from '@geekmidas/constructs/endpoints';
|
|
7572
|
-
import {
|
|
7592
|
+
import { IdParamsSchema } from '${modelsImport}/common';
|
|
7573
7593
|
import { UserResponseSchema } from '${modelsImport}/user';
|
|
7574
7594
|
|
|
7575
7595
|
export const getUserEndpoint = e
|
|
7576
7596
|
.get('/users/:id')
|
|
7577
|
-
.params(
|
|
7597
|
+
.params(IdParamsSchema)
|
|
7578
7598
|
.output(UserResponseSchema)
|
|
7579
7599
|
.handle(async ({ params }) => ({
|
|
7580
7600
|
id: params.id,
|
|
@@ -7600,6 +7620,91 @@ export const getUserEndpoint = e
|
|
|
7600
7620
|
`
|
|
7601
7621
|
}
|
|
7602
7622
|
];
|
|
7623
|
+
if (options.monorepo) {
|
|
7624
|
+
files.push({
|
|
7625
|
+
path: "src/services/auth.ts",
|
|
7626
|
+
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
7627
|
+
|
|
7628
|
+
export interface Session {
|
|
7629
|
+
user: {
|
|
7630
|
+
id: string;
|
|
7631
|
+
email: string;
|
|
7632
|
+
name: string;
|
|
7633
|
+
};
|
|
7634
|
+
}
|
|
7635
|
+
|
|
7636
|
+
export interface AuthClient {
|
|
7637
|
+
getSession: (cookie: string) => Promise<Session | null>;
|
|
7638
|
+
}
|
|
7639
|
+
|
|
7640
|
+
export const authService = {
|
|
7641
|
+
serviceName: 'auth' as const,
|
|
7642
|
+
async register({ envParser, context }: ServiceRegisterOptions) {
|
|
7643
|
+
const logger = context.getLogger();
|
|
7644
|
+
|
|
7645
|
+
const config = envParser
|
|
7646
|
+
.create((get) => ({
|
|
7647
|
+
url: get('AUTH_URL').string(),
|
|
7648
|
+
}))
|
|
7649
|
+
.parse();
|
|
7650
|
+
|
|
7651
|
+
logger.info({ authUrl: config.url }, 'Auth service configured');
|
|
7652
|
+
|
|
7653
|
+
return {
|
|
7654
|
+
getSession: async (cookie: string): Promise<Session | null> => {
|
|
7655
|
+
const res = await fetch(\`\${config.url}/api/auth/get-session\`, {
|
|
7656
|
+
headers: { cookie },
|
|
7657
|
+
});
|
|
7658
|
+
if (!res.ok) return null;
|
|
7659
|
+
return res.json();
|
|
7660
|
+
},
|
|
7661
|
+
};
|
|
7662
|
+
},
|
|
7663
|
+
} satisfies Service<'auth', AuthClient>;
|
|
7664
|
+
`
|
|
7665
|
+
});
|
|
7666
|
+
files.push({
|
|
7667
|
+
path: "src/router.ts",
|
|
7668
|
+
content: `import { e } from '@geekmidas/constructs/endpoints';
|
|
7669
|
+
import { UnauthorizedError } from '@geekmidas/errors';
|
|
7670
|
+
import { authService, type Session } from './services/auth.js';
|
|
7671
|
+
import { logger } from './config/logger.js';
|
|
7672
|
+
|
|
7673
|
+
// Public router - no auth required
|
|
7674
|
+
export const publicRouter = e.logger(logger);
|
|
7675
|
+
|
|
7676
|
+
// Router with auth service available (but session not enforced)
|
|
7677
|
+
export const r = publicRouter.services([authService]);
|
|
7678
|
+
|
|
7679
|
+
// Session router - requires active session, throws if not authenticated
|
|
7680
|
+
export const sessionRouter = r.session<Session>(async ({ services, header }) => {
|
|
7681
|
+
const cookie = header('cookie') || '';
|
|
7682
|
+
const session = await services.auth.getSession(cookie);
|
|
7683
|
+
|
|
7684
|
+
if (!session?.user) {
|
|
7685
|
+
throw new UnauthorizedError('No active session');
|
|
7686
|
+
}
|
|
7687
|
+
|
|
7688
|
+
return session;
|
|
7689
|
+
});
|
|
7690
|
+
`
|
|
7691
|
+
});
|
|
7692
|
+
files.push({
|
|
7693
|
+
path: getRoutePath("profile.ts"),
|
|
7694
|
+
content: `import { z } from 'zod';
|
|
7695
|
+
import { sessionRouter } from '~/router';
|
|
7696
|
+
|
|
7697
|
+
export const profileEndpoint = sessionRouter
|
|
7698
|
+
.get('/profile')
|
|
7699
|
+
.output(z.object({
|
|
7700
|
+
id: z.string(),
|
|
7701
|
+
email: z.string(),
|
|
7702
|
+
name: z.string(),
|
|
7703
|
+
}))
|
|
7704
|
+
.handle(async ({ session }) => session.user);
|
|
7705
|
+
`
|
|
7706
|
+
});
|
|
7707
|
+
}
|
|
7603
7708
|
if (options.database) files.push({
|
|
7604
7709
|
path: "src/services/database.ts",
|
|
7605
7710
|
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
@@ -8362,6 +8467,1419 @@ function generateSourceFiles(options, template) {
|
|
|
8362
8467
|
return template.files(options);
|
|
8363
8468
|
}
|
|
8364
8469
|
|
|
8470
|
+
//#endregion
|
|
8471
|
+
//#region src/init/generators/ui.ts
|
|
8472
|
+
/**
|
|
8473
|
+
* Generate UI package files for fullstack template
|
|
8474
|
+
* Based on @geekmidas/ui with shadcn/ui, Tailwind CSS v4, and Storybook
|
|
8475
|
+
*/
|
|
8476
|
+
function generateUiPackageFiles(options) {
|
|
8477
|
+
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
8478
|
+
const packageName = `@${options.name}/ui`;
|
|
8479
|
+
const packageJson = {
|
|
8480
|
+
name: packageName,
|
|
8481
|
+
version: "0.0.1",
|
|
8482
|
+
private: true,
|
|
8483
|
+
type: "module",
|
|
8484
|
+
exports: {
|
|
8485
|
+
".": "./src/index.ts",
|
|
8486
|
+
"./components": "./src/components/index.ts",
|
|
8487
|
+
"./lib/utils": "./src/lib/utils.ts",
|
|
8488
|
+
"./styles": "./src/styles/globals.css"
|
|
8489
|
+
},
|
|
8490
|
+
scripts: {
|
|
8491
|
+
"ts:check": "tsc --noEmit",
|
|
8492
|
+
storybook: "storybook dev -p 6006",
|
|
8493
|
+
"build:storybook": "storybook build -o dist/storybook"
|
|
8494
|
+
},
|
|
8495
|
+
dependencies: {
|
|
8496
|
+
"@radix-ui/react-dialog": "~1.1.4",
|
|
8497
|
+
"@radix-ui/react-label": "~2.1.2",
|
|
8498
|
+
"@radix-ui/react-separator": "~1.1.2",
|
|
8499
|
+
"@radix-ui/react-slot": "~1.2.4",
|
|
8500
|
+
"@radix-ui/react-tabs": "~1.1.2",
|
|
8501
|
+
"@radix-ui/react-tooltip": "~1.1.6",
|
|
8502
|
+
"class-variance-authority": "~0.7.1",
|
|
8503
|
+
clsx: "^2.1.1",
|
|
8504
|
+
"lucide-react": "~0.562.0",
|
|
8505
|
+
"tailwind-merge": "~3.4.0"
|
|
8506
|
+
},
|
|
8507
|
+
devDependencies: {
|
|
8508
|
+
"@storybook/addon-a11y": "^8.4.7",
|
|
8509
|
+
"@storybook/addon-essentials": "^8.4.7",
|
|
8510
|
+
"@storybook/addon-interactions": "^8.4.7",
|
|
8511
|
+
"@storybook/react": "^8.4.7",
|
|
8512
|
+
"@storybook/react-vite": "^8.4.7",
|
|
8513
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
8514
|
+
"@types/react": "^19.0.0",
|
|
8515
|
+
"@types/react-dom": "^19.0.0",
|
|
8516
|
+
react: "^19.0.0",
|
|
8517
|
+
"react-dom": "^19.0.0",
|
|
8518
|
+
storybook: "^8.4.7",
|
|
8519
|
+
tailwindcss: "^4.0.0",
|
|
8520
|
+
typescript: "^5.8.2",
|
|
8521
|
+
vite: "^6.0.0"
|
|
8522
|
+
},
|
|
8523
|
+
peerDependencies: {
|
|
8524
|
+
react: ">=18.0.0",
|
|
8525
|
+
"react-dom": ">=18.0.0",
|
|
8526
|
+
tailwindcss: ">=4.0.0"
|
|
8527
|
+
}
|
|
8528
|
+
};
|
|
8529
|
+
const tsConfig = {
|
|
8530
|
+
extends: "../../tsconfig.json",
|
|
8531
|
+
compilerOptions: {
|
|
8532
|
+
jsx: "react-jsx",
|
|
8533
|
+
lib: [
|
|
8534
|
+
"ES2023",
|
|
8535
|
+
"DOM",
|
|
8536
|
+
"DOM.Iterable"
|
|
8537
|
+
],
|
|
8538
|
+
noEmit: true,
|
|
8539
|
+
baseUrl: ".",
|
|
8540
|
+
paths: { "~/*": ["./src/*"] }
|
|
8541
|
+
},
|
|
8542
|
+
include: ["src/**/*"],
|
|
8543
|
+
exclude: [
|
|
8544
|
+
"node_modules",
|
|
8545
|
+
"dist",
|
|
8546
|
+
"**/*.stories.tsx"
|
|
8547
|
+
]
|
|
8548
|
+
};
|
|
8549
|
+
const componentsJson = {
|
|
8550
|
+
$schema: "https://ui.shadcn.com/schema.json",
|
|
8551
|
+
style: "new-york",
|
|
8552
|
+
rsc: false,
|
|
8553
|
+
tsx: true,
|
|
8554
|
+
tailwind: {
|
|
8555
|
+
config: "",
|
|
8556
|
+
css: "src/styles/globals.css",
|
|
8557
|
+
baseColor: "neutral",
|
|
8558
|
+
cssVariables: true,
|
|
8559
|
+
prefix: ""
|
|
8560
|
+
},
|
|
8561
|
+
aliases: {
|
|
8562
|
+
components: "~/components",
|
|
8563
|
+
utils: "~/lib/utils",
|
|
8564
|
+
ui: "~/components/ui",
|
|
8565
|
+
lib: "~/lib",
|
|
8566
|
+
hooks: "~/hooks"
|
|
8567
|
+
},
|
|
8568
|
+
iconLibrary: "lucide"
|
|
8569
|
+
};
|
|
8570
|
+
const storybookMain = `import type { StorybookConfig } from '@storybook/react-vite';
|
|
8571
|
+
|
|
8572
|
+
const config: StorybookConfig = {
|
|
8573
|
+
stories: ['../src/**/*.stories.@(ts|tsx)'],
|
|
8574
|
+
addons: [
|
|
8575
|
+
'@storybook/addon-essentials',
|
|
8576
|
+
'@storybook/addon-interactions',
|
|
8577
|
+
'@storybook/addon-a11y',
|
|
8578
|
+
],
|
|
8579
|
+
framework: {
|
|
8580
|
+
name: '@storybook/react-vite',
|
|
8581
|
+
options: {},
|
|
8582
|
+
},
|
|
8583
|
+
docs: {
|
|
8584
|
+
autodocs: 'tag',
|
|
8585
|
+
},
|
|
8586
|
+
viteFinal: async (config) => {
|
|
8587
|
+
// Add Tailwind CSS v4 plugin
|
|
8588
|
+
const tailwindcss = await import('@tailwindcss/vite');
|
|
8589
|
+
config.plugins = config.plugins || [];
|
|
8590
|
+
config.plugins.push(tailwindcss.default());
|
|
8591
|
+
return config;
|
|
8592
|
+
},
|
|
8593
|
+
};
|
|
8594
|
+
|
|
8595
|
+
export default config;
|
|
8596
|
+
`;
|
|
8597
|
+
const storybookPreview = `import type { Preview } from '@storybook/react';
|
|
8598
|
+
import '../src/styles/globals.css';
|
|
8599
|
+
|
|
8600
|
+
const preview: Preview = {
|
|
8601
|
+
parameters: {
|
|
8602
|
+
backgrounds: {
|
|
8603
|
+
default: 'dark',
|
|
8604
|
+
values: [
|
|
8605
|
+
{ name: 'dark', value: '#171717' },
|
|
8606
|
+
{ name: 'surface', value: '#1c1c1c' },
|
|
8607
|
+
{ name: 'light', value: '#fafafa' },
|
|
8608
|
+
],
|
|
8609
|
+
},
|
|
8610
|
+
controls: {
|
|
8611
|
+
matchers: {
|
|
8612
|
+
color: /(background|color)$/i,
|
|
8613
|
+
date: /Date$/i,
|
|
8614
|
+
},
|
|
8615
|
+
},
|
|
8616
|
+
layout: 'centered',
|
|
8617
|
+
},
|
|
8618
|
+
};
|
|
8619
|
+
|
|
8620
|
+
export default preview;
|
|
8621
|
+
`;
|
|
8622
|
+
const globalsCss = `@import "tailwindcss";
|
|
8623
|
+
|
|
8624
|
+
@theme {
|
|
8625
|
+
--color-background: hsl(var(--background));
|
|
8626
|
+
--color-foreground: hsl(var(--foreground));
|
|
8627
|
+
--color-card: hsl(var(--card));
|
|
8628
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
8629
|
+
--color-popover: hsl(var(--popover));
|
|
8630
|
+
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
8631
|
+
--color-primary: hsl(var(--primary));
|
|
8632
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
8633
|
+
--color-secondary: hsl(var(--secondary));
|
|
8634
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
8635
|
+
--color-muted: hsl(var(--muted));
|
|
8636
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
8637
|
+
--color-accent: hsl(var(--accent));
|
|
8638
|
+
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
8639
|
+
--color-destructive: hsl(var(--destructive));
|
|
8640
|
+
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
8641
|
+
--color-border: hsl(var(--border));
|
|
8642
|
+
--color-input: hsl(var(--input));
|
|
8643
|
+
--color-ring: hsl(var(--ring));
|
|
8644
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
8645
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
8646
|
+
--radius-lg: var(--radius);
|
|
8647
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
8648
|
+
}
|
|
8649
|
+
|
|
8650
|
+
@layer base {
|
|
8651
|
+
:root {
|
|
8652
|
+
--background: 0 0% 100%;
|
|
8653
|
+
--foreground: 0 0% 3.9%;
|
|
8654
|
+
--card: 0 0% 100%;
|
|
8655
|
+
--card-foreground: 0 0% 3.9%;
|
|
8656
|
+
--popover: 0 0% 100%;
|
|
8657
|
+
--popover-foreground: 0 0% 3.9%;
|
|
8658
|
+
--primary: 160 84% 39%;
|
|
8659
|
+
--primary-foreground: 0 0% 98%;
|
|
8660
|
+
--secondary: 0 0% 96.1%;
|
|
8661
|
+
--secondary-foreground: 0 0% 9%;
|
|
8662
|
+
--muted: 0 0% 96.1%;
|
|
8663
|
+
--muted-foreground: 0 0% 45.1%;
|
|
8664
|
+
--accent: 0 0% 96.1%;
|
|
8665
|
+
--accent-foreground: 0 0% 9%;
|
|
8666
|
+
--destructive: 0 84.2% 60.2%;
|
|
8667
|
+
--destructive-foreground: 0 0% 98%;
|
|
8668
|
+
--border: 0 0% 89.8%;
|
|
8669
|
+
--input: 0 0% 89.8%;
|
|
8670
|
+
--ring: 160 84% 39%;
|
|
8671
|
+
--radius: 0.5rem;
|
|
8672
|
+
}
|
|
8673
|
+
|
|
8674
|
+
.dark {
|
|
8675
|
+
--background: 0 0% 9%;
|
|
8676
|
+
--foreground: 0 0% 98%;
|
|
8677
|
+
--card: 0 0% 11%;
|
|
8678
|
+
--card-foreground: 0 0% 98%;
|
|
8679
|
+
--popover: 0 0% 11%;
|
|
8680
|
+
--popover-foreground: 0 0% 98%;
|
|
8681
|
+
--primary: 160 84% 52%;
|
|
8682
|
+
--primary-foreground: 0 0% 9%;
|
|
8683
|
+
--secondary: 0 0% 15%;
|
|
8684
|
+
--secondary-foreground: 0 0% 98%;
|
|
8685
|
+
--muted: 0 0% 15%;
|
|
8686
|
+
--muted-foreground: 0 0% 64%;
|
|
8687
|
+
--accent: 0 0% 15%;
|
|
8688
|
+
--accent-foreground: 0 0% 98%;
|
|
8689
|
+
--destructive: 0 62.8% 50.6%;
|
|
8690
|
+
--destructive-foreground: 0 0% 98%;
|
|
8691
|
+
--border: 0 0% 18%;
|
|
8692
|
+
--input: 0 0% 18%;
|
|
8693
|
+
--ring: 160 84% 52%;
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
|
|
8697
|
+
@layer base {
|
|
8698
|
+
* {
|
|
8699
|
+
@apply border-border;
|
|
8700
|
+
}
|
|
8701
|
+
body {
|
|
8702
|
+
@apply bg-background text-foreground;
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
`;
|
|
8706
|
+
const utilsTs = `import { type ClassValue, clsx } from 'clsx';
|
|
8707
|
+
import { twMerge } from 'tailwind-merge';
|
|
8708
|
+
|
|
8709
|
+
export function cn(...inputs: ClassValue[]) {
|
|
8710
|
+
return twMerge(clsx(inputs));
|
|
8711
|
+
}
|
|
8712
|
+
`;
|
|
8713
|
+
const buttonTsx = `import { Slot } from '@radix-ui/react-slot';
|
|
8714
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
8715
|
+
import * as React from 'react';
|
|
8716
|
+
|
|
8717
|
+
import { cn } from '~/lib/utils';
|
|
8718
|
+
|
|
8719
|
+
const buttonVariants = cva(
|
|
8720
|
+
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
8721
|
+
{
|
|
8722
|
+
variants: {
|
|
8723
|
+
variant: {
|
|
8724
|
+
default:
|
|
8725
|
+
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
|
8726
|
+
destructive:
|
|
8727
|
+
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
|
8728
|
+
outline:
|
|
8729
|
+
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
|
8730
|
+
secondary:
|
|
8731
|
+
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
|
8732
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
8733
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
8734
|
+
},
|
|
8735
|
+
size: {
|
|
8736
|
+
default: 'h-9 px-4 py-2',
|
|
8737
|
+
sm: 'h-8 rounded-md px-3 text-xs',
|
|
8738
|
+
lg: 'h-10 rounded-md px-8',
|
|
8739
|
+
icon: 'h-9 w-9',
|
|
8740
|
+
},
|
|
8741
|
+
},
|
|
8742
|
+
defaultVariants: {
|
|
8743
|
+
variant: 'default',
|
|
8744
|
+
size: 'default',
|
|
8745
|
+
},
|
|
8746
|
+
},
|
|
8747
|
+
);
|
|
8748
|
+
|
|
8749
|
+
export interface ButtonProps
|
|
8750
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
8751
|
+
VariantProps<typeof buttonVariants> {
|
|
8752
|
+
asChild?: boolean;
|
|
8753
|
+
}
|
|
8754
|
+
|
|
8755
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
8756
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
8757
|
+
const Comp = asChild ? Slot : 'button';
|
|
8758
|
+
return (
|
|
8759
|
+
<Comp
|
|
8760
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
8761
|
+
ref={ref}
|
|
8762
|
+
{...props}
|
|
8763
|
+
/>
|
|
8764
|
+
);
|
|
8765
|
+
},
|
|
8766
|
+
);
|
|
8767
|
+
Button.displayName = 'Button';
|
|
8768
|
+
|
|
8769
|
+
export { Button, buttonVariants };
|
|
8770
|
+
`;
|
|
8771
|
+
const buttonStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
8772
|
+
import { Button } from '.';
|
|
8773
|
+
|
|
8774
|
+
const meta: Meta<typeof Button> = {
|
|
8775
|
+
title: 'Components/Button',
|
|
8776
|
+
component: Button,
|
|
8777
|
+
tags: ['autodocs'],
|
|
8778
|
+
argTypes: {
|
|
8779
|
+
variant: {
|
|
8780
|
+
control: 'select',
|
|
8781
|
+
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
|
|
8782
|
+
},
|
|
8783
|
+
size: {
|
|
8784
|
+
control: 'select',
|
|
8785
|
+
options: ['default', 'sm', 'lg', 'icon'],
|
|
8786
|
+
},
|
|
8787
|
+
},
|
|
8788
|
+
};
|
|
8789
|
+
|
|
8790
|
+
export default meta;
|
|
8791
|
+
type Story = StoryObj<typeof Button>;
|
|
8792
|
+
|
|
8793
|
+
export const Default: Story = {
|
|
8794
|
+
args: {
|
|
8795
|
+
children: 'Button',
|
|
8796
|
+
variant: 'default',
|
|
8797
|
+
size: 'default',
|
|
8798
|
+
},
|
|
8799
|
+
};
|
|
8800
|
+
|
|
8801
|
+
export const Secondary: Story = {
|
|
8802
|
+
args: {
|
|
8803
|
+
children: 'Secondary',
|
|
8804
|
+
variant: 'secondary',
|
|
8805
|
+
},
|
|
8806
|
+
};
|
|
8807
|
+
|
|
8808
|
+
export const Destructive: Story = {
|
|
8809
|
+
args: {
|
|
8810
|
+
children: 'Destructive',
|
|
8811
|
+
variant: 'destructive',
|
|
8812
|
+
},
|
|
8813
|
+
};
|
|
8814
|
+
|
|
8815
|
+
export const Outline: Story = {
|
|
8816
|
+
args: {
|
|
8817
|
+
children: 'Outline',
|
|
8818
|
+
variant: 'outline',
|
|
8819
|
+
},
|
|
8820
|
+
};
|
|
8821
|
+
|
|
8822
|
+
export const Ghost: Story = {
|
|
8823
|
+
args: {
|
|
8824
|
+
children: 'Ghost',
|
|
8825
|
+
variant: 'ghost',
|
|
8826
|
+
},
|
|
8827
|
+
};
|
|
8828
|
+
|
|
8829
|
+
export const Link: Story = {
|
|
8830
|
+
args: {
|
|
8831
|
+
children: 'Link',
|
|
8832
|
+
variant: 'link',
|
|
8833
|
+
},
|
|
8834
|
+
};
|
|
8835
|
+
`;
|
|
8836
|
+
const inputTsx = `import * as React from 'react';
|
|
8837
|
+
|
|
8838
|
+
import { cn } from '~/lib/utils';
|
|
8839
|
+
|
|
8840
|
+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
|
8841
|
+
({ className, type, ...props }, ref) => {
|
|
8842
|
+
return (
|
|
8843
|
+
<input
|
|
8844
|
+
type={type}
|
|
8845
|
+
className={cn(
|
|
8846
|
+
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
|
8847
|
+
className,
|
|
8848
|
+
)}
|
|
8849
|
+
ref={ref}
|
|
8850
|
+
{...props}
|
|
8851
|
+
/>
|
|
8852
|
+
);
|
|
8853
|
+
},
|
|
8854
|
+
);
|
|
8855
|
+
Input.displayName = 'Input';
|
|
8856
|
+
|
|
8857
|
+
export { Input };
|
|
8858
|
+
`;
|
|
8859
|
+
const cardTsx = `import * as React from 'react';
|
|
8860
|
+
|
|
8861
|
+
import { cn } from '~/lib/utils';
|
|
8862
|
+
|
|
8863
|
+
const Card = React.forwardRef<
|
|
8864
|
+
HTMLDivElement,
|
|
8865
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8866
|
+
>(({ className, ...props }, ref) => (
|
|
8867
|
+
<div
|
|
8868
|
+
ref={ref}
|
|
8869
|
+
className={cn(
|
|
8870
|
+
'rounded-xl border bg-card text-card-foreground shadow',
|
|
8871
|
+
className,
|
|
8872
|
+
)}
|
|
8873
|
+
{...props}
|
|
8874
|
+
/>
|
|
8875
|
+
));
|
|
8876
|
+
Card.displayName = 'Card';
|
|
8877
|
+
|
|
8878
|
+
const CardHeader = React.forwardRef<
|
|
8879
|
+
HTMLDivElement,
|
|
8880
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8881
|
+
>(({ className, ...props }, ref) => (
|
|
8882
|
+
<div
|
|
8883
|
+
ref={ref}
|
|
8884
|
+
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
|
8885
|
+
{...props}
|
|
8886
|
+
/>
|
|
8887
|
+
));
|
|
8888
|
+
CardHeader.displayName = 'CardHeader';
|
|
8889
|
+
|
|
8890
|
+
const CardTitle = React.forwardRef<
|
|
8891
|
+
HTMLDivElement,
|
|
8892
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8893
|
+
>(({ className, ...props }, ref) => (
|
|
8894
|
+
<div
|
|
8895
|
+
ref={ref}
|
|
8896
|
+
className={cn('font-semibold leading-none tracking-tight', className)}
|
|
8897
|
+
{...props}
|
|
8898
|
+
/>
|
|
8899
|
+
));
|
|
8900
|
+
CardTitle.displayName = 'CardTitle';
|
|
8901
|
+
|
|
8902
|
+
const CardDescription = React.forwardRef<
|
|
8903
|
+
HTMLDivElement,
|
|
8904
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8905
|
+
>(({ className, ...props }, ref) => (
|
|
8906
|
+
<div
|
|
8907
|
+
ref={ref}
|
|
8908
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
8909
|
+
{...props}
|
|
8910
|
+
/>
|
|
8911
|
+
));
|
|
8912
|
+
CardDescription.displayName = 'CardDescription';
|
|
8913
|
+
|
|
8914
|
+
const CardContent = React.forwardRef<
|
|
8915
|
+
HTMLDivElement,
|
|
8916
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8917
|
+
>(({ className, ...props }, ref) => (
|
|
8918
|
+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
8919
|
+
));
|
|
8920
|
+
CardContent.displayName = 'CardContent';
|
|
8921
|
+
|
|
8922
|
+
const CardFooter = React.forwardRef<
|
|
8923
|
+
HTMLDivElement,
|
|
8924
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8925
|
+
>(({ className, ...props }, ref) => (
|
|
8926
|
+
<div
|
|
8927
|
+
ref={ref}
|
|
8928
|
+
className={cn('flex items-center p-6 pt-0', className)}
|
|
8929
|
+
{...props}
|
|
8930
|
+
/>
|
|
8931
|
+
));
|
|
8932
|
+
CardFooter.displayName = 'CardFooter';
|
|
8933
|
+
|
|
8934
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
8935
|
+
`;
|
|
8936
|
+
const inputStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
8937
|
+
import { Input } from '.';
|
|
8938
|
+
|
|
8939
|
+
const meta: Meta<typeof Input> = {
|
|
8940
|
+
title: 'Components/Input',
|
|
8941
|
+
component: Input,
|
|
8942
|
+
tags: ['autodocs'],
|
|
8943
|
+
argTypes: {
|
|
8944
|
+
type: {
|
|
8945
|
+
control: 'select',
|
|
8946
|
+
options: ['text', 'email', 'password', 'number', 'search', 'tel', 'url'],
|
|
8947
|
+
},
|
|
8948
|
+
disabled: {
|
|
8949
|
+
control: 'boolean',
|
|
8950
|
+
},
|
|
8951
|
+
},
|
|
8952
|
+
};
|
|
8953
|
+
|
|
8954
|
+
export default meta;
|
|
8955
|
+
type Story = StoryObj<typeof Input>;
|
|
8956
|
+
|
|
8957
|
+
export const Default: Story = {
|
|
8958
|
+
args: {
|
|
8959
|
+
placeholder: 'Enter text...',
|
|
8960
|
+
},
|
|
8961
|
+
};
|
|
8962
|
+
|
|
8963
|
+
export const Email: Story = {
|
|
8964
|
+
args: {
|
|
8965
|
+
type: 'email',
|
|
8966
|
+
placeholder: 'Enter email...',
|
|
8967
|
+
},
|
|
8968
|
+
};
|
|
8969
|
+
|
|
8970
|
+
export const Password: Story = {
|
|
8971
|
+
args: {
|
|
8972
|
+
type: 'password',
|
|
8973
|
+
placeholder: 'Enter password...',
|
|
8974
|
+
},
|
|
8975
|
+
};
|
|
8976
|
+
|
|
8977
|
+
export const Disabled: Story = {
|
|
8978
|
+
args: {
|
|
8979
|
+
placeholder: 'Disabled input',
|
|
8980
|
+
disabled: true,
|
|
8981
|
+
},
|
|
8982
|
+
};
|
|
8983
|
+
|
|
8984
|
+
export const WithValue: Story = {
|
|
8985
|
+
args: {
|
|
8986
|
+
defaultValue: 'Hello World',
|
|
8987
|
+
},
|
|
8988
|
+
};
|
|
8989
|
+
`;
|
|
8990
|
+
const cardStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
8991
|
+
import { Button } from '../button';
|
|
8992
|
+
import {
|
|
8993
|
+
Card,
|
|
8994
|
+
CardContent,
|
|
8995
|
+
CardDescription,
|
|
8996
|
+
CardFooter,
|
|
8997
|
+
CardHeader,
|
|
8998
|
+
CardTitle,
|
|
8999
|
+
} from '.';
|
|
9000
|
+
import { Input } from '../input';
|
|
9001
|
+
|
|
9002
|
+
const meta: Meta<typeof Card> = {
|
|
9003
|
+
title: 'Components/Card',
|
|
9004
|
+
component: Card,
|
|
9005
|
+
tags: ['autodocs'],
|
|
9006
|
+
};
|
|
9007
|
+
|
|
9008
|
+
export default meta;
|
|
9009
|
+
type Story = StoryObj<typeof Card>;
|
|
9010
|
+
|
|
9011
|
+
export const Default: Story = {
|
|
9012
|
+
render: () => (
|
|
9013
|
+
<Card className="w-[350px]">
|
|
9014
|
+
<CardHeader>
|
|
9015
|
+
<CardTitle>Card Title</CardTitle>
|
|
9016
|
+
<CardDescription>Card description goes here.</CardDescription>
|
|
9017
|
+
</CardHeader>
|
|
9018
|
+
<CardContent>
|
|
9019
|
+
<p>Card content goes here.</p>
|
|
9020
|
+
</CardContent>
|
|
9021
|
+
</Card>
|
|
9022
|
+
),
|
|
9023
|
+
};
|
|
9024
|
+
|
|
9025
|
+
export const WithFooter: Story = {
|
|
9026
|
+
render: () => (
|
|
9027
|
+
<Card className="w-[350px]">
|
|
9028
|
+
<CardHeader>
|
|
9029
|
+
<CardTitle>Create Account</CardTitle>
|
|
9030
|
+
<CardDescription>Enter your details below.</CardDescription>
|
|
9031
|
+
</CardHeader>
|
|
9032
|
+
<CardContent className="space-y-4">
|
|
9033
|
+
<Input placeholder="Email" type="email" />
|
|
9034
|
+
<Input placeholder="Password" type="password" />
|
|
9035
|
+
</CardContent>
|
|
9036
|
+
<CardFooter className="flex justify-between">
|
|
9037
|
+
<Button variant="outline">Cancel</Button>
|
|
9038
|
+
<Button>Create</Button>
|
|
9039
|
+
</CardFooter>
|
|
9040
|
+
</Card>
|
|
9041
|
+
),
|
|
9042
|
+
};
|
|
9043
|
+
|
|
9044
|
+
export const Simple: Story = {
|
|
9045
|
+
render: () => (
|
|
9046
|
+
<Card className="w-[350px] p-6">
|
|
9047
|
+
<p>Simple card with just content.</p>
|
|
9048
|
+
</Card>
|
|
9049
|
+
),
|
|
9050
|
+
};
|
|
9051
|
+
`;
|
|
9052
|
+
const labelTsx = `import * as LabelPrimitive from '@radix-ui/react-label';
|
|
9053
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
9054
|
+
import * as React from 'react';
|
|
9055
|
+
|
|
9056
|
+
import { cn } from '~/lib/utils';
|
|
9057
|
+
|
|
9058
|
+
const labelVariants = cva(
|
|
9059
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
9060
|
+
);
|
|
9061
|
+
|
|
9062
|
+
const Label = React.forwardRef<
|
|
9063
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
9064
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
|
9065
|
+
VariantProps<typeof labelVariants>
|
|
9066
|
+
>(({ className, ...props }, ref) => (
|
|
9067
|
+
<LabelPrimitive.Root
|
|
9068
|
+
ref={ref}
|
|
9069
|
+
className={cn(labelVariants(), className)}
|
|
9070
|
+
{...props}
|
|
9071
|
+
/>
|
|
9072
|
+
));
|
|
9073
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
9074
|
+
|
|
9075
|
+
export { Label };
|
|
9076
|
+
`;
|
|
9077
|
+
const labelStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9078
|
+
import { Input } from '../input';
|
|
9079
|
+
import { Label } from '.';
|
|
9080
|
+
|
|
9081
|
+
const meta: Meta<typeof Label> = {
|
|
9082
|
+
title: 'Components/Label',
|
|
9083
|
+
component: Label,
|
|
9084
|
+
tags: ['autodocs'],
|
|
9085
|
+
};
|
|
9086
|
+
|
|
9087
|
+
export default meta;
|
|
9088
|
+
type Story = StoryObj<typeof Label>;
|
|
9089
|
+
|
|
9090
|
+
export const Default: Story = {
|
|
9091
|
+
args: {
|
|
9092
|
+
children: 'Label',
|
|
9093
|
+
},
|
|
9094
|
+
};
|
|
9095
|
+
|
|
9096
|
+
export const WithInput: Story = {
|
|
9097
|
+
render: () => (
|
|
9098
|
+
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
9099
|
+
<Label htmlFor="email">Email</Label>
|
|
9100
|
+
<Input type="email" id="email" placeholder="Email" />
|
|
9101
|
+
</div>
|
|
9102
|
+
),
|
|
9103
|
+
};
|
|
9104
|
+
|
|
9105
|
+
export const Disabled: Story = {
|
|
9106
|
+
render: () => (
|
|
9107
|
+
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
9108
|
+
<Label htmlFor="disabled" className="peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
9109
|
+
Disabled
|
|
9110
|
+
</Label>
|
|
9111
|
+
<Input type="text" id="disabled" placeholder="Disabled" disabled className="peer" />
|
|
9112
|
+
</div>
|
|
9113
|
+
),
|
|
9114
|
+
};
|
|
9115
|
+
`;
|
|
9116
|
+
const badgeTsx = `import { cva, type VariantProps } from 'class-variance-authority';
|
|
9117
|
+
import * as React from 'react';
|
|
9118
|
+
|
|
9119
|
+
import { cn } from '~/lib/utils';
|
|
9120
|
+
|
|
9121
|
+
const badgeVariants = cva(
|
|
9122
|
+
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
9123
|
+
{
|
|
9124
|
+
variants: {
|
|
9125
|
+
variant: {
|
|
9126
|
+
default:
|
|
9127
|
+
'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
|
|
9128
|
+
secondary:
|
|
9129
|
+
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
9130
|
+
destructive:
|
|
9131
|
+
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
|
9132
|
+
outline: 'text-foreground',
|
|
9133
|
+
},
|
|
9134
|
+
},
|
|
9135
|
+
defaultVariants: {
|
|
9136
|
+
variant: 'default',
|
|
9137
|
+
},
|
|
9138
|
+
},
|
|
9139
|
+
);
|
|
9140
|
+
|
|
9141
|
+
export interface BadgeProps
|
|
9142
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
9143
|
+
VariantProps<typeof badgeVariants> {}
|
|
9144
|
+
|
|
9145
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
9146
|
+
return (
|
|
9147
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
9148
|
+
);
|
|
9149
|
+
}
|
|
9150
|
+
|
|
9151
|
+
export { Badge, badgeVariants };
|
|
9152
|
+
`;
|
|
9153
|
+
const badgeStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9154
|
+
import { Badge } from '.';
|
|
9155
|
+
|
|
9156
|
+
const meta: Meta<typeof Badge> = {
|
|
9157
|
+
title: 'Components/Badge',
|
|
9158
|
+
component: Badge,
|
|
9159
|
+
tags: ['autodocs'],
|
|
9160
|
+
argTypes: {
|
|
9161
|
+
variant: {
|
|
9162
|
+
control: 'select',
|
|
9163
|
+
options: ['default', 'secondary', 'destructive', 'outline'],
|
|
9164
|
+
},
|
|
9165
|
+
},
|
|
9166
|
+
};
|
|
9167
|
+
|
|
9168
|
+
export default meta;
|
|
9169
|
+
type Story = StoryObj<typeof Badge>;
|
|
9170
|
+
|
|
9171
|
+
export const Default: Story = {
|
|
9172
|
+
args: {
|
|
9173
|
+
children: 'Badge',
|
|
9174
|
+
variant: 'default',
|
|
9175
|
+
},
|
|
9176
|
+
};
|
|
9177
|
+
|
|
9178
|
+
export const Secondary: Story = {
|
|
9179
|
+
args: {
|
|
9180
|
+
children: 'Secondary',
|
|
9181
|
+
variant: 'secondary',
|
|
9182
|
+
},
|
|
9183
|
+
};
|
|
9184
|
+
|
|
9185
|
+
export const Destructive: Story = {
|
|
9186
|
+
args: {
|
|
9187
|
+
children: 'Destructive',
|
|
9188
|
+
variant: 'destructive',
|
|
9189
|
+
},
|
|
9190
|
+
};
|
|
9191
|
+
|
|
9192
|
+
export const Outline: Story = {
|
|
9193
|
+
args: {
|
|
9194
|
+
children: 'Outline',
|
|
9195
|
+
variant: 'outline',
|
|
9196
|
+
},
|
|
9197
|
+
};
|
|
9198
|
+
`;
|
|
9199
|
+
const separatorTsx = `import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
9200
|
+
import * as React from 'react';
|
|
9201
|
+
|
|
9202
|
+
import { cn } from '~/lib/utils';
|
|
9203
|
+
|
|
9204
|
+
const Separator = React.forwardRef<
|
|
9205
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
9206
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
9207
|
+
>(
|
|
9208
|
+
(
|
|
9209
|
+
{ className, orientation = 'horizontal', decorative = true, ...props },
|
|
9210
|
+
ref,
|
|
9211
|
+
) => (
|
|
9212
|
+
<SeparatorPrimitive.Root
|
|
9213
|
+
ref={ref}
|
|
9214
|
+
decorative={decorative}
|
|
9215
|
+
orientation={orientation}
|
|
9216
|
+
className={cn(
|
|
9217
|
+
'shrink-0 bg-border',
|
|
9218
|
+
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
9219
|
+
className,
|
|
9220
|
+
)}
|
|
9221
|
+
{...props}
|
|
9222
|
+
/>
|
|
9223
|
+
),
|
|
9224
|
+
);
|
|
9225
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
9226
|
+
|
|
9227
|
+
export { Separator };
|
|
9228
|
+
`;
|
|
9229
|
+
const separatorStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9230
|
+
import { Separator } from '.';
|
|
9231
|
+
|
|
9232
|
+
const meta: Meta<typeof Separator> = {
|
|
9233
|
+
title: 'Components/Separator',
|
|
9234
|
+
component: Separator,
|
|
9235
|
+
tags: ['autodocs'],
|
|
9236
|
+
argTypes: {
|
|
9237
|
+
orientation: {
|
|
9238
|
+
control: 'select',
|
|
9239
|
+
options: ['horizontal', 'vertical'],
|
|
9240
|
+
},
|
|
9241
|
+
},
|
|
9242
|
+
};
|
|
9243
|
+
|
|
9244
|
+
export default meta;
|
|
9245
|
+
type Story = StoryObj<typeof Separator>;
|
|
9246
|
+
|
|
9247
|
+
export const Horizontal: Story = {
|
|
9248
|
+
render: () => (
|
|
9249
|
+
<div className="w-[300px]">
|
|
9250
|
+
<div className="space-y-1">
|
|
9251
|
+
<h4 className="text-sm font-medium leading-none">Radix Primitives</h4>
|
|
9252
|
+
<p className="text-sm text-muted-foreground">
|
|
9253
|
+
An open-source UI component library.
|
|
9254
|
+
</p>
|
|
9255
|
+
</div>
|
|
9256
|
+
<Separator className="my-4" />
|
|
9257
|
+
<div className="flex h-5 items-center space-x-4 text-sm">
|
|
9258
|
+
<div>Blog</div>
|
|
9259
|
+
<Separator orientation="vertical" />
|
|
9260
|
+
<div>Docs</div>
|
|
9261
|
+
<Separator orientation="vertical" />
|
|
9262
|
+
<div>Source</div>
|
|
9263
|
+
</div>
|
|
9264
|
+
</div>
|
|
9265
|
+
),
|
|
9266
|
+
};
|
|
9267
|
+
|
|
9268
|
+
export const Vertical: Story = {
|
|
9269
|
+
render: () => (
|
|
9270
|
+
<div className="flex h-5 items-center space-x-4 text-sm">
|
|
9271
|
+
<div>Blog</div>
|
|
9272
|
+
<Separator orientation="vertical" />
|
|
9273
|
+
<div>Docs</div>
|
|
9274
|
+
<Separator orientation="vertical" />
|
|
9275
|
+
<div>Source</div>
|
|
9276
|
+
</div>
|
|
9277
|
+
),
|
|
9278
|
+
};
|
|
9279
|
+
`;
|
|
9280
|
+
const tabsTsx = `import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
9281
|
+
import * as React from 'react';
|
|
9282
|
+
|
|
9283
|
+
import { cn } from '~/lib/utils';
|
|
9284
|
+
|
|
9285
|
+
const Tabs = TabsPrimitive.Root;
|
|
9286
|
+
|
|
9287
|
+
const TabsList = React.forwardRef<
|
|
9288
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
9289
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
9290
|
+
>(({ className, ...props }, ref) => (
|
|
9291
|
+
<TabsPrimitive.List
|
|
9292
|
+
ref={ref}
|
|
9293
|
+
className={cn(
|
|
9294
|
+
'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
|
|
9295
|
+
className,
|
|
9296
|
+
)}
|
|
9297
|
+
{...props}
|
|
9298
|
+
/>
|
|
9299
|
+
));
|
|
9300
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
9301
|
+
|
|
9302
|
+
const TabsTrigger = React.forwardRef<
|
|
9303
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
9304
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
9305
|
+
>(({ className, ...props }, ref) => (
|
|
9306
|
+
<TabsPrimitive.Trigger
|
|
9307
|
+
ref={ref}
|
|
9308
|
+
className={cn(
|
|
9309
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
|
|
9310
|
+
className,
|
|
9311
|
+
)}
|
|
9312
|
+
{...props}
|
|
9313
|
+
/>
|
|
9314
|
+
));
|
|
9315
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
9316
|
+
|
|
9317
|
+
const TabsContent = React.forwardRef<
|
|
9318
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
9319
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
9320
|
+
>(({ className, ...props }, ref) => (
|
|
9321
|
+
<TabsPrimitive.Content
|
|
9322
|
+
ref={ref}
|
|
9323
|
+
className={cn(
|
|
9324
|
+
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
9325
|
+
className,
|
|
9326
|
+
)}
|
|
9327
|
+
{...props}
|
|
9328
|
+
/>
|
|
9329
|
+
));
|
|
9330
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
9331
|
+
|
|
9332
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
9333
|
+
`;
|
|
9334
|
+
const tabsStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9335
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '.';
|
|
9336
|
+
import { Button } from '../button';
|
|
9337
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../card';
|
|
9338
|
+
import { Input } from '../input';
|
|
9339
|
+
import { Label } from '../label';
|
|
9340
|
+
|
|
9341
|
+
const meta: Meta<typeof Tabs> = {
|
|
9342
|
+
title: 'Components/Tabs',
|
|
9343
|
+
component: Tabs,
|
|
9344
|
+
tags: ['autodocs'],
|
|
9345
|
+
};
|
|
9346
|
+
|
|
9347
|
+
export default meta;
|
|
9348
|
+
type Story = StoryObj<typeof Tabs>;
|
|
9349
|
+
|
|
9350
|
+
export const Default: Story = {
|
|
9351
|
+
render: () => (
|
|
9352
|
+
<Tabs defaultValue="account" className="w-[400px]">
|
|
9353
|
+
<TabsList>
|
|
9354
|
+
<TabsTrigger value="account">Account</TabsTrigger>
|
|
9355
|
+
<TabsTrigger value="password">Password</TabsTrigger>
|
|
9356
|
+
</TabsList>
|
|
9357
|
+
<TabsContent value="account">
|
|
9358
|
+
<Card>
|
|
9359
|
+
<CardHeader>
|
|
9360
|
+
<CardTitle>Account</CardTitle>
|
|
9361
|
+
<CardDescription>
|
|
9362
|
+
Make changes to your account here. Click save when you're done.
|
|
9363
|
+
</CardDescription>
|
|
9364
|
+
</CardHeader>
|
|
9365
|
+
<CardContent className="space-y-2">
|
|
9366
|
+
<div className="space-y-1">
|
|
9367
|
+
<Label htmlFor="name">Name</Label>
|
|
9368
|
+
<Input id="name" defaultValue="Pedro Duarte" />
|
|
9369
|
+
</div>
|
|
9370
|
+
<div className="space-y-1">
|
|
9371
|
+
<Label htmlFor="username">Username</Label>
|
|
9372
|
+
<Input id="username" defaultValue="@peduarte" />
|
|
9373
|
+
</div>
|
|
9374
|
+
</CardContent>
|
|
9375
|
+
<CardFooter>
|
|
9376
|
+
<Button>Save changes</Button>
|
|
9377
|
+
</CardFooter>
|
|
9378
|
+
</Card>
|
|
9379
|
+
</TabsContent>
|
|
9380
|
+
<TabsContent value="password">
|
|
9381
|
+
<Card>
|
|
9382
|
+
<CardHeader>
|
|
9383
|
+
<CardTitle>Password</CardTitle>
|
|
9384
|
+
<CardDescription>
|
|
9385
|
+
Change your password here. After saving, you'll be logged out.
|
|
9386
|
+
</CardDescription>
|
|
9387
|
+
</CardHeader>
|
|
9388
|
+
<CardContent className="space-y-2">
|
|
9389
|
+
<div className="space-y-1">
|
|
9390
|
+
<Label htmlFor="current">Current password</Label>
|
|
9391
|
+
<Input id="current" type="password" />
|
|
9392
|
+
</div>
|
|
9393
|
+
<div className="space-y-1">
|
|
9394
|
+
<Label htmlFor="new">New password</Label>
|
|
9395
|
+
<Input id="new" type="password" />
|
|
9396
|
+
</div>
|
|
9397
|
+
</CardContent>
|
|
9398
|
+
<CardFooter>
|
|
9399
|
+
<Button>Save password</Button>
|
|
9400
|
+
</CardFooter>
|
|
9401
|
+
</Card>
|
|
9402
|
+
</TabsContent>
|
|
9403
|
+
</Tabs>
|
|
9404
|
+
),
|
|
9405
|
+
};
|
|
9406
|
+
`;
|
|
9407
|
+
const tooltipTsx = `import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
9408
|
+
import * as React from 'react';
|
|
9409
|
+
|
|
9410
|
+
import { cn } from '~/lib/utils';
|
|
9411
|
+
|
|
9412
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
9413
|
+
|
|
9414
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
9415
|
+
|
|
9416
|
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
9417
|
+
|
|
9418
|
+
const TooltipContent = React.forwardRef<
|
|
9419
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
9420
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
9421
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
9422
|
+
<TooltipPrimitive.Portal>
|
|
9423
|
+
<TooltipPrimitive.Content
|
|
9424
|
+
ref={ref}
|
|
9425
|
+
sideOffset={sideOffset}
|
|
9426
|
+
className={cn(
|
|
9427
|
+
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
9428
|
+
className,
|
|
9429
|
+
)}
|
|
9430
|
+
{...props}
|
|
9431
|
+
/>
|
|
9432
|
+
</TooltipPrimitive.Portal>
|
|
9433
|
+
));
|
|
9434
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
9435
|
+
|
|
9436
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
9437
|
+
`;
|
|
9438
|
+
const tooltipStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9439
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '.';
|
|
9440
|
+
import { Button } from '../button';
|
|
9441
|
+
|
|
9442
|
+
const meta: Meta<typeof Tooltip> = {
|
|
9443
|
+
title: 'Components/Tooltip',
|
|
9444
|
+
component: Tooltip,
|
|
9445
|
+
tags: ['autodocs'],
|
|
9446
|
+
decorators: [
|
|
9447
|
+
(Story) => (
|
|
9448
|
+
<TooltipProvider>
|
|
9449
|
+
<Story />
|
|
9450
|
+
</TooltipProvider>
|
|
9451
|
+
),
|
|
9452
|
+
],
|
|
9453
|
+
};
|
|
9454
|
+
|
|
9455
|
+
export default meta;
|
|
9456
|
+
type Story = StoryObj<typeof Tooltip>;
|
|
9457
|
+
|
|
9458
|
+
export const Default: Story = {
|
|
9459
|
+
render: () => (
|
|
9460
|
+
<Tooltip>
|
|
9461
|
+
<TooltipTrigger asChild>
|
|
9462
|
+
<Button variant="outline">Hover me</Button>
|
|
9463
|
+
</TooltipTrigger>
|
|
9464
|
+
<TooltipContent>
|
|
9465
|
+
<p>Add to library</p>
|
|
9466
|
+
</TooltipContent>
|
|
9467
|
+
</Tooltip>
|
|
9468
|
+
),
|
|
9469
|
+
};
|
|
9470
|
+
|
|
9471
|
+
export const Positions: Story = {
|
|
9472
|
+
render: () => (
|
|
9473
|
+
<div className="flex gap-4">
|
|
9474
|
+
<Tooltip>
|
|
9475
|
+
<TooltipTrigger asChild>
|
|
9476
|
+
<Button variant="outline">Top</Button>
|
|
9477
|
+
</TooltipTrigger>
|
|
9478
|
+
<TooltipContent side="top">
|
|
9479
|
+
<p>Top tooltip</p>
|
|
9480
|
+
</TooltipContent>
|
|
9481
|
+
</Tooltip>
|
|
9482
|
+
<Tooltip>
|
|
9483
|
+
<TooltipTrigger asChild>
|
|
9484
|
+
<Button variant="outline">Bottom</Button>
|
|
9485
|
+
</TooltipTrigger>
|
|
9486
|
+
<TooltipContent side="bottom">
|
|
9487
|
+
<p>Bottom tooltip</p>
|
|
9488
|
+
</TooltipContent>
|
|
9489
|
+
</Tooltip>
|
|
9490
|
+
<Tooltip>
|
|
9491
|
+
<TooltipTrigger asChild>
|
|
9492
|
+
<Button variant="outline">Left</Button>
|
|
9493
|
+
</TooltipTrigger>
|
|
9494
|
+
<TooltipContent side="left">
|
|
9495
|
+
<p>Left tooltip</p>
|
|
9496
|
+
</TooltipContent>
|
|
9497
|
+
</Tooltip>
|
|
9498
|
+
<Tooltip>
|
|
9499
|
+
<TooltipTrigger asChild>
|
|
9500
|
+
<Button variant="outline">Right</Button>
|
|
9501
|
+
</TooltipTrigger>
|
|
9502
|
+
<TooltipContent side="right">
|
|
9503
|
+
<p>Right tooltip</p>
|
|
9504
|
+
</TooltipContent>
|
|
9505
|
+
</Tooltip>
|
|
9506
|
+
</div>
|
|
9507
|
+
),
|
|
9508
|
+
};
|
|
9509
|
+
`;
|
|
9510
|
+
const dialogTsx = `import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
9511
|
+
import { X } from 'lucide-react';
|
|
9512
|
+
import * as React from 'react';
|
|
9513
|
+
|
|
9514
|
+
import { cn } from '~/lib/utils';
|
|
9515
|
+
|
|
9516
|
+
const Dialog = DialogPrimitive.Root;
|
|
9517
|
+
|
|
9518
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
9519
|
+
|
|
9520
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
9521
|
+
|
|
9522
|
+
const DialogClose = DialogPrimitive.Close;
|
|
9523
|
+
|
|
9524
|
+
const DialogOverlay = React.forwardRef<
|
|
9525
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
9526
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
9527
|
+
>(({ className, ...props }, ref) => (
|
|
9528
|
+
<DialogPrimitive.Overlay
|
|
9529
|
+
ref={ref}
|
|
9530
|
+
className={cn(
|
|
9531
|
+
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
9532
|
+
className,
|
|
9533
|
+
)}
|
|
9534
|
+
{...props}
|
|
9535
|
+
/>
|
|
9536
|
+
));
|
|
9537
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
9538
|
+
|
|
9539
|
+
const DialogContent = React.forwardRef<
|
|
9540
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
9541
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
9542
|
+
>(({ className, children, ...props }, ref) => (
|
|
9543
|
+
<DialogPortal>
|
|
9544
|
+
<DialogOverlay />
|
|
9545
|
+
<DialogPrimitive.Content
|
|
9546
|
+
ref={ref}
|
|
9547
|
+
className={cn(
|
|
9548
|
+
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
|
9549
|
+
className,
|
|
9550
|
+
)}
|
|
9551
|
+
{...props}
|
|
9552
|
+
>
|
|
9553
|
+
{children}
|
|
9554
|
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
|
9555
|
+
<X className="h-4 w-4" />
|
|
9556
|
+
<span className="sr-only">Close</span>
|
|
9557
|
+
</DialogPrimitive.Close>
|
|
9558
|
+
</DialogPrimitive.Content>
|
|
9559
|
+
</DialogPortal>
|
|
9560
|
+
));
|
|
9561
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
9562
|
+
|
|
9563
|
+
const DialogHeader = ({
|
|
9564
|
+
className,
|
|
9565
|
+
...props
|
|
9566
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
9567
|
+
<div
|
|
9568
|
+
className={cn(
|
|
9569
|
+
'flex flex-col space-y-1.5 text-center sm:text-left',
|
|
9570
|
+
className,
|
|
9571
|
+
)}
|
|
9572
|
+
{...props}
|
|
9573
|
+
/>
|
|
9574
|
+
);
|
|
9575
|
+
DialogHeader.displayName = 'DialogHeader';
|
|
9576
|
+
|
|
9577
|
+
const DialogFooter = ({
|
|
9578
|
+
className,
|
|
9579
|
+
...props
|
|
9580
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
9581
|
+
<div
|
|
9582
|
+
className={cn(
|
|
9583
|
+
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
|
9584
|
+
className,
|
|
9585
|
+
)}
|
|
9586
|
+
{...props}
|
|
9587
|
+
/>
|
|
9588
|
+
);
|
|
9589
|
+
DialogFooter.displayName = 'DialogFooter';
|
|
9590
|
+
|
|
9591
|
+
const DialogTitle = React.forwardRef<
|
|
9592
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
9593
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
9594
|
+
>(({ className, ...props }, ref) => (
|
|
9595
|
+
<DialogPrimitive.Title
|
|
9596
|
+
ref={ref}
|
|
9597
|
+
className={cn(
|
|
9598
|
+
'text-lg font-semibold leading-none tracking-tight',
|
|
9599
|
+
className,
|
|
9600
|
+
)}
|
|
9601
|
+
{...props}
|
|
9602
|
+
/>
|
|
9603
|
+
));
|
|
9604
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
9605
|
+
|
|
9606
|
+
const DialogDescription = React.forwardRef<
|
|
9607
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
9608
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
9609
|
+
>(({ className, ...props }, ref) => (
|
|
9610
|
+
<DialogPrimitive.Description
|
|
9611
|
+
ref={ref}
|
|
9612
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
9613
|
+
{...props}
|
|
9614
|
+
/>
|
|
9615
|
+
));
|
|
9616
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
9617
|
+
|
|
9618
|
+
export {
|
|
9619
|
+
Dialog,
|
|
9620
|
+
DialogPortal,
|
|
9621
|
+
DialogOverlay,
|
|
9622
|
+
DialogTrigger,
|
|
9623
|
+
DialogClose,
|
|
9624
|
+
DialogContent,
|
|
9625
|
+
DialogHeader,
|
|
9626
|
+
DialogFooter,
|
|
9627
|
+
DialogTitle,
|
|
9628
|
+
DialogDescription,
|
|
9629
|
+
};
|
|
9630
|
+
`;
|
|
9631
|
+
const dialogStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9632
|
+
import {
|
|
9633
|
+
Dialog,
|
|
9634
|
+
DialogContent,
|
|
9635
|
+
DialogDescription,
|
|
9636
|
+
DialogFooter,
|
|
9637
|
+
DialogHeader,
|
|
9638
|
+
DialogTitle,
|
|
9639
|
+
DialogTrigger,
|
|
9640
|
+
} from '.';
|
|
9641
|
+
import { Button } from '../button';
|
|
9642
|
+
import { Input } from '../input';
|
|
9643
|
+
import { Label } from '../label';
|
|
9644
|
+
|
|
9645
|
+
const meta: Meta<typeof Dialog> = {
|
|
9646
|
+
title: 'Components/Dialog',
|
|
9647
|
+
component: Dialog,
|
|
9648
|
+
tags: ['autodocs'],
|
|
9649
|
+
};
|
|
9650
|
+
|
|
9651
|
+
export default meta;
|
|
9652
|
+
type Story = StoryObj<typeof Dialog>;
|
|
9653
|
+
|
|
9654
|
+
export const Default: Story = {
|
|
9655
|
+
render: () => (
|
|
9656
|
+
<Dialog>
|
|
9657
|
+
<DialogTrigger asChild>
|
|
9658
|
+
<Button variant="outline">Edit Profile</Button>
|
|
9659
|
+
</DialogTrigger>
|
|
9660
|
+
<DialogContent className="sm:max-w-[425px]">
|
|
9661
|
+
<DialogHeader>
|
|
9662
|
+
<DialogTitle>Edit profile</DialogTitle>
|
|
9663
|
+
<DialogDescription>
|
|
9664
|
+
Make changes to your profile here. Click save when you're done.
|
|
9665
|
+
</DialogDescription>
|
|
9666
|
+
</DialogHeader>
|
|
9667
|
+
<div className="grid gap-4 py-4">
|
|
9668
|
+
<div className="grid grid-cols-4 items-center gap-4">
|
|
9669
|
+
<Label htmlFor="name" className="text-right">
|
|
9670
|
+
Name
|
|
9671
|
+
</Label>
|
|
9672
|
+
<Input id="name" defaultValue="Pedro Duarte" className="col-span-3" />
|
|
9673
|
+
</div>
|
|
9674
|
+
<div className="grid grid-cols-4 items-center gap-4">
|
|
9675
|
+
<Label htmlFor="username" className="text-right">
|
|
9676
|
+
Username
|
|
9677
|
+
</Label>
|
|
9678
|
+
<Input id="username" defaultValue="@peduarte" className="col-span-3" />
|
|
9679
|
+
</div>
|
|
9680
|
+
</div>
|
|
9681
|
+
<DialogFooter>
|
|
9682
|
+
<Button type="submit">Save changes</Button>
|
|
9683
|
+
</DialogFooter>
|
|
9684
|
+
</DialogContent>
|
|
9685
|
+
</Dialog>
|
|
9686
|
+
),
|
|
9687
|
+
};
|
|
9688
|
+
|
|
9689
|
+
export const Alert: Story = {
|
|
9690
|
+
render: () => (
|
|
9691
|
+
<Dialog>
|
|
9692
|
+
<DialogTrigger asChild>
|
|
9693
|
+
<Button variant="destructive">Delete Account</Button>
|
|
9694
|
+
</DialogTrigger>
|
|
9695
|
+
<DialogContent>
|
|
9696
|
+
<DialogHeader>
|
|
9697
|
+
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
|
9698
|
+
<DialogDescription>
|
|
9699
|
+
This action cannot be undone. This will permanently delete your
|
|
9700
|
+
account and remove your data from our servers.
|
|
9701
|
+
</DialogDescription>
|
|
9702
|
+
</DialogHeader>
|
|
9703
|
+
<DialogFooter>
|
|
9704
|
+
<Button variant="outline">Cancel</Button>
|
|
9705
|
+
<Button variant="destructive">Delete</Button>
|
|
9706
|
+
</DialogFooter>
|
|
9707
|
+
</DialogContent>
|
|
9708
|
+
</Dialog>
|
|
9709
|
+
),
|
|
9710
|
+
};
|
|
9711
|
+
`;
|
|
9712
|
+
const componentsUiIndex = `export { Button, type ButtonProps, buttonVariants } from './button';
|
|
9713
|
+
export { Input } from './input';
|
|
9714
|
+
export {
|
|
9715
|
+
Card,
|
|
9716
|
+
CardHeader,
|
|
9717
|
+
CardFooter,
|
|
9718
|
+
CardTitle,
|
|
9719
|
+
CardDescription,
|
|
9720
|
+
CardContent,
|
|
9721
|
+
} from './card';
|
|
9722
|
+
export { Label } from './label';
|
|
9723
|
+
export { Badge, type BadgeProps, badgeVariants } from './badge';
|
|
9724
|
+
export { Separator } from './separator';
|
|
9725
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs';
|
|
9726
|
+
export {
|
|
9727
|
+
Tooltip,
|
|
9728
|
+
TooltipTrigger,
|
|
9729
|
+
TooltipContent,
|
|
9730
|
+
TooltipProvider,
|
|
9731
|
+
} from './tooltip';
|
|
9732
|
+
export {
|
|
9733
|
+
Dialog,
|
|
9734
|
+
DialogPortal,
|
|
9735
|
+
DialogOverlay,
|
|
9736
|
+
DialogTrigger,
|
|
9737
|
+
DialogClose,
|
|
9738
|
+
DialogContent,
|
|
9739
|
+
DialogHeader,
|
|
9740
|
+
DialogFooter,
|
|
9741
|
+
DialogTitle,
|
|
9742
|
+
DialogDescription,
|
|
9743
|
+
} from './dialog';
|
|
9744
|
+
`;
|
|
9745
|
+
const buttonIndexTsx = buttonTsx;
|
|
9746
|
+
const inputIndexTsx = inputTsx;
|
|
9747
|
+
const cardIndexTsx = cardTsx;
|
|
9748
|
+
const componentsIndex = `export * from './ui';
|
|
9749
|
+
`;
|
|
9750
|
+
const indexTs = `// @${options.name}/ui - Shared UI component library
|
|
9751
|
+
|
|
9752
|
+
// shadcn/ui components
|
|
9753
|
+
export * from './components';
|
|
9754
|
+
|
|
9755
|
+
// Utilities
|
|
9756
|
+
export { cn } from './lib/utils';
|
|
9757
|
+
`;
|
|
9758
|
+
const gitignore = `node_modules/
|
|
9759
|
+
dist/
|
|
9760
|
+
storybook-static/
|
|
9761
|
+
*.log
|
|
9762
|
+
`;
|
|
9763
|
+
return [
|
|
9764
|
+
{
|
|
9765
|
+
path: "packages/ui/package.json",
|
|
9766
|
+
content: `${JSON.stringify(packageJson, null, 2)}\n`
|
|
9767
|
+
},
|
|
9768
|
+
{
|
|
9769
|
+
path: "packages/ui/tsconfig.json",
|
|
9770
|
+
content: `${JSON.stringify(tsConfig, null, 2)}\n`
|
|
9771
|
+
},
|
|
9772
|
+
{
|
|
9773
|
+
path: "packages/ui/components.json",
|
|
9774
|
+
content: `${JSON.stringify(componentsJson, null, 2)}\n`
|
|
9775
|
+
},
|
|
9776
|
+
{
|
|
9777
|
+
path: "packages/ui/.storybook/main.ts",
|
|
9778
|
+
content: storybookMain
|
|
9779
|
+
},
|
|
9780
|
+
{
|
|
9781
|
+
path: "packages/ui/.storybook/preview.ts",
|
|
9782
|
+
content: storybookPreview
|
|
9783
|
+
},
|
|
9784
|
+
{
|
|
9785
|
+
path: "packages/ui/src/styles/globals.css",
|
|
9786
|
+
content: globalsCss
|
|
9787
|
+
},
|
|
9788
|
+
{
|
|
9789
|
+
path: "packages/ui/src/lib/utils.ts",
|
|
9790
|
+
content: utilsTs
|
|
9791
|
+
},
|
|
9792
|
+
{
|
|
9793
|
+
path: "packages/ui/src/components/ui/button/index.tsx",
|
|
9794
|
+
content: buttonIndexTsx
|
|
9795
|
+
},
|
|
9796
|
+
{
|
|
9797
|
+
path: "packages/ui/src/components/ui/button/button.stories.tsx",
|
|
9798
|
+
content: buttonStories
|
|
9799
|
+
},
|
|
9800
|
+
{
|
|
9801
|
+
path: "packages/ui/src/components/ui/input/index.tsx",
|
|
9802
|
+
content: inputIndexTsx
|
|
9803
|
+
},
|
|
9804
|
+
{
|
|
9805
|
+
path: "packages/ui/src/components/ui/input/input.stories.tsx",
|
|
9806
|
+
content: inputStories
|
|
9807
|
+
},
|
|
9808
|
+
{
|
|
9809
|
+
path: "packages/ui/src/components/ui/card/index.tsx",
|
|
9810
|
+
content: cardIndexTsx
|
|
9811
|
+
},
|
|
9812
|
+
{
|
|
9813
|
+
path: "packages/ui/src/components/ui/card/card.stories.tsx",
|
|
9814
|
+
content: cardStories
|
|
9815
|
+
},
|
|
9816
|
+
{
|
|
9817
|
+
path: "packages/ui/src/components/ui/label/index.tsx",
|
|
9818
|
+
content: labelTsx
|
|
9819
|
+
},
|
|
9820
|
+
{
|
|
9821
|
+
path: "packages/ui/src/components/ui/label/label.stories.tsx",
|
|
9822
|
+
content: labelStories
|
|
9823
|
+
},
|
|
9824
|
+
{
|
|
9825
|
+
path: "packages/ui/src/components/ui/badge/index.tsx",
|
|
9826
|
+
content: badgeTsx
|
|
9827
|
+
},
|
|
9828
|
+
{
|
|
9829
|
+
path: "packages/ui/src/components/ui/badge/badge.stories.tsx",
|
|
9830
|
+
content: badgeStories
|
|
9831
|
+
},
|
|
9832
|
+
{
|
|
9833
|
+
path: "packages/ui/src/components/ui/separator/index.tsx",
|
|
9834
|
+
content: separatorTsx
|
|
9835
|
+
},
|
|
9836
|
+
{
|
|
9837
|
+
path: "packages/ui/src/components/ui/separator/separator.stories.tsx",
|
|
9838
|
+
content: separatorStories
|
|
9839
|
+
},
|
|
9840
|
+
{
|
|
9841
|
+
path: "packages/ui/src/components/ui/tabs/index.tsx",
|
|
9842
|
+
content: tabsTsx
|
|
9843
|
+
},
|
|
9844
|
+
{
|
|
9845
|
+
path: "packages/ui/src/components/ui/tabs/tabs.stories.tsx",
|
|
9846
|
+
content: tabsStories
|
|
9847
|
+
},
|
|
9848
|
+
{
|
|
9849
|
+
path: "packages/ui/src/components/ui/tooltip/index.tsx",
|
|
9850
|
+
content: tooltipTsx
|
|
9851
|
+
},
|
|
9852
|
+
{
|
|
9853
|
+
path: "packages/ui/src/components/ui/tooltip/tooltip.stories.tsx",
|
|
9854
|
+
content: tooltipStories
|
|
9855
|
+
},
|
|
9856
|
+
{
|
|
9857
|
+
path: "packages/ui/src/components/ui/dialog/index.tsx",
|
|
9858
|
+
content: dialogTsx
|
|
9859
|
+
},
|
|
9860
|
+
{
|
|
9861
|
+
path: "packages/ui/src/components/ui/dialog/dialog.stories.tsx",
|
|
9862
|
+
content: dialogStories
|
|
9863
|
+
},
|
|
9864
|
+
{
|
|
9865
|
+
path: "packages/ui/src/components/ui/index.ts",
|
|
9866
|
+
content: componentsUiIndex
|
|
9867
|
+
},
|
|
9868
|
+
{
|
|
9869
|
+
path: "packages/ui/src/components/index.ts",
|
|
9870
|
+
content: componentsIndex
|
|
9871
|
+
},
|
|
9872
|
+
{
|
|
9873
|
+
path: "packages/ui/src/index.ts",
|
|
9874
|
+
content: indexTs
|
|
9875
|
+
},
|
|
9876
|
+
{
|
|
9877
|
+
path: "packages/ui/.gitignore",
|
|
9878
|
+
content: gitignore
|
|
9879
|
+
}
|
|
9880
|
+
];
|
|
9881
|
+
}
|
|
9882
|
+
|
|
8365
9883
|
//#endregion
|
|
8366
9884
|
//#region src/init/generators/web.ts
|
|
8367
9885
|
/**
|
|
@@ -8371,29 +9889,35 @@ function generateWebAppFiles(options) {
|
|
|
8371
9889
|
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
8372
9890
|
const packageName = `@${options.name}/web`;
|
|
8373
9891
|
const modelsPackage = `@${options.name}/models`;
|
|
9892
|
+
const uiPackage = `@${options.name}/ui`;
|
|
8374
9893
|
const packageJson = {
|
|
8375
9894
|
name: packageName,
|
|
8376
9895
|
version: "0.0.1",
|
|
8377
9896
|
private: true,
|
|
8378
9897
|
type: "module",
|
|
8379
9898
|
scripts: {
|
|
8380
|
-
dev: "next dev
|
|
8381
|
-
build: "next build",
|
|
9899
|
+
dev: "gkm exec -- next dev --turbopack",
|
|
9900
|
+
build: "gkm exec -- next build",
|
|
8382
9901
|
start: "next start",
|
|
8383
9902
|
typecheck: "tsc --noEmit"
|
|
8384
9903
|
},
|
|
8385
9904
|
dependencies: {
|
|
8386
9905
|
[modelsPackage]: "workspace:*",
|
|
9906
|
+
[uiPackage]: "workspace:*",
|
|
8387
9907
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
8388
9908
|
"@tanstack/react-query": "~5.80.0",
|
|
9909
|
+
"better-auth": "~1.2.0",
|
|
8389
9910
|
next: "~16.1.0",
|
|
8390
9911
|
react: "~19.2.0",
|
|
8391
9912
|
"react-dom": "~19.2.0"
|
|
8392
9913
|
},
|
|
8393
9914
|
devDependencies: {
|
|
9915
|
+
"@geekmidas/cli": GEEKMIDAS_VERSIONS["@geekmidas/cli"],
|
|
9916
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
8394
9917
|
"@types/node": "~22.0.0",
|
|
8395
9918
|
"@types/react": "~19.0.0",
|
|
8396
9919
|
"@types/react-dom": "~19.0.0",
|
|
9920
|
+
tailwindcss: "^4.0.0",
|
|
8397
9921
|
typescript: "~5.8.2"
|
|
8398
9922
|
}
|
|
8399
9923
|
};
|
|
@@ -8402,10 +9926,16 @@ function generateWebAppFiles(options) {
|
|
|
8402
9926
|
const nextConfig: NextConfig = {
|
|
8403
9927
|
output: 'standalone',
|
|
8404
9928
|
reactStrictMode: true,
|
|
8405
|
-
transpilePackages: ['${modelsPackage}'],
|
|
9929
|
+
transpilePackages: ['${modelsPackage}', '${uiPackage}'],
|
|
8406
9930
|
};
|
|
8407
9931
|
|
|
8408
9932
|
export default nextConfig;
|
|
9933
|
+
`;
|
|
9934
|
+
const postcssConfig = `export default {
|
|
9935
|
+
plugins: {
|
|
9936
|
+
'@tailwindcss/postcss': {},
|
|
9937
|
+
},
|
|
9938
|
+
};
|
|
8409
9939
|
`;
|
|
8410
9940
|
const tsConfig = {
|
|
8411
9941
|
extends: "../../tsconfig.json",
|
|
@@ -8428,9 +9958,11 @@ export default nextConfig;
|
|
|
8428
9958
|
incremental: true,
|
|
8429
9959
|
plugins: [{ name: "next" }],
|
|
8430
9960
|
paths: {
|
|
8431
|
-
"
|
|
9961
|
+
"~/*": ["./src/*"],
|
|
8432
9962
|
[`${modelsPackage}`]: ["../../packages/models/src"],
|
|
8433
|
-
[`${modelsPackage}/*`]: ["../../packages/models/src/*"]
|
|
9963
|
+
[`${modelsPackage}/*`]: ["../../packages/models/src/*"],
|
|
9964
|
+
[`${uiPackage}`]: ["../../packages/ui/src"],
|
|
9965
|
+
[`${uiPackage}/*`]: ["../../packages/ui/src/*"]
|
|
8434
9966
|
},
|
|
8435
9967
|
baseUrl: "."
|
|
8436
9968
|
},
|
|
@@ -8442,68 +9974,66 @@ export default nextConfig;
|
|
|
8442
9974
|
],
|
|
8443
9975
|
exclude: ["node_modules"]
|
|
8444
9976
|
};
|
|
9977
|
+
const queryClientTs = `import { QueryClient } from '@tanstack/react-query';
|
|
9978
|
+
|
|
9979
|
+
function makeQueryClient() {
|
|
9980
|
+
return new QueryClient({
|
|
9981
|
+
defaultOptions: {
|
|
9982
|
+
queries: {
|
|
9983
|
+
staleTime: 60 * 1000,
|
|
9984
|
+
},
|
|
9985
|
+
},
|
|
9986
|
+
});
|
|
9987
|
+
}
|
|
9988
|
+
|
|
9989
|
+
let browserQueryClient: QueryClient | undefined = undefined;
|
|
9990
|
+
|
|
9991
|
+
export function getQueryClient() {
|
|
9992
|
+
if (typeof window === 'undefined') {
|
|
9993
|
+
// Server: always make a new query client
|
|
9994
|
+
return makeQueryClient();
|
|
9995
|
+
}
|
|
9996
|
+
// Browser: reuse existing query client
|
|
9997
|
+
if (!browserQueryClient) browserQueryClient = makeQueryClient();
|
|
9998
|
+
return browserQueryClient;
|
|
9999
|
+
}
|
|
10000
|
+
`;
|
|
10001
|
+
const authClientTs = `import { createAuthClient } from 'better-auth/react';
|
|
10002
|
+
import { magicLinkClient } from 'better-auth/client/plugins';
|
|
10003
|
+
|
|
10004
|
+
export const authClient = createAuthClient({
|
|
10005
|
+
baseURL: process.env.NEXT_PUBLIC_AUTH_URL || 'http://localhost:3002',
|
|
10006
|
+
plugins: [magicLinkClient()],
|
|
10007
|
+
});
|
|
10008
|
+
|
|
10009
|
+
export const { signIn, signUp, signOut, useSession, magicLink } = authClient;
|
|
10010
|
+
`;
|
|
8445
10011
|
const providersTsx = `'use client';
|
|
8446
10012
|
|
|
8447
|
-
import {
|
|
8448
|
-
import {
|
|
10013
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
10014
|
+
import { getQueryClient } from '~/lib/query-client';
|
|
8449
10015
|
|
|
8450
10016
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
8451
|
-
const
|
|
8452
|
-
() =>
|
|
8453
|
-
new QueryClient({
|
|
8454
|
-
defaultOptions: {
|
|
8455
|
-
queries: {
|
|
8456
|
-
staleTime: 60 * 1000,
|
|
8457
|
-
},
|
|
8458
|
-
},
|
|
8459
|
-
}),
|
|
8460
|
-
);
|
|
10017
|
+
const queryClient = getQueryClient();
|
|
8461
10018
|
|
|
8462
10019
|
return (
|
|
8463
10020
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
8464
10021
|
);
|
|
8465
10022
|
}
|
|
8466
10023
|
`;
|
|
8467
|
-
const apiIndexTs = `import {
|
|
8468
|
-
import {
|
|
8469
|
-
|
|
8470
|
-
// TODO: Run 'gkm openapi' to generate typed paths from your API
|
|
8471
|
-
// This is a placeholder that will be replaced by the generated openapi.ts
|
|
8472
|
-
interface paths {
|
|
8473
|
-
'/health': {
|
|
8474
|
-
get: {
|
|
8475
|
-
responses: {
|
|
8476
|
-
200: {
|
|
8477
|
-
content: {
|
|
8478
|
-
'application/json': { status: string; timestamp: string };
|
|
8479
|
-
};
|
|
8480
|
-
};
|
|
8481
|
-
};
|
|
8482
|
-
};
|
|
8483
|
-
};
|
|
8484
|
-
'/users': {
|
|
8485
|
-
get: {
|
|
8486
|
-
responses: {
|
|
8487
|
-
200: {
|
|
8488
|
-
content: {
|
|
8489
|
-
'application/json': { users: Array<{ id: string; name: string }> };
|
|
8490
|
-
};
|
|
8491
|
-
};
|
|
8492
|
-
};
|
|
8493
|
-
};
|
|
8494
|
-
};
|
|
8495
|
-
}
|
|
8496
|
-
|
|
8497
|
-
const baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
|
8498
|
-
|
|
8499
|
-
const fetcher = new TypedFetcher<paths>({ baseURL });
|
|
10024
|
+
const apiIndexTs = `import { createApi } from './openapi';
|
|
10025
|
+
import { getQueryClient } from '~/lib/query-client';
|
|
8500
10026
|
|
|
8501
|
-
const
|
|
8502
|
-
|
|
8503
|
-
|
|
10027
|
+
export const api = createApi({
|
|
10028
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
|
|
10029
|
+
queryClient: getQueryClient(),
|
|
10030
|
+
});
|
|
10031
|
+
`;
|
|
10032
|
+
const globalsCss = `@import '${uiPackage}/styles';
|
|
8504
10033
|
`;
|
|
8505
10034
|
const layoutTsx = `import type { Metadata } from 'next';
|
|
8506
10035
|
import { Providers } from './providers';
|
|
10036
|
+
import './globals.css';
|
|
8507
10037
|
|
|
8508
10038
|
export const metadata: Metadata = {
|
|
8509
10039
|
title: '${options.name}',
|
|
@@ -8524,42 +10054,59 @@ export default function RootLayout({
|
|
|
8524
10054
|
);
|
|
8525
10055
|
}
|
|
8526
10056
|
`;
|
|
8527
|
-
const pageTsx = `import { api } from '
|
|
10057
|
+
const pageTsx = `import { api } from '~/api';
|
|
10058
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '${uiPackage}/components';
|
|
8528
10059
|
|
|
8529
10060
|
export default async function Home() {
|
|
8530
10061
|
// Type-safe API call using the generated client
|
|
8531
10062
|
const health = await api('GET /health').catch(() => null);
|
|
8532
10063
|
|
|
8533
10064
|
return (
|
|
8534
|
-
<main
|
|
8535
|
-
<
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
</
|
|
8556
|
-
|
|
10065
|
+
<main className="min-h-screen bg-background p-8">
|
|
10066
|
+
<div className="mx-auto max-w-4xl space-y-8">
|
|
10067
|
+
<div className="space-y-2">
|
|
10068
|
+
<h1 className="text-4xl font-bold tracking-tight">Welcome to ${options.name}</h1>
|
|
10069
|
+
<p className="text-muted-foreground">Your fullstack application is ready.</p>
|
|
10070
|
+
</div>
|
|
10071
|
+
|
|
10072
|
+
<Card>
|
|
10073
|
+
<CardHeader>
|
|
10074
|
+
<CardTitle>API Status</CardTitle>
|
|
10075
|
+
<CardDescription>Connection to your backend API</CardDescription>
|
|
10076
|
+
</CardHeader>
|
|
10077
|
+
<CardContent>
|
|
10078
|
+
{health ? (
|
|
10079
|
+
<pre className="rounded-lg bg-muted p-4 text-sm">
|
|
10080
|
+
{JSON.stringify(health, null, 2)}
|
|
10081
|
+
</pre>
|
|
10082
|
+
) : (
|
|
10083
|
+
<p className="text-destructive">Unable to connect to API</p>
|
|
10084
|
+
)}
|
|
10085
|
+
</CardContent>
|
|
10086
|
+
</Card>
|
|
10087
|
+
|
|
10088
|
+
<Card>
|
|
10089
|
+
<CardHeader>
|
|
10090
|
+
<CardTitle>Next Steps</CardTitle>
|
|
10091
|
+
<CardDescription>Get started with your project</CardDescription>
|
|
10092
|
+
</CardHeader>
|
|
10093
|
+
<CardContent className="space-y-4">
|
|
10094
|
+
<ul className="list-inside list-disc space-y-2 text-muted-foreground">
|
|
10095
|
+
<li>Run <code className="rounded bg-muted px-1">gkm openapi</code> to generate typed API client</li>
|
|
10096
|
+
<li>Edit <code className="rounded bg-muted px-1">apps/web/src/app/page.tsx</code> to customize this page</li>
|
|
10097
|
+
<li>Add API routes in <code className="rounded bg-muted px-1">apps/api/src/endpoints/</code></li>
|
|
10098
|
+
<li>Add UI components with <code className="rounded bg-muted px-1">npx shadcn@latest add</code> in packages/ui</li>
|
|
10099
|
+
</ul>
|
|
10100
|
+
<div className="flex gap-4">
|
|
10101
|
+
<Button>Get Started</Button>
|
|
10102
|
+
<Button variant="outline">Documentation</Button>
|
|
10103
|
+
</div>
|
|
10104
|
+
</CardContent>
|
|
10105
|
+
</Card>
|
|
10106
|
+
</div>
|
|
8557
10107
|
</main>
|
|
8558
10108
|
);
|
|
8559
10109
|
}
|
|
8560
|
-
`;
|
|
8561
|
-
const envLocal = `# API URL for client-side requests
|
|
8562
|
-
NEXT_PUBLIC_API_URL=http://localhost:3000
|
|
8563
10110
|
`;
|
|
8564
10111
|
const gitignore = `.next/
|
|
8565
10112
|
node_modules/
|
|
@@ -8575,10 +10122,18 @@ node_modules/
|
|
|
8575
10122
|
path: "apps/web/next.config.ts",
|
|
8576
10123
|
content: nextConfig
|
|
8577
10124
|
},
|
|
10125
|
+
{
|
|
10126
|
+
path: "apps/web/postcss.config.mjs",
|
|
10127
|
+
content: postcssConfig
|
|
10128
|
+
},
|
|
8578
10129
|
{
|
|
8579
10130
|
path: "apps/web/tsconfig.json",
|
|
8580
10131
|
content: `${JSON.stringify(tsConfig, null, 2)}\n`
|
|
8581
10132
|
},
|
|
10133
|
+
{
|
|
10134
|
+
path: "apps/web/src/app/globals.css",
|
|
10135
|
+
content: globalsCss
|
|
10136
|
+
},
|
|
8582
10137
|
{
|
|
8583
10138
|
path: "apps/web/src/app/layout.tsx",
|
|
8584
10139
|
content: layoutTsx
|
|
@@ -8592,12 +10147,16 @@ node_modules/
|
|
|
8592
10147
|
content: pageTsx
|
|
8593
10148
|
},
|
|
8594
10149
|
{
|
|
8595
|
-
path: "apps/web/src/
|
|
8596
|
-
content:
|
|
10150
|
+
path: "apps/web/src/lib/query-client.ts",
|
|
10151
|
+
content: queryClientTs
|
|
10152
|
+
},
|
|
10153
|
+
{
|
|
10154
|
+
path: "apps/web/src/lib/auth-client.ts",
|
|
10155
|
+
content: authClientTs
|
|
8597
10156
|
},
|
|
8598
10157
|
{
|
|
8599
|
-
path: "apps/web
|
|
8600
|
-
content:
|
|
10158
|
+
path: "apps/web/src/api/index.ts",
|
|
10159
|
+
content: apiIndexTs
|
|
8601
10160
|
},
|
|
8602
10161
|
{
|
|
8603
10162
|
path: "apps/web/.gitignore",
|
|
@@ -8836,6 +10395,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
8836
10395
|
const rootFiles = baseTemplate ? [...generateMonorepoFiles(templateOptions, baseTemplate), ...generateModelsPackage(templateOptions)] : [];
|
|
8837
10396
|
const webAppFiles = isFullstack ? generateWebAppFiles(templateOptions) : [];
|
|
8838
10397
|
const authAppFiles = isFullstack ? generateAuthAppFiles(templateOptions) : [];
|
|
10398
|
+
const uiPackageFiles = isFullstack ? generateUiPackageFiles(templateOptions) : [];
|
|
8839
10399
|
for (const { path, content } of rootFiles) {
|
|
8840
10400
|
const fullPath = (0, node_path.join)(targetDir, path);
|
|
8841
10401
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
@@ -8861,6 +10421,11 @@ async function initCommand(projectName, options = {}) {
|
|
|
8861
10421
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
8862
10422
|
await (0, node_fs_promises.writeFile)(fullPath, content);
|
|
8863
10423
|
}
|
|
10424
|
+
for (const { path, content } of uiPackageFiles) {
|
|
10425
|
+
const fullPath = (0, node_path.join)(targetDir, path);
|
|
10426
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
10427
|
+
await (0, node_fs_promises.writeFile)(fullPath, content);
|
|
10428
|
+
}
|
|
8864
10429
|
console.log("🔐 Initializing encrypted secrets...\n");
|
|
8865
10430
|
const secretServices = [];
|
|
8866
10431
|
if (services.db) secretServices.push("postgres");
|
|
@@ -8880,6 +10445,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
8880
10445
|
customSecrets[passwordKey] = app.password;
|
|
8881
10446
|
}
|
|
8882
10447
|
customSecrets.AUTH_PORT = "3002";
|
|
10448
|
+
customSecrets.AUTH_URL = "http://localhost:3002";
|
|
8883
10449
|
customSecrets.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
8884
10450
|
customSecrets.BETTER_AUTH_URL = "http://localhost:3002";
|
|
8885
10451
|
customSecrets.BETTER_AUTH_TRUSTED_ORIGINS = "http://localhost:3000,http://localhost:3001";
|
|
@@ -8956,7 +10522,8 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
8956
10522
|
console.log(` │ └── web/ # Next.js frontend`);
|
|
8957
10523
|
}
|
|
8958
10524
|
console.log(` ├── packages/`);
|
|
8959
|
-
console.log(` │
|
|
10525
|
+
console.log(` │ ├── models/ # Shared Zod schemas`);
|
|
10526
|
+
if (isFullstackTemplate(options.template)) console.log(` │ └── ui/ # Shared UI components`);
|
|
8960
10527
|
console.log(` ├── .gkm/secrets/ # Encrypted secrets`);
|
|
8961
10528
|
console.log(` ├── gkm.config.ts # Workspace config`);
|
|
8962
10529
|
console.log(` └── turbo.json # Turbo config`);
|
|
@@ -8972,7 +10539,7 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
8972
10539
|
console.log(` ${getRunCommand(pkgManager, "deploy")}`);
|
|
8973
10540
|
console.log("");
|
|
8974
10541
|
}
|
|
8975
|
-
console.log("📚 Documentation: https://
|
|
10542
|
+
console.log("📚 Documentation: https://geekmidas.github.io/toolbox/");
|
|
8976
10543
|
console.log("");
|
|
8977
10544
|
}
|
|
8978
10545
|
|
|
@@ -9524,6 +11091,46 @@ program.command("whoami").description("Show current authentication status").acti
|
|
|
9524
11091
|
process.exit(1);
|
|
9525
11092
|
}
|
|
9526
11093
|
});
|
|
11094
|
+
program.command("state:pull").description("Pull deployment state from remote to local").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").action(async (options) => {
|
|
11095
|
+
try {
|
|
11096
|
+
const globalOptions = program.opts();
|
|
11097
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11098
|
+
await statePullCommand(options);
|
|
11099
|
+
} catch (error) {
|
|
11100
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11101
|
+
process.exit(1);
|
|
11102
|
+
}
|
|
11103
|
+
});
|
|
11104
|
+
program.command("state:push").description("Push deployment state from local to remote").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").action(async (options) => {
|
|
11105
|
+
try {
|
|
11106
|
+
const globalOptions = program.opts();
|
|
11107
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11108
|
+
await statePushCommand(options);
|
|
11109
|
+
} catch (error) {
|
|
11110
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11111
|
+
process.exit(1);
|
|
11112
|
+
}
|
|
11113
|
+
});
|
|
11114
|
+
program.command("state:show").description("Show deployment state for a stage").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--json", "Output as JSON").action(async (options) => {
|
|
11115
|
+
try {
|
|
11116
|
+
const globalOptions = program.opts();
|
|
11117
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11118
|
+
await stateShowCommand(options);
|
|
11119
|
+
} catch (error) {
|
|
11120
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11121
|
+
process.exit(1);
|
|
11122
|
+
}
|
|
11123
|
+
});
|
|
11124
|
+
program.command("state:diff").description("Compare local and remote deployment state").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").action(async (options) => {
|
|
11125
|
+
try {
|
|
11126
|
+
const globalOptions = program.opts();
|
|
11127
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11128
|
+
await stateDiffCommand(options);
|
|
11129
|
+
} catch (error) {
|
|
11130
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11131
|
+
process.exit(1);
|
|
11132
|
+
}
|
|
11133
|
+
});
|
|
9527
11134
|
program.parse();
|
|
9528
11135
|
|
|
9529
11136
|
//#endregion
|