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