create-vexcms 0.0.3 → 0.0.5
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
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# create-vexcms
|
|
2
|
+
|
|
3
|
+
Scaffolding CLI for [VEX CMS](https://github.com/ianyimi/vex) projects. Creates a complete Next.js application with Convex backend, Better Auth authentication, and an admin panel — ready to run.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm create vexcms@latest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with a project name:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm create vexcms@latest my-project
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Supports relative paths:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm create vexcms@latest apps/website
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Flags
|
|
24
|
+
|
|
25
|
+
| Flag | Description |
|
|
26
|
+
|------|-------------|
|
|
27
|
+
| `--bare` | Skip the marketing site template. Scaffolds an empty VEX CMS project with no collections. |
|
|
28
|
+
| `--orgs` | Enable multi-tenant organizations (adds Better Auth organizations plugin). |
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Empty project with no pre-built collections
|
|
32
|
+
pnpm create vexcms@latest my-app --bare
|
|
33
|
+
|
|
34
|
+
# Project with organizations support
|
|
35
|
+
pnpm create vexcms@latest my-app --orgs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Interactive Prompts
|
|
39
|
+
|
|
40
|
+
The CLI walks you through:
|
|
41
|
+
|
|
42
|
+
1. **Project name** — validates npm package name rules, supports `.` for current directory
|
|
43
|
+
2. **Framework** — Next.js (recommended) or TanStack Start (coming soon)
|
|
44
|
+
3. **Email/password auth** — enable or disable (default: yes)
|
|
45
|
+
4. **OAuth providers** — multi-select from 30+ Better Auth providers (Google, GitHub, Discord, etc.)
|
|
46
|
+
5. **Organizations** — enable multi-tenant support (default: no, skipped if `--orgs` passed)
|
|
47
|
+
6. **Git repository** — initialize git (default: yes)
|
|
48
|
+
7. **Install dependencies** — run package install (default: no)
|
|
49
|
+
|
|
50
|
+
## Templates
|
|
51
|
+
|
|
52
|
+
### Marketing Site (default)
|
|
53
|
+
|
|
54
|
+
Pre-built collections for a marketing website:
|
|
55
|
+
|
|
56
|
+
- **Pages** — with title, slug, richtext content, draft/publish versioning, and live preview
|
|
57
|
+
- **Headers** — name, logo upload, sticky option
|
|
58
|
+
- **Footers** — name, richtext content, copyright text
|
|
59
|
+
- **Themes** — name, colors, font family
|
|
60
|
+
- **Site Settings** — site name, description, favicon upload
|
|
61
|
+
|
|
62
|
+
Also includes:
|
|
63
|
+
|
|
64
|
+
- Landing page with sign-in/sign-up flow
|
|
65
|
+
- First-user auto-admin promotion
|
|
66
|
+
- Admin panel onboarding tour (driver.js)
|
|
67
|
+
- Preview routes at `/preview/[slug]` with live preview support
|
|
68
|
+
- Public page routes at `/[slug]` (published pages only)
|
|
69
|
+
- Draft-aware queries using `vexQuery`
|
|
70
|
+
|
|
71
|
+
### Bare (`--bare`)
|
|
72
|
+
|
|
73
|
+
Empty VEX CMS project with:
|
|
74
|
+
|
|
75
|
+
- Authentication setup (Better Auth + Convex adapter)
|
|
76
|
+
- Admin panel wired at `/admin`
|
|
77
|
+
- Empty `collections: []` in `vex.config.ts`
|
|
78
|
+
- No onboarding tour
|
|
79
|
+
|
|
80
|
+
## Getting Started
|
|
81
|
+
|
|
82
|
+
After scaffolding your project:
|
|
83
|
+
|
|
84
|
+
### 1. Install dependencies
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cd my-project
|
|
88
|
+
pnpm install
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Generate a Better Auth secret
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pnpm secret:create
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This generates a random 32-character string and copies it to your clipboard.
|
|
98
|
+
|
|
99
|
+
### 3. Configure environment variables
|
|
100
|
+
|
|
101
|
+
The CLI creates a `.env.local` file with `NEXT_PUBLIC_SITE_URL` set to your chosen port. Add the auth secret:
|
|
102
|
+
|
|
103
|
+
```env
|
|
104
|
+
# .env.local (auto-generated)
|
|
105
|
+
NEXT_PUBLIC_SITE_URL=http://localhost:3010
|
|
106
|
+
|
|
107
|
+
# Add manually:
|
|
108
|
+
BETTER_AUTH_SECRET=your-generated-secret-here
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 4. Start VEX dev server
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pnpm vex:dev
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This starts the VEX CLI watcher (generates schema, types, and queries) and the Convex dev server. On first run, it will prompt you to create or select a Convex project.
|
|
118
|
+
|
|
119
|
+
### 5. Add environment variables to Convex
|
|
120
|
+
|
|
121
|
+
In the [Convex Dashboard](https://dashboard.convex.dev), navigate to your project's **Settings > Environment Variables** and add:
|
|
122
|
+
|
|
123
|
+
- `BETTER_AUTH_SECRET` — the same secret from step 2
|
|
124
|
+
- `SITE_URL` — `http://localhost:3010` (or your chosen port)
|
|
125
|
+
|
|
126
|
+
### 6. Start the Next.js dev server
|
|
127
|
+
|
|
128
|
+
In a separate terminal:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pnpm dev
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 7. Create your admin account
|
|
135
|
+
|
|
136
|
+
Open `http://localhost:3010` in your browser. Click **"Create Admin Account"** to sign up. The first user is automatically promoted to admin and redirected to the admin panel.
|
|
137
|
+
|
|
138
|
+
### Available Scripts
|
|
139
|
+
|
|
140
|
+
| Script | Description |
|
|
141
|
+
|--------|-------------|
|
|
142
|
+
| `pnpm dev` | Start Next.js development server |
|
|
143
|
+
| `pnpm vex:dev` | Start VEX CLI watcher + Convex dev server |
|
|
144
|
+
| `pnpm vex:generate` | One-shot schema/type/query generation |
|
|
145
|
+
| `pnpm vex:update` | Update all `@vexcms/*` packages to latest |
|
|
146
|
+
| `pnpm secret:create` | Generate a random 32-character secret and copy to clipboard |
|
|
147
|
+
| `pnpm build` | Production build |
|
|
148
|
+
|
|
149
|
+
## Versioning
|
|
150
|
+
|
|
151
|
+
`create-vexcms` is versioned alongside all `@vexcms/*` packages. Running `pnpm create vexcms@latest` always scaffolds with the latest package versions. You can also pin a specific version:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pnpm create vexcms@0.0.3
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The scaffolded project's `@vexcms/*` dependencies match the version of `create-vexcms` used.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -98,7 +98,9 @@ async function copyTemplate(framework, targetPath) {
|
|
|
98
98
|
overwrite: false,
|
|
99
99
|
errorOnExist: false,
|
|
100
100
|
filter: (src) => {
|
|
101
|
-
|
|
101
|
+
const basename = src.split("/").pop() ?? "";
|
|
102
|
+
const skipFiles = ["_gitignore", "_env.example", "_prettierrc", "_prettierignore"];
|
|
103
|
+
return !skipFiles.includes(basename);
|
|
102
104
|
}
|
|
103
105
|
});
|
|
104
106
|
const sourceGitignore = join(templatePath, "_gitignore");
|
|
@@ -109,6 +111,18 @@ async function copyTemplate(framework, targetPath) {
|
|
|
109
111
|
errorOnExist: false
|
|
110
112
|
});
|
|
111
113
|
}
|
|
114
|
+
const dotfileRenames = [
|
|
115
|
+
["_env.example", ".env.example"],
|
|
116
|
+
["_prettierrc", ".prettierrc"],
|
|
117
|
+
["_prettierignore", ".prettierignore"]
|
|
118
|
+
];
|
|
119
|
+
for (const [from, to] of dotfileRenames) {
|
|
120
|
+
const source = join(templatePath, from);
|
|
121
|
+
const target = join(targetPath, to);
|
|
122
|
+
if (await fs2.pathExists(source)) {
|
|
123
|
+
await fs2.copy(source, target, { overwrite: false, errorOnExist: false });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
112
126
|
}
|
|
113
127
|
async function overlayTemplate({ overlayDir, targetDir }) {
|
|
114
128
|
await fs2.copy(overlayDir, targetDir, { overwrite: true });
|
|
@@ -157,6 +171,27 @@ var VexFrameworkInstaller = class {
|
|
|
157
171
|
}
|
|
158
172
|
await fs3.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
159
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Configure the dev server port.
|
|
176
|
+
* Updates package.json dev script and creates .env.local with NEXT_PUBLIC_SITE_URL.
|
|
177
|
+
*/
|
|
178
|
+
async configurePort(port) {
|
|
179
|
+
const pkgPath = path.join(this.targetPath, "package.json");
|
|
180
|
+
const pkg = await fs3.readJson(pkgPath);
|
|
181
|
+
if (pkg.scripts?.dev) {
|
|
182
|
+
if (pkg.scripts.dev.includes("--port")) {
|
|
183
|
+
pkg.scripts.dev = pkg.scripts.dev.replace(/--port[= ]\d+/, `--port=${port}`);
|
|
184
|
+
} else {
|
|
185
|
+
pkg.scripts.dev = `${pkg.scripts.dev} --port=${port}`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
await fs3.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
189
|
+
const envLocalPath = path.join(this.targetPath, ".env.local");
|
|
190
|
+
const envContent = `NEXT_PUBLIC_SITE_URL=http://localhost:${port}
|
|
191
|
+
BETTER_AUTH_SECRET=""
|
|
192
|
+
`;
|
|
193
|
+
await fs3.writeFile(envLocalPath, envContent);
|
|
194
|
+
}
|
|
160
195
|
/**
|
|
161
196
|
* Detect the package manager used to invoke the CLI
|
|
162
197
|
*/
|
|
@@ -330,6 +365,7 @@ var VexFrameworkInstaller = class {
|
|
|
330
365
|
const nameSpinner = ora("Configuring project...").start();
|
|
331
366
|
try {
|
|
332
367
|
await this.updatePackageName(options.projectName);
|
|
368
|
+
await this.configurePort(options.port);
|
|
333
369
|
nameSpinner.succeed("Project configured");
|
|
334
370
|
} catch (error) {
|
|
335
371
|
nameSpinner.fail("Failed to configure project");
|
|
@@ -2308,7 +2344,7 @@ async function main() {
|
|
|
2308
2344
|
inputArg = args[0];
|
|
2309
2345
|
} else {
|
|
2310
2346
|
inputArg = await input({
|
|
2311
|
-
message: "What is your project named?",
|
|
2347
|
+
message: "(1/8) What is your project named?",
|
|
2312
2348
|
default: "my-vexcms-app",
|
|
2313
2349
|
validate: (value) => {
|
|
2314
2350
|
const name = value.includes("/") ? path2.basename(value) : value;
|
|
@@ -2336,7 +2372,7 @@ async function main() {
|
|
|
2336
2372
|
let framework;
|
|
2337
2373
|
while (true) {
|
|
2338
2374
|
framework = await select({
|
|
2339
|
-
message: "Select a framework:",
|
|
2375
|
+
message: "(2/8) Select a framework:",
|
|
2340
2376
|
choices: [
|
|
2341
2377
|
{ name: "Next.js (Recommended)", value: "nextjs" },
|
|
2342
2378
|
{ name: "TanStack Start (Coming Soon)", value: "tanstack" }
|
|
@@ -2348,8 +2384,18 @@ async function main() {
|
|
|
2348
2384
|
}
|
|
2349
2385
|
break;
|
|
2350
2386
|
}
|
|
2387
|
+
const portInput = await input({
|
|
2388
|
+
message: "(3/8) Dev server port:",
|
|
2389
|
+
default: "3010",
|
|
2390
|
+
validate: (value) => {
|
|
2391
|
+
const num = parseInt(value, 10);
|
|
2392
|
+
if (isNaN(num) || num < 1 || num > 65535) return "Must be a valid port number (1-65535)";
|
|
2393
|
+
return true;
|
|
2394
|
+
}
|
|
2395
|
+
});
|
|
2396
|
+
const port = parseInt(portInput, 10);
|
|
2351
2397
|
const emailPasswordAuth = await confirm({
|
|
2352
|
-
message: "Enable email/password authentication?",
|
|
2398
|
+
message: "(4/8) Enable email/password authentication?",
|
|
2353
2399
|
default: true
|
|
2354
2400
|
});
|
|
2355
2401
|
const popularProviders = getPopularProviders();
|
|
@@ -2366,25 +2412,26 @@ async function main() {
|
|
|
2366
2412
|
}))
|
|
2367
2413
|
];
|
|
2368
2414
|
const oauthProviders = await checkbox({
|
|
2369
|
-
message: "Select OAuth providers (space to toggle, enter to confirm):",
|
|
2415
|
+
message: "(5/8) Select OAuth providers (space to toggle, enter to confirm):",
|
|
2370
2416
|
choices: allProviderChoices
|
|
2371
2417
|
});
|
|
2372
2418
|
const orgs = opts.orgs ?? await confirm({
|
|
2373
|
-
message: "Enable multi-tenant (organizations)?",
|
|
2419
|
+
message: "(6/8) Enable multi-tenant (organizations)?",
|
|
2374
2420
|
default: false
|
|
2375
2421
|
});
|
|
2376
2422
|
const initGit = await confirm({
|
|
2377
|
-
message: "Initialize a Git repository?",
|
|
2423
|
+
message: "(7/8) Initialize a Git repository?",
|
|
2378
2424
|
default: true
|
|
2379
2425
|
});
|
|
2380
2426
|
const installDependencies = await confirm({
|
|
2381
|
-
message: "Install dependencies?",
|
|
2427
|
+
message: "(8/8) Install dependencies?",
|
|
2382
2428
|
default: false
|
|
2383
2429
|
});
|
|
2384
2430
|
const options = {
|
|
2385
2431
|
projectName,
|
|
2386
2432
|
projectDir: targetDir,
|
|
2387
2433
|
framework,
|
|
2434
|
+
port,
|
|
2388
2435
|
bare,
|
|
2389
2436
|
orgs,
|
|
2390
2437
|
emailPasswordAuth,
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Since the ".env" file is gitignored, you can use the ".env.example" file to
|
|
2
|
+
# build a new ".env" file when you clone the repo. Keep this file up-to-date
|
|
3
|
+
# when you add new variables to `.env`.
|
|
4
|
+
|
|
5
|
+
# This file will be committed to version control, so make sure not to have any
|
|
6
|
+
# secrets in it. If you are cloning this repo, create a copy of this file named
|
|
7
|
+
# ".env" and populate it with your secrets.
|
|
8
|
+
|
|
9
|
+
# When adding additional environment variables, the schema in "/src/env.js"
|
|
10
|
+
# should be updated accordingly.
|
|
11
|
+
|
|
12
|
+
NEXT_PUBLIC_CONVEX_URL=""
|
|
13
|
+
NEXT_PUBLIC_CONVEX_SITE_URL=""
|
|
14
|
+
|
|
15
|
+
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
|
|
16
|
+
BETTER_AUTH_SECRET=""
|
|
17
|
+
|
|
18
|
+
# {{ENV_OAUTH_VARS}}
|
|
19
|
+
|
|
File without changes
|
|
File without changes
|