@revealui/cli 0.2.0 → 0.3.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 (66) hide show
  1. package/README.md +114 -6
  2. package/bin/create-revealui.js +3 -3
  3. package/bin/revealui.js +6 -0
  4. package/dist/cli.d.ts +3 -1
  5. package/dist/cli.js +1403 -673
  6. package/dist/cli.js.map +1 -1
  7. package/dist/index.d.ts +0 -10
  8. package/dist/index.js +1384 -657
  9. package/dist/index.js.map +1 -1
  10. package/package.json +15 -6
  11. package/templates/{minimal → basic-blog}/package.json +8 -6
  12. package/templates/basic-blog/postcss.config.mjs +5 -0
  13. package/templates/basic-blog/revealui.config.ts +19 -0
  14. package/templates/basic-blog/src/app/globals.css +6 -0
  15. package/templates/{minimal → basic-blog}/src/app/layout.tsx +4 -4
  16. package/templates/basic-blog/src/app/page.tsx +57 -0
  17. package/templates/basic-blog/src/app/posts/[slug]/page.tsx +66 -0
  18. package/templates/basic-blog/src/app/posts/page.tsx +61 -0
  19. package/templates/basic-blog/src/collections/Posts.ts +42 -0
  20. package/templates/basic-blog/src/seed.ts +73 -0
  21. package/templates/{minimal → basic-blog}/tsconfig.json +1 -1
  22. package/templates/e-commerce/.env.example +36 -0
  23. package/templates/e-commerce/_gitignore +26 -0
  24. package/templates/e-commerce/next.config.mjs +10 -0
  25. package/templates/e-commerce/package.json +36 -0
  26. package/templates/e-commerce/postcss.config.mjs +5 -0
  27. package/templates/e-commerce/revealui.config.ts +20 -0
  28. package/templates/e-commerce/src/app/globals.css +6 -0
  29. package/templates/e-commerce/src/app/layout.tsx +15 -0
  30. package/templates/e-commerce/src/app/page.tsx +82 -0
  31. package/templates/e-commerce/src/app/products/[slug]/page.tsx +80 -0
  32. package/templates/e-commerce/src/app/products/page.tsx +72 -0
  33. package/templates/e-commerce/src/collections/Orders.ts +63 -0
  34. package/templates/e-commerce/src/collections/Products.ts +50 -0
  35. package/templates/e-commerce/src/seed.ts +72 -0
  36. package/templates/e-commerce/tsconfig.json +11 -0
  37. package/templates/portfolio/.env.example +36 -0
  38. package/templates/portfolio/_gitignore +26 -0
  39. package/templates/portfolio/next.config.mjs +10 -0
  40. package/templates/portfolio/package.json +36 -0
  41. package/templates/portfolio/postcss.config.mjs +5 -0
  42. package/templates/portfolio/revealui.config.ts +19 -0
  43. package/templates/portfolio/src/app/globals.css +6 -0
  44. package/templates/portfolio/src/app/layout.tsx +15 -0
  45. package/templates/portfolio/src/app/page.tsx +60 -0
  46. package/templates/portfolio/src/app/projects/[slug]/page.tsx +95 -0
  47. package/templates/portfolio/src/app/projects/page.tsx +85 -0
  48. package/templates/portfolio/src/collections/Projects.ts +49 -0
  49. package/templates/portfolio/src/seed.ts +73 -0
  50. package/templates/portfolio/tsconfig.json +11 -0
  51. package/templates/starter/.env.example +36 -0
  52. package/templates/starter/_gitignore +26 -0
  53. package/templates/starter/next.config.mjs +10 -0
  54. package/templates/starter/package.json +36 -0
  55. package/templates/starter/postcss.config.mjs +5 -0
  56. package/templates/{minimal → starter}/revealui.config.ts +4 -4
  57. package/templates/starter/src/app/globals.css +6 -0
  58. package/templates/starter/src/app/layout.tsx +15 -0
  59. package/templates/starter/src/app/page.tsx +18 -0
  60. package/templates/starter/src/seed.ts +40 -0
  61. package/templates/starter/tsconfig.json +11 -0
  62. package/templates/minimal/src/app/globals.css +0 -15
  63. package/templates/minimal/src/app/page.tsx +0 -20
  64. /package/templates/{minimal → basic-blog}/.env.example +0 -0
  65. /package/templates/{minimal → basic-blog}/_gitignore +0 -0
  66. /package/templates/{minimal → basic-blog}/next.config.mjs +0 -0
package/dist/index.js CHANGED
@@ -1,158 +1,456 @@
1
1
  #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
- };
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
-
12
- // ../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.56.2_@types+node@25.3.0__@swc+core@1.15.11_@swc+h_406b258ea3c313c6308725a34065a5eb/node_modules/tsup/assets/esm_shims.js
13
- import path from "path";
14
- import { fileURLToPath } from "url";
15
- var init_esm_shims = __esm({
16
- "../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.56.2_@types+node@25.3.0__@swc+core@1.15.11_@swc+h_406b258ea3c313c6308725a34065a5eb/node_modules/tsup/assets/esm_shims.js"() {
17
- "use strict";
18
- }
19
- });
20
2
 
21
3
  // src/cli.ts
22
- import { createLogger } from "@revealui/setup/utils";
4
+ import { createLogger as createLogger11 } from "@revealui/setup/utils";
23
5
  import { Command } from "commander";
24
- function createCli() {
25
- const program = new Command();
26
- program.name("create-revealui").description("Create a new RevealUI project").version("0.1.0").argument("[project-name]", "Name of the project").option("-t, --template <name>", "Template to use (basic-blog, e-commerce, portfolio)").option("--skip-git", "Skip git initialization", false).option("--skip-install", "Skip dependency installation", false).action(async (projectName, options) => {
27
- logger.header("\u{1F680} Create RevealUI Project");
28
- const { run: run2 } = await Promise.resolve().then(() => (init_index(), index_exports));
29
- await run2(projectName, options);
30
- });
31
- return program;
6
+
7
+ // src/commands/create-flow.ts
8
+ import { readFileSync } from "fs";
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ import { createLogger as createLogger6 } from "@revealui/setup/utils";
12
+ import { importSPKI, jwtVerify } from "jose";
13
+
14
+ // src/prompts/database.ts
15
+ import inquirer from "inquirer";
16
+
17
+ // src/validators/credentials.ts
18
+ import { createLogger } from "@revealui/setup/utils";
19
+ var logger = createLogger({ prefix: "Validator" });
20
+ async function validateStripeKey(key) {
21
+ if (!(key.startsWith("sk_test_") || key.startsWith("sk_live_"))) {
22
+ return {
23
+ valid: false,
24
+ message: "Stripe key must start with sk_test_ or sk_live_"
25
+ };
26
+ }
27
+ return { valid: true };
32
28
  }
33
- var logger;
34
- var init_cli = __esm({
35
- "src/cli.ts"() {
36
- "use strict";
37
- init_esm_shims();
38
- logger = createLogger({ prefix: "CLI" });
29
+ async function validateNeonUrl(url) {
30
+ try {
31
+ const parsed = new URL(url);
32
+ if (!parsed.protocol.startsWith("postgres")) {
33
+ return {
34
+ valid: false,
35
+ message: "Database URL must use postgres:// or postgresql:// protocol"
36
+ };
37
+ }
38
+ return { valid: true };
39
+ } catch {
40
+ return {
41
+ valid: false,
42
+ message: "Invalid database URL format"
43
+ };
39
44
  }
40
- });
41
-
42
- // src/validators/node-version.ts
43
- import { createLogger as createLogger2 } from "@revealui/setup/utils";
44
- function validateNodeVersion() {
45
- const currentVersion = process.version.slice(1);
46
- const [currentMajor, currentMinor] = currentVersion.split(".").map(Number);
47
- const [requiredMajor, requiredMinor] = REQUIRED_NODE_VERSION.split(".").map(Number);
48
- if (currentMajor < requiredMajor || currentMajor === requiredMajor && currentMinor < requiredMinor) {
49
- logger2.error(
50
- `Node.js ${REQUIRED_NODE_VERSION} or higher is required. You have ${currentVersion}.`
51
- );
52
- logger2.info("Please upgrade Node.js: https://nodejs.org/");
53
- return false;
45
+ }
46
+ async function validateVercelToken(token) {
47
+ if (!token || token.length < 20) {
48
+ return {
49
+ valid: false,
50
+ message: "Vercel token appears invalid (too short)"
51
+ };
54
52
  }
55
- return true;
53
+ return { valid: true };
56
54
  }
57
- var logger2, REQUIRED_NODE_VERSION;
58
- var init_node_version = __esm({
59
- "src/validators/node-version.ts"() {
60
- "use strict";
61
- init_esm_shims();
62
- logger2 = createLogger2({ prefix: "Setup" });
63
- REQUIRED_NODE_VERSION = "24.12.0";
55
+ async function validateSupabaseUrl(url) {
56
+ try {
57
+ const parsed = new URL(url);
58
+ if (!parsed.hostname.includes("supabase")) {
59
+ logger.warn("URL does not appear to be a Supabase URL");
60
+ }
61
+ return { valid: true };
62
+ } catch {
63
+ return {
64
+ valid: false,
65
+ message: "Invalid Supabase URL format"
66
+ };
64
67
  }
65
- });
68
+ }
66
69
 
67
- // src/generators/devbox.ts
68
- import fs from "fs/promises";
69
- import path2 from "path";
70
- async function generateDevbox(projectPath) {
71
- const devboxConfig = {
72
- packages: ["nodejs@24.12.0", "pnpm@10.28.2", "postgresql@16", "stripe-cli@latest"],
73
- shell: {
74
- init_hook: [
75
- "corepack enable",
76
- 'echo "\u{1F680} RevealUI Devbox shell ready!"',
77
- 'echo "Run: pnpm dev to start development"'
70
+ // src/prompts/database.ts
71
+ async function promptDatabaseConfig() {
72
+ const { provider } = await inquirer.prompt([
73
+ {
74
+ type: "list",
75
+ name: "provider",
76
+ message: "Which database provider would you like to use?",
77
+ choices: [
78
+ {
79
+ name: "NeonDB - Serverless PostgreSQL (recommended)",
80
+ value: "neon"
81
+ },
82
+ {
83
+ name: "Supabase - PostgreSQL with built-in features",
84
+ value: "supabase"
85
+ },
86
+ {
87
+ name: "Local PostgreSQL - Use existing local database",
88
+ value: "local"
89
+ },
90
+ {
91
+ name: "Skip - Configure later",
92
+ value: "skip"
93
+ }
78
94
  ],
79
- scripts: {
80
- dev: "pnpm dev",
81
- setup: "pnpm install && pnpm db:init",
82
- test: "pnpm test"
95
+ default: "neon"
96
+ }
97
+ ]);
98
+ if (provider === "skip") {
99
+ return { provider: "skip" };
100
+ }
101
+ if (provider === "local") {
102
+ const { postgresUrl: postgresUrl2 } = await inquirer.prompt([
103
+ {
104
+ type: "input",
105
+ name: "postgresUrl",
106
+ message: "Enter your PostgreSQL connection string:",
107
+ default: "postgresql://postgres:postgres@localhost:5432/revealui",
108
+ validate: async (input) => {
109
+ const result = await validateNeonUrl(input);
110
+ return result.valid ? true : result.message || "Invalid database URL";
111
+ }
112
+ }
113
+ ]);
114
+ return { provider: "local", postgresUrl: postgresUrl2 };
115
+ }
116
+ const { postgresUrl } = await inquirer.prompt([
117
+ {
118
+ type: "input",
119
+ name: "postgresUrl",
120
+ message: `Enter your ${provider === "neon" ? "Neon" : "Supabase"} database connection string:`,
121
+ validate: async (input) => {
122
+ if (!input || input.trim() === "") {
123
+ return "Database URL is required";
124
+ }
125
+ const result = await validateNeonUrl(input);
126
+ return result.valid ? true : result.message || "Invalid database URL";
83
127
  }
128
+ }
129
+ ]);
130
+ return { provider, postgresUrl };
131
+ }
132
+
133
+ // src/prompts/devenv.ts
134
+ import inquirer2 from "inquirer";
135
+ async function promptDevEnvConfig() {
136
+ const answers = await inquirer2.prompt([
137
+ {
138
+ type: "confirm",
139
+ name: "createDevContainer",
140
+ message: "Create Dev Container configuration for VS Code / GitHub Codespaces?",
141
+ default: true
84
142
  },
85
- env: {
86
- NODE_ENV: "development"
143
+ {
144
+ type: "confirm",
145
+ name: "createDevbox",
146
+ message: "Create Devbox configuration for Nix-powered development?",
147
+ default: true
87
148
  }
88
- };
89
- await fs.writeFile(
90
- path2.join(projectPath, "devbox.json"),
91
- JSON.stringify(devboxConfig, null, 2),
92
- "utf-8"
93
- );
149
+ ]);
150
+ return answers;
94
151
  }
95
- var init_devbox = __esm({
96
- "src/generators/devbox.ts"() {
97
- "use strict";
98
- init_esm_shims();
99
- }
100
- });
101
152
 
102
- // src/generators/devcontainer.ts
103
- import fs2 from "fs/promises";
104
- import path3 from "path";
105
- async function generateDevContainer(projectPath) {
106
- const devcontainerDir = path3.join(projectPath, ".devcontainer");
107
- await fs2.mkdir(devcontainerDir, { recursive: true });
108
- const devcontainerConfig = {
109
- name: "RevealUI Development",
110
- image: "mcr.microsoft.com/devcontainers/typescript-node:24",
111
- features: {
112
- "ghcr.io/devcontainers/features/common-utils:2": {
113
- installZsh: true,
114
- installOhMyZsh: true
153
+ // src/prompts/payments.ts
154
+ import inquirer3 from "inquirer";
155
+ async function promptPaymentConfig() {
156
+ const { enabled } = await inquirer3.prompt([
157
+ {
158
+ type: "confirm",
159
+ name: "enabled",
160
+ message: "Do you want to configure Stripe payments?",
161
+ default: true
162
+ }
163
+ ]);
164
+ if (!enabled) {
165
+ return { enabled: false };
166
+ }
167
+ const answers = await inquirer3.prompt([
168
+ {
169
+ type: "input",
170
+ name: "stripeSecretKey",
171
+ message: "Enter your Stripe secret key (sk_test_... or sk_live_...):",
172
+ validate: async (input) => {
173
+ if (!input || input.trim() === "") {
174
+ return "Stripe secret key is required";
175
+ }
176
+ const result = await validateStripeKey(input);
177
+ return result.valid ? true : result.message || "Invalid Stripe key";
115
178
  }
116
179
  },
117
- forwardPorts: [3e3, 4e3, 5432],
118
- portsAttributes: {
119
- "3000": {
120
- label: "Web App",
121
- onAutoForward: "notify"
122
- },
123
- "4000": {
124
- label: "CMS",
125
- onAutoForward: "notify"
126
- },
127
- "5432": {
128
- label: "PostgreSQL",
129
- onAutoForward: "silent"
180
+ {
181
+ type: "input",
182
+ name: "stripePublishableKey",
183
+ message: "Enter your Stripe publishable key (pk_test_... or pk_live_...):",
184
+ validate: (input) => {
185
+ if (!input || input.trim() === "") {
186
+ return "Stripe publishable key is required";
187
+ }
188
+ if (!(input.startsWith("pk_test_") || input.startsWith("pk_live_"))) {
189
+ return "Stripe publishable key must start with pk_test_ or pk_live_";
190
+ }
191
+ return true;
130
192
  }
131
193
  },
132
- postCreateCommand: "corepack enable && pnpm install",
133
- customizations: {
134
- vscode: {
135
- extensions: [
136
- "dbaeumer.vscode-eslint",
137
- "biomejs.biome",
138
- "bradlc.vscode-tailwindcss",
139
- "Prisma.prisma",
140
- "ms-azuretools.vscode-docker",
141
- "streetsidesoftware.code-spell-checker"
142
- ],
143
- settings: {
144
- "editor.defaultFormatter": "biomejs.biome",
145
- "editor.formatOnSave": true,
146
- "editor.codeActionsOnSave": {
147
- "quickfix.biome": "explicit",
148
- "source.organizeImports.biome": "explicit"
149
- }
194
+ {
195
+ type: "input",
196
+ name: "stripeWebhookSecret",
197
+ message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
198
+ default: ""
199
+ }
200
+ ]);
201
+ return {
202
+ enabled: true,
203
+ stripeSecretKey: answers.stripeSecretKey,
204
+ stripePublishableKey: answers.stripePublishableKey,
205
+ stripeWebhookSecret: answers.stripeWebhookSecret || void 0
206
+ };
207
+ }
208
+
209
+ // src/prompts/project.ts
210
+ import fs from "fs";
211
+ import path from "path";
212
+ import inquirer4 from "inquirer";
213
+ var VALID_TEMPLATES = ["basic-blog", "e-commerce", "portfolio"];
214
+ async function promptProjectConfig(defaultName, templateArg, nonInteractive = false) {
215
+ let projectName;
216
+ if (defaultName) {
217
+ projectName = defaultName;
218
+ } else if (nonInteractive) {
219
+ projectName = "my-revealui-project";
220
+ } else {
221
+ const answers = await inquirer4.prompt([
222
+ {
223
+ type: "input",
224
+ name: "projectName",
225
+ message: "What is your project name?",
226
+ default: "my-revealui-project",
227
+ validate: (input) => {
228
+ if (!/^[a-z0-9-]+$/.test(input)) {
229
+ return "Project name must contain only lowercase letters, numbers, and hyphens";
230
+ }
231
+ const projectPath = path.resolve(process.cwd(), input);
232
+ if (fs.existsSync(projectPath)) {
233
+ return `Directory "${input}" already exists`;
234
+ }
235
+ return true;
236
+ }
237
+ }
238
+ ]);
239
+ projectName = answers.projectName;
240
+ }
241
+ let template;
242
+ if (templateArg && VALID_TEMPLATES.includes(templateArg)) {
243
+ template = templateArg;
244
+ } else if (nonInteractive) {
245
+ template = "basic-blog";
246
+ } else {
247
+ const answers = await inquirer4.prompt([
248
+ {
249
+ type: "list",
250
+ name: "template",
251
+ message: "Which template would you like to use?",
252
+ choices: [
253
+ { name: "Basic Blog - A simple blog with posts and pages", value: "basic-blog" },
254
+ { name: "E-commerce - Product catalog with checkout", value: "e-commerce" },
255
+ { name: "Portfolio - Personal portfolio site", value: "portfolio" }
256
+ ],
257
+ default: "basic-blog"
258
+ }
259
+ ]);
260
+ template = answers.template;
261
+ }
262
+ return {
263
+ projectName,
264
+ projectPath: path.resolve(process.cwd(), projectName),
265
+ template
266
+ };
267
+ }
268
+
269
+ // src/prompts/storage.ts
270
+ import inquirer5 from "inquirer";
271
+ async function promptStorageConfig() {
272
+ const { provider } = await inquirer5.prompt([
273
+ {
274
+ type: "list",
275
+ name: "provider",
276
+ message: "Which storage provider would you like to use?",
277
+ choices: [
278
+ {
279
+ name: "Vercel Blob - Simple object storage (recommended)",
280
+ value: "vercel-blob"
281
+ },
282
+ {
283
+ name: "Supabase Storage - Integrated with Supabase",
284
+ value: "supabase"
285
+ },
286
+ {
287
+ name: "Skip - Configure later",
288
+ value: "skip"
289
+ }
290
+ ],
291
+ default: "vercel-blob"
292
+ }
293
+ ]);
294
+ if (provider === "skip") {
295
+ return { provider: "skip" };
296
+ }
297
+ if (provider === "vercel-blob") {
298
+ const { blobToken } = await inquirer5.prompt([
299
+ {
300
+ type: "input",
301
+ name: "blobToken",
302
+ message: "Enter your Vercel Blob read/write token:",
303
+ validate: async (input) => {
304
+ if (!input || input.trim() === "") {
305
+ return "Blob token is required";
306
+ }
307
+ const result = await validateVercelToken(input);
308
+ return result.valid ? true : result.message || "Invalid token";
309
+ }
310
+ }
311
+ ]);
312
+ return { provider: "vercel-blob", blobToken };
313
+ }
314
+ const answers = await inquirer5.prompt([
315
+ {
316
+ type: "input",
317
+ name: "supabaseUrl",
318
+ message: "Enter your Supabase project URL:",
319
+ validate: async (input) => {
320
+ if (!input || input.trim() === "") {
321
+ return "Supabase URL is required";
150
322
  }
323
+ const result = await validateSupabaseUrl(input);
324
+ return result.valid ? true : result.message || "Invalid URL";
151
325
  }
152
326
  },
153
- remoteUser: "node"
327
+ {
328
+ type: "input",
329
+ name: "supabasePublishableKey",
330
+ message: "Enter your Supabase publishable key (sb_publishable_...):",
331
+ validate: (input) => {
332
+ if (!input || input.trim() === "") {
333
+ return "Supabase publishable key is required";
334
+ }
335
+ return true;
336
+ }
337
+ }
338
+ ]);
339
+ return {
340
+ provider: "supabase",
341
+ supabaseUrl: answers.supabaseUrl,
342
+ supabasePublishableKey: answers.supabasePublishableKey
343
+ };
344
+ }
345
+
346
+ // src/validators/node-version.ts
347
+ import { createLogger as createLogger2 } from "@revealui/setup/utils";
348
+ var logger2 = createLogger2({ prefix: "Setup" });
349
+ var REQUIRED_NODE_VERSION = "24.13.0";
350
+ function validateNodeVersion() {
351
+ const currentVersion = process.version.slice(1);
352
+ const [currentMajor, currentMinor] = currentVersion.split(".").map(Number);
353
+ const [requiredMajor, requiredMinor] = REQUIRED_NODE_VERSION.split(".").map(Number);
354
+ if (currentMajor < requiredMajor || currentMajor === requiredMajor && currentMinor < requiredMinor) {
355
+ logger2.error(
356
+ `Node.js ${REQUIRED_NODE_VERSION} or higher is required. You have ${currentVersion}.`
357
+ );
358
+ logger2.info("Please upgrade Node.js: https://nodejs.org/");
359
+ return false;
360
+ }
361
+ return true;
362
+ }
363
+
364
+ // src/commands/create.ts
365
+ import crypto from "crypto";
366
+ import fs5 from "fs/promises";
367
+ import path5 from "path";
368
+ import { fileURLToPath } from "url";
369
+ import { createLogger as createLogger5 } from "@revealui/setup/utils";
370
+ import ora2 from "ora";
371
+
372
+ // src/generators/devbox.ts
373
+ import fs2 from "fs/promises";
374
+ import path2 from "path";
375
+ async function generateDevbox(projectPath) {
376
+ const devboxConfig = {
377
+ packages: ["nodejs@24.13.0", "pnpm@10.28.2", "postgresql@16", "stripe-cli@latest"],
378
+ shell: {
379
+ init_hook: [
380
+ "corepack enable",
381
+ 'echo "\u{1F680} RevealUI Devbox shell ready!"',
382
+ 'echo "Run: pnpm dev to start development"'
383
+ ],
384
+ scripts: {
385
+ dev: "pnpm dev",
386
+ setup: "pnpm install && pnpm db:init",
387
+ test: "pnpm test"
388
+ }
389
+ },
390
+ env: {
391
+ NODE_ENV: "development"
392
+ }
154
393
  };
155
394
  await fs2.writeFile(
395
+ path2.join(projectPath, "devbox.json"),
396
+ JSON.stringify(devboxConfig, null, 2),
397
+ "utf-8"
398
+ );
399
+ }
400
+
401
+ // src/generators/devcontainer.ts
402
+ import fs3 from "fs/promises";
403
+ import path3 from "path";
404
+ async function generateDevContainer(projectPath) {
405
+ const devcontainerDir = path3.join(projectPath, ".devcontainer");
406
+ await fs3.mkdir(devcontainerDir, { recursive: true });
407
+ const devcontainerConfig = {
408
+ name: "RevealUI Development",
409
+ image: "mcr.microsoft.com/devcontainers/typescript-node:24",
410
+ features: {
411
+ "ghcr.io/devcontainers/features/common-utils:2": {
412
+ installZsh: true,
413
+ installOhMyZsh: true
414
+ }
415
+ },
416
+ forwardPorts: [3e3, 4e3, 5432],
417
+ portsAttributes: {
418
+ "3000": {
419
+ label: "Web App",
420
+ onAutoForward: "notify"
421
+ },
422
+ "4000": {
423
+ label: "CMS",
424
+ onAutoForward: "notify"
425
+ },
426
+ "5432": {
427
+ label: "PostgreSQL",
428
+ onAutoForward: "silent"
429
+ }
430
+ },
431
+ postCreateCommand: "corepack enable && pnpm install",
432
+ customizations: {
433
+ vscode: {
434
+ extensions: [
435
+ "biomejs.biome",
436
+ "bradlc.vscode-tailwindcss",
437
+ "Prisma.prisma",
438
+ "ms-azuretools.vscode-docker",
439
+ "streetsidesoftware.code-spell-checker"
440
+ ],
441
+ settings: {
442
+ "editor.defaultFormatter": "biomejs.biome",
443
+ "editor.formatOnSave": true,
444
+ "editor.codeActionsOnSave": {
445
+ "quickfix.biome": "explicit",
446
+ "source.organizeImports.biome": "explicit"
447
+ }
448
+ }
449
+ }
450
+ },
451
+ remoteUser: "node"
452
+ };
453
+ await fs3.writeFile(
156
454
  path3.join(devcontainerDir, "devcontainer.json"),
157
455
  JSON.stringify(devcontainerConfig, null, 2),
158
456
  "utf-8"
@@ -182,7 +480,7 @@ services:
182
480
  volumes:
183
481
  postgres-data:
184
482
  `;
185
- await fs2.writeFile(path3.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
483
+ await fs3.writeFile(path3.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
186
484
  const dockerfile = `FROM mcr.microsoft.com/devcontainers/typescript-node:24
187
485
 
188
486
  # Install additional tools
@@ -192,7 +490,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\
192
490
  # Enable pnpm
193
491
  RUN corepack enable
194
492
  `;
195
- await fs2.writeFile(path3.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
493
+ await fs3.writeFile(path3.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
196
494
  const readme = `# Dev Container Setup
197
495
 
198
496
  This directory contains the Dev Container configuration for RevealUI.
@@ -213,11 +511,10 @@ This directory contains the Dev Container configuration for RevealUI.
213
511
 
214
512
  ## What's Included
215
513
 
216
- - Node.js 24.12.0
514
+ - Node.js 24.13.0
217
515
  - pnpm package manager
218
516
  - PostgreSQL 16 with pgvector
219
517
  - VS Code extensions:
220
- - ESLint
221
518
  - Biome
222
519
  - Tailwind CSS
223
520
  - Prisma
@@ -235,17 +532,11 @@ For GitHub Codespaces, set secrets in your repository settings.
235
532
  - 4000: CMS
236
533
  - 5432: PostgreSQL database
237
534
  `;
238
- await fs2.writeFile(path3.join(devcontainerDir, "README.md"), readme, "utf-8");
535
+ await fs3.writeFile(path3.join(devcontainerDir, "README.md"), readme, "utf-8");
239
536
  }
240
- var init_devcontainer = __esm({
241
- "src/generators/devcontainer.ts"() {
242
- "use strict";
243
- init_esm_shims();
244
- }
245
- });
246
537
 
247
538
  // src/generators/readme.ts
248
- import fs3 from "fs/promises";
539
+ import fs4 from "fs/promises";
249
540
  import path4 from "path";
250
541
  async function generateReadme(projectPath, projectConfig) {
251
542
  const readme = `# ${projectConfig.projectName}
@@ -282,7 +573,7 @@ The web application runs on [http://localhost:3000](http://localhost:3000).
282
573
  ### Standard Setup
283
574
 
284
575
  Requirements:
285
- - Node.js 24.12.0 or higher
576
+ - Node.js 24.13.0 or higher
286
577
  - pnpm 10.28.2 or higher
287
578
  - PostgreSQL 16
288
579
 
@@ -346,19 +637,14 @@ This project was created using the **${projectConfig.template}** template.
346
637
 
347
638
  MIT
348
639
  `;
349
- await fs3.writeFile(path4.join(projectPath, "README.md"), readme, "utf-8");
640
+ await fs4.writeFile(path4.join(projectPath, "README.md"), readme, "utf-8");
350
641
  }
351
- var init_readme = __esm({
352
- "src/generators/readme.ts"() {
353
- "use strict";
354
- init_esm_shims();
355
- }
356
- });
357
642
 
358
643
  // src/installers/dependencies.ts
359
644
  import { createLogger as createLogger3 } from "@revealui/setup/utils";
360
645
  import { execa } from "execa";
361
646
  import ora from "ora";
647
+ var logger3 = createLogger3({ prefix: "Install" });
362
648
  async function installDependencies(projectPath) {
363
649
  const spinner = ora("Installing dependencies with pnpm...").start();
364
650
  try {
@@ -381,18 +667,11 @@ async function isPnpmInstalled() {
381
667
  return false;
382
668
  }
383
669
  }
384
- var logger3;
385
- var init_dependencies = __esm({
386
- "src/installers/dependencies.ts"() {
387
- "use strict";
388
- init_esm_shims();
389
- logger3 = createLogger3({ prefix: "Install" });
390
- }
391
- });
392
670
 
393
671
  // src/utils/git.ts
394
672
  import { createLogger as createLogger4 } from "@revealui/setup/utils";
395
673
  import { execa as execa2 } from "execa";
674
+ var logger4 = createLogger4({ prefix: "Git" });
396
675
  async function initializeGitRepo(projectPath) {
397
676
  try {
398
677
  await execa2("git", ["init"], { cwd: projectPath });
@@ -420,22 +699,12 @@ async function isGitInstalled() {
420
699
  return false;
421
700
  }
422
701
  }
423
- var logger4;
424
- var init_git = __esm({
425
- "src/utils/git.ts"() {
426
- "use strict";
427
- init_esm_shims();
428
- logger4 = createLogger4({ prefix: "Git" });
429
- }
430
- });
431
702
 
432
703
  // src/commands/create.ts
433
- import crypto from "crypto";
434
- import fs4 from "fs/promises";
435
- import path5 from "path";
436
- import { fileURLToPath as fileURLToPath2 } from "url";
437
- import { createLogger as createLogger5 } from "@revealui/setup/utils";
438
- import ora2 from "ora";
704
+ var logger5 = createLogger5({ prefix: "Create" });
705
+ var __filename2 = fileURLToPath(import.meta.url);
706
+ var __dirname2 = path5.dirname(__filename2);
707
+ var TEMPLATES_DIR = path5.resolve(__dirname2, "../../templates");
439
708
  function buildEnvLocal(cfg) {
440
709
  const lines = [
441
710
  "# Generated by @revealui/cli \u2014 fill in the remaining placeholders before running `pnpm dev`",
@@ -479,20 +748,28 @@ function buildEnvLocal(cfg) {
479
748
  function generateSecret() {
480
749
  return crypto.randomBytes(24).toString("hex");
481
750
  }
751
+ async function listAvailableTemplates() {
752
+ try {
753
+ const entries = await fs5.readdir(TEMPLATES_DIR, { withFileTypes: true });
754
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
755
+ } catch {
756
+ return [];
757
+ }
758
+ }
482
759
  async function copyTemplate(templateName, targetPath) {
483
760
  const templatePath = path5.join(TEMPLATES_DIR, templateName);
484
761
  try {
485
- await fs4.access(templatePath);
762
+ await fs5.access(templatePath);
486
763
  } catch {
487
- throw new Error(
488
- `Template "${templateName}" not found at ${templatePath}. Available templates: minimal, basic-blog, e-commerce, portfolio`
489
- );
764
+ const available = await listAvailableTemplates();
765
+ const listing = available.length > 0 ? `Available templates: ${available.join(", ")}` : `No templates found in ${TEMPLATES_DIR}`;
766
+ throw new Error(`Template "${templateName}" not found at ${templatePath}. ${listing}`);
490
767
  }
491
768
  await copyDir(templatePath, targetPath);
492
769
  }
493
770
  async function copyDir(src, dest) {
494
- await fs4.mkdir(dest, { recursive: true });
495
- const entries = await fs4.readdir(src, { withFileTypes: true });
771
+ await fs5.mkdir(dest, { recursive: true });
772
+ const entries = await fs5.readdir(src, { withFileTypes: true });
496
773
  for (const entry of entries) {
497
774
  const srcPath = path5.join(src, entry.name);
498
775
  const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
@@ -500,17 +777,17 @@ async function copyDir(src, dest) {
500
777
  if (entry.isDirectory()) {
501
778
  await copyDir(srcPath, destPath);
502
779
  } else {
503
- await fs4.copyFile(srcPath, destPath);
780
+ await fs5.copyFile(srcPath, destPath);
504
781
  }
505
782
  }
506
783
  }
507
784
  function resolveTemplateName(template) {
508
785
  const map = {
509
- "basic-blog": "minimal",
510
- "e-commerce": "minimal",
511
- portfolio: "minimal"
786
+ "basic-blog": "basic-blog",
787
+ "e-commerce": "e-commerce",
788
+ portfolio: "portfolio"
512
789
  };
513
- return map[template] ?? "minimal";
790
+ return map[template] ?? "starter";
514
791
  }
515
792
  async function createProject(cfg) {
516
793
  const { project, skipGit = false, skipInstall = false } = cfg;
@@ -525,13 +802,13 @@ async function createProject(cfg) {
525
802
  }
526
803
  const pkgJsonPath = path5.join(projectPath, "package.json");
527
804
  try {
528
- const raw = await fs4.readFile(pkgJsonPath, "utf-8");
529
- await fs4.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
805
+ const raw = await fs5.readFile(pkgJsonPath, "utf-8");
806
+ await fs5.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
530
807
  } catch {
531
808
  }
532
809
  const envSpinner = ora2("Writing .env.local...").start();
533
810
  try {
534
- await fs4.writeFile(path5.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
811
+ await fs5.writeFile(path5.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
535
812
  envSpinner.succeed(".env.local written");
536
813
  } catch (err) {
537
814
  envSpinner.fail("Failed to write .env.local");
@@ -550,7 +827,9 @@ async function createProject(cfg) {
550
827
  if (!skipInstall) {
551
828
  const pnpmOk = await isPnpmInstalled();
552
829
  if (!pnpmOk) {
553
- logger5.warn("pnpm not found \u2014 skipping dependency installation. Run `pnpm install` manually.");
830
+ logger5.warn(
831
+ "pnpm not found \u2014 skipping dependency installation. Run `pnpm install` manually."
832
+ );
554
833
  } else {
555
834
  await installDependencies(projectPath);
556
835
  }
@@ -569,393 +848,11 @@ async function createProject(cfg) {
569
848
  logger5.info("Skipping git initialisation (--skip-git)");
570
849
  }
571
850
  }
572
- var logger5, __filename2, __dirname2, TEMPLATES_DIR;
573
- var init_create = __esm({
574
- "src/commands/create.ts"() {
575
- "use strict";
576
- init_esm_shims();
577
- init_devbox();
578
- init_devcontainer();
579
- init_readme();
580
- init_dependencies();
581
- init_git();
582
- logger5 = createLogger5({ prefix: "Create" });
583
- __filename2 = fileURLToPath2(import.meta.url);
584
- __dirname2 = path5.dirname(__filename2);
585
- TEMPLATES_DIR = path5.resolve(__dirname2, "../../templates");
586
- }
587
- });
588
851
 
589
- // src/validators/credentials.ts
590
- import { createLogger as createLogger6 } from "@revealui/setup/utils";
591
- async function validateStripeKey(key) {
592
- if (!(key.startsWith("sk_test_") || key.startsWith("sk_live_"))) {
593
- return {
594
- valid: false,
595
- message: "Stripe key must start with sk_test_ or sk_live_"
596
- };
597
- }
598
- return { valid: true };
599
- }
600
- async function validateNeonUrl(url) {
601
- try {
602
- const parsed = new URL(url);
603
- if (!parsed.protocol.startsWith("postgres")) {
604
- return {
605
- valid: false,
606
- message: "Database URL must use postgres:// or postgresql:// protocol"
607
- };
608
- }
609
- return { valid: true };
610
- } catch {
611
- return {
612
- valid: false,
613
- message: "Invalid database URL format"
614
- };
615
- }
616
- }
617
- async function validateVercelToken(token) {
618
- if (!token || token.length < 20) {
619
- return {
620
- valid: false,
621
- message: "Vercel token appears invalid (too short)"
622
- };
623
- }
624
- return { valid: true };
625
- }
626
- async function validateSupabaseUrl(url) {
627
- try {
628
- const parsed = new URL(url);
629
- if (!parsed.hostname.includes("supabase")) {
630
- logger6.warn("URL does not appear to be a Supabase URL");
631
- }
632
- return { valid: true };
633
- } catch {
634
- return {
635
- valid: false,
636
- message: "Invalid Supabase URL format"
637
- };
638
- }
639
- }
640
- var logger6;
641
- var init_credentials = __esm({
642
- "src/validators/credentials.ts"() {
643
- "use strict";
644
- init_esm_shims();
645
- logger6 = createLogger6({ prefix: "Validator" });
646
- }
647
- });
648
-
649
- // src/prompts/database.ts
650
- import inquirer from "inquirer";
651
- async function promptDatabaseConfig() {
652
- const { provider } = await inquirer.prompt([
653
- {
654
- type: "list",
655
- name: "provider",
656
- message: "Which database provider would you like to use?",
657
- choices: [
658
- {
659
- name: "NeonDB - Serverless PostgreSQL (recommended)",
660
- value: "neon"
661
- },
662
- {
663
- name: "Supabase - PostgreSQL with built-in features",
664
- value: "supabase"
665
- },
666
- {
667
- name: "Local PostgreSQL - Use existing local database",
668
- value: "local"
669
- },
670
- {
671
- name: "Skip - Configure later",
672
- value: "skip"
673
- }
674
- ],
675
- default: "neon"
676
- }
677
- ]);
678
- if (provider === "skip") {
679
- return { provider: "skip" };
680
- }
681
- if (provider === "local") {
682
- const { postgresUrl: postgresUrl2 } = await inquirer.prompt([
683
- {
684
- type: "input",
685
- name: "postgresUrl",
686
- message: "Enter your PostgreSQL connection string:",
687
- default: "postgresql://postgres:postgres@localhost:5432/revealui",
688
- validate: async (input) => {
689
- const result = await validateNeonUrl(input);
690
- return result.valid ? true : result.message || "Invalid database URL";
691
- }
692
- }
693
- ]);
694
- return { provider: "local", postgresUrl: postgresUrl2 };
695
- }
696
- const { postgresUrl } = await inquirer.prompt([
697
- {
698
- type: "input",
699
- name: "postgresUrl",
700
- message: `Enter your ${provider === "neon" ? "Neon" : "Supabase"} database connection string:`,
701
- validate: async (input) => {
702
- if (!input || input.trim() === "") {
703
- return "Database URL is required";
704
- }
705
- const result = await validateNeonUrl(input);
706
- return result.valid ? true : result.message || "Invalid database URL";
707
- }
708
- }
709
- ]);
710
- return { provider, postgresUrl };
711
- }
712
- var init_database = __esm({
713
- "src/prompts/database.ts"() {
714
- "use strict";
715
- init_esm_shims();
716
- init_credentials();
717
- }
718
- });
719
-
720
- // src/prompts/devenv.ts
721
- import inquirer2 from "inquirer";
722
- async function promptDevEnvConfig() {
723
- const answers = await inquirer2.prompt([
724
- {
725
- type: "confirm",
726
- name: "createDevContainer",
727
- message: "Create Dev Container configuration for VS Code / GitHub Codespaces?",
728
- default: true
729
- },
730
- {
731
- type: "confirm",
732
- name: "createDevbox",
733
- message: "Create Devbox configuration for Nix-powered development?",
734
- default: true
735
- }
736
- ]);
737
- return answers;
738
- }
739
- var init_devenv = __esm({
740
- "src/prompts/devenv.ts"() {
741
- "use strict";
742
- init_esm_shims();
743
- }
744
- });
745
-
746
- // src/prompts/payments.ts
747
- import inquirer3 from "inquirer";
748
- async function promptPaymentConfig() {
749
- const { enabled } = await inquirer3.prompt([
750
- {
751
- type: "confirm",
752
- name: "enabled",
753
- message: "Do you want to configure Stripe payments?",
754
- default: true
755
- }
756
- ]);
757
- if (!enabled) {
758
- return { enabled: false };
759
- }
760
- const answers = await inquirer3.prompt([
761
- {
762
- type: "input",
763
- name: "stripeSecretKey",
764
- message: "Enter your Stripe secret key (sk_test_... or sk_live_...):",
765
- validate: async (input) => {
766
- if (!input || input.trim() === "") {
767
- return "Stripe secret key is required";
768
- }
769
- const result = await validateStripeKey(input);
770
- return result.valid ? true : result.message || "Invalid Stripe key";
771
- }
772
- },
773
- {
774
- type: "input",
775
- name: "stripePublishableKey",
776
- message: "Enter your Stripe publishable key (pk_test_... or pk_live_...):",
777
- validate: (input) => {
778
- if (!input || input.trim() === "") {
779
- return "Stripe publishable key is required";
780
- }
781
- if (!(input.startsWith("pk_test_") || input.startsWith("pk_live_"))) {
782
- return "Stripe publishable key must start with pk_test_ or pk_live_";
783
- }
784
- return true;
785
- }
786
- },
787
- {
788
- type: "input",
789
- name: "stripeWebhookSecret",
790
- message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
791
- default: ""
792
- }
793
- ]);
794
- return {
795
- enabled: true,
796
- stripeSecretKey: answers.stripeSecretKey,
797
- stripePublishableKey: answers.stripePublishableKey,
798
- stripeWebhookSecret: answers.stripeWebhookSecret || void 0
799
- };
800
- }
801
- var init_payments = __esm({
802
- "src/prompts/payments.ts"() {
803
- "use strict";
804
- init_esm_shims();
805
- init_credentials();
806
- }
807
- });
808
-
809
- // src/prompts/project.ts
810
- import fs5 from "fs";
811
- import path6 from "path";
812
- import inquirer4 from "inquirer";
813
- async function promptProjectConfig(defaultName) {
814
- const answers = await inquirer4.prompt([
815
- {
816
- type: "input",
817
- name: "projectName",
818
- message: "What is your project name?",
819
- default: defaultName || "my-revealui-project",
820
- validate: (input) => {
821
- if (!/^[a-z0-9-]+$/.test(input)) {
822
- return "Project name must contain only lowercase letters, numbers, and hyphens";
823
- }
824
- const projectPath = path6.resolve(process.cwd(), input);
825
- if (fs5.existsSync(projectPath)) {
826
- return `Directory "${input}" already exists`;
827
- }
828
- return true;
829
- }
830
- },
831
- {
832
- type: "list",
833
- name: "template",
834
- message: "Which template would you like to use?",
835
- choices: [
836
- {
837
- name: "Basic Blog - A simple blog with posts and pages",
838
- value: "basic-blog"
839
- },
840
- {
841
- name: "E-commerce - Product catalog with checkout",
842
- value: "e-commerce"
843
- },
844
- {
845
- name: "Portfolio - Personal portfolio site",
846
- value: "portfolio"
847
- }
848
- ],
849
- default: "basic-blog"
850
- }
851
- ]);
852
- return {
853
- projectName: answers.projectName,
854
- projectPath: path6.resolve(process.cwd(), answers.projectName),
855
- template: answers.template
856
- };
857
- }
858
- var init_project = __esm({
859
- "src/prompts/project.ts"() {
860
- "use strict";
861
- init_esm_shims();
862
- }
863
- });
864
-
865
- // src/prompts/storage.ts
866
- import inquirer5 from "inquirer";
867
- async function promptStorageConfig() {
868
- const { provider } = await inquirer5.prompt([
869
- {
870
- type: "list",
871
- name: "provider",
872
- message: "Which storage provider would you like to use?",
873
- choices: [
874
- {
875
- name: "Vercel Blob - Simple object storage (recommended)",
876
- value: "vercel-blob"
877
- },
878
- {
879
- name: "Supabase Storage - Integrated with Supabase",
880
- value: "supabase"
881
- },
882
- {
883
- name: "Skip - Configure later",
884
- value: "skip"
885
- }
886
- ],
887
- default: "vercel-blob"
888
- }
889
- ]);
890
- if (provider === "skip") {
891
- return { provider: "skip" };
892
- }
893
- if (provider === "vercel-blob") {
894
- const { blobToken } = await inquirer5.prompt([
895
- {
896
- type: "input",
897
- name: "blobToken",
898
- message: "Enter your Vercel Blob read/write token:",
899
- validate: async (input) => {
900
- if (!input || input.trim() === "") {
901
- return "Blob token is required";
902
- }
903
- const result = await validateVercelToken(input);
904
- return result.valid ? true : result.message || "Invalid token";
905
- }
906
- }
907
- ]);
908
- return { provider: "vercel-blob", blobToken };
909
- }
910
- const answers = await inquirer5.prompt([
911
- {
912
- type: "input",
913
- name: "supabaseUrl",
914
- message: "Enter your Supabase project URL:",
915
- validate: async (input) => {
916
- if (!input || input.trim() === "") {
917
- return "Supabase URL is required";
918
- }
919
- const result = await validateSupabaseUrl(input);
920
- return result.valid ? true : result.message || "Invalid URL";
921
- }
922
- },
923
- {
924
- type: "input",
925
- name: "supabaseAnonKey",
926
- message: "Enter your Supabase anonymous key:",
927
- validate: (input) => {
928
- if (!input || input.trim() === "") {
929
- return "Supabase anonymous key is required";
930
- }
931
- return true;
932
- }
933
- }
934
- ]);
935
- return {
936
- provider: "supabase",
937
- supabaseUrl: answers.supabaseUrl,
938
- supabaseAnonKey: answers.supabaseAnonKey
939
- };
940
- }
941
- var init_storage = __esm({
942
- "src/prompts/storage.ts"() {
943
- "use strict";
944
- init_esm_shims();
945
- init_credentials();
946
- }
947
- });
948
-
949
- // src/index.ts
950
- var index_exports = {};
951
- __export(index_exports, {
952
- run: () => run
953
- });
954
- import { readFileSync } from "fs";
955
- import { homedir } from "os";
956
- import { join } from "path";
957
- import { createLogger as createLogger7 } from "@revealui/setup/utils";
958
- function checkProLicense() {
852
+ // src/commands/create-flow.ts
853
+ var logger6 = createLogger6({ prefix: "@revealui/cli" });
854
+ var PRO_TEMPLATES = /* @__PURE__ */ new Set([]);
855
+ async function checkProLicense() {
959
856
  let key = process.env.REVEALUI_LICENSE_KEY;
960
857
  if (!key) {
961
858
  try {
@@ -966,111 +863,941 @@ function checkProLicense() {
966
863
  }
967
864
  }
968
865
  if (!key) return false;
866
+ const publicKeyPem = process.env.REVEALUI_LICENSE_PUBLIC_KEY;
867
+ if (publicKeyPem) {
868
+ try {
869
+ const publicKey = await importSPKI(publicKeyPem, "RS256");
870
+ const { payload } = await jwtVerify(key, publicKey);
871
+ const tier = payload.tier ?? "free";
872
+ return tier === "pro" || tier === "enterprise";
873
+ } catch {
874
+ return false;
875
+ }
876
+ }
969
877
  try {
970
878
  const parts = key.split(".");
971
- if (parts.length < 2) return false;
972
- const payload = JSON.parse(Buffer.from(parts[1] ?? "", "base64").toString("utf8"));
879
+ if (parts.length < 3) return false;
880
+ const payload = JSON.parse(Buffer.from(parts[1] ?? "", "base64url").toString("utf8"));
881
+ if (payload.exp !== void 0 && payload.exp < Math.floor(Date.now() / 1e3)) {
882
+ return false;
883
+ }
973
884
  const tier = payload.tier ?? "free";
974
885
  return tier === "pro" || tier === "enterprise";
975
886
  } catch {
976
887
  return false;
977
888
  }
978
889
  }
979
- async function run(projectName, _options) {
980
- try {
981
- logger7.info("[1/8] Validating Node.js version...");
982
- if (!validateNodeVersion()) {
983
- process.exit(1);
890
+ async function runCreateFlow(projectName, options) {
891
+ logger6.info("[1/8] Validating Node.js version...");
892
+ if (!validateNodeVersion()) {
893
+ process.exit(1);
894
+ }
895
+ logger6.success(`Node.js version: ${process.version}`);
896
+ logger6.info("[2/8] Configure your project");
897
+ const projectConfig = await promptProjectConfig(projectName, options.template, options.yes);
898
+ logger6.success(`Project: ${projectConfig.projectName}`);
899
+ logger6.success(`Template: ${projectConfig.template}`);
900
+ if (PRO_TEMPLATES.has(projectConfig.template)) {
901
+ if (!await checkProLicense()) {
902
+ logger6.error(`The "${projectConfig.template}" template requires a RevealUI Pro license.`);
903
+ logger6.info("Get Pro at https://revealui.com/pricing");
904
+ logger6.info("Set your license key: export REVEALUI_LICENSE_KEY=<your-key>");
905
+ process.exit(2);
984
906
  }
985
- logger7.success(`Node.js version: ${process.version}`);
986
- logger7.info("[2/8] Configure your project");
987
- const projectConfig = await promptProjectConfig(projectName);
988
- logger7.success(`Project: ${projectConfig.projectName}`);
989
- logger7.success(`Template: ${projectConfig.template}`);
990
- if (PRO_TEMPLATES.has(projectConfig.template)) {
991
- if (!checkProLicense()) {
992
- logger7.error(`The "${projectConfig.template}" template requires a RevealUI Pro license.`);
993
- logger7.info("Get Pro at https://revealui.com/pricing");
994
- logger7.info("Set your license key: export REVEALUI_LICENSE_KEY=<your-key>");
995
- process.exit(2);
907
+ logger6.success("Pro license verified");
908
+ }
909
+ const nonInteractive = options.yes === true;
910
+ logger6.info("[3/8] Configure database");
911
+ const databaseConfig = nonInteractive ? { provider: "skip" } : await promptDatabaseConfig();
912
+ if (databaseConfig.provider !== "skip") {
913
+ logger6.success(`Database: ${databaseConfig.provider}`);
914
+ } else {
915
+ logger6.info("Database configuration skipped");
916
+ }
917
+ logger6.info("[4/8] Configure storage");
918
+ const storageConfig = nonInteractive ? { provider: "skip" } : await promptStorageConfig();
919
+ if (storageConfig.provider !== "skip") {
920
+ logger6.success(`Storage: ${storageConfig.provider}`);
921
+ } else {
922
+ logger6.info("Storage configuration skipped");
923
+ }
924
+ logger6.info("[5/8] Configure payments");
925
+ const paymentConfig = nonInteractive ? { enabled: false } : await promptPaymentConfig();
926
+ if (paymentConfig.enabled) {
927
+ logger6.success("Stripe configured");
928
+ } else {
929
+ logger6.info("Payments disabled");
930
+ }
931
+ logger6.info("[6/8] Configure development environment");
932
+ const devEnvConfig = nonInteractive ? { createDevContainer: false, createDevbox: false } : await promptDevEnvConfig();
933
+ logger6.success(
934
+ `Dev Container: ${devEnvConfig.createDevContainer ? "Yes" : "No"}, Devbox: ${devEnvConfig.createDevbox ? "Yes" : "No"}`
935
+ );
936
+ logger6.info("[7/8] Creating project...");
937
+ await createProject({
938
+ project: projectConfig,
939
+ database: databaseConfig,
940
+ storage: storageConfig,
941
+ payment: paymentConfig,
942
+ devenv: devEnvConfig,
943
+ skipGit: options.skipGit,
944
+ skipInstall: options.skipInstall
945
+ });
946
+ logger6.success("Project created successfully");
947
+ logger6.info("[8/8] Next steps");
948
+ logger6.divider();
949
+ logger6.info(`cd ${projectConfig.projectName}`);
950
+ logger6.info("pnpm install");
951
+ logger6.info("pnpm dev");
952
+ logger6.divider();
953
+ logger6.success(`Project ${projectConfig.projectName} created successfully`);
954
+ }
955
+
956
+ // src/commands/db.ts
957
+ import fs8 from "fs/promises";
958
+ import path8 from "path";
959
+ import { createLogger as createLogger7 } from "@revealui/setup/utils";
960
+ import { execa as execa4 } from "execa";
961
+
962
+ // src/utils/command.ts
963
+ import net from "net";
964
+ import { execa as execa3 } from "execa";
965
+ async function commandExists(command) {
966
+ try {
967
+ await execa3("bash", ["-lc", `command -v ${command}`], {
968
+ stdio: "pipe"
969
+ });
970
+ return true;
971
+ } catch {
972
+ return false;
973
+ }
974
+ }
975
+ async function isTcpReachable(host, port, timeoutMs = 1500) {
976
+ return await new Promise((resolve) => {
977
+ const socket = new net.Socket();
978
+ const finalize = (value) => {
979
+ socket.removeAllListeners();
980
+ socket.destroy();
981
+ resolve(value);
982
+ };
983
+ socket.setTimeout(timeoutMs);
984
+ socket.once("connect", () => finalize(true));
985
+ socket.once("timeout", () => finalize(false));
986
+ socket.once("error", () => finalize(false));
987
+ socket.connect(port, host);
988
+ });
989
+ }
990
+
991
+ // src/utils/db.ts
992
+ import fs6 from "fs/promises";
993
+ import path6 from "path";
994
+ function resolveLocalDbConfig(cwd = process.cwd(), env = process.env) {
995
+ const pgdata = env.PGDATA || path6.join(cwd, ".pgdata");
996
+ const pghost = env.PGHOST || pgdata;
997
+ const pgdatabase = env.PGDATABASE || "postgres";
998
+ const pguser = env.PGUSER || "postgres";
999
+ const postgresUrl = env.POSTGRES_URL || "postgresql://postgres@localhost:5432/postgres";
1000
+ const databaseUrl = env.DATABASE_URL || postgresUrl;
1001
+ return {
1002
+ pgdata,
1003
+ pghost,
1004
+ pgdatabase,
1005
+ pguser,
1006
+ postgresUrl,
1007
+ databaseUrl
1008
+ };
1009
+ }
1010
+ function isLocalDbUrl(url) {
1011
+ if (!url) return false;
1012
+ try {
1013
+ const parsed = new URL(url);
1014
+ return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
1015
+ } catch {
1016
+ return false;
1017
+ }
1018
+ }
1019
+ function detectDbTarget(url) {
1020
+ if (!url) return "missing";
1021
+ return isLocalDbUrl(url) ? "local" : "remote";
1022
+ }
1023
+ function buildPostgresConfig(pgdata) {
1024
+ return `
1025
+ # RevealUI Development Settings
1026
+ listen_addresses = 'localhost'
1027
+ port = 5432
1028
+ max_connections = 100
1029
+ shared_buffers = 128MB
1030
+ unix_socket_directories = '${pgdata}'
1031
+ `.trimStart();
1032
+ }
1033
+ function buildPgHbaConfig() {
1034
+ return `
1035
+ # TYPE DATABASE USER ADDRESS METHOD
1036
+ local all all trust
1037
+ host all all 127.0.0.1/32 trust
1038
+ host all all ::1/128 trust
1039
+ `.trimStart();
1040
+ }
1041
+ async function writeLocalDbConfigs(pgdata) {
1042
+ await fs6.appendFile(path6.join(pgdata, "postgresql.conf"), buildPostgresConfig(pgdata), "utf8");
1043
+ await fs6.writeFile(path6.join(pgdata, "pg_hba.conf"), buildPgHbaConfig(), "utf8");
1044
+ }
1045
+
1046
+ // src/utils/workspace.ts
1047
+ import fs7 from "fs";
1048
+ import path7 from "path";
1049
+ function findWorkspaceRoot(startDir = process.cwd()) {
1050
+ let current = path7.resolve(startDir);
1051
+ while (true) {
1052
+ const packageJsonPath = path7.join(current, "package.json");
1053
+ if (fs7.existsSync(packageJsonPath)) {
1054
+ try {
1055
+ const pkg = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
1056
+ if (pkg.name === "revealui" && pkg.private === true) {
1057
+ return current;
1058
+ }
1059
+ } catch {
996
1060
  }
997
- logger7.success("Pro license verified");
998
1061
  }
999
- logger7.info("[3/8] Configure database");
1000
- const databaseConfig = await promptDatabaseConfig();
1001
- if (databaseConfig.provider !== "skip") {
1002
- logger7.success(`Database: ${databaseConfig.provider}`);
1003
- } else {
1004
- logger7.info("Database configuration skipped");
1062
+ const parent = path7.dirname(current);
1063
+ if (parent === current) {
1064
+ return null;
1005
1065
  }
1006
- logger7.info("[4/8] Configure storage");
1007
- const storageConfig = await promptStorageConfig();
1008
- if (storageConfig.provider !== "skip") {
1009
- logger7.success(`Storage: ${storageConfig.provider}`);
1010
- } else {
1011
- logger7.info("Storage configuration skipped");
1066
+ current = parent;
1067
+ }
1068
+ }
1069
+
1070
+ // src/commands/db.ts
1071
+ var logger7 = createLogger7({ prefix: "DB" });
1072
+ function isPgCtlNotRunningError(error) {
1073
+ return typeof error === "object" && error !== null && "exitCode" in error && error.exitCode === 3;
1074
+ }
1075
+ function getWorkspaceRootOrThrow() {
1076
+ const root = findWorkspaceRoot();
1077
+ if (!root) {
1078
+ throw new Error("RevealUI workspace root not found");
1079
+ }
1080
+ return root;
1081
+ }
1082
+ function buildDbEnv(root) {
1083
+ const config = resolveLocalDbConfig(root, process.env);
1084
+ return {
1085
+ ...process.env,
1086
+ PGDATA: config.pgdata,
1087
+ PGHOST: config.pghost,
1088
+ PGDATABASE: config.pgdatabase,
1089
+ PGUSER: config.pguser,
1090
+ POSTGRES_URL: config.postgresUrl,
1091
+ DATABASE_URL: config.databaseUrl
1092
+ };
1093
+ }
1094
+ function getPgDataOrThrow(env) {
1095
+ const pgdata = env.PGDATA;
1096
+ if (!pgdata) {
1097
+ throw new Error("PGDATA is not configured for the local RevealUI database");
1098
+ }
1099
+ return pgdata;
1100
+ }
1101
+ async function requireCommand(command) {
1102
+ if (!await commandExists(command)) {
1103
+ throw new Error(`${command} is not available in PATH`);
1104
+ }
1105
+ }
1106
+ async function runDbInitCommand(options = {}) {
1107
+ const root = getWorkspaceRootOrThrow();
1108
+ const env = buildDbEnv(root);
1109
+ const pgdata = getPgDataOrThrow(env);
1110
+ await requireCommand("initdb");
1111
+ try {
1112
+ await fs8.access(pgdata);
1113
+ if (!options.force) {
1114
+ throw new Error(`PostgreSQL is already initialized at ${pgdata}. Use --force to reset it.`);
1012
1115
  }
1013
- logger7.info("[5/8] Configure payments");
1014
- const paymentConfig = await promptPaymentConfig();
1015
- if (paymentConfig.enabled) {
1016
- logger7.success("Stripe configured");
1017
- } else {
1018
- logger7.info("Payments disabled");
1116
+ await fs8.rm(pgdata, { recursive: true, force: true });
1117
+ } catch (error) {
1118
+ if (error instanceof Error && !error.message.includes("already initialized")) {
1119
+ } else if (error instanceof Error) {
1120
+ throw error;
1019
1121
  }
1020
- logger7.info("[6/8] Configure development environment");
1021
- const devEnvConfig = await promptDevEnvConfig();
1022
- logger7.success(
1023
- `Dev Container: ${devEnvConfig.createDevContainer ? "Yes" : "No"}, Devbox: ${devEnvConfig.createDevbox ? "Yes" : "No"}`
1024
- );
1025
- logger7.info("[7/8] Creating project...");
1026
- await createProject({
1027
- project: projectConfig,
1028
- database: databaseConfig,
1029
- storage: storageConfig,
1030
- payment: paymentConfig,
1031
- devenv: devEnvConfig,
1032
- skipGit: _options.skipGit,
1033
- skipInstall: _options.skipInstall
1122
+ }
1123
+ await execa4(
1124
+ "initdb",
1125
+ ["--locale=C.UTF-8", "--encoding=UTF8", "-D", pgdata, "--username=postgres"],
1126
+ {
1127
+ env,
1128
+ stdio: "inherit"
1129
+ }
1130
+ );
1131
+ await writeLocalDbConfigs(pgdata);
1132
+ logger7.success(`PostgreSQL initialized at ${pgdata}`);
1133
+ }
1134
+ async function runDbStartCommand() {
1135
+ const root = getWorkspaceRootOrThrow();
1136
+ const env = buildDbEnv(root);
1137
+ const pgdata = getPgDataOrThrow(env);
1138
+ await requireCommand("pg_ctl");
1139
+ await fs8.access(pgdata);
1140
+ await execa4(
1141
+ "pg_ctl",
1142
+ ["start", "-D", pgdata, "-l", path8.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
1143
+ {
1144
+ env,
1145
+ stdio: "inherit"
1146
+ }
1147
+ );
1148
+ }
1149
+ async function runDbStopCommand() {
1150
+ const root = getWorkspaceRootOrThrow();
1151
+ const env = buildDbEnv(root);
1152
+ const pgdata = getPgDataOrThrow(env);
1153
+ await requireCommand("pg_ctl");
1154
+ await execa4("pg_ctl", ["stop", "-D", pgdata], {
1155
+ env,
1156
+ stdio: "inherit"
1157
+ });
1158
+ }
1159
+ async function runDbStatusCommand() {
1160
+ const root = getWorkspaceRootOrThrow();
1161
+ const env = buildDbEnv(root);
1162
+ const pgdata = getPgDataOrThrow(env);
1163
+ await requireCommand("pg_ctl");
1164
+ try {
1165
+ await execa4("pg_ctl", ["status", "-D", pgdata], {
1166
+ env,
1167
+ stdio: "inherit"
1034
1168
  });
1035
- logger7.success("Project created successfully");
1036
- logger7.info("[8/8] Next steps");
1037
- logger7.divider();
1038
- logger7.info(`cd ${projectConfig.projectName}`);
1039
- logger7.info("pnpm install");
1040
- logger7.info("pnpm dev");
1041
- logger7.divider();
1042
- logger7.success(`\u{1F389} Project ${projectConfig.projectName} created successfully!`);
1043
1169
  } catch (error) {
1044
- if (error instanceof Error) {
1045
- logger7.error(`Failed to create project: ${error.message}`);
1170
+ if (isPgCtlNotRunningError(error)) {
1171
+ logger7.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
1172
+ return;
1173
+ }
1174
+ throw error;
1175
+ }
1176
+ }
1177
+ async function runDbResetCommand(options = {}) {
1178
+ if (!options.force) {
1179
+ throw new Error("db reset is destructive. Re-run with --force.");
1180
+ }
1181
+ const root = getWorkspaceRootOrThrow();
1182
+ const env = buildDbEnv(root);
1183
+ const pgdata = getPgDataOrThrow(env);
1184
+ if (await commandExists("pg_ctl")) {
1185
+ try {
1186
+ await execa4("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
1187
+ } catch {
1188
+ }
1189
+ }
1190
+ await fs8.rm(pgdata, { recursive: true, force: true });
1191
+ await runDbInitCommand({ force: false });
1192
+ }
1193
+ async function runDbMigrateCommand() {
1194
+ const root = getWorkspaceRootOrThrow();
1195
+ const env = buildDbEnv(root);
1196
+ await execa4("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
1197
+ cwd: root,
1198
+ env,
1199
+ stdio: "inherit"
1200
+ });
1201
+ }
1202
+ async function runDbCleanupCommand(options = {}) {
1203
+ const root = getWorkspaceRootOrThrow();
1204
+ const env = { ...process.env };
1205
+ if (options.dryRun) env.DRY_RUN = "true";
1206
+ if (options.tables) env.TABLES = options.tables;
1207
+ await execa4("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
1208
+ cwd: root,
1209
+ env,
1210
+ stdio: "inherit"
1211
+ });
1212
+ }
1213
+
1214
+ // src/commands/dev.ts
1215
+ import { createLogger as createLogger10 } from "@revealui/setup/utils";
1216
+ import { execa as execa6 } from "execa";
1217
+
1218
+ // src/runtime/doctor.ts
1219
+ function getMcpDetail(env) {
1220
+ const configuredKeys = [
1221
+ env.VERCEL_API_KEY ? "vercel" : null,
1222
+ env.STRIPE_SECRET_KEY ? "stripe" : null
1223
+ ].filter((value) => value !== null);
1224
+ if (configuredKeys.length === 0) {
1225
+ return {
1226
+ ok: false,
1227
+ detail: "mcp credentials missing (set VERCEL_API_KEY and/or STRIPE_SECRET_KEY)"
1228
+ };
1229
+ }
1230
+ return {
1231
+ ok: true,
1232
+ detail: `mcp credentials configured for ${configuredKeys.join(", ")}`
1233
+ };
1234
+ }
1235
+ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
1236
+ const workspaceRoot = findWorkspaceRoot(cwd);
1237
+ const dbConfig = resolveLocalDbConfig(workspaceRoot ?? cwd, env);
1238
+ const dbTarget = detectDbTarget(env.POSTGRES_URL || env.DATABASE_URL);
1239
+ const nodeOk = validateNodeVersion();
1240
+ const pnpmOk = await commandExists("pnpm");
1241
+ const nixOk = await commandExists("nix");
1242
+ const direnvActive = Boolean(env.DIRENV_FILE || env.DIRENV_DIR);
1243
+ const pgCtlOk = await commandExists("pg_ctl");
1244
+ const initdbOk = await commandExists("initdb");
1245
+ const dockerOk = await commandExists("docker");
1246
+ const postgresReachable = dbTarget === "local" ? await isTcpReachable("127.0.0.1", 5432, 1e3) : false;
1247
+ const mcp = getMcpDetail(env);
1248
+ return {
1249
+ workspaceRoot,
1250
+ dbTarget,
1251
+ checks: [
1252
+ {
1253
+ id: "workspace",
1254
+ ok: workspaceRoot !== null,
1255
+ detail: workspaceRoot ?? "RevealUI workspace root not found"
1256
+ },
1257
+ {
1258
+ id: "node",
1259
+ ok: nodeOk,
1260
+ detail: process.version
1261
+ },
1262
+ {
1263
+ id: "pnpm",
1264
+ ok: pnpmOk,
1265
+ detail: pnpmOk ? "pnpm available" : "pnpm not found"
1266
+ },
1267
+ {
1268
+ id: "nix",
1269
+ ok: nixOk,
1270
+ detail: nixOk ? "nix available" : "nix not found"
1271
+ },
1272
+ {
1273
+ id: "direnv",
1274
+ ok: direnvActive,
1275
+ detail: direnvActive ? "direnv active" : "direnv not active"
1276
+ },
1277
+ {
1278
+ id: "db-target",
1279
+ ok: dbTarget !== "missing",
1280
+ detail: dbTarget === "missing" ? "No POSTGRES_URL or DATABASE_URL configured" : `${dbTarget} database target (${dbConfig.postgresUrl})`
1281
+ },
1282
+ {
1283
+ id: "pg_ctl",
1284
+ ok: pgCtlOk,
1285
+ detail: pgCtlOk ? "pg_ctl available" : "pg_ctl not found"
1286
+ },
1287
+ {
1288
+ id: "initdb",
1289
+ ok: initdbOk,
1290
+ detail: initdbOk ? "initdb available" : "initdb not found"
1291
+ },
1292
+ {
1293
+ id: "postgres",
1294
+ ok: dbTarget === "local" ? postgresReachable : true,
1295
+ detail: dbTarget === "local" ? postgresReachable ? "local postgres reachable on 127.0.0.1:5432" : "local postgres not reachable on 127.0.0.1:5432" : "postgres reachability skipped for non-local target"
1296
+ },
1297
+ {
1298
+ id: "docker",
1299
+ ok: dockerOk,
1300
+ detail: dockerOk ? "docker available" : "docker not found"
1301
+ },
1302
+ {
1303
+ id: "mcp",
1304
+ ok: mcp.ok,
1305
+ detail: mcp.detail
1306
+ }
1307
+ ]
1308
+ };
1309
+ }
1310
+ function formatDoctorReport(report) {
1311
+ const lines = ["", `RevealUI \xB7 ${report.workspaceRoot ?? "workspace not found"}`, ""];
1312
+ for (const check of report.checks) {
1313
+ lines.push(`${check.ok ? "OK" : "NO"} ${check.id.padEnd(10)} ${check.detail}`);
1314
+ }
1315
+ return lines.join("\n");
1316
+ }
1317
+
1318
+ // src/utils/dev-config.ts
1319
+ import fs9 from "fs";
1320
+ import path9 from "path";
1321
+ function getDevConfigPath(startDir = process.cwd()) {
1322
+ const workspaceRoot = findWorkspaceRoot(startDir);
1323
+ if (!workspaceRoot) {
1324
+ return null;
1325
+ }
1326
+ return path9.join(workspaceRoot, ".revealui", "dev.json");
1327
+ }
1328
+ function readDevConfig(startDir = process.cwd()) {
1329
+ const configPath = getDevConfigPath(startDir);
1330
+ if (!(configPath && fs9.existsSync(configPath))) {
1331
+ return {};
1332
+ }
1333
+ try {
1334
+ return JSON.parse(fs9.readFileSync(configPath, "utf8"));
1335
+ } catch {
1336
+ return {};
1337
+ }
1338
+ }
1339
+ function writeDevConfig(config, startDir = process.cwd()) {
1340
+ const configPath = getDevConfigPath(startDir);
1341
+ if (!configPath) {
1342
+ throw new Error("RevealUI workspace root not found");
1343
+ }
1344
+ fs9.mkdirSync(path9.dirname(configPath), { recursive: true });
1345
+ fs9.writeFileSync(`${configPath}`, `${JSON.stringify(config, null, 2)}
1346
+ `, "utf8");
1347
+ return configPath;
1348
+ }
1349
+
1350
+ // src/commands/doctor.ts
1351
+ import { createLogger as createLogger8 } from "@revealui/setup/utils";
1352
+ var logger8 = createLogger8({ prefix: "Doctor" });
1353
+ function getDoctorFixPlan(report) {
1354
+ const attempted = [];
1355
+ const skipped = [];
1356
+ const postgresCheck = report.checks.find((check) => check.id === "postgres");
1357
+ const initdbCheck = report.checks.find((check) => check.id === "initdb");
1358
+ const pgCtlCheck = report.checks.find((check) => check.id === "pg_ctl");
1359
+ const mcpCheck = report.checks.find((check) => check.id === "mcp");
1360
+ if (report.dbTarget === "local" && postgresCheck && !postgresCheck.ok) {
1361
+ if (initdbCheck?.ok && pgCtlCheck?.ok) {
1362
+ attempted.push("initialize/start local postgres");
1046
1363
  } else {
1047
- logger7.error("An unexpected error occurred");
1364
+ skipped.push("local postgres repair requires both initdb and pg_ctl in PATH");
1365
+ }
1366
+ }
1367
+ if (mcpCheck && !mcpCheck.ok) {
1368
+ skipped.push("MCP readiness requires credentials and cannot be auto-fixed safely");
1369
+ }
1370
+ if (attempted.length === 0 && skipped.length === 0) {
1371
+ skipped.push("no safe automatic fixes available");
1372
+ }
1373
+ return { attempted, skipped };
1374
+ }
1375
+ async function applyDoctorFixes(report) {
1376
+ const plan = getDoctorFixPlan(report);
1377
+ if (plan.attempted.includes("initialize/start local postgres")) {
1378
+ try {
1379
+ await runDbInitCommand();
1380
+ } catch {
1381
+ }
1382
+ await runDbStartCommand();
1383
+ }
1384
+ return plan;
1385
+ }
1386
+ function formatDoctorFixPlan(plan) {
1387
+ const lines = ["", "RevealUI Doctor Fix Plan", ""];
1388
+ for (const action of plan.attempted) {
1389
+ lines.push(`fix ${action}`);
1390
+ }
1391
+ for (const action of plan.skipped) {
1392
+ lines.push(`skip ${action}`);
1393
+ }
1394
+ return lines.join("\n");
1395
+ }
1396
+ async function runDoctorCommand(options = {}) {
1397
+ let report = await gatherDoctorReport();
1398
+ const fixPlan = getDoctorFixPlan(report);
1399
+ if (options.json) {
1400
+ process.stdout.write(`${JSON.stringify({ report, fixPlan }, null, 2)}
1401
+ `);
1402
+ return;
1403
+ }
1404
+ process.stdout.write(`${formatDoctorReport(report)}
1405
+ `);
1406
+ process.stdout.write(`${formatDoctorFixPlan(fixPlan)}
1407
+ `);
1408
+ if (options.fix) {
1409
+ if (fixPlan.attempted.length === 0) {
1410
+ logger8.warn("No safe automatic fixes available");
1411
+ } else {
1412
+ await applyDoctorFixes(report);
1413
+ report = await gatherDoctorReport();
1414
+ process.stdout.write(`${formatDoctorReport(report)}
1415
+ `);
1416
+ }
1417
+ }
1418
+ if (report.checks.some((check) => !check.ok)) {
1419
+ logger8.warn("Some checks failed");
1420
+ if (options.strict || options.json || process.env.CI) {
1421
+ process.exitCode = 1;
1048
1422
  }
1049
- process.exit(1);
1050
1423
  }
1051
1424
  }
1052
- var logger7, PRO_TEMPLATES;
1053
- var init_index = __esm({
1054
- "src/index.ts"() {
1055
- init_esm_shims();
1056
- init_cli();
1057
- init_node_version();
1058
- init_create();
1059
- init_database();
1060
- init_devenv();
1061
- init_payments();
1062
- init_project();
1063
- init_storage();
1064
- logger7 = createLogger7({ prefix: "@revealui/cli" });
1065
- PRO_TEMPLATES = /* @__PURE__ */ new Set(["e-commerce", "portfolio"]);
1066
- if (import.meta.url === `file://${process.argv[1]}`) {
1067
- const cli = createCli();
1068
- cli.parse(process.argv);
1425
+
1426
+ // src/commands/shell.ts
1427
+ import { createLogger as createLogger9 } from "@revealui/setup/utils";
1428
+ import { execa as execa5 } from "execa";
1429
+ var logger9 = createLogger9({ prefix: "Shell" });
1430
+ async function runShellCommand(options = {}) {
1431
+ if (!(options.inside || process.env.IN_NIX_SHELL) && await commandExists("nix")) {
1432
+ const workspaceRoot = findWorkspaceRoot();
1433
+ if (workspaceRoot) {
1434
+ const reentryArgs = options.ensure ? ["dev", "up"] : ["dev", "status"];
1435
+ await execa5(
1436
+ "nix",
1437
+ [
1438
+ "develop",
1439
+ "-c",
1440
+ "node",
1441
+ "packages/cli/bin/revealui.js",
1442
+ ...reentryArgs,
1443
+ ...options.forwardArgs ?? [],
1444
+ "--inside",
1445
+ ...options.json ? ["--json"] : []
1446
+ ],
1447
+ {
1448
+ cwd: workspaceRoot,
1449
+ stdio: "inherit"
1450
+ }
1451
+ );
1452
+ return true;
1453
+ }
1454
+ }
1455
+ const report = await gatherDoctorReport();
1456
+ if (options.ensure && report.dbTarget === "local" && await commandExists("pg_ctl")) {
1457
+ const postgresCheck = report.checks.find((check) => check.id === "postgres");
1458
+ if (postgresCheck && !postgresCheck.ok) {
1459
+ try {
1460
+ await runDbInitCommand();
1461
+ } catch {
1462
+ }
1463
+ await runDbStartCommand();
1069
1464
  }
1070
1465
  }
1071
- });
1072
- init_index();
1073
- export {
1074
- run
1466
+ const freshReport = await gatherDoctorReport();
1467
+ if (options.json) {
1468
+ process.stdout.write(`${JSON.stringify(freshReport, null, 2)}
1469
+ `);
1470
+ return false;
1471
+ }
1472
+ process.stdout.write(`${formatDoctorReport(freshReport)}
1473
+ `);
1474
+ logger9.info("Use `revealui doctor --json` for machine-readable status.");
1475
+ return false;
1476
+ }
1477
+
1478
+ // src/commands/dev.ts
1479
+ var logger10 = createLogger10({ prefix: "Dev" });
1480
+ var DEV_PROFILES = {
1481
+ local: {},
1482
+ agent: {
1483
+ include: ["mcp"]
1484
+ },
1485
+ cms: {
1486
+ script: "dev:cms"
1487
+ },
1488
+ fullstack: {
1489
+ include: ["mcp"],
1490
+ script: "dev"
1491
+ }
1075
1492
  };
1493
+ function getConfiguredProfile() {
1494
+ const configured = readDevConfig().defaultProfile;
1495
+ if (configured && configured in DEV_PROFILES) {
1496
+ return configured;
1497
+ }
1498
+ return void 0;
1499
+ }
1500
+ function resolveDevUpOptions(options = {}) {
1501
+ const selectedProfile = options.profile ?? getConfiguredProfile();
1502
+ const profile = selectedProfile ? DEV_PROFILES[selectedProfile] : void 0;
1503
+ if (selectedProfile && !profile) {
1504
+ throw new Error(
1505
+ `Unknown dev profile "${selectedProfile}". Use one of: ${Object.keys(DEV_PROFILES).join(", ")}`
1506
+ );
1507
+ }
1508
+ const include = Array.from(
1509
+ /* @__PURE__ */ new Set([...profile?.include ?? [], ...options.include ?? []])
1510
+ );
1511
+ return {
1512
+ ensure: options.ensure ?? true,
1513
+ json: options.json ?? false,
1514
+ inside: options.inside ?? false,
1515
+ profile: selectedProfile,
1516
+ script: options.script ?? profile?.script,
1517
+ include
1518
+ };
1519
+ }
1520
+ function getDevPlan(options = {}) {
1521
+ const resolved = resolveDevUpOptions(options);
1522
+ return {
1523
+ profile: resolved.profile ?? (options.include?.length || options.script ? "custom" : "local"),
1524
+ ensure: resolved.ensure,
1525
+ include: resolved.include,
1526
+ script: resolved.script,
1527
+ dryRun: options.dryRun ?? false
1528
+ };
1529
+ }
1530
+ function formatDevPlan(plan) {
1531
+ const lines = ["", "RevealUI Dev Plan", ""];
1532
+ lines.push(`profile ${plan.profile}`);
1533
+ lines.push(`ensure ${plan.ensure ? "yes" : "no"}`);
1534
+ lines.push(`dry-run ${plan.dryRun ? "yes" : "no"}`);
1535
+ lines.push(`include ${plan.include.length > 0 ? plan.include.join(", ") : "none"}`);
1536
+ lines.push(`script ${plan.script ?? "none"}`);
1537
+ return lines.join("\n");
1538
+ }
1539
+ function getDevActions(plan) {
1540
+ const actions = [];
1541
+ if (plan.ensure) {
1542
+ actions.push("ensure local shell and database prerequisites");
1543
+ } else {
1544
+ actions.push("skip automatic shell/database ensure");
1545
+ }
1546
+ actions.push("run database migrations");
1547
+ if (shouldIncludeMcp(plan.include)) {
1548
+ actions.push("validate MCP credentials via `pnpm setup:mcp`");
1549
+ }
1550
+ if (plan.script) {
1551
+ actions.push(`start pnpm script \`${plan.script}\``);
1552
+ }
1553
+ return actions;
1554
+ }
1555
+ function formatDevActions(plan) {
1556
+ const actions = getDevActions(plan);
1557
+ const lines = ["", "RevealUI Dev Actions", ""];
1558
+ for (const action of actions) {
1559
+ lines.push(`- ${action}`);
1560
+ }
1561
+ return lines.join("\n");
1562
+ }
1563
+ function shouldIncludeMcp(include) {
1564
+ return include.includes("mcp");
1565
+ }
1566
+ async function runDevUpCommand(options = {}) {
1567
+ const resolved = resolveDevUpOptions(options);
1568
+ const plan = getDevPlan(options);
1569
+ const forwardArgs = [];
1570
+ if (options.dryRun) {
1571
+ forwardArgs.push("--dry-run");
1572
+ }
1573
+ if (options.fix) {
1574
+ forwardArgs.push("--fix");
1575
+ }
1576
+ if (options.profile) {
1577
+ forwardArgs.push("--profile", options.profile);
1578
+ }
1579
+ for (const service of options.include ?? []) {
1580
+ forwardArgs.push("--include", service);
1581
+ }
1582
+ if (options.script) {
1583
+ forwardArgs.push("--script", options.script);
1584
+ }
1585
+ if (options.ensure === false) {
1586
+ forwardArgs.push("--no-ensure");
1587
+ }
1588
+ const reentered = await runShellCommand({
1589
+ ensure: resolved.ensure,
1590
+ json: resolved.json,
1591
+ inside: resolved.inside,
1592
+ forwardArgs
1593
+ });
1594
+ if (reentered) {
1595
+ return;
1596
+ }
1597
+ if (resolved.json) {
1598
+ return;
1599
+ }
1600
+ process.stdout.write(`${formatDevPlan(plan)}
1601
+ `);
1602
+ process.stdout.write(`${formatDevActions(plan)}
1603
+ `);
1604
+ if (plan.dryRun) {
1605
+ logger10.info("Dry run only; no migrations or services were started.");
1606
+ return;
1607
+ }
1608
+ if (options.fix) {
1609
+ await runDoctorCommand({ fix: true });
1610
+ }
1611
+ await runDbMigrateCommand();
1612
+ logger10.success("Database migration complete");
1613
+ if (shouldIncludeMcp(resolved.include)) {
1614
+ const workspaceRoot = findWorkspaceRoot();
1615
+ if (!workspaceRoot) {
1616
+ throw new Error("RevealUI workspace root not found");
1617
+ }
1618
+ logger10.info("Validating MCP setup");
1619
+ await execa6("pnpm", ["setup:mcp"], {
1620
+ cwd: workspaceRoot,
1621
+ stdio: "inherit"
1622
+ });
1623
+ }
1624
+ if (resolved.script) {
1625
+ const workspaceRoot = findWorkspaceRoot();
1626
+ if (!workspaceRoot) {
1627
+ throw new Error("RevealUI workspace root not found");
1628
+ }
1629
+ logger10.info(`Starting dev script: ${resolved.script}`);
1630
+ await execa6("pnpm", [resolved.script], {
1631
+ cwd: workspaceRoot,
1632
+ stdio: "inherit"
1633
+ });
1634
+ }
1635
+ }
1636
+ async function runDevStatusCommand(options = {}) {
1637
+ const plan = getDevPlan(options);
1638
+ const forwardArgs = [];
1639
+ if (options.profile) {
1640
+ forwardArgs.push("--profile", options.profile);
1641
+ }
1642
+ for (const service of options.include ?? []) {
1643
+ forwardArgs.push("--include", service);
1644
+ }
1645
+ if (options.script) {
1646
+ forwardArgs.push("--script", options.script);
1647
+ }
1648
+ if (options.ensure === false) {
1649
+ forwardArgs.push("--no-ensure");
1650
+ }
1651
+ if (!(options.inside || process.env.IN_NIX_SHELL) && await commandExists("nix")) {
1652
+ const reentered = await runShellCommand({
1653
+ ensure: false,
1654
+ json: options.json,
1655
+ inside: options.inside,
1656
+ forwardArgs
1657
+ });
1658
+ if (reentered) {
1659
+ return;
1660
+ }
1661
+ }
1662
+ const report = await gatherDoctorReport();
1663
+ if (options.json) {
1664
+ process.stdout.write(
1665
+ `${JSON.stringify({ report, plan, actions: getDevActions(plan) }, null, 2)}
1666
+ `
1667
+ );
1668
+ return;
1669
+ }
1670
+ process.stdout.write(`${formatDoctorReport(report)}
1671
+ `);
1672
+ process.stdout.write(`${formatDevPlan(plan)}
1673
+ `);
1674
+ process.stdout.write(`${formatDevActions(plan)}
1675
+ `);
1676
+ }
1677
+ async function runDevDownCommand() {
1678
+ await runDbStopCommand();
1679
+ }
1680
+ async function runDevProfileSetCommand(profile) {
1681
+ if (!(profile in DEV_PROFILES)) {
1682
+ throw new Error(
1683
+ `Unknown dev profile "${profile}". Use one of: ${Object.keys(DEV_PROFILES).join(", ")}`
1684
+ );
1685
+ }
1686
+ const configPath = writeDevConfig({ defaultProfile: profile });
1687
+ logger10.success(`Default dev profile set to "${profile}" in ${configPath}`);
1688
+ }
1689
+ async function runDevProfileShowCommand(options = {}) {
1690
+ const configuredProfile = getConfiguredProfile() ?? null;
1691
+ if (options.json) {
1692
+ process.stdout.write(`${JSON.stringify({ defaultProfile: configuredProfile }, null, 2)}
1693
+ `);
1694
+ return;
1695
+ }
1696
+ process.stdout.write(`Default dev profile: ${configuredProfile ?? "not set"}
1697
+ `);
1698
+ }
1699
+
1700
+ // src/cli.ts
1701
+ var logger11 = createLogger11({ prefix: "CLI" });
1702
+ function configureCreateCommand(command, legacyName) {
1703
+ command.description("Create a new RevealUI project").argument("[project-name]", "Name of the project").option("-t, --template <name>", "Template to use (basic-blog, e-commerce, portfolio)").option("--skip-git", "Skip git initialization", false).option("--skip-install", "Skip dependency installation", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(async (projectName, options) => {
1704
+ logger11.header(legacyName ? "Create RevealUI Project" : "RevealUI Create");
1705
+ await runCreateFlow(projectName, options);
1706
+ });
1707
+ return command;
1708
+ }
1709
+ function createCli() {
1710
+ const program = new Command();
1711
+ program.name("revealui").description("RevealUI operational CLI").version("0.2.0");
1712
+ configureCreateCommand(program.command("create"), void 0);
1713
+ program.command("doctor").description("Check RevealUI workspace and developer environment health").option("--json", "Output machine-readable JSON", false).option("--fix", "Apply safe automatic fixes when possible", false).option("--strict", "Exit nonzero when checks fail", false).action(async (options) => {
1714
+ await runDoctorCommand(options);
1715
+ });
1716
+ const db = program.command("db").description("Manage the local RevealUI database");
1717
+ db.command("init").description("Initialize the local PostgreSQL data directory").option("--force", "Delete and recreate the local data directory", false).action(async (options) => {
1718
+ await runDbInitCommand(options);
1719
+ });
1720
+ db.command("start").description("Start the local PostgreSQL server").action(async () => {
1721
+ await runDbStartCommand();
1722
+ });
1723
+ db.command("stop").description("Stop the local PostgreSQL server").action(async () => {
1724
+ await runDbStopCommand();
1725
+ });
1726
+ db.command("status").description("Show local PostgreSQL status").action(async () => {
1727
+ await runDbStatusCommand();
1728
+ });
1729
+ db.command("reset").description("Reset the local PostgreSQL data directory").option("--force", "Confirm the destructive reset", false).action(async (options) => {
1730
+ await runDbResetCommand(options);
1731
+ });
1732
+ db.command("migrate").description("Run Drizzle migrations using the local RevealUI database environment").action(async () => {
1733
+ await runDbMigrateCommand();
1734
+ });
1735
+ db.command("cleanup").description(
1736
+ "Delete expired sessions, rate-limit rows, password-reset tokens, magic links, and publish due scheduled pages. Uses DATABASE_URL / POSTGRES_URL from the environment."
1737
+ ).option("--dry-run", "Count stale rows without deleting them", false).option(
1738
+ "--tables <names>",
1739
+ "Comma-separated subset: sessions,rateLimits,passwordResetTokens,magicLinks,scheduledPages"
1740
+ ).action(async (options) => {
1741
+ await runDbCleanupCommand(options);
1742
+ });
1743
+ const dev = program.command("dev").description("Prepare and manage the RevealUI development workspace");
1744
+ dev.command("up").description(
1745
+ "Ensure the local dev environment is ready, migrate the DB, optionally validate MCP, and start a dev script"
1746
+ ).option("--json", "Output machine-readable JSON", false).option("--dry-run", "Print the effective plan and actions without executing them", false).option("--fix", "Apply safe automatic fixes before bootstrapping when possible", false).option("--no-ensure", "Skip automatic local DB initialization/start").option("--profile <name>", "Named dev profile: local, agent, cms, fullstack").option(
1747
+ "--include <service...>",
1748
+ "Additional development services to prepare (currently supports: mcp)"
1749
+ ).option("--script <name>", "Optional pnpm script to run after environment bootstrap").option("--inside", "Internal flag used after re-entering nix develop", false).action(
1750
+ async (options) => {
1751
+ await runDevUpCommand(options);
1752
+ }
1753
+ );
1754
+ dev.command("status").description("Show current RevealUI development environment status").option("--json", "Output machine-readable JSON", false).option("--profile <name>", "Named dev profile: local, agent, cms, fullstack").option(
1755
+ "--include <service...>",
1756
+ "Additional development services to preview in the effective plan"
1757
+ ).option("--script <name>", "Optional pnpm script to preview in the effective plan").option("--inside", "Internal flag used after re-entering nix develop", false).action(
1758
+ async (options) => {
1759
+ await runDevStatusCommand(options);
1760
+ }
1761
+ );
1762
+ dev.command("down").description("Stop local RevealUI development services that are managed by the CLI").action(async () => {
1763
+ await runDevDownCommand();
1764
+ });
1765
+ dev.command("shell").description("Alias for `revealui dev up` without starting an app script").option("--json", "Output machine-readable JSON", false).option("--dry-run", "Print the effective plan and actions without executing them", false).option("--fix", "Apply safe automatic fixes before bootstrapping when possible", false).option("--no-ensure", "Skip automatic local DB initialization/start").option("--profile <name>", "Named dev profile: local, agent, cms, fullstack").option(
1766
+ "--include <service...>",
1767
+ "Additional development services to prepare (currently supports: mcp)"
1768
+ ).option("--inside", "Internal flag used after re-entering nix develop", false).action(
1769
+ async (options) => {
1770
+ await runDevUpCommand({
1771
+ ensure: options.ensure,
1772
+ json: options.json,
1773
+ dryRun: options.dryRun,
1774
+ fix: options.fix,
1775
+ profile: options.profile,
1776
+ include: options.include,
1777
+ inside: options.inside
1778
+ });
1779
+ }
1780
+ );
1781
+ const devProfile = dev.command("profile").description("Persist or inspect the default dev profile");
1782
+ devProfile.command("set").description("Set the default profile used by `revealui dev up` and `revealui dev status`").argument("<name>", "Profile name: local, agent, cms, fullstack").action(async (name) => {
1783
+ await runDevProfileSetCommand(name);
1784
+ });
1785
+ devProfile.command("show").description("Show the configured default dev profile").option("--json", "Output machine-readable JSON", false).action(async (options) => {
1786
+ await runDevProfileShowCommand(options);
1787
+ });
1788
+ program.command("shell").description("Deprecated alias for `revealui dev shell`").option("--ensure", "Initialize/start the local DB when possible", false).option("--json", "Output machine-readable JSON", false).option("--inside", "Internal flag used after re-entering nix develop", false).action(async (options) => {
1789
+ await runDevUpCommand({
1790
+ ensure: options.ensure,
1791
+ json: options.json,
1792
+ inside: options.inside
1793
+ });
1794
+ });
1795
+ return program;
1796
+ }
1797
+
1798
+ // src/index.ts
1799
+ if (import.meta.url === `file://${process.argv[1]}`) {
1800
+ const cli = createCli();
1801
+ cli.parse(process.argv);
1802
+ }
1076
1803
  //# sourceMappingURL=index.js.map