convex-devtools 0.1.0 → 1.0.0
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 +23 -7
- package/dist/cli/{chunk-A7PTPOQI.js → chunk-4PBPPYEF.js} +2 -2
- package/dist/cli/{chunk-A7PTPOQI.js.map → chunk-4PBPPYEF.js.map} +1 -1
- package/dist/cli/chunk-75H32MO3.js +205 -0
- package/dist/cli/chunk-75H32MO3.js.map +1 -0
- package/dist/cli/{chunk-K2AL37MJ.js → chunk-ENBIFEKV.js} +49 -5
- package/dist/cli/chunk-ENBIFEKV.js.map +1 -0
- package/dist/cli/index.js +44 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/server/convex-client.js +1 -1
- package/dist/cli/server/index.d.ts +1 -0
- package/dist/cli/server/index.js +2 -2
- package/dist/cli/server/schema-watcher.d.ts +4 -0
- package/dist/cli/server/schema-watcher.js +1 -1
- package/dist/ui/assets/index-CoKLkVGV.css +1 -0
- package/dist/ui/assets/index-DyHgwxFr.js +56 -0
- package/dist/ui/index.html +2 -2
- package/package.json +4 -2
- package/dist/cli/chunk-5AG6RQBI.js +0 -102
- package/dist/cli/chunk-5AG6RQBI.js.map +0 -1
- package/dist/cli/chunk-K2AL37MJ.js.map +0 -1
- package/dist/ui/assets/index-B7zbq--d.css +0 -1
- package/dist/ui/assets/index-CqKYd9Ws.js +0 -56
package/README.md
CHANGED
|
@@ -63,6 +63,12 @@ convex-devtools --dir /path/to/your/project
|
|
|
63
63
|
# Run on a custom port
|
|
64
64
|
convex-devtools --port 3000
|
|
65
65
|
|
|
66
|
+
# Share collections across all projects on this machine
|
|
67
|
+
convex-devtools --storage global
|
|
68
|
+
|
|
69
|
+
# Use a custom shared database path
|
|
70
|
+
convex-devtools --storage path --storage-path /path/to/devtools.sqlite
|
|
71
|
+
|
|
66
72
|
# Don't auto-open browser
|
|
67
73
|
convex-devtools --no-open
|
|
68
74
|
```
|
|
@@ -71,13 +77,23 @@ The tool will automatically open in your browser at `http://localhost:5173`.
|
|
|
71
77
|
|
|
72
78
|
## CLI Options
|
|
73
79
|
|
|
74
|
-
| Option
|
|
75
|
-
|
|
|
76
|
-
| `-p, --port <number>`
|
|
77
|
-
| `-d, --dir <path>`
|
|
78
|
-
| `--
|
|
79
|
-
|
|
|
80
|
-
|
|
|
80
|
+
| Option | Description | Default |
|
|
81
|
+
| ----------------------- | ------------------------------------------------ | --------- |
|
|
82
|
+
| `-p, --port <number>` | Port for the devtools server | `5173` |
|
|
83
|
+
| `-d, --dir <path>` | Path to Convex project directory | `.` |
|
|
84
|
+
| `--storage <mode>` | Storage scope: `project`, `global`, or `path` | `project` |
|
|
85
|
+
| `--storage-path <path>` | Custom storage path (only with `--storage path`) | - |
|
|
86
|
+
| `--no-open` | Don't open browser automatically | - |
|
|
87
|
+
| `-V, --version` | Display version number | - |
|
|
88
|
+
| `-h, --help` | Display help information | - |
|
|
89
|
+
|
|
90
|
+
## Storage Options
|
|
91
|
+
|
|
92
|
+
By default, collections and history are stored per project directory, so multiple ports running against different projects won’t share data.
|
|
93
|
+
|
|
94
|
+
- **project** (default): data stored under `<project>/.convex-devtools/devtools.sqlite`.
|
|
95
|
+
- **global**: shared data stored under `~/.convex-devtools/devtools.sqlite`.
|
|
96
|
+
- **path**: store data at a custom SQLite path you provide.
|
|
81
97
|
|
|
82
98
|
## Identity Mocking
|
|
83
99
|
|
|
@@ -12,7 +12,7 @@ var ConvexClient = class {
|
|
|
12
12
|
const url = `${this.baseUrl}/${endpoint}`;
|
|
13
13
|
const headers = {
|
|
14
14
|
"Content-Type": "application/json",
|
|
15
|
-
"Convex-Client": "convex-devtools-
|
|
15
|
+
"Convex-Client": "convex-devtools-1.0.0"
|
|
16
16
|
};
|
|
17
17
|
if (options?.jwtToken) {
|
|
18
18
|
headers["Authorization"] = `Bearer ${options.jwtToken}`;
|
|
@@ -134,4 +134,4 @@ var ConvexClient = class {
|
|
|
134
134
|
export {
|
|
135
135
|
ConvexClient
|
|
136
136
|
};
|
|
137
|
-
//# sourceMappingURL=chunk-
|
|
137
|
+
//# sourceMappingURL=chunk-4PBPPYEF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/convex-client.ts"],"sourcesContent":["/**\n * Convex HTTP Client wrapper for DevTools\n * Supports two authentication methods:\n * 1. Deploy key - runs as admin (for admin operations)\n * 2. JWT token - runs as the authenticated user (from your auth provider like Clerk)\n */\n\nexport interface UserIdentity {\n subject: string;\n issuer?: string;\n tokenIdentifier?: string;\n name?: string;\n email?: string;\n pictureUrl?: string;\n // Custom claims\n [key: string]: unknown;\n}\n\nexport interface InvokeOptions {\n identity?: UserIdentity;\n jwtToken?: string; // Real JWT token from auth provider\n}\n\nexport class ConvexClient {\n private baseUrl: string;\n private deployKey: string;\n\n constructor(convexUrl: string, deployKey: string) {\n // Convert deployment URL to HTTP endpoint\n // e.g., https://happy-otter-123.convex.cloud -> https://happy-otter-123.convex.cloud\n this.baseUrl = convexUrl;\n this.deployKey = deployKey;\n }\n\n async invoke(\n functionPath: string,\n functionType: 'query' | 'mutation' | 'action',\n args: Record<string, unknown> = {},\n options?: InvokeOptions\n ): Promise<unknown> {\n // Normalize function path: module/submodule:functionName\n const normalizedPath = this.normalizeFunctionPath(functionPath);\n\n // Build the request\n const endpoint = this.getEndpoint(functionType);\n const url = `${this.baseUrl}/${endpoint}`;\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Convex-Client': 'convex-devtools-
|
|
1
|
+
{"version":3,"sources":["../../src/server/convex-client.ts"],"sourcesContent":["/**\n * Convex HTTP Client wrapper for DevTools\n * Supports two authentication methods:\n * 1. Deploy key - runs as admin (for admin operations)\n * 2. JWT token - runs as the authenticated user (from your auth provider like Clerk)\n */\n\nexport interface UserIdentity {\n subject: string;\n issuer?: string;\n tokenIdentifier?: string;\n name?: string;\n email?: string;\n pictureUrl?: string;\n // Custom claims\n [key: string]: unknown;\n}\n\nexport interface InvokeOptions {\n identity?: UserIdentity;\n jwtToken?: string; // Real JWT token from auth provider\n}\n\nexport class ConvexClient {\n private baseUrl: string;\n private deployKey: string;\n\n constructor(convexUrl: string, deployKey: string) {\n // Convert deployment URL to HTTP endpoint\n // e.g., https://happy-otter-123.convex.cloud -> https://happy-otter-123.convex.cloud\n this.baseUrl = convexUrl;\n this.deployKey = deployKey;\n }\n\n async invoke(\n functionPath: string,\n functionType: 'query' | 'mutation' | 'action',\n args: Record<string, unknown> = {},\n options?: InvokeOptions\n ): Promise<unknown> {\n // Normalize function path: module/submodule:functionName\n const normalizedPath = this.normalizeFunctionPath(functionPath);\n\n // Build the request\n const endpoint = this.getEndpoint(functionType);\n const url = `${this.baseUrl}/${endpoint}`;\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Convex-Client': 'convex-devtools-1.0.0',\n };\n\n // Authentication priority:\n // 1. JWT token (authenticates as the user who owns the token)\n // 2. Deploy key (authenticates as admin)\n // 3. No auth (unauthenticated request)\n if (options?.jwtToken) {\n // Use Bearer token auth - this is what the Convex HTTP API expects for user auth\n headers['Authorization'] = `Bearer ${options.jwtToken}`;\n console.log('[ConvexClient] Using JWT token authentication');\n } else if (this.deployKey) {\n // Deploy key gives admin access but cannot impersonate users\n headers['Authorization'] = `Convex ${this.deployKey}`;\n console.log('[ConvexClient] Using deploy key (admin) authentication');\n }\n // Without any auth, calls will be unauthenticated\n\n const body = {\n path: normalizedPath,\n args: this.encodeArgs(args),\n format: 'json',\n };\n\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorData;\n try {\n errorData = JSON.parse(errorText);\n } catch {\n errorData = { message: errorText };\n }\n\n const error = new Error(errorData.message || `HTTP ${response.status}`);\n (error as any).code = errorData.code;\n (error as any).data = errorData;\n throw error;\n }\n\n const result = await response.json();\n return this.decodeResult(result);\n }\n\n private normalizeFunctionPath(path: string): string {\n // Convert various formats to Convex function path format\n // Input: \"products/products:list\" or \"products.products.list\" or \"products/products/list\"\n // Output: \"products/products:list\"\n\n // Already in correct format\n if (path.includes(':')) {\n return path;\n }\n\n // Dot notation: products.products.list -> products/products:list\n if (path.includes('.')) {\n const parts = path.split('.');\n const funcName = parts.pop()!;\n return `${parts.join('/')}:${funcName}`;\n }\n\n // Slash only: products/products/list -> products/products:list\n const parts = path.split('/');\n if (parts.length > 1) {\n const funcName = parts.pop()!;\n return `${parts.join('/')}:${funcName}`;\n }\n\n return path;\n }\n\n private getEndpoint(functionType: 'query' | 'mutation' | 'action'): string {\n switch (functionType) {\n case 'query':\n return 'api/query';\n case 'mutation':\n return 'api/mutation';\n case 'action':\n return 'api/action';\n }\n }\n\n private encodeArgs(args: Record<string, unknown>): Record<string, unknown> {\n // Convex uses a special encoding format for complex types\n // For now, we'll pass through JSON-serializable values\n // Special handling for Convex types like Id, etc.\n return this.convertToConvexJson(args) as Record<string, unknown>;\n }\n\n private convertToConvexJson(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => this.convertToConvexJson(v));\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n\n // Check for special $type markers (Convex encoded JSON)\n if ('$type' in obj) {\n return obj; // Already encoded\n }\n\n // Regular object\n const converted: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n converted[k] = this.convertToConvexJson(v);\n }\n return converted;\n }\n\n // Handle BigInt\n if (typeof value === 'bigint') {\n return { $type: 'bigint', value: value.toString() };\n }\n\n return value;\n }\n\n private decodeResult(result: unknown): unknown {\n // Decode Convex-specific types back to JavaScript\n return this.convertFromConvexJson(result);\n }\n\n private convertFromConvexJson(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => this.convertFromConvexJson(v));\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n\n // Check for special $type markers\n if ('$type' in obj) {\n switch (obj.$type) {\n case 'bigint':\n return BigInt(obj.value as string);\n case 'bytes':\n return new Uint8Array(obj.value as number[]);\n default:\n return obj;\n }\n }\n\n // Regular object\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n result[k] = this.convertFromConvexJson(v);\n }\n return result;\n }\n\n return value;\n }\n}\n"],"mappings":"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","names":["parts"]}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConvexClient
|
|
3
|
+
} from "./chunk-4PBPPYEF.js";
|
|
4
|
+
|
|
5
|
+
// src/server/index.ts
|
|
6
|
+
import cors from "cors";
|
|
7
|
+
import express from "express";
|
|
8
|
+
import http from "http";
|
|
9
|
+
import path2 from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
12
|
+
|
|
13
|
+
// src/server/persistence-db.ts
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
import { createRequire } from "module";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import initSqlJs from "sql.js";
|
|
18
|
+
var DEFAULT_DATA = {
|
|
19
|
+
collections: [],
|
|
20
|
+
history: []
|
|
21
|
+
};
|
|
22
|
+
var PersistenceDb = class _PersistenceDb {
|
|
23
|
+
db;
|
|
24
|
+
dbFilePath;
|
|
25
|
+
SQL;
|
|
26
|
+
constructor(db, dbFilePath, SQL) {
|
|
27
|
+
this.db = db;
|
|
28
|
+
this.dbFilePath = dbFilePath;
|
|
29
|
+
this.SQL = SQL;
|
|
30
|
+
this.db.exec(
|
|
31
|
+
"CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL)"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
static async create(dbFilePath) {
|
|
35
|
+
const dir = path.dirname(dbFilePath);
|
|
36
|
+
if (!fs.existsSync(dir)) {
|
|
37
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
const require2 = createRequire(import.meta.url);
|
|
40
|
+
const wasmPath = require2.resolve("sql.js/dist/sql-wasm.wasm");
|
|
41
|
+
const SQL = await initSqlJs({
|
|
42
|
+
locateFile: () => wasmPath
|
|
43
|
+
});
|
|
44
|
+
let db;
|
|
45
|
+
if (fs.existsSync(dbFilePath)) {
|
|
46
|
+
const fileBuffer = fs.readFileSync(dbFilePath);
|
|
47
|
+
db = new SQL.Database(new Uint8Array(fileBuffer));
|
|
48
|
+
} else {
|
|
49
|
+
db = new SQL.Database();
|
|
50
|
+
}
|
|
51
|
+
return new _PersistenceDb(db, dbFilePath, SQL);
|
|
52
|
+
}
|
|
53
|
+
/** Reload database from disk to pick up changes from other processes */
|
|
54
|
+
reloadFromDisk() {
|
|
55
|
+
if (fs.existsSync(this.dbFilePath)) {
|
|
56
|
+
const fileBuffer = fs.readFileSync(this.dbFilePath);
|
|
57
|
+
this.db = new this.SQL.Database(new Uint8Array(fileBuffer));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
getData() {
|
|
61
|
+
this.reloadFromDisk();
|
|
62
|
+
const result = this.db.exec(
|
|
63
|
+
"SELECT value FROM kv WHERE key = 'persistence'"
|
|
64
|
+
);
|
|
65
|
+
if (!result.length || !result[0].values.length) {
|
|
66
|
+
return DEFAULT_DATA;
|
|
67
|
+
}
|
|
68
|
+
const value = result[0].values[0][0];
|
|
69
|
+
if (typeof value !== "string") {
|
|
70
|
+
return DEFAULT_DATA;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(value);
|
|
74
|
+
return {
|
|
75
|
+
collections: Array.isArray(parsed.collections) ? parsed.collections : [],
|
|
76
|
+
history: Array.isArray(parsed.history) ? parsed.history : []
|
|
77
|
+
};
|
|
78
|
+
} catch {
|
|
79
|
+
return DEFAULT_DATA;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
setData(data) {
|
|
83
|
+
const payload = JSON.stringify({
|
|
84
|
+
collections: Array.isArray(data.collections) ? data.collections : [],
|
|
85
|
+
history: Array.isArray(data.history) ? data.history : []
|
|
86
|
+
});
|
|
87
|
+
this.db.run(
|
|
88
|
+
"INSERT INTO kv (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at",
|
|
89
|
+
["persistence", payload, (/* @__PURE__ */ new Date()).toISOString()]
|
|
90
|
+
);
|
|
91
|
+
this.persistToDisk();
|
|
92
|
+
}
|
|
93
|
+
persistToDisk() {
|
|
94
|
+
const data = this.db.export();
|
|
95
|
+
fs.writeFileSync(this.dbFilePath, Buffer.from(data));
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// src/server/index.ts
|
|
100
|
+
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
101
|
+
async function createServer(config) {
|
|
102
|
+
const app = express();
|
|
103
|
+
app.use(cors());
|
|
104
|
+
app.use(express.json({ limit: "10mb" }));
|
|
105
|
+
const uiPath = path2.join(__dirname, "..", "ui");
|
|
106
|
+
app.use(express.static(uiPath));
|
|
107
|
+
const convexClient = new ConvexClient(config.convexUrl, config.deployKey);
|
|
108
|
+
const persistenceDb = await PersistenceDb.create(
|
|
109
|
+
config.persistencePath ?? path2.join(config.projectDir, ".convex-devtools", "devtools.sqlite")
|
|
110
|
+
);
|
|
111
|
+
app.get("/api/schema", (_req, res) => {
|
|
112
|
+
const schema = config.schemaWatcher.getSchema();
|
|
113
|
+
if (!schema) {
|
|
114
|
+
res.status(503).json({ error: "Schema not yet loaded" });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
res.json(schema);
|
|
118
|
+
});
|
|
119
|
+
app.get("/api/health", (_req, res) => {
|
|
120
|
+
res.json({
|
|
121
|
+
status: "ok",
|
|
122
|
+
convexUrl: config.convexUrl,
|
|
123
|
+
projectDir: config.projectDir,
|
|
124
|
+
projectName: path2.basename(config.projectDir)
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
app.get("/api/persistence", (_req, res) => {
|
|
128
|
+
res.json(persistenceDb.getData());
|
|
129
|
+
});
|
|
130
|
+
app.put("/api/persistence", (req, res) => {
|
|
131
|
+
const { collections, history } = req.body || {};
|
|
132
|
+
persistenceDb.setData({
|
|
133
|
+
collections: Array.isArray(collections) ? collections : [],
|
|
134
|
+
history: Array.isArray(history) ? history : []
|
|
135
|
+
});
|
|
136
|
+
res.json({ success: true });
|
|
137
|
+
});
|
|
138
|
+
app.post("/api/invoke", async (req, res) => {
|
|
139
|
+
const { functionPath, functionType, args, jwtToken } = req.body;
|
|
140
|
+
if (!functionPath || !functionType) {
|
|
141
|
+
res.status(400).json({ error: "Missing functionPath or functionType" });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const startTime = Date.now();
|
|
146
|
+
const result = await convexClient.invoke(
|
|
147
|
+
functionPath,
|
|
148
|
+
functionType,
|
|
149
|
+
args || {},
|
|
150
|
+
{ jwtToken }
|
|
151
|
+
);
|
|
152
|
+
const duration = Date.now() - startTime;
|
|
153
|
+
res.json({
|
|
154
|
+
success: true,
|
|
155
|
+
result,
|
|
156
|
+
duration,
|
|
157
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
158
|
+
});
|
|
159
|
+
} catch (error) {
|
|
160
|
+
res.json({
|
|
161
|
+
success: false,
|
|
162
|
+
error: {
|
|
163
|
+
message: error.message,
|
|
164
|
+
code: error.code,
|
|
165
|
+
data: error.data
|
|
166
|
+
},
|
|
167
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
const server = http.createServer(app);
|
|
172
|
+
const wss = new WebSocketServer({ server, path: "/ws" });
|
|
173
|
+
const clients = /* @__PURE__ */ new Set();
|
|
174
|
+
wss.on("connection", (ws) => {
|
|
175
|
+
clients.add(ws);
|
|
176
|
+
const schema = config.schemaWatcher.getSchema();
|
|
177
|
+
if (schema) {
|
|
178
|
+
ws.send(JSON.stringify({ type: "schema", data: schema }));
|
|
179
|
+
}
|
|
180
|
+
ws.on("close", () => {
|
|
181
|
+
clients.delete(ws);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
config.schemaWatcher.on("schema-updated", (schema) => {
|
|
185
|
+
const message = JSON.stringify({ type: "schema", data: schema });
|
|
186
|
+
for (const client of clients) {
|
|
187
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
188
|
+
client.send(message);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
app.get("*", (_req, res) => {
|
|
193
|
+
res.sendFile(path2.join(uiPath, "index.html"));
|
|
194
|
+
});
|
|
195
|
+
return new Promise((resolve) => {
|
|
196
|
+
server.listen(config.port, () => {
|
|
197
|
+
resolve(server);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export {
|
|
203
|
+
createServer
|
|
204
|
+
};
|
|
205
|
+
//# sourceMappingURL=chunk-75H32MO3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/index.ts","../../src/server/persistence-db.ts"],"sourcesContent":["import cors from 'cors';\nimport express from 'express';\nimport http from 'http';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { WebSocket, WebSocketServer } from 'ws';\nimport { ConvexClient } from './convex-client.js';\nimport { PersistenceDb } from './persistence-db.js';\nimport { SchemaWatcher } from './schema-watcher.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport interface ServerConfig {\n port: number;\n projectDir: string;\n convexUrl: string;\n deployKey: string;\n schemaWatcher: SchemaWatcher;\n persistencePath?: string;\n}\n\nexport async function createServer(config: ServerConfig): Promise<http.Server> {\n const app = express();\n\n app.use(cors());\n app.use(express.json({ limit: '10mb' }));\n\n // Serve static UI files in production\n const uiPath = path.join(__dirname, '..', 'ui');\n app.use(express.static(uiPath));\n\n // Create Convex client\n const convexClient = new ConvexClient(config.convexUrl, config.deployKey);\n\n // Shared persistence database (collections/history)\n const persistenceDb = await PersistenceDb.create(\n config.persistencePath ??\n path.join(config.projectDir, '.convex-devtools', 'devtools.sqlite')\n );\n\n // API Routes\n app.get('/api/schema', (_req, res) => {\n const schema = config.schemaWatcher.getSchema();\n if (!schema) {\n res.status(503).json({ error: 'Schema not yet loaded' });\n return;\n }\n res.json(schema);\n });\n\n app.get('/api/health', (_req, res) => {\n res.json({\n status: 'ok',\n convexUrl: config.convexUrl,\n projectDir: config.projectDir,\n projectName: path.basename(config.projectDir),\n });\n });\n\n app.get('/api/persistence', (_req, res) => {\n res.json(persistenceDb.getData());\n });\n\n app.put('/api/persistence', (req, res) => {\n const { collections, history } = req.body || {};\n persistenceDb.setData({\n collections: Array.isArray(collections) ? collections : [],\n history: Array.isArray(history) ? history : [],\n });\n res.json({ success: true });\n });\n\n // Invoke a function\n app.post('/api/invoke', async (req, res) => {\n const { functionPath, functionType, args, jwtToken } = req.body;\n\n if (!functionPath || !functionType) {\n res.status(400).json({ error: 'Missing functionPath or functionType' });\n return;\n }\n\n try {\n const startTime = Date.now();\n const result = await convexClient.invoke(\n functionPath,\n functionType,\n args || {},\n { jwtToken }\n );\n const duration = Date.now() - startTime;\n\n res.json({\n success: true,\n result,\n duration,\n timestamp: new Date().toISOString(),\n });\n } catch (error: any) {\n res.json({\n success: false,\n error: {\n message: error.message,\n code: error.code,\n data: error.data,\n },\n timestamp: new Date().toISOString(),\n });\n }\n });\n\n // Create HTTP server\n const server = http.createServer(app);\n\n // WebSocket for real-time schema updates\n const wss = new WebSocketServer({ server, path: '/ws' });\n\n const clients = new Set<WebSocket>();\n\n wss.on('connection', (ws) => {\n clients.add(ws);\n\n // Send initial schema\n const schema = config.schemaWatcher.getSchema();\n if (schema) {\n ws.send(JSON.stringify({ type: 'schema', data: schema }));\n }\n\n ws.on('close', () => {\n clients.delete(ws);\n });\n });\n\n // Broadcast schema updates\n config.schemaWatcher.on('schema-updated', (schema) => {\n const message = JSON.stringify({ type: 'schema', data: schema });\n for (const client of clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(message);\n }\n }\n });\n\n // SPA fallback - serve index.html for non-API routes\n app.get('*', (_req, res) => {\n res.sendFile(path.join(uiPath, 'index.html'));\n });\n\n return new Promise((resolve) => {\n server.listen(config.port, () => {\n resolve(server);\n });\n });\n}\n","import fs from 'fs';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport initSqlJs, { Database, SqlJsStatic } from 'sql.js';\n\nexport interface PersistenceData {\n collections: unknown[];\n history: unknown[];\n}\n\nconst DEFAULT_DATA: PersistenceData = {\n collections: [],\n history: [],\n};\n\nexport class PersistenceDb {\n private db: Database;\n private dbFilePath: string;\n private SQL: SqlJsStatic;\n\n private constructor(db: Database, dbFilePath: string, SQL: SqlJsStatic) {\n this.db = db;\n this.dbFilePath = dbFilePath;\n this.SQL = SQL;\n this.db.exec(\n 'CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL)'\n );\n }\n\n static async create(dbFilePath: string): Promise<PersistenceDb> {\n const dir = path.dirname(dbFilePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n const require = createRequire(import.meta.url);\n const wasmPath = require.resolve('sql.js/dist/sql-wasm.wasm');\n\n const SQL: SqlJsStatic = await initSqlJs({\n locateFile: () => wasmPath,\n });\n\n let db: Database;\n if (fs.existsSync(dbFilePath)) {\n const fileBuffer = fs.readFileSync(dbFilePath);\n db = new SQL.Database(new Uint8Array(fileBuffer));\n } else {\n db = new SQL.Database();\n }\n\n return new PersistenceDb(db, dbFilePath, SQL);\n }\n\n /** Reload database from disk to pick up changes from other processes */\n private reloadFromDisk(): void {\n if (fs.existsSync(this.dbFilePath)) {\n const fileBuffer = fs.readFileSync(this.dbFilePath);\n this.db = new this.SQL.Database(new Uint8Array(fileBuffer));\n }\n }\n\n getData(): PersistenceData {\n // Reload from disk to get latest data from other server instances\n this.reloadFromDisk();\n\n const result = this.db.exec(\n \"SELECT value FROM kv WHERE key = 'persistence'\"\n );\n\n if (!result.length || !result[0].values.length) {\n return DEFAULT_DATA;\n }\n\n const value = result[0].values[0][0];\n if (typeof value !== 'string') {\n return DEFAULT_DATA;\n }\n\n try {\n const parsed = JSON.parse(value) as PersistenceData;\n return {\n collections: Array.isArray(parsed.collections)\n ? parsed.collections\n : [],\n history: Array.isArray(parsed.history) ? parsed.history : [],\n };\n } catch {\n return DEFAULT_DATA;\n }\n }\n\n setData(data: PersistenceData): void {\n const payload = JSON.stringify({\n collections: Array.isArray(data.collections) ? data.collections : [],\n history: Array.isArray(data.history) ? data.history : [],\n });\n\n this.db.run(\n 'INSERT INTO kv (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at',\n ['persistence', payload, new Date().toISOString()]\n );\n\n this.persistToDisk();\n }\n\n private persistToDisk(): void {\n const data = this.db.export();\n fs.writeFileSync(this.dbFilePath, Buffer.from(data));\n }\n}\n"],"mappings":"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","names":["path","require","path"]}
|
|
@@ -154,7 +154,12 @@ var SchemaWatcher = class extends EventEmitter {
|
|
|
154
154
|
if (funcType.startsWith("internal")) {
|
|
155
155
|
normalizedType = funcType.replace("internal", "").toLowerCase();
|
|
156
156
|
}
|
|
157
|
-
const
|
|
157
|
+
const jsdocComment = this.extractJSDocAbove(content, match.index);
|
|
158
|
+
const args = this.extractArgsFromPosition(
|
|
159
|
+
content,
|
|
160
|
+
match.index,
|
|
161
|
+
jsdocComment
|
|
162
|
+
);
|
|
158
163
|
functions.push({
|
|
159
164
|
name: funcName,
|
|
160
165
|
path: `${modulePath}:${funcName}`,
|
|
@@ -167,23 +172,62 @@ var SchemaWatcher = class extends EventEmitter {
|
|
|
167
172
|
}
|
|
168
173
|
return functions;
|
|
169
174
|
}
|
|
170
|
-
|
|
175
|
+
extractJSDocAbove(content, position) {
|
|
176
|
+
const beforePosition = content.slice(0, position);
|
|
177
|
+
const jsdocMatch = beforePosition.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
|
|
178
|
+
if (jsdocMatch) {
|
|
179
|
+
return jsdocMatch[1];
|
|
180
|
+
}
|
|
181
|
+
const last500 = beforePosition.slice(-500);
|
|
182
|
+
const jsdocMatch2 = last500.match(/\/\*\*([\s\S]*?)\*\//);
|
|
183
|
+
return jsdocMatch2 ? jsdocMatch2[1] : "";
|
|
184
|
+
}
|
|
185
|
+
parseJSDocParams(jsdoc) {
|
|
186
|
+
const params = /* @__PURE__ */ new Map();
|
|
187
|
+
const paramPattern = /@param\s+(\w+)\s*-?\s*([^@]*)/g;
|
|
188
|
+
let match;
|
|
189
|
+
while ((match = paramPattern.exec(jsdoc)) !== null) {
|
|
190
|
+
const [, paramName, description] = match;
|
|
191
|
+
const trimmedDesc = description.trim();
|
|
192
|
+
const enumMatches = trimmedDesc.match(/'([^']+)'/g);
|
|
193
|
+
const enumValues = enumMatches ? enumMatches.map((e) => e.replace(/'/g, "")) : void 0;
|
|
194
|
+
params.set(paramName, {
|
|
195
|
+
description: trimmedDesc,
|
|
196
|
+
enumValues: enumValues && enumValues.length > 0 ? enumValues : void 0
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return params;
|
|
200
|
+
}
|
|
201
|
+
extractArgsFromPosition(content, startIndex, jsdocComment = "") {
|
|
171
202
|
const args = [];
|
|
203
|
+
const jsdocParams = this.parseJSDocParams(jsdocComment);
|
|
172
204
|
const afterStart = content.slice(startIndex);
|
|
173
|
-
const argsMatch = afterStart.match(/args:\s*\{([^}]*)\}/);
|
|
205
|
+
const argsMatch = afterStart.match(/args:\s*\{([^}]*)\}/s);
|
|
174
206
|
if (argsMatch) {
|
|
175
207
|
const argsContent = argsMatch[1];
|
|
176
208
|
const argPattern = /(\w+):\s*(v\.optional\()?v\.(\w+)/g;
|
|
177
209
|
let argMatch;
|
|
178
210
|
while ((argMatch = argPattern.exec(argsContent)) !== null) {
|
|
179
211
|
const [, argName, isOptional, argType] = argMatch;
|
|
212
|
+
const jsdocInfo = jsdocParams.get(argName);
|
|
180
213
|
args.push({
|
|
181
214
|
name: argName,
|
|
182
215
|
type: argType,
|
|
183
|
-
optional: !!isOptional
|
|
216
|
+
optional: !!isOptional,
|
|
217
|
+
description: jsdocInfo?.description,
|
|
218
|
+
enumValues: jsdocInfo?.enumValues
|
|
184
219
|
});
|
|
185
220
|
}
|
|
186
221
|
}
|
|
222
|
+
const hasPaginationOpts = afterStart.match(/paginationOptsValidator/s);
|
|
223
|
+
if (hasPaginationOpts) {
|
|
224
|
+
args.push({
|
|
225
|
+
name: "paginationOpts",
|
|
226
|
+
type: "PaginationOptions",
|
|
227
|
+
optional: false,
|
|
228
|
+
description: "Pagination options with cursor and numItems"
|
|
229
|
+
});
|
|
230
|
+
}
|
|
187
231
|
return args;
|
|
188
232
|
}
|
|
189
233
|
async parseSchemaFile(convexDir) {
|
|
@@ -213,4 +257,4 @@ var SchemaWatcher = class extends EventEmitter {
|
|
|
213
257
|
export {
|
|
214
258
|
SchemaWatcher
|
|
215
259
|
};
|
|
216
|
-
//# sourceMappingURL=chunk-
|
|
260
|
+
//# sourceMappingURL=chunk-ENBIFEKV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/schema-watcher.ts"],"sourcesContent":["import chokidar from 'chokidar';\nimport { EventEmitter } from 'events';\nimport fs from 'fs';\nimport path from 'path';\n\nexport interface FunctionInfo {\n name: string;\n path: string; // Full path like \"products/products:list\"\n type: 'query' | 'mutation' | 'action';\n args: ArgInfo[];\n returns?: string;\n}\n\nexport interface ArgInfo {\n name: string;\n type: string;\n optional: boolean;\n description?: string;\n enumValues?: string[];\n}\n\nexport interface ModuleInfo {\n name: string;\n path: string;\n functions: FunctionInfo[];\n children: ModuleInfo[];\n}\n\nexport interface SchemaInfo {\n modules: ModuleInfo[];\n tables: TableInfo[];\n lastUpdated: Date;\n}\n\nexport interface TableInfo {\n name: string;\n fields: FieldInfo[];\n}\n\nexport interface FieldInfo {\n name: string;\n type: string;\n optional: boolean;\n}\n\nexport class SchemaWatcher extends EventEmitter {\n private projectDir: string;\n private watcher: chokidar.FSWatcher | null = null;\n private schemaInfo: SchemaInfo | null = null;\n\n constructor(projectDir: string) {\n super();\n this.projectDir = projectDir;\n }\n\n async start(): Promise<void> {\n // Initial parse\n await this.parseSchema();\n\n // Watch for changes\n const convexDir = path.join(this.projectDir, 'convex');\n\n this.watcher = chokidar.watch(convexDir, {\n persistent: true,\n ignoreInitial: true,\n ignored: [\n '**/node_modules/**',\n '**/_generated/**',\n '**/test.setup.ts',\n '**/*.test.ts',\n ],\n });\n\n this.watcher.on('change', async (filePath) => {\n if (filePath.endsWith('.ts') && !filePath.includes('_generated')) {\n console.log(`[SchemaWatcher] File changed: ${filePath}`);\n await this.parseSchema();\n this.emit('schema-updated', this.schemaInfo);\n }\n });\n\n this.watcher.on('add', async (filePath) => {\n if (filePath.endsWith('.ts') && !filePath.includes('_generated')) {\n console.log(`[SchemaWatcher] File added: ${filePath}`);\n await this.parseSchema();\n this.emit('schema-updated', this.schemaInfo);\n }\n });\n }\n\n stop(): void {\n if (this.watcher) {\n this.watcher.close();\n this.watcher = null;\n }\n }\n\n getSchema(): SchemaInfo | null {\n return this.schemaInfo;\n }\n\n private async parseSchema(): Promise<void> {\n try {\n const convexDir = path.join(this.projectDir, 'convex');\n const modules = await this.parseConvexDirectory(convexDir);\n const tables = await this.parseSchemaFile(convexDir);\n\n this.schemaInfo = {\n modules,\n tables,\n lastUpdated: new Date(),\n };\n\n const funcCount = this.countFunctions(modules);\n console.log(\n `[SchemaWatcher] Parsed ${funcCount} functions from ${modules.length} modules`\n );\n } catch (error) {\n console.error('[SchemaWatcher] Error parsing schema:', error);\n }\n }\n\n private countFunctions(modules: ModuleInfo[]): number {\n let count = 0;\n for (const mod of modules) {\n count += mod.functions.length;\n count += this.countFunctions(mod.children);\n }\n return count;\n }\n\n private async parseConvexDirectory(convexDir: string): Promise<ModuleInfo[]> {\n const modules: ModuleInfo[] = [];\n\n // Read convex directory structure\n const entries = fs.readdirSync(convexDir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip hidden, generated, and test files\n if (\n entry.name.startsWith('.') ||\n entry.name.startsWith('_') ||\n entry.name === 'node_modules' ||\n entry.name.endsWith('.test.ts') ||\n entry.name === 'test.setup.ts'\n ) {\n continue;\n }\n\n if (entry.isDirectory()) {\n // Parse subdirectory as module\n const subModule = await this.parseSubdirectory(\n path.join(convexDir, entry.name),\n entry.name\n );\n if (subModule.functions.length > 0 || subModule.children.length > 0) {\n modules.push(subModule);\n }\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n // Parse root-level file\n const filePath = path.join(convexDir, entry.name);\n const moduleName = entry.name.replace('.ts', '');\n const functions = await this.parseFile(filePath, moduleName);\n\n if (functions.length > 0) {\n modules.push({\n name: moduleName,\n path: moduleName,\n functions,\n children: [],\n });\n }\n }\n }\n\n return modules;\n }\n\n private async parseSubdirectory(\n dirPath: string,\n parentPath: string\n ): Promise<ModuleInfo> {\n const module: ModuleInfo = {\n name: path.basename(dirPath),\n path: parentPath,\n functions: [],\n children: [],\n };\n\n const entries = fs.readdirSync(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip tests directory and test files\n if (\n entry.name === 'tests' ||\n entry.name.endsWith('.test.ts') ||\n entry.name.startsWith('.')\n ) {\n continue;\n }\n\n if (entry.isDirectory()) {\n const subModule = await this.parseSubdirectory(\n path.join(dirPath, entry.name),\n `${parentPath}/${entry.name}`\n );\n if (subModule.functions.length > 0 || subModule.children.length > 0) {\n module.children.push(subModule);\n }\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n const filePath = path.join(dirPath, entry.name);\n const moduleName = entry.name.replace('.ts', '');\n const modulePath = `${parentPath}/${moduleName}`;\n const functions = await this.parseFile(filePath, modulePath);\n\n // Create a child module for each file (group by file)\n if (functions.length > 0) {\n module.children.push({\n name: moduleName,\n path: modulePath,\n functions,\n children: [],\n });\n }\n }\n }\n\n return module;\n }\n\n private async parseFile(\n filePath: string,\n modulePath: string\n ): Promise<FunctionInfo[]> {\n const functions: FunctionInfo[] = [];\n\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n\n // Parse exported functions using regex\n // Match patterns like: export const functionName = query({ or mutation({ or action({\n const exportPattern =\n /export\\s+const\\s+(\\w+)\\s*=\\s*(query|mutation|action|internalQuery|internalMutation|internalAction)\\s*\\(\\s*\\{/g;\n\n let match;\n while ((match = exportPattern.exec(content)) !== null) {\n const [, funcName, funcType] = match;\n\n // Normalize internal functions to their base type\n let normalizedType = funcType as 'query' | 'mutation' | 'action';\n if (funcType.startsWith('internal')) {\n normalizedType = funcType.replace('internal', '').toLowerCase() as\n | 'query'\n | 'mutation'\n | 'action';\n }\n\n // Extract JSDoc comment above the function\n const jsdocComment = this.extractJSDocAbove(content, match.index);\n\n // Extract args from the function definition\n const args = this.extractArgsFromPosition(\n content,\n match.index,\n jsdocComment\n );\n\n functions.push({\n name: funcName,\n path: `${modulePath}:${funcName}`,\n type: normalizedType,\n args,\n });\n }\n } catch (error) {\n console.error(`[SchemaWatcher] Error parsing file ${filePath}:`, error);\n }\n\n return functions;\n }\n\n private extractJSDocAbove(content: string, position: number): string {\n // Look backwards from position to find JSDoc comment\n // Allow some whitespace and newlines between the JSDoc and the export\n const beforePosition = content.slice(0, position);\n // Match JSDoc that ends with */ followed by optional whitespace before the export\n const jsdocMatch = beforePosition.match(/\\/\\*\\*([\\s\\S]*?)\\*\\/\\s*$/);\n if (jsdocMatch) {\n return jsdocMatch[1];\n }\n\n // Also try to find JSDoc within the last 500 chars (in case there's space between)\n const last500 = beforePosition.slice(-500);\n const jsdocMatch2 = last500.match(/\\/\\*\\*([\\s\\S]*?)\\*\\//);\n return jsdocMatch2 ? jsdocMatch2[1] : '';\n }\n\n private parseJSDocParams(\n jsdoc: string\n ): Map<string, { description: string; enumValues?: string[] }> {\n const params = new Map<\n string,\n { description: string; enumValues?: string[] }\n >();\n\n // Match @param patterns like: @param sortBy - Sort order: 'newest', 'oldest'\n const paramPattern = /@param\\s+(\\w+)\\s*-?\\s*([^@]*)/g;\n let match;\n while ((match = paramPattern.exec(jsdoc)) !== null) {\n const [, paramName, description] = match;\n const trimmedDesc = description.trim();\n\n // Extract enum values from description (quoted strings like 'value1', 'value2')\n const enumMatches = trimmedDesc.match(/'([^']+)'/g);\n const enumValues = enumMatches\n ? enumMatches.map((e) => e.replace(/'/g, ''))\n : undefined;\n\n params.set(paramName, {\n description: trimmedDesc,\n enumValues:\n enumValues && enumValues.length > 0 ? enumValues : undefined,\n });\n }\n\n return params;\n }\n\n private extractArgsFromPosition(\n content: string,\n startIndex: number,\n jsdocComment: string = ''\n ): ArgInfo[] {\n const args: ArgInfo[] = [];\n const jsdocParams = this.parseJSDocParams(jsdocComment);\n\n // Find the args: { ... } section\n const afterStart = content.slice(startIndex);\n const argsMatch = afterStart.match(/args:\\s*\\{([^}]*)\\}/s);\n\n if (argsMatch) {\n const argsContent = argsMatch[1];\n\n // Parse individual args\n // Matches patterns like: argName: v.string(), argName: v.optional(v.id('users'))\n const argPattern = /(\\w+):\\s*(v\\.optional\\()?v\\.(\\w+)/g;\n\n let argMatch;\n while ((argMatch = argPattern.exec(argsContent)) !== null) {\n const [, argName, isOptional, argType] = argMatch;\n const jsdocInfo = jsdocParams.get(argName);\n\n args.push({\n name: argName,\n type: argType,\n optional: !!isOptional,\n description: jsdocInfo?.description,\n enumValues: jsdocInfo?.enumValues,\n });\n }\n }\n\n // Check for paginationOpts (built-in Convex pagination)\n const hasPaginationOpts = afterStart.match(/paginationOptsValidator/s);\n if (hasPaginationOpts) {\n // Add paginationOpts as a synthetic argument\n args.push({\n name: 'paginationOpts',\n type: 'PaginationOptions',\n optional: false,\n description: 'Pagination options with cursor and numItems',\n });\n }\n\n return args;\n }\n\n private async parseSchemaFile(convexDir: string): Promise<TableInfo[]> {\n const tables: TableInfo[] = [];\n const schemaPath = path.join(convexDir, 'schema.ts');\n\n if (!fs.existsSync(schemaPath)) {\n return tables;\n }\n\n try {\n const content = fs.readFileSync(schemaPath, 'utf-8');\n\n // Match table definitions: tableName: defineTable(\n const tablePattern = /(\\w+):\\s*defineTable\\(/g;\n\n let match;\n while ((match = tablePattern.exec(content)) !== null) {\n tables.push({\n name: match[1],\n fields: [], // Could parse fields but keeping simple for now\n });\n }\n } catch (error) {\n console.error('[SchemaWatcher] Error parsing schema file:', error);\n }\n\n return tables;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-75H32MO3.js";
|
|
5
5
|
import {
|
|
6
6
|
SchemaWatcher
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-ENBIFEKV.js";
|
|
8
|
+
import "./chunk-4PBPPYEF.js";
|
|
9
9
|
|
|
10
10
|
// src/cli/index.ts
|
|
11
11
|
import chalk from "chalk";
|
|
@@ -13,10 +13,18 @@ import { Command } from "commander";
|
|
|
13
13
|
import dotenv from "dotenv";
|
|
14
14
|
import fs from "fs";
|
|
15
15
|
import open from "open";
|
|
16
|
+
import os from "os";
|
|
16
17
|
import path from "path";
|
|
17
18
|
var program = new Command();
|
|
18
|
-
program.name("convex-devtools").description("A standalone development tool for testing Convex functions").version("
|
|
19
|
-
program.option("-p, --port <number>", "Port for the devtools server", "5173").option("-d, --dir <path>", "Path to Convex project directory", ".").option(
|
|
19
|
+
program.name("convex-devtools").description("A standalone development tool for testing Convex functions").version("1.0.0");
|
|
20
|
+
program.option("-p, --port <number>", "Port for the devtools server", "5173").option("-d, --dir <path>", "Path to Convex project directory", ".").option(
|
|
21
|
+
"--storage <mode>",
|
|
22
|
+
"Storage scope: project (default), global, or path",
|
|
23
|
+
"project"
|
|
24
|
+
).option(
|
|
25
|
+
"--storage-path <path>",
|
|
26
|
+
"Custom storage path when using --storage path"
|
|
27
|
+
).option("--no-open", "Do not open browser automatically").action(async (options) => {
|
|
20
28
|
const projectDir = path.resolve(options.dir);
|
|
21
29
|
const envLocalPath = path.join(projectDir, ".env.local");
|
|
22
30
|
const envPath = path.join(projectDir, ".env");
|
|
@@ -69,7 +77,7 @@ program.option("-p, --port <number>", "Port for the devtools server", "5173").op
|
|
|
69
77
|
}
|
|
70
78
|
console.log(chalk.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
71
79
|
console.log(
|
|
72
|
-
chalk.cyan("\u2551") + chalk.white.bold(" Convex DevTools
|
|
80
|
+
chalk.cyan("\u2551") + chalk.white.bold(" Convex DevTools v1.0.0 ") + chalk.cyan("\u2551")
|
|
73
81
|
);
|
|
74
82
|
console.log(chalk.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
75
83
|
console.log();
|
|
@@ -77,6 +85,34 @@ program.option("-p, --port <number>", "Port for the devtools server", "5173").op
|
|
|
77
85
|
console.log(chalk.green("\u2713") + ` Convex URL: ${chalk.dim(convexUrl)}`);
|
|
78
86
|
console.log(chalk.green("\u2713") + ` Project: ${chalk.dim(projectDir)}`);
|
|
79
87
|
console.log();
|
|
88
|
+
const storageMode = String(options.storage || "project");
|
|
89
|
+
let persistencePath;
|
|
90
|
+
if (storageMode === "project") {
|
|
91
|
+
persistencePath = path.join(
|
|
92
|
+
projectDir,
|
|
93
|
+
".convex-devtools",
|
|
94
|
+
"devtools.sqlite"
|
|
95
|
+
);
|
|
96
|
+
} else if (storageMode === "global") {
|
|
97
|
+
persistencePath = path.join(
|
|
98
|
+
os.homedir(),
|
|
99
|
+
".convex-devtools",
|
|
100
|
+
"devtools.sqlite"
|
|
101
|
+
);
|
|
102
|
+
} else if (storageMode === "path") {
|
|
103
|
+
if (!options.storagePath) {
|
|
104
|
+
console.error(
|
|
105
|
+
chalk.red("\u2717 --storage path requires --storage-path <path>")
|
|
106
|
+
);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
persistencePath = path.resolve(String(options.storagePath));
|
|
110
|
+
} else {
|
|
111
|
+
console.error(
|
|
112
|
+
chalk.red("\u2717 Invalid --storage value. Use project, global, or path.")
|
|
113
|
+
);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
80
116
|
const schemaWatcher = new SchemaWatcher(projectDir);
|
|
81
117
|
await schemaWatcher.start();
|
|
82
118
|
const port = parseInt(options.port, 10);
|
|
@@ -85,7 +121,8 @@ program.option("-p, --port <number>", "Port for the devtools server", "5173").op
|
|
|
85
121
|
projectDir,
|
|
86
122
|
convexUrl,
|
|
87
123
|
deployKey,
|
|
88
|
-
schemaWatcher
|
|
124
|
+
schemaWatcher,
|
|
125
|
+
persistencePath
|
|
89
126
|
});
|
|
90
127
|
console.log(
|
|
91
128
|
chalk.green("\u2713") + ` DevTools running at ${chalk.cyan(`http://localhost:${port}`)}`
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport dotenv from 'dotenv';\nimport fs from 'fs';\nimport open from 'open';\nimport path from 'path';\nimport { createServer } from '../server/index.js';\nimport { SchemaWatcher } from '../server/schema-watcher.js';\n\nconst program = new Command();\n\nprogram\n .name('convex-devtools')\n .description('A standalone development tool for testing Convex functions')\n .version('
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport dotenv from 'dotenv';\nimport fs from 'fs';\nimport open from 'open';\nimport os from 'os';\nimport path from 'path';\nimport { createServer } from '../server/index.js';\nimport { SchemaWatcher } from '../server/schema-watcher.js';\n\nconst program = new Command();\n\nprogram\n .name('convex-devtools')\n .description('A standalone development tool for testing Convex functions')\n .version('1.0.0');\n\nprogram\n .option('-p, --port <number>', 'Port for the devtools server', '5173')\n .option('-d, --dir <path>', 'Path to Convex project directory', '.')\n .option(\n '--storage <mode>',\n 'Storage scope: project (default), global, or path',\n 'project'\n )\n .option(\n '--storage-path <path>',\n 'Custom storage path when using --storage path'\n )\n .option('--no-open', 'Do not open browser automatically')\n .action(async (options) => {\n const projectDir = path.resolve(options.dir);\n\n // Load .env.local from the project directory\n const envLocalPath = path.join(projectDir, '.env.local');\n const envPath = path.join(projectDir, '.env');\n\n if (fs.existsSync(envLocalPath)) {\n dotenv.config({ path: envLocalPath });\n } else if (fs.existsSync(envPath)) {\n dotenv.config({ path: envPath });\n }\n\n // Check for required environment variables\n if (process.env.CONVEX_DEVTOOLS_ENABLED !== 'true') {\n console.error(\n chalk.red('✗ CONVEX_DEVTOOLS_ENABLED is not set to \"true\"')\n );\n console.error(\n chalk.yellow(\n ' Add CONVEX_DEVTOOLS_ENABLED=true to your .env.local file'\n )\n );\n console.error(\n chalk.yellow(' This tool is intended for local development only.')\n );\n process.exit(1);\n }\n\n const convexUrl = process.env.CONVEX_URL;\n if (!convexUrl) {\n console.error(chalk.red('✗ CONVEX_URL is not set'));\n console.error(\n chalk.yellow(\n ' Make sure you have a valid Convex deployment URL in your .env.local'\n )\n );\n process.exit(1);\n }\n\n // Deploy key is optional for local development\n // Without it, identity mocking won't work but you can still invoke functions\n const deployKey = process.env.CONVEX_DEPLOY_KEY || '';\n if (!deployKey) {\n console.log(chalk.yellow('⚠ CONVEX_DEPLOY_KEY is not set'));\n console.log(chalk.yellow(' Identity mocking will be disabled.'));\n console.log(\n chalk.yellow(\n ' To enable, add to .env.local: CONVEX_DEPLOY_KEY=prod:xxx or dev:xxx'\n )\n );\n console.log();\n }\n\n // Check for convex/_generated directory\n const generatedDir = path.join(projectDir, 'convex', '_generated');\n if (!fs.existsSync(generatedDir)) {\n console.error(chalk.red('✗ Convex generated files not found'));\n console.error(chalk.yellow(` Expected: ${generatedDir}`));\n console.error(chalk.yellow(' Run \"npx convex dev\" to generate them.'));\n process.exit(1);\n }\n\n console.log(chalk.cyan('╔══════════════════════════════════════════╗'));\n console.log(\n chalk.cyan('║') +\n chalk.white.bold(' Convex DevTools v1.0.0 ') +\n chalk.cyan('║')\n );\n console.log(chalk.cyan('╚══════════════════════════════════════════╝'));\n console.log();\n console.log(chalk.green('✓') + ' Environment validated');\n console.log(chalk.green('✓') + ` Convex URL: ${chalk.dim(convexUrl)}`);\n console.log(chalk.green('✓') + ` Project: ${chalk.dim(projectDir)}`);\n console.log();\n\n const storageMode = String(options.storage || 'project');\n let persistencePath: string | undefined;\n\n if (storageMode === 'project') {\n persistencePath = path.join(\n projectDir,\n '.convex-devtools',\n 'devtools.sqlite'\n );\n } else if (storageMode === 'global') {\n persistencePath = path.join(\n os.homedir(),\n '.convex-devtools',\n 'devtools.sqlite'\n );\n } else if (storageMode === 'path') {\n if (!options.storagePath) {\n console.error(\n chalk.red('✗ --storage path requires --storage-path <path>')\n );\n process.exit(1);\n }\n persistencePath = path.resolve(String(options.storagePath));\n } else {\n console.error(\n chalk.red('✗ Invalid --storage value. Use project, global, or path.')\n );\n process.exit(1);\n }\n\n // Start schema watcher\n const schemaWatcher = new SchemaWatcher(projectDir);\n await schemaWatcher.start();\n\n // Start server\n const port = parseInt(options.port, 10);\n const server = await createServer({\n port,\n projectDir,\n convexUrl,\n deployKey,\n schemaWatcher,\n persistencePath,\n });\n\n console.log(\n chalk.green('✓') +\n ` DevTools running at ${chalk.cyan(`http://localhost:${port}`)}`\n );\n console.log();\n console.log(chalk.dim('Press Ctrl+C to stop'));\n\n if (options.open !== false) {\n await open(`http://localhost:${port}`);\n }\n\n // Handle shutdown\n process.on('SIGINT', async () => {\n console.log(chalk.dim('\\nShutting down...'));\n schemaWatcher.stop();\n server.close();\n process.exit(0);\n });\n });\n\nprogram.parse();\n"],"mappings":"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","names":[]}
|