Skip to content

Commit 1adc62c

Browse files
author
Seth Raphael
committed
Refactor CLI and documentation for Convex Self Static Hosting. Updated README to replace 'module' terminology with 'component' for clarity. Simplified example code in main.tsx by removing the auto-detection helper. Enhanced Cloudflare setup script to verify user authentication and improved token retrieval process. Updated upload functionality to reflect component changes and ensure accurate logging during uploads.
1 parent d53eb67 commit 1adc62c

6 files changed

Lines changed: 80 additions & 91 deletions

File tree

README.md

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -90,32 +90,19 @@ export const { generateUploadUrl, recordAsset, gcOldAssets, listAssets } =
9090
}
9191
```
9292

93-
The CLI will automatically find your `dist/` directory and upload to the `staticHosting` module.
93+
The CLI will automatically find your `dist/` directory and upload to the `staticHosting` component.
9494

9595
**CLI Options:**
9696
```bash
9797
npx @get-convex/self-static-hosting upload [options]
9898

9999
Options:
100100
-d, --dist <path> Path to dist directory (default: ./dist)
101-
-m, --module <name> Convex module name (default: staticHosting)
101+
-c, --component <name> Convex component name (default: staticHosting)
102102
--domain <name> Domain for Cloudflare cache purge (auto-detects zone ID)
103103
-h, --help Show help
104104
```
105105

106-
### 4. Update your app's entry point (optional)
107-
108-
In your `main.tsx`, use the helper to auto-detect the Convex URL when deployed:
109-
110-
```tsx
111-
import { ConvexProvider, ConvexReactClient } from "convex/react";
112-
import { getConvexUrlWithFallback } from "@get-convex/self-static-hosting";
113-
114-
// Works both in development (uses VITE_CONVEX_URL) and production (auto-detects)
115-
const convexUrl = getConvexUrlWithFallback(import.meta.env.VITE_CONVEX_URL);
116-
const convex = new ConvexReactClient(convexUrl);
117-
```
118-
119106
## Deployment
120107

121108
```bash

example/src/main.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import { StrictMode } from "react";
22
import { createRoot } from "react-dom/client";
33
import { ConvexProvider, ConvexReactClient } from "convex/react";
4-
import { getConvexUrlWithFallback } from "@get-convex/self-static-hosting";
54
import App from "./App.jsx";
65
import "./index.css";
76

8-
// When running locally, use VITE_CONVEX_URL from .env.local
9-
// When deployed to Convex static hosting, derive the URL from the hostname
10-
const convexUrl = getConvexUrlWithFallback(import.meta.env.VITE_CONVEX_URL);
11-
12-
const convex = new ConvexReactClient(convexUrl);
7+
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
138

149
createRoot(document.getElementById("root")!).render(
1510
<StrictMode>

src/cli/init.ts

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -92,38 +92,16 @@ export default http;
9292
}
9393
\`\`\`
9494
95-
If using a path prefix, specify the module:
95+
If using a path prefix, specify the component:
9696
\`\`\`json
9797
{
9898
"scripts": {
99-
"deploy:static": "npm run build && npx @get-convex/self-static-hosting upload --module staticHosting"
99+
"deploy:static": "npm run build && npx @get-convex/self-static-hosting upload --component staticHosting"
100100
}
101101
}
102102
\`\`\`
103103
104-
### 5. src/main.tsx (modify entry point)
105-
106-
\`\`\`typescript
107-
import React from "react";
108-
import ReactDOM from "react-dom/client";
109-
import { ConvexProvider, ConvexReactClient } from "convex/react";
110-
import { getConvexUrlWithFallback } from "@get-convex/self-static-hosting";
111-
import App from "./App";
112-
113-
// Auto-detects Convex URL when deployed to *.convex.site
114-
const convexUrl = getConvexUrlWithFallback(import.meta.env.VITE_CONVEX_URL);
115-
const convex = new ConvexReactClient(convexUrl);
116-
117-
ReactDOM.createRoot(document.getElementById("root")!).render(
118-
<React.StrictMode>
119-
<ConvexProvider client={convex}>
120-
<App />
121-
</ConvexProvider>
122-
</React.StrictMode>
123-
);
124-
\`\`\`
125-
126-
### 6. src/App.tsx (optional: add live reload banner)
104+
### 5. src/App.tsx (optional: add live reload banner)
127105
128106
\`\`\`typescript
129107
import { UpdateBanner } from "@get-convex/self-static-hosting/react";

src/cli/setup-cloudflare.ts

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,43 @@ function getConvexSiteUrl(): string | null {
9696
}
9797

9898
/**
99-
* Get API token from wrangler config
99+
* Check if wrangler is logged in by running `wrangler whoami`
100+
*/
101+
function isWranglerLoggedIn(): boolean {
102+
try {
103+
const result = execSync("npx wrangler whoami", { stdio: "pipe", encoding: "utf-8" });
104+
// If the command succeeds and doesn't contain "not authenticated", we're logged in
105+
return !result.toLowerCase().includes("not authenticated");
106+
} catch {
107+
return false;
108+
}
109+
}
110+
111+
/**
112+
* Get API token from wrangler config (checks multiple locations/formats)
100113
*/
101114
function getWranglerToken(): string | null {
102-
const configPath = join(homedir(), ".wrangler", "config", "default.toml");
103-
if (existsSync(configPath)) {
104-
const content = readFileSync(configPath, "utf-8");
105-
const match = content.match(/oauth_token\s*=\s*"([^"]+)"/);
106-
if (match) {
107-
return match[1];
115+
// Try the new config location first (wrangler 3.x+)
116+
const configPaths = [
117+
join(homedir(), ".wrangler", "config", "default.toml"),
118+
join(homedir(), ".wrangler", "config.toml"),
119+
];
120+
121+
const tokenPatterns = [
122+
/oauth_token\s*=\s*"([^"]+)"/,
123+
/access_token\s*=\s*"([^"]+)"/,
124+
/token\s*=\s*"([^"]+)"/,
125+
];
126+
127+
for (const configPath of configPaths) {
128+
if (existsSync(configPath)) {
129+
const content = readFileSync(configPath, "utf-8");
130+
for (const pattern of tokenPatterns) {
131+
const match = content.match(pattern);
132+
if (match) {
133+
return match[1];
134+
}
135+
}
108136
}
109137
}
110138
return null;
@@ -281,8 +309,8 @@ async function main(): Promise<void> {
281309
log("");
282310
log("Step 2: Checking Cloudflare authentication...");
283311

284-
let token = getWranglerToken();
285-
if (!token) {
312+
let loggedIn = isWranglerLoggedIn();
313+
if (!loggedIn) {
286314
info("Not logged in to Cloudflare.");
287315
const shouldLogin = await promptYesNo("Would you like to login now?");
288316
if (!shouldLogin) {
@@ -295,15 +323,35 @@ async function main(): Promise<void> {
295323
log("Opening browser for Cloudflare login...");
296324
spawnSync("npx", ["wrangler", "login"], { stdio: "inherit" });
297325

298-
token = getWranglerToken();
299-
if (!token) {
326+
// Verify login succeeded
327+
loggedIn = isWranglerLoggedIn();
328+
if (!loggedIn) {
300329
error("Login failed. Please try again.");
301330
rl.close();
302331
process.exit(1);
303332
}
304333
}
305334
success("Logged in to Cloudflare");
306335

336+
// Try to get OAuth token from wrangler config for API calls
337+
let token = getWranglerToken();
338+
if (!token) {
339+
warn("Could not read wrangler OAuth token from config file.");
340+
log("This may happen with newer versions of wrangler.");
341+
log("");
342+
log("Please create an API token manually:");
343+
log(" 1. Go to https://dash.cloudflare.com/profile/api-tokens");
344+
log(" 2. Click 'Create Token'");
345+
log(" 3. Use 'Edit zone DNS' template (or custom with Zone:Read, DNS:Edit, Cache Purge)");
346+
log("");
347+
token = await prompt("Paste your API token here: ");
348+
if (!token) {
349+
error("API token is required to continue.");
350+
rl.close();
351+
process.exit(1);
352+
}
353+
}
354+
307355
// Step 3: Get Convex site URL
308356
log("");
309357
log("Step 3: Getting your Convex deployment URL...");

src/cli/upload.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
* Options:
99
* --dist <path> Path to dist directory (default: ./dist)
10-
* --module <name> Convex module with upload functions (default: staticHosting)
10+
* --component <name> Convex component with upload functions (default: staticHosting)
1111
* --domain <domain> Domain for Cloudflare cache purge (auto-detects zone ID)
1212
* --help Show help
1313
*/
@@ -47,15 +47,15 @@ function getMimeType(path: string): string {
4747

4848
interface ParsedArgs {
4949
dist: string;
50-
module: string;
50+
component: string;
5151
domain: string | null;
5252
help: boolean;
5353
}
5454

5555
function parseArgs(args: string[]): ParsedArgs {
5656
const result: ParsedArgs = {
5757
dist: "./dist",
58-
module: "staticHosting",
58+
component: "staticHosting",
5959
domain: null,
6060
help: false,
6161
};
@@ -66,8 +66,8 @@ function parseArgs(args: string[]): ParsedArgs {
6666
result.help = true;
6767
} else if (arg === "--dist" || arg === "-d") {
6868
result.dist = args[++i] || result.dist;
69-
} else if (arg === "--module" || arg === "-m") {
70-
result.module = args[++i] || result.module;
69+
} else if (arg === "--component" || arg === "-c") {
70+
result.component = args[++i] || result.component;
7171
} else if (arg === "--domain") {
7272
result.domain = args[++i] || null;
7373
}
@@ -83,10 +83,10 @@ Usage: npx @get-convex/self-static-hosting upload [options]
8383
Upload static files from a dist directory to Convex storage.
8484
8585
Options:
86-
-d, --dist <path> Path to dist directory (default: ./dist)
87-
-m, --module <name> Convex module with upload functions (default: staticHosting)
88-
--domain <name> Domain for Cloudflare cache purge (e.g., example.com)
89-
-h, --help Show this help message
86+
-d, --dist <path> Path to dist directory (default: ./dist)
87+
-c, --component <name> Convex component with upload functions (default: staticHosting)
88+
--domain <name> Domain for Cloudflare cache purge (e.g., example.com)
89+
-h, --help Show this help message
9090
9191
Cloudflare Cache Purging:
9292
The CLI will automatically purge Cloudflare cache if credentials are available.
@@ -277,7 +277,7 @@ async function main(): Promise<void> {
277277
}
278278

279279
const distDir = resolve(args.dist);
280-
const moduleName = args.module;
280+
const componentName = args.component;
281281

282282
if (!existsSync(distDir)) {
283283
console.error(`Error: dist directory not found: ${distDir}`);
@@ -292,14 +292,14 @@ async function main(): Promise<void> {
292292
console.log(
293293
`Uploading ${files.length} files with deployment ID: ${deploymentId}`,
294294
);
295-
console.log(`Module: ${moduleName}`);
295+
console.log(`Component: ${componentName}`);
296296
console.log("");
297297

298298
for (const file of files) {
299299
const content = readFileSync(file.localPath);
300300

301301
// Get upload URL via internal function
302-
const uploadUrlOutput = convexRun(`${moduleName}:generateUploadUrl`);
302+
const uploadUrlOutput = convexRun(`${componentName}:generateUploadUrl`);
303303
const uploadUrl = JSON.parse(uploadUrlOutput);
304304

305305
// Upload to storage
@@ -312,7 +312,7 @@ async function main(): Promise<void> {
312312
const { storageId } = (await response.json()) as { storageId: string };
313313

314314
// Record in database via internal function
315-
convexRun(`${moduleName}:recordAsset`, {
315+
convexRun(`${componentName}:recordAsset`, {
316316
path: file.path,
317317
storageId,
318318
contentType: file.contentType,
@@ -325,7 +325,7 @@ async function main(): Promise<void> {
325325
console.log("");
326326

327327
// Garbage collect old files
328-
const deletedOutput = convexRun(`${moduleName}:gcOldAssets`, {
328+
const deletedOutput = convexRun(`${componentName}:gcOldAssets`, {
329329
currentDeploymentId: deploymentId,
330330
});
331331
const deleted = JSON.parse(deletedOutput);
@@ -364,7 +364,7 @@ async function main(): Promise<void> {
364364
console.log("☁️ Purging Cloudflare cache...");
365365
try {
366366
// Use Convex function (useful for CI/CD where you might want logging)
367-
convexRun(`${moduleName}:purgeCloudflareCache`, {
367+
convexRun(`${componentName}:purgeCloudflareCache`, {
368368
zoneId: cloudflareZoneId,
369369
apiToken: cloudflareApiToken,
370370
purgeAll: true,

src/client/index.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -374,25 +374,6 @@ export function getConvexUrl(): string {
374374
);
375375
}
376376

377-
/**
378-
* Get the Convex URL, with fallback to environment variable.
379-
* Safe to call in both browser and non-browser contexts when env var is set.
380-
*
381-
* @param envUrl - The environment variable value (e.g., import.meta.env.VITE_CONVEX_URL)
382-
*
383-
* @example
384-
* ```typescript
385-
* const convexUrl = getConvexUrlWithFallback(import.meta.env.VITE_CONVEX_URL);
386-
* ```
387-
*/
388-
export function getConvexUrlWithFallback(envUrl?: string): string {
389-
if (envUrl) {
390-
return envUrl;
391-
}
392-
393-
return getConvexUrl();
394-
}
395-
396377
/**
397378
* Expose an action to purge Cloudflare cache after deployment.
398379
* This is optional - only needed if you're using Cloudflare as a CDN.

0 commit comments

Comments
 (0)