@geekmidas/cli 0.54.0 → 1.0.1

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