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