@revealui/cli 0.4.0 → 0.6.2
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 +17 -112
- package/dist/cli.js +1107 -435
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1107 -435
- package/dist/index.js.map +1 -1
- package/package.json +5 -6
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { createLogger as
|
|
4
|
+
import { createLogger as createLogger14 } from "@revealui/setup/utils";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/agent.ts
|
|
@@ -15,7 +15,9 @@ async function runAgentStatusCommand() {
|
|
|
15
15
|
logger.info(`Project root: ${projectRoot}`);
|
|
16
16
|
logger.info(`Available: ${available ? "yes" : "no"}`);
|
|
17
17
|
if (!available) {
|
|
18
|
-
logger.warn(
|
|
18
|
+
logger.warn(
|
|
19
|
+
"No LLM provider detected. Start BitNet, Ollama, or install an Ubuntu inference snap."
|
|
20
|
+
);
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
async function runAgentHeadlessCommand(prompt) {
|
|
@@ -200,9 +202,6 @@ async function detectProvider() {
|
|
|
200
202
|
}
|
|
201
203
|
} catch {
|
|
202
204
|
}
|
|
203
|
-
if (process.env.GROQ_API_KEY) {
|
|
204
|
-
return { available: true, provider: "groq", model: "llama-3.3-70b-versatile", projectRoot };
|
|
205
|
-
}
|
|
206
205
|
return { available: false, provider: "none", model: "none", projectRoot };
|
|
207
206
|
}
|
|
208
207
|
function buildMinimalInstructions() {
|
|
@@ -215,20 +214,337 @@ function buildMinimalInstructions() {
|
|
|
215
214
|
].join("\n");
|
|
216
215
|
}
|
|
217
216
|
|
|
217
|
+
// src/commands/auth.ts
|
|
218
|
+
import fs from "fs/promises";
|
|
219
|
+
import os from "os";
|
|
220
|
+
import path from "path";
|
|
221
|
+
import { createLogger as createLogger2 } from "@revealui/setup/utils";
|
|
222
|
+
import { execa as execa2 } from "execa";
|
|
223
|
+
|
|
224
|
+
// src/utils/command.ts
|
|
225
|
+
import net from "net";
|
|
226
|
+
import { execa } from "execa";
|
|
227
|
+
async function commandExists(command) {
|
|
228
|
+
try {
|
|
229
|
+
await execa("bash", ["-lc", `command -v ${command}`], {
|
|
230
|
+
stdio: "pipe"
|
|
231
|
+
});
|
|
232
|
+
return true;
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function isTcpReachable(host, port, timeoutMs = 1500) {
|
|
238
|
+
return await new Promise((resolve2) => {
|
|
239
|
+
const socket = new net.Socket();
|
|
240
|
+
const finalize = (value) => {
|
|
241
|
+
socket.removeAllListeners();
|
|
242
|
+
socket.destroy();
|
|
243
|
+
resolve2(value);
|
|
244
|
+
};
|
|
245
|
+
socket.setTimeout(timeoutMs);
|
|
246
|
+
socket.once("connect", () => finalize(true));
|
|
247
|
+
socket.once("timeout", () => finalize(false));
|
|
248
|
+
socket.once("error", () => finalize(false));
|
|
249
|
+
socket.connect(port, host);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/commands/auth.ts
|
|
254
|
+
var logger2 = createLogger2({ prefix: "Auth" });
|
|
255
|
+
var REVVAULT_NPM_PATH = "revealui/env/npm";
|
|
256
|
+
var NPM_TOKEN_INTERPOLATION = "${NPM_TOKEN}";
|
|
257
|
+
var NPMRC_AUTH_LINE = `//registry.npmjs.org/:_authToken=${NPM_TOKEN_INTERPOLATION}`;
|
|
258
|
+
function write(text5) {
|
|
259
|
+
process.stdout.write(text5);
|
|
260
|
+
}
|
|
261
|
+
function writeJson(data) {
|
|
262
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
263
|
+
`);
|
|
264
|
+
}
|
|
265
|
+
function getNpmrcPaths() {
|
|
266
|
+
const user = path.join(os.homedir(), ".npmrc");
|
|
267
|
+
let dir = process.cwd();
|
|
268
|
+
let project = null;
|
|
269
|
+
for (let i = 0; i < 10; i++) {
|
|
270
|
+
const candidate = path.join(dir, ".npmrc");
|
|
271
|
+
if (dir !== os.homedir()) {
|
|
272
|
+
project ??= candidate;
|
|
273
|
+
}
|
|
274
|
+
const parent = path.dirname(dir);
|
|
275
|
+
if (parent === dir) break;
|
|
276
|
+
dir = parent;
|
|
277
|
+
}
|
|
278
|
+
return { user, project };
|
|
279
|
+
}
|
|
280
|
+
async function fileContains(filePath, needle) {
|
|
281
|
+
try {
|
|
282
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
283
|
+
return content.includes(needle);
|
|
284
|
+
} catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function getTokenFromEnv() {
|
|
289
|
+
return process.env.NPM_TOKEN || void 0;
|
|
290
|
+
}
|
|
291
|
+
async function getTokenFromNpmrc() {
|
|
292
|
+
const userRc = path.join(os.homedir(), ".npmrc");
|
|
293
|
+
try {
|
|
294
|
+
const content = await fs.readFile(userRc, "utf-8");
|
|
295
|
+
const match = content.match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);
|
|
296
|
+
return match?.[1]?.trim() || void 0;
|
|
297
|
+
} catch {
|
|
298
|
+
return void 0;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function maskToken(token) {
|
|
302
|
+
if (token.length <= 8) return "***";
|
|
303
|
+
return `${token.slice(0, 4)}...${token.slice(-4)}`;
|
|
304
|
+
}
|
|
305
|
+
async function hasRevvault() {
|
|
306
|
+
return commandExists("revvault");
|
|
307
|
+
}
|
|
308
|
+
async function revvaultGet(secretPath) {
|
|
309
|
+
try {
|
|
310
|
+
const { stdout } = await execa2("revvault", ["get", secretPath], {
|
|
311
|
+
stdio: "pipe"
|
|
312
|
+
});
|
|
313
|
+
return stdout.trim() || void 0;
|
|
314
|
+
} catch {
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async function revvaultSet(secretPath, value) {
|
|
319
|
+
try {
|
|
320
|
+
await execa2("revvault", ["set", secretPath], {
|
|
321
|
+
input: value,
|
|
322
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
323
|
+
});
|
|
324
|
+
return true;
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function runAuthStatusCommand(options) {
|
|
330
|
+
const envToken = await getTokenFromEnv();
|
|
331
|
+
const npmrcToken = await getTokenFromNpmrc();
|
|
332
|
+
const hasVault = await hasRevvault();
|
|
333
|
+
const vaultValue = hasVault ? await revvaultGet(REVVAULT_NPM_PATH) : void 0;
|
|
334
|
+
const paths = getNpmrcPaths();
|
|
335
|
+
const projectUsesEnvVar = paths.project ? await fileContains(paths.project, NPM_TOKEN_INTERPOLATION) : false;
|
|
336
|
+
const effectiveToken = envToken || npmrcToken;
|
|
337
|
+
let npmUser;
|
|
338
|
+
try {
|
|
339
|
+
const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
|
|
340
|
+
npmUser = stdout.trim();
|
|
341
|
+
} catch {
|
|
342
|
+
npmUser = void 0;
|
|
343
|
+
}
|
|
344
|
+
if (options.json) {
|
|
345
|
+
writeJson({
|
|
346
|
+
authenticated: !!npmUser,
|
|
347
|
+
user: npmUser ?? null,
|
|
348
|
+
tokenSource: envToken ? "env" : npmrcToken ? "npmrc" : null,
|
|
349
|
+
tokenMasked: effectiveToken ? maskToken(effectiveToken) : null,
|
|
350
|
+
revvaultConfigured: !!vaultValue,
|
|
351
|
+
projectNpmrcUsesEnvVar: projectUsesEnvVar,
|
|
352
|
+
paths: {
|
|
353
|
+
userNpmrc: paths.user,
|
|
354
|
+
projectNpmrc: paths.project
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
logger2.header("npm Authentication Status");
|
|
360
|
+
if (npmUser) {
|
|
361
|
+
logger2.success(`Authenticated as: ${npmUser}`);
|
|
362
|
+
} else {
|
|
363
|
+
logger2.error("Not authenticated \u2014 npm whoami failed");
|
|
364
|
+
}
|
|
365
|
+
write("\n");
|
|
366
|
+
logger2.info(
|
|
367
|
+
`Token source: ${envToken ? "$NPM_TOKEN (env)" : npmrcToken ? "~/.npmrc (file)" : "none"}`
|
|
368
|
+
);
|
|
369
|
+
if (effectiveToken) {
|
|
370
|
+
logger2.info(`Token: ${maskToken(effectiveToken)}`);
|
|
371
|
+
}
|
|
372
|
+
write("\n");
|
|
373
|
+
logger2.info(
|
|
374
|
+
`RevVault (${REVVAULT_NPM_PATH}): ${vaultValue ? "configured" : hasVault ? "not set" : "revvault not installed"}`
|
|
375
|
+
);
|
|
376
|
+
logger2.info(`Project .npmrc uses NPM_TOKEN: ${projectUsesEnvVar ? "yes" : "no"}`);
|
|
377
|
+
if (!projectUsesEnvVar && paths.project) {
|
|
378
|
+
logger2.warn(
|
|
379
|
+
"Project .npmrc does not reference NPM_TOKEN \u2014 direnv/RevVault tokens won't be used for publishing"
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (!vaultValue && hasVault && effectiveToken) {
|
|
383
|
+
logger2.warn("Token is not stored in RevVault. Run: revealui auth set-token to persist it.");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function runAuthSetTokenCommand(options) {
|
|
387
|
+
logger2.header("Set npm Publish Token");
|
|
388
|
+
const token = options.token || process.env.NPM_TOKEN;
|
|
389
|
+
if (!token) {
|
|
390
|
+
logger2.error("No token provided. Pass --token <value> or set NPM_TOKEN in your environment.");
|
|
391
|
+
logger2.info("Create a Granular Access Token at: https://www.npmjs.com/settings/<user>/tokens");
|
|
392
|
+
logger2.info(" Type: Granular Access Token");
|
|
393
|
+
logger2.info(" Packages: All packages (read and write)");
|
|
394
|
+
logger2.info(" This bypasses 2FA \u2014 no OTP needed for publish");
|
|
395
|
+
process.exitCode = 1;
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (!token.startsWith("npm_")) {
|
|
399
|
+
logger2.warn('Token does not start with "npm_" \u2014 this may not be a valid npm token');
|
|
400
|
+
}
|
|
401
|
+
if (!options.skipVault) {
|
|
402
|
+
const hasVault = await hasRevvault();
|
|
403
|
+
if (hasVault) {
|
|
404
|
+
const stored = await revvaultSet(REVVAULT_NPM_PATH, `NPM_TOKEN=${token}`);
|
|
405
|
+
if (stored) {
|
|
406
|
+
logger2.success(`Stored in RevVault (${REVVAULT_NPM_PATH})`);
|
|
407
|
+
} else {
|
|
408
|
+
logger2.error("Failed to store in RevVault");
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
logger2.warn("RevVault not installed \u2014 skipping vault storage");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!options.skipNpmrc) {
|
|
415
|
+
const paths = getNpmrcPaths();
|
|
416
|
+
if (paths.project) {
|
|
417
|
+
const hasInterpolation = await fileContains(paths.project, NPM_TOKEN_INTERPOLATION);
|
|
418
|
+
if (!hasInterpolation) {
|
|
419
|
+
try {
|
|
420
|
+
const content = await fs.readFile(paths.project, "utf-8");
|
|
421
|
+
const updatedContent = `${content.trimEnd()}
|
|
422
|
+
${NPMRC_AUTH_LINE}
|
|
423
|
+
`;
|
|
424
|
+
await fs.writeFile(paths.project, updatedContent, "utf-8");
|
|
425
|
+
logger2.success(`Added NPM_TOKEN interpolation to ${paths.project}`);
|
|
426
|
+
} catch (err) {
|
|
427
|
+
logger2.error(`Failed to update ${paths.project}: ${err}`);
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
logger2.info("Project .npmrc already uses NPM_TOKEN");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const userRc = path.join(os.homedir(), ".npmrc");
|
|
435
|
+
try {
|
|
436
|
+
const content = await fs.readFile(userRc, "utf-8");
|
|
437
|
+
const filtered = content.split("\n").filter((line) => !line.includes("//registry.npmjs.org/:_authToken=")).join("\n");
|
|
438
|
+
if (filtered !== content) {
|
|
439
|
+
await fs.writeFile(userRc, filtered, "utf-8");
|
|
440
|
+
logger2.success("Removed hardcoded token from ~/.npmrc (now managed via env var)");
|
|
441
|
+
}
|
|
442
|
+
} catch {
|
|
443
|
+
}
|
|
444
|
+
write("\n");
|
|
445
|
+
logger2.info("Verifying...");
|
|
446
|
+
process.env.NPM_TOKEN = token;
|
|
447
|
+
try {
|
|
448
|
+
const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
|
|
449
|
+
logger2.success(`Authenticated as: ${stdout.trim()}`);
|
|
450
|
+
} catch {
|
|
451
|
+
logger2.error("npm whoami failed \u2014 token may be invalid or expired");
|
|
452
|
+
process.exitCode = 1;
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
write("\n");
|
|
456
|
+
logger2.success("Token configured. Run `direnv reload` to load it in all terminals.");
|
|
457
|
+
}
|
|
458
|
+
async function runAuthVerifyCommand(options) {
|
|
459
|
+
const checks = [];
|
|
460
|
+
let npmUser;
|
|
461
|
+
try {
|
|
462
|
+
const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
|
|
463
|
+
npmUser = stdout.trim();
|
|
464
|
+
checks.push({ name: "npm whoami", pass: true, detail: npmUser });
|
|
465
|
+
} catch {
|
|
466
|
+
checks.push({
|
|
467
|
+
name: "npm whoami",
|
|
468
|
+
pass: false,
|
|
469
|
+
detail: "Not authenticated"
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
const envToken = await getTokenFromEnv();
|
|
473
|
+
const npmrcToken = await getTokenFromNpmrc();
|
|
474
|
+
const source = envToken ? "$NPM_TOKEN (env)" : npmrcToken ? "~/.npmrc (hardcoded)" : "none";
|
|
475
|
+
checks.push({
|
|
476
|
+
name: "Token source",
|
|
477
|
+
pass: !!envToken,
|
|
478
|
+
detail: source + (npmrcToken && !envToken ? " \u2014 consider migrating to RevVault" : "")
|
|
479
|
+
});
|
|
480
|
+
const hasVault = await hasRevvault();
|
|
481
|
+
if (hasVault) {
|
|
482
|
+
const vaultValue = await revvaultGet(REVVAULT_NPM_PATH);
|
|
483
|
+
checks.push({
|
|
484
|
+
name: "RevVault",
|
|
485
|
+
pass: !!vaultValue,
|
|
486
|
+
detail: vaultValue ? `${REVVAULT_NPM_PATH} configured` : "Not stored in vault"
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
const paths = getNpmrcPaths();
|
|
490
|
+
if (paths.project) {
|
|
491
|
+
const usesEnvVar = await fileContains(paths.project, NPM_TOKEN_INTERPOLATION);
|
|
492
|
+
checks.push({
|
|
493
|
+
name: ".npmrc NPM_TOKEN",
|
|
494
|
+
pass: usesEnvVar,
|
|
495
|
+
detail: usesEnvVar ? "Project .npmrc uses env var" : "Missing \u2014 direnv token won't reach npm"
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
const envrcPath = path.join(process.cwd(), ".envrc");
|
|
499
|
+
const loadsNpm = await fileContains(envrcPath, REVVAULT_NPM_PATH);
|
|
500
|
+
checks.push({
|
|
501
|
+
name: ".envrc loads npm",
|
|
502
|
+
pass: loadsNpm,
|
|
503
|
+
detail: loadsNpm ? "revealui/env/npm in .envrc" : "Missing \u2014 direnv won't export NPM_TOKEN"
|
|
504
|
+
});
|
|
505
|
+
const hardcodedToken = await getTokenFromNpmrc();
|
|
506
|
+
checks.push({
|
|
507
|
+
name: "~/.npmrc clean",
|
|
508
|
+
pass: !hardcodedToken,
|
|
509
|
+
detail: hardcodedToken ? `Hardcoded token found (${maskToken(hardcodedToken)})` : "No hardcoded tokens"
|
|
510
|
+
});
|
|
511
|
+
if (options.json) {
|
|
512
|
+
const allPass2 = checks.every((c) => c.pass);
|
|
513
|
+
writeJson({ pass: allPass2, checks });
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
logger2.header("npm Auth Verification");
|
|
517
|
+
for (const check of checks) {
|
|
518
|
+
if (check.pass) {
|
|
519
|
+
logger2.success(`${check.name}: ${check.detail}`);
|
|
520
|
+
} else {
|
|
521
|
+
logger2.error(`${check.name}: ${check.detail}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const allPass = checks.every((c) => c.pass);
|
|
525
|
+
write("\n");
|
|
526
|
+
if (allPass) {
|
|
527
|
+
logger2.success("All checks passed \u2014 ready to publish");
|
|
528
|
+
} else {
|
|
529
|
+
logger2.warn("Some checks failed. Run `revealui auth set-token` to fix.");
|
|
530
|
+
process.exitCode = 1;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
218
534
|
// src/commands/create-flow.ts
|
|
219
535
|
import { readFileSync } from "fs";
|
|
220
536
|
import { homedir } from "os";
|
|
221
537
|
import { join } from "path";
|
|
222
|
-
import { createLogger as
|
|
538
|
+
import { createLogger as createLogger8 } from "@revealui/setup/utils";
|
|
223
539
|
import { importSPKI, jwtVerify } from "jose";
|
|
224
540
|
|
|
225
541
|
// src/prompts/database.ts
|
|
226
|
-
import
|
|
542
|
+
import { isCancel, select, text } from "@clack/prompts";
|
|
227
543
|
|
|
228
544
|
// src/validators/credentials.ts
|
|
229
|
-
import { createLogger as
|
|
230
|
-
var
|
|
231
|
-
|
|
545
|
+
import { createLogger as createLogger3 } from "@revealui/setup/utils";
|
|
546
|
+
var logger3 = createLogger3({ prefix: "Validator" });
|
|
547
|
+
function validateStripeKey(key) {
|
|
232
548
|
if (!(key.startsWith("sk_test_") || key.startsWith("sk_live_"))) {
|
|
233
549
|
return {
|
|
234
550
|
valid: false,
|
|
@@ -237,7 +553,7 @@ async function validateStripeKey(key) {
|
|
|
237
553
|
}
|
|
238
554
|
return { valid: true };
|
|
239
555
|
}
|
|
240
|
-
|
|
556
|
+
function validateNeonUrl(url) {
|
|
241
557
|
try {
|
|
242
558
|
const parsed = new URL(url);
|
|
243
559
|
if (!parsed.protocol.startsWith("postgres")) {
|
|
@@ -254,7 +570,7 @@ async function validateNeonUrl(url) {
|
|
|
254
570
|
};
|
|
255
571
|
}
|
|
256
572
|
}
|
|
257
|
-
|
|
573
|
+
function validateVercelToken(token) {
|
|
258
574
|
if (!token || token.length < 20) {
|
|
259
575
|
return {
|
|
260
576
|
valid: false,
|
|
@@ -263,11 +579,11 @@ async function validateVercelToken(token) {
|
|
|
263
579
|
}
|
|
264
580
|
return { valid: true };
|
|
265
581
|
}
|
|
266
|
-
|
|
582
|
+
function validateSupabaseUrl(url) {
|
|
267
583
|
try {
|
|
268
584
|
const parsed = new URL(url);
|
|
269
585
|
if (!parsed.hostname.includes("supabase")) {
|
|
270
|
-
|
|
586
|
+
logger3.warn("URL does not appear to be a Supabase URL");
|
|
271
587
|
}
|
|
272
588
|
return { valid: true };
|
|
273
589
|
} catch {
|
|
@@ -277,150 +593,152 @@ async function validateSupabaseUrl(url) {
|
|
|
277
593
|
};
|
|
278
594
|
}
|
|
279
595
|
}
|
|
596
|
+
function validateNpmToken(token) {
|
|
597
|
+
if (!token.startsWith("npm_")) {
|
|
598
|
+
return {
|
|
599
|
+
valid: false,
|
|
600
|
+
message: "npm token must start with npm_"
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (token.length < 20) {
|
|
604
|
+
return {
|
|
605
|
+
valid: false,
|
|
606
|
+
message: "npm token appears invalid (too short)"
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return { valid: true };
|
|
610
|
+
}
|
|
280
611
|
|
|
281
612
|
// src/prompts/database.ts
|
|
282
613
|
async function promptDatabaseConfig() {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
name: "Local PostgreSQL - Use existing local database",
|
|
299
|
-
value: "local"
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
name: "Skip - Configure later",
|
|
303
|
-
value: "skip"
|
|
304
|
-
}
|
|
305
|
-
],
|
|
306
|
-
default: "neon"
|
|
307
|
-
}
|
|
308
|
-
]);
|
|
614
|
+
const provider = await select({
|
|
615
|
+
message: "Which database provider would you like to use?",
|
|
616
|
+
options: [
|
|
617
|
+
{ value: "neon", label: "NeonDB - Serverless PostgreSQL (recommended)" },
|
|
618
|
+
{ value: "supabase", label: "Supabase - PostgreSQL with built-in features" },
|
|
619
|
+
{ value: "local", label: "Local PostgreSQL - Use existing local database" },
|
|
620
|
+
{ value: "skip", label: "Skip - Configure later" }
|
|
621
|
+
],
|
|
622
|
+
initialValue: "neon"
|
|
623
|
+
});
|
|
624
|
+
if (isCancel(provider)) {
|
|
625
|
+
process.exit(0);
|
|
626
|
+
}
|
|
309
627
|
if (provider === "skip") {
|
|
310
628
|
return { provider: "skip" };
|
|
311
629
|
}
|
|
312
630
|
if (provider === "local") {
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const result = await validateNeonUrl(input);
|
|
321
|
-
return result.valid ? true : result.message || "Invalid database URL";
|
|
322
|
-
}
|
|
631
|
+
const postgresUrl2 = await text({
|
|
632
|
+
message: "Enter your PostgreSQL connection string:",
|
|
633
|
+
defaultValue: "postgresql://postgres:postgres@localhost:5432/revealui",
|
|
634
|
+
validate: (input) => {
|
|
635
|
+
if (!input) return void 0;
|
|
636
|
+
const result = validateNeonUrl(input);
|
|
637
|
+
return result.valid ? void 0 : result.message || "Invalid database URL";
|
|
323
638
|
}
|
|
324
|
-
|
|
639
|
+
});
|
|
640
|
+
if (isCancel(postgresUrl2)) {
|
|
641
|
+
process.exit(0);
|
|
642
|
+
}
|
|
325
643
|
return { provider: "local", postgresUrl: postgresUrl2 };
|
|
326
644
|
}
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (!input || input.trim() === "") {
|
|
334
|
-
return "Database URL is required";
|
|
335
|
-
}
|
|
336
|
-
const result = await validateNeonUrl(input);
|
|
337
|
-
return result.valid ? true : result.message || "Invalid database URL";
|
|
645
|
+
const label = provider === "neon" ? "Neon" : "Supabase";
|
|
646
|
+
const postgresUrl = await text({
|
|
647
|
+
message: `Enter your ${label} database connection string:`,
|
|
648
|
+
validate: (input) => {
|
|
649
|
+
if (!input || input.trim() === "") {
|
|
650
|
+
return "Database URL is required";
|
|
338
651
|
}
|
|
652
|
+
const result = validateNeonUrl(input);
|
|
653
|
+
return result.valid ? void 0 : result.message || "Invalid database URL";
|
|
339
654
|
}
|
|
340
|
-
|
|
655
|
+
});
|
|
656
|
+
if (isCancel(postgresUrl)) {
|
|
657
|
+
process.exit(0);
|
|
658
|
+
}
|
|
341
659
|
return { provider, postgresUrl };
|
|
342
660
|
}
|
|
343
661
|
|
|
344
662
|
// src/prompts/devenv.ts
|
|
345
|
-
import
|
|
663
|
+
import { confirm, isCancel as isCancel2 } from "@clack/prompts";
|
|
346
664
|
async function promptDevEnvConfig() {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return
|
|
665
|
+
const createDevContainer = await confirm({
|
|
666
|
+
message: "Create Dev Container configuration for VS Code / GitHub Codespaces?",
|
|
667
|
+
initialValue: true
|
|
668
|
+
});
|
|
669
|
+
if (isCancel2(createDevContainer)) {
|
|
670
|
+
process.exit(0);
|
|
671
|
+
}
|
|
672
|
+
const createDevbox = await confirm({
|
|
673
|
+
message: "Create Devbox configuration for Nix-powered development?",
|
|
674
|
+
initialValue: true
|
|
675
|
+
});
|
|
676
|
+
if (isCancel2(createDevbox)) {
|
|
677
|
+
process.exit(0);
|
|
678
|
+
}
|
|
679
|
+
return { createDevContainer, createDevbox };
|
|
362
680
|
}
|
|
363
681
|
|
|
364
682
|
// src/prompts/payments.ts
|
|
365
|
-
import
|
|
683
|
+
import { confirm as confirm2, isCancel as isCancel3, text as text2 } from "@clack/prompts";
|
|
366
684
|
async function promptPaymentConfig() {
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
]);
|
|
685
|
+
const enabled = await confirm2({
|
|
686
|
+
message: "Do you want to configure Stripe payments?",
|
|
687
|
+
initialValue: true
|
|
688
|
+
});
|
|
689
|
+
if (isCancel3(enabled)) {
|
|
690
|
+
process.exit(0);
|
|
691
|
+
}
|
|
375
692
|
if (!enabled) {
|
|
376
693
|
return { enabled: false };
|
|
377
694
|
}
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
validate: async (input) => {
|
|
384
|
-
if (!input || input.trim() === "") {
|
|
385
|
-
return "Stripe secret key is required";
|
|
386
|
-
}
|
|
387
|
-
const result = await validateStripeKey(input);
|
|
388
|
-
return result.valid ? true : result.message || "Invalid Stripe key";
|
|
695
|
+
const stripeSecretKey = await text2({
|
|
696
|
+
message: "Enter your Stripe secret key (sk_test_... or sk_live_...):",
|
|
697
|
+
validate: (input) => {
|
|
698
|
+
if (!input || input.trim() === "") {
|
|
699
|
+
return "Stripe secret key is required";
|
|
389
700
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return true;
|
|
701
|
+
const result = validateStripeKey(input);
|
|
702
|
+
return result.valid ? void 0 : result.message || "Invalid Stripe key";
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
if (isCancel3(stripeSecretKey)) {
|
|
706
|
+
process.exit(0);
|
|
707
|
+
}
|
|
708
|
+
const stripePublishableKey = await text2({
|
|
709
|
+
message: "Enter your Stripe publishable key (pk_test_... or pk_live_...):",
|
|
710
|
+
validate: (input) => {
|
|
711
|
+
if (!input || input.trim() === "") {
|
|
712
|
+
return "Stripe publishable key is required";
|
|
403
713
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
|
|
409
|
-
default: ""
|
|
714
|
+
if (!(input.startsWith("pk_test_") || input.startsWith("pk_live_"))) {
|
|
715
|
+
return "Stripe publishable key must start with pk_test_ or pk_live_";
|
|
716
|
+
}
|
|
717
|
+
return void 0;
|
|
410
718
|
}
|
|
411
|
-
|
|
719
|
+
});
|
|
720
|
+
if (isCancel3(stripePublishableKey)) {
|
|
721
|
+
process.exit(0);
|
|
722
|
+
}
|
|
723
|
+
const stripeWebhookSecret = await text2({
|
|
724
|
+
message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
|
|
725
|
+
defaultValue: ""
|
|
726
|
+
});
|
|
727
|
+
if (isCancel3(stripeWebhookSecret)) {
|
|
728
|
+
process.exit(0);
|
|
729
|
+
}
|
|
412
730
|
return {
|
|
413
731
|
enabled: true,
|
|
414
|
-
stripeSecretKey
|
|
415
|
-
stripePublishableKey
|
|
416
|
-
stripeWebhookSecret:
|
|
732
|
+
stripeSecretKey,
|
|
733
|
+
stripePublishableKey,
|
|
734
|
+
stripeWebhookSecret: stripeWebhookSecret || void 0
|
|
417
735
|
};
|
|
418
736
|
}
|
|
419
737
|
|
|
420
738
|
// src/prompts/project.ts
|
|
421
|
-
import
|
|
422
|
-
import
|
|
423
|
-
import
|
|
739
|
+
import fs2 from "fs";
|
|
740
|
+
import path2 from "path";
|
|
741
|
+
import { isCancel as isCancel4, select as select2, text as text3 } from "@clack/prompts";
|
|
424
742
|
var VALID_TEMPLATES = ["basic-blog", "e-commerce", "portfolio"];
|
|
425
743
|
async function promptProjectConfig(defaultName, templateArg, nonInteractive = false) {
|
|
426
744
|
let projectName;
|
|
@@ -429,25 +747,31 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
|
|
|
429
747
|
} else if (nonInteractive) {
|
|
430
748
|
projectName = "my-revealui-project";
|
|
431
749
|
} else {
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
return true;
|
|
750
|
+
const name = await text3({
|
|
751
|
+
message: "What is your project name?",
|
|
752
|
+
defaultValue: "my-revealui-project",
|
|
753
|
+
validate: (input) => {
|
|
754
|
+
if (!input) return void 0;
|
|
755
|
+
const isValid = input.split("").every((ch) => {
|
|
756
|
+
const code = ch.charCodeAt(0);
|
|
757
|
+
return code >= 97 && code <= 122 || // a-z
|
|
758
|
+
code >= 48 && code <= 57 || // 0-9
|
|
759
|
+
code === 45;
|
|
760
|
+
});
|
|
761
|
+
if (!isValid) {
|
|
762
|
+
return "Project name must contain only lowercase letters, numbers, and hyphens";
|
|
447
763
|
}
|
|
764
|
+
const projectPath = path2.resolve(process.cwd(), input);
|
|
765
|
+
if (fs2.existsSync(projectPath)) {
|
|
766
|
+
return `Directory "${input}" already exists`;
|
|
767
|
+
}
|
|
768
|
+
return void 0;
|
|
448
769
|
}
|
|
449
|
-
|
|
450
|
-
|
|
770
|
+
});
|
|
771
|
+
if (isCancel4(name)) {
|
|
772
|
+
process.exit(0);
|
|
773
|
+
}
|
|
774
|
+
projectName = name;
|
|
451
775
|
}
|
|
452
776
|
let template;
|
|
453
777
|
if (templateArg && VALID_TEMPLATES.includes(templateArg)) {
|
|
@@ -455,118 +779,106 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
|
|
|
455
779
|
} else if (nonInteractive) {
|
|
456
780
|
template = "basic-blog";
|
|
457
781
|
} else {
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
template = answers.template;
|
|
782
|
+
const selected = await select2({
|
|
783
|
+
message: "Which template would you like to use?",
|
|
784
|
+
options: [
|
|
785
|
+
{ value: "basic-blog", label: "Basic Blog - A simple blog with posts and pages" },
|
|
786
|
+
{ value: "e-commerce", label: "E-commerce - Product catalog with checkout" },
|
|
787
|
+
{ value: "portfolio", label: "Portfolio - Personal portfolio site" }
|
|
788
|
+
],
|
|
789
|
+
initialValue: "basic-blog"
|
|
790
|
+
});
|
|
791
|
+
if (isCancel4(selected)) {
|
|
792
|
+
process.exit(0);
|
|
793
|
+
}
|
|
794
|
+
template = selected;
|
|
472
795
|
}
|
|
473
796
|
return {
|
|
474
797
|
projectName,
|
|
475
|
-
projectPath:
|
|
798
|
+
projectPath: path2.resolve(process.cwd(), projectName),
|
|
476
799
|
template
|
|
477
800
|
};
|
|
478
801
|
}
|
|
479
802
|
|
|
480
803
|
// src/prompts/storage.ts
|
|
481
|
-
import
|
|
804
|
+
import { isCancel as isCancel5, select as select3, text as text4 } from "@clack/prompts";
|
|
482
805
|
async function promptStorageConfig() {
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
value: "supabase"
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
name: "Skip - Configure later",
|
|
499
|
-
value: "skip"
|
|
500
|
-
}
|
|
501
|
-
],
|
|
502
|
-
default: "vercel-blob"
|
|
503
|
-
}
|
|
504
|
-
]);
|
|
806
|
+
const provider = await select3({
|
|
807
|
+
message: "Which storage provider would you like to use?",
|
|
808
|
+
options: [
|
|
809
|
+
{ value: "vercel-blob", label: "Vercel Blob - Simple object storage (recommended)" },
|
|
810
|
+
{ value: "supabase", label: "Supabase Storage - Integrated with Supabase" },
|
|
811
|
+
{ value: "skip", label: "Skip - Configure later" }
|
|
812
|
+
],
|
|
813
|
+
initialValue: "vercel-blob"
|
|
814
|
+
});
|
|
815
|
+
if (isCancel5(provider)) {
|
|
816
|
+
process.exit(0);
|
|
817
|
+
}
|
|
505
818
|
if (provider === "skip") {
|
|
506
819
|
return { provider: "skip" };
|
|
507
820
|
}
|
|
508
821
|
if (provider === "vercel-blob") {
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
validate: async (input) => {
|
|
515
|
-
if (!input || input.trim() === "") {
|
|
516
|
-
return "Blob token is required";
|
|
517
|
-
}
|
|
518
|
-
const result = await validateVercelToken(input);
|
|
519
|
-
return result.valid ? true : result.message || "Invalid token";
|
|
822
|
+
const blobToken = await text4({
|
|
823
|
+
message: "Enter your Vercel Blob read/write token:",
|
|
824
|
+
validate: (input) => {
|
|
825
|
+
if (!input || input.trim() === "") {
|
|
826
|
+
return "Blob token is required";
|
|
520
827
|
}
|
|
828
|
+
const result = validateVercelToken(input);
|
|
829
|
+
return result.valid ? void 0 : result.message || "Invalid token";
|
|
521
830
|
}
|
|
522
|
-
|
|
831
|
+
});
|
|
832
|
+
if (isCancel5(blobToken)) {
|
|
833
|
+
process.exit(0);
|
|
834
|
+
}
|
|
523
835
|
return { provider: "vercel-blob", blobToken };
|
|
524
836
|
}
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
validate: async (input) => {
|
|
531
|
-
if (!input || input.trim() === "") {
|
|
532
|
-
return "Supabase URL is required";
|
|
533
|
-
}
|
|
534
|
-
const result = await validateSupabaseUrl(input);
|
|
535
|
-
return result.valid ? true : result.message || "Invalid URL";
|
|
837
|
+
const supabaseUrl = await text4({
|
|
838
|
+
message: "Enter your Supabase project URL:",
|
|
839
|
+
validate: (input) => {
|
|
840
|
+
if (!input || input.trim() === "") {
|
|
841
|
+
return "Supabase URL is required";
|
|
536
842
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
843
|
+
const result = validateSupabaseUrl(input);
|
|
844
|
+
return result.valid ? void 0 : result.message || "Invalid URL";
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
if (isCancel5(supabaseUrl)) {
|
|
848
|
+
process.exit(0);
|
|
849
|
+
}
|
|
850
|
+
const supabasePublishableKey = await text4({
|
|
851
|
+
message: "Enter your Supabase publishable key (sb_publishable_...):",
|
|
852
|
+
validate: (input) => {
|
|
853
|
+
if (!input || input.trim() === "") {
|
|
854
|
+
return "Supabase publishable key is required";
|
|
547
855
|
}
|
|
856
|
+
return void 0;
|
|
548
857
|
}
|
|
549
|
-
|
|
858
|
+
});
|
|
859
|
+
if (isCancel5(supabasePublishableKey)) {
|
|
860
|
+
process.exit(0);
|
|
861
|
+
}
|
|
550
862
|
return {
|
|
551
863
|
provider: "supabase",
|
|
552
|
-
supabaseUrl
|
|
553
|
-
supabasePublishableKey
|
|
864
|
+
supabaseUrl,
|
|
865
|
+
supabasePublishableKey
|
|
554
866
|
};
|
|
555
867
|
}
|
|
556
868
|
|
|
557
869
|
// src/validators/node-version.ts
|
|
558
|
-
import { createLogger as
|
|
559
|
-
var
|
|
870
|
+
import { createLogger as createLogger4 } from "@revealui/setup/utils";
|
|
871
|
+
var logger4 = createLogger4({ prefix: "Setup" });
|
|
560
872
|
var REQUIRED_NODE_VERSION = "24.13.0";
|
|
561
873
|
function validateNodeVersion() {
|
|
562
874
|
const currentVersion = process.version.slice(1);
|
|
563
875
|
const [currentMajor, currentMinor] = currentVersion.split(".").map(Number);
|
|
564
876
|
const [requiredMajor, requiredMinor] = REQUIRED_NODE_VERSION.split(".").map(Number);
|
|
565
877
|
if (currentMajor < requiredMajor || currentMajor === requiredMajor && currentMinor < requiredMinor) {
|
|
566
|
-
|
|
878
|
+
logger4.error(
|
|
567
879
|
`Node.js ${REQUIRED_NODE_VERSION} or higher is required. You have ${currentVersion}.`
|
|
568
880
|
);
|
|
569
|
-
|
|
881
|
+
logger4.info("Please upgrade Node.js: https://nodejs.org/");
|
|
570
882
|
return false;
|
|
571
883
|
}
|
|
572
884
|
return true;
|
|
@@ -575,15 +887,15 @@ function validateNodeVersion() {
|
|
|
575
887
|
// src/commands/create.ts
|
|
576
888
|
import crypto from "crypto";
|
|
577
889
|
import { existsSync } from "fs";
|
|
578
|
-
import
|
|
579
|
-
import
|
|
890
|
+
import fs6 from "fs/promises";
|
|
891
|
+
import path6 from "path";
|
|
580
892
|
import { fileURLToPath } from "url";
|
|
581
|
-
import { createLogger as
|
|
893
|
+
import { createLogger as createLogger7 } from "@revealui/setup/utils";
|
|
582
894
|
import ora2 from "ora";
|
|
583
895
|
|
|
584
896
|
// src/generators/devbox.ts
|
|
585
|
-
import
|
|
586
|
-
import
|
|
897
|
+
import fs3 from "fs/promises";
|
|
898
|
+
import path3 from "path";
|
|
587
899
|
async function generateDevbox(projectPath) {
|
|
588
900
|
const devboxConfig = {
|
|
589
901
|
packages: ["nodejs@24.13.0", "pnpm@10.28.2", "postgresql@16", "stripe-cli@latest"],
|
|
@@ -603,19 +915,19 @@ async function generateDevbox(projectPath) {
|
|
|
603
915
|
NODE_ENV: "development"
|
|
604
916
|
}
|
|
605
917
|
};
|
|
606
|
-
await
|
|
607
|
-
|
|
918
|
+
await fs3.writeFile(
|
|
919
|
+
path3.join(projectPath, "devbox.json"),
|
|
608
920
|
JSON.stringify(devboxConfig, null, 2),
|
|
609
921
|
"utf-8"
|
|
610
922
|
);
|
|
611
923
|
}
|
|
612
924
|
|
|
613
925
|
// src/generators/devcontainer.ts
|
|
614
|
-
import
|
|
615
|
-
import
|
|
926
|
+
import fs4 from "fs/promises";
|
|
927
|
+
import path4 from "path";
|
|
616
928
|
async function generateDevContainer(projectPath) {
|
|
617
|
-
const devcontainerDir =
|
|
618
|
-
await
|
|
929
|
+
const devcontainerDir = path4.join(projectPath, ".devcontainer");
|
|
930
|
+
await fs4.mkdir(devcontainerDir, { recursive: true });
|
|
619
931
|
const devcontainerConfig = {
|
|
620
932
|
name: "RevealUI Development",
|
|
621
933
|
image: "mcr.microsoft.com/devcontainers/typescript-node:24",
|
|
@@ -662,8 +974,8 @@ async function generateDevContainer(projectPath) {
|
|
|
662
974
|
},
|
|
663
975
|
remoteUser: "node"
|
|
664
976
|
};
|
|
665
|
-
await
|
|
666
|
-
|
|
977
|
+
await fs4.writeFile(
|
|
978
|
+
path4.join(devcontainerDir, "devcontainer.json"),
|
|
667
979
|
JSON.stringify(devcontainerConfig, null, 2),
|
|
668
980
|
"utf-8"
|
|
669
981
|
);
|
|
@@ -692,7 +1004,7 @@ services:
|
|
|
692
1004
|
volumes:
|
|
693
1005
|
postgres-data:
|
|
694
1006
|
`;
|
|
695
|
-
await
|
|
1007
|
+
await fs4.writeFile(path4.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
|
|
696
1008
|
const dockerfile = `FROM mcr.microsoft.com/devcontainers/typescript-node:24
|
|
697
1009
|
|
|
698
1010
|
# Install additional tools
|
|
@@ -702,7 +1014,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\
|
|
|
702
1014
|
# Enable pnpm
|
|
703
1015
|
RUN corepack enable
|
|
704
1016
|
`;
|
|
705
|
-
await
|
|
1017
|
+
await fs4.writeFile(path4.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
|
|
706
1018
|
const readme = `# Dev Container Setup
|
|
707
1019
|
|
|
708
1020
|
This directory contains the Dev Container configuration for RevealUI.
|
|
@@ -744,12 +1056,12 @@ For GitHub Codespaces, set secrets in your repository settings.
|
|
|
744
1056
|
- 4000: CMS
|
|
745
1057
|
- 5432: PostgreSQL database
|
|
746
1058
|
`;
|
|
747
|
-
await
|
|
1059
|
+
await fs4.writeFile(path4.join(devcontainerDir, "README.md"), readme, "utf-8");
|
|
748
1060
|
}
|
|
749
1061
|
|
|
750
1062
|
// src/generators/readme.ts
|
|
751
|
-
import
|
|
752
|
-
import
|
|
1063
|
+
import fs5 from "fs/promises";
|
|
1064
|
+
import path5 from "path";
|
|
753
1065
|
async function generateReadme(projectPath, projectConfig) {
|
|
754
1066
|
const readme = `# ${projectConfig.projectName}
|
|
755
1067
|
|
|
@@ -821,31 +1133,31 @@ This project was created using the **${projectConfig.template}** template.
|
|
|
821
1133
|
|
|
822
1134
|
MIT
|
|
823
1135
|
`;
|
|
824
|
-
await
|
|
1136
|
+
await fs5.writeFile(path5.join(projectPath, "README.md"), readme, "utf-8");
|
|
825
1137
|
}
|
|
826
1138
|
|
|
827
1139
|
// src/installers/dependencies.ts
|
|
828
|
-
import { createLogger as
|
|
829
|
-
import { execa } from "execa";
|
|
1140
|
+
import { createLogger as createLogger5 } from "@revealui/setup/utils";
|
|
1141
|
+
import { execa as execa3 } from "execa";
|
|
830
1142
|
import ora from "ora";
|
|
831
|
-
var
|
|
1143
|
+
var logger5 = createLogger5({ prefix: "Install" });
|
|
832
1144
|
async function installDependencies(projectPath) {
|
|
833
1145
|
const spinner = ora("Installing dependencies with pnpm...").start();
|
|
834
1146
|
try {
|
|
835
|
-
await
|
|
1147
|
+
await execa3("pnpm", ["install"], {
|
|
836
1148
|
cwd: projectPath,
|
|
837
1149
|
stdio: "pipe"
|
|
838
1150
|
});
|
|
839
1151
|
spinner.succeed("Dependencies installed successfully");
|
|
840
1152
|
} catch (error) {
|
|
841
1153
|
spinner.fail("Failed to install dependencies");
|
|
842
|
-
|
|
1154
|
+
logger5.error('Please run "pnpm install" manually');
|
|
843
1155
|
throw error;
|
|
844
1156
|
}
|
|
845
1157
|
}
|
|
846
1158
|
async function isPnpmInstalled() {
|
|
847
1159
|
try {
|
|
848
|
-
await
|
|
1160
|
+
await execa3("pnpm", ["--version"]);
|
|
849
1161
|
return true;
|
|
850
1162
|
} catch {
|
|
851
1163
|
return false;
|
|
@@ -853,31 +1165,31 @@ async function isPnpmInstalled() {
|
|
|
853
1165
|
}
|
|
854
1166
|
|
|
855
1167
|
// src/utils/git.ts
|
|
856
|
-
import { createLogger as
|
|
857
|
-
import { execa as
|
|
858
|
-
var
|
|
1168
|
+
import { createLogger as createLogger6 } from "@revealui/setup/utils";
|
|
1169
|
+
import { execa as execa4 } from "execa";
|
|
1170
|
+
var logger6 = createLogger6({ prefix: "Git" });
|
|
859
1171
|
async function initializeGitRepo(projectPath) {
|
|
860
1172
|
try {
|
|
861
|
-
await
|
|
862
|
-
|
|
1173
|
+
await execa4("git", ["init"], { cwd: projectPath });
|
|
1174
|
+
logger6.success("Initialized git repository");
|
|
863
1175
|
} catch (error) {
|
|
864
|
-
|
|
1176
|
+
logger6.warn("Failed to initialize git repository");
|
|
865
1177
|
throw error;
|
|
866
1178
|
}
|
|
867
1179
|
}
|
|
868
1180
|
async function createInitialCommit(projectPath) {
|
|
869
1181
|
try {
|
|
870
|
-
await
|
|
871
|
-
await
|
|
872
|
-
|
|
1182
|
+
await execa4("git", ["add", "."], { cwd: projectPath });
|
|
1183
|
+
await execa4("git", ["commit", "-m", "Initial commit from @revealui/cli"], { cwd: projectPath });
|
|
1184
|
+
logger6.success("Created initial commit");
|
|
873
1185
|
} catch (error) {
|
|
874
|
-
|
|
1186
|
+
logger6.warn("Failed to create initial commit");
|
|
875
1187
|
throw error;
|
|
876
1188
|
}
|
|
877
1189
|
}
|
|
878
1190
|
async function isGitInstalled() {
|
|
879
1191
|
try {
|
|
880
|
-
await
|
|
1192
|
+
await execa4("git", ["--version"]);
|
|
881
1193
|
return true;
|
|
882
1194
|
} catch {
|
|
883
1195
|
return false;
|
|
@@ -885,10 +1197,10 @@ async function isGitInstalled() {
|
|
|
885
1197
|
}
|
|
886
1198
|
|
|
887
1199
|
// src/commands/create.ts
|
|
888
|
-
var
|
|
1200
|
+
var logger7 = createLogger7({ prefix: "Create" });
|
|
889
1201
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
890
|
-
var __dirname2 =
|
|
891
|
-
var TEMPLATES_DIR = existsSync(
|
|
1202
|
+
var __dirname2 = path6.dirname(__filename2);
|
|
1203
|
+
var TEMPLATES_DIR = existsSync(path6.resolve(__dirname2, "../../templates")) ? path6.resolve(__dirname2, "../../templates") : path6.resolve(__dirname2, "../templates");
|
|
892
1204
|
function buildEnvLocal(cfg) {
|
|
893
1205
|
const lines = [
|
|
894
1206
|
"# Generated by @revealui/cli \u2014 fill in the remaining placeholders before running `pnpm dev`",
|
|
@@ -934,16 +1246,16 @@ function generateSecret() {
|
|
|
934
1246
|
}
|
|
935
1247
|
async function listAvailableTemplates() {
|
|
936
1248
|
try {
|
|
937
|
-
const entries = await
|
|
1249
|
+
const entries = await fs6.readdir(TEMPLATES_DIR, { withFileTypes: true });
|
|
938
1250
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
939
1251
|
} catch {
|
|
940
1252
|
return [];
|
|
941
1253
|
}
|
|
942
1254
|
}
|
|
943
1255
|
async function copyTemplate(templateName, targetPath) {
|
|
944
|
-
const templatePath =
|
|
1256
|
+
const templatePath = path6.join(TEMPLATES_DIR, templateName);
|
|
945
1257
|
try {
|
|
946
|
-
await
|
|
1258
|
+
await fs6.access(templatePath);
|
|
947
1259
|
} catch {
|
|
948
1260
|
const available = await listAvailableTemplates();
|
|
949
1261
|
const listing = available.length > 0 ? `Available templates: ${available.join(", ")}` : `No templates found in ${TEMPLATES_DIR}`;
|
|
@@ -952,16 +1264,16 @@ async function copyTemplate(templateName, targetPath) {
|
|
|
952
1264
|
await copyDir(templatePath, targetPath);
|
|
953
1265
|
}
|
|
954
1266
|
async function copyDir(src, dest) {
|
|
955
|
-
await
|
|
956
|
-
const entries = await
|
|
1267
|
+
await fs6.mkdir(dest, { recursive: true });
|
|
1268
|
+
const entries = await fs6.readdir(src, { withFileTypes: true });
|
|
957
1269
|
for (const entry of entries) {
|
|
958
|
-
const srcPath =
|
|
1270
|
+
const srcPath = path6.join(src, entry.name);
|
|
959
1271
|
const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
|
|
960
|
-
const destPath =
|
|
1272
|
+
const destPath = path6.join(dest, destName);
|
|
961
1273
|
if (entry.isDirectory()) {
|
|
962
1274
|
await copyDir(srcPath, destPath);
|
|
963
1275
|
} else {
|
|
964
|
-
await
|
|
1276
|
+
await fs6.copyFile(srcPath, destPath);
|
|
965
1277
|
}
|
|
966
1278
|
}
|
|
967
1279
|
}
|
|
@@ -974,7 +1286,17 @@ function resolveTemplateName(template) {
|
|
|
974
1286
|
return map[template] ?? "starter";
|
|
975
1287
|
}
|
|
976
1288
|
async function pullContentRules(projectPath) {
|
|
977
|
-
const
|
|
1289
|
+
const rawBaseUrl = process.env.REVEALUI_RULES_URL ?? "https://raw.githubusercontent.com/RevealUIStudio/editor-configs/main/harnesses";
|
|
1290
|
+
let baseUrl;
|
|
1291
|
+
try {
|
|
1292
|
+
const parsed = new URL(rawBaseUrl);
|
|
1293
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
baseUrl = rawBaseUrl;
|
|
1297
|
+
} catch {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
978
1300
|
const spinner = ora2("Pulling AI coding rules...").start();
|
|
979
1301
|
try {
|
|
980
1302
|
const manifestRes = await fetch(`${baseUrl}/manifest.json`, {
|
|
@@ -988,18 +1310,27 @@ async function pullContentRules(projectPath) {
|
|
|
988
1310
|
const generatorId = "claude-code";
|
|
989
1311
|
const ossDefs = manifest.definitions.filter((d) => d.tier === "oss");
|
|
990
1312
|
let written = 0;
|
|
1313
|
+
const allowedExtensions = /* @__PURE__ */ new Set([".md", ".json", ".txt", ".yaml", ".yml", ".ts", ".js"]);
|
|
1314
|
+
const maxFileSize = 1048576;
|
|
991
1315
|
for (const def of ossDefs) {
|
|
992
1316
|
const paths = def.generatorPaths[generatorId] ?? [];
|
|
993
1317
|
for (const relPath of paths) {
|
|
994
1318
|
try {
|
|
1319
|
+
if (relPath.includes("..") || relPath.startsWith("/")) continue;
|
|
1320
|
+
const ext = path6.extname(relPath).toLowerCase();
|
|
1321
|
+
if (!allowedExtensions.has(ext)) continue;
|
|
1322
|
+
const absolutePath = path6.resolve(projectPath, relPath);
|
|
1323
|
+
if (!absolutePath.startsWith(path6.resolve(projectPath))) continue;
|
|
995
1324
|
const fileRes = await fetch(`${baseUrl}/generators/${generatorId}/${relPath}`, {
|
|
996
1325
|
signal: AbortSignal.timeout(5e3)
|
|
997
1326
|
});
|
|
998
1327
|
if (!fileRes.ok) continue;
|
|
1328
|
+
const contentLength = fileRes.headers.get("content-length");
|
|
1329
|
+
if (contentLength && Number.parseInt(contentLength, 10) > maxFileSize) continue;
|
|
999
1330
|
const content = await fileRes.text();
|
|
1000
|
-
|
|
1001
|
-
await
|
|
1002
|
-
await
|
|
1331
|
+
if (content.length > maxFileSize) continue;
|
|
1332
|
+
await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
|
|
1333
|
+
await fs6.writeFile(absolutePath, content, "utf-8");
|
|
1003
1334
|
written++;
|
|
1004
1335
|
} catch {
|
|
1005
1336
|
}
|
|
@@ -1025,58 +1356,58 @@ async function createProject(cfg) {
|
|
|
1025
1356
|
spinner.fail("Failed to copy template files");
|
|
1026
1357
|
throw err;
|
|
1027
1358
|
}
|
|
1028
|
-
const pkgJsonPath =
|
|
1359
|
+
const pkgJsonPath = path6.join(projectPath, "package.json");
|
|
1029
1360
|
try {
|
|
1030
|
-
const raw = await
|
|
1031
|
-
await
|
|
1361
|
+
const raw = await fs6.readFile(pkgJsonPath, "utf-8");
|
|
1362
|
+
await fs6.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
|
|
1032
1363
|
} catch {
|
|
1033
1364
|
}
|
|
1034
1365
|
const envSpinner = ora2("Writing .env.local...").start();
|
|
1035
1366
|
try {
|
|
1036
|
-
await
|
|
1367
|
+
await fs6.writeFile(path6.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
|
|
1037
1368
|
envSpinner.succeed(".env.local written");
|
|
1038
1369
|
} catch (err) {
|
|
1039
1370
|
envSpinner.fail("Failed to write .env.local");
|
|
1040
1371
|
throw err;
|
|
1041
1372
|
}
|
|
1042
1373
|
await generateReadme(projectPath, project);
|
|
1043
|
-
|
|
1374
|
+
logger7.success("README.md generated");
|
|
1044
1375
|
if (cfg.devenv.createDevContainer) {
|
|
1045
1376
|
await generateDevContainer(projectPath);
|
|
1046
|
-
|
|
1377
|
+
logger7.success(".devcontainer/ generated");
|
|
1047
1378
|
}
|
|
1048
1379
|
if (cfg.devenv.createDevbox) {
|
|
1049
1380
|
await generateDevbox(projectPath);
|
|
1050
|
-
|
|
1381
|
+
logger7.success("devbox.json generated");
|
|
1051
1382
|
}
|
|
1052
1383
|
await pullContentRules(projectPath);
|
|
1053
1384
|
if (!skipInstall) {
|
|
1054
1385
|
const pnpmOk = await isPnpmInstalled();
|
|
1055
1386
|
if (!pnpmOk) {
|
|
1056
|
-
|
|
1387
|
+
logger7.warn(
|
|
1057
1388
|
"pnpm not found \u2014 skipping dependency installation. Run `pnpm install` manually."
|
|
1058
1389
|
);
|
|
1059
1390
|
} else {
|
|
1060
1391
|
await installDependencies(projectPath);
|
|
1061
1392
|
}
|
|
1062
1393
|
} else {
|
|
1063
|
-
|
|
1394
|
+
logger7.info("Skipping dependency installation (--skip-install)");
|
|
1064
1395
|
}
|
|
1065
1396
|
if (!skipGit) {
|
|
1066
1397
|
const gitOk = await isGitInstalled();
|
|
1067
1398
|
if (!gitOk) {
|
|
1068
|
-
|
|
1399
|
+
logger7.warn("git not found \u2014 skipping repository initialisation.");
|
|
1069
1400
|
} else {
|
|
1070
1401
|
await initializeGitRepo(projectPath);
|
|
1071
1402
|
await createInitialCommit(projectPath);
|
|
1072
1403
|
}
|
|
1073
1404
|
} else {
|
|
1074
|
-
|
|
1405
|
+
logger7.info("Skipping git initialisation (--skip-git)");
|
|
1075
1406
|
}
|
|
1076
1407
|
}
|
|
1077
1408
|
|
|
1078
1409
|
// src/commands/create-flow.ts
|
|
1079
|
-
var
|
|
1410
|
+
var logger8 = createLogger8({ prefix: "@revealui/cli" });
|
|
1080
1411
|
var PRO_TEMPLATES = /* @__PURE__ */ new Set([]);
|
|
1081
1412
|
async function checkProLicense() {
|
|
1082
1413
|
let key = process.env.REVEALUI_LICENSE_KEY;
|
|
@@ -1114,108 +1445,113 @@ async function checkProLicense() {
|
|
|
1114
1445
|
}
|
|
1115
1446
|
}
|
|
1116
1447
|
function printBanner() {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1448
|
+
logger8.divider();
|
|
1449
|
+
logger8.info(" RevealUI \u2014 Agentic Business Runtime");
|
|
1450
|
+
logger8.info(" Build your business, not your boilerplate.");
|
|
1451
|
+
logger8.divider();
|
|
1452
|
+
logger8.info("");
|
|
1122
1453
|
}
|
|
1123
1454
|
function printPostCreateSummary(projectName) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1455
|
+
logger8.divider();
|
|
1456
|
+
logger8.success("Your RevealUI project is ready!");
|
|
1457
|
+
logger8.info("");
|
|
1458
|
+
logger8.info(" Quick start:");
|
|
1459
|
+
logger8.info(` cd ${projectName}`);
|
|
1460
|
+
logger8.info(" pnpm install");
|
|
1461
|
+
logger8.info(" pnpm dev");
|
|
1462
|
+
logger8.info("");
|
|
1463
|
+
logger8.info(" What was created:");
|
|
1464
|
+
logger8.info(` ./${projectName}/ \u2014 project root`);
|
|
1465
|
+
logger8.info(` ./${projectName}/.env.local \u2014 environment variables (edit before pnpm dev)`);
|
|
1466
|
+
logger8.info(` ./${projectName}/README.md \u2014 getting started guide`);
|
|
1467
|
+
logger8.info("");
|
|
1468
|
+
logger8.info(" RevealUI ecosystem:");
|
|
1469
|
+
logger8.info(" Studio: Native AI experience \u2014 agent hub, local inference, dev environment");
|
|
1470
|
+
logger8.info(" Terminal: TUI client \u2014 run `revealui terminal install`");
|
|
1471
|
+
logger8.info(" CMS: Admin dashboard at your-domain.com/admin");
|
|
1472
|
+
logger8.info("");
|
|
1473
|
+
logger8.info(" Helpful links:");
|
|
1474
|
+
logger8.info(" Docs: https://docs.revealui.com");
|
|
1475
|
+
logger8.info(" GitHub: https://github.com/RevealUIStudio/revealui");
|
|
1476
|
+
logger8.info(" Support: support@revealui.com");
|
|
1477
|
+
logger8.divider();
|
|
1142
1478
|
}
|
|
1143
1479
|
function formatCreateError(err) {
|
|
1144
1480
|
const message = err instanceof Error ? err.message : String(err);
|
|
1145
1481
|
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
1146
|
-
|
|
1147
|
-
|
|
1482
|
+
logger8.error("Project creation failed.");
|
|
1483
|
+
logger8.info("");
|
|
1148
1484
|
if (code === "EACCES") {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1485
|
+
logger8.info(" Permission denied. Try:");
|
|
1486
|
+
logger8.info(" - Running from a directory you own");
|
|
1487
|
+
logger8.info(" - Checking folder permissions with `ls -la`");
|
|
1152
1488
|
} else if (code === "ENOENT") {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1489
|
+
logger8.info(" A required file or directory was not found.");
|
|
1490
|
+
logger8.info(" - Check that you are in the correct directory");
|
|
1491
|
+
logger8.info(" - Try running `revealui doctor` to diagnose your environment");
|
|
1156
1492
|
} else if (code === "ENOSPC") {
|
|
1157
|
-
|
|
1493
|
+
logger8.info(" Disk full. Free up space and try again.");
|
|
1158
1494
|
} else if (message.includes("fetch") || message.includes("ENOTFOUND") || message.includes("ETIMEDOUT")) {
|
|
1159
|
-
|
|
1495
|
+
logger8.info(" Network error. Check your internet connection and try again.");
|
|
1160
1496
|
} else {
|
|
1161
|
-
|
|
1497
|
+
logger8.info(` ${message}`);
|
|
1162
1498
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1499
|
+
logger8.info("");
|
|
1500
|
+
logger8.info(" Troubleshooting:");
|
|
1501
|
+
logger8.info(" revealui doctor \u2014 diagnose your environment");
|
|
1502
|
+
logger8.info(" https://docs.revealui.com/docs/TROUBLESHOOTING");
|
|
1503
|
+
logger8.info(" support@revealui.com \u2014 we are here to help");
|
|
1504
|
+
logger8.divider();
|
|
1169
1505
|
}
|
|
1170
1506
|
async function runCreateFlow(projectName, options) {
|
|
1171
1507
|
printBanner();
|
|
1172
1508
|
try {
|
|
1173
|
-
|
|
1509
|
+
logger8.info("[1/8] Validating Node.js version...");
|
|
1174
1510
|
if (!validateNodeVersion()) {
|
|
1175
1511
|
process.exit(1);
|
|
1176
1512
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1513
|
+
logger8.success(`Node.js version: ${process.version}`);
|
|
1514
|
+
logger8.info("[2/8] Configure your project");
|
|
1179
1515
|
const projectConfig = await promptProjectConfig(projectName, options.template, options.yes);
|
|
1180
|
-
|
|
1181
|
-
|
|
1516
|
+
logger8.success(`Project: ${projectConfig.projectName}`);
|
|
1517
|
+
logger8.success(`Template: ${projectConfig.template}`);
|
|
1182
1518
|
if (PRO_TEMPLATES.has(projectConfig.template)) {
|
|
1183
1519
|
if (!await checkProLicense()) {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1520
|
+
logger8.error(`The "${projectConfig.template}" template requires a RevealUI Pro license.`);
|
|
1521
|
+
logger8.info("Get Pro at https://revealui.com/pricing");
|
|
1522
|
+
logger8.info("Set your license key: export REVEALUI_LICENSE_KEY=<your-key>");
|
|
1187
1523
|
process.exit(2);
|
|
1188
1524
|
}
|
|
1189
|
-
|
|
1525
|
+
logger8.success("Pro license verified");
|
|
1190
1526
|
}
|
|
1191
1527
|
const nonInteractive = options.yes === true;
|
|
1192
|
-
|
|
1528
|
+
logger8.info("[3/8] Configure database");
|
|
1193
1529
|
const databaseConfig = nonInteractive ? { provider: "skip" } : await promptDatabaseConfig();
|
|
1194
1530
|
if (databaseConfig.provider !== "skip") {
|
|
1195
|
-
|
|
1531
|
+
logger8.success(`Database: ${databaseConfig.provider}`);
|
|
1196
1532
|
} else {
|
|
1197
|
-
|
|
1533
|
+
logger8.info("Database configuration skipped");
|
|
1198
1534
|
}
|
|
1199
|
-
|
|
1535
|
+
logger8.info("[4/8] Configure storage");
|
|
1200
1536
|
const storageConfig = nonInteractive ? { provider: "skip" } : await promptStorageConfig();
|
|
1201
1537
|
if (storageConfig.provider !== "skip") {
|
|
1202
|
-
|
|
1538
|
+
logger8.success(`Storage: ${storageConfig.provider}`);
|
|
1203
1539
|
} else {
|
|
1204
|
-
|
|
1540
|
+
logger8.info("Storage configuration skipped");
|
|
1205
1541
|
}
|
|
1206
|
-
|
|
1542
|
+
logger8.info("[5/8] Configure payments");
|
|
1207
1543
|
const paymentConfig = nonInteractive ? { enabled: false } : await promptPaymentConfig();
|
|
1208
1544
|
if (paymentConfig.enabled) {
|
|
1209
|
-
|
|
1545
|
+
logger8.success("Stripe configured");
|
|
1210
1546
|
} else {
|
|
1211
|
-
|
|
1547
|
+
logger8.info("Payments disabled");
|
|
1212
1548
|
}
|
|
1213
|
-
|
|
1549
|
+
logger8.info("[6/8] Configure development environment");
|
|
1214
1550
|
const devEnvConfig = nonInteractive ? { createDevContainer: false, createDevbox: false } : await promptDevEnvConfig();
|
|
1215
|
-
|
|
1551
|
+
logger8.success(
|
|
1216
1552
|
`Dev Container: ${devEnvConfig.createDevContainer ? "Yes" : "No"}, Devbox: ${devEnvConfig.createDevbox ? "Yes" : "No"}`
|
|
1217
1553
|
);
|
|
1218
|
-
|
|
1554
|
+
logger8.info("[7/8] Creating project...");
|
|
1219
1555
|
await createProject({
|
|
1220
1556
|
project: projectConfig,
|
|
1221
1557
|
database: databaseConfig,
|
|
@@ -1225,8 +1561,8 @@ async function runCreateFlow(projectName, options) {
|
|
|
1225
1561
|
skipGit: options.skipGit,
|
|
1226
1562
|
skipInstall: options.skipInstall
|
|
1227
1563
|
});
|
|
1228
|
-
|
|
1229
|
-
|
|
1564
|
+
logger8.success("Project created successfully");
|
|
1565
|
+
logger8.info("[8/8] Done!");
|
|
1230
1566
|
printPostCreateSummary(projectConfig.projectName);
|
|
1231
1567
|
} catch (err) {
|
|
1232
1568
|
formatCreateError(err);
|
|
@@ -1235,45 +1571,16 @@ async function runCreateFlow(projectName, options) {
|
|
|
1235
1571
|
}
|
|
1236
1572
|
|
|
1237
1573
|
// src/commands/db.ts
|
|
1238
|
-
import
|
|
1239
|
-
import
|
|
1240
|
-
import { createLogger as
|
|
1241
|
-
import { execa as
|
|
1242
|
-
|
|
1243
|
-
// src/utils/command.ts
|
|
1244
|
-
import net from "net";
|
|
1245
|
-
import { execa as execa3 } from "execa";
|
|
1246
|
-
async function commandExists(command) {
|
|
1247
|
-
try {
|
|
1248
|
-
await execa3("bash", ["-lc", `command -v ${command}`], {
|
|
1249
|
-
stdio: "pipe"
|
|
1250
|
-
});
|
|
1251
|
-
return true;
|
|
1252
|
-
} catch {
|
|
1253
|
-
return false;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
async function isTcpReachable(host, port, timeoutMs = 1500) {
|
|
1257
|
-
return await new Promise((resolve) => {
|
|
1258
|
-
const socket = new net.Socket();
|
|
1259
|
-
const finalize = (value) => {
|
|
1260
|
-
socket.removeAllListeners();
|
|
1261
|
-
socket.destroy();
|
|
1262
|
-
resolve(value);
|
|
1263
|
-
};
|
|
1264
|
-
socket.setTimeout(timeoutMs);
|
|
1265
|
-
socket.once("connect", () => finalize(true));
|
|
1266
|
-
socket.once("timeout", () => finalize(false));
|
|
1267
|
-
socket.once("error", () => finalize(false));
|
|
1268
|
-
socket.connect(port, host);
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1574
|
+
import fs9 from "fs/promises";
|
|
1575
|
+
import path9 from "path";
|
|
1576
|
+
import { createLogger as createLogger9 } from "@revealui/setup/utils";
|
|
1577
|
+
import { execa as execa5 } from "execa";
|
|
1271
1578
|
|
|
1272
1579
|
// src/utils/db.ts
|
|
1273
|
-
import
|
|
1274
|
-
import
|
|
1580
|
+
import fs7 from "fs/promises";
|
|
1581
|
+
import path7 from "path";
|
|
1275
1582
|
function resolveLocalDbConfig(cwd = process.cwd(), env = process.env) {
|
|
1276
|
-
const pgdata = env.PGDATA ||
|
|
1583
|
+
const pgdata = env.PGDATA || path7.join(cwd, ".pgdata");
|
|
1277
1584
|
const pghost = env.PGHOST || pgdata;
|
|
1278
1585
|
const pgdatabase = env.PGDATABASE || "postgres";
|
|
1279
1586
|
const pguser = env.PGUSER || "postgres";
|
|
@@ -1320,27 +1627,27 @@ host all all ::1/128 trust
|
|
|
1320
1627
|
`.trimStart();
|
|
1321
1628
|
}
|
|
1322
1629
|
async function writeLocalDbConfigs(pgdata) {
|
|
1323
|
-
await
|
|
1324
|
-
await
|
|
1630
|
+
await fs7.appendFile(path7.join(pgdata, "postgresql.conf"), buildPostgresConfig(pgdata), "utf8");
|
|
1631
|
+
await fs7.writeFile(path7.join(pgdata, "pg_hba.conf"), buildPgHbaConfig(), "utf8");
|
|
1325
1632
|
}
|
|
1326
1633
|
|
|
1327
1634
|
// src/utils/workspace.ts
|
|
1328
|
-
import
|
|
1329
|
-
import
|
|
1635
|
+
import fs8 from "fs";
|
|
1636
|
+
import path8 from "path";
|
|
1330
1637
|
function findWorkspaceRoot(startDir = process.cwd()) {
|
|
1331
|
-
let current =
|
|
1638
|
+
let current = path8.resolve(startDir);
|
|
1332
1639
|
while (true) {
|
|
1333
|
-
const packageJsonPath =
|
|
1334
|
-
if (
|
|
1640
|
+
const packageJsonPath = path8.join(current, "package.json");
|
|
1641
|
+
if (fs8.existsSync(packageJsonPath)) {
|
|
1335
1642
|
try {
|
|
1336
|
-
const pkg = JSON.parse(
|
|
1643
|
+
const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
|
|
1337
1644
|
if (pkg.name === "revealui" && pkg.private === true) {
|
|
1338
1645
|
return current;
|
|
1339
1646
|
}
|
|
1340
1647
|
} catch {
|
|
1341
1648
|
}
|
|
1342
1649
|
}
|
|
1343
|
-
const parent =
|
|
1650
|
+
const parent = path8.dirname(current);
|
|
1344
1651
|
if (parent === current) {
|
|
1345
1652
|
return null;
|
|
1346
1653
|
}
|
|
@@ -1349,7 +1656,7 @@ function findWorkspaceRoot(startDir = process.cwd()) {
|
|
|
1349
1656
|
}
|
|
1350
1657
|
|
|
1351
1658
|
// src/commands/db.ts
|
|
1352
|
-
var
|
|
1659
|
+
var logger9 = createLogger9({ prefix: "DB" });
|
|
1353
1660
|
function isPgCtlNotRunningError(error) {
|
|
1354
1661
|
return typeof error === "object" && error !== null && "exitCode" in error && error.exitCode === 3;
|
|
1355
1662
|
}
|
|
@@ -1390,18 +1697,18 @@ async function runDbInitCommand(options = {}) {
|
|
|
1390
1697
|
const pgdata = getPgDataOrThrow(env);
|
|
1391
1698
|
await requireCommand("initdb");
|
|
1392
1699
|
try {
|
|
1393
|
-
await
|
|
1700
|
+
await fs9.access(pgdata);
|
|
1394
1701
|
if (!options.force) {
|
|
1395
1702
|
throw new Error(`PostgreSQL is already initialized at ${pgdata}. Use --force to reset it.`);
|
|
1396
1703
|
}
|
|
1397
|
-
await
|
|
1704
|
+
await fs9.rm(pgdata, { recursive: true, force: true });
|
|
1398
1705
|
} catch (error) {
|
|
1399
1706
|
if (error instanceof Error && !error.message.includes("already initialized")) {
|
|
1400
1707
|
} else if (error instanceof Error) {
|
|
1401
1708
|
throw error;
|
|
1402
1709
|
}
|
|
1403
1710
|
}
|
|
1404
|
-
await
|
|
1711
|
+
await execa5(
|
|
1405
1712
|
"initdb",
|
|
1406
1713
|
["--locale=C.UTF-8", "--encoding=UTF8", "-D", pgdata, "--username=postgres"],
|
|
1407
1714
|
{
|
|
@@ -1410,17 +1717,17 @@ async function runDbInitCommand(options = {}) {
|
|
|
1410
1717
|
}
|
|
1411
1718
|
);
|
|
1412
1719
|
await writeLocalDbConfigs(pgdata);
|
|
1413
|
-
|
|
1720
|
+
logger9.success(`PostgreSQL initialized at ${pgdata}`);
|
|
1414
1721
|
}
|
|
1415
1722
|
async function runDbStartCommand() {
|
|
1416
1723
|
const root = getWorkspaceRootOrThrow();
|
|
1417
1724
|
const env = buildDbEnv(root);
|
|
1418
1725
|
const pgdata = getPgDataOrThrow(env);
|
|
1419
1726
|
await requireCommand("pg_ctl");
|
|
1420
|
-
await
|
|
1421
|
-
await
|
|
1727
|
+
await fs9.access(pgdata);
|
|
1728
|
+
await execa5(
|
|
1422
1729
|
"pg_ctl",
|
|
1423
|
-
["start", "-D", pgdata, "-l",
|
|
1730
|
+
["start", "-D", pgdata, "-l", path9.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
|
|
1424
1731
|
{
|
|
1425
1732
|
env,
|
|
1426
1733
|
stdio: "inherit"
|
|
@@ -1432,7 +1739,7 @@ async function runDbStopCommand() {
|
|
|
1432
1739
|
const env = buildDbEnv(root);
|
|
1433
1740
|
const pgdata = getPgDataOrThrow(env);
|
|
1434
1741
|
await requireCommand("pg_ctl");
|
|
1435
|
-
await
|
|
1742
|
+
await execa5("pg_ctl", ["stop", "-D", pgdata], {
|
|
1436
1743
|
env,
|
|
1437
1744
|
stdio: "inherit"
|
|
1438
1745
|
});
|
|
@@ -1443,13 +1750,13 @@ async function runDbStatusCommand() {
|
|
|
1443
1750
|
const pgdata = getPgDataOrThrow(env);
|
|
1444
1751
|
await requireCommand("pg_ctl");
|
|
1445
1752
|
try {
|
|
1446
|
-
await
|
|
1753
|
+
await execa5("pg_ctl", ["status", "-D", pgdata], {
|
|
1447
1754
|
env,
|
|
1448
1755
|
stdio: "inherit"
|
|
1449
1756
|
});
|
|
1450
1757
|
} catch (error) {
|
|
1451
1758
|
if (isPgCtlNotRunningError(error)) {
|
|
1452
|
-
|
|
1759
|
+
logger9.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
|
|
1453
1760
|
return;
|
|
1454
1761
|
}
|
|
1455
1762
|
throw error;
|
|
@@ -1464,17 +1771,17 @@ async function runDbResetCommand(options = {}) {
|
|
|
1464
1771
|
const pgdata = getPgDataOrThrow(env);
|
|
1465
1772
|
if (await commandExists("pg_ctl")) {
|
|
1466
1773
|
try {
|
|
1467
|
-
await
|
|
1774
|
+
await execa5("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
|
|
1468
1775
|
} catch {
|
|
1469
1776
|
}
|
|
1470
1777
|
}
|
|
1471
|
-
await
|
|
1778
|
+
await fs9.rm(pgdata, { recursive: true, force: true });
|
|
1472
1779
|
await runDbInitCommand({ force: false });
|
|
1473
1780
|
}
|
|
1474
1781
|
async function runDbMigrateCommand() {
|
|
1475
1782
|
const root = getWorkspaceRootOrThrow();
|
|
1476
1783
|
const env = buildDbEnv(root);
|
|
1477
|
-
await
|
|
1784
|
+
await execa5("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
|
|
1478
1785
|
cwd: root,
|
|
1479
1786
|
env,
|
|
1480
1787
|
stdio: "inherit"
|
|
@@ -1485,7 +1792,7 @@ async function runDbCleanupCommand(options = {}) {
|
|
|
1485
1792
|
const env = { ...process.env };
|
|
1486
1793
|
if (options.dryRun) env.DRY_RUN = "true";
|
|
1487
1794
|
if (options.tables) env.TABLES = options.tables;
|
|
1488
|
-
await
|
|
1795
|
+
await execa5("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
|
|
1489
1796
|
cwd: root,
|
|
1490
1797
|
env,
|
|
1491
1798
|
stdio: "inherit"
|
|
@@ -1493,8 +1800,8 @@ async function runDbCleanupCommand(options = {}) {
|
|
|
1493
1800
|
}
|
|
1494
1801
|
|
|
1495
1802
|
// src/commands/dev.ts
|
|
1496
|
-
import { createLogger as
|
|
1497
|
-
import { execa as
|
|
1803
|
+
import { createLogger as createLogger12 } from "@revealui/setup/utils";
|
|
1804
|
+
import { execa as execa7 } from "execa";
|
|
1498
1805
|
|
|
1499
1806
|
// src/runtime/doctor.ts
|
|
1500
1807
|
function getMcpDetail(env) {
|
|
@@ -1513,6 +1820,140 @@ function getMcpDetail(env) {
|
|
|
1513
1820
|
detail: `mcp credentials configured for ${configuredKeys.join(", ")}`
|
|
1514
1821
|
};
|
|
1515
1822
|
}
|
|
1823
|
+
var ENV_VAR_SPECS = [
|
|
1824
|
+
// Core
|
|
1825
|
+
{
|
|
1826
|
+
key: "REVEALUI_SECRET",
|
|
1827
|
+
label: "App secret",
|
|
1828
|
+
required: false
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
key: "REVEALUI_ADMIN_EMAIL",
|
|
1832
|
+
label: "Admin email",
|
|
1833
|
+
required: false
|
|
1834
|
+
},
|
|
1835
|
+
// Database
|
|
1836
|
+
{
|
|
1837
|
+
key: "POSTGRES_URL",
|
|
1838
|
+
label: "PostgreSQL URL",
|
|
1839
|
+
required: true,
|
|
1840
|
+
validate: validateNeonUrl
|
|
1841
|
+
},
|
|
1842
|
+
// Stripe
|
|
1843
|
+
{
|
|
1844
|
+
key: "STRIPE_SECRET_KEY",
|
|
1845
|
+
label: "Stripe secret key",
|
|
1846
|
+
required: false,
|
|
1847
|
+
validate: validateStripeKey
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
key: "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY",
|
|
1851
|
+
label: "Stripe publishable key",
|
|
1852
|
+
required: false,
|
|
1853
|
+
validate: (v) => ({
|
|
1854
|
+
valid: v.startsWith("pk_test_") || v.startsWith("pk_live_"),
|
|
1855
|
+
message: "Must start with pk_test_ or pk_live_"
|
|
1856
|
+
})
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
key: "STRIPE_WEBHOOK_SECRET",
|
|
1860
|
+
label: "Stripe webhook secret",
|
|
1861
|
+
required: false,
|
|
1862
|
+
validate: (v) => ({
|
|
1863
|
+
valid: v.startsWith("whsec_"),
|
|
1864
|
+
message: "Must start with whsec_"
|
|
1865
|
+
})
|
|
1866
|
+
},
|
|
1867
|
+
// Supabase
|
|
1868
|
+
{
|
|
1869
|
+
key: "NEXT_PUBLIC_SUPABASE_URL",
|
|
1870
|
+
label: "Supabase URL",
|
|
1871
|
+
required: false,
|
|
1872
|
+
validate: validateSupabaseUrl
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
key: "SUPABASE_SERVICE_ROLE_KEY",
|
|
1876
|
+
label: "Supabase service role key",
|
|
1877
|
+
required: false,
|
|
1878
|
+
validate: (v) => ({
|
|
1879
|
+
valid: v.startsWith("eyJ"),
|
|
1880
|
+
message: "Must be a JWT (starts with eyJ)"
|
|
1881
|
+
})
|
|
1882
|
+
},
|
|
1883
|
+
// Services
|
|
1884
|
+
{
|
|
1885
|
+
key: "RESEND_API_KEY",
|
|
1886
|
+
label: "Resend API key",
|
|
1887
|
+
required: false,
|
|
1888
|
+
validate: (v) => ({
|
|
1889
|
+
valid: v.startsWith("re_"),
|
|
1890
|
+
message: "Must start with re_"
|
|
1891
|
+
})
|
|
1892
|
+
},
|
|
1893
|
+
// npm
|
|
1894
|
+
{
|
|
1895
|
+
key: "NPM_TOKEN",
|
|
1896
|
+
label: "npm publish token",
|
|
1897
|
+
required: false,
|
|
1898
|
+
validate: validateNpmToken
|
|
1899
|
+
},
|
|
1900
|
+
// License
|
|
1901
|
+
{
|
|
1902
|
+
key: "REVEALUI_LICENSE_PRIVATE_KEY",
|
|
1903
|
+
label: "License signing key",
|
|
1904
|
+
required: false
|
|
1905
|
+
},
|
|
1906
|
+
// CRON
|
|
1907
|
+
{
|
|
1908
|
+
key: "REVEALUI_CRON_SECRET",
|
|
1909
|
+
label: "Cron secret",
|
|
1910
|
+
required: false
|
|
1911
|
+
},
|
|
1912
|
+
// AI (open-model inference)
|
|
1913
|
+
{
|
|
1914
|
+
key: "BITNET_BASE_URL",
|
|
1915
|
+
label: "BitNet base URL",
|
|
1916
|
+
required: false
|
|
1917
|
+
},
|
|
1918
|
+
{
|
|
1919
|
+
key: "OLLAMA_BASE_URL",
|
|
1920
|
+
label: "Ollama base URL",
|
|
1921
|
+
required: false
|
|
1922
|
+
}
|
|
1923
|
+
];
|
|
1924
|
+
function maskValue(value) {
|
|
1925
|
+
if (value.length <= 8) return "***";
|
|
1926
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
1927
|
+
}
|
|
1928
|
+
function gatherCredentialChecks(env) {
|
|
1929
|
+
const checks = [];
|
|
1930
|
+
for (const spec of ENV_VAR_SPECS) {
|
|
1931
|
+
const value = env[spec.key];
|
|
1932
|
+
if (!value) {
|
|
1933
|
+
checks.push({
|
|
1934
|
+
id: `env:${spec.key}`,
|
|
1935
|
+
ok: !spec.required,
|
|
1936
|
+
detail: spec.required ? `${spec.label} \u2014 missing (required)` : `${spec.label} \u2014 not set`
|
|
1937
|
+
});
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
if (spec.validate) {
|
|
1941
|
+
const result = spec.validate(value);
|
|
1942
|
+
checks.push({
|
|
1943
|
+
id: `env:${spec.key}`,
|
|
1944
|
+
ok: result.valid,
|
|
1945
|
+
detail: result.valid ? `${spec.label} \u2014 ${maskValue(value)}` : `${spec.label} \u2014 ${result.message} (got ${maskValue(value)})`
|
|
1946
|
+
});
|
|
1947
|
+
} else {
|
|
1948
|
+
checks.push({
|
|
1949
|
+
id: `env:${spec.key}`,
|
|
1950
|
+
ok: true,
|
|
1951
|
+
detail: `${spec.label} \u2014 set`
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return checks;
|
|
1956
|
+
}
|
|
1516
1957
|
async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
|
|
1517
1958
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
1518
1959
|
const dbConfig = resolveLocalDbConfig(workspaceRoot ?? cwd, env);
|
|
@@ -1526,6 +1967,7 @@ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
|
|
|
1526
1967
|
const dockerOk = await commandExists("docker");
|
|
1527
1968
|
const postgresReachable = dbTarget === "local" ? await isTcpReachable("127.0.0.1", 5432, 1e3) : false;
|
|
1528
1969
|
const mcp = getMcpDetail(env);
|
|
1970
|
+
const credentials = gatherCredentialChecks(env);
|
|
1529
1971
|
return {
|
|
1530
1972
|
workspaceRoot,
|
|
1531
1973
|
dbTarget,
|
|
@@ -1584,7 +2026,8 @@ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
|
|
|
1584
2026
|
id: "mcp",
|
|
1585
2027
|
ok: mcp.ok,
|
|
1586
2028
|
detail: mcp.detail
|
|
1587
|
-
}
|
|
2029
|
+
},
|
|
2030
|
+
...credentials
|
|
1588
2031
|
]
|
|
1589
2032
|
};
|
|
1590
2033
|
}
|
|
@@ -1597,22 +2040,22 @@ function formatDoctorReport(report) {
|
|
|
1597
2040
|
}
|
|
1598
2041
|
|
|
1599
2042
|
// src/utils/dev-config.ts
|
|
1600
|
-
import
|
|
1601
|
-
import
|
|
2043
|
+
import fs10 from "fs";
|
|
2044
|
+
import path10 from "path";
|
|
1602
2045
|
function getDevConfigPath(startDir = process.cwd()) {
|
|
1603
2046
|
const workspaceRoot = findWorkspaceRoot(startDir);
|
|
1604
2047
|
if (!workspaceRoot) {
|
|
1605
2048
|
return null;
|
|
1606
2049
|
}
|
|
1607
|
-
return
|
|
2050
|
+
return path10.join(workspaceRoot, ".revealui", "dev.json");
|
|
1608
2051
|
}
|
|
1609
2052
|
function readDevConfig(startDir = process.cwd()) {
|
|
1610
2053
|
const configPath = getDevConfigPath(startDir);
|
|
1611
|
-
if (!(configPath &&
|
|
2054
|
+
if (!(configPath && fs10.existsSync(configPath))) {
|
|
1612
2055
|
return {};
|
|
1613
2056
|
}
|
|
1614
2057
|
try {
|
|
1615
|
-
return JSON.parse(
|
|
2058
|
+
return JSON.parse(fs10.readFileSync(configPath, "utf8"));
|
|
1616
2059
|
} catch {
|
|
1617
2060
|
return {};
|
|
1618
2061
|
}
|
|
@@ -1622,15 +2065,15 @@ function writeDevConfig(config, startDir = process.cwd()) {
|
|
|
1622
2065
|
if (!configPath) {
|
|
1623
2066
|
throw new Error("RevealUI workspace root not found");
|
|
1624
2067
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
2068
|
+
fs10.mkdirSync(path10.dirname(configPath), { recursive: true });
|
|
2069
|
+
fs10.writeFileSync(`${configPath}`, `${JSON.stringify(config, null, 2)}
|
|
1627
2070
|
`, "utf8");
|
|
1628
2071
|
return configPath;
|
|
1629
2072
|
}
|
|
1630
2073
|
|
|
1631
2074
|
// src/commands/doctor.ts
|
|
1632
|
-
import { createLogger as
|
|
1633
|
-
var
|
|
2075
|
+
import { createLogger as createLogger10 } from "@revealui/setup/utils";
|
|
2076
|
+
var logger10 = createLogger10({ prefix: "Doctor" });
|
|
1634
2077
|
function getDoctorFixPlan(report) {
|
|
1635
2078
|
const attempted = [];
|
|
1636
2079
|
const skipped = [];
|
|
@@ -1688,7 +2131,7 @@ async function runDoctorCommand(options = {}) {
|
|
|
1688
2131
|
`);
|
|
1689
2132
|
if (options.fix) {
|
|
1690
2133
|
if (fixPlan.attempted.length === 0) {
|
|
1691
|
-
|
|
2134
|
+
logger10.warn("No safe automatic fixes available");
|
|
1692
2135
|
} else {
|
|
1693
2136
|
await applyDoctorFixes(report);
|
|
1694
2137
|
report = await gatherDoctorReport();
|
|
@@ -1697,7 +2140,7 @@ async function runDoctorCommand(options = {}) {
|
|
|
1697
2140
|
}
|
|
1698
2141
|
}
|
|
1699
2142
|
if (report.checks.some((check) => !check.ok)) {
|
|
1700
|
-
|
|
2143
|
+
logger10.warn("Some checks failed");
|
|
1701
2144
|
if (options.strict || options.json || process.env.CI) {
|
|
1702
2145
|
process.exitCode = 1;
|
|
1703
2146
|
}
|
|
@@ -1705,15 +2148,15 @@ async function runDoctorCommand(options = {}) {
|
|
|
1705
2148
|
}
|
|
1706
2149
|
|
|
1707
2150
|
// src/commands/shell.ts
|
|
1708
|
-
import { createLogger as
|
|
1709
|
-
import { execa as
|
|
1710
|
-
var
|
|
2151
|
+
import { createLogger as createLogger11 } from "@revealui/setup/utils";
|
|
2152
|
+
import { execa as execa6 } from "execa";
|
|
2153
|
+
var logger11 = createLogger11({ prefix: "Shell" });
|
|
1711
2154
|
async function runShellCommand(options = {}) {
|
|
1712
2155
|
if (!(options.inside || process.env.IN_NIX_SHELL) && await commandExists("nix")) {
|
|
1713
2156
|
const workspaceRoot = findWorkspaceRoot();
|
|
1714
2157
|
if (workspaceRoot) {
|
|
1715
2158
|
const reentryArgs = options.ensure ? ["dev", "up"] : ["dev", "status"];
|
|
1716
|
-
await
|
|
2159
|
+
await execa6(
|
|
1717
2160
|
"nix",
|
|
1718
2161
|
[
|
|
1719
2162
|
"develop",
|
|
@@ -1752,12 +2195,12 @@ async function runShellCommand(options = {}) {
|
|
|
1752
2195
|
}
|
|
1753
2196
|
process.stdout.write(`${formatDoctorReport(freshReport)}
|
|
1754
2197
|
`);
|
|
1755
|
-
|
|
2198
|
+
logger11.info("Use `revealui doctor --json` for machine-readable status.");
|
|
1756
2199
|
return false;
|
|
1757
2200
|
}
|
|
1758
2201
|
|
|
1759
2202
|
// src/commands/dev.ts
|
|
1760
|
-
var
|
|
2203
|
+
var logger12 = createLogger12({ prefix: "Dev" });
|
|
1761
2204
|
var DEV_PROFILES = {
|
|
1762
2205
|
local: {},
|
|
1763
2206
|
agent: {
|
|
@@ -1883,21 +2326,21 @@ async function runDevUpCommand(options = {}) {
|
|
|
1883
2326
|
process.stdout.write(`${formatDevActions(plan)}
|
|
1884
2327
|
`);
|
|
1885
2328
|
if (plan.dryRun) {
|
|
1886
|
-
|
|
2329
|
+
logger12.info("Dry run only; no migrations or services were started.");
|
|
1887
2330
|
return;
|
|
1888
2331
|
}
|
|
1889
2332
|
if (options.fix) {
|
|
1890
2333
|
await runDoctorCommand({ fix: true });
|
|
1891
2334
|
}
|
|
1892
2335
|
await runDbMigrateCommand();
|
|
1893
|
-
|
|
2336
|
+
logger12.success("Database migration complete");
|
|
1894
2337
|
if (shouldIncludeMcp(resolved.include)) {
|
|
1895
2338
|
const workspaceRoot = findWorkspaceRoot();
|
|
1896
2339
|
if (!workspaceRoot) {
|
|
1897
2340
|
throw new Error("RevealUI workspace root not found");
|
|
1898
2341
|
}
|
|
1899
|
-
|
|
1900
|
-
await
|
|
2342
|
+
logger12.info("Validating MCP setup");
|
|
2343
|
+
await execa7("pnpm", ["setup:mcp"], {
|
|
1901
2344
|
cwd: workspaceRoot,
|
|
1902
2345
|
stdio: "inherit"
|
|
1903
2346
|
});
|
|
@@ -1907,8 +2350,8 @@ async function runDevUpCommand(options = {}) {
|
|
|
1907
2350
|
if (!workspaceRoot) {
|
|
1908
2351
|
throw new Error("RevealUI workspace root not found");
|
|
1909
2352
|
}
|
|
1910
|
-
|
|
1911
|
-
await
|
|
2353
|
+
logger12.info(`Starting dev script: ${resolved.script}`);
|
|
2354
|
+
await execa7("pnpm", [resolved.script], {
|
|
1912
2355
|
cwd: workspaceRoot,
|
|
1913
2356
|
stdio: "inherit"
|
|
1914
2357
|
});
|
|
@@ -1965,7 +2408,7 @@ async function runDevProfileSetCommand(profile) {
|
|
|
1965
2408
|
);
|
|
1966
2409
|
}
|
|
1967
2410
|
const configPath = writeDevConfig({ defaultProfile: profile });
|
|
1968
|
-
|
|
2411
|
+
logger12.success(`Default dev profile set to "${profile}" in ${configPath}`);
|
|
1969
2412
|
}
|
|
1970
2413
|
async function runDevProfileShowCommand(options = {}) {
|
|
1971
2414
|
const configuredProfile = getConfiguredProfile() ?? null;
|
|
@@ -1978,11 +2421,223 @@ async function runDevProfileShowCommand(options = {}) {
|
|
|
1978
2421
|
`);
|
|
1979
2422
|
}
|
|
1980
2423
|
|
|
2424
|
+
// src/commands/terminal.ts
|
|
2425
|
+
import { copyFile, mkdir, readdir, stat } from "fs/promises";
|
|
2426
|
+
import { homedir as homedir2, platform } from "os";
|
|
2427
|
+
import { dirname, join as join2, resolve } from "path";
|
|
2428
|
+
import { createLogger as createLogger13 } from "@revealui/setup/utils";
|
|
2429
|
+
var logger13 = createLogger13({ prefix: "Terminal" });
|
|
2430
|
+
async function dirExists(path11) {
|
|
2431
|
+
try {
|
|
2432
|
+
const s = await stat(path11);
|
|
2433
|
+
return s.isDirectory();
|
|
2434
|
+
} catch {
|
|
2435
|
+
return false;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
async function fileExists(path11) {
|
|
2439
|
+
try {
|
|
2440
|
+
const s = await stat(path11);
|
|
2441
|
+
return s.isFile();
|
|
2442
|
+
} catch {
|
|
2443
|
+
return false;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
function getMacProfiles(home) {
|
|
2447
|
+
return [
|
|
2448
|
+
{
|
|
2449
|
+
name: "iTerm2",
|
|
2450
|
+
sourceFile: "iterm2-revealui.json",
|
|
2451
|
+
destPath: join2(
|
|
2452
|
+
home,
|
|
2453
|
+
"Library",
|
|
2454
|
+
"Application Support",
|
|
2455
|
+
"iTerm2",
|
|
2456
|
+
"DynamicProfiles",
|
|
2457
|
+
"revealui.json"
|
|
2458
|
+
),
|
|
2459
|
+
postInstall: 'Profile loaded automatically. Select "RevealUI" in iTerm2 > Settings > Profiles.',
|
|
2460
|
+
detect: async () => dirExists(join2(home, "Library", "Application Support", "iTerm2"))
|
|
2461
|
+
},
|
|
2462
|
+
{
|
|
2463
|
+
name: "Terminal.app",
|
|
2464
|
+
sourceFile: "Terminal.app-RevealUI.terminal",
|
|
2465
|
+
destPath: join2(home, "Desktop", "RevealUI.terminal"),
|
|
2466
|
+
postInstall: "Double-click ~/Desktop/RevealUI.terminal to import, then set as default in Terminal > Settings > Profiles.",
|
|
2467
|
+
detect: async () => fileExists("/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal")
|
|
2468
|
+
},
|
|
2469
|
+
{
|
|
2470
|
+
name: "Alacritty",
|
|
2471
|
+
sourceFile: "alacritty-revealui.toml",
|
|
2472
|
+
destPath: join2(home, ".config", "alacritty", "revealui.toml"),
|
|
2473
|
+
postInstall: 'Add `import = ["~/.config/alacritty/revealui.toml"]` to your alacritty.toml [general] section.',
|
|
2474
|
+
detect: async () => dirExists(join2(home, ".config", "alacritty"))
|
|
2475
|
+
},
|
|
2476
|
+
{
|
|
2477
|
+
name: "Kitty",
|
|
2478
|
+
sourceFile: "kitty-revealui.conf",
|
|
2479
|
+
destPath: join2(home, ".config", "kitty", "revealui.conf"),
|
|
2480
|
+
postInstall: "Add `include revealui.conf` to your ~/.config/kitty/kitty.conf.",
|
|
2481
|
+
detect: async () => dirExists(join2(home, ".config", "kitty"))
|
|
2482
|
+
}
|
|
2483
|
+
];
|
|
2484
|
+
}
|
|
2485
|
+
function getLinuxProfiles(home) {
|
|
2486
|
+
return [
|
|
2487
|
+
{
|
|
2488
|
+
name: "Alacritty",
|
|
2489
|
+
sourceFile: "alacritty-revealui.toml",
|
|
2490
|
+
destPath: join2(home, ".config", "alacritty", "revealui.toml"),
|
|
2491
|
+
postInstall: 'Add `import = ["~/.config/alacritty/revealui.toml"]` to your alacritty.toml [general] section.',
|
|
2492
|
+
detect: async () => dirExists(join2(home, ".config", "alacritty"))
|
|
2493
|
+
},
|
|
2494
|
+
{
|
|
2495
|
+
name: "Kitty",
|
|
2496
|
+
sourceFile: "kitty-revealui.conf",
|
|
2497
|
+
destPath: join2(home, ".config", "kitty", "revealui.conf"),
|
|
2498
|
+
postInstall: "Add `include revealui.conf` to your ~/.config/kitty/kitty.conf.",
|
|
2499
|
+
detect: async () => dirExists(join2(home, ".config", "kitty"))
|
|
2500
|
+
},
|
|
2501
|
+
{
|
|
2502
|
+
name: "GNOME Terminal",
|
|
2503
|
+
sourceFile: "gnome-terminal-revealui.dconf",
|
|
2504
|
+
destPath: join2(home, ".config", "revealui", "gnome-terminal-revealui.dconf"),
|
|
2505
|
+
postInstall: "Import with: dconf load /org/gnome/terminal/legacy/profiles:/ < ~/.config/revealui/gnome-terminal-revealui.dconf",
|
|
2506
|
+
detect: async () => {
|
|
2507
|
+
const gnomeConfigDir = join2(home, ".config", "dconf");
|
|
2508
|
+
return dirExists(gnomeConfigDir);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
];
|
|
2512
|
+
}
|
|
2513
|
+
async function findConfigDir() {
|
|
2514
|
+
const monorepoPath = resolve(
|
|
2515
|
+
dirname(new URL(import.meta.url).pathname),
|
|
2516
|
+
"..",
|
|
2517
|
+
"..",
|
|
2518
|
+
"..",
|
|
2519
|
+
"..",
|
|
2520
|
+
"config",
|
|
2521
|
+
"terminal"
|
|
2522
|
+
);
|
|
2523
|
+
if (await dirExists(monorepoPath)) return monorepoPath;
|
|
2524
|
+
const npmPath = resolve(dirname(new URL(import.meta.url).pathname), "..", "config", "terminal");
|
|
2525
|
+
if (await dirExists(npmPath)) return npmPath;
|
|
2526
|
+
return null;
|
|
2527
|
+
}
|
|
2528
|
+
async function runTerminalInstallCommand(options) {
|
|
2529
|
+
const os2 = platform();
|
|
2530
|
+
const home = homedir2();
|
|
2531
|
+
if (os2 !== "darwin" && os2 !== "linux") {
|
|
2532
|
+
logger13.error(
|
|
2533
|
+
`Unsupported platform: ${os2}. Terminal profiles are available for macOS and Linux.`
|
|
2534
|
+
);
|
|
2535
|
+
if (os2 === "win32") {
|
|
2536
|
+
logger13.info("For Windows Terminal, copy config/terminal/ profiles manually.");
|
|
2537
|
+
}
|
|
2538
|
+
process.exitCode = 1;
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
const profiles = os2 === "darwin" ? getMacProfiles(home) : getLinuxProfiles(home);
|
|
2542
|
+
if (options.list) {
|
|
2543
|
+
if (options.json) {
|
|
2544
|
+
const detected = await Promise.all(
|
|
2545
|
+
profiles.map(async (p) => ({
|
|
2546
|
+
name: p.name,
|
|
2547
|
+
sourceFile: p.sourceFile,
|
|
2548
|
+
destPath: p.destPath,
|
|
2549
|
+
detected: await p.detect()
|
|
2550
|
+
}))
|
|
2551
|
+
);
|
|
2552
|
+
process.stdout.write(`${JSON.stringify({ platform: os2, profiles: detected }, null, 2)}
|
|
2553
|
+
`);
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
logger13.header("Available Terminal Profiles");
|
|
2557
|
+
logger13.info(`Platform: ${os2 === "darwin" ? "macOS" : "Linux"}`);
|
|
2558
|
+
for (const profile of profiles) {
|
|
2559
|
+
const detected = await profile.detect();
|
|
2560
|
+
const icon = detected ? "[detected]" : "[not found]";
|
|
2561
|
+
logger13.info(` ${profile.name} ${icon} \u2014 ${profile.sourceFile}`);
|
|
2562
|
+
}
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
const configDir = await findConfigDir();
|
|
2566
|
+
if (!configDir) {
|
|
2567
|
+
logger13.error("Could not find config/terminal/ directory.");
|
|
2568
|
+
logger13.info(
|
|
2569
|
+
"Run this command from the RevealUI monorepo root, or install via npx create-revealui."
|
|
2570
|
+
);
|
|
2571
|
+
process.exitCode = 1;
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
const sourceFiles = await readdir(configDir);
|
|
2575
|
+
let targetProfiles = profiles;
|
|
2576
|
+
if (options.terminal) {
|
|
2577
|
+
const match = profiles.find((p) => p.name.toLowerCase() === options.terminal?.toLowerCase());
|
|
2578
|
+
if (!match) {
|
|
2579
|
+
logger13.error(`Unknown terminal: ${options.terminal}`);
|
|
2580
|
+
logger13.info(`Available: ${profiles.map((p) => p.name).join(", ")}`);
|
|
2581
|
+
process.exitCode = 1;
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
targetProfiles = [match];
|
|
2585
|
+
}
|
|
2586
|
+
let installed = 0;
|
|
2587
|
+
let skipped = 0;
|
|
2588
|
+
logger13.header("RevealUI Terminal Profile Installer");
|
|
2589
|
+
logger13.info(`Platform: ${os2 === "darwin" ? "macOS" : "Linux"}`);
|
|
2590
|
+
for (const profile of targetProfiles) {
|
|
2591
|
+
const detected = await profile.detect();
|
|
2592
|
+
if (!(detected || options.terminal)) {
|
|
2593
|
+
continue;
|
|
2594
|
+
}
|
|
2595
|
+
if (!detected && options.terminal) {
|
|
2596
|
+
logger13.warn(`${profile.name} not detected, installing anyway (--terminal flag).`);
|
|
2597
|
+
}
|
|
2598
|
+
if (!sourceFiles.includes(profile.sourceFile)) {
|
|
2599
|
+
logger13.warn(`Source file missing: ${profile.sourceFile} \u2014 skipping ${profile.name}`);
|
|
2600
|
+
skipped++;
|
|
2601
|
+
continue;
|
|
2602
|
+
}
|
|
2603
|
+
const sourcePath = join2(configDir, profile.sourceFile);
|
|
2604
|
+
const destPath = profile.destPath;
|
|
2605
|
+
if (await fileExists(destPath)) {
|
|
2606
|
+
if (!options.force) {
|
|
2607
|
+
logger13.warn(`${profile.name}: ${destPath} already exists. Use --force to overwrite.`);
|
|
2608
|
+
skipped++;
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
2613
|
+
await copyFile(sourcePath, destPath);
|
|
2614
|
+
installed++;
|
|
2615
|
+
logger13.success(`${profile.name}: installed to ${destPath}`);
|
|
2616
|
+
logger13.info(` ${profile.postInstall}`);
|
|
2617
|
+
}
|
|
2618
|
+
if (installed === 0 && skipped === 0) {
|
|
2619
|
+
logger13.info("No supported terminal emulators detected.");
|
|
2620
|
+
logger13.info(`Supported: ${profiles.map((p) => p.name).join(", ")}`);
|
|
2621
|
+
logger13.info("Use --terminal <name> to install for a specific terminal.");
|
|
2622
|
+
} else if (installed > 0) {
|
|
2623
|
+
logger13.success(
|
|
2624
|
+
`Installed ${installed} profile(s). ${skipped > 0 ? `Skipped ${skipped}.` : ""}`
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
if (options.json) {
|
|
2628
|
+
process.stdout.write(`${JSON.stringify({ installed, skipped, platform: os2 }, null, 2)}
|
|
2629
|
+
`);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
async function runTerminalListCommand(options) {
|
|
2633
|
+
await runTerminalInstallCommand({ list: true, json: options.json });
|
|
2634
|
+
}
|
|
2635
|
+
|
|
1981
2636
|
// src/cli.ts
|
|
1982
|
-
var
|
|
2637
|
+
var logger14 = createLogger14({ prefix: "CLI" });
|
|
1983
2638
|
function configureCreateCommand(command, legacyName) {
|
|
1984
2639
|
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) => {
|
|
1985
|
-
|
|
2640
|
+
logger14.header(legacyName ? "Create RevealUI Project" : "RevealUI Create");
|
|
1986
2641
|
await runCreateFlow(projectName, options);
|
|
1987
2642
|
});
|
|
1988
2643
|
return command;
|
|
@@ -2077,6 +2732,23 @@ function createCli() {
|
|
|
2077
2732
|
devProfile.command("show").description("Show the configured default dev profile").option("--json", "Output machine-readable JSON", false).action(async (options) => {
|
|
2078
2733
|
await runDevProfileShowCommand(options);
|
|
2079
2734
|
});
|
|
2735
|
+
const auth = program.command("auth").description("Manage npm registry authentication and publish tokens");
|
|
2736
|
+
auth.command("status").description("Show current npm authentication state").option("--json", "Output machine-readable JSON", false).action(async (options) => {
|
|
2737
|
+
await runAuthStatusCommand(options);
|
|
2738
|
+
});
|
|
2739
|
+
auth.command("set-token").description("Store an npm token via RevVault and configure .npmrc").option("--token <value>", "npm token (or set NPM_TOKEN env var)").option("--skip-vault", "Skip RevVault storage", false).option("--skip-npmrc", "Skip .npmrc modification", false).action(async (options) => {
|
|
2740
|
+
await runAuthSetTokenCommand(options);
|
|
2741
|
+
});
|
|
2742
|
+
auth.command("verify").description("Verify the full npm auth chain (env \u2192 RevVault \u2192 .npmrc \u2192 registry)").option("--json", "Output machine-readable JSON", false).action(async (options) => {
|
|
2743
|
+
await runAuthVerifyCommand(options);
|
|
2744
|
+
});
|
|
2745
|
+
const terminal = program.command("terminal").description("Install RevealUI terminal profiles for your terminal emulator");
|
|
2746
|
+
terminal.command("install").description("Detect and install terminal profiles for supported emulators").option("--terminal <name>", "Install for a specific terminal (e.g. iTerm2, Alacritty, Kitty)").option("--force", "Overwrite existing profile files", false).option("--json", "Output machine-readable JSON", false).action(async (options) => {
|
|
2747
|
+
await runTerminalInstallCommand(options);
|
|
2748
|
+
});
|
|
2749
|
+
terminal.command("list").description("List available terminal profiles and detected emulators").option("--json", "Output machine-readable JSON", false).action(async (options) => {
|
|
2750
|
+
await runTerminalListCommand(options);
|
|
2751
|
+
});
|
|
2080
2752
|
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) => {
|
|
2081
2753
|
await runDevUpCommand({
|
|
2082
2754
|
ensure: options.ensure,
|