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