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
- return !src.endsWith("_gitignore");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-vexcms",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "create-vexcms": "./dist/index.js"
@@ -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
+