@revealui/cli 0.3.3 → 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 +1432 -469
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1432 -469
- package/dist/index.js.map +1 -1
- package/package.json +16 -9
- package/templates/basic-blog/next.config.mjs +1 -3
- package/templates/basic-blog/package.json +1 -1
- package/templates/e-commerce/next.config.mjs +1 -3
- package/templates/e-commerce/package.json +1 -1
- package/templates/portfolio/next.config.mjs +1 -3
- package/templates/portfolio/package.json +1 -1
- package/templates/starter/next.config.mjs +1 -3
- package/templates/starter/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,21 +1,549 @@
|
|
|
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
|
+
// src/commands/agent.ts
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
import { createLogger } from "@revealui/setup/utils";
|
|
8
|
+
var logger = createLogger({ prefix: "agent" });
|
|
9
|
+
async function runAgentStatusCommand() {
|
|
10
|
+
const { available, provider, model, projectRoot } = await detectProvider();
|
|
11
|
+
logger.info(`Provider: ${provider}`);
|
|
12
|
+
logger.info(`Model: ${model}`);
|
|
13
|
+
logger.info(`Project root: ${projectRoot}`);
|
|
14
|
+
logger.info(`Available: ${available ? "yes" : "no"}`);
|
|
15
|
+
if (!available) {
|
|
16
|
+
logger.warn("No LLM provider detected. Start BitNet, Ollama, or set GROQ_API_KEY.");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function runAgentHeadlessCommand(prompt) {
|
|
20
|
+
const deps = await loadProDeps();
|
|
21
|
+
if (!deps) return;
|
|
22
|
+
const { StreamingAgentRuntime, createLLMClientFromEnv, createCodingTools } = deps;
|
|
23
|
+
const projectRoot = process.cwd();
|
|
24
|
+
const tools = createCodingTools({ projectRoot });
|
|
25
|
+
const llmClient = createLLMClientFromEnv();
|
|
26
|
+
const agent = {
|
|
27
|
+
id: "revealui-coding-agent",
|
|
28
|
+
name: "RevealUI Coding Agent",
|
|
29
|
+
instructions: buildMinimalInstructions(),
|
|
30
|
+
tools,
|
|
31
|
+
config: {},
|
|
32
|
+
getContext: () => ({ projectRoot, workingDirectory: projectRoot })
|
|
33
|
+
};
|
|
34
|
+
const task = {
|
|
35
|
+
id: `task-${Date.now()}`,
|
|
36
|
+
type: "headless-prompt",
|
|
37
|
+
description: prompt
|
|
38
|
+
};
|
|
39
|
+
const runtime = new StreamingAgentRuntime({
|
|
40
|
+
maxIterations: 10,
|
|
41
|
+
timeout: 12e4
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
for await (const chunk of runtime.streamTask(agent, task, llmClient)) {
|
|
45
|
+
switch (chunk.type) {
|
|
46
|
+
case "text":
|
|
47
|
+
if (chunk.content) process.stdout.write(chunk.content);
|
|
48
|
+
break;
|
|
49
|
+
case "tool_call_start":
|
|
50
|
+
if (chunk.toolCall) {
|
|
51
|
+
process.stderr.write(`
|
|
52
|
+
[tool] ${chunk.toolCall.name}
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
case "tool_call_result":
|
|
57
|
+
if (chunk.toolResult?.content) {
|
|
58
|
+
process.stderr.write(` \u2192 ${chunk.toolResult.content}
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
case "error":
|
|
63
|
+
process.stderr.write(`
|
|
64
|
+
[error] ${chunk.error}
|
|
65
|
+
`);
|
|
66
|
+
break;
|
|
67
|
+
case "done":
|
|
68
|
+
process.stdout.write("\n");
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
await runtime.cleanup();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function runAgentReplCommand() {
|
|
77
|
+
const deps = await loadProDeps();
|
|
78
|
+
if (!deps) return;
|
|
79
|
+
const { StreamingAgentRuntime, createLLMClientFromEnv, createCodingTools } = deps;
|
|
80
|
+
const projectRoot = process.cwd();
|
|
81
|
+
const tools = createCodingTools({ projectRoot });
|
|
82
|
+
const llmClient = createLLMClientFromEnv();
|
|
83
|
+
const agent = {
|
|
84
|
+
id: "revealui-coding-agent",
|
|
85
|
+
name: "RevealUI Coding Agent",
|
|
86
|
+
instructions: buildMinimalInstructions(),
|
|
87
|
+
tools,
|
|
88
|
+
config: {},
|
|
89
|
+
getContext: () => ({ projectRoot, workingDirectory: projectRoot })
|
|
90
|
+
};
|
|
91
|
+
logger.info('RevealUI Agent (type "exit" or Ctrl+C to quit)');
|
|
92
|
+
const rl = createInterface({
|
|
93
|
+
input: process.stdin,
|
|
94
|
+
output: process.stdout,
|
|
95
|
+
prompt: "\n> "
|
|
96
|
+
});
|
|
97
|
+
rl.prompt();
|
|
98
|
+
rl.on("line", async (line) => {
|
|
99
|
+
const input = line.trim();
|
|
100
|
+
if (!input) {
|
|
101
|
+
rl.prompt();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (input === "exit" || input === "quit") {
|
|
105
|
+
rl.close();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const task = {
|
|
109
|
+
id: `task-${Date.now()}`,
|
|
110
|
+
type: "repl-prompt",
|
|
111
|
+
description: input
|
|
112
|
+
};
|
|
113
|
+
const runtime = new StreamingAgentRuntime({
|
|
114
|
+
maxIterations: 10,
|
|
115
|
+
timeout: 12e4
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
for await (const chunk of runtime.streamTask(agent, task, llmClient)) {
|
|
119
|
+
switch (chunk.type) {
|
|
120
|
+
case "text":
|
|
121
|
+
if (chunk.content) process.stdout.write(chunk.content);
|
|
122
|
+
break;
|
|
123
|
+
case "tool_call_start":
|
|
124
|
+
if (chunk.toolCall) {
|
|
125
|
+
process.stderr.write(`
|
|
126
|
+
[tool] ${chunk.toolCall.name}
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case "tool_call_result":
|
|
131
|
+
if (chunk.toolResult?.content) {
|
|
132
|
+
process.stderr.write(` \u2192 ${chunk.toolResult.content}
|
|
133
|
+
`);
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case "error":
|
|
137
|
+
process.stderr.write(`
|
|
138
|
+
[error] ${chunk.error}
|
|
139
|
+
`);
|
|
140
|
+
break;
|
|
141
|
+
case "done":
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
147
|
+
} finally {
|
|
148
|
+
await runtime.cleanup();
|
|
149
|
+
}
|
|
150
|
+
rl.prompt();
|
|
151
|
+
});
|
|
152
|
+
rl.on("close", () => {
|
|
153
|
+
process.exit(0);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async function loadProDeps() {
|
|
157
|
+
const aiRuntimePath = "@revealui/ai/orchestration/streaming-runtime";
|
|
158
|
+
const aiClientPath = "@revealui/ai/llm/client";
|
|
159
|
+
const aiToolsPath = "@revealui/ai/tools/coding";
|
|
160
|
+
try {
|
|
161
|
+
const [runtimeMod, clientMod, toolsMod] = await Promise.all([
|
|
162
|
+
import(aiRuntimePath),
|
|
163
|
+
import(aiClientPath),
|
|
164
|
+
import(aiToolsPath)
|
|
165
|
+
]);
|
|
166
|
+
return {
|
|
167
|
+
StreamingAgentRuntime: runtimeMod.StreamingAgentRuntime,
|
|
168
|
+
createLLMClientFromEnv: clientMod.createLLMClientFromEnv,
|
|
169
|
+
createCodingTools: toolsMod.createCodingTools
|
|
170
|
+
};
|
|
171
|
+
} catch {
|
|
172
|
+
logger.error(
|
|
173
|
+
"Pro packages not found. Install @revealui/ai to use the agent.\n npm install @revealui/ai"
|
|
174
|
+
);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function detectProvider() {
|
|
179
|
+
const projectRoot = process.cwd();
|
|
180
|
+
if (process.env.BITNET_BASE_URL) {
|
|
181
|
+
try {
|
|
182
|
+
const res = await fetch(`${process.env.BITNET_BASE_URL}/v1/models`, {
|
|
183
|
+
signal: AbortSignal.timeout(2e3)
|
|
184
|
+
});
|
|
185
|
+
if (res.ok) {
|
|
186
|
+
return { available: true, provider: "bitnet", model: "bitnet-b1.58-2B-4T", projectRoot };
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const ollamaUrl = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434";
|
|
192
|
+
try {
|
|
193
|
+
const res = await fetch(`${ollamaUrl}/api/tags`, {
|
|
194
|
+
signal: AbortSignal.timeout(2e3)
|
|
195
|
+
});
|
|
196
|
+
if (res.ok) {
|
|
197
|
+
return { available: true, provider: "ollama", model: "llama3.2:3b", projectRoot };
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
if (process.env.GROQ_API_KEY) {
|
|
202
|
+
return { available: true, provider: "groq", model: "llama-3.3-70b-versatile", projectRoot };
|
|
203
|
+
}
|
|
204
|
+
return { available: false, provider: "none", model: "none", projectRoot };
|
|
205
|
+
}
|
|
206
|
+
function buildMinimalInstructions() {
|
|
207
|
+
return [
|
|
208
|
+
"You are the RevealUI coding agent. You help with software development tasks in this project.",
|
|
209
|
+
"Use the available tools to read, write, edit, search, and execute commands.",
|
|
210
|
+
"Always read files before modifying them. Prefer surgical edits over full rewrites.",
|
|
211
|
+
"Follow project conventions discovered via the project_context tool.",
|
|
212
|
+
"Be concise and direct. Show your work through tool usage, not verbose explanations."
|
|
213
|
+
].join("\n");
|
|
214
|
+
}
|
|
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
|
+
|
|
5
533
|
// src/commands/create-flow.ts
|
|
6
534
|
import { readFileSync } from "fs";
|
|
7
535
|
import { homedir } from "os";
|
|
8
536
|
import { join } from "path";
|
|
9
|
-
import { createLogger as
|
|
537
|
+
import { createLogger as createLogger8 } from "@revealui/setup/utils";
|
|
10
538
|
import { importSPKI, jwtVerify } from "jose";
|
|
11
539
|
|
|
12
540
|
// src/prompts/database.ts
|
|
13
|
-
import
|
|
541
|
+
import { isCancel, select, text } from "@clack/prompts";
|
|
14
542
|
|
|
15
543
|
// src/validators/credentials.ts
|
|
16
|
-
import { createLogger } from "@revealui/setup/utils";
|
|
17
|
-
var
|
|
18
|
-
|
|
544
|
+
import { createLogger as createLogger3 } from "@revealui/setup/utils";
|
|
545
|
+
var logger3 = createLogger3({ prefix: "Validator" });
|
|
546
|
+
function validateStripeKey(key) {
|
|
19
547
|
if (!(key.startsWith("sk_test_") || key.startsWith("sk_live_"))) {
|
|
20
548
|
return {
|
|
21
549
|
valid: false,
|
|
@@ -24,7 +552,7 @@ async function validateStripeKey(key) {
|
|
|
24
552
|
}
|
|
25
553
|
return { valid: true };
|
|
26
554
|
}
|
|
27
|
-
|
|
555
|
+
function validateNeonUrl(url) {
|
|
28
556
|
try {
|
|
29
557
|
const parsed = new URL(url);
|
|
30
558
|
if (!parsed.protocol.startsWith("postgres")) {
|
|
@@ -41,7 +569,7 @@ async function validateNeonUrl(url) {
|
|
|
41
569
|
};
|
|
42
570
|
}
|
|
43
571
|
}
|
|
44
|
-
|
|
572
|
+
function validateVercelToken(token) {
|
|
45
573
|
if (!token || token.length < 20) {
|
|
46
574
|
return {
|
|
47
575
|
valid: false,
|
|
@@ -50,11 +578,11 @@ async function validateVercelToken(token) {
|
|
|
50
578
|
}
|
|
51
579
|
return { valid: true };
|
|
52
580
|
}
|
|
53
|
-
|
|
581
|
+
function validateSupabaseUrl(url) {
|
|
54
582
|
try {
|
|
55
583
|
const parsed = new URL(url);
|
|
56
584
|
if (!parsed.hostname.includes("supabase")) {
|
|
57
|
-
|
|
585
|
+
logger3.warn("URL does not appear to be a Supabase URL");
|
|
58
586
|
}
|
|
59
587
|
return { valid: true };
|
|
60
588
|
} catch {
|
|
@@ -64,150 +592,152 @@ async function validateSupabaseUrl(url) {
|
|
|
64
592
|
};
|
|
65
593
|
}
|
|
66
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
|
+
}
|
|
67
610
|
|
|
68
611
|
// src/prompts/database.ts
|
|
69
612
|
async function promptDatabaseConfig() {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: "Local PostgreSQL - Use existing local database",
|
|
86
|
-
value: "local"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
name: "Skip - Configure later",
|
|
90
|
-
value: "skip"
|
|
91
|
-
}
|
|
92
|
-
],
|
|
93
|
-
default: "neon"
|
|
94
|
-
}
|
|
95
|
-
]);
|
|
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
|
+
}
|
|
96
626
|
if (provider === "skip") {
|
|
97
627
|
return { provider: "skip" };
|
|
98
628
|
}
|
|
99
629
|
if (provider === "local") {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const result = await validateNeonUrl(input);
|
|
108
|
-
return result.valid ? true : result.message || "Invalid database URL";
|
|
109
|
-
}
|
|
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";
|
|
110
637
|
}
|
|
111
|
-
|
|
638
|
+
});
|
|
639
|
+
if (isCancel(postgresUrl2)) {
|
|
640
|
+
process.exit(0);
|
|
641
|
+
}
|
|
112
642
|
return { provider: "local", postgresUrl: postgresUrl2 };
|
|
113
643
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!input || input.trim() === "") {
|
|
121
|
-
return "Database URL is required";
|
|
122
|
-
}
|
|
123
|
-
const result = await validateNeonUrl(input);
|
|
124
|
-
return result.valid ? true : result.message || "Invalid database URL";
|
|
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";
|
|
125
650
|
}
|
|
651
|
+
const result = validateNeonUrl(input);
|
|
652
|
+
return result.valid ? void 0 : result.message || "Invalid database URL";
|
|
126
653
|
}
|
|
127
|
-
|
|
654
|
+
});
|
|
655
|
+
if (isCancel(postgresUrl)) {
|
|
656
|
+
process.exit(0);
|
|
657
|
+
}
|
|
128
658
|
return { provider, postgresUrl };
|
|
129
659
|
}
|
|
130
660
|
|
|
131
661
|
// src/prompts/devenv.ts
|
|
132
|
-
import
|
|
662
|
+
import { confirm, isCancel as isCancel2 } from "@clack/prompts";
|
|
133
663
|
async function promptDevEnvConfig() {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
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 };
|
|
149
679
|
}
|
|
150
680
|
|
|
151
681
|
// src/prompts/payments.ts
|
|
152
|
-
import
|
|
682
|
+
import { confirm as confirm2, isCancel as isCancel3, text as text2 } from "@clack/prompts";
|
|
153
683
|
async function promptPaymentConfig() {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
]);
|
|
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
|
+
}
|
|
162
691
|
if (!enabled) {
|
|
163
692
|
return { enabled: false };
|
|
164
693
|
}
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
validate: async (input) => {
|
|
171
|
-
if (!input || input.trim() === "") {
|
|
172
|
-
return "Stripe secret key is required";
|
|
173
|
-
}
|
|
174
|
-
const result = await validateStripeKey(input);
|
|
175
|
-
return result.valid ? true : result.message || "Invalid Stripe key";
|
|
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";
|
|
176
699
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
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";
|
|
190
712
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
|
|
196
|
-
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;
|
|
197
717
|
}
|
|
198
|
-
|
|
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
|
+
}
|
|
199
729
|
return {
|
|
200
730
|
enabled: true,
|
|
201
|
-
stripeSecretKey
|
|
202
|
-
stripePublishableKey
|
|
203
|
-
stripeWebhookSecret:
|
|
731
|
+
stripeSecretKey,
|
|
732
|
+
stripePublishableKey,
|
|
733
|
+
stripeWebhookSecret: stripeWebhookSecret || void 0
|
|
204
734
|
};
|
|
205
735
|
}
|
|
206
736
|
|
|
207
737
|
// src/prompts/project.ts
|
|
208
|
-
import
|
|
209
|
-
import
|
|
210
|
-
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";
|
|
211
741
|
var VALID_TEMPLATES = ["basic-blog", "e-commerce", "portfolio"];
|
|
212
742
|
async function promptProjectConfig(defaultName, templateArg, nonInteractive = false) {
|
|
213
743
|
let projectName;
|
|
@@ -216,25 +746,31 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
|
|
|
216
746
|
} else if (nonInteractive) {
|
|
217
747
|
projectName = "my-revealui-project";
|
|
218
748
|
} else {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
return true;
|
|
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";
|
|
234
762
|
}
|
|
763
|
+
const projectPath = path2.resolve(process.cwd(), input);
|
|
764
|
+
if (fs2.existsSync(projectPath)) {
|
|
765
|
+
return `Directory "${input}" already exists`;
|
|
766
|
+
}
|
|
767
|
+
return void 0;
|
|
235
768
|
}
|
|
236
|
-
|
|
237
|
-
|
|
769
|
+
});
|
|
770
|
+
if (isCancel4(name)) {
|
|
771
|
+
process.exit(0);
|
|
772
|
+
}
|
|
773
|
+
projectName = name;
|
|
238
774
|
}
|
|
239
775
|
let template;
|
|
240
776
|
if (templateArg && VALID_TEMPLATES.includes(templateArg)) {
|
|
@@ -242,118 +778,106 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
|
|
|
242
778
|
} else if (nonInteractive) {
|
|
243
779
|
template = "basic-blog";
|
|
244
780
|
} else {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
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;
|
|
259
794
|
}
|
|
260
795
|
return {
|
|
261
796
|
projectName,
|
|
262
|
-
projectPath:
|
|
797
|
+
projectPath: path2.resolve(process.cwd(), projectName),
|
|
263
798
|
template
|
|
264
799
|
};
|
|
265
800
|
}
|
|
266
801
|
|
|
267
802
|
// src/prompts/storage.ts
|
|
268
|
-
import
|
|
803
|
+
import { isCancel as isCancel5, select as select3, text as text4 } from "@clack/prompts";
|
|
269
804
|
async function promptStorageConfig() {
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
value: "supabase"
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: "Skip - Configure later",
|
|
286
|
-
value: "skip"
|
|
287
|
-
}
|
|
288
|
-
],
|
|
289
|
-
default: "vercel-blob"
|
|
290
|
-
}
|
|
291
|
-
]);
|
|
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
|
+
}
|
|
292
817
|
if (provider === "skip") {
|
|
293
818
|
return { provider: "skip" };
|
|
294
819
|
}
|
|
295
820
|
if (provider === "vercel-blob") {
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
validate: async (input) => {
|
|
302
|
-
if (!input || input.trim() === "") {
|
|
303
|
-
return "Blob token is required";
|
|
304
|
-
}
|
|
305
|
-
const result = await validateVercelToken(input);
|
|
306
|
-
return result.valid ? true : result.message || "Invalid token";
|
|
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";
|
|
307
826
|
}
|
|
827
|
+
const result = validateVercelToken(input);
|
|
828
|
+
return result.valid ? void 0 : result.message || "Invalid token";
|
|
308
829
|
}
|
|
309
|
-
|
|
830
|
+
});
|
|
831
|
+
if (isCancel5(blobToken)) {
|
|
832
|
+
process.exit(0);
|
|
833
|
+
}
|
|
310
834
|
return { provider: "vercel-blob", blobToken };
|
|
311
835
|
}
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
validate: async (input) => {
|
|
318
|
-
if (!input || input.trim() === "") {
|
|
319
|
-
return "Supabase URL is required";
|
|
320
|
-
}
|
|
321
|
-
const result = await validateSupabaseUrl(input);
|
|
322
|
-
return result.valid ? true : result.message || "Invalid URL";
|
|
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";
|
|
323
841
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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";
|
|
334
854
|
}
|
|
855
|
+
return void 0;
|
|
335
856
|
}
|
|
336
|
-
|
|
857
|
+
});
|
|
858
|
+
if (isCancel5(supabasePublishableKey)) {
|
|
859
|
+
process.exit(0);
|
|
860
|
+
}
|
|
337
861
|
return {
|
|
338
862
|
provider: "supabase",
|
|
339
|
-
supabaseUrl
|
|
340
|
-
supabasePublishableKey
|
|
863
|
+
supabaseUrl,
|
|
864
|
+
supabasePublishableKey
|
|
341
865
|
};
|
|
342
866
|
}
|
|
343
867
|
|
|
344
868
|
// src/validators/node-version.ts
|
|
345
|
-
import { createLogger as
|
|
346
|
-
var
|
|
869
|
+
import { createLogger as createLogger4 } from "@revealui/setup/utils";
|
|
870
|
+
var logger4 = createLogger4({ prefix: "Setup" });
|
|
347
871
|
var REQUIRED_NODE_VERSION = "24.13.0";
|
|
348
872
|
function validateNodeVersion() {
|
|
349
873
|
const currentVersion = process.version.slice(1);
|
|
350
874
|
const [currentMajor, currentMinor] = currentVersion.split(".").map(Number);
|
|
351
875
|
const [requiredMajor, requiredMinor] = REQUIRED_NODE_VERSION.split(".").map(Number);
|
|
352
876
|
if (currentMajor < requiredMajor || currentMajor === requiredMajor && currentMinor < requiredMinor) {
|
|
353
|
-
|
|
877
|
+
logger4.error(
|
|
354
878
|
`Node.js ${REQUIRED_NODE_VERSION} or higher is required. You have ${currentVersion}.`
|
|
355
879
|
);
|
|
356
|
-
|
|
880
|
+
logger4.info("Please upgrade Node.js: https://nodejs.org/");
|
|
357
881
|
return false;
|
|
358
882
|
}
|
|
359
883
|
return true;
|
|
@@ -362,15 +886,15 @@ function validateNodeVersion() {
|
|
|
362
886
|
// src/commands/create.ts
|
|
363
887
|
import crypto from "crypto";
|
|
364
888
|
import { existsSync } from "fs";
|
|
365
|
-
import
|
|
366
|
-
import
|
|
889
|
+
import fs6 from "fs/promises";
|
|
890
|
+
import path6 from "path";
|
|
367
891
|
import { fileURLToPath } from "url";
|
|
368
|
-
import { createLogger as
|
|
892
|
+
import { createLogger as createLogger7 } from "@revealui/setup/utils";
|
|
369
893
|
import ora2 from "ora";
|
|
370
894
|
|
|
371
895
|
// src/generators/devbox.ts
|
|
372
|
-
import
|
|
373
|
-
import
|
|
896
|
+
import fs3 from "fs/promises";
|
|
897
|
+
import path3 from "path";
|
|
374
898
|
async function generateDevbox(projectPath) {
|
|
375
899
|
const devboxConfig = {
|
|
376
900
|
packages: ["nodejs@24.13.0", "pnpm@10.28.2", "postgresql@16", "stripe-cli@latest"],
|
|
@@ -390,19 +914,19 @@ async function generateDevbox(projectPath) {
|
|
|
390
914
|
NODE_ENV: "development"
|
|
391
915
|
}
|
|
392
916
|
};
|
|
393
|
-
await
|
|
394
|
-
|
|
917
|
+
await fs3.writeFile(
|
|
918
|
+
path3.join(projectPath, "devbox.json"),
|
|
395
919
|
JSON.stringify(devboxConfig, null, 2),
|
|
396
920
|
"utf-8"
|
|
397
921
|
);
|
|
398
922
|
}
|
|
399
923
|
|
|
400
924
|
// src/generators/devcontainer.ts
|
|
401
|
-
import
|
|
402
|
-
import
|
|
925
|
+
import fs4 from "fs/promises";
|
|
926
|
+
import path4 from "path";
|
|
403
927
|
async function generateDevContainer(projectPath) {
|
|
404
|
-
const devcontainerDir =
|
|
405
|
-
await
|
|
928
|
+
const devcontainerDir = path4.join(projectPath, ".devcontainer");
|
|
929
|
+
await fs4.mkdir(devcontainerDir, { recursive: true });
|
|
406
930
|
const devcontainerConfig = {
|
|
407
931
|
name: "RevealUI Development",
|
|
408
932
|
image: "mcr.microsoft.com/devcontainers/typescript-node:24",
|
|
@@ -449,8 +973,8 @@ async function generateDevContainer(projectPath) {
|
|
|
449
973
|
},
|
|
450
974
|
remoteUser: "node"
|
|
451
975
|
};
|
|
452
|
-
await
|
|
453
|
-
|
|
976
|
+
await fs4.writeFile(
|
|
977
|
+
path4.join(devcontainerDir, "devcontainer.json"),
|
|
454
978
|
JSON.stringify(devcontainerConfig, null, 2),
|
|
455
979
|
"utf-8"
|
|
456
980
|
);
|
|
@@ -479,7 +1003,7 @@ services:
|
|
|
479
1003
|
volumes:
|
|
480
1004
|
postgres-data:
|
|
481
1005
|
`;
|
|
482
|
-
await
|
|
1006
|
+
await fs4.writeFile(path4.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
|
|
483
1007
|
const dockerfile = `FROM mcr.microsoft.com/devcontainers/typescript-node:24
|
|
484
1008
|
|
|
485
1009
|
# Install additional tools
|
|
@@ -489,7 +1013,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\
|
|
|
489
1013
|
# Enable pnpm
|
|
490
1014
|
RUN corepack enable
|
|
491
1015
|
`;
|
|
492
|
-
await
|
|
1016
|
+
await fs4.writeFile(path4.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
|
|
493
1017
|
const readme = `# Dev Container Setup
|
|
494
1018
|
|
|
495
1019
|
This directory contains the Dev Container configuration for RevealUI.
|
|
@@ -531,12 +1055,12 @@ For GitHub Codespaces, set secrets in your repository settings.
|
|
|
531
1055
|
- 4000: CMS
|
|
532
1056
|
- 5432: PostgreSQL database
|
|
533
1057
|
`;
|
|
534
|
-
await
|
|
1058
|
+
await fs4.writeFile(path4.join(devcontainerDir, "README.md"), readme, "utf-8");
|
|
535
1059
|
}
|
|
536
1060
|
|
|
537
1061
|
// src/generators/readme.ts
|
|
538
|
-
import
|
|
539
|
-
import
|
|
1062
|
+
import fs5 from "fs/promises";
|
|
1063
|
+
import path5 from "path";
|
|
540
1064
|
async function generateReadme(projectPath, projectConfig) {
|
|
541
1065
|
const readme = `# ${projectConfig.projectName}
|
|
542
1066
|
|
|
@@ -563,70 +1087,42 @@ Run the development server:
|
|
|
563
1087
|
pnpm dev
|
|
564
1088
|
\`\`\`
|
|
565
1089
|
|
|
566
|
-
Open [http://localhost:4000](http://localhost:4000) with your browser
|
|
567
|
-
|
|
568
|
-
The web application runs on [http://localhost:3000](http://localhost:3000).
|
|
1090
|
+
Open [http://localhost:4000](http://localhost:4000) with your browser.
|
|
569
1091
|
|
|
570
|
-
##
|
|
1092
|
+
## Requirements
|
|
571
1093
|
|
|
572
|
-
### Standard Setup
|
|
573
|
-
|
|
574
|
-
Requirements:
|
|
575
1094
|
- Node.js 24.13.0 or higher
|
|
576
|
-
- pnpm 10
|
|
577
|
-
- PostgreSQL 16
|
|
578
|
-
|
|
579
|
-
### Dev Containers
|
|
580
|
-
|
|
581
|
-
Open in VS Code and select "Reopen in Container", or use GitHub Codespaces.
|
|
582
|
-
|
|
583
|
-
### Devbox
|
|
584
|
-
|
|
585
|
-
Install Devbox:
|
|
586
|
-
|
|
587
|
-
\`\`\`bash
|
|
588
|
-
curl -fsSL https://get.jetpack.io/devbox | bash
|
|
589
|
-
\`\`\`
|
|
590
|
-
|
|
591
|
-
Then start the Devbox shell:
|
|
592
|
-
|
|
593
|
-
\`\`\`bash
|
|
594
|
-
devbox shell
|
|
595
|
-
pnpm dev
|
|
596
|
-
\`\`\`
|
|
1095
|
+
- pnpm 10 or higher
|
|
1096
|
+
- PostgreSQL 16 (or use a hosted provider like [Neon](https://neon.tech))
|
|
597
1097
|
|
|
598
1098
|
## Project Structure
|
|
599
1099
|
|
|
600
1100
|
\`\`\`
|
|
601
1101
|
${projectConfig.projectName}/
|
|
602
|
-
\u251C\u2500\u2500
|
|
603
|
-
\u2502 \u251C\u2500\u2500
|
|
604
|
-
\u2502 \
|
|
605
|
-
\
|
|
606
|
-
\
|
|
607
|
-
\
|
|
608
|
-
\
|
|
609
|
-
\u251C\u2500\u2500 .devcontainer/ # Dev Container configuration
|
|
610
|
-
\u251C\u2500\u2500 devbox.json # Devbox configuration
|
|
611
|
-
\u2514\u2500\u2500 .env.development.local # Environment variables
|
|
1102
|
+
\u251C\u2500\u2500 src/
|
|
1103
|
+
\u2502 \u251C\u2500\u2500 app/ # Next.js App Router pages
|
|
1104
|
+
\u2502 \u251C\u2500\u2500 collections/ # RevealUI collection definitions
|
|
1105
|
+
\u2502 \u2514\u2500\u2500 seed.ts # Database seed script
|
|
1106
|
+
\u251C\u2500\u2500 revealui.config.ts # RevealUI configuration
|
|
1107
|
+
\u251C\u2500\u2500 next.config.mjs # Next.js configuration
|
|
1108
|
+
\u2514\u2500\u2500 .env.local # Environment variables (git-ignored)
|
|
612
1109
|
\`\`\`
|
|
613
1110
|
|
|
614
1111
|
## Available Scripts
|
|
615
1112
|
|
|
616
|
-
- \`pnpm dev\` - Start development
|
|
1113
|
+
- \`pnpm dev\` - Start the development server
|
|
617
1114
|
- \`pnpm build\` - Build for production
|
|
618
1115
|
- \`pnpm test\` - Run tests
|
|
619
|
-
- \`pnpm lint\` -
|
|
1116
|
+
- \`pnpm lint\` - Lint with Biome
|
|
620
1117
|
- \`pnpm typecheck\` - Type check
|
|
621
|
-
- \`pnpm db:init\` - Initialize database
|
|
1118
|
+
- \`pnpm db:init\` - Initialize the database
|
|
622
1119
|
- \`pnpm db:migrate\` - Run migrations
|
|
623
|
-
- \`pnpm db:seed\` - Seed
|
|
1120
|
+
- \`pnpm db:seed\` - Seed sample content
|
|
624
1121
|
|
|
625
1122
|
## Learn More
|
|
626
1123
|
|
|
627
|
-
- [RevealUI Documentation](https://
|
|
1124
|
+
- [RevealUI Documentation](https://docs.revealui.com)
|
|
628
1125
|
- [Next.js Documentation](https://nextjs.org/docs)
|
|
629
|
-
- [Hono Documentation](https://hono.dev)
|
|
630
1126
|
|
|
631
1127
|
## Template
|
|
632
1128
|
|
|
@@ -636,31 +1132,31 @@ This project was created using the **${projectConfig.template}** template.
|
|
|
636
1132
|
|
|
637
1133
|
MIT
|
|
638
1134
|
`;
|
|
639
|
-
await
|
|
1135
|
+
await fs5.writeFile(path5.join(projectPath, "README.md"), readme, "utf-8");
|
|
640
1136
|
}
|
|
641
1137
|
|
|
642
1138
|
// src/installers/dependencies.ts
|
|
643
|
-
import { createLogger as
|
|
644
|
-
import { execa } from "execa";
|
|
1139
|
+
import { createLogger as createLogger5 } from "@revealui/setup/utils";
|
|
1140
|
+
import { execa as execa3 } from "execa";
|
|
645
1141
|
import ora from "ora";
|
|
646
|
-
var
|
|
1142
|
+
var logger5 = createLogger5({ prefix: "Install" });
|
|
647
1143
|
async function installDependencies(projectPath) {
|
|
648
1144
|
const spinner = ora("Installing dependencies with pnpm...").start();
|
|
649
1145
|
try {
|
|
650
|
-
await
|
|
1146
|
+
await execa3("pnpm", ["install"], {
|
|
651
1147
|
cwd: projectPath,
|
|
652
1148
|
stdio: "pipe"
|
|
653
1149
|
});
|
|
654
1150
|
spinner.succeed("Dependencies installed successfully");
|
|
655
1151
|
} catch (error) {
|
|
656
1152
|
spinner.fail("Failed to install dependencies");
|
|
657
|
-
|
|
1153
|
+
logger5.error('Please run "pnpm install" manually');
|
|
658
1154
|
throw error;
|
|
659
1155
|
}
|
|
660
1156
|
}
|
|
661
1157
|
async function isPnpmInstalled() {
|
|
662
1158
|
try {
|
|
663
|
-
await
|
|
1159
|
+
await execa3("pnpm", ["--version"]);
|
|
664
1160
|
return true;
|
|
665
1161
|
} catch {
|
|
666
1162
|
return false;
|
|
@@ -668,31 +1164,31 @@ async function isPnpmInstalled() {
|
|
|
668
1164
|
}
|
|
669
1165
|
|
|
670
1166
|
// src/utils/git.ts
|
|
671
|
-
import { createLogger as
|
|
672
|
-
import { execa as
|
|
673
|
-
var
|
|
1167
|
+
import { createLogger as createLogger6 } from "@revealui/setup/utils";
|
|
1168
|
+
import { execa as execa4 } from "execa";
|
|
1169
|
+
var logger6 = createLogger6({ prefix: "Git" });
|
|
674
1170
|
async function initializeGitRepo(projectPath) {
|
|
675
1171
|
try {
|
|
676
|
-
await
|
|
677
|
-
|
|
1172
|
+
await execa4("git", ["init"], { cwd: projectPath });
|
|
1173
|
+
logger6.success("Initialized git repository");
|
|
678
1174
|
} catch (error) {
|
|
679
|
-
|
|
1175
|
+
logger6.warn("Failed to initialize git repository");
|
|
680
1176
|
throw error;
|
|
681
1177
|
}
|
|
682
1178
|
}
|
|
683
1179
|
async function createInitialCommit(projectPath) {
|
|
684
1180
|
try {
|
|
685
|
-
await
|
|
686
|
-
await
|
|
687
|
-
|
|
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");
|
|
688
1184
|
} catch (error) {
|
|
689
|
-
|
|
1185
|
+
logger6.warn("Failed to create initial commit");
|
|
690
1186
|
throw error;
|
|
691
1187
|
}
|
|
692
1188
|
}
|
|
693
1189
|
async function isGitInstalled() {
|
|
694
1190
|
try {
|
|
695
|
-
await
|
|
1191
|
+
await execa4("git", ["--version"]);
|
|
696
1192
|
return true;
|
|
697
1193
|
} catch {
|
|
698
1194
|
return false;
|
|
@@ -700,10 +1196,10 @@ async function isGitInstalled() {
|
|
|
700
1196
|
}
|
|
701
1197
|
|
|
702
1198
|
// src/commands/create.ts
|
|
703
|
-
var
|
|
1199
|
+
var logger7 = createLogger7({ prefix: "Create" });
|
|
704
1200
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
705
|
-
var __dirname2 =
|
|
706
|
-
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");
|
|
707
1203
|
function buildEnvLocal(cfg) {
|
|
708
1204
|
const lines = [
|
|
709
1205
|
"# Generated by @revealui/cli \u2014 fill in the remaining placeholders before running `pnpm dev`",
|
|
@@ -749,16 +1245,16 @@ function generateSecret() {
|
|
|
749
1245
|
}
|
|
750
1246
|
async function listAvailableTemplates() {
|
|
751
1247
|
try {
|
|
752
|
-
const entries = await
|
|
1248
|
+
const entries = await fs6.readdir(TEMPLATES_DIR, { withFileTypes: true });
|
|
753
1249
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
754
1250
|
} catch {
|
|
755
1251
|
return [];
|
|
756
1252
|
}
|
|
757
1253
|
}
|
|
758
1254
|
async function copyTemplate(templateName, targetPath) {
|
|
759
|
-
const templatePath =
|
|
1255
|
+
const templatePath = path6.join(TEMPLATES_DIR, templateName);
|
|
760
1256
|
try {
|
|
761
|
-
await
|
|
1257
|
+
await fs6.access(templatePath);
|
|
762
1258
|
} catch {
|
|
763
1259
|
const available = await listAvailableTemplates();
|
|
764
1260
|
const listing = available.length > 0 ? `Available templates: ${available.join(", ")}` : `No templates found in ${TEMPLATES_DIR}`;
|
|
@@ -767,16 +1263,16 @@ async function copyTemplate(templateName, targetPath) {
|
|
|
767
1263
|
await copyDir(templatePath, targetPath);
|
|
768
1264
|
}
|
|
769
1265
|
async function copyDir(src, dest) {
|
|
770
|
-
await
|
|
771
|
-
const entries = await
|
|
1266
|
+
await fs6.mkdir(dest, { recursive: true });
|
|
1267
|
+
const entries = await fs6.readdir(src, { withFileTypes: true });
|
|
772
1268
|
for (const entry of entries) {
|
|
773
|
-
const srcPath =
|
|
1269
|
+
const srcPath = path6.join(src, entry.name);
|
|
774
1270
|
const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
|
|
775
|
-
const destPath =
|
|
1271
|
+
const destPath = path6.join(dest, destName);
|
|
776
1272
|
if (entry.isDirectory()) {
|
|
777
1273
|
await copyDir(srcPath, destPath);
|
|
778
1274
|
} else {
|
|
779
|
-
await
|
|
1275
|
+
await fs6.copyFile(srcPath, destPath);
|
|
780
1276
|
}
|
|
781
1277
|
}
|
|
782
1278
|
}
|
|
@@ -788,6 +1284,66 @@ function resolveTemplateName(template) {
|
|
|
788
1284
|
};
|
|
789
1285
|
return map[template] ?? "starter";
|
|
790
1286
|
}
|
|
1287
|
+
async function pullContentRules(projectPath) {
|
|
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
|
+
}
|
|
1299
|
+
const spinner = ora2("Pulling AI coding rules...").start();
|
|
1300
|
+
try {
|
|
1301
|
+
const manifestRes = await fetch(`${baseUrl}/manifest.json`, {
|
|
1302
|
+
signal: AbortSignal.timeout(1e4)
|
|
1303
|
+
});
|
|
1304
|
+
if (!manifestRes.ok) {
|
|
1305
|
+
spinner.info("AI rules not available \u2014 skipping");
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
const manifest = await manifestRes.json();
|
|
1309
|
+
const generatorId = "claude-code";
|
|
1310
|
+
const ossDefs = manifest.definitions.filter((d) => d.tier === "oss");
|
|
1311
|
+
let written = 0;
|
|
1312
|
+
const allowedExtensions = /* @__PURE__ */ new Set([".md", ".json", ".txt", ".yaml", ".yml", ".ts", ".js"]);
|
|
1313
|
+
const maxFileSize = 1048576;
|
|
1314
|
+
for (const def of ossDefs) {
|
|
1315
|
+
const paths = def.generatorPaths[generatorId] ?? [];
|
|
1316
|
+
for (const relPath of paths) {
|
|
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;
|
|
1323
|
+
const fileRes = await fetch(`${baseUrl}/generators/${generatorId}/${relPath}`, {
|
|
1324
|
+
signal: AbortSignal.timeout(5e3)
|
|
1325
|
+
});
|
|
1326
|
+
if (!fileRes.ok) continue;
|
|
1327
|
+
const contentLength = fileRes.headers.get("content-length");
|
|
1328
|
+
if (contentLength && Number.parseInt(contentLength, 10) > maxFileSize) continue;
|
|
1329
|
+
const content = await fileRes.text();
|
|
1330
|
+
if (content.length > maxFileSize) continue;
|
|
1331
|
+
await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
|
|
1332
|
+
await fs6.writeFile(absolutePath, content, "utf-8");
|
|
1333
|
+
written++;
|
|
1334
|
+
} catch {
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
if (written > 0) {
|
|
1339
|
+
spinner.succeed(`Pulled ${written} AI coding rules`);
|
|
1340
|
+
} else {
|
|
1341
|
+
spinner.info("No AI rules pulled");
|
|
1342
|
+
}
|
|
1343
|
+
} catch {
|
|
1344
|
+
spinner.info("AI rules not available \u2014 skipping");
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
791
1347
|
async function createProject(cfg) {
|
|
792
1348
|
const { project, skipGit = false, skipInstall = false } = cfg;
|
|
793
1349
|
const { projectPath, projectName, template } = project;
|
|
@@ -799,57 +1355,58 @@ async function createProject(cfg) {
|
|
|
799
1355
|
spinner.fail("Failed to copy template files");
|
|
800
1356
|
throw err;
|
|
801
1357
|
}
|
|
802
|
-
const pkgJsonPath =
|
|
1358
|
+
const pkgJsonPath = path6.join(projectPath, "package.json");
|
|
803
1359
|
try {
|
|
804
|
-
const raw = await
|
|
805
|
-
await
|
|
1360
|
+
const raw = await fs6.readFile(pkgJsonPath, "utf-8");
|
|
1361
|
+
await fs6.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
|
|
806
1362
|
} catch {
|
|
807
1363
|
}
|
|
808
1364
|
const envSpinner = ora2("Writing .env.local...").start();
|
|
809
1365
|
try {
|
|
810
|
-
await
|
|
1366
|
+
await fs6.writeFile(path6.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
|
|
811
1367
|
envSpinner.succeed(".env.local written");
|
|
812
1368
|
} catch (err) {
|
|
813
1369
|
envSpinner.fail("Failed to write .env.local");
|
|
814
1370
|
throw err;
|
|
815
1371
|
}
|
|
816
1372
|
await generateReadme(projectPath, project);
|
|
817
|
-
|
|
1373
|
+
logger7.success("README.md generated");
|
|
818
1374
|
if (cfg.devenv.createDevContainer) {
|
|
819
1375
|
await generateDevContainer(projectPath);
|
|
820
|
-
|
|
1376
|
+
logger7.success(".devcontainer/ generated");
|
|
821
1377
|
}
|
|
822
1378
|
if (cfg.devenv.createDevbox) {
|
|
823
1379
|
await generateDevbox(projectPath);
|
|
824
|
-
|
|
1380
|
+
logger7.success("devbox.json generated");
|
|
825
1381
|
}
|
|
1382
|
+
await pullContentRules(projectPath);
|
|
826
1383
|
if (!skipInstall) {
|
|
827
1384
|
const pnpmOk = await isPnpmInstalled();
|
|
828
1385
|
if (!pnpmOk) {
|
|
829
|
-
|
|
1386
|
+
logger7.warn(
|
|
830
1387
|
"pnpm not found \u2014 skipping dependency installation. Run `pnpm install` manually."
|
|
831
1388
|
);
|
|
832
1389
|
} else {
|
|
833
1390
|
await installDependencies(projectPath);
|
|
834
1391
|
}
|
|
835
1392
|
} else {
|
|
836
|
-
|
|
1393
|
+
logger7.info("Skipping dependency installation (--skip-install)");
|
|
837
1394
|
}
|
|
838
1395
|
if (!skipGit) {
|
|
839
1396
|
const gitOk = await isGitInstalled();
|
|
840
1397
|
if (!gitOk) {
|
|
841
|
-
|
|
1398
|
+
logger7.warn("git not found \u2014 skipping repository initialisation.");
|
|
842
1399
|
} else {
|
|
843
1400
|
await initializeGitRepo(projectPath);
|
|
844
1401
|
await createInitialCommit(projectPath);
|
|
845
1402
|
}
|
|
846
1403
|
} else {
|
|
847
|
-
|
|
1404
|
+
logger7.info("Skipping git initialisation (--skip-git)");
|
|
848
1405
|
}
|
|
849
1406
|
}
|
|
850
1407
|
|
|
851
1408
|
// src/commands/create-flow.ts
|
|
852
|
-
var
|
|
1409
|
+
var logger8 = createLogger8({ prefix: "@revealui/cli" });
|
|
853
1410
|
var PRO_TEMPLATES = /* @__PURE__ */ new Set([]);
|
|
854
1411
|
async function checkProLicense() {
|
|
855
1412
|
let key = process.env.REVEALUI_LICENSE_KEY;
|
|
@@ -862,7 +1419,7 @@ async function checkProLicense() {
|
|
|
862
1419
|
}
|
|
863
1420
|
}
|
|
864
1421
|
if (!key) return false;
|
|
865
|
-
const publicKeyPem = process.env.REVEALUI_LICENSE_PUBLIC_KEY;
|
|
1422
|
+
const publicKeyPem = process.env.REVEALUI_LICENSE_PUBLIC_KEY?.replace(/\\n/g, "\n");
|
|
866
1423
|
if (publicKeyPem) {
|
|
867
1424
|
try {
|
|
868
1425
|
const publicKey = await importSPKI(publicKeyPem, "RS256");
|
|
@@ -886,112 +1443,143 @@ async function checkProLicense() {
|
|
|
886
1443
|
return false;
|
|
887
1444
|
}
|
|
888
1445
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1446
|
+
function printBanner() {
|
|
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("");
|
|
1452
|
+
}
|
|
1453
|
+
function printPostCreateSummary(projectName) {
|
|
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();
|
|
1477
|
+
}
|
|
1478
|
+
function formatCreateError(err) {
|
|
1479
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1480
|
+
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
1481
|
+
logger8.error("Project creation failed.");
|
|
1482
|
+
logger8.info("");
|
|
1483
|
+
if (code === "EACCES") {
|
|
1484
|
+
logger8.info(" Permission denied. Try:");
|
|
1485
|
+
logger8.info(" - Running from a directory you own");
|
|
1486
|
+
logger8.info(" - Checking folder permissions with `ls -la`");
|
|
1487
|
+
} else if (code === "ENOENT") {
|
|
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");
|
|
1491
|
+
} else if (code === "ENOSPC") {
|
|
1492
|
+
logger8.info(" Disk full. Free up space and try again.");
|
|
1493
|
+
} else if (message.includes("fetch") || message.includes("ENOTFOUND") || message.includes("ETIMEDOUT")) {
|
|
1494
|
+
logger8.info(" Network error. Check your internet connection and try again.");
|
|
927
1495
|
} else {
|
|
928
|
-
|
|
1496
|
+
logger8.info(` ${message}`);
|
|
929
1497
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
);
|
|
935
|
-
|
|
936
|
-
await createProject({
|
|
937
|
-
project: projectConfig,
|
|
938
|
-
database: databaseConfig,
|
|
939
|
-
storage: storageConfig,
|
|
940
|
-
payment: paymentConfig,
|
|
941
|
-
devenv: devEnvConfig,
|
|
942
|
-
skipGit: options.skipGit,
|
|
943
|
-
skipInstall: options.skipInstall
|
|
944
|
-
});
|
|
945
|
-
logger6.success("Project created successfully");
|
|
946
|
-
logger6.info("[8/8] Next steps");
|
|
947
|
-
logger6.divider();
|
|
948
|
-
logger6.info(`cd ${projectConfig.projectName}`);
|
|
949
|
-
logger6.info("pnpm install");
|
|
950
|
-
logger6.info("pnpm dev");
|
|
951
|
-
logger6.divider();
|
|
952
|
-
logger6.success(`Project ${projectConfig.projectName} created successfully`);
|
|
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();
|
|
953
1504
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
import fs8 from "fs/promises";
|
|
957
|
-
import path8 from "path";
|
|
958
|
-
import { createLogger as createLogger7 } from "@revealui/setup/utils";
|
|
959
|
-
import { execa as execa4 } from "execa";
|
|
960
|
-
|
|
961
|
-
// src/utils/command.ts
|
|
962
|
-
import net from "net";
|
|
963
|
-
import { execa as execa3 } from "execa";
|
|
964
|
-
async function commandExists(command) {
|
|
1505
|
+
async function runCreateFlow(projectName, options) {
|
|
1506
|
+
printBanner();
|
|
965
1507
|
try {
|
|
966
|
-
|
|
967
|
-
|
|
1508
|
+
logger8.info("[1/8] Validating Node.js version...");
|
|
1509
|
+
if (!validateNodeVersion()) {
|
|
1510
|
+
process.exit(1);
|
|
1511
|
+
}
|
|
1512
|
+
logger8.success(`Node.js version: ${process.version}`);
|
|
1513
|
+
logger8.info("[2/8] Configure your project");
|
|
1514
|
+
const projectConfig = await promptProjectConfig(projectName, options.template, options.yes);
|
|
1515
|
+
logger8.success(`Project: ${projectConfig.projectName}`);
|
|
1516
|
+
logger8.success(`Template: ${projectConfig.template}`);
|
|
1517
|
+
if (PRO_TEMPLATES.has(projectConfig.template)) {
|
|
1518
|
+
if (!await checkProLicense()) {
|
|
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>");
|
|
1522
|
+
process.exit(2);
|
|
1523
|
+
}
|
|
1524
|
+
logger8.success("Pro license verified");
|
|
1525
|
+
}
|
|
1526
|
+
const nonInteractive = options.yes === true;
|
|
1527
|
+
logger8.info("[3/8] Configure database");
|
|
1528
|
+
const databaseConfig = nonInteractive ? { provider: "skip" } : await promptDatabaseConfig();
|
|
1529
|
+
if (databaseConfig.provider !== "skip") {
|
|
1530
|
+
logger8.success(`Database: ${databaseConfig.provider}`);
|
|
1531
|
+
} else {
|
|
1532
|
+
logger8.info("Database configuration skipped");
|
|
1533
|
+
}
|
|
1534
|
+
logger8.info("[4/8] Configure storage");
|
|
1535
|
+
const storageConfig = nonInteractive ? { provider: "skip" } : await promptStorageConfig();
|
|
1536
|
+
if (storageConfig.provider !== "skip") {
|
|
1537
|
+
logger8.success(`Storage: ${storageConfig.provider}`);
|
|
1538
|
+
} else {
|
|
1539
|
+
logger8.info("Storage configuration skipped");
|
|
1540
|
+
}
|
|
1541
|
+
logger8.info("[5/8] Configure payments");
|
|
1542
|
+
const paymentConfig = nonInteractive ? { enabled: false } : await promptPaymentConfig();
|
|
1543
|
+
if (paymentConfig.enabled) {
|
|
1544
|
+
logger8.success("Stripe configured");
|
|
1545
|
+
} else {
|
|
1546
|
+
logger8.info("Payments disabled");
|
|
1547
|
+
}
|
|
1548
|
+
logger8.info("[6/8] Configure development environment");
|
|
1549
|
+
const devEnvConfig = nonInteractive ? { createDevContainer: false, createDevbox: false } : await promptDevEnvConfig();
|
|
1550
|
+
logger8.success(
|
|
1551
|
+
`Dev Container: ${devEnvConfig.createDevContainer ? "Yes" : "No"}, Devbox: ${devEnvConfig.createDevbox ? "Yes" : "No"}`
|
|
1552
|
+
);
|
|
1553
|
+
logger8.info("[7/8] Creating project...");
|
|
1554
|
+
await createProject({
|
|
1555
|
+
project: projectConfig,
|
|
1556
|
+
database: databaseConfig,
|
|
1557
|
+
storage: storageConfig,
|
|
1558
|
+
payment: paymentConfig,
|
|
1559
|
+
devenv: devEnvConfig,
|
|
1560
|
+
skipGit: options.skipGit,
|
|
1561
|
+
skipInstall: options.skipInstall
|
|
968
1562
|
});
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1563
|
+
logger8.success("Project created successfully");
|
|
1564
|
+
logger8.info("[8/8] Done!");
|
|
1565
|
+
printPostCreateSummary(projectConfig.projectName);
|
|
1566
|
+
} catch (err) {
|
|
1567
|
+
formatCreateError(err);
|
|
1568
|
+
process.exit(1);
|
|
972
1569
|
}
|
|
973
1570
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
resolve(value);
|
|
981
|
-
};
|
|
982
|
-
socket.setTimeout(timeoutMs);
|
|
983
|
-
socket.once("connect", () => finalize(true));
|
|
984
|
-
socket.once("timeout", () => finalize(false));
|
|
985
|
-
socket.once("error", () => finalize(false));
|
|
986
|
-
socket.connect(port, host);
|
|
987
|
-
});
|
|
988
|
-
}
|
|
1571
|
+
|
|
1572
|
+
// src/commands/db.ts
|
|
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";
|
|
989
1577
|
|
|
990
1578
|
// src/utils/db.ts
|
|
991
|
-
import
|
|
992
|
-
import
|
|
1579
|
+
import fs7 from "fs/promises";
|
|
1580
|
+
import path7 from "path";
|
|
993
1581
|
function resolveLocalDbConfig(cwd = process.cwd(), env = process.env) {
|
|
994
|
-
const pgdata = env.PGDATA ||
|
|
1582
|
+
const pgdata = env.PGDATA || path7.join(cwd, ".pgdata");
|
|
995
1583
|
const pghost = env.PGHOST || pgdata;
|
|
996
1584
|
const pgdatabase = env.PGDATABASE || "postgres";
|
|
997
1585
|
const pguser = env.PGUSER || "postgres";
|
|
@@ -1038,27 +1626,27 @@ host all all ::1/128 trust
|
|
|
1038
1626
|
`.trimStart();
|
|
1039
1627
|
}
|
|
1040
1628
|
async function writeLocalDbConfigs(pgdata) {
|
|
1041
|
-
await
|
|
1042
|
-
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");
|
|
1043
1631
|
}
|
|
1044
1632
|
|
|
1045
1633
|
// src/utils/workspace.ts
|
|
1046
|
-
import
|
|
1047
|
-
import
|
|
1634
|
+
import fs8 from "fs";
|
|
1635
|
+
import path8 from "path";
|
|
1048
1636
|
function findWorkspaceRoot(startDir = process.cwd()) {
|
|
1049
|
-
let current =
|
|
1637
|
+
let current = path8.resolve(startDir);
|
|
1050
1638
|
while (true) {
|
|
1051
|
-
const packageJsonPath =
|
|
1052
|
-
if (
|
|
1639
|
+
const packageJsonPath = path8.join(current, "package.json");
|
|
1640
|
+
if (fs8.existsSync(packageJsonPath)) {
|
|
1053
1641
|
try {
|
|
1054
|
-
const pkg = JSON.parse(
|
|
1642
|
+
const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
|
|
1055
1643
|
if (pkg.name === "revealui" && pkg.private === true) {
|
|
1056
1644
|
return current;
|
|
1057
1645
|
}
|
|
1058
1646
|
} catch {
|
|
1059
1647
|
}
|
|
1060
1648
|
}
|
|
1061
|
-
const parent =
|
|
1649
|
+
const parent = path8.dirname(current);
|
|
1062
1650
|
if (parent === current) {
|
|
1063
1651
|
return null;
|
|
1064
1652
|
}
|
|
@@ -1067,7 +1655,7 @@ function findWorkspaceRoot(startDir = process.cwd()) {
|
|
|
1067
1655
|
}
|
|
1068
1656
|
|
|
1069
1657
|
// src/commands/db.ts
|
|
1070
|
-
var
|
|
1658
|
+
var logger9 = createLogger9({ prefix: "DB" });
|
|
1071
1659
|
function isPgCtlNotRunningError(error) {
|
|
1072
1660
|
return typeof error === "object" && error !== null && "exitCode" in error && error.exitCode === 3;
|
|
1073
1661
|
}
|
|
@@ -1108,18 +1696,18 @@ async function runDbInitCommand(options = {}) {
|
|
|
1108
1696
|
const pgdata = getPgDataOrThrow(env);
|
|
1109
1697
|
await requireCommand("initdb");
|
|
1110
1698
|
try {
|
|
1111
|
-
await
|
|
1699
|
+
await fs9.access(pgdata);
|
|
1112
1700
|
if (!options.force) {
|
|
1113
1701
|
throw new Error(`PostgreSQL is already initialized at ${pgdata}. Use --force to reset it.`);
|
|
1114
1702
|
}
|
|
1115
|
-
await
|
|
1703
|
+
await fs9.rm(pgdata, { recursive: true, force: true });
|
|
1116
1704
|
} catch (error) {
|
|
1117
1705
|
if (error instanceof Error && !error.message.includes("already initialized")) {
|
|
1118
1706
|
} else if (error instanceof Error) {
|
|
1119
1707
|
throw error;
|
|
1120
1708
|
}
|
|
1121
1709
|
}
|
|
1122
|
-
await
|
|
1710
|
+
await execa5(
|
|
1123
1711
|
"initdb",
|
|
1124
1712
|
["--locale=C.UTF-8", "--encoding=UTF8", "-D", pgdata, "--username=postgres"],
|
|
1125
1713
|
{
|
|
@@ -1128,17 +1716,17 @@ async function runDbInitCommand(options = {}) {
|
|
|
1128
1716
|
}
|
|
1129
1717
|
);
|
|
1130
1718
|
await writeLocalDbConfigs(pgdata);
|
|
1131
|
-
|
|
1719
|
+
logger9.success(`PostgreSQL initialized at ${pgdata}`);
|
|
1132
1720
|
}
|
|
1133
1721
|
async function runDbStartCommand() {
|
|
1134
1722
|
const root = getWorkspaceRootOrThrow();
|
|
1135
1723
|
const env = buildDbEnv(root);
|
|
1136
1724
|
const pgdata = getPgDataOrThrow(env);
|
|
1137
1725
|
await requireCommand("pg_ctl");
|
|
1138
|
-
await
|
|
1139
|
-
await
|
|
1726
|
+
await fs9.access(pgdata);
|
|
1727
|
+
await execa5(
|
|
1140
1728
|
"pg_ctl",
|
|
1141
|
-
["start", "-D", pgdata, "-l",
|
|
1729
|
+
["start", "-D", pgdata, "-l", path9.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
|
|
1142
1730
|
{
|
|
1143
1731
|
env,
|
|
1144
1732
|
stdio: "inherit"
|
|
@@ -1150,7 +1738,7 @@ async function runDbStopCommand() {
|
|
|
1150
1738
|
const env = buildDbEnv(root);
|
|
1151
1739
|
const pgdata = getPgDataOrThrow(env);
|
|
1152
1740
|
await requireCommand("pg_ctl");
|
|
1153
|
-
await
|
|
1741
|
+
await execa5("pg_ctl", ["stop", "-D", pgdata], {
|
|
1154
1742
|
env,
|
|
1155
1743
|
stdio: "inherit"
|
|
1156
1744
|
});
|
|
@@ -1161,13 +1749,13 @@ async function runDbStatusCommand() {
|
|
|
1161
1749
|
const pgdata = getPgDataOrThrow(env);
|
|
1162
1750
|
await requireCommand("pg_ctl");
|
|
1163
1751
|
try {
|
|
1164
|
-
await
|
|
1752
|
+
await execa5("pg_ctl", ["status", "-D", pgdata], {
|
|
1165
1753
|
env,
|
|
1166
1754
|
stdio: "inherit"
|
|
1167
1755
|
});
|
|
1168
1756
|
} catch (error) {
|
|
1169
1757
|
if (isPgCtlNotRunningError(error)) {
|
|
1170
|
-
|
|
1758
|
+
logger9.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
|
|
1171
1759
|
return;
|
|
1172
1760
|
}
|
|
1173
1761
|
throw error;
|
|
@@ -1182,17 +1770,17 @@ async function runDbResetCommand(options = {}) {
|
|
|
1182
1770
|
const pgdata = getPgDataOrThrow(env);
|
|
1183
1771
|
if (await commandExists("pg_ctl")) {
|
|
1184
1772
|
try {
|
|
1185
|
-
await
|
|
1773
|
+
await execa5("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
|
|
1186
1774
|
} catch {
|
|
1187
1775
|
}
|
|
1188
1776
|
}
|
|
1189
|
-
await
|
|
1777
|
+
await fs9.rm(pgdata, { recursive: true, force: true });
|
|
1190
1778
|
await runDbInitCommand({ force: false });
|
|
1191
1779
|
}
|
|
1192
1780
|
async function runDbMigrateCommand() {
|
|
1193
1781
|
const root = getWorkspaceRootOrThrow();
|
|
1194
1782
|
const env = buildDbEnv(root);
|
|
1195
|
-
await
|
|
1783
|
+
await execa5("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
|
|
1196
1784
|
cwd: root,
|
|
1197
1785
|
env,
|
|
1198
1786
|
stdio: "inherit"
|
|
@@ -1203,7 +1791,7 @@ async function runDbCleanupCommand(options = {}) {
|
|
|
1203
1791
|
const env = { ...process.env };
|
|
1204
1792
|
if (options.dryRun) env.DRY_RUN = "true";
|
|
1205
1793
|
if (options.tables) env.TABLES = options.tables;
|
|
1206
|
-
await
|
|
1794
|
+
await execa5("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
|
|
1207
1795
|
cwd: root,
|
|
1208
1796
|
env,
|
|
1209
1797
|
stdio: "inherit"
|
|
@@ -1211,8 +1799,8 @@ async function runDbCleanupCommand(options = {}) {
|
|
|
1211
1799
|
}
|
|
1212
1800
|
|
|
1213
1801
|
// src/commands/dev.ts
|
|
1214
|
-
import { createLogger as
|
|
1215
|
-
import { execa as
|
|
1802
|
+
import { createLogger as createLogger12 } from "@revealui/setup/utils";
|
|
1803
|
+
import { execa as execa7 } from "execa";
|
|
1216
1804
|
|
|
1217
1805
|
// src/runtime/doctor.ts
|
|
1218
1806
|
function getMcpDetail(env) {
|
|
@@ -1231,6 +1819,139 @@ function getMcpDetail(env) {
|
|
|
1231
1819
|
detail: `mcp credentials configured for ${configuredKeys.join(", ")}`
|
|
1232
1820
|
};
|
|
1233
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
|
+
}
|
|
1234
1955
|
async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
|
|
1235
1956
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
1236
1957
|
const dbConfig = resolveLocalDbConfig(workspaceRoot ?? cwd, env);
|
|
@@ -1244,6 +1965,7 @@ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
|
|
|
1244
1965
|
const dockerOk = await commandExists("docker");
|
|
1245
1966
|
const postgresReachable = dbTarget === "local" ? await isTcpReachable("127.0.0.1", 5432, 1e3) : false;
|
|
1246
1967
|
const mcp = getMcpDetail(env);
|
|
1968
|
+
const credentials = gatherCredentialChecks(env);
|
|
1247
1969
|
return {
|
|
1248
1970
|
workspaceRoot,
|
|
1249
1971
|
dbTarget,
|
|
@@ -1302,7 +2024,8 @@ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
|
|
|
1302
2024
|
id: "mcp",
|
|
1303
2025
|
ok: mcp.ok,
|
|
1304
2026
|
detail: mcp.detail
|
|
1305
|
-
}
|
|
2027
|
+
},
|
|
2028
|
+
...credentials
|
|
1306
2029
|
]
|
|
1307
2030
|
};
|
|
1308
2031
|
}
|
|
@@ -1315,22 +2038,22 @@ function formatDoctorReport(report) {
|
|
|
1315
2038
|
}
|
|
1316
2039
|
|
|
1317
2040
|
// src/utils/dev-config.ts
|
|
1318
|
-
import
|
|
1319
|
-
import
|
|
2041
|
+
import fs10 from "fs";
|
|
2042
|
+
import path10 from "path";
|
|
1320
2043
|
function getDevConfigPath(startDir = process.cwd()) {
|
|
1321
2044
|
const workspaceRoot = findWorkspaceRoot(startDir);
|
|
1322
2045
|
if (!workspaceRoot) {
|
|
1323
2046
|
return null;
|
|
1324
2047
|
}
|
|
1325
|
-
return
|
|
2048
|
+
return path10.join(workspaceRoot, ".revealui", "dev.json");
|
|
1326
2049
|
}
|
|
1327
2050
|
function readDevConfig(startDir = process.cwd()) {
|
|
1328
2051
|
const configPath = getDevConfigPath(startDir);
|
|
1329
|
-
if (!(configPath &&
|
|
2052
|
+
if (!(configPath && fs10.existsSync(configPath))) {
|
|
1330
2053
|
return {};
|
|
1331
2054
|
}
|
|
1332
2055
|
try {
|
|
1333
|
-
return JSON.parse(
|
|
2056
|
+
return JSON.parse(fs10.readFileSync(configPath, "utf8"));
|
|
1334
2057
|
} catch {
|
|
1335
2058
|
return {};
|
|
1336
2059
|
}
|
|
@@ -1340,15 +2063,15 @@ function writeDevConfig(config, startDir = process.cwd()) {
|
|
|
1340
2063
|
if (!configPath) {
|
|
1341
2064
|
throw new Error("RevealUI workspace root not found");
|
|
1342
2065
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
2066
|
+
fs10.mkdirSync(path10.dirname(configPath), { recursive: true });
|
|
2067
|
+
fs10.writeFileSync(`${configPath}`, `${JSON.stringify(config, null, 2)}
|
|
1345
2068
|
`, "utf8");
|
|
1346
2069
|
return configPath;
|
|
1347
2070
|
}
|
|
1348
2071
|
|
|
1349
2072
|
// src/commands/doctor.ts
|
|
1350
|
-
import { createLogger as
|
|
1351
|
-
var
|
|
2073
|
+
import { createLogger as createLogger10 } from "@revealui/setup/utils";
|
|
2074
|
+
var logger10 = createLogger10({ prefix: "Doctor" });
|
|
1352
2075
|
function getDoctorFixPlan(report) {
|
|
1353
2076
|
const attempted = [];
|
|
1354
2077
|
const skipped = [];
|
|
@@ -1406,7 +2129,7 @@ async function runDoctorCommand(options = {}) {
|
|
|
1406
2129
|
`);
|
|
1407
2130
|
if (options.fix) {
|
|
1408
2131
|
if (fixPlan.attempted.length === 0) {
|
|
1409
|
-
|
|
2132
|
+
logger10.warn("No safe automatic fixes available");
|
|
1410
2133
|
} else {
|
|
1411
2134
|
await applyDoctorFixes(report);
|
|
1412
2135
|
report = await gatherDoctorReport();
|
|
@@ -1415,7 +2138,7 @@ async function runDoctorCommand(options = {}) {
|
|
|
1415
2138
|
}
|
|
1416
2139
|
}
|
|
1417
2140
|
if (report.checks.some((check) => !check.ok)) {
|
|
1418
|
-
|
|
2141
|
+
logger10.warn("Some checks failed");
|
|
1419
2142
|
if (options.strict || options.json || process.env.CI) {
|
|
1420
2143
|
process.exitCode = 1;
|
|
1421
2144
|
}
|
|
@@ -1423,15 +2146,15 @@ async function runDoctorCommand(options = {}) {
|
|
|
1423
2146
|
}
|
|
1424
2147
|
|
|
1425
2148
|
// src/commands/shell.ts
|
|
1426
|
-
import { createLogger as
|
|
1427
|
-
import { execa as
|
|
1428
|
-
var
|
|
2149
|
+
import { createLogger as createLogger11 } from "@revealui/setup/utils";
|
|
2150
|
+
import { execa as execa6 } from "execa";
|
|
2151
|
+
var logger11 = createLogger11({ prefix: "Shell" });
|
|
1429
2152
|
async function runShellCommand(options = {}) {
|
|
1430
2153
|
if (!(options.inside || process.env.IN_NIX_SHELL) && await commandExists("nix")) {
|
|
1431
2154
|
const workspaceRoot = findWorkspaceRoot();
|
|
1432
2155
|
if (workspaceRoot) {
|
|
1433
2156
|
const reentryArgs = options.ensure ? ["dev", "up"] : ["dev", "status"];
|
|
1434
|
-
await
|
|
2157
|
+
await execa6(
|
|
1435
2158
|
"nix",
|
|
1436
2159
|
[
|
|
1437
2160
|
"develop",
|
|
@@ -1470,12 +2193,12 @@ async function runShellCommand(options = {}) {
|
|
|
1470
2193
|
}
|
|
1471
2194
|
process.stdout.write(`${formatDoctorReport(freshReport)}
|
|
1472
2195
|
`);
|
|
1473
|
-
|
|
2196
|
+
logger11.info("Use `revealui doctor --json` for machine-readable status.");
|
|
1474
2197
|
return false;
|
|
1475
2198
|
}
|
|
1476
2199
|
|
|
1477
2200
|
// src/commands/dev.ts
|
|
1478
|
-
var
|
|
2201
|
+
var logger12 = createLogger12({ prefix: "Dev" });
|
|
1479
2202
|
var DEV_PROFILES = {
|
|
1480
2203
|
local: {},
|
|
1481
2204
|
agent: {
|
|
@@ -1601,21 +2324,21 @@ async function runDevUpCommand(options = {}) {
|
|
|
1601
2324
|
process.stdout.write(`${formatDevActions(plan)}
|
|
1602
2325
|
`);
|
|
1603
2326
|
if (plan.dryRun) {
|
|
1604
|
-
|
|
2327
|
+
logger12.info("Dry run only; no migrations or services were started.");
|
|
1605
2328
|
return;
|
|
1606
2329
|
}
|
|
1607
2330
|
if (options.fix) {
|
|
1608
2331
|
await runDoctorCommand({ fix: true });
|
|
1609
2332
|
}
|
|
1610
2333
|
await runDbMigrateCommand();
|
|
1611
|
-
|
|
2334
|
+
logger12.success("Database migration complete");
|
|
1612
2335
|
if (shouldIncludeMcp(resolved.include)) {
|
|
1613
2336
|
const workspaceRoot = findWorkspaceRoot();
|
|
1614
2337
|
if (!workspaceRoot) {
|
|
1615
2338
|
throw new Error("RevealUI workspace root not found");
|
|
1616
2339
|
}
|
|
1617
|
-
|
|
1618
|
-
await
|
|
2340
|
+
logger12.info("Validating MCP setup");
|
|
2341
|
+
await execa7("pnpm", ["setup:mcp"], {
|
|
1619
2342
|
cwd: workspaceRoot,
|
|
1620
2343
|
stdio: "inherit"
|
|
1621
2344
|
});
|
|
@@ -1625,8 +2348,8 @@ async function runDevUpCommand(options = {}) {
|
|
|
1625
2348
|
if (!workspaceRoot) {
|
|
1626
2349
|
throw new Error("RevealUI workspace root not found");
|
|
1627
2350
|
}
|
|
1628
|
-
|
|
1629
|
-
await
|
|
2351
|
+
logger12.info(`Starting dev script: ${resolved.script}`);
|
|
2352
|
+
await execa7("pnpm", [resolved.script], {
|
|
1630
2353
|
cwd: workspaceRoot,
|
|
1631
2354
|
stdio: "inherit"
|
|
1632
2355
|
});
|
|
@@ -1683,7 +2406,7 @@ async function runDevProfileSetCommand(profile) {
|
|
|
1683
2406
|
);
|
|
1684
2407
|
}
|
|
1685
2408
|
const configPath = writeDevConfig({ defaultProfile: profile });
|
|
1686
|
-
|
|
2409
|
+
logger12.success(`Default dev profile set to "${profile}" in ${configPath}`);
|
|
1687
2410
|
}
|
|
1688
2411
|
async function runDevProfileShowCommand(options = {}) {
|
|
1689
2412
|
const configuredProfile = getConfiguredProfile() ?? null;
|
|
@@ -1696,11 +2419,223 @@ async function runDevProfileShowCommand(options = {}) {
|
|
|
1696
2419
|
`);
|
|
1697
2420
|
}
|
|
1698
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
|
+
|
|
1699
2634
|
// src/cli.ts
|
|
1700
|
-
var
|
|
2635
|
+
var logger14 = createLogger14({ prefix: "CLI" });
|
|
1701
2636
|
function configureCreateCommand(command, legacyName) {
|
|
1702
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) => {
|
|
1703
|
-
|
|
2638
|
+
logger14.header(legacyName ? "Create RevealUI Project" : "RevealUI Create");
|
|
1704
2639
|
await runCreateFlow(projectName, options);
|
|
1705
2640
|
});
|
|
1706
2641
|
return command;
|
|
@@ -1712,6 +2647,17 @@ function createCli() {
|
|
|
1712
2647
|
program.command("doctor").description("Check RevealUI workspace and developer environment health").option("--json", "Output machine-readable JSON", false).option("--fix", "Apply safe automatic fixes when possible", false).option("--strict", "Exit nonzero when checks fail", false).action(async (options) => {
|
|
1713
2648
|
await runDoctorCommand(options);
|
|
1714
2649
|
});
|
|
2650
|
+
const agent = program.command("agent").description("RevealUI coding agent (powered by local or cloud LLMs)");
|
|
2651
|
+
agent.option("-p, --prompt <text>", "Run a single prompt in headless mode").action(async (options) => {
|
|
2652
|
+
if (options.prompt) {
|
|
2653
|
+
await runAgentHeadlessCommand(options.prompt);
|
|
2654
|
+
} else {
|
|
2655
|
+
await runAgentReplCommand();
|
|
2656
|
+
}
|
|
2657
|
+
});
|
|
2658
|
+
agent.command("status").description("Show agent status: model, provider, project root").action(async () => {
|
|
2659
|
+
await runAgentStatusCommand();
|
|
2660
|
+
});
|
|
1715
2661
|
const db = program.command("db").description("Manage the local RevealUI database");
|
|
1716
2662
|
db.command("init").description("Initialize the local PostgreSQL data directory").option("--force", "Delete and recreate the local data directory", false).action(async (options) => {
|
|
1717
2663
|
await runDbInitCommand(options);
|
|
@@ -1784,6 +2730,23 @@ function createCli() {
|
|
|
1784
2730
|
devProfile.command("show").description("Show the configured default dev profile").option("--json", "Output machine-readable JSON", false).action(async (options) => {
|
|
1785
2731
|
await runDevProfileShowCommand(options);
|
|
1786
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
|
+
});
|
|
1787
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) => {
|
|
1788
2751
|
await runDevUpCommand({
|
|
1789
2752
|
ensure: options.ensure,
|