@geekmidas/cli 0.53.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -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 +2242 -568
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2219 -545
- 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 +12 -8
- 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/endpoints/auth.ts +16 -0
- package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/health.ts +13 -0
- package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/users.ts +15 -0
- package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +55 -0
- 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 +41 -17
- package/src/deploy/__tests__/sniffer.spec.ts +168 -10
- 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 +130 -5
- 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/docker/templates.ts +3 -3
- 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/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,19 +1,20 @@
|
|
|
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"));
|
|
@@ -25,13 +26,13 @@ const __geekmidas_constructs_subscribers = require_chunk.__toESM(require("@geekm
|
|
|
25
26
|
const node_crypto = require_chunk.__toESM(require("node:crypto"));
|
|
26
27
|
const pg = require_chunk.__toESM(require("pg"));
|
|
27
28
|
const node_dns_promises = require_chunk.__toESM(require("node:dns/promises"));
|
|
29
|
+
const node_module = require_chunk.__toESM(require("node:module"));
|
|
28
30
|
const node_url = require_chunk.__toESM(require("node:url"));
|
|
29
31
|
const prompts = require_chunk.__toESM(require("prompts"));
|
|
30
|
-
const node_module = require_chunk.__toESM(require("node:module"));
|
|
31
32
|
|
|
32
33
|
//#region package.json
|
|
33
34
|
var name = "@geekmidas/cli";
|
|
34
|
-
var version = "0.
|
|
35
|
+
var version = "0.54.0";
|
|
35
36
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
36
37
|
var private$1 = false;
|
|
37
38
|
var type = "module";
|
|
@@ -75,6 +76,9 @@ var repository = {
|
|
|
75
76
|
};
|
|
76
77
|
var dependencies = {
|
|
77
78
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
79
|
+
"@aws-sdk/client-route-53": "~3.971.0",
|
|
80
|
+
"@aws-sdk/client-ssm": "~3.971.0",
|
|
81
|
+
"@aws-sdk/credential-providers": "~3.971.0",
|
|
78
82
|
"@geekmidas/constructs": "workspace:~",
|
|
79
83
|
"@geekmidas/envkit": "workspace:~",
|
|
80
84
|
"@geekmidas/errors": "workspace:~",
|
|
@@ -88,7 +92,8 @@ var dependencies = {
|
|
|
88
92
|
"lodash.kebabcase": "^4.1.1",
|
|
89
93
|
"openapi-typescript": "^7.4.2",
|
|
90
94
|
"pg": "~8.17.1",
|
|
91
|
-
"prompts": "~2.4.2"
|
|
95
|
+
"prompts": "~2.4.2",
|
|
96
|
+
"tsx": "~4.20.3"
|
|
92
97
|
};
|
|
93
98
|
var devDependencies = {
|
|
94
99
|
"@geekmidas/testkit": "workspace:*",
|
|
@@ -118,138 +123,6 @@ var package_default = {
|
|
|
118
123
|
peerDependenciesMeta
|
|
119
124
|
};
|
|
120
125
|
|
|
121
|
-
//#endregion
|
|
122
|
-
//#region src/auth/credentials.ts
|
|
123
|
-
/**
|
|
124
|
-
* Get the path to the credentials directory
|
|
125
|
-
*/
|
|
126
|
-
function getCredentialsDir(options) {
|
|
127
|
-
const root = options?.root ?? (0, node_os.homedir)();
|
|
128
|
-
return (0, node_path.join)(root, ".gkm");
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Get the path to the credentials file
|
|
132
|
-
*/
|
|
133
|
-
function getCredentialsPath(options) {
|
|
134
|
-
return (0, node_path.join)(getCredentialsDir(options), "credentials.json");
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Ensure the credentials directory exists
|
|
138
|
-
*/
|
|
139
|
-
function ensureCredentialsDir(options) {
|
|
140
|
-
const dir = getCredentialsDir(options);
|
|
141
|
-
if (!(0, node_fs.existsSync)(dir)) (0, node_fs.mkdirSync)(dir, {
|
|
142
|
-
recursive: true,
|
|
143
|
-
mode: 448
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Read stored credentials from disk
|
|
148
|
-
*/
|
|
149
|
-
async function readCredentials(options) {
|
|
150
|
-
const path = getCredentialsPath(options);
|
|
151
|
-
if (!(0, node_fs.existsSync)(path)) return {};
|
|
152
|
-
try {
|
|
153
|
-
const content = await (0, node_fs_promises.readFile)(path, "utf-8");
|
|
154
|
-
return JSON.parse(content);
|
|
155
|
-
} catch {
|
|
156
|
-
return {};
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Write credentials to disk
|
|
161
|
-
*/
|
|
162
|
-
async function writeCredentials(credentials, options) {
|
|
163
|
-
ensureCredentialsDir(options);
|
|
164
|
-
const path = getCredentialsPath(options);
|
|
165
|
-
await (0, node_fs_promises.writeFile)(path, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Store Dokploy credentials
|
|
169
|
-
*/
|
|
170
|
-
async function storeDokployCredentials(token, endpoint, options) {
|
|
171
|
-
const credentials = await readCredentials(options);
|
|
172
|
-
credentials.dokploy = {
|
|
173
|
-
token,
|
|
174
|
-
endpoint,
|
|
175
|
-
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
176
|
-
};
|
|
177
|
-
await writeCredentials(credentials, options);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Get stored Dokploy credentials
|
|
181
|
-
*/
|
|
182
|
-
async function getDokployCredentials(options) {
|
|
183
|
-
const credentials = await readCredentials(options);
|
|
184
|
-
if (!credentials.dokploy) return null;
|
|
185
|
-
return {
|
|
186
|
-
token: credentials.dokploy.token,
|
|
187
|
-
endpoint: credentials.dokploy.endpoint,
|
|
188
|
-
registryId: credentials.dokploy.registryId
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Remove Dokploy credentials
|
|
193
|
-
*/
|
|
194
|
-
async function removeDokployCredentials(options) {
|
|
195
|
-
const credentials = await readCredentials(options);
|
|
196
|
-
if (!credentials.dokploy) return false;
|
|
197
|
-
delete credentials.dokploy;
|
|
198
|
-
await writeCredentials(credentials, options);
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Get Dokploy API token, checking stored credentials first, then environment
|
|
203
|
-
*/
|
|
204
|
-
async function getDokployToken(options) {
|
|
205
|
-
const envToken = process.env.DOKPLOY_API_TOKEN;
|
|
206
|
-
if (envToken) return envToken;
|
|
207
|
-
const stored = await getDokployCredentials(options);
|
|
208
|
-
if (stored) return stored.token;
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Store Dokploy registry ID
|
|
213
|
-
*/
|
|
214
|
-
async function storeDokployRegistryId(registryId, options) {
|
|
215
|
-
const credentials = await readCredentials(options);
|
|
216
|
-
if (!credentials.dokploy) throw new Error("Dokploy credentials not found. Run \"gkm login --service dokploy\" first.");
|
|
217
|
-
credentials.dokploy.registryId = registryId;
|
|
218
|
-
await writeCredentials(credentials, options);
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Get Dokploy registry ID from stored credentials
|
|
222
|
-
*/
|
|
223
|
-
async function getDokployRegistryId(options) {
|
|
224
|
-
const stored = await getDokployCredentials(options);
|
|
225
|
-
return stored?.registryId ?? void 0;
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Store Hostinger API token
|
|
229
|
-
*
|
|
230
|
-
* @param token - API token from hpanel.hostinger.com/profile/api
|
|
231
|
-
*/
|
|
232
|
-
async function storeHostingerToken(token, options) {
|
|
233
|
-
const credentials = await readCredentials(options);
|
|
234
|
-
credentials.hostinger = {
|
|
235
|
-
token,
|
|
236
|
-
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
237
|
-
};
|
|
238
|
-
await writeCredentials(credentials, options);
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Get stored Hostinger API token
|
|
242
|
-
*
|
|
243
|
-
* Checks environment variable first (HOSTINGER_API_TOKEN),
|
|
244
|
-
* then falls back to stored credentials.
|
|
245
|
-
*/
|
|
246
|
-
async function getHostingerToken(options) {
|
|
247
|
-
const envToken = process.env.HOSTINGER_API_TOKEN;
|
|
248
|
-
if (envToken) return envToken;
|
|
249
|
-
const credentials = await readCredentials(options);
|
|
250
|
-
return credentials.hostinger?.token ?? null;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
126
|
//#endregion
|
|
254
127
|
//#region src/auth/index.ts
|
|
255
128
|
const logger$11 = console;
|
|
@@ -257,7 +130,7 @@ const logger$11 = console;
|
|
|
257
130
|
* Validate Dokploy token by making a test API call
|
|
258
131
|
*/
|
|
259
132
|
async function validateDokployToken(endpoint, token) {
|
|
260
|
-
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-
|
|
133
|
+
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-CWc02yyg.cjs"));
|
|
261
134
|
const api = new DokployApi$1({
|
|
262
135
|
baseUrl: endpoint,
|
|
263
136
|
token
|
|
@@ -345,10 +218,10 @@ async function loginCommand(options) {
|
|
|
345
218
|
logger$11.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
346
219
|
process.exit(1);
|
|
347
220
|
}
|
|
348
|
-
await storeDokployCredentials(token, endpoint);
|
|
221
|
+
await require_credentials.storeDokployCredentials(token, endpoint);
|
|
349
222
|
logger$11.log("\n✓ Successfully logged in to Dokploy!");
|
|
350
223
|
logger$11.log(` Endpoint: ${endpoint}`);
|
|
351
|
-
logger$11.log(` Credentials stored in: ${getCredentialsPath()}`);
|
|
224
|
+
logger$11.log(` Credentials stored in: ${require_credentials.getCredentialsPath()}`);
|
|
352
225
|
logger$11.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
353
226
|
}
|
|
354
227
|
}
|
|
@@ -358,13 +231,13 @@ async function loginCommand(options) {
|
|
|
358
231
|
async function logoutCommand(options) {
|
|
359
232
|
const { service = "dokploy" } = options;
|
|
360
233
|
if (service === "all") {
|
|
361
|
-
const dokployRemoved = await removeDokployCredentials();
|
|
234
|
+
const dokployRemoved = await require_credentials.removeDokployCredentials();
|
|
362
235
|
if (dokployRemoved) logger$11.log("\n✓ Logged out from all services");
|
|
363
236
|
else logger$11.log("\nNo stored credentials found");
|
|
364
237
|
return;
|
|
365
238
|
}
|
|
366
239
|
if (service === "dokploy") {
|
|
367
|
-
const removed = await removeDokployCredentials();
|
|
240
|
+
const removed = await require_credentials.removeDokployCredentials();
|
|
368
241
|
if (removed) logger$11.log("\n✓ Logged out from Dokploy");
|
|
369
242
|
else logger$11.log("\nNo Dokploy credentials found");
|
|
370
243
|
}
|
|
@@ -374,13 +247,13 @@ async function logoutCommand(options) {
|
|
|
374
247
|
*/
|
|
375
248
|
async function whoamiCommand() {
|
|
376
249
|
logger$11.log("\n📋 Current credentials:\n");
|
|
377
|
-
const dokploy = await getDokployCredentials();
|
|
250
|
+
const dokploy = await require_credentials.getDokployCredentials();
|
|
378
251
|
if (dokploy) {
|
|
379
252
|
logger$11.log(" Dokploy:");
|
|
380
253
|
logger$11.log(` Endpoint: ${dokploy.endpoint}`);
|
|
381
254
|
logger$11.log(` Token: ${maskToken(dokploy.token)}`);
|
|
382
255
|
} else logger$11.log(" Dokploy: Not logged in");
|
|
383
|
-
logger$11.log(`\n Credentials file: ${getCredentialsPath()}`);
|
|
256
|
+
logger$11.log(`\n Credentials file: ${require_credentials.getCredentialsPath()}`);
|
|
384
257
|
}
|
|
385
258
|
/**
|
|
386
259
|
* Mask a token for display
|
|
@@ -2152,7 +2025,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2152
2025
|
let masterKey;
|
|
2153
2026
|
if (context.production?.bundle && !skipBundle) {
|
|
2154
2027
|
logger$7.log(`\n📦 Bundling production server...`);
|
|
2155
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
2028
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-tHLLwYuU.cjs"));
|
|
2156
2029
|
const allConstructs = [
|
|
2157
2030
|
...endpoints.map((e) => e.construct),
|
|
2158
2031
|
...functions.map((f) => f.construct),
|
|
@@ -2280,37 +2153,6 @@ function getAppOutputPath(workspace, _appName, app) {
|
|
|
2280
2153
|
//#endregion
|
|
2281
2154
|
//#region src/deploy/state.ts
|
|
2282
2155
|
/**
|
|
2283
|
-
* Get the state file path for a stage
|
|
2284
|
-
*/
|
|
2285
|
-
function getStateFilePath(workspaceRoot, stage) {
|
|
2286
|
-
return (0, node_path.join)(workspaceRoot, ".gkm", `deploy-${stage}.json`);
|
|
2287
|
-
}
|
|
2288
|
-
/**
|
|
2289
|
-
* Read the deploy state for a stage
|
|
2290
|
-
* Returns null if state file doesn't exist
|
|
2291
|
-
*/
|
|
2292
|
-
async function readStageState(workspaceRoot, stage) {
|
|
2293
|
-
const filePath = getStateFilePath(workspaceRoot, stage);
|
|
2294
|
-
try {
|
|
2295
|
-
const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
2296
|
-
return JSON.parse(content);
|
|
2297
|
-
} catch (error) {
|
|
2298
|
-
if (error.code === "ENOENT") return null;
|
|
2299
|
-
console.warn(`Warning: Could not read deploy state: ${error}`);
|
|
2300
|
-
return null;
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
/**
|
|
2304
|
-
* Write the deploy state for a stage
|
|
2305
|
-
*/
|
|
2306
|
-
async function writeStageState(workspaceRoot, stage, state) {
|
|
2307
|
-
const filePath = getStateFilePath(workspaceRoot, stage);
|
|
2308
|
-
const dir = (0, node_path.join)(workspaceRoot, ".gkm");
|
|
2309
|
-
await (0, node_fs_promises.mkdir)(dir, { recursive: true });
|
|
2310
|
-
state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2311
|
-
await (0, node_fs_promises.writeFile)(filePath, JSON.stringify(state, null, 2));
|
|
2312
|
-
}
|
|
2313
|
-
/**
|
|
2314
2156
|
* Create a new empty state for a stage
|
|
2315
2157
|
*/
|
|
2316
2158
|
function createEmptyState(stage, environmentId) {
|
|
@@ -2405,155 +2247,90 @@ function isDnsVerified(state, hostname, serverIp) {
|
|
|
2405
2247
|
}
|
|
2406
2248
|
|
|
2407
2249
|
//#endregion
|
|
2408
|
-
//#region src/deploy/dns/
|
|
2250
|
+
//#region src/deploy/dns/DnsProvider.ts
|
|
2251
|
+
/**
|
|
2252
|
+
* Check if value is a DnsProvider implementation.
|
|
2253
|
+
*/
|
|
2254
|
+
function isDnsProvider(value) {
|
|
2255
|
+
return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.getRecords === "function" && typeof value.upsertRecords === "function";
|
|
2256
|
+
}
|
|
2409
2257
|
/**
|
|
2410
|
-
*
|
|
2258
|
+
* Create a DNS provider based on configuration.
|
|
2411
2259
|
*
|
|
2412
|
-
*
|
|
2413
|
-
*
|
|
2260
|
+
* - 'hostinger': HostingerProvider
|
|
2261
|
+
* - 'route53': Route53Provider
|
|
2262
|
+
* - 'manual': Returns null (user handles DNS)
|
|
2263
|
+
* - Custom: Use provided DnsProvider implementation
|
|
2264
|
+
*/
|
|
2265
|
+
async function createDnsProvider(options) {
|
|
2266
|
+
const { config } = options;
|
|
2267
|
+
if (config.provider === "manual") return null;
|
|
2268
|
+
if (isDnsProvider(config.provider)) return config.provider;
|
|
2269
|
+
const provider = config.provider;
|
|
2270
|
+
if (provider === "hostinger") {
|
|
2271
|
+
const { HostingerProvider } = await Promise.resolve().then(() => require("./HostingerProvider-DUV9-Tzg.cjs"));
|
|
2272
|
+
return new HostingerProvider();
|
|
2273
|
+
}
|
|
2274
|
+
if (provider === "route53") {
|
|
2275
|
+
const { Route53Provider } = await Promise.resolve().then(() => require("./Route53Provider-CpRIqu69.cjs"));
|
|
2276
|
+
const route53Config = config;
|
|
2277
|
+
return new Route53Provider({
|
|
2278
|
+
region: route53Config.region,
|
|
2279
|
+
profile: route53Config.profile,
|
|
2280
|
+
hostedZoneId: route53Config.hostedZoneId
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
if (provider === "cloudflare") throw new Error("Cloudflare DNS provider not yet implemented");
|
|
2284
|
+
throw new Error(`Unknown DNS provider: ${JSON.stringify(config)}`);
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
//#endregion
|
|
2288
|
+
//#region src/deploy/dns/index.ts
|
|
2289
|
+
const logger$6 = console;
|
|
2290
|
+
/**
|
|
2291
|
+
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
2414
2292
|
*/
|
|
2415
|
-
|
|
2293
|
+
function isLegacyDnsConfig(config) {
|
|
2294
|
+
return typeof config === "object" && config !== null && "provider" in config && "domain" in config;
|
|
2295
|
+
}
|
|
2416
2296
|
/**
|
|
2417
|
-
*
|
|
2297
|
+
* Normalize DNS config to new multi-domain format
|
|
2418
2298
|
*/
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
this.statusText = statusText;
|
|
2424
|
-
this.errors = errors;
|
|
2425
|
-
this.name = "HostingerApiError";
|
|
2299
|
+
function normalizeDnsConfig(config) {
|
|
2300
|
+
if (isLegacyDnsConfig(config)) {
|
|
2301
|
+
const { domain,...providerConfig } = config;
|
|
2302
|
+
return { [domain]: providerConfig };
|
|
2426
2303
|
}
|
|
2427
|
-
|
|
2304
|
+
return config;
|
|
2305
|
+
}
|
|
2428
2306
|
/**
|
|
2429
|
-
*
|
|
2307
|
+
* Find the root domain for a hostname from available DNS configs
|
|
2430
2308
|
*
|
|
2431
2309
|
* @example
|
|
2432
|
-
*
|
|
2433
|
-
*
|
|
2434
|
-
*
|
|
2435
|
-
* // Get all records for a domain
|
|
2436
|
-
* const records = await api.getRecords('traflabs.io');
|
|
2437
|
-
*
|
|
2438
|
-
* // Create/update records
|
|
2439
|
-
* await api.upsertRecords('traflabs.io', [
|
|
2440
|
-
* { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
|
|
2441
|
-
* ]);
|
|
2442
|
-
* ```
|
|
2310
|
+
* findRootDomain('api.geekmidas.com', { 'geekmidas.com': {...}, 'geekmidas.dev': {...} })
|
|
2311
|
+
* // Returns 'geekmidas.com'
|
|
2443
2312
|
*/
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
},
|
|
2460
|
-
body: body ? JSON.stringify(body) : void 0
|
|
2461
|
-
});
|
|
2462
|
-
if (!response.ok) {
|
|
2463
|
-
let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
|
|
2464
|
-
let errors;
|
|
2465
|
-
try {
|
|
2466
|
-
const errorBody = await response.json();
|
|
2467
|
-
if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
|
|
2468
|
-
errors = errorBody.errors;
|
|
2469
|
-
} catch {}
|
|
2470
|
-
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
2313
|
+
function findRootDomain(hostname, dnsConfig) {
|
|
2314
|
+
const domains = Object.keys(dnsConfig).sort((a, b) => b.length - a.length);
|
|
2315
|
+
for (const domain of domains) if (hostname === domain || hostname.endsWith(`.${domain}`)) return domain;
|
|
2316
|
+
return null;
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Group hostnames by their root domain
|
|
2320
|
+
*/
|
|
2321
|
+
function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
2322
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2323
|
+
for (const [appName, hostname] of appHostnames) {
|
|
2324
|
+
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
2325
|
+
if (!rootDomain) {
|
|
2326
|
+
logger$6.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
2327
|
+
continue;
|
|
2471
2328
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
return JSON.parse(text);
|
|
2475
|
-
}
|
|
2476
|
-
/**
|
|
2477
|
-
* Get all DNS records for a domain
|
|
2478
|
-
*
|
|
2479
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2480
|
-
*/
|
|
2481
|
-
async getRecords(domain) {
|
|
2482
|
-
const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
|
|
2483
|
-
return response.data || [];
|
|
2484
|
-
}
|
|
2485
|
-
/**
|
|
2486
|
-
* Create or update DNS records
|
|
2487
|
-
*
|
|
2488
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2489
|
-
* @param records - Records to create/update
|
|
2490
|
-
* @param overwrite - If true, replaces all existing records. If false, merges with existing.
|
|
2491
|
-
*/
|
|
2492
|
-
async upsertRecords(domain, records, overwrite = false) {
|
|
2493
|
-
await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
|
|
2494
|
-
overwrite,
|
|
2495
|
-
zone: records
|
|
2496
|
-
});
|
|
2497
|
-
}
|
|
2498
|
-
/**
|
|
2499
|
-
* Validate DNS records before applying
|
|
2500
|
-
*
|
|
2501
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2502
|
-
* @param records - Records to validate
|
|
2503
|
-
* @returns true if valid, throws if invalid
|
|
2504
|
-
*/
|
|
2505
|
-
async validateRecords(domain, records) {
|
|
2506
|
-
await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
|
|
2507
|
-
overwrite: false,
|
|
2508
|
-
zone: records
|
|
2509
|
-
});
|
|
2510
|
-
return true;
|
|
2329
|
+
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
2330
|
+
grouped.get(rootDomain).set(appName, hostname);
|
|
2511
2331
|
}
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
*
|
|
2515
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2516
|
-
* @param filters - Filters to match records for deletion
|
|
2517
|
-
*/
|
|
2518
|
-
async deleteRecords(domain, filters) {
|
|
2519
|
-
await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
|
|
2520
|
-
}
|
|
2521
|
-
/**
|
|
2522
|
-
* Check if a specific record exists
|
|
2523
|
-
*
|
|
2524
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2525
|
-
* @param name - Subdomain name (e.g., 'api.joemoer')
|
|
2526
|
-
* @param type - Record type (e.g., 'A')
|
|
2527
|
-
*/
|
|
2528
|
-
async recordExists(domain, name$1, type$1 = "A") {
|
|
2529
|
-
const records = await this.getRecords(domain);
|
|
2530
|
-
return records.some((r) => r.name === name$1 && r.type === type$1);
|
|
2531
|
-
}
|
|
2532
|
-
/**
|
|
2533
|
-
* Create a single A record if it doesn't exist
|
|
2534
|
-
*
|
|
2535
|
-
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
2536
|
-
* @param subdomain - Subdomain name (e.g., 'api.joemoer')
|
|
2537
|
-
* @param ip - IP address to point to
|
|
2538
|
-
* @param ttl - TTL in seconds (default: 300)
|
|
2539
|
-
* @returns true if created, false if already exists
|
|
2540
|
-
*/
|
|
2541
|
-
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
2542
|
-
const exists = await this.recordExists(domain, subdomain, "A");
|
|
2543
|
-
if (exists) return false;
|
|
2544
|
-
await this.upsertRecords(domain, [{
|
|
2545
|
-
name: subdomain,
|
|
2546
|
-
type: "A",
|
|
2547
|
-
ttl,
|
|
2548
|
-
records: [{ content: ip }]
|
|
2549
|
-
}]);
|
|
2550
|
-
return true;
|
|
2551
|
-
}
|
|
2552
|
-
};
|
|
2553
|
-
|
|
2554
|
-
//#endregion
|
|
2555
|
-
//#region src/deploy/dns/index.ts
|
|
2556
|
-
const logger$6 = console;
|
|
2332
|
+
return grouped;
|
|
2333
|
+
}
|
|
2557
2334
|
/**
|
|
2558
2335
|
* Resolve IP address from a hostname
|
|
2559
2336
|
*/
|
|
@@ -2625,127 +2402,91 @@ function printDnsRecordsSimple(records, rootDomain) {
|
|
|
2625
2402
|
logger$6.log("");
|
|
2626
2403
|
}
|
|
2627
2404
|
/**
|
|
2628
|
-
*
|
|
2629
|
-
*/
|
|
2630
|
-
async function promptForToken(message) {
|
|
2631
|
-
const { stdin, stdout } = await import("node:process");
|
|
2632
|
-
if (!stdin.isTTY) throw new Error("Interactive input required for Hostinger token.");
|
|
2633
|
-
stdout.write(message);
|
|
2634
|
-
return new Promise((resolve$3) => {
|
|
2635
|
-
let value = "";
|
|
2636
|
-
const onData = (char) => {
|
|
2637
|
-
const c = char.toString();
|
|
2638
|
-
if (c === "\n" || c === "\r") {
|
|
2639
|
-
stdin.setRawMode(false);
|
|
2640
|
-
stdin.pause();
|
|
2641
|
-
stdin.removeListener("data", onData);
|
|
2642
|
-
stdout.write("\n");
|
|
2643
|
-
resolve$3(value);
|
|
2644
|
-
} else if (c === "") {
|
|
2645
|
-
stdin.setRawMode(false);
|
|
2646
|
-
stdin.pause();
|
|
2647
|
-
stdout.write("\n");
|
|
2648
|
-
process.exit(1);
|
|
2649
|
-
} else if (c === "" || c === "\b") {
|
|
2650
|
-
if (value.length > 0) value = value.slice(0, -1);
|
|
2651
|
-
} else value += c;
|
|
2652
|
-
};
|
|
2653
|
-
stdin.setRawMode(true);
|
|
2654
|
-
stdin.resume();
|
|
2655
|
-
stdin.on("data", onData);
|
|
2656
|
-
});
|
|
2657
|
-
}
|
|
2658
|
-
/**
|
|
2659
|
-
* Create DNS records using the configured provider
|
|
2660
|
-
*/
|
|
2661
|
-
async function createDnsRecords(records, dnsConfig) {
|
|
2662
|
-
const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
|
|
2663
|
-
if (provider === "manual") return records.map((r) => ({
|
|
2664
|
-
...r,
|
|
2665
|
-
created: false,
|
|
2666
|
-
existed: false
|
|
2667
|
-
}));
|
|
2668
|
-
if (provider === "hostinger") return createHostingerRecords(records, rootDomain, ttl);
|
|
2669
|
-
if (provider === "cloudflare") {
|
|
2670
|
-
logger$6.log(" ⚠ Cloudflare DNS integration not yet implemented");
|
|
2671
|
-
return records.map((r) => ({
|
|
2672
|
-
...r,
|
|
2673
|
-
error: "Cloudflare not implemented"
|
|
2674
|
-
}));
|
|
2675
|
-
}
|
|
2676
|
-
return records;
|
|
2677
|
-
}
|
|
2678
|
-
/**
|
|
2679
|
-
* Create DNS records at Hostinger
|
|
2405
|
+
* Create DNS records for a single domain using its configured provider
|
|
2680
2406
|
*/
|
|
2681
|
-
async function
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
logger$6.log("\n 📋 Hostinger API token not found.");
|
|
2685
|
-
logger$6.log(" Get your token from: https://hpanel.hostinger.com/profile/api\n");
|
|
2686
|
-
try {
|
|
2687
|
-
token = await promptForToken(" Hostinger API Token: ");
|
|
2688
|
-
await storeHostingerToken(token);
|
|
2689
|
-
logger$6.log(" ✓ Token saved");
|
|
2690
|
-
} catch {
|
|
2691
|
-
logger$6.log(" ⚠ Could not get token, skipping DNS creation");
|
|
2692
|
-
return records.map((r) => ({
|
|
2693
|
-
...r,
|
|
2694
|
-
error: "No API token"
|
|
2695
|
-
}));
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
const api = new HostingerApi(token);
|
|
2699
|
-
const results = [];
|
|
2700
|
-
let existingRecords = [];
|
|
2407
|
+
async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
2408
|
+
const ttl = "ttl" in providerConfig && providerConfig.ttl ? providerConfig.ttl : 300;
|
|
2409
|
+
let provider;
|
|
2701
2410
|
try {
|
|
2702
|
-
|
|
2411
|
+
provider = await createDnsProvider({ config: providerConfig });
|
|
2703
2412
|
} catch (error) {
|
|
2704
2413
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2705
|
-
logger$6.log(` ⚠ Failed to
|
|
2414
|
+
logger$6.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
2706
2415
|
return records.map((r) => ({
|
|
2707
2416
|
...r,
|
|
2708
2417
|
error: message
|
|
2709
2418
|
}));
|
|
2710
2419
|
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2420
|
+
if (!provider) return records.map((r) => ({
|
|
2421
|
+
...r,
|
|
2422
|
+
created: false,
|
|
2423
|
+
existed: false
|
|
2424
|
+
}));
|
|
2425
|
+
const results = [];
|
|
2426
|
+
const upsertRecords = records.map((r) => ({
|
|
2427
|
+
name: r.subdomain,
|
|
2428
|
+
type: r.type,
|
|
2429
|
+
ttl,
|
|
2430
|
+
value: r.value
|
|
2431
|
+
}));
|
|
2432
|
+
try {
|
|
2433
|
+
const upsertResults = await provider.upsertRecords(rootDomain, upsertRecords);
|
|
2434
|
+
for (const [i, record] of records.entries()) {
|
|
2435
|
+
const result = upsertResults[i];
|
|
2436
|
+
if (!result) {
|
|
2437
|
+
results.push({
|
|
2438
|
+
hostname: record.hostname,
|
|
2439
|
+
subdomain: record.subdomain,
|
|
2440
|
+
type: record.type,
|
|
2441
|
+
value: record.value,
|
|
2442
|
+
appName: record.appName,
|
|
2443
|
+
error: "No result returned from provider"
|
|
2444
|
+
});
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
if (result.unchanged) results.push({
|
|
2448
|
+
hostname: record.hostname,
|
|
2449
|
+
subdomain: record.subdomain,
|
|
2450
|
+
type: record.type,
|
|
2451
|
+
value: record.value,
|
|
2452
|
+
appName: record.appName,
|
|
2716
2453
|
existed: true,
|
|
2717
2454
|
created: false
|
|
2718
2455
|
});
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
}]);
|
|
2728
|
-
results.push({
|
|
2729
|
-
...record,
|
|
2730
|
-
created: true,
|
|
2731
|
-
existed: false
|
|
2732
|
-
});
|
|
2733
|
-
} catch (error) {
|
|
2734
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2735
|
-
results.push({
|
|
2736
|
-
...record,
|
|
2737
|
-
error: message
|
|
2456
|
+
else results.push({
|
|
2457
|
+
hostname: record.hostname,
|
|
2458
|
+
subdomain: record.subdomain,
|
|
2459
|
+
type: record.type,
|
|
2460
|
+
value: record.value,
|
|
2461
|
+
appName: record.appName,
|
|
2462
|
+
created: result.created,
|
|
2463
|
+
existed: !result.created
|
|
2738
2464
|
});
|
|
2739
2465
|
}
|
|
2466
|
+
} catch (error) {
|
|
2467
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2468
|
+
logger$6.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
2469
|
+
return records.map((r) => ({
|
|
2470
|
+
hostname: r.hostname,
|
|
2471
|
+
subdomain: r.subdomain,
|
|
2472
|
+
type: r.type,
|
|
2473
|
+
value: r.value,
|
|
2474
|
+
appName: r.appName,
|
|
2475
|
+
error: message
|
|
2476
|
+
}));
|
|
2740
2477
|
}
|
|
2741
2478
|
return results;
|
|
2742
2479
|
}
|
|
2743
2480
|
/**
|
|
2744
2481
|
* Main DNS orchestration function for deployments
|
|
2482
|
+
*
|
|
2483
|
+
* Supports both legacy single-domain format and new multi-domain format:
|
|
2484
|
+
* - Legacy: { provider: 'hostinger', domain: 'example.com' }
|
|
2485
|
+
* - Multi: { 'example.com': { provider: 'hostinger' }, 'example.dev': { provider: 'route53' } }
|
|
2745
2486
|
*/
|
|
2746
2487
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
|
|
2747
2488
|
if (!dnsConfig) return null;
|
|
2748
|
-
const
|
|
2489
|
+
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
2749
2490
|
logger$6.log("\n🌐 Setting up DNS records...");
|
|
2750
2491
|
let serverIp;
|
|
2751
2492
|
try {
|
|
@@ -2757,31 +2498,43 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
|
|
|
2757
2498
|
logger$6.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
2758
2499
|
return null;
|
|
2759
2500
|
}
|
|
2760
|
-
const
|
|
2761
|
-
if (
|
|
2762
|
-
logger$6.log(" No DNS records needed");
|
|
2501
|
+
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
2502
|
+
if (groupedHostnames.size === 0) {
|
|
2503
|
+
logger$6.log(" No DNS records needed (no hostnames match configured domains)");
|
|
2763
2504
|
return {
|
|
2764
2505
|
records: [],
|
|
2765
2506
|
success: true,
|
|
2766
2507
|
serverIp
|
|
2767
2508
|
};
|
|
2768
2509
|
}
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2510
|
+
const allRecords = [];
|
|
2511
|
+
let hasFailures = false;
|
|
2512
|
+
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
2513
|
+
const providerConfig = normalizedConfig[rootDomain];
|
|
2514
|
+
if (!providerConfig) {
|
|
2515
|
+
logger$6.log(` ⚠ No provider config for ${rootDomain}`);
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
2519
|
+
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
2520
|
+
if (requiredRecords.length === 0) continue;
|
|
2521
|
+
logger$6.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
2522
|
+
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
2523
|
+
allRecords.push(...domainRecords);
|
|
2524
|
+
const created = domainRecords.filter((r) => r.created).length;
|
|
2525
|
+
const existed = domainRecords.filter((r) => r.existed).length;
|
|
2526
|
+
const failed = domainRecords.filter((r) => r.error).length;
|
|
2527
|
+
if (created > 0) logger$6.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
2528
|
+
if (existed > 0) logger$6.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
2529
|
+
if (failed > 0) {
|
|
2530
|
+
logger$6.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
2531
|
+
hasFailures = true;
|
|
2532
|
+
}
|
|
2533
|
+
printDnsRecordsTable(domainRecords, rootDomain);
|
|
2534
|
+
if (providerConfig.provider === "manual" || failed > 0) printDnsRecordsSimple(domainRecords.filter((r) => !r.created && !r.existed), rootDomain);
|
|
2535
|
+
}
|
|
2783
2536
|
return {
|
|
2784
|
-
records:
|
|
2537
|
+
records: allRecords,
|
|
2785
2538
|
success: !hasFailures,
|
|
2786
2539
|
serverIp
|
|
2787
2540
|
};
|
|
@@ -3386,7 +3139,7 @@ WORKDIR /app
|
|
|
3386
3139
|
COPY . .
|
|
3387
3140
|
|
|
3388
3141
|
# Build production server using gkm
|
|
3389
|
-
RUN
|
|
3142
|
+
RUN ${pm.exec} gkm build --provider server --production
|
|
3390
3143
|
|
|
3391
3144
|
# Stage 3: Production
|
|
3392
3145
|
FROM ${baseImage} AS runner
|
|
@@ -3468,7 +3221,7 @@ WORKDIR /app
|
|
|
3468
3221
|
COPY --from=pruner /app/out/full/ ./
|
|
3469
3222
|
|
|
3470
3223
|
# Build production server using gkm
|
|
3471
|
-
RUN
|
|
3224
|
+
RUN ${pm.exec} gkm build --provider server --production
|
|
3472
3225
|
|
|
3473
3226
|
# Stage 4: Production
|
|
3474
3227
|
FROM ${baseImage} AS runner
|
|
@@ -3789,7 +3542,7 @@ RUN if [ -n "$GKM_ENCRYPTED_CREDENTIALS" ]; then \
|
|
|
3789
3542
|
fi
|
|
3790
3543
|
|
|
3791
3544
|
# Build production server using gkm
|
|
3792
|
-
RUN cd ${appPath} &&
|
|
3545
|
+
RUN cd ${appPath} && ${pm.exec} gkm build --provider server --production
|
|
3793
3546
|
|
|
3794
3547
|
# Stage 4: Production
|
|
3795
3548
|
FROM ${baseImage} AS runner
|
|
@@ -4328,7 +4081,7 @@ const logger$3 = console;
|
|
|
4328
4081
|
* Get the Dokploy API token from stored credentials or environment
|
|
4329
4082
|
*/
|
|
4330
4083
|
async function getApiToken$1() {
|
|
4331
|
-
const token = await getDokployToken();
|
|
4084
|
+
const token = await require_credentials.getDokployToken();
|
|
4332
4085
|
if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
|
|
4333
4086
|
return token;
|
|
4334
4087
|
}
|
|
@@ -4357,7 +4110,7 @@ async function deployDokploy(options) {
|
|
|
4357
4110
|
registryOptions.registryId = config.registryId;
|
|
4358
4111
|
logger$3.log(` Using Dokploy registry: ${config.registryId}`);
|
|
4359
4112
|
} else {
|
|
4360
|
-
const storedRegistryId = await getDokployRegistryId();
|
|
4113
|
+
const storedRegistryId = await require_credentials.getDokployRegistryId();
|
|
4361
4114
|
if (storedRegistryId) {
|
|
4362
4115
|
registryOptions.registryId = storedRegistryId;
|
|
4363
4116
|
logger$3.log(` Using stored Dokploy registry: ${storedRegistryId}`);
|
|
@@ -4590,7 +4343,7 @@ const logger$2 = console;
|
|
|
4590
4343
|
* Get the Dokploy API token from stored credentials or environment
|
|
4591
4344
|
*/
|
|
4592
4345
|
async function getApiToken() {
|
|
4593
|
-
const token = await getDokployToken();
|
|
4346
|
+
const token = await require_credentials.getDokployToken();
|
|
4594
4347
|
if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
|
|
4595
4348
|
return token;
|
|
4596
4349
|
}
|
|
@@ -4599,7 +4352,7 @@ async function getApiToken() {
|
|
|
4599
4352
|
*/
|
|
4600
4353
|
async function getEndpoint(providedEndpoint) {
|
|
4601
4354
|
if (providedEndpoint) return providedEndpoint;
|
|
4602
|
-
const stored = await getDokployCredentials();
|
|
4355
|
+
const stored = await require_credentials.getDokployCredentials();
|
|
4603
4356
|
if (stored) return stored.endpoint;
|
|
4604
4357
|
throw new Error("Dokploy endpoint not specified.\nEither run \"gkm login --service dokploy\" first, or provide --endpoint.");
|
|
4605
4358
|
}
|
|
@@ -4744,7 +4497,7 @@ async function deployListCommand(options) {
|
|
|
4744
4497
|
logger$2.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
4745
4498
|
return;
|
|
4746
4499
|
}
|
|
4747
|
-
const storedRegistryId = await getDokployRegistryId();
|
|
4500
|
+
const storedRegistryId = await require_credentials.getDokployRegistryId();
|
|
4748
4501
|
for (const registry of registries) {
|
|
4749
4502
|
const isDefault = registry.registryId === storedRegistryId;
|
|
4750
4503
|
const marker = isDefault ? " (default)" : "";
|
|
@@ -4756,6 +4509,48 @@ async function deployListCommand(options) {
|
|
|
4756
4509
|
}
|
|
4757
4510
|
}
|
|
4758
4511
|
|
|
4512
|
+
//#endregion
|
|
4513
|
+
//#region src/deploy/StateProvider.ts
|
|
4514
|
+
/**
|
|
4515
|
+
* Check if value is a StateProvider implementation.
|
|
4516
|
+
*/
|
|
4517
|
+
function isStateProvider(value) {
|
|
4518
|
+
return typeof value === "object" && value !== null && typeof value.read === "function" && typeof value.write === "function";
|
|
4519
|
+
}
|
|
4520
|
+
/**
|
|
4521
|
+
* Create a state provider based on configuration.
|
|
4522
|
+
*
|
|
4523
|
+
* - 'local': LocalStateProvider (default)
|
|
4524
|
+
* - 'ssm': CachedStateProvider with SSM as source of truth
|
|
4525
|
+
* - Custom: Use provided StateProvider implementation
|
|
4526
|
+
*/
|
|
4527
|
+
async function createStateProvider(options) {
|
|
4528
|
+
const { config, workspaceRoot, workspaceName } = options;
|
|
4529
|
+
if (!config) {
|
|
4530
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CdspeSVL.cjs"));
|
|
4531
|
+
return new LocalStateProvider(workspaceRoot);
|
|
4532
|
+
}
|
|
4533
|
+
if (isStateProvider(config.provider)) return config.provider;
|
|
4534
|
+
const provider = config.provider;
|
|
4535
|
+
if (provider === "local") {
|
|
4536
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CdspeSVL.cjs"));
|
|
4537
|
+
return new LocalStateProvider(workspaceRoot);
|
|
4538
|
+
}
|
|
4539
|
+
if (provider === "ssm") {
|
|
4540
|
+
if (!workspaceName) throw new Error("Workspace name is required for SSM state provider. Set \"name\" in gkm.config.ts.");
|
|
4541
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CdspeSVL.cjs"));
|
|
4542
|
+
const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-BxAPU99a.cjs"));
|
|
4543
|
+
const { CachedStateProvider: CachedStateProvider$1 } = await Promise.resolve().then(() => require("./CachedStateProvider-D_uISMmJ.cjs"));
|
|
4544
|
+
const local = new LocalStateProvider(workspaceRoot);
|
|
4545
|
+
const ssm = new SSMStateProvider({
|
|
4546
|
+
workspaceName,
|
|
4547
|
+
region: config.region
|
|
4548
|
+
});
|
|
4549
|
+
return new CachedStateProvider$1(ssm, local);
|
|
4550
|
+
}
|
|
4551
|
+
throw new Error(`Unknown state provider: ${JSON.stringify(config)}`);
|
|
4552
|
+
}
|
|
4553
|
+
|
|
4759
4554
|
//#endregion
|
|
4760
4555
|
//#region src/deploy/secrets.ts
|
|
4761
4556
|
/**
|
|
@@ -4858,6 +4653,14 @@ function generateSecretsReport(encryptedApps, sniffedApps) {
|
|
|
4858
4653
|
const __filename$1 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
4859
4654
|
const __dirname$1 = (0, node_path.dirname)(__filename$1);
|
|
4860
4655
|
/**
|
|
4656
|
+
* Resolve the tsx package path from the CLI package's dependencies.
|
|
4657
|
+
* This ensures tsx is available regardless of whether the target project has it installed.
|
|
4658
|
+
*/
|
|
4659
|
+
function resolveTsxPath() {
|
|
4660
|
+
const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
4661
|
+
return require$2.resolve("tsx");
|
|
4662
|
+
}
|
|
4663
|
+
/**
|
|
4861
4664
|
* Resolve the path to a sniffer helper file.
|
|
4862
4665
|
* Handles both dev (.ts with tsx) and production (.mjs from dist).
|
|
4863
4666
|
*
|
|
@@ -4882,8 +4685,9 @@ function resolveSnifferFile(baseName) {
|
|
|
4882
4685
|
* 1. Frontend apps: Returns empty (no server secrets)
|
|
4883
4686
|
* 2. Apps with `requiredEnv`: Uses explicit list from config
|
|
4884
4687
|
* 3. Entry apps: Imports entry file in subprocess to capture config.parse() calls
|
|
4885
|
-
* 4.
|
|
4886
|
-
* 5. Apps with
|
|
4688
|
+
* 4. Route-based apps: Loads route files and calls getEnvironment() on each construct
|
|
4689
|
+
* 5. Apps with `envParser` (no routes): Runs SnifferEnvironmentParser to detect usage
|
|
4690
|
+
* 6. Apps with neither: Returns empty
|
|
4887
4691
|
*
|
|
4888
4692
|
* This function handles "fire and forget" async operations gracefully,
|
|
4889
4693
|
* capturing errors and unhandled rejections without failing the build.
|
|
@@ -4912,6 +4716,14 @@ async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
|
|
|
4912
4716
|
requiredEnvVars: result.envVars
|
|
4913
4717
|
};
|
|
4914
4718
|
}
|
|
4719
|
+
if (app.routes) {
|
|
4720
|
+
const result = await sniffRouteFiles(app.routes, app.path, workspacePath);
|
|
4721
|
+
if (logWarnings && result.error) console.warn(`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`);
|
|
4722
|
+
return {
|
|
4723
|
+
appName,
|
|
4724
|
+
requiredEnvVars: result.envVars
|
|
4725
|
+
};
|
|
4726
|
+
}
|
|
4915
4727
|
if (app.envParser) {
|
|
4916
4728
|
const result = await sniffEnvParser(app.envParser, app.path, workspacePath);
|
|
4917
4729
|
if (logWarnings) {
|
|
@@ -5003,6 +4815,80 @@ async function sniffEntryFile(entryPath, appPath, workspacePath) {
|
|
|
5003
4815
|
});
|
|
5004
4816
|
}
|
|
5005
4817
|
/**
|
|
4818
|
+
* Sniff route files by loading constructs and calling getEnvironment().
|
|
4819
|
+
*
|
|
4820
|
+
* Route-based apps have endpoints, functions, crons, and subscribers that
|
|
4821
|
+
* use services. Each service's register() method accesses environment variables.
|
|
4822
|
+
*
|
|
4823
|
+
* This runs in a subprocess with tsx loader to properly handle TypeScript
|
|
4824
|
+
* compilation and path alias resolution (e.g., `src/...` imports).
|
|
4825
|
+
*
|
|
4826
|
+
* @param routes - Glob pattern(s) for route files
|
|
4827
|
+
* @param appPath - The app's path relative to workspace (e.g., 'apps/api')
|
|
4828
|
+
* @param workspacePath - Absolute path to workspace root
|
|
4829
|
+
* @returns EntrySniffResult with env vars and optional error
|
|
4830
|
+
*/
|
|
4831
|
+
async function sniffRouteFiles(routes, appPath, workspacePath) {
|
|
4832
|
+
const fullAppPath = (0, node_path.resolve)(workspacePath, appPath);
|
|
4833
|
+
const workerPath = resolveSnifferFile("sniffer-routes-worker");
|
|
4834
|
+
const tsxPath = resolveTsxPath();
|
|
4835
|
+
const routesArray = Array.isArray(routes) ? routes : [routes];
|
|
4836
|
+
const pattern = routesArray[0];
|
|
4837
|
+
if (!pattern) return {
|
|
4838
|
+
envVars: [],
|
|
4839
|
+
error: new Error("No route patterns provided")
|
|
4840
|
+
};
|
|
4841
|
+
return new Promise((resolvePromise) => {
|
|
4842
|
+
const child = (0, node_child_process.spawn)("node", [
|
|
4843
|
+
"--import",
|
|
4844
|
+
tsxPath,
|
|
4845
|
+
workerPath,
|
|
4846
|
+
fullAppPath,
|
|
4847
|
+
pattern
|
|
4848
|
+
], {
|
|
4849
|
+
cwd: fullAppPath,
|
|
4850
|
+
stdio: [
|
|
4851
|
+
"ignore",
|
|
4852
|
+
"pipe",
|
|
4853
|
+
"pipe"
|
|
4854
|
+
],
|
|
4855
|
+
env: { ...process.env }
|
|
4856
|
+
});
|
|
4857
|
+
let stdout = "";
|
|
4858
|
+
let stderr = "";
|
|
4859
|
+
child.stdout.on("data", (data) => {
|
|
4860
|
+
stdout += data.toString();
|
|
4861
|
+
});
|
|
4862
|
+
child.stderr.on("data", (data) => {
|
|
4863
|
+
stderr += data.toString();
|
|
4864
|
+
});
|
|
4865
|
+
child.on("close", (code) => {
|
|
4866
|
+
if (stderr) stderr.split("\n").filter((line) => line.trim()).forEach((line) => console.warn(line));
|
|
4867
|
+
try {
|
|
4868
|
+
const jsonMatch = stdout.match(/\{[^{}]*"envVars"[^{}]*\}[^{]*$/);
|
|
4869
|
+
if (jsonMatch) {
|
|
4870
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
4871
|
+
resolvePromise({
|
|
4872
|
+
envVars: result.envVars || [],
|
|
4873
|
+
error: result.error ? new Error(result.error) : void 0
|
|
4874
|
+
});
|
|
4875
|
+
return;
|
|
4876
|
+
}
|
|
4877
|
+
} catch {}
|
|
4878
|
+
resolvePromise({
|
|
4879
|
+
envVars: [],
|
|
4880
|
+
error: new Error(`Failed to sniff route files (exit code ${code}): ${stderr || stdout || "No output"}`)
|
|
4881
|
+
});
|
|
4882
|
+
});
|
|
4883
|
+
child.on("error", (err) => {
|
|
4884
|
+
resolvePromise({
|
|
4885
|
+
envVars: [],
|
|
4886
|
+
error: err
|
|
4887
|
+
});
|
|
4888
|
+
});
|
|
4889
|
+
});
|
|
4890
|
+
}
|
|
4891
|
+
/**
|
|
5006
4892
|
* Run the SnifferEnvironmentParser on an envParser module to detect
|
|
5007
4893
|
* which environment variables it accesses.
|
|
5008
4894
|
*
|
|
@@ -5331,7 +5217,7 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
5331
5217
|
*/
|
|
5332
5218
|
async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
5333
5219
|
logger$1.log("\n🔧 Checking Dokploy setup...");
|
|
5334
|
-
let creds = await getDokployCredentials();
|
|
5220
|
+
let creds = await require_credentials.getDokployCredentials();
|
|
5335
5221
|
if (!creds) {
|
|
5336
5222
|
logger$1.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
5337
5223
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
@@ -5346,7 +5232,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5346
5232
|
logger$1.log("\nValidating credentials...");
|
|
5347
5233
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
5348
5234
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
5349
|
-
await storeDokployCredentials(token, normalizedEndpoint);
|
|
5235
|
+
await require_credentials.storeDokployCredentials(token, normalizedEndpoint);
|
|
5350
5236
|
creds = {
|
|
5351
5237
|
token,
|
|
5352
5238
|
endpoint: normalizedEndpoint
|
|
@@ -5363,7 +5249,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5363
5249
|
try {
|
|
5364
5250
|
const projectDetails = await api.getProject(existingConfig.projectId);
|
|
5365
5251
|
logger$1.log("✓ Project verified");
|
|
5366
|
-
const storedRegistryId = existingConfig.registryId ?? await getDokployRegistryId();
|
|
5252
|
+
const storedRegistryId = existingConfig.registryId ?? await require_credentials.getDokployRegistryId();
|
|
5367
5253
|
const environments = projectDetails.environments ?? [];
|
|
5368
5254
|
let environment = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
5369
5255
|
if (!environment) {
|
|
@@ -5432,14 +5318,14 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5432
5318
|
logger$1.log(` ✓ Created application: ${applicationId}`);
|
|
5433
5319
|
}
|
|
5434
5320
|
logger$1.log("\n🐳 Checking registry...");
|
|
5435
|
-
let registryId = await getDokployRegistryId();
|
|
5321
|
+
let registryId = await require_credentials.getDokployRegistryId();
|
|
5436
5322
|
if (registryId) try {
|
|
5437
5323
|
const registry = await api.getRegistry(registryId);
|
|
5438
5324
|
logger$1.log(` Using registry: ${registry.registryName}`);
|
|
5439
5325
|
} catch {
|
|
5440
5326
|
logger$1.log(" ⚠ Stored registry not found, clearing...");
|
|
5441
5327
|
registryId = void 0;
|
|
5442
|
-
await storeDokployRegistryId("");
|
|
5328
|
+
await require_credentials.storeDokployRegistryId("");
|
|
5443
5329
|
}
|
|
5444
5330
|
if (!registryId) {
|
|
5445
5331
|
const registries = await api.listRegistries();
|
|
@@ -5450,7 +5336,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5450
5336
|
const password = await prompt("Registry password/token: ", true);
|
|
5451
5337
|
const registry = await api.createRegistry("Default Registry", dockerConfig.registry, username, password);
|
|
5452
5338
|
registryId = registry.registryId;
|
|
5453
|
-
await storeDokployRegistryId(registryId);
|
|
5339
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5454
5340
|
logger$1.log(` ✓ Registry created: ${registryId}`);
|
|
5455
5341
|
} else logger$1.log(" ⚠ No registry configured. Set docker.registry in gkm.config.ts");
|
|
5456
5342
|
else {
|
|
@@ -5464,7 +5350,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5464
5350
|
const index = parseInt(selection, 10) - 1;
|
|
5465
5351
|
if (index >= 0 && index < registries.length) {
|
|
5466
5352
|
registryId = registries[index].registryId;
|
|
5467
|
-
await storeDokployRegistryId(registryId);
|
|
5353
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5468
5354
|
logger$1.log(` ✓ Selected: ${registries[index].registryName}`);
|
|
5469
5355
|
} else if (dockerConfig.registry && index === registries.length) {
|
|
5470
5356
|
logger$1.log(`\n Creating new registry...`);
|
|
@@ -5473,7 +5359,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
5473
5359
|
const password = await prompt(" Registry password/token: ", true);
|
|
5474
5360
|
const registry = await api.createRegistry(dockerConfig.registry.replace(/^https?:\/\//, ""), dockerConfig.registry, username, password);
|
|
5475
5361
|
registryId = registry.registryId;
|
|
5476
|
-
await storeDokployRegistryId(registryId);
|
|
5362
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5477
5363
|
logger$1.log(` ✓ Registry created: ${registryId}`);
|
|
5478
5364
|
} else logger$1.log(" ⚠ Invalid selection, skipping registry setup");
|
|
5479
5365
|
}
|
|
@@ -5555,7 +5441,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5555
5441
|
if (report.appsWithSecrets.length > 0) logger$1.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
5556
5442
|
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$1.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
5557
5443
|
}
|
|
5558
|
-
let creds = await getDokployCredentials();
|
|
5444
|
+
let creds = await require_credentials.getDokployCredentials();
|
|
5559
5445
|
if (!creds) {
|
|
5560
5446
|
logger$1.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
5561
5447
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
@@ -5570,7 +5456,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5570
5456
|
logger$1.log("\nValidating credentials...");
|
|
5571
5457
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
5572
5458
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
5573
|
-
await storeDokployCredentials(token, normalizedEndpoint);
|
|
5459
|
+
await require_credentials.storeDokployCredentials(token, normalizedEndpoint);
|
|
5574
5460
|
creds = {
|
|
5575
5461
|
token,
|
|
5576
5462
|
endpoint: normalizedEndpoint
|
|
@@ -5612,7 +5498,12 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5612
5498
|
logger$1.log(` ✓ Created project: ${project.projectId}`);
|
|
5613
5499
|
}
|
|
5614
5500
|
logger$1.log("\n📋 Loading deploy state...");
|
|
5615
|
-
|
|
5501
|
+
const stateProvider = await createStateProvider({
|
|
5502
|
+
config: workspace.state,
|
|
5503
|
+
workspaceRoot: workspace.root,
|
|
5504
|
+
workspaceName: workspace.name
|
|
5505
|
+
});
|
|
5506
|
+
let state = await stateProvider.read(stage);
|
|
5616
5507
|
if (state) {
|
|
5617
5508
|
logger$1.log(` Found existing state for stage "${stage}"`);
|
|
5618
5509
|
if (state.environmentId !== environmentId) {
|
|
@@ -5624,7 +5515,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5624
5515
|
state = createEmptyState(stage, environmentId);
|
|
5625
5516
|
}
|
|
5626
5517
|
logger$1.log("\n🐳 Checking registry...");
|
|
5627
|
-
let registryId = await getDokployRegistryId();
|
|
5518
|
+
let registryId = await require_credentials.getDokployRegistryId();
|
|
5628
5519
|
const registry = workspace.deploy.dokploy?.registry;
|
|
5629
5520
|
if (registryId) try {
|
|
5630
5521
|
const reg = await api.getRegistry(registryId);
|
|
@@ -5632,13 +5523,13 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5632
5523
|
} catch {
|
|
5633
5524
|
logger$1.log(" ⚠ Stored registry not found, clearing...");
|
|
5634
5525
|
registryId = void 0;
|
|
5635
|
-
await storeDokployRegistryId("");
|
|
5526
|
+
await require_credentials.storeDokployRegistryId("");
|
|
5636
5527
|
}
|
|
5637
5528
|
if (!registryId) {
|
|
5638
5529
|
const registries = await api.listRegistries();
|
|
5639
5530
|
if (registries.length > 0) {
|
|
5640
5531
|
registryId = registries[0].registryId;
|
|
5641
|
-
await storeDokployRegistryId(registryId);
|
|
5532
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5642
5533
|
logger$1.log(` Using registry: ${registries[0].registryName}`);
|
|
5643
5534
|
} else if (registry) {
|
|
5644
5535
|
logger$1.log(" No registries found in Dokploy. Let's create one.");
|
|
@@ -5647,7 +5538,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5647
5538
|
const password = await prompt("Registry password/token: ", true);
|
|
5648
5539
|
const reg = await api.createRegistry("Default Registry", registry, username, password);
|
|
5649
5540
|
registryId = reg.registryId;
|
|
5650
|
-
await storeDokployRegistryId(registryId);
|
|
5541
|
+
await require_credentials.storeDokployRegistryId(registryId);
|
|
5651
5542
|
logger$1.log(` ✓ Registry created: ${registryId}`);
|
|
5652
5543
|
} else logger$1.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
5653
5544
|
}
|
|
@@ -5947,14 +5838,14 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5947
5838
|
}
|
|
5948
5839
|
}
|
|
5949
5840
|
logger$1.log("\n📋 Saving deploy state...");
|
|
5950
|
-
await
|
|
5951
|
-
logger$1.log(
|
|
5841
|
+
await stateProvider.write(stage, state);
|
|
5842
|
+
logger$1.log(" ✓ State saved");
|
|
5952
5843
|
const dnsConfig = workspace.deploy.dns;
|
|
5953
5844
|
if (dnsConfig && appHostnames.size > 0) {
|
|
5954
5845
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
5955
5846
|
if (dnsResult?.serverIp && appHostnames.size > 0) {
|
|
5956
5847
|
await verifyDnsRecords(appHostnames, dnsResult.serverIp, state);
|
|
5957
|
-
await
|
|
5848
|
+
await stateProvider.write(stage, state);
|
|
5958
5849
|
}
|
|
5959
5850
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
5960
5851
|
logger$1.log("\n🔒 Validating domains for SSL certificates...");
|
|
@@ -6023,7 +5914,7 @@ async function deployCommand(options) {
|
|
|
6023
5914
|
dokployConfig = setupResult.config;
|
|
6024
5915
|
finalRegistry = dokployConfig.registry ?? dockerConfig.registry;
|
|
6025
5916
|
if (setupResult.serviceUrls) {
|
|
6026
|
-
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await Promise.resolve().then(() => require("./storage-
|
|
5917
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
|
|
6027
5918
|
let secrets = await readStageSecrets$1(stage);
|
|
6028
5919
|
if (!secrets) {
|
|
6029
5920
|
logger$1.log(` Creating secrets file for stage "${stage}"...`);
|
|
@@ -6113,19 +6004,187 @@ async function deployCommand(options) {
|
|
|
6113
6004
|
}
|
|
6114
6005
|
|
|
6115
6006
|
//#endregion
|
|
6116
|
-
//#region src/
|
|
6007
|
+
//#region src/deploy/state-commands.ts
|
|
6117
6008
|
/**
|
|
6118
|
-
*
|
|
6119
|
-
*
|
|
6009
|
+
* Pull state from remote to local.
|
|
6010
|
+
* `gkm state:pull --stage=<stage>`
|
|
6120
6011
|
*/
|
|
6121
|
-
function
|
|
6122
|
-
|
|
6012
|
+
async function statePullCommand(options) {
|
|
6013
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
6014
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
6015
|
+
console.error("No remote state provider configured.");
|
|
6016
|
+
console.error("Add a remote provider in gkm.config.ts:");
|
|
6017
|
+
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
6018
|
+
process.exit(1);
|
|
6019
|
+
}
|
|
6020
|
+
const provider = await createStateProvider({
|
|
6021
|
+
config: workspace.state,
|
|
6022
|
+
workspaceRoot: workspace.root,
|
|
6023
|
+
workspaceName: workspace.name
|
|
6024
|
+
});
|
|
6025
|
+
if (!(provider instanceof require_CachedStateProvider.CachedStateProvider)) {
|
|
6026
|
+
console.error("State provider does not support pull operation.");
|
|
6027
|
+
process.exit(1);
|
|
6028
|
+
}
|
|
6029
|
+
console.log(`Pulling state for stage: ${options.stage}...`);
|
|
6030
|
+
const state = await provider.pull(options.stage);
|
|
6031
|
+
if (state) {
|
|
6032
|
+
console.log("State pulled successfully.");
|
|
6033
|
+
printStateSummary(state);
|
|
6034
|
+
} else console.log("No remote state found for this stage.");
|
|
6123
6035
|
}
|
|
6124
|
-
/**
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6036
|
+
/**
|
|
6037
|
+
* Push local state to remote.
|
|
6038
|
+
* `gkm state:push --stage=<stage>`
|
|
6039
|
+
*/
|
|
6040
|
+
async function statePushCommand(options) {
|
|
6041
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
6042
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
6043
|
+
console.error("No remote state provider configured.");
|
|
6044
|
+
console.error("Add a remote provider in gkm.config.ts:");
|
|
6045
|
+
console.error(" state: { provider: \"ssm\", region: \"us-east-1\" }");
|
|
6046
|
+
process.exit(1);
|
|
6047
|
+
}
|
|
6048
|
+
const provider = await createStateProvider({
|
|
6049
|
+
config: workspace.state,
|
|
6050
|
+
workspaceRoot: workspace.root,
|
|
6051
|
+
workspaceName: workspace.name
|
|
6052
|
+
});
|
|
6053
|
+
if (!(provider instanceof require_CachedStateProvider.CachedStateProvider)) {
|
|
6054
|
+
console.error("State provider does not support push operation.");
|
|
6055
|
+
process.exit(1);
|
|
6056
|
+
}
|
|
6057
|
+
console.log(`Pushing state for stage: ${options.stage}...`);
|
|
6058
|
+
const state = await provider.push(options.stage);
|
|
6059
|
+
if (state) {
|
|
6060
|
+
console.log("State pushed successfully.");
|
|
6061
|
+
printStateSummary(state);
|
|
6062
|
+
} else console.log("No local state found for this stage.");
|
|
6063
|
+
}
|
|
6064
|
+
/**
|
|
6065
|
+
* Show current state.
|
|
6066
|
+
* `gkm state:show --stage=<stage>`
|
|
6067
|
+
*/
|
|
6068
|
+
async function stateShowCommand(options) {
|
|
6069
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
6070
|
+
const provider = await createStateProvider({
|
|
6071
|
+
config: workspace.state,
|
|
6072
|
+
workspaceRoot: workspace.root,
|
|
6073
|
+
workspaceName: workspace.name
|
|
6074
|
+
});
|
|
6075
|
+
const state = await provider.read(options.stage);
|
|
6076
|
+
if (!state) {
|
|
6077
|
+
console.log(`No state found for stage: ${options.stage}`);
|
|
6078
|
+
return;
|
|
6079
|
+
}
|
|
6080
|
+
if (options.json) console.log(JSON.stringify(state, null, 2));
|
|
6081
|
+
else printStateDetails(state);
|
|
6082
|
+
}
|
|
6083
|
+
/**
|
|
6084
|
+
* Compare local and remote state.
|
|
6085
|
+
* `gkm state:diff --stage=<stage>`
|
|
6086
|
+
*/
|
|
6087
|
+
async function stateDiffCommand(options) {
|
|
6088
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
6089
|
+
if (!workspace.state || workspace.state.provider === "local") {
|
|
6090
|
+
console.error("No remote state provider configured.");
|
|
6091
|
+
console.error("Diff requires a remote provider to compare against.");
|
|
6092
|
+
process.exit(1);
|
|
6093
|
+
}
|
|
6094
|
+
const provider = await createStateProvider({
|
|
6095
|
+
config: workspace.state,
|
|
6096
|
+
workspaceRoot: workspace.root,
|
|
6097
|
+
workspaceName: workspace.name
|
|
6098
|
+
});
|
|
6099
|
+
if (!(provider instanceof require_CachedStateProvider.CachedStateProvider)) {
|
|
6100
|
+
console.error("State provider does not support diff operation.");
|
|
6101
|
+
process.exit(1);
|
|
6102
|
+
}
|
|
6103
|
+
console.log(`Comparing state for stage: ${options.stage}...\n`);
|
|
6104
|
+
const { local, remote } = await provider.diff(options.stage);
|
|
6105
|
+
if (!local && !remote) {
|
|
6106
|
+
console.log("No state found (local or remote).");
|
|
6107
|
+
return;
|
|
6108
|
+
}
|
|
6109
|
+
if (!local) console.log("Local: (none)");
|
|
6110
|
+
else console.log(`Local: Last deployed ${local.lastDeployedAt}`);
|
|
6111
|
+
if (!remote) console.log("Remote: (none)");
|
|
6112
|
+
else console.log(`Remote: Last deployed ${remote.lastDeployedAt}`);
|
|
6113
|
+
console.log("");
|
|
6114
|
+
const localApps = local?.applications ?? {};
|
|
6115
|
+
const remoteApps = remote?.applications ?? {};
|
|
6116
|
+
const allApps = new Set([...Object.keys(localApps), ...Object.keys(remoteApps)]);
|
|
6117
|
+
if (allApps.size > 0) {
|
|
6118
|
+
console.log("Applications:");
|
|
6119
|
+
for (const app of allApps) {
|
|
6120
|
+
const localId = localApps[app];
|
|
6121
|
+
const remoteId = remoteApps[app];
|
|
6122
|
+
if (localId === remoteId) console.log(` ${app}: ${localId ?? "(none)"}`);
|
|
6123
|
+
else if (!localId) console.log(` ${app}: (none) -> ${remoteId} [REMOTE ONLY]`);
|
|
6124
|
+
else if (!remoteId) console.log(` ${app}: ${localId} -> (none) [LOCAL ONLY]`);
|
|
6125
|
+
else console.log(` ${app}: ${localId} (local) != ${remoteId} (remote) [MISMATCH]`);
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
const localServices = local?.services ?? {};
|
|
6129
|
+
const remoteServices = remote?.services ?? {};
|
|
6130
|
+
if (Object.keys(localServices).length > 0 || Object.keys(remoteServices).length > 0) {
|
|
6131
|
+
console.log("\nServices:");
|
|
6132
|
+
const serviceKeys = new Set([...Object.keys(localServices), ...Object.keys(remoteServices)]);
|
|
6133
|
+
for (const key of serviceKeys) {
|
|
6134
|
+
const localVal = localServices[key];
|
|
6135
|
+
const remoteVal = remoteServices[key];
|
|
6136
|
+
if (localVal === remoteVal) console.log(` ${key}: ${localVal ?? "(none)"}`);
|
|
6137
|
+
else console.log(` ${key}: ${localVal ?? "(none)"} (local) != ${remoteVal ?? "(none)"} (remote)`);
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
function printStateSummary(state) {
|
|
6142
|
+
const appCount = Object.keys(state.applications).length;
|
|
6143
|
+
const hasPostgres = !!state.services.postgresId;
|
|
6144
|
+
const hasRedis = !!state.services.redisId;
|
|
6145
|
+
console.log(` Stage: ${state.stage}`);
|
|
6146
|
+
console.log(` Applications: ${appCount}`);
|
|
6147
|
+
console.log(` Postgres: ${hasPostgres ? "configured" : "none"}`);
|
|
6148
|
+
console.log(` Redis: ${hasRedis ? "configured" : "none"}`);
|
|
6149
|
+
console.log(` Last deployed: ${state.lastDeployedAt}`);
|
|
6150
|
+
}
|
|
6151
|
+
function printStateDetails(state) {
|
|
6152
|
+
console.log(`Stage: ${state.stage}`);
|
|
6153
|
+
console.log(`Environment ID: ${state.environmentId}`);
|
|
6154
|
+
console.log(`Last Deployed: ${state.lastDeployedAt}`);
|
|
6155
|
+
console.log("");
|
|
6156
|
+
console.log("Applications:");
|
|
6157
|
+
const apps = Object.entries(state.applications);
|
|
6158
|
+
if (apps.length === 0) console.log(" (none)");
|
|
6159
|
+
else for (const [name$1, id] of apps) console.log(` ${name$1}: ${id}`);
|
|
6160
|
+
console.log("");
|
|
6161
|
+
console.log("Services:");
|
|
6162
|
+
if (!state.services.postgresId && !state.services.redisId) console.log(" (none)");
|
|
6163
|
+
else {
|
|
6164
|
+
if (state.services.postgresId) console.log(` Postgres: ${state.services.postgresId}`);
|
|
6165
|
+
if (state.services.redisId) console.log(` Redis: ${state.services.redisId}`);
|
|
6166
|
+
}
|
|
6167
|
+
if (state.dnsVerified && Object.keys(state.dnsVerified).length > 0) {
|
|
6168
|
+
console.log("");
|
|
6169
|
+
console.log("DNS Verified:");
|
|
6170
|
+
for (const [hostname, info] of Object.entries(state.dnsVerified)) console.log(` ${hostname}: ${info.serverIp} (${info.verifiedAt})`);
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
|
|
6174
|
+
//#endregion
|
|
6175
|
+
//#region src/secrets/generator.ts
|
|
6176
|
+
/**
|
|
6177
|
+
* Generate a secure random password using URL-safe base64 characters.
|
|
6178
|
+
* @param length Password length (default: 32)
|
|
6179
|
+
*/
|
|
6180
|
+
function generateSecurePassword(length = 32) {
|
|
6181
|
+
return (0, node_crypto.randomBytes)(Math.ceil(length * 3 / 4)).toString("base64url").slice(0, length);
|
|
6182
|
+
}
|
|
6183
|
+
/** Default service configurations */
|
|
6184
|
+
const SERVICE_DEFAULTS = {
|
|
6185
|
+
postgres: {
|
|
6186
|
+
host: "postgres",
|
|
6187
|
+
port: 5432,
|
|
6129
6188
|
username: "app",
|
|
6130
6189
|
database: "app"
|
|
6131
6190
|
},
|
|
@@ -6318,7 +6377,10 @@ function generateAuthAppFiles(options) {
|
|
|
6318
6377
|
compilerOptions: {
|
|
6319
6378
|
noEmit: true,
|
|
6320
6379
|
baseUrl: ".",
|
|
6321
|
-
paths: {
|
|
6380
|
+
paths: {
|
|
6381
|
+
"~/*": ["./src/*"],
|
|
6382
|
+
[`@${options.name}/*`]: ["../../packages/*/src"]
|
|
6383
|
+
}
|
|
6322
6384
|
},
|
|
6323
6385
|
include: ["src/**/*.ts"],
|
|
6324
6386
|
exclude: ["node_modules", "dist"]
|
|
@@ -6528,7 +6590,10 @@ export default defineConfig({
|
|
|
6528
6590
|
compilerOptions: {
|
|
6529
6591
|
noEmit: true,
|
|
6530
6592
|
baseUrl: ".",
|
|
6531
|
-
paths: {
|
|
6593
|
+
paths: {
|
|
6594
|
+
"~/*": ["./src/*"],
|
|
6595
|
+
[`@${options.name}/*`]: ["../../packages/*/src"]
|
|
6596
|
+
}
|
|
6532
6597
|
},
|
|
6533
6598
|
include: ["src/**/*.ts"],
|
|
6534
6599
|
exclude: ["node_modules", "dist"]
|
|
@@ -6648,7 +6713,10 @@ function generateSingleAppConfigFiles(options, _template, _helpers) {
|
|
|
6648
6713
|
compilerOptions: {
|
|
6649
6714
|
noEmit: true,
|
|
6650
6715
|
baseUrl: ".",
|
|
6651
|
-
paths: {
|
|
6716
|
+
paths: {
|
|
6717
|
+
"~/*": ["./src/*"],
|
|
6718
|
+
[`@${options.name}/*`]: ["../../packages/*/src"]
|
|
6719
|
+
}
|
|
6652
6720
|
},
|
|
6653
6721
|
include: ["src/**/*.ts"],
|
|
6654
6722
|
exclude: ["node_modules", "dist"]
|
|
@@ -6946,7 +7014,11 @@ function generateModelsPackage(options) {
|
|
|
6946
7014
|
// Common Schemas
|
|
6947
7015
|
// ============================================
|
|
6948
7016
|
|
|
6949
|
-
export const IdSchema = z.
|
|
7017
|
+
export const IdSchema = z.uuid();
|
|
7018
|
+
|
|
7019
|
+
export const IdParamsSchema = z.object({
|
|
7020
|
+
id: IdSchema,
|
|
7021
|
+
});
|
|
6950
7022
|
|
|
6951
7023
|
export const TimestampsSchema = z.object({
|
|
6952
7024
|
createdAt: z.coerce.date(),
|
|
@@ -6972,6 +7044,7 @@ export const PaginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =
|
|
|
6972
7044
|
// ============================================
|
|
6973
7045
|
|
|
6974
7046
|
export type Id = z.infer<typeof IdSchema>;
|
|
7047
|
+
export type IdParams = z.infer<typeof IdParamsSchema>;
|
|
6975
7048
|
export type Timestamps = z.infer<typeof TimestampsSchema>;
|
|
6976
7049
|
export type Pagination = z.infer<typeof PaginationSchema>;
|
|
6977
7050
|
`;
|
|
@@ -7064,6 +7137,7 @@ function generateMonorepoFiles(options, _template) {
|
|
|
7064
7137
|
lint: "biome lint .",
|
|
7065
7138
|
fmt: "biome format . --write",
|
|
7066
7139
|
"fmt:check": "biome format .",
|
|
7140
|
+
...isFullstack ? { storybook: "pnpm --filter ./packages/ui storybook" } : {},
|
|
7067
7141
|
...options.deployTarget === "dokploy" ? { deploy: "gkm deploy --provider dokploy --stage production" } : {}
|
|
7068
7142
|
},
|
|
7069
7143
|
dependencies: { zod: "~4.1.0" },
|
|
@@ -7459,7 +7533,20 @@ export const config = envParser
|
|
|
7459
7533
|
},
|
|
7460
7534
|
{
|
|
7461
7535
|
path: getRoutePath("health.ts"),
|
|
7462
|
-
content: `import {
|
|
7536
|
+
content: monorepo ? `import { z } from 'zod';
|
|
7537
|
+
import { publicRouter } from '~/router';
|
|
7538
|
+
|
|
7539
|
+
export const healthEndpoint = publicRouter
|
|
7540
|
+
.get('/health')
|
|
7541
|
+
.output(z.object({
|
|
7542
|
+
status: z.string(),
|
|
7543
|
+
timestamp: z.string(),
|
|
7544
|
+
}))
|
|
7545
|
+
.handle(async () => ({
|
|
7546
|
+
status: 'ok',
|
|
7547
|
+
timestamp: new Date().toISOString(),
|
|
7548
|
+
}));
|
|
7549
|
+
` : `import { e } from '@geekmidas/constructs/endpoints';
|
|
7463
7550
|
import { z } from 'zod';
|
|
7464
7551
|
|
|
7465
7552
|
export const healthEndpoint = e
|
|
@@ -7512,12 +7599,12 @@ export const listUsersEndpoint = e
|
|
|
7512
7599
|
{
|
|
7513
7600
|
path: getRoutePath("users/get.ts"),
|
|
7514
7601
|
content: modelsImport ? `import { e } from '@geekmidas/constructs/endpoints';
|
|
7515
|
-
import {
|
|
7602
|
+
import { IdParamsSchema } from '${modelsImport}/common';
|
|
7516
7603
|
import { UserResponseSchema } from '${modelsImport}/user';
|
|
7517
7604
|
|
|
7518
7605
|
export const getUserEndpoint = e
|
|
7519
7606
|
.get('/users/:id')
|
|
7520
|
-
.params(
|
|
7607
|
+
.params(IdParamsSchema)
|
|
7521
7608
|
.output(UserResponseSchema)
|
|
7522
7609
|
.handle(async ({ params }) => ({
|
|
7523
7610
|
id: params.id,
|
|
@@ -7543,6 +7630,91 @@ export const getUserEndpoint = e
|
|
|
7543
7630
|
`
|
|
7544
7631
|
}
|
|
7545
7632
|
];
|
|
7633
|
+
if (options.monorepo) {
|
|
7634
|
+
files.push({
|
|
7635
|
+
path: "src/services/auth.ts",
|
|
7636
|
+
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
7637
|
+
|
|
7638
|
+
export interface Session {
|
|
7639
|
+
user: {
|
|
7640
|
+
id: string;
|
|
7641
|
+
email: string;
|
|
7642
|
+
name: string;
|
|
7643
|
+
};
|
|
7644
|
+
}
|
|
7645
|
+
|
|
7646
|
+
export interface AuthClient {
|
|
7647
|
+
getSession: (cookie: string) => Promise<Session | null>;
|
|
7648
|
+
}
|
|
7649
|
+
|
|
7650
|
+
export const authService = {
|
|
7651
|
+
serviceName: 'auth' as const,
|
|
7652
|
+
async register({ envParser, context }: ServiceRegisterOptions) {
|
|
7653
|
+
const logger = context.getLogger();
|
|
7654
|
+
|
|
7655
|
+
const config = envParser
|
|
7656
|
+
.create((get) => ({
|
|
7657
|
+
url: get('AUTH_URL').string(),
|
|
7658
|
+
}))
|
|
7659
|
+
.parse();
|
|
7660
|
+
|
|
7661
|
+
logger.info({ authUrl: config.url }, 'Auth service configured');
|
|
7662
|
+
|
|
7663
|
+
return {
|
|
7664
|
+
getSession: async (cookie: string): Promise<Session | null> => {
|
|
7665
|
+
const res = await fetch(\`\${config.url}/api/auth/get-session\`, {
|
|
7666
|
+
headers: { cookie },
|
|
7667
|
+
});
|
|
7668
|
+
if (!res.ok) return null;
|
|
7669
|
+
return res.json();
|
|
7670
|
+
},
|
|
7671
|
+
};
|
|
7672
|
+
},
|
|
7673
|
+
} satisfies Service<'auth', AuthClient>;
|
|
7674
|
+
`
|
|
7675
|
+
});
|
|
7676
|
+
files.push({
|
|
7677
|
+
path: "src/router.ts",
|
|
7678
|
+
content: `import { e } from '@geekmidas/constructs/endpoints';
|
|
7679
|
+
import { UnauthorizedError } from '@geekmidas/errors';
|
|
7680
|
+
import { authService, type Session } from './services/auth.js';
|
|
7681
|
+
import { logger } from './config/logger.js';
|
|
7682
|
+
|
|
7683
|
+
// Public router - no auth required
|
|
7684
|
+
export const publicRouter = e.logger(logger);
|
|
7685
|
+
|
|
7686
|
+
// Router with auth service available (but session not enforced)
|
|
7687
|
+
export const r = publicRouter.services([authService]);
|
|
7688
|
+
|
|
7689
|
+
// Session router - requires active session, throws if not authenticated
|
|
7690
|
+
export const sessionRouter = r.session<Session>(async ({ services, header }) => {
|
|
7691
|
+
const cookie = header('cookie') || '';
|
|
7692
|
+
const session = await services.auth.getSession(cookie);
|
|
7693
|
+
|
|
7694
|
+
if (!session?.user) {
|
|
7695
|
+
throw new UnauthorizedError('No active session');
|
|
7696
|
+
}
|
|
7697
|
+
|
|
7698
|
+
return session;
|
|
7699
|
+
});
|
|
7700
|
+
`
|
|
7701
|
+
});
|
|
7702
|
+
files.push({
|
|
7703
|
+
path: getRoutePath("profile.ts"),
|
|
7704
|
+
content: `import { z } from 'zod';
|
|
7705
|
+
import { sessionRouter } from '~/router';
|
|
7706
|
+
|
|
7707
|
+
export const profileEndpoint = sessionRouter
|
|
7708
|
+
.get('/profile')
|
|
7709
|
+
.output(z.object({
|
|
7710
|
+
id: z.string(),
|
|
7711
|
+
email: z.string(),
|
|
7712
|
+
name: z.string(),
|
|
7713
|
+
}))
|
|
7714
|
+
.handle(async ({ session }) => session.user);
|
|
7715
|
+
`
|
|
7716
|
+
});
|
|
7717
|
+
}
|
|
7546
7718
|
if (options.database) files.push({
|
|
7547
7719
|
path: "src/services/database.ts",
|
|
7548
7720
|
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
@@ -8305,6 +8477,1419 @@ function generateSourceFiles(options, template) {
|
|
|
8305
8477
|
return template.files(options);
|
|
8306
8478
|
}
|
|
8307
8479
|
|
|
8480
|
+
//#endregion
|
|
8481
|
+
//#region src/init/generators/ui.ts
|
|
8482
|
+
/**
|
|
8483
|
+
* Generate UI package files for fullstack template
|
|
8484
|
+
* Based on @geekmidas/ui with shadcn/ui, Tailwind CSS v4, and Storybook
|
|
8485
|
+
*/
|
|
8486
|
+
function generateUiPackageFiles(options) {
|
|
8487
|
+
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
8488
|
+
const packageName = `@${options.name}/ui`;
|
|
8489
|
+
const packageJson = {
|
|
8490
|
+
name: packageName,
|
|
8491
|
+
version: "0.0.1",
|
|
8492
|
+
private: true,
|
|
8493
|
+
type: "module",
|
|
8494
|
+
exports: {
|
|
8495
|
+
".": "./src/index.ts",
|
|
8496
|
+
"./components": "./src/components/index.ts",
|
|
8497
|
+
"./lib/utils": "./src/lib/utils.ts",
|
|
8498
|
+
"./styles": "./src/styles/globals.css"
|
|
8499
|
+
},
|
|
8500
|
+
scripts: {
|
|
8501
|
+
"ts:check": "tsc --noEmit",
|
|
8502
|
+
storybook: "storybook dev -p 6006",
|
|
8503
|
+
"build:storybook": "storybook build -o dist/storybook"
|
|
8504
|
+
},
|
|
8505
|
+
dependencies: {
|
|
8506
|
+
"@radix-ui/react-dialog": "~1.1.4",
|
|
8507
|
+
"@radix-ui/react-label": "~2.1.2",
|
|
8508
|
+
"@radix-ui/react-separator": "~1.1.2",
|
|
8509
|
+
"@radix-ui/react-slot": "~1.2.4",
|
|
8510
|
+
"@radix-ui/react-tabs": "~1.1.2",
|
|
8511
|
+
"@radix-ui/react-tooltip": "~1.1.6",
|
|
8512
|
+
"class-variance-authority": "~0.7.1",
|
|
8513
|
+
clsx: "^2.1.1",
|
|
8514
|
+
"lucide-react": "~0.562.0",
|
|
8515
|
+
"tailwind-merge": "~3.4.0"
|
|
8516
|
+
},
|
|
8517
|
+
devDependencies: {
|
|
8518
|
+
"@storybook/addon-a11y": "^8.4.7",
|
|
8519
|
+
"@storybook/addon-essentials": "^8.4.7",
|
|
8520
|
+
"@storybook/addon-interactions": "^8.4.7",
|
|
8521
|
+
"@storybook/react": "^8.4.7",
|
|
8522
|
+
"@storybook/react-vite": "^8.4.7",
|
|
8523
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
8524
|
+
"@types/react": "^19.0.0",
|
|
8525
|
+
"@types/react-dom": "^19.0.0",
|
|
8526
|
+
react: "^19.0.0",
|
|
8527
|
+
"react-dom": "^19.0.0",
|
|
8528
|
+
storybook: "^8.4.7",
|
|
8529
|
+
tailwindcss: "^4.0.0",
|
|
8530
|
+
typescript: "^5.8.2",
|
|
8531
|
+
vite: "^6.0.0"
|
|
8532
|
+
},
|
|
8533
|
+
peerDependencies: {
|
|
8534
|
+
react: ">=18.0.0",
|
|
8535
|
+
"react-dom": ">=18.0.0",
|
|
8536
|
+
tailwindcss: ">=4.0.0"
|
|
8537
|
+
}
|
|
8538
|
+
};
|
|
8539
|
+
const tsConfig = {
|
|
8540
|
+
extends: "../../tsconfig.json",
|
|
8541
|
+
compilerOptions: {
|
|
8542
|
+
jsx: "react-jsx",
|
|
8543
|
+
lib: [
|
|
8544
|
+
"ES2023",
|
|
8545
|
+
"DOM",
|
|
8546
|
+
"DOM.Iterable"
|
|
8547
|
+
],
|
|
8548
|
+
noEmit: true,
|
|
8549
|
+
baseUrl: ".",
|
|
8550
|
+
paths: { "~/*": ["./src/*"] }
|
|
8551
|
+
},
|
|
8552
|
+
include: ["src/**/*"],
|
|
8553
|
+
exclude: [
|
|
8554
|
+
"node_modules",
|
|
8555
|
+
"dist",
|
|
8556
|
+
"**/*.stories.tsx"
|
|
8557
|
+
]
|
|
8558
|
+
};
|
|
8559
|
+
const componentsJson = {
|
|
8560
|
+
$schema: "https://ui.shadcn.com/schema.json",
|
|
8561
|
+
style: "new-york",
|
|
8562
|
+
rsc: false,
|
|
8563
|
+
tsx: true,
|
|
8564
|
+
tailwind: {
|
|
8565
|
+
config: "",
|
|
8566
|
+
css: "src/styles/globals.css",
|
|
8567
|
+
baseColor: "neutral",
|
|
8568
|
+
cssVariables: true,
|
|
8569
|
+
prefix: ""
|
|
8570
|
+
},
|
|
8571
|
+
aliases: {
|
|
8572
|
+
components: "~/components",
|
|
8573
|
+
utils: "~/lib/utils",
|
|
8574
|
+
ui: "~/components/ui",
|
|
8575
|
+
lib: "~/lib",
|
|
8576
|
+
hooks: "~/hooks"
|
|
8577
|
+
},
|
|
8578
|
+
iconLibrary: "lucide"
|
|
8579
|
+
};
|
|
8580
|
+
const storybookMain = `import type { StorybookConfig } from '@storybook/react-vite';
|
|
8581
|
+
|
|
8582
|
+
const config: StorybookConfig = {
|
|
8583
|
+
stories: ['../src/**/*.stories.@(ts|tsx)'],
|
|
8584
|
+
addons: [
|
|
8585
|
+
'@storybook/addon-essentials',
|
|
8586
|
+
'@storybook/addon-interactions',
|
|
8587
|
+
'@storybook/addon-a11y',
|
|
8588
|
+
],
|
|
8589
|
+
framework: {
|
|
8590
|
+
name: '@storybook/react-vite',
|
|
8591
|
+
options: {},
|
|
8592
|
+
},
|
|
8593
|
+
docs: {
|
|
8594
|
+
autodocs: 'tag',
|
|
8595
|
+
},
|
|
8596
|
+
viteFinal: async (config) => {
|
|
8597
|
+
// Add Tailwind CSS v4 plugin
|
|
8598
|
+
const tailwindcss = await import('@tailwindcss/vite');
|
|
8599
|
+
config.plugins = config.plugins || [];
|
|
8600
|
+
config.plugins.push(tailwindcss.default());
|
|
8601
|
+
return config;
|
|
8602
|
+
},
|
|
8603
|
+
};
|
|
8604
|
+
|
|
8605
|
+
export default config;
|
|
8606
|
+
`;
|
|
8607
|
+
const storybookPreview = `import type { Preview } from '@storybook/react';
|
|
8608
|
+
import '../src/styles/globals.css';
|
|
8609
|
+
|
|
8610
|
+
const preview: Preview = {
|
|
8611
|
+
parameters: {
|
|
8612
|
+
backgrounds: {
|
|
8613
|
+
default: 'dark',
|
|
8614
|
+
values: [
|
|
8615
|
+
{ name: 'dark', value: '#171717' },
|
|
8616
|
+
{ name: 'surface', value: '#1c1c1c' },
|
|
8617
|
+
{ name: 'light', value: '#fafafa' },
|
|
8618
|
+
],
|
|
8619
|
+
},
|
|
8620
|
+
controls: {
|
|
8621
|
+
matchers: {
|
|
8622
|
+
color: /(background|color)$/i,
|
|
8623
|
+
date: /Date$/i,
|
|
8624
|
+
},
|
|
8625
|
+
},
|
|
8626
|
+
layout: 'centered',
|
|
8627
|
+
},
|
|
8628
|
+
};
|
|
8629
|
+
|
|
8630
|
+
export default preview;
|
|
8631
|
+
`;
|
|
8632
|
+
const globalsCss = `@import "tailwindcss";
|
|
8633
|
+
|
|
8634
|
+
@theme {
|
|
8635
|
+
--color-background: hsl(var(--background));
|
|
8636
|
+
--color-foreground: hsl(var(--foreground));
|
|
8637
|
+
--color-card: hsl(var(--card));
|
|
8638
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
8639
|
+
--color-popover: hsl(var(--popover));
|
|
8640
|
+
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
8641
|
+
--color-primary: hsl(var(--primary));
|
|
8642
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
8643
|
+
--color-secondary: hsl(var(--secondary));
|
|
8644
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
8645
|
+
--color-muted: hsl(var(--muted));
|
|
8646
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
8647
|
+
--color-accent: hsl(var(--accent));
|
|
8648
|
+
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
8649
|
+
--color-destructive: hsl(var(--destructive));
|
|
8650
|
+
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
8651
|
+
--color-border: hsl(var(--border));
|
|
8652
|
+
--color-input: hsl(var(--input));
|
|
8653
|
+
--color-ring: hsl(var(--ring));
|
|
8654
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
8655
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
8656
|
+
--radius-lg: var(--radius);
|
|
8657
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
8658
|
+
}
|
|
8659
|
+
|
|
8660
|
+
@layer base {
|
|
8661
|
+
:root {
|
|
8662
|
+
--background: 0 0% 100%;
|
|
8663
|
+
--foreground: 0 0% 3.9%;
|
|
8664
|
+
--card: 0 0% 100%;
|
|
8665
|
+
--card-foreground: 0 0% 3.9%;
|
|
8666
|
+
--popover: 0 0% 100%;
|
|
8667
|
+
--popover-foreground: 0 0% 3.9%;
|
|
8668
|
+
--primary: 160 84% 39%;
|
|
8669
|
+
--primary-foreground: 0 0% 98%;
|
|
8670
|
+
--secondary: 0 0% 96.1%;
|
|
8671
|
+
--secondary-foreground: 0 0% 9%;
|
|
8672
|
+
--muted: 0 0% 96.1%;
|
|
8673
|
+
--muted-foreground: 0 0% 45.1%;
|
|
8674
|
+
--accent: 0 0% 96.1%;
|
|
8675
|
+
--accent-foreground: 0 0% 9%;
|
|
8676
|
+
--destructive: 0 84.2% 60.2%;
|
|
8677
|
+
--destructive-foreground: 0 0% 98%;
|
|
8678
|
+
--border: 0 0% 89.8%;
|
|
8679
|
+
--input: 0 0% 89.8%;
|
|
8680
|
+
--ring: 160 84% 39%;
|
|
8681
|
+
--radius: 0.5rem;
|
|
8682
|
+
}
|
|
8683
|
+
|
|
8684
|
+
.dark {
|
|
8685
|
+
--background: 0 0% 9%;
|
|
8686
|
+
--foreground: 0 0% 98%;
|
|
8687
|
+
--card: 0 0% 11%;
|
|
8688
|
+
--card-foreground: 0 0% 98%;
|
|
8689
|
+
--popover: 0 0% 11%;
|
|
8690
|
+
--popover-foreground: 0 0% 98%;
|
|
8691
|
+
--primary: 160 84% 52%;
|
|
8692
|
+
--primary-foreground: 0 0% 9%;
|
|
8693
|
+
--secondary: 0 0% 15%;
|
|
8694
|
+
--secondary-foreground: 0 0% 98%;
|
|
8695
|
+
--muted: 0 0% 15%;
|
|
8696
|
+
--muted-foreground: 0 0% 64%;
|
|
8697
|
+
--accent: 0 0% 15%;
|
|
8698
|
+
--accent-foreground: 0 0% 98%;
|
|
8699
|
+
--destructive: 0 62.8% 50.6%;
|
|
8700
|
+
--destructive-foreground: 0 0% 98%;
|
|
8701
|
+
--border: 0 0% 18%;
|
|
8702
|
+
--input: 0 0% 18%;
|
|
8703
|
+
--ring: 160 84% 52%;
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
8706
|
+
|
|
8707
|
+
@layer base {
|
|
8708
|
+
* {
|
|
8709
|
+
@apply border-border;
|
|
8710
|
+
}
|
|
8711
|
+
body {
|
|
8712
|
+
@apply bg-background text-foreground;
|
|
8713
|
+
}
|
|
8714
|
+
}
|
|
8715
|
+
`;
|
|
8716
|
+
const utilsTs = `import { type ClassValue, clsx } from 'clsx';
|
|
8717
|
+
import { twMerge } from 'tailwind-merge';
|
|
8718
|
+
|
|
8719
|
+
export function cn(...inputs: ClassValue[]) {
|
|
8720
|
+
return twMerge(clsx(inputs));
|
|
8721
|
+
}
|
|
8722
|
+
`;
|
|
8723
|
+
const buttonTsx = `import { Slot } from '@radix-ui/react-slot';
|
|
8724
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
8725
|
+
import * as React from 'react';
|
|
8726
|
+
|
|
8727
|
+
import { cn } from '~/lib/utils';
|
|
8728
|
+
|
|
8729
|
+
const buttonVariants = cva(
|
|
8730
|
+
'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',
|
|
8731
|
+
{
|
|
8732
|
+
variants: {
|
|
8733
|
+
variant: {
|
|
8734
|
+
default:
|
|
8735
|
+
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
|
8736
|
+
destructive:
|
|
8737
|
+
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
|
8738
|
+
outline:
|
|
8739
|
+
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
|
8740
|
+
secondary:
|
|
8741
|
+
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
|
8742
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
8743
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
8744
|
+
},
|
|
8745
|
+
size: {
|
|
8746
|
+
default: 'h-9 px-4 py-2',
|
|
8747
|
+
sm: 'h-8 rounded-md px-3 text-xs',
|
|
8748
|
+
lg: 'h-10 rounded-md px-8',
|
|
8749
|
+
icon: 'h-9 w-9',
|
|
8750
|
+
},
|
|
8751
|
+
},
|
|
8752
|
+
defaultVariants: {
|
|
8753
|
+
variant: 'default',
|
|
8754
|
+
size: 'default',
|
|
8755
|
+
},
|
|
8756
|
+
},
|
|
8757
|
+
);
|
|
8758
|
+
|
|
8759
|
+
export interface ButtonProps
|
|
8760
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
8761
|
+
VariantProps<typeof buttonVariants> {
|
|
8762
|
+
asChild?: boolean;
|
|
8763
|
+
}
|
|
8764
|
+
|
|
8765
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
8766
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
8767
|
+
const Comp = asChild ? Slot : 'button';
|
|
8768
|
+
return (
|
|
8769
|
+
<Comp
|
|
8770
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
8771
|
+
ref={ref}
|
|
8772
|
+
{...props}
|
|
8773
|
+
/>
|
|
8774
|
+
);
|
|
8775
|
+
},
|
|
8776
|
+
);
|
|
8777
|
+
Button.displayName = 'Button';
|
|
8778
|
+
|
|
8779
|
+
export { Button, buttonVariants };
|
|
8780
|
+
`;
|
|
8781
|
+
const buttonStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
8782
|
+
import { Button } from '.';
|
|
8783
|
+
|
|
8784
|
+
const meta: Meta<typeof Button> = {
|
|
8785
|
+
title: 'Components/Button',
|
|
8786
|
+
component: Button,
|
|
8787
|
+
tags: ['autodocs'],
|
|
8788
|
+
argTypes: {
|
|
8789
|
+
variant: {
|
|
8790
|
+
control: 'select',
|
|
8791
|
+
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
|
|
8792
|
+
},
|
|
8793
|
+
size: {
|
|
8794
|
+
control: 'select',
|
|
8795
|
+
options: ['default', 'sm', 'lg', 'icon'],
|
|
8796
|
+
},
|
|
8797
|
+
},
|
|
8798
|
+
};
|
|
8799
|
+
|
|
8800
|
+
export default meta;
|
|
8801
|
+
type Story = StoryObj<typeof Button>;
|
|
8802
|
+
|
|
8803
|
+
export const Default: Story = {
|
|
8804
|
+
args: {
|
|
8805
|
+
children: 'Button',
|
|
8806
|
+
variant: 'default',
|
|
8807
|
+
size: 'default',
|
|
8808
|
+
},
|
|
8809
|
+
};
|
|
8810
|
+
|
|
8811
|
+
export const Secondary: Story = {
|
|
8812
|
+
args: {
|
|
8813
|
+
children: 'Secondary',
|
|
8814
|
+
variant: 'secondary',
|
|
8815
|
+
},
|
|
8816
|
+
};
|
|
8817
|
+
|
|
8818
|
+
export const Destructive: Story = {
|
|
8819
|
+
args: {
|
|
8820
|
+
children: 'Destructive',
|
|
8821
|
+
variant: 'destructive',
|
|
8822
|
+
},
|
|
8823
|
+
};
|
|
8824
|
+
|
|
8825
|
+
export const Outline: Story = {
|
|
8826
|
+
args: {
|
|
8827
|
+
children: 'Outline',
|
|
8828
|
+
variant: 'outline',
|
|
8829
|
+
},
|
|
8830
|
+
};
|
|
8831
|
+
|
|
8832
|
+
export const Ghost: Story = {
|
|
8833
|
+
args: {
|
|
8834
|
+
children: 'Ghost',
|
|
8835
|
+
variant: 'ghost',
|
|
8836
|
+
},
|
|
8837
|
+
};
|
|
8838
|
+
|
|
8839
|
+
export const Link: Story = {
|
|
8840
|
+
args: {
|
|
8841
|
+
children: 'Link',
|
|
8842
|
+
variant: 'link',
|
|
8843
|
+
},
|
|
8844
|
+
};
|
|
8845
|
+
`;
|
|
8846
|
+
const inputTsx = `import * as React from 'react';
|
|
8847
|
+
|
|
8848
|
+
import { cn } from '~/lib/utils';
|
|
8849
|
+
|
|
8850
|
+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
|
8851
|
+
({ className, type, ...props }, ref) => {
|
|
8852
|
+
return (
|
|
8853
|
+
<input
|
|
8854
|
+
type={type}
|
|
8855
|
+
className={cn(
|
|
8856
|
+
'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',
|
|
8857
|
+
className,
|
|
8858
|
+
)}
|
|
8859
|
+
ref={ref}
|
|
8860
|
+
{...props}
|
|
8861
|
+
/>
|
|
8862
|
+
);
|
|
8863
|
+
},
|
|
8864
|
+
);
|
|
8865
|
+
Input.displayName = 'Input';
|
|
8866
|
+
|
|
8867
|
+
export { Input };
|
|
8868
|
+
`;
|
|
8869
|
+
const cardTsx = `import * as React from 'react';
|
|
8870
|
+
|
|
8871
|
+
import { cn } from '~/lib/utils';
|
|
8872
|
+
|
|
8873
|
+
const Card = React.forwardRef<
|
|
8874
|
+
HTMLDivElement,
|
|
8875
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8876
|
+
>(({ className, ...props }, ref) => (
|
|
8877
|
+
<div
|
|
8878
|
+
ref={ref}
|
|
8879
|
+
className={cn(
|
|
8880
|
+
'rounded-xl border bg-card text-card-foreground shadow',
|
|
8881
|
+
className,
|
|
8882
|
+
)}
|
|
8883
|
+
{...props}
|
|
8884
|
+
/>
|
|
8885
|
+
));
|
|
8886
|
+
Card.displayName = 'Card';
|
|
8887
|
+
|
|
8888
|
+
const CardHeader = React.forwardRef<
|
|
8889
|
+
HTMLDivElement,
|
|
8890
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8891
|
+
>(({ className, ...props }, ref) => (
|
|
8892
|
+
<div
|
|
8893
|
+
ref={ref}
|
|
8894
|
+
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
|
8895
|
+
{...props}
|
|
8896
|
+
/>
|
|
8897
|
+
));
|
|
8898
|
+
CardHeader.displayName = 'CardHeader';
|
|
8899
|
+
|
|
8900
|
+
const CardTitle = React.forwardRef<
|
|
8901
|
+
HTMLDivElement,
|
|
8902
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8903
|
+
>(({ className, ...props }, ref) => (
|
|
8904
|
+
<div
|
|
8905
|
+
ref={ref}
|
|
8906
|
+
className={cn('font-semibold leading-none tracking-tight', className)}
|
|
8907
|
+
{...props}
|
|
8908
|
+
/>
|
|
8909
|
+
));
|
|
8910
|
+
CardTitle.displayName = 'CardTitle';
|
|
8911
|
+
|
|
8912
|
+
const CardDescription = React.forwardRef<
|
|
8913
|
+
HTMLDivElement,
|
|
8914
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8915
|
+
>(({ className, ...props }, ref) => (
|
|
8916
|
+
<div
|
|
8917
|
+
ref={ref}
|
|
8918
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
8919
|
+
{...props}
|
|
8920
|
+
/>
|
|
8921
|
+
));
|
|
8922
|
+
CardDescription.displayName = 'CardDescription';
|
|
8923
|
+
|
|
8924
|
+
const CardContent = React.forwardRef<
|
|
8925
|
+
HTMLDivElement,
|
|
8926
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8927
|
+
>(({ className, ...props }, ref) => (
|
|
8928
|
+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
8929
|
+
));
|
|
8930
|
+
CardContent.displayName = 'CardContent';
|
|
8931
|
+
|
|
8932
|
+
const CardFooter = React.forwardRef<
|
|
8933
|
+
HTMLDivElement,
|
|
8934
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8935
|
+
>(({ className, ...props }, ref) => (
|
|
8936
|
+
<div
|
|
8937
|
+
ref={ref}
|
|
8938
|
+
className={cn('flex items-center p-6 pt-0', className)}
|
|
8939
|
+
{...props}
|
|
8940
|
+
/>
|
|
8941
|
+
));
|
|
8942
|
+
CardFooter.displayName = 'CardFooter';
|
|
8943
|
+
|
|
8944
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
8945
|
+
`;
|
|
8946
|
+
const inputStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
8947
|
+
import { Input } from '.';
|
|
8948
|
+
|
|
8949
|
+
const meta: Meta<typeof Input> = {
|
|
8950
|
+
title: 'Components/Input',
|
|
8951
|
+
component: Input,
|
|
8952
|
+
tags: ['autodocs'],
|
|
8953
|
+
argTypes: {
|
|
8954
|
+
type: {
|
|
8955
|
+
control: 'select',
|
|
8956
|
+
options: ['text', 'email', 'password', 'number', 'search', 'tel', 'url'],
|
|
8957
|
+
},
|
|
8958
|
+
disabled: {
|
|
8959
|
+
control: 'boolean',
|
|
8960
|
+
},
|
|
8961
|
+
},
|
|
8962
|
+
};
|
|
8963
|
+
|
|
8964
|
+
export default meta;
|
|
8965
|
+
type Story = StoryObj<typeof Input>;
|
|
8966
|
+
|
|
8967
|
+
export const Default: Story = {
|
|
8968
|
+
args: {
|
|
8969
|
+
placeholder: 'Enter text...',
|
|
8970
|
+
},
|
|
8971
|
+
};
|
|
8972
|
+
|
|
8973
|
+
export const Email: Story = {
|
|
8974
|
+
args: {
|
|
8975
|
+
type: 'email',
|
|
8976
|
+
placeholder: 'Enter email...',
|
|
8977
|
+
},
|
|
8978
|
+
};
|
|
8979
|
+
|
|
8980
|
+
export const Password: Story = {
|
|
8981
|
+
args: {
|
|
8982
|
+
type: 'password',
|
|
8983
|
+
placeholder: 'Enter password...',
|
|
8984
|
+
},
|
|
8985
|
+
};
|
|
8986
|
+
|
|
8987
|
+
export const Disabled: Story = {
|
|
8988
|
+
args: {
|
|
8989
|
+
placeholder: 'Disabled input',
|
|
8990
|
+
disabled: true,
|
|
8991
|
+
},
|
|
8992
|
+
};
|
|
8993
|
+
|
|
8994
|
+
export const WithValue: Story = {
|
|
8995
|
+
args: {
|
|
8996
|
+
defaultValue: 'Hello World',
|
|
8997
|
+
},
|
|
8998
|
+
};
|
|
8999
|
+
`;
|
|
9000
|
+
const cardStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9001
|
+
import { Button } from '../button';
|
|
9002
|
+
import {
|
|
9003
|
+
Card,
|
|
9004
|
+
CardContent,
|
|
9005
|
+
CardDescription,
|
|
9006
|
+
CardFooter,
|
|
9007
|
+
CardHeader,
|
|
9008
|
+
CardTitle,
|
|
9009
|
+
} from '.';
|
|
9010
|
+
import { Input } from '../input';
|
|
9011
|
+
|
|
9012
|
+
const meta: Meta<typeof Card> = {
|
|
9013
|
+
title: 'Components/Card',
|
|
9014
|
+
component: Card,
|
|
9015
|
+
tags: ['autodocs'],
|
|
9016
|
+
};
|
|
9017
|
+
|
|
9018
|
+
export default meta;
|
|
9019
|
+
type Story = StoryObj<typeof Card>;
|
|
9020
|
+
|
|
9021
|
+
export const Default: Story = {
|
|
9022
|
+
render: () => (
|
|
9023
|
+
<Card className="w-[350px]">
|
|
9024
|
+
<CardHeader>
|
|
9025
|
+
<CardTitle>Card Title</CardTitle>
|
|
9026
|
+
<CardDescription>Card description goes here.</CardDescription>
|
|
9027
|
+
</CardHeader>
|
|
9028
|
+
<CardContent>
|
|
9029
|
+
<p>Card content goes here.</p>
|
|
9030
|
+
</CardContent>
|
|
9031
|
+
</Card>
|
|
9032
|
+
),
|
|
9033
|
+
};
|
|
9034
|
+
|
|
9035
|
+
export const WithFooter: Story = {
|
|
9036
|
+
render: () => (
|
|
9037
|
+
<Card className="w-[350px]">
|
|
9038
|
+
<CardHeader>
|
|
9039
|
+
<CardTitle>Create Account</CardTitle>
|
|
9040
|
+
<CardDescription>Enter your details below.</CardDescription>
|
|
9041
|
+
</CardHeader>
|
|
9042
|
+
<CardContent className="space-y-4">
|
|
9043
|
+
<Input placeholder="Email" type="email" />
|
|
9044
|
+
<Input placeholder="Password" type="password" />
|
|
9045
|
+
</CardContent>
|
|
9046
|
+
<CardFooter className="flex justify-between">
|
|
9047
|
+
<Button variant="outline">Cancel</Button>
|
|
9048
|
+
<Button>Create</Button>
|
|
9049
|
+
</CardFooter>
|
|
9050
|
+
</Card>
|
|
9051
|
+
),
|
|
9052
|
+
};
|
|
9053
|
+
|
|
9054
|
+
export const Simple: Story = {
|
|
9055
|
+
render: () => (
|
|
9056
|
+
<Card className="w-[350px] p-6">
|
|
9057
|
+
<p>Simple card with just content.</p>
|
|
9058
|
+
</Card>
|
|
9059
|
+
),
|
|
9060
|
+
};
|
|
9061
|
+
`;
|
|
9062
|
+
const labelTsx = `import * as LabelPrimitive from '@radix-ui/react-label';
|
|
9063
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
9064
|
+
import * as React from 'react';
|
|
9065
|
+
|
|
9066
|
+
import { cn } from '~/lib/utils';
|
|
9067
|
+
|
|
9068
|
+
const labelVariants = cva(
|
|
9069
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
9070
|
+
);
|
|
9071
|
+
|
|
9072
|
+
const Label = React.forwardRef<
|
|
9073
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
9074
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
|
9075
|
+
VariantProps<typeof labelVariants>
|
|
9076
|
+
>(({ className, ...props }, ref) => (
|
|
9077
|
+
<LabelPrimitive.Root
|
|
9078
|
+
ref={ref}
|
|
9079
|
+
className={cn(labelVariants(), className)}
|
|
9080
|
+
{...props}
|
|
9081
|
+
/>
|
|
9082
|
+
));
|
|
9083
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
9084
|
+
|
|
9085
|
+
export { Label };
|
|
9086
|
+
`;
|
|
9087
|
+
const labelStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9088
|
+
import { Input } from '../input';
|
|
9089
|
+
import { Label } from '.';
|
|
9090
|
+
|
|
9091
|
+
const meta: Meta<typeof Label> = {
|
|
9092
|
+
title: 'Components/Label',
|
|
9093
|
+
component: Label,
|
|
9094
|
+
tags: ['autodocs'],
|
|
9095
|
+
};
|
|
9096
|
+
|
|
9097
|
+
export default meta;
|
|
9098
|
+
type Story = StoryObj<typeof Label>;
|
|
9099
|
+
|
|
9100
|
+
export const Default: Story = {
|
|
9101
|
+
args: {
|
|
9102
|
+
children: 'Label',
|
|
9103
|
+
},
|
|
9104
|
+
};
|
|
9105
|
+
|
|
9106
|
+
export const WithInput: Story = {
|
|
9107
|
+
render: () => (
|
|
9108
|
+
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
9109
|
+
<Label htmlFor="email">Email</Label>
|
|
9110
|
+
<Input type="email" id="email" placeholder="Email" />
|
|
9111
|
+
</div>
|
|
9112
|
+
),
|
|
9113
|
+
};
|
|
9114
|
+
|
|
9115
|
+
export const Disabled: Story = {
|
|
9116
|
+
render: () => (
|
|
9117
|
+
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
9118
|
+
<Label htmlFor="disabled" className="peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
9119
|
+
Disabled
|
|
9120
|
+
</Label>
|
|
9121
|
+
<Input type="text" id="disabled" placeholder="Disabled" disabled className="peer" />
|
|
9122
|
+
</div>
|
|
9123
|
+
),
|
|
9124
|
+
};
|
|
9125
|
+
`;
|
|
9126
|
+
const badgeTsx = `import { cva, type VariantProps } from 'class-variance-authority';
|
|
9127
|
+
import * as React from 'react';
|
|
9128
|
+
|
|
9129
|
+
import { cn } from '~/lib/utils';
|
|
9130
|
+
|
|
9131
|
+
const badgeVariants = cva(
|
|
9132
|
+
'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',
|
|
9133
|
+
{
|
|
9134
|
+
variants: {
|
|
9135
|
+
variant: {
|
|
9136
|
+
default:
|
|
9137
|
+
'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
|
|
9138
|
+
secondary:
|
|
9139
|
+
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
9140
|
+
destructive:
|
|
9141
|
+
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
|
9142
|
+
outline: 'text-foreground',
|
|
9143
|
+
},
|
|
9144
|
+
},
|
|
9145
|
+
defaultVariants: {
|
|
9146
|
+
variant: 'default',
|
|
9147
|
+
},
|
|
9148
|
+
},
|
|
9149
|
+
);
|
|
9150
|
+
|
|
9151
|
+
export interface BadgeProps
|
|
9152
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
9153
|
+
VariantProps<typeof badgeVariants> {}
|
|
9154
|
+
|
|
9155
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
9156
|
+
return (
|
|
9157
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
9158
|
+
);
|
|
9159
|
+
}
|
|
9160
|
+
|
|
9161
|
+
export { Badge, badgeVariants };
|
|
9162
|
+
`;
|
|
9163
|
+
const badgeStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9164
|
+
import { Badge } from '.';
|
|
9165
|
+
|
|
9166
|
+
const meta: Meta<typeof Badge> = {
|
|
9167
|
+
title: 'Components/Badge',
|
|
9168
|
+
component: Badge,
|
|
9169
|
+
tags: ['autodocs'],
|
|
9170
|
+
argTypes: {
|
|
9171
|
+
variant: {
|
|
9172
|
+
control: 'select',
|
|
9173
|
+
options: ['default', 'secondary', 'destructive', 'outline'],
|
|
9174
|
+
},
|
|
9175
|
+
},
|
|
9176
|
+
};
|
|
9177
|
+
|
|
9178
|
+
export default meta;
|
|
9179
|
+
type Story = StoryObj<typeof Badge>;
|
|
9180
|
+
|
|
9181
|
+
export const Default: Story = {
|
|
9182
|
+
args: {
|
|
9183
|
+
children: 'Badge',
|
|
9184
|
+
variant: 'default',
|
|
9185
|
+
},
|
|
9186
|
+
};
|
|
9187
|
+
|
|
9188
|
+
export const Secondary: Story = {
|
|
9189
|
+
args: {
|
|
9190
|
+
children: 'Secondary',
|
|
9191
|
+
variant: 'secondary',
|
|
9192
|
+
},
|
|
9193
|
+
};
|
|
9194
|
+
|
|
9195
|
+
export const Destructive: Story = {
|
|
9196
|
+
args: {
|
|
9197
|
+
children: 'Destructive',
|
|
9198
|
+
variant: 'destructive',
|
|
9199
|
+
},
|
|
9200
|
+
};
|
|
9201
|
+
|
|
9202
|
+
export const Outline: Story = {
|
|
9203
|
+
args: {
|
|
9204
|
+
children: 'Outline',
|
|
9205
|
+
variant: 'outline',
|
|
9206
|
+
},
|
|
9207
|
+
};
|
|
9208
|
+
`;
|
|
9209
|
+
const separatorTsx = `import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
9210
|
+
import * as React from 'react';
|
|
9211
|
+
|
|
9212
|
+
import { cn } from '~/lib/utils';
|
|
9213
|
+
|
|
9214
|
+
const Separator = React.forwardRef<
|
|
9215
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
9216
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
9217
|
+
>(
|
|
9218
|
+
(
|
|
9219
|
+
{ className, orientation = 'horizontal', decorative = true, ...props },
|
|
9220
|
+
ref,
|
|
9221
|
+
) => (
|
|
9222
|
+
<SeparatorPrimitive.Root
|
|
9223
|
+
ref={ref}
|
|
9224
|
+
decorative={decorative}
|
|
9225
|
+
orientation={orientation}
|
|
9226
|
+
className={cn(
|
|
9227
|
+
'shrink-0 bg-border',
|
|
9228
|
+
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
9229
|
+
className,
|
|
9230
|
+
)}
|
|
9231
|
+
{...props}
|
|
9232
|
+
/>
|
|
9233
|
+
),
|
|
9234
|
+
);
|
|
9235
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
9236
|
+
|
|
9237
|
+
export { Separator };
|
|
9238
|
+
`;
|
|
9239
|
+
const separatorStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9240
|
+
import { Separator } from '.';
|
|
9241
|
+
|
|
9242
|
+
const meta: Meta<typeof Separator> = {
|
|
9243
|
+
title: 'Components/Separator',
|
|
9244
|
+
component: Separator,
|
|
9245
|
+
tags: ['autodocs'],
|
|
9246
|
+
argTypes: {
|
|
9247
|
+
orientation: {
|
|
9248
|
+
control: 'select',
|
|
9249
|
+
options: ['horizontal', 'vertical'],
|
|
9250
|
+
},
|
|
9251
|
+
},
|
|
9252
|
+
};
|
|
9253
|
+
|
|
9254
|
+
export default meta;
|
|
9255
|
+
type Story = StoryObj<typeof Separator>;
|
|
9256
|
+
|
|
9257
|
+
export const Horizontal: Story = {
|
|
9258
|
+
render: () => (
|
|
9259
|
+
<div className="w-[300px]">
|
|
9260
|
+
<div className="space-y-1">
|
|
9261
|
+
<h4 className="text-sm font-medium leading-none">Radix Primitives</h4>
|
|
9262
|
+
<p className="text-sm text-muted-foreground">
|
|
9263
|
+
An open-source UI component library.
|
|
9264
|
+
</p>
|
|
9265
|
+
</div>
|
|
9266
|
+
<Separator className="my-4" />
|
|
9267
|
+
<div className="flex h-5 items-center space-x-4 text-sm">
|
|
9268
|
+
<div>Blog</div>
|
|
9269
|
+
<Separator orientation="vertical" />
|
|
9270
|
+
<div>Docs</div>
|
|
9271
|
+
<Separator orientation="vertical" />
|
|
9272
|
+
<div>Source</div>
|
|
9273
|
+
</div>
|
|
9274
|
+
</div>
|
|
9275
|
+
),
|
|
9276
|
+
};
|
|
9277
|
+
|
|
9278
|
+
export const Vertical: Story = {
|
|
9279
|
+
render: () => (
|
|
9280
|
+
<div className="flex h-5 items-center space-x-4 text-sm">
|
|
9281
|
+
<div>Blog</div>
|
|
9282
|
+
<Separator orientation="vertical" />
|
|
9283
|
+
<div>Docs</div>
|
|
9284
|
+
<Separator orientation="vertical" />
|
|
9285
|
+
<div>Source</div>
|
|
9286
|
+
</div>
|
|
9287
|
+
),
|
|
9288
|
+
};
|
|
9289
|
+
`;
|
|
9290
|
+
const tabsTsx = `import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
9291
|
+
import * as React from 'react';
|
|
9292
|
+
|
|
9293
|
+
import { cn } from '~/lib/utils';
|
|
9294
|
+
|
|
9295
|
+
const Tabs = TabsPrimitive.Root;
|
|
9296
|
+
|
|
9297
|
+
const TabsList = React.forwardRef<
|
|
9298
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
9299
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
9300
|
+
>(({ className, ...props }, ref) => (
|
|
9301
|
+
<TabsPrimitive.List
|
|
9302
|
+
ref={ref}
|
|
9303
|
+
className={cn(
|
|
9304
|
+
'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
|
|
9305
|
+
className,
|
|
9306
|
+
)}
|
|
9307
|
+
{...props}
|
|
9308
|
+
/>
|
|
9309
|
+
));
|
|
9310
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
9311
|
+
|
|
9312
|
+
const TabsTrigger = React.forwardRef<
|
|
9313
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
9314
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
9315
|
+
>(({ className, ...props }, ref) => (
|
|
9316
|
+
<TabsPrimitive.Trigger
|
|
9317
|
+
ref={ref}
|
|
9318
|
+
className={cn(
|
|
9319
|
+
'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',
|
|
9320
|
+
className,
|
|
9321
|
+
)}
|
|
9322
|
+
{...props}
|
|
9323
|
+
/>
|
|
9324
|
+
));
|
|
9325
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
9326
|
+
|
|
9327
|
+
const TabsContent = React.forwardRef<
|
|
9328
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
9329
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
9330
|
+
>(({ className, ...props }, ref) => (
|
|
9331
|
+
<TabsPrimitive.Content
|
|
9332
|
+
ref={ref}
|
|
9333
|
+
className={cn(
|
|
9334
|
+
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
9335
|
+
className,
|
|
9336
|
+
)}
|
|
9337
|
+
{...props}
|
|
9338
|
+
/>
|
|
9339
|
+
));
|
|
9340
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
9341
|
+
|
|
9342
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
9343
|
+
`;
|
|
9344
|
+
const tabsStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9345
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '.';
|
|
9346
|
+
import { Button } from '../button';
|
|
9347
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../card';
|
|
9348
|
+
import { Input } from '../input';
|
|
9349
|
+
import { Label } from '../label';
|
|
9350
|
+
|
|
9351
|
+
const meta: Meta<typeof Tabs> = {
|
|
9352
|
+
title: 'Components/Tabs',
|
|
9353
|
+
component: Tabs,
|
|
9354
|
+
tags: ['autodocs'],
|
|
9355
|
+
};
|
|
9356
|
+
|
|
9357
|
+
export default meta;
|
|
9358
|
+
type Story = StoryObj<typeof Tabs>;
|
|
9359
|
+
|
|
9360
|
+
export const Default: Story = {
|
|
9361
|
+
render: () => (
|
|
9362
|
+
<Tabs defaultValue="account" className="w-[400px]">
|
|
9363
|
+
<TabsList>
|
|
9364
|
+
<TabsTrigger value="account">Account</TabsTrigger>
|
|
9365
|
+
<TabsTrigger value="password">Password</TabsTrigger>
|
|
9366
|
+
</TabsList>
|
|
9367
|
+
<TabsContent value="account">
|
|
9368
|
+
<Card>
|
|
9369
|
+
<CardHeader>
|
|
9370
|
+
<CardTitle>Account</CardTitle>
|
|
9371
|
+
<CardDescription>
|
|
9372
|
+
Make changes to your account here. Click save when you're done.
|
|
9373
|
+
</CardDescription>
|
|
9374
|
+
</CardHeader>
|
|
9375
|
+
<CardContent className="space-y-2">
|
|
9376
|
+
<div className="space-y-1">
|
|
9377
|
+
<Label htmlFor="name">Name</Label>
|
|
9378
|
+
<Input id="name" defaultValue="Pedro Duarte" />
|
|
9379
|
+
</div>
|
|
9380
|
+
<div className="space-y-1">
|
|
9381
|
+
<Label htmlFor="username">Username</Label>
|
|
9382
|
+
<Input id="username" defaultValue="@peduarte" />
|
|
9383
|
+
</div>
|
|
9384
|
+
</CardContent>
|
|
9385
|
+
<CardFooter>
|
|
9386
|
+
<Button>Save changes</Button>
|
|
9387
|
+
</CardFooter>
|
|
9388
|
+
</Card>
|
|
9389
|
+
</TabsContent>
|
|
9390
|
+
<TabsContent value="password">
|
|
9391
|
+
<Card>
|
|
9392
|
+
<CardHeader>
|
|
9393
|
+
<CardTitle>Password</CardTitle>
|
|
9394
|
+
<CardDescription>
|
|
9395
|
+
Change your password here. After saving, you'll be logged out.
|
|
9396
|
+
</CardDescription>
|
|
9397
|
+
</CardHeader>
|
|
9398
|
+
<CardContent className="space-y-2">
|
|
9399
|
+
<div className="space-y-1">
|
|
9400
|
+
<Label htmlFor="current">Current password</Label>
|
|
9401
|
+
<Input id="current" type="password" />
|
|
9402
|
+
</div>
|
|
9403
|
+
<div className="space-y-1">
|
|
9404
|
+
<Label htmlFor="new">New password</Label>
|
|
9405
|
+
<Input id="new" type="password" />
|
|
9406
|
+
</div>
|
|
9407
|
+
</CardContent>
|
|
9408
|
+
<CardFooter>
|
|
9409
|
+
<Button>Save password</Button>
|
|
9410
|
+
</CardFooter>
|
|
9411
|
+
</Card>
|
|
9412
|
+
</TabsContent>
|
|
9413
|
+
</Tabs>
|
|
9414
|
+
),
|
|
9415
|
+
};
|
|
9416
|
+
`;
|
|
9417
|
+
const tooltipTsx = `import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
9418
|
+
import * as React from 'react';
|
|
9419
|
+
|
|
9420
|
+
import { cn } from '~/lib/utils';
|
|
9421
|
+
|
|
9422
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
9423
|
+
|
|
9424
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
9425
|
+
|
|
9426
|
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
9427
|
+
|
|
9428
|
+
const TooltipContent = React.forwardRef<
|
|
9429
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
9430
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
9431
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
9432
|
+
<TooltipPrimitive.Portal>
|
|
9433
|
+
<TooltipPrimitive.Content
|
|
9434
|
+
ref={ref}
|
|
9435
|
+
sideOffset={sideOffset}
|
|
9436
|
+
className={cn(
|
|
9437
|
+
'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',
|
|
9438
|
+
className,
|
|
9439
|
+
)}
|
|
9440
|
+
{...props}
|
|
9441
|
+
/>
|
|
9442
|
+
</TooltipPrimitive.Portal>
|
|
9443
|
+
));
|
|
9444
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
9445
|
+
|
|
9446
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
9447
|
+
`;
|
|
9448
|
+
const tooltipStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9449
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '.';
|
|
9450
|
+
import { Button } from '../button';
|
|
9451
|
+
|
|
9452
|
+
const meta: Meta<typeof Tooltip> = {
|
|
9453
|
+
title: 'Components/Tooltip',
|
|
9454
|
+
component: Tooltip,
|
|
9455
|
+
tags: ['autodocs'],
|
|
9456
|
+
decorators: [
|
|
9457
|
+
(Story) => (
|
|
9458
|
+
<TooltipProvider>
|
|
9459
|
+
<Story />
|
|
9460
|
+
</TooltipProvider>
|
|
9461
|
+
),
|
|
9462
|
+
],
|
|
9463
|
+
};
|
|
9464
|
+
|
|
9465
|
+
export default meta;
|
|
9466
|
+
type Story = StoryObj<typeof Tooltip>;
|
|
9467
|
+
|
|
9468
|
+
export const Default: Story = {
|
|
9469
|
+
render: () => (
|
|
9470
|
+
<Tooltip>
|
|
9471
|
+
<TooltipTrigger asChild>
|
|
9472
|
+
<Button variant="outline">Hover me</Button>
|
|
9473
|
+
</TooltipTrigger>
|
|
9474
|
+
<TooltipContent>
|
|
9475
|
+
<p>Add to library</p>
|
|
9476
|
+
</TooltipContent>
|
|
9477
|
+
</Tooltip>
|
|
9478
|
+
),
|
|
9479
|
+
};
|
|
9480
|
+
|
|
9481
|
+
export const Positions: Story = {
|
|
9482
|
+
render: () => (
|
|
9483
|
+
<div className="flex gap-4">
|
|
9484
|
+
<Tooltip>
|
|
9485
|
+
<TooltipTrigger asChild>
|
|
9486
|
+
<Button variant="outline">Top</Button>
|
|
9487
|
+
</TooltipTrigger>
|
|
9488
|
+
<TooltipContent side="top">
|
|
9489
|
+
<p>Top tooltip</p>
|
|
9490
|
+
</TooltipContent>
|
|
9491
|
+
</Tooltip>
|
|
9492
|
+
<Tooltip>
|
|
9493
|
+
<TooltipTrigger asChild>
|
|
9494
|
+
<Button variant="outline">Bottom</Button>
|
|
9495
|
+
</TooltipTrigger>
|
|
9496
|
+
<TooltipContent side="bottom">
|
|
9497
|
+
<p>Bottom tooltip</p>
|
|
9498
|
+
</TooltipContent>
|
|
9499
|
+
</Tooltip>
|
|
9500
|
+
<Tooltip>
|
|
9501
|
+
<TooltipTrigger asChild>
|
|
9502
|
+
<Button variant="outline">Left</Button>
|
|
9503
|
+
</TooltipTrigger>
|
|
9504
|
+
<TooltipContent side="left">
|
|
9505
|
+
<p>Left tooltip</p>
|
|
9506
|
+
</TooltipContent>
|
|
9507
|
+
</Tooltip>
|
|
9508
|
+
<Tooltip>
|
|
9509
|
+
<TooltipTrigger asChild>
|
|
9510
|
+
<Button variant="outline">Right</Button>
|
|
9511
|
+
</TooltipTrigger>
|
|
9512
|
+
<TooltipContent side="right">
|
|
9513
|
+
<p>Right tooltip</p>
|
|
9514
|
+
</TooltipContent>
|
|
9515
|
+
</Tooltip>
|
|
9516
|
+
</div>
|
|
9517
|
+
),
|
|
9518
|
+
};
|
|
9519
|
+
`;
|
|
9520
|
+
const dialogTsx = `import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
9521
|
+
import { X } from 'lucide-react';
|
|
9522
|
+
import * as React from 'react';
|
|
9523
|
+
|
|
9524
|
+
import { cn } from '~/lib/utils';
|
|
9525
|
+
|
|
9526
|
+
const Dialog = DialogPrimitive.Root;
|
|
9527
|
+
|
|
9528
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
9529
|
+
|
|
9530
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
9531
|
+
|
|
9532
|
+
const DialogClose = DialogPrimitive.Close;
|
|
9533
|
+
|
|
9534
|
+
const DialogOverlay = React.forwardRef<
|
|
9535
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
9536
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
9537
|
+
>(({ className, ...props }, ref) => (
|
|
9538
|
+
<DialogPrimitive.Overlay
|
|
9539
|
+
ref={ref}
|
|
9540
|
+
className={cn(
|
|
9541
|
+
'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',
|
|
9542
|
+
className,
|
|
9543
|
+
)}
|
|
9544
|
+
{...props}
|
|
9545
|
+
/>
|
|
9546
|
+
));
|
|
9547
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
9548
|
+
|
|
9549
|
+
const DialogContent = React.forwardRef<
|
|
9550
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
9551
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
9552
|
+
>(({ className, children, ...props }, ref) => (
|
|
9553
|
+
<DialogPortal>
|
|
9554
|
+
<DialogOverlay />
|
|
9555
|
+
<DialogPrimitive.Content
|
|
9556
|
+
ref={ref}
|
|
9557
|
+
className={cn(
|
|
9558
|
+
'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',
|
|
9559
|
+
className,
|
|
9560
|
+
)}
|
|
9561
|
+
{...props}
|
|
9562
|
+
>
|
|
9563
|
+
{children}
|
|
9564
|
+
<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">
|
|
9565
|
+
<X className="h-4 w-4" />
|
|
9566
|
+
<span className="sr-only">Close</span>
|
|
9567
|
+
</DialogPrimitive.Close>
|
|
9568
|
+
</DialogPrimitive.Content>
|
|
9569
|
+
</DialogPortal>
|
|
9570
|
+
));
|
|
9571
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
9572
|
+
|
|
9573
|
+
const DialogHeader = ({
|
|
9574
|
+
className,
|
|
9575
|
+
...props
|
|
9576
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
9577
|
+
<div
|
|
9578
|
+
className={cn(
|
|
9579
|
+
'flex flex-col space-y-1.5 text-center sm:text-left',
|
|
9580
|
+
className,
|
|
9581
|
+
)}
|
|
9582
|
+
{...props}
|
|
9583
|
+
/>
|
|
9584
|
+
);
|
|
9585
|
+
DialogHeader.displayName = 'DialogHeader';
|
|
9586
|
+
|
|
9587
|
+
const DialogFooter = ({
|
|
9588
|
+
className,
|
|
9589
|
+
...props
|
|
9590
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
9591
|
+
<div
|
|
9592
|
+
className={cn(
|
|
9593
|
+
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
|
9594
|
+
className,
|
|
9595
|
+
)}
|
|
9596
|
+
{...props}
|
|
9597
|
+
/>
|
|
9598
|
+
);
|
|
9599
|
+
DialogFooter.displayName = 'DialogFooter';
|
|
9600
|
+
|
|
9601
|
+
const DialogTitle = React.forwardRef<
|
|
9602
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
9603
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
9604
|
+
>(({ className, ...props }, ref) => (
|
|
9605
|
+
<DialogPrimitive.Title
|
|
9606
|
+
ref={ref}
|
|
9607
|
+
className={cn(
|
|
9608
|
+
'text-lg font-semibold leading-none tracking-tight',
|
|
9609
|
+
className,
|
|
9610
|
+
)}
|
|
9611
|
+
{...props}
|
|
9612
|
+
/>
|
|
9613
|
+
));
|
|
9614
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
9615
|
+
|
|
9616
|
+
const DialogDescription = React.forwardRef<
|
|
9617
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
9618
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
9619
|
+
>(({ className, ...props }, ref) => (
|
|
9620
|
+
<DialogPrimitive.Description
|
|
9621
|
+
ref={ref}
|
|
9622
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
9623
|
+
{...props}
|
|
9624
|
+
/>
|
|
9625
|
+
));
|
|
9626
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
9627
|
+
|
|
9628
|
+
export {
|
|
9629
|
+
Dialog,
|
|
9630
|
+
DialogPortal,
|
|
9631
|
+
DialogOverlay,
|
|
9632
|
+
DialogTrigger,
|
|
9633
|
+
DialogClose,
|
|
9634
|
+
DialogContent,
|
|
9635
|
+
DialogHeader,
|
|
9636
|
+
DialogFooter,
|
|
9637
|
+
DialogTitle,
|
|
9638
|
+
DialogDescription,
|
|
9639
|
+
};
|
|
9640
|
+
`;
|
|
9641
|
+
const dialogStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
9642
|
+
import {
|
|
9643
|
+
Dialog,
|
|
9644
|
+
DialogContent,
|
|
9645
|
+
DialogDescription,
|
|
9646
|
+
DialogFooter,
|
|
9647
|
+
DialogHeader,
|
|
9648
|
+
DialogTitle,
|
|
9649
|
+
DialogTrigger,
|
|
9650
|
+
} from '.';
|
|
9651
|
+
import { Button } from '../button';
|
|
9652
|
+
import { Input } from '../input';
|
|
9653
|
+
import { Label } from '../label';
|
|
9654
|
+
|
|
9655
|
+
const meta: Meta<typeof Dialog> = {
|
|
9656
|
+
title: 'Components/Dialog',
|
|
9657
|
+
component: Dialog,
|
|
9658
|
+
tags: ['autodocs'],
|
|
9659
|
+
};
|
|
9660
|
+
|
|
9661
|
+
export default meta;
|
|
9662
|
+
type Story = StoryObj<typeof Dialog>;
|
|
9663
|
+
|
|
9664
|
+
export const Default: Story = {
|
|
9665
|
+
render: () => (
|
|
9666
|
+
<Dialog>
|
|
9667
|
+
<DialogTrigger asChild>
|
|
9668
|
+
<Button variant="outline">Edit Profile</Button>
|
|
9669
|
+
</DialogTrigger>
|
|
9670
|
+
<DialogContent className="sm:max-w-[425px]">
|
|
9671
|
+
<DialogHeader>
|
|
9672
|
+
<DialogTitle>Edit profile</DialogTitle>
|
|
9673
|
+
<DialogDescription>
|
|
9674
|
+
Make changes to your profile here. Click save when you're done.
|
|
9675
|
+
</DialogDescription>
|
|
9676
|
+
</DialogHeader>
|
|
9677
|
+
<div className="grid gap-4 py-4">
|
|
9678
|
+
<div className="grid grid-cols-4 items-center gap-4">
|
|
9679
|
+
<Label htmlFor="name" className="text-right">
|
|
9680
|
+
Name
|
|
9681
|
+
</Label>
|
|
9682
|
+
<Input id="name" defaultValue="Pedro Duarte" className="col-span-3" />
|
|
9683
|
+
</div>
|
|
9684
|
+
<div className="grid grid-cols-4 items-center gap-4">
|
|
9685
|
+
<Label htmlFor="username" className="text-right">
|
|
9686
|
+
Username
|
|
9687
|
+
</Label>
|
|
9688
|
+
<Input id="username" defaultValue="@peduarte" className="col-span-3" />
|
|
9689
|
+
</div>
|
|
9690
|
+
</div>
|
|
9691
|
+
<DialogFooter>
|
|
9692
|
+
<Button type="submit">Save changes</Button>
|
|
9693
|
+
</DialogFooter>
|
|
9694
|
+
</DialogContent>
|
|
9695
|
+
</Dialog>
|
|
9696
|
+
),
|
|
9697
|
+
};
|
|
9698
|
+
|
|
9699
|
+
export const Alert: Story = {
|
|
9700
|
+
render: () => (
|
|
9701
|
+
<Dialog>
|
|
9702
|
+
<DialogTrigger asChild>
|
|
9703
|
+
<Button variant="destructive">Delete Account</Button>
|
|
9704
|
+
</DialogTrigger>
|
|
9705
|
+
<DialogContent>
|
|
9706
|
+
<DialogHeader>
|
|
9707
|
+
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
|
9708
|
+
<DialogDescription>
|
|
9709
|
+
This action cannot be undone. This will permanently delete your
|
|
9710
|
+
account and remove your data from our servers.
|
|
9711
|
+
</DialogDescription>
|
|
9712
|
+
</DialogHeader>
|
|
9713
|
+
<DialogFooter>
|
|
9714
|
+
<Button variant="outline">Cancel</Button>
|
|
9715
|
+
<Button variant="destructive">Delete</Button>
|
|
9716
|
+
</DialogFooter>
|
|
9717
|
+
</DialogContent>
|
|
9718
|
+
</Dialog>
|
|
9719
|
+
),
|
|
9720
|
+
};
|
|
9721
|
+
`;
|
|
9722
|
+
const componentsUiIndex = `export { Button, type ButtonProps, buttonVariants } from './button';
|
|
9723
|
+
export { Input } from './input';
|
|
9724
|
+
export {
|
|
9725
|
+
Card,
|
|
9726
|
+
CardHeader,
|
|
9727
|
+
CardFooter,
|
|
9728
|
+
CardTitle,
|
|
9729
|
+
CardDescription,
|
|
9730
|
+
CardContent,
|
|
9731
|
+
} from './card';
|
|
9732
|
+
export { Label } from './label';
|
|
9733
|
+
export { Badge, type BadgeProps, badgeVariants } from './badge';
|
|
9734
|
+
export { Separator } from './separator';
|
|
9735
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs';
|
|
9736
|
+
export {
|
|
9737
|
+
Tooltip,
|
|
9738
|
+
TooltipTrigger,
|
|
9739
|
+
TooltipContent,
|
|
9740
|
+
TooltipProvider,
|
|
9741
|
+
} from './tooltip';
|
|
9742
|
+
export {
|
|
9743
|
+
Dialog,
|
|
9744
|
+
DialogPortal,
|
|
9745
|
+
DialogOverlay,
|
|
9746
|
+
DialogTrigger,
|
|
9747
|
+
DialogClose,
|
|
9748
|
+
DialogContent,
|
|
9749
|
+
DialogHeader,
|
|
9750
|
+
DialogFooter,
|
|
9751
|
+
DialogTitle,
|
|
9752
|
+
DialogDescription,
|
|
9753
|
+
} from './dialog';
|
|
9754
|
+
`;
|
|
9755
|
+
const buttonIndexTsx = buttonTsx;
|
|
9756
|
+
const inputIndexTsx = inputTsx;
|
|
9757
|
+
const cardIndexTsx = cardTsx;
|
|
9758
|
+
const componentsIndex = `export * from './ui';
|
|
9759
|
+
`;
|
|
9760
|
+
const indexTs = `// @${options.name}/ui - Shared UI component library
|
|
9761
|
+
|
|
9762
|
+
// shadcn/ui components
|
|
9763
|
+
export * from './components';
|
|
9764
|
+
|
|
9765
|
+
// Utilities
|
|
9766
|
+
export { cn } from './lib/utils';
|
|
9767
|
+
`;
|
|
9768
|
+
const gitignore = `node_modules/
|
|
9769
|
+
dist/
|
|
9770
|
+
storybook-static/
|
|
9771
|
+
*.log
|
|
9772
|
+
`;
|
|
9773
|
+
return [
|
|
9774
|
+
{
|
|
9775
|
+
path: "packages/ui/package.json",
|
|
9776
|
+
content: `${JSON.stringify(packageJson, null, 2)}\n`
|
|
9777
|
+
},
|
|
9778
|
+
{
|
|
9779
|
+
path: "packages/ui/tsconfig.json",
|
|
9780
|
+
content: `${JSON.stringify(tsConfig, null, 2)}\n`
|
|
9781
|
+
},
|
|
9782
|
+
{
|
|
9783
|
+
path: "packages/ui/components.json",
|
|
9784
|
+
content: `${JSON.stringify(componentsJson, null, 2)}\n`
|
|
9785
|
+
},
|
|
9786
|
+
{
|
|
9787
|
+
path: "packages/ui/.storybook/main.ts",
|
|
9788
|
+
content: storybookMain
|
|
9789
|
+
},
|
|
9790
|
+
{
|
|
9791
|
+
path: "packages/ui/.storybook/preview.ts",
|
|
9792
|
+
content: storybookPreview
|
|
9793
|
+
},
|
|
9794
|
+
{
|
|
9795
|
+
path: "packages/ui/src/styles/globals.css",
|
|
9796
|
+
content: globalsCss
|
|
9797
|
+
},
|
|
9798
|
+
{
|
|
9799
|
+
path: "packages/ui/src/lib/utils.ts",
|
|
9800
|
+
content: utilsTs
|
|
9801
|
+
},
|
|
9802
|
+
{
|
|
9803
|
+
path: "packages/ui/src/components/ui/button/index.tsx",
|
|
9804
|
+
content: buttonIndexTsx
|
|
9805
|
+
},
|
|
9806
|
+
{
|
|
9807
|
+
path: "packages/ui/src/components/ui/button/button.stories.tsx",
|
|
9808
|
+
content: buttonStories
|
|
9809
|
+
},
|
|
9810
|
+
{
|
|
9811
|
+
path: "packages/ui/src/components/ui/input/index.tsx",
|
|
9812
|
+
content: inputIndexTsx
|
|
9813
|
+
},
|
|
9814
|
+
{
|
|
9815
|
+
path: "packages/ui/src/components/ui/input/input.stories.tsx",
|
|
9816
|
+
content: inputStories
|
|
9817
|
+
},
|
|
9818
|
+
{
|
|
9819
|
+
path: "packages/ui/src/components/ui/card/index.tsx",
|
|
9820
|
+
content: cardIndexTsx
|
|
9821
|
+
},
|
|
9822
|
+
{
|
|
9823
|
+
path: "packages/ui/src/components/ui/card/card.stories.tsx",
|
|
9824
|
+
content: cardStories
|
|
9825
|
+
},
|
|
9826
|
+
{
|
|
9827
|
+
path: "packages/ui/src/components/ui/label/index.tsx",
|
|
9828
|
+
content: labelTsx
|
|
9829
|
+
},
|
|
9830
|
+
{
|
|
9831
|
+
path: "packages/ui/src/components/ui/label/label.stories.tsx",
|
|
9832
|
+
content: labelStories
|
|
9833
|
+
},
|
|
9834
|
+
{
|
|
9835
|
+
path: "packages/ui/src/components/ui/badge/index.tsx",
|
|
9836
|
+
content: badgeTsx
|
|
9837
|
+
},
|
|
9838
|
+
{
|
|
9839
|
+
path: "packages/ui/src/components/ui/badge/badge.stories.tsx",
|
|
9840
|
+
content: badgeStories
|
|
9841
|
+
},
|
|
9842
|
+
{
|
|
9843
|
+
path: "packages/ui/src/components/ui/separator/index.tsx",
|
|
9844
|
+
content: separatorTsx
|
|
9845
|
+
},
|
|
9846
|
+
{
|
|
9847
|
+
path: "packages/ui/src/components/ui/separator/separator.stories.tsx",
|
|
9848
|
+
content: separatorStories
|
|
9849
|
+
},
|
|
9850
|
+
{
|
|
9851
|
+
path: "packages/ui/src/components/ui/tabs/index.tsx",
|
|
9852
|
+
content: tabsTsx
|
|
9853
|
+
},
|
|
9854
|
+
{
|
|
9855
|
+
path: "packages/ui/src/components/ui/tabs/tabs.stories.tsx",
|
|
9856
|
+
content: tabsStories
|
|
9857
|
+
},
|
|
9858
|
+
{
|
|
9859
|
+
path: "packages/ui/src/components/ui/tooltip/index.tsx",
|
|
9860
|
+
content: tooltipTsx
|
|
9861
|
+
},
|
|
9862
|
+
{
|
|
9863
|
+
path: "packages/ui/src/components/ui/tooltip/tooltip.stories.tsx",
|
|
9864
|
+
content: tooltipStories
|
|
9865
|
+
},
|
|
9866
|
+
{
|
|
9867
|
+
path: "packages/ui/src/components/ui/dialog/index.tsx",
|
|
9868
|
+
content: dialogTsx
|
|
9869
|
+
},
|
|
9870
|
+
{
|
|
9871
|
+
path: "packages/ui/src/components/ui/dialog/dialog.stories.tsx",
|
|
9872
|
+
content: dialogStories
|
|
9873
|
+
},
|
|
9874
|
+
{
|
|
9875
|
+
path: "packages/ui/src/components/ui/index.ts",
|
|
9876
|
+
content: componentsUiIndex
|
|
9877
|
+
},
|
|
9878
|
+
{
|
|
9879
|
+
path: "packages/ui/src/components/index.ts",
|
|
9880
|
+
content: componentsIndex
|
|
9881
|
+
},
|
|
9882
|
+
{
|
|
9883
|
+
path: "packages/ui/src/index.ts",
|
|
9884
|
+
content: indexTs
|
|
9885
|
+
},
|
|
9886
|
+
{
|
|
9887
|
+
path: "packages/ui/.gitignore",
|
|
9888
|
+
content: gitignore
|
|
9889
|
+
}
|
|
9890
|
+
];
|
|
9891
|
+
}
|
|
9892
|
+
|
|
8308
9893
|
//#endregion
|
|
8309
9894
|
//#region src/init/generators/web.ts
|
|
8310
9895
|
/**
|
|
@@ -8314,29 +9899,35 @@ function generateWebAppFiles(options) {
|
|
|
8314
9899
|
if (!options.monorepo || options.template !== "fullstack") return [];
|
|
8315
9900
|
const packageName = `@${options.name}/web`;
|
|
8316
9901
|
const modelsPackage = `@${options.name}/models`;
|
|
9902
|
+
const uiPackage = `@${options.name}/ui`;
|
|
8317
9903
|
const packageJson = {
|
|
8318
9904
|
name: packageName,
|
|
8319
9905
|
version: "0.0.1",
|
|
8320
9906
|
private: true,
|
|
8321
9907
|
type: "module",
|
|
8322
9908
|
scripts: {
|
|
8323
|
-
dev: "next dev
|
|
8324
|
-
build: "next build",
|
|
9909
|
+
dev: "gkm exec -- next dev --turbopack",
|
|
9910
|
+
build: "gkm exec -- next build",
|
|
8325
9911
|
start: "next start",
|
|
8326
9912
|
typecheck: "tsc --noEmit"
|
|
8327
9913
|
},
|
|
8328
9914
|
dependencies: {
|
|
8329
9915
|
[modelsPackage]: "workspace:*",
|
|
9916
|
+
[uiPackage]: "workspace:*",
|
|
8330
9917
|
"@geekmidas/client": GEEKMIDAS_VERSIONS["@geekmidas/client"],
|
|
8331
9918
|
"@tanstack/react-query": "~5.80.0",
|
|
9919
|
+
"better-auth": "~1.2.0",
|
|
8332
9920
|
next: "~16.1.0",
|
|
8333
9921
|
react: "~19.2.0",
|
|
8334
9922
|
"react-dom": "~19.2.0"
|
|
8335
9923
|
},
|
|
8336
9924
|
devDependencies: {
|
|
9925
|
+
"@geekmidas/cli": GEEKMIDAS_VERSIONS["@geekmidas/cli"],
|
|
9926
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
8337
9927
|
"@types/node": "~22.0.0",
|
|
8338
9928
|
"@types/react": "~19.0.0",
|
|
8339
9929
|
"@types/react-dom": "~19.0.0",
|
|
9930
|
+
tailwindcss: "^4.0.0",
|
|
8340
9931
|
typescript: "~5.8.2"
|
|
8341
9932
|
}
|
|
8342
9933
|
};
|
|
@@ -8345,10 +9936,16 @@ function generateWebAppFiles(options) {
|
|
|
8345
9936
|
const nextConfig: NextConfig = {
|
|
8346
9937
|
output: 'standalone',
|
|
8347
9938
|
reactStrictMode: true,
|
|
8348
|
-
transpilePackages: ['${modelsPackage}'],
|
|
9939
|
+
transpilePackages: ['${modelsPackage}', '${uiPackage}'],
|
|
8349
9940
|
};
|
|
8350
9941
|
|
|
8351
9942
|
export default nextConfig;
|
|
9943
|
+
`;
|
|
9944
|
+
const postcssConfig = `export default {
|
|
9945
|
+
plugins: {
|
|
9946
|
+
'@tailwindcss/postcss': {},
|
|
9947
|
+
},
|
|
9948
|
+
};
|
|
8352
9949
|
`;
|
|
8353
9950
|
const tsConfig = {
|
|
8354
9951
|
extends: "../../tsconfig.json",
|
|
@@ -8371,9 +9968,11 @@ export default nextConfig;
|
|
|
8371
9968
|
incremental: true,
|
|
8372
9969
|
plugins: [{ name: "next" }],
|
|
8373
9970
|
paths: {
|
|
8374
|
-
"
|
|
9971
|
+
"~/*": ["./src/*"],
|
|
8375
9972
|
[`${modelsPackage}`]: ["../../packages/models/src"],
|
|
8376
|
-
[`${modelsPackage}/*`]: ["../../packages/models/src/*"]
|
|
9973
|
+
[`${modelsPackage}/*`]: ["../../packages/models/src/*"],
|
|
9974
|
+
[`${uiPackage}`]: ["../../packages/ui/src"],
|
|
9975
|
+
[`${uiPackage}/*`]: ["../../packages/ui/src/*"]
|
|
8377
9976
|
},
|
|
8378
9977
|
baseUrl: "."
|
|
8379
9978
|
},
|
|
@@ -8385,68 +9984,66 @@ export default nextConfig;
|
|
|
8385
9984
|
],
|
|
8386
9985
|
exclude: ["node_modules"]
|
|
8387
9986
|
};
|
|
9987
|
+
const queryClientTs = `import { QueryClient } from '@tanstack/react-query';
|
|
9988
|
+
|
|
9989
|
+
function makeQueryClient() {
|
|
9990
|
+
return new QueryClient({
|
|
9991
|
+
defaultOptions: {
|
|
9992
|
+
queries: {
|
|
9993
|
+
staleTime: 60 * 1000,
|
|
9994
|
+
},
|
|
9995
|
+
},
|
|
9996
|
+
});
|
|
9997
|
+
}
|
|
9998
|
+
|
|
9999
|
+
let browserQueryClient: QueryClient | undefined = undefined;
|
|
10000
|
+
|
|
10001
|
+
export function getQueryClient() {
|
|
10002
|
+
if (typeof window === 'undefined') {
|
|
10003
|
+
// Server: always make a new query client
|
|
10004
|
+
return makeQueryClient();
|
|
10005
|
+
}
|
|
10006
|
+
// Browser: reuse existing query client
|
|
10007
|
+
if (!browserQueryClient) browserQueryClient = makeQueryClient();
|
|
10008
|
+
return browserQueryClient;
|
|
10009
|
+
}
|
|
10010
|
+
`;
|
|
10011
|
+
const authClientTs = `import { createAuthClient } from 'better-auth/react';
|
|
10012
|
+
import { magicLinkClient } from 'better-auth/client/plugins';
|
|
10013
|
+
|
|
10014
|
+
export const authClient = createAuthClient({
|
|
10015
|
+
baseURL: process.env.NEXT_PUBLIC_AUTH_URL || 'http://localhost:3002',
|
|
10016
|
+
plugins: [magicLinkClient()],
|
|
10017
|
+
});
|
|
10018
|
+
|
|
10019
|
+
export const { signIn, signUp, signOut, useSession, magicLink } = authClient;
|
|
10020
|
+
`;
|
|
8388
10021
|
const providersTsx = `'use client';
|
|
8389
10022
|
|
|
8390
|
-
import {
|
|
8391
|
-
import {
|
|
10023
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
10024
|
+
import { getQueryClient } from '~/lib/query-client';
|
|
8392
10025
|
|
|
8393
10026
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
8394
|
-
const
|
|
8395
|
-
() =>
|
|
8396
|
-
new QueryClient({
|
|
8397
|
-
defaultOptions: {
|
|
8398
|
-
queries: {
|
|
8399
|
-
staleTime: 60 * 1000,
|
|
8400
|
-
},
|
|
8401
|
-
},
|
|
8402
|
-
}),
|
|
8403
|
-
);
|
|
10027
|
+
const queryClient = getQueryClient();
|
|
8404
10028
|
|
|
8405
10029
|
return (
|
|
8406
10030
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
8407
10031
|
);
|
|
8408
10032
|
}
|
|
8409
10033
|
`;
|
|
8410
|
-
const apiIndexTs = `import {
|
|
8411
|
-
import {
|
|
8412
|
-
|
|
8413
|
-
// TODO: Run 'gkm openapi' to generate typed paths from your API
|
|
8414
|
-
// This is a placeholder that will be replaced by the generated openapi.ts
|
|
8415
|
-
interface paths {
|
|
8416
|
-
'/health': {
|
|
8417
|
-
get: {
|
|
8418
|
-
responses: {
|
|
8419
|
-
200: {
|
|
8420
|
-
content: {
|
|
8421
|
-
'application/json': { status: string; timestamp: string };
|
|
8422
|
-
};
|
|
8423
|
-
};
|
|
8424
|
-
};
|
|
8425
|
-
};
|
|
8426
|
-
};
|
|
8427
|
-
'/users': {
|
|
8428
|
-
get: {
|
|
8429
|
-
responses: {
|
|
8430
|
-
200: {
|
|
8431
|
-
content: {
|
|
8432
|
-
'application/json': { users: Array<{ id: string; name: string }> };
|
|
8433
|
-
};
|
|
8434
|
-
};
|
|
8435
|
-
};
|
|
8436
|
-
};
|
|
8437
|
-
};
|
|
8438
|
-
}
|
|
8439
|
-
|
|
8440
|
-
const baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
|
10034
|
+
const apiIndexTs = `import { createApi } from './openapi';
|
|
10035
|
+
import { getQueryClient } from '~/lib/query-client';
|
|
8441
10036
|
|
|
8442
|
-
const
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
10037
|
+
export const api = createApi({
|
|
10038
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
|
|
10039
|
+
queryClient: getQueryClient(),
|
|
10040
|
+
});
|
|
10041
|
+
`;
|
|
10042
|
+
const globalsCss = `@import '${uiPackage}/styles';
|
|
8447
10043
|
`;
|
|
8448
10044
|
const layoutTsx = `import type { Metadata } from 'next';
|
|
8449
10045
|
import { Providers } from './providers';
|
|
10046
|
+
import './globals.css';
|
|
8450
10047
|
|
|
8451
10048
|
export const metadata: Metadata = {
|
|
8452
10049
|
title: '${options.name}',
|
|
@@ -8467,42 +10064,59 @@ export default function RootLayout({
|
|
|
8467
10064
|
);
|
|
8468
10065
|
}
|
|
8469
10066
|
`;
|
|
8470
|
-
const pageTsx = `import { api } from '
|
|
10067
|
+
const pageTsx = `import { api } from '~/api';
|
|
10068
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '${uiPackage}/components';
|
|
8471
10069
|
|
|
8472
10070
|
export default async function Home() {
|
|
8473
10071
|
// Type-safe API call using the generated client
|
|
8474
10072
|
const health = await api('GET /health').catch(() => null);
|
|
8475
10073
|
|
|
8476
10074
|
return (
|
|
8477
|
-
<main
|
|
8478
|
-
<
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
</
|
|
8499
|
-
|
|
10075
|
+
<main className="min-h-screen bg-background p-8">
|
|
10076
|
+
<div className="mx-auto max-w-4xl space-y-8">
|
|
10077
|
+
<div className="space-y-2">
|
|
10078
|
+
<h1 className="text-4xl font-bold tracking-tight">Welcome to ${options.name}</h1>
|
|
10079
|
+
<p className="text-muted-foreground">Your fullstack application is ready.</p>
|
|
10080
|
+
</div>
|
|
10081
|
+
|
|
10082
|
+
<Card>
|
|
10083
|
+
<CardHeader>
|
|
10084
|
+
<CardTitle>API Status</CardTitle>
|
|
10085
|
+
<CardDescription>Connection to your backend API</CardDescription>
|
|
10086
|
+
</CardHeader>
|
|
10087
|
+
<CardContent>
|
|
10088
|
+
{health ? (
|
|
10089
|
+
<pre className="rounded-lg bg-muted p-4 text-sm">
|
|
10090
|
+
{JSON.stringify(health, null, 2)}
|
|
10091
|
+
</pre>
|
|
10092
|
+
) : (
|
|
10093
|
+
<p className="text-destructive">Unable to connect to API</p>
|
|
10094
|
+
)}
|
|
10095
|
+
</CardContent>
|
|
10096
|
+
</Card>
|
|
10097
|
+
|
|
10098
|
+
<Card>
|
|
10099
|
+
<CardHeader>
|
|
10100
|
+
<CardTitle>Next Steps</CardTitle>
|
|
10101
|
+
<CardDescription>Get started with your project</CardDescription>
|
|
10102
|
+
</CardHeader>
|
|
10103
|
+
<CardContent className="space-y-4">
|
|
10104
|
+
<ul className="list-inside list-disc space-y-2 text-muted-foreground">
|
|
10105
|
+
<li>Run <code className="rounded bg-muted px-1">gkm openapi</code> to generate typed API client</li>
|
|
10106
|
+
<li>Edit <code className="rounded bg-muted px-1">apps/web/src/app/page.tsx</code> to customize this page</li>
|
|
10107
|
+
<li>Add API routes in <code className="rounded bg-muted px-1">apps/api/src/endpoints/</code></li>
|
|
10108
|
+
<li>Add UI components with <code className="rounded bg-muted px-1">npx shadcn@latest add</code> in packages/ui</li>
|
|
10109
|
+
</ul>
|
|
10110
|
+
<div className="flex gap-4">
|
|
10111
|
+
<Button>Get Started</Button>
|
|
10112
|
+
<Button variant="outline">Documentation</Button>
|
|
10113
|
+
</div>
|
|
10114
|
+
</CardContent>
|
|
10115
|
+
</Card>
|
|
10116
|
+
</div>
|
|
8500
10117
|
</main>
|
|
8501
10118
|
);
|
|
8502
10119
|
}
|
|
8503
|
-
`;
|
|
8504
|
-
const envLocal = `# API URL for client-side requests
|
|
8505
|
-
NEXT_PUBLIC_API_URL=http://localhost:3000
|
|
8506
10120
|
`;
|
|
8507
10121
|
const gitignore = `.next/
|
|
8508
10122
|
node_modules/
|
|
@@ -8518,10 +10132,18 @@ node_modules/
|
|
|
8518
10132
|
path: "apps/web/next.config.ts",
|
|
8519
10133
|
content: nextConfig
|
|
8520
10134
|
},
|
|
10135
|
+
{
|
|
10136
|
+
path: "apps/web/postcss.config.mjs",
|
|
10137
|
+
content: postcssConfig
|
|
10138
|
+
},
|
|
8521
10139
|
{
|
|
8522
10140
|
path: "apps/web/tsconfig.json",
|
|
8523
10141
|
content: `${JSON.stringify(tsConfig, null, 2)}\n`
|
|
8524
10142
|
},
|
|
10143
|
+
{
|
|
10144
|
+
path: "apps/web/src/app/globals.css",
|
|
10145
|
+
content: globalsCss
|
|
10146
|
+
},
|
|
8525
10147
|
{
|
|
8526
10148
|
path: "apps/web/src/app/layout.tsx",
|
|
8527
10149
|
content: layoutTsx
|
|
@@ -8535,12 +10157,16 @@ node_modules/
|
|
|
8535
10157
|
content: pageTsx
|
|
8536
10158
|
},
|
|
8537
10159
|
{
|
|
8538
|
-
path: "apps/web/src/
|
|
8539
|
-
content:
|
|
10160
|
+
path: "apps/web/src/lib/query-client.ts",
|
|
10161
|
+
content: queryClientTs
|
|
8540
10162
|
},
|
|
8541
10163
|
{
|
|
8542
|
-
path: "apps/web
|
|
8543
|
-
content:
|
|
10164
|
+
path: "apps/web/src/lib/auth-client.ts",
|
|
10165
|
+
content: authClientTs
|
|
10166
|
+
},
|
|
10167
|
+
{
|
|
10168
|
+
path: "apps/web/src/api/index.ts",
|
|
10169
|
+
content: apiIndexTs
|
|
8544
10170
|
},
|
|
8545
10171
|
{
|
|
8546
10172
|
path: "apps/web/.gitignore",
|
|
@@ -8779,6 +10405,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
8779
10405
|
const rootFiles = baseTemplate ? [...generateMonorepoFiles(templateOptions, baseTemplate), ...generateModelsPackage(templateOptions)] : [];
|
|
8780
10406
|
const webAppFiles = isFullstack ? generateWebAppFiles(templateOptions) : [];
|
|
8781
10407
|
const authAppFiles = isFullstack ? generateAuthAppFiles(templateOptions) : [];
|
|
10408
|
+
const uiPackageFiles = isFullstack ? generateUiPackageFiles(templateOptions) : [];
|
|
8782
10409
|
for (const { path, content } of rootFiles) {
|
|
8783
10410
|
const fullPath = (0, node_path.join)(targetDir, path);
|
|
8784
10411
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
@@ -8804,6 +10431,11 @@ async function initCommand(projectName, options = {}) {
|
|
|
8804
10431
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
8805
10432
|
await (0, node_fs_promises.writeFile)(fullPath, content);
|
|
8806
10433
|
}
|
|
10434
|
+
for (const { path, content } of uiPackageFiles) {
|
|
10435
|
+
const fullPath = (0, node_path.join)(targetDir, path);
|
|
10436
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
|
|
10437
|
+
await (0, node_fs_promises.writeFile)(fullPath, content);
|
|
10438
|
+
}
|
|
8807
10439
|
console.log("🔐 Initializing encrypted secrets...\n");
|
|
8808
10440
|
const secretServices = [];
|
|
8809
10441
|
if (services.db) secretServices.push("postgres");
|
|
@@ -8823,6 +10455,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
8823
10455
|
customSecrets[passwordKey] = app.password;
|
|
8824
10456
|
}
|
|
8825
10457
|
customSecrets.AUTH_PORT = "3002";
|
|
10458
|
+
customSecrets.AUTH_URL = "http://localhost:3002";
|
|
8826
10459
|
customSecrets.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
8827
10460
|
customSecrets.BETTER_AUTH_URL = "http://localhost:3002";
|
|
8828
10461
|
customSecrets.BETTER_AUTH_TRUSTED_ORIGINS = "http://localhost:3000,http://localhost:3001";
|
|
@@ -8899,7 +10532,8 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
8899
10532
|
console.log(` │ └── web/ # Next.js frontend`);
|
|
8900
10533
|
}
|
|
8901
10534
|
console.log(` ├── packages/`);
|
|
8902
|
-
console.log(` │
|
|
10535
|
+
console.log(` │ ├── models/ # Shared Zod schemas`);
|
|
10536
|
+
if (isFullstackTemplate(options.template)) console.log(` │ └── ui/ # Shared UI components`);
|
|
8903
10537
|
console.log(` ├── .gkm/secrets/ # Encrypted secrets`);
|
|
8904
10538
|
console.log(` ├── gkm.config.ts # Workspace config`);
|
|
8905
10539
|
console.log(` └── turbo.json # Turbo config`);
|
|
@@ -8915,7 +10549,7 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
8915
10549
|
console.log(` ${getRunCommand(pkgManager, "deploy")}`);
|
|
8916
10550
|
console.log("");
|
|
8917
10551
|
}
|
|
8918
|
-
console.log("📚 Documentation: https://
|
|
10552
|
+
console.log("📚 Documentation: https://geekmidas.github.io/toolbox/");
|
|
8919
10553
|
console.log("");
|
|
8920
10554
|
}
|
|
8921
10555
|
|
|
@@ -9467,6 +11101,46 @@ program.command("whoami").description("Show current authentication status").acti
|
|
|
9467
11101
|
process.exit(1);
|
|
9468
11102
|
}
|
|
9469
11103
|
});
|
|
11104
|
+
program.command("state:pull").description("Pull deployment state from remote to local").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 statePullCommand(options);
|
|
11109
|
+
} catch (error) {
|
|
11110
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11111
|
+
process.exit(1);
|
|
11112
|
+
}
|
|
11113
|
+
});
|
|
11114
|
+
program.command("state:push").description("Push deployment state from local to remote").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").action(async (options) => {
|
|
11115
|
+
try {
|
|
11116
|
+
const globalOptions = program.opts();
|
|
11117
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11118
|
+
await statePushCommand(options);
|
|
11119
|
+
} catch (error) {
|
|
11120
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11121
|
+
process.exit(1);
|
|
11122
|
+
}
|
|
11123
|
+
});
|
|
11124
|
+
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) => {
|
|
11125
|
+
try {
|
|
11126
|
+
const globalOptions = program.opts();
|
|
11127
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11128
|
+
await stateShowCommand(options);
|
|
11129
|
+
} catch (error) {
|
|
11130
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11131
|
+
process.exit(1);
|
|
11132
|
+
}
|
|
11133
|
+
});
|
|
11134
|
+
program.command("state:diff").description("Compare local and remote deployment state").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").action(async (options) => {
|
|
11135
|
+
try {
|
|
11136
|
+
const globalOptions = program.opts();
|
|
11137
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11138
|
+
await stateDiffCommand(options);
|
|
11139
|
+
} catch (error) {
|
|
11140
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11141
|
+
process.exit(1);
|
|
11142
|
+
}
|
|
11143
|
+
});
|
|
9470
11144
|
program.parse();
|
|
9471
11145
|
|
|
9472
11146
|
//#endregion
|