create-kide-app 0.1.5 → 0.1.7

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.
Files changed (3) hide show
  1. package/README.md +14 -12
  2. package/index.js +294 -58
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,27 +5,29 @@ Scaffold a new [Kide CMS](https://github.com/mhernesniemi/kide-cms) project.
5
5
  ## Usage
6
6
 
7
7
  ```bash
8
- npm create kide-app my-project
9
- # or
10
- pnpm create kide-app my-project
11
- # or
12
- bunx create-kide-app my-project
8
+ pnpx create-kide-app my-project
13
9
  ```
14
10
 
15
- The CLI will guide you through:
11
+ The CLI asks for:
16
12
 
17
13
  1. **Project name** — directory to create
18
14
  2. **Deploy target** — Local/Node.js or Cloudflare
19
- 3. **Demo content** — optionally seed the database
15
+ 3. **Seed demo content** — local target only
20
16
 
21
17
  ## What it does
22
18
 
23
- - Downloads the latest Kide CMS template from GitHub
24
- - Applies platform-specific configuration (Node.js adapter, Cloudflare D1/R2, etc.)
25
- - Installs dependencies
26
- - Generates the CMS schema
27
- - Optionally seeds demo content
19
+ - Clones the latest Kide CMS from GitHub.
20
+ - Applies target-specific configuration (Node.js adapter, or Cloudflare D1/R2/Workers).
21
+ - Installs dependencies with pnpm.
22
+ - Optionally creates a GitHub repo (if the `gh` CLI is installed and authenticated).
23
+ - Generates the CMS schema.
24
+ - For **local**: optionally seeds demo content, then starts the dev server.
25
+ - For **Cloudflare**: logs into wrangler (if needed), creates a D1 database and R2 bucket, applies migrations, builds and deploys, and prints the live URL + admin URL.
28
26
 
29
27
  ## Requirements
30
28
 
31
29
  - Node.js >= 22.12.0
30
+ - `pnpm` installed
31
+ - `git` on PATH
32
+ - Optional: [`gh` CLI](https://cli.github.com) authenticated (`gh auth login`) for GitHub repo creation
33
+ - For Cloudflare: a Cloudflare account (the CLI runs `wrangler login` for you)
package/index.js CHANGED
@@ -2,7 +2,14 @@
2
2
 
3
3
  import * as p from "@clack/prompts";
4
4
  import { execSync, spawn } from "node:child_process";
5
- import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import {
6
+ cpSync,
7
+ existsSync,
8
+ mkdirSync,
9
+ readFileSync,
10
+ rmSync,
11
+ writeFileSync,
12
+ } from "node:fs";
6
13
  import path from "node:path";
7
14
 
8
15
  // Async spawn wrapper so long-running commands don't block clack spinners
@@ -26,14 +33,50 @@ const runAsync = (cmd, cwd) =>
26
33
 
27
34
  // --- Package manager detection ---
28
35
 
29
- const pm = { name: "pnpm", exec: "pnpm exec", dlx: "pnpm dlx", run: "pnpm", install: "pnpm install" };
36
+ const pm = {
37
+ name: "pnpm",
38
+ exec: "pnpm exec",
39
+ dlx: "pnpm dlx",
40
+ run: "pnpm",
41
+ install: "pnpm install",
42
+ };
30
43
 
31
44
  // --- Template repo ---
32
45
 
33
46
  const REPO = "https://github.com/mhernesniemi/kide-cms.git";
34
47
 
35
48
  // Files from the kide-cms repo that shouldn't leak into scaffolded projects.
36
- const CLEANUP = ["docs", "CLAUDE.md", ".claude", "data", ".cms-data", "dist", ".astro", ".DS_Store"];
49
+ // NOTE: `.claude/settings.local.json` is removed but `.claude/skills/` is kept,
50
+ // so scaffolds ship the /migrate skill alongside AGENTS.md.
51
+ const CLEANUP = [
52
+ "docs",
53
+ "CLAUDE.md",
54
+ ".claude/settings.local.json",
55
+ "data",
56
+ ".cms-data",
57
+ "dist",
58
+ ".astro",
59
+ ".DS_Store",
60
+ ];
61
+
62
+ // Resolve the latest release tag (v-prefixed semver) so scaffolds pin to a
63
+ // deliberate release instead of whatever HEAD happens to be. Returns null when
64
+ // the repo has no tags (falls back to the default branch).
65
+ const resolveLatestTag = () => {
66
+ try {
67
+ const output = execSync(
68
+ `git ls-remote --tags --sort=-v:refname ${REPO} "v*"`,
69
+ { stdio: "pipe" },
70
+ ).toString();
71
+ for (const line of output.split("\n")) {
72
+ const match = line.match(/refs\/tags\/(v[0-9][^^\s]*)$/);
73
+ if (match) return match[1];
74
+ }
75
+ } catch {
76
+ // Network/git hiccup — fall back to default branch
77
+ }
78
+ return null;
79
+ };
37
80
 
38
81
  // --- Main ---
39
82
 
@@ -97,8 +140,24 @@ async function main() {
97
140
 
98
141
  s.start(`Scaffolding project (using ${pm.name})`);
99
142
 
143
+ const templateRef = resolveLatestTag();
144
+ let templateCommit = null;
145
+
100
146
  try {
101
- execSync(`git clone --depth 1 ${REPO} "${projectDir}"`, { stdio: "pipe" });
147
+ const branchFlag = templateRef ? `--branch "${templateRef}" ` : "";
148
+ execSync(`git clone --depth 1 ${branchFlag}${REPO} "${projectDir}"`, {
149
+ stdio: "pipe",
150
+ });
151
+ try {
152
+ templateCommit = execSync("git rev-parse HEAD", {
153
+ cwd: projectDir,
154
+ stdio: "pipe",
155
+ })
156
+ .toString()
157
+ .trim();
158
+ } catch {
159
+ // best-effort — stamp without a commit hash
160
+ }
102
161
  rmSync(path.join(projectDir, ".git"), { recursive: true, force: true });
103
162
  } catch {
104
163
  s.stop("Failed to download template.");
@@ -111,7 +170,11 @@ async function main() {
111
170
  rmSync(path.join(projectDir, f), { recursive: true, force: true });
112
171
  }
113
172
 
114
- s.stop("Project scaffolded");
173
+ s.stop(
174
+ templateRef
175
+ ? `Project scaffolded from ${templateRef}`
176
+ : "Project scaffolded",
177
+ );
115
178
 
116
179
  // --- Apply target-specific files ---
117
180
 
@@ -121,13 +184,32 @@ async function main() {
121
184
  const targetDir = path.join(adaptersDir, target);
122
185
 
123
186
  if (target === "cloudflare") {
124
- cpSync(path.join(targetDir, "astro.config.mjs"), path.join(projectDir, "astro.config.mjs"));
125
- cpSync(path.join(targetDir, "src/cms/adapters/db.ts"), path.join(projectDir, "src/cms/adapters/db.ts"));
126
- cpSync(path.join(targetDir, "drizzle.config.ts"), path.join(projectDir, "drizzle.config.ts"));
127
- cpSync(path.join(targetDir, "src/cms/adapters/storage.ts"), path.join(projectDir, "src/cms/adapters/storage.ts"));
187
+ cpSync(
188
+ path.join(targetDir, "astro.config.mjs"),
189
+ path.join(projectDir, "astro.config.mjs"),
190
+ );
191
+ cpSync(
192
+ path.join(targetDir, "src/cms/adapters/db.ts"),
193
+ path.join(projectDir, "src/cms/adapters/db.ts"),
194
+ );
195
+ cpSync(
196
+ path.join(targetDir, "drizzle.config.ts"),
197
+ path.join(projectDir, "drizzle.config.ts"),
198
+ );
199
+ cpSync(
200
+ path.join(targetDir, "src/cms/adapters/storage.ts"),
201
+ path.join(projectDir, "src/cms/adapters/storage.ts"),
202
+ );
203
+ cpSync(
204
+ path.join(targetDir, "src/cms/adapters/cf-env.ts"),
205
+ path.join(projectDir, "src/cms/adapters/cf-env.ts"),
206
+ );
128
207
  const uploadsRouteDir = path.join(projectDir, "src/pages/uploads");
129
208
  mkdirSync(uploadsRouteDir, { recursive: true });
130
- cpSync(path.join(targetDir, "src/pages/uploads/[...path].ts"), path.join(uploadsRouteDir, "[...path].ts"));
209
+ cpSync(
210
+ path.join(targetDir, "src/pages/uploads/[...path].ts"),
211
+ path.join(uploadsRouteDir, "[...path].ts"),
212
+ );
131
213
  }
132
214
 
133
215
  const pkgPath = path.join(projectDir, "package.json");
@@ -142,27 +224,101 @@ async function main() {
142
224
  // Move better-sqlite3 to devDependencies — drizzle-kit needs it to push schema to local D1
143
225
  if (pkg.dependencies["better-sqlite3"]) {
144
226
  if (!pkg.devDependencies) pkg.devDependencies = {};
145
- pkg.devDependencies["better-sqlite3"] = pkg.dependencies["better-sqlite3"];
227
+ pkg.devDependencies["better-sqlite3"] =
228
+ pkg.dependencies["better-sqlite3"];
146
229
  delete pkg.dependencies["better-sqlite3"];
147
230
  }
148
231
  delete pkg.dependencies["sharp"];
149
232
 
150
- let wranglerContent = readFileSync(path.join(targetDir, "wrangler.toml"), "utf-8");
151
- wranglerContent = wranglerContent.replaceAll("{{PROJECT_NAME}}", projectName);
233
+ let wranglerContent = readFileSync(
234
+ path.join(targetDir, "wrangler.toml"),
235
+ "utf-8",
236
+ );
237
+ wranglerContent = wranglerContent.replaceAll(
238
+ "{{PROJECT_NAME}}",
239
+ projectName,
240
+ );
241
+ // Seed a placeholder database_id so `pnpm dev` works out of the box —
242
+ // miniflare requires a non-empty id even for the local D1. It's overwritten
243
+ // with the real id if a D1 is created below; otherwise paste the real id
244
+ // before deploying (`wrangler deploy` rejects an id it doesn't own).
245
+ wranglerContent = wranglerContent.replace(
246
+ /database_id = ""[^\n]*/,
247
+ `database_id = "${crypto.randomUUID()}" # local placeholder - replace with the id from \`wrangler d1 create ${projectName}-db\` before deploying`,
248
+ );
152
249
  writeFileSync(path.join(projectDir, "wrangler.toml"), wranglerContent);
153
250
 
154
251
  pkg.devDependencies.wrangler = "^4.0.0";
155
252
 
156
253
  pkg.scripts.dev = "astro dev";
157
254
  pkg.scripts.build = "astro build";
158
- pkg.scripts.preview = "astro build && wrangler dev --config dist/server/wrangler.json";
159
- pkg.scripts.deploy = "astro build && wrangler deploy --config dist/server/wrangler.json";
255
+ pkg.scripts.preview =
256
+ "astro build && wrangler dev --config dist/server/wrangler.json";
257
+ pkg.scripts.deploy =
258
+ "astro build && wrangler deploy --config dist/server/wrangler.json";
259
+
260
+ // @cloudflare/vite-plugin statically imports module.registerHooks (Node
261
+ // >=22.15), and Node >=23 changes the native ABI (breaking the prebuilt
262
+ // better-sqlite3 used for local D1). So pin Node 22.18–22.x and guard `dev`
263
+ // with a friendly check instead of a cryptic ESM "registerHooks" crash.
264
+ pkg.engines = { ...(pkg.engines ?? {}), node: ">=22.18.0" };
265
+ pkg.scripts.predev =
266
+ `node -e "const v=process.versions.node.split('.').map(Number);` +
267
+ `if(v[0]<22||(v[0]===22&&v[1]<18)||v[0]>=23){` +
268
+ `console.error('\\n[kide] Cloudflare dev needs Node 22.18+ (22.x). You have '+process.version+'.\\n` +
269
+ ` Run: nvm install 22 && nvm use (or use fnm/volta)\\n');` +
270
+ `process.exit(1)}"`;
271
+ writeFileSync(path.join(projectDir, ".nvmrc"), "22\n");
160
272
  }
161
273
 
162
274
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
163
275
 
164
276
  rmSync(adaptersDir, { recursive: true, force: true });
165
277
 
278
+ // Stamp the scaffold provenance. This file is the project's record of which
279
+ // template release it came from — used to diff against upstream and to check
280
+ // whether published security advisories apply to this project.
281
+ let cliVersion = null;
282
+ try {
283
+ cliVersion = JSON.parse(
284
+ readFileSync(new URL("./package.json", import.meta.url), "utf-8"),
285
+ ).version;
286
+ } catch {
287
+ // best-effort
288
+ }
289
+ const versionStamp = {
290
+ template: REPO.replace(/\.git$/, ""),
291
+ kideVersion: pkg.version ?? null,
292
+ ref: templateRef ?? "HEAD",
293
+ commit: templateCommit,
294
+ target,
295
+ corePath: "src/cms",
296
+ scaffoldedAt: new Date().toISOString(),
297
+ createKideApp: cliVersion,
298
+ };
299
+ writeFileSync(
300
+ path.join(projectDir, ".kide-version"),
301
+ `${JSON.stringify(versionStamp, null, 2)}\n`,
302
+ );
303
+
304
+ // Wire up the local MCP server so Claude Code (and other MCP clients that read
305
+ // a project-scoped `.mcp.json`) discover it automatically — no manual
306
+ // `claude mcp add` needed. The command runs from the project root, where the
307
+ // `cms:mcp` script lives, so no `cwd` override is required.
308
+ const mcpConfig = {
309
+ mcpServers: {
310
+ kide: {
311
+ type: "stdio",
312
+ command: "pnpm",
313
+ args: ["cms:mcp"],
314
+ },
315
+ },
316
+ };
317
+ writeFileSync(
318
+ path.join(projectDir, ".mcp.json"),
319
+ `${JSON.stringify(mcpConfig, null, 2)}\n`,
320
+ );
321
+
166
322
  s.stop("Configuration applied");
167
323
 
168
324
  // --- Install dependencies ---
@@ -179,10 +335,13 @@ async function main() {
179
335
 
180
336
  let gitInitialized = false;
181
337
  try {
182
- execSync("git init -q && git add . && git commit -q -m 'Initial commit from create-kide-app'", {
183
- cwd: projectDir,
184
- stdio: "pipe",
185
- });
338
+ execSync(
339
+ "git init -q && git add . && git commit -q -m 'Initial commit from create-kide-app'",
340
+ {
341
+ cwd: projectDir,
342
+ stdio: "pipe",
343
+ },
344
+ );
186
345
  gitInitialized = true;
187
346
  } catch {
188
347
  // git not available — silently skip
@@ -209,7 +368,9 @@ async function main() {
209
368
  // Get the GitHub username so we can check repo availability
210
369
  let ghUser = "";
211
370
  try {
212
- ghUser = execSync("gh api user --jq .login", { stdio: "pipe" }).toString().trim();
371
+ ghUser = execSync("gh api user --jq .login", { stdio: "pipe" })
372
+ .toString()
373
+ .trim();
213
374
  } catch {
214
375
  // ignore
215
376
  }
@@ -222,7 +383,8 @@ async function main() {
222
383
  initialValue: projectName,
223
384
  validate: (value) => {
224
385
  if (!value) return "Repository name is required";
225
- if (!/^[a-zA-Z0-9._-]+$/.test(value)) return "Only letters, numbers, dots, hyphens, and underscores";
386
+ if (!/^[a-zA-Z0-9._-]+$/.test(value))
387
+ return "Only letters, numbers, dots, hyphens, and underscores";
226
388
  },
227
389
  });
228
390
  if (p.isCancel(input)) break;
@@ -230,7 +392,10 @@ async function main() {
230
392
  if (ghUser) {
231
393
  try {
232
394
  execSync(`gh repo view ${ghUser}/${input}`, { stdio: "pipe" });
233
- p.note(`A repository named "${input}" already exists. Pick a different name.`, "Name taken");
395
+ p.note(
396
+ `A repository named "${input}" already exists. Pick a different name.`,
397
+ "Name taken",
398
+ );
234
399
  continue;
235
400
  } catch {
236
401
  // Repo doesn't exist — name is free
@@ -251,14 +416,20 @@ async function main() {
251
416
  if (!p.isCancel(visibility)) {
252
417
  s.start("Creating GitHub repository");
253
418
  try {
254
- execSync(`gh repo create ${repoName} ${visibility}`, { cwd: projectDir, stdio: "pipe" });
255
-
256
- // Use SSH for the remote (works with the user's existing SSH keys;
257
- // avoids HTTPS credential prompts when gh's git_protocol defaults to https).
258
- execSync(`git remote add origin git@github.com:${ghUser}/${repoName}.git`, {
419
+ execSync(`gh repo create ${repoName} ${visibility}`, {
259
420
  cwd: projectDir,
260
421
  stdio: "pipe",
261
422
  });
423
+
424
+ // Use SSH for the remote (works with the user's existing SSH keys;
425
+ // avoids HTTPS credential prompts when gh's git_protocol defaults to https).
426
+ execSync(
427
+ `git remote add origin git@github.com:${ghUser}/${repoName}.git`,
428
+ {
429
+ cwd: projectDir,
430
+ stdio: "pipe",
431
+ },
432
+ );
262
433
  execSync("git branch -M main && git push -u origin main", {
263
434
  cwd: projectDir,
264
435
  stdio: "pipe",
@@ -297,7 +468,11 @@ async function main() {
297
468
  stdio: "pipe",
298
469
  }).toString();
299
470
  pushOk = !out.includes("Error:") && out.includes("Changes applied");
300
- s.stop(pushOk ? "Schema pushed" : "Schema push failed — run `pnpm exec drizzle-kit push` manually");
471
+ s.stop(
472
+ pushOk
473
+ ? "Schema pushed"
474
+ : "Schema push failed — run `pnpm exec drizzle-kit push` manually",
475
+ );
301
476
  } catch {
302
477
  s.stop("Schema will be set up on first dev start");
303
478
  }
@@ -316,7 +491,7 @@ async function main() {
316
491
  "Seeding for Cloudflare requires a D1 database.",
317
492
  "",
318
493
  ` ${pm.dlx} wrangler d1 create ${projectName}-db`,
319
- " # Add the database_id to wrangler.toml",
494
+ " # then replace the placeholder database_id in wrangler.toml",
320
495
  ` ${pm.dlx} wrangler d1 migrations apply ${projectName}-db --local`,
321
496
  ` ${pm.run} cms:seed`,
322
497
  ].join("\n"),
@@ -326,10 +501,17 @@ async function main() {
326
501
 
327
502
  // --- Cloudflare resource setup ---
328
503
 
329
- const cf = { d1Created: false, r2Created: false, migrationsApplied: false, deployed: false, url: null };
504
+ const cf = {
505
+ d1Created: false,
506
+ r2Created: false,
507
+ migrationsApplied: false,
508
+ deployed: false,
509
+ url: null,
510
+ };
330
511
  if (target === "cloudflare") {
331
512
  const setupNow = await p.confirm({
332
- message: "Set up Cloudflare resources now? (creates D1 database and R2 bucket)",
513
+ message:
514
+ "Set up Cloudflare resources now? (creates D1 database and R2 bucket)",
333
515
  initialValue: true,
334
516
  });
335
517
 
@@ -337,14 +519,26 @@ async function main() {
337
519
  // Check wrangler authentication
338
520
  let authenticated = false;
339
521
  try {
340
- execSync(`${pm.exec} wrangler whoami`, { cwd: projectDir, stdio: "pipe" });
522
+ execSync(`${pm.exec} wrangler whoami`, {
523
+ cwd: projectDir,
524
+ stdio: "pipe",
525
+ });
341
526
  authenticated = true;
342
527
  } catch {
343
- p.note("You need to log in to Cloudflare first.", "Wrangler login required");
344
- const doLogin = await p.confirm({ message: "Open browser to log in?", initialValue: true });
528
+ p.note(
529
+ "You need to log in to Cloudflare first.",
530
+ "Wrangler login required",
531
+ );
532
+ const doLogin = await p.confirm({
533
+ message: "Open browser to log in?",
534
+ initialValue: true,
535
+ });
345
536
  if (!p.isCancel(doLogin) && doLogin) {
346
537
  try {
347
- execSync(`${pm.exec} wrangler login`, { cwd: projectDir, stdio: "inherit" });
538
+ execSync(`${pm.exec} wrangler login`, {
539
+ cwd: projectDir,
540
+ stdio: "inherit",
541
+ });
348
542
  authenticated = true;
349
543
  } catch {
350
544
  s.stop("Login failed");
@@ -357,22 +551,38 @@ async function main() {
357
551
  let databaseId = null;
358
552
  s.start("Creating D1 database");
359
553
  try {
360
- const output = execSync(`${pm.exec} wrangler d1 create ${projectName}-db`, {
361
- cwd: projectDir,
362
- stdio: "pipe",
363
- }).toString();
554
+ const output = execSync(
555
+ `${pm.exec} wrangler d1 create ${projectName}-db`,
556
+ {
557
+ cwd: projectDir,
558
+ stdio: "pipe",
559
+ },
560
+ ).toString();
364
561
  const match = output.match(/database_id\s*=\s*"([^"]+)"/);
365
- if (match) databaseId = match[1];
366
- cf.d1Created = true;
367
- s.stop("D1 database created");
562
+ if (match) {
563
+ databaseId = match[1];
564
+ cf.d1Created = true;
565
+ s.stop("D1 database created");
566
+ } else {
567
+ // Created but the id couldn't be parsed from wrangler's output —
568
+ // don't mark as done, so the summary tells the user to wire it up.
569
+ s.stop(
570
+ "D1 database created, but its id could not be read — copy the database_id to wrangler.toml manually",
571
+ );
572
+ }
368
573
  } catch (err) {
369
574
  // Already exists — look it up
370
575
  try {
371
- const listOutput = execSync(`${pm.exec} wrangler d1 list`, { cwd: projectDir, stdio: "pipe" }).toString();
576
+ const listOutput = execSync(`${pm.exec} wrangler d1 list`, {
577
+ cwd: projectDir,
578
+ stdio: "pipe",
579
+ }).toString();
372
580
  const lines = listOutput.split("\n");
373
581
  const dbLine = lines.find((l) => l.includes(`${projectName}-db`));
374
582
  if (dbLine) {
375
- const idMatch = dbLine.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
583
+ const idMatch = dbLine.match(
584
+ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/,
585
+ );
376
586
  if (idMatch) databaseId = idMatch[0];
377
587
  }
378
588
  if (databaseId) {
@@ -391,14 +601,20 @@ async function main() {
391
601
  if (databaseId) {
392
602
  const wranglerPath = path.join(projectDir, "wrangler.toml");
393
603
  let wranglerContent = readFileSync(wranglerPath, "utf-8");
394
- wranglerContent = wranglerContent.replace(/database_id = "" #[^\n]*/, `database_id = "${databaseId}"`);
604
+ wranglerContent = wranglerContent.replace(
605
+ /database_id = "[^"]*"[^\n]*/,
606
+ `database_id = "${databaseId}"`,
607
+ );
395
608
  writeFileSync(wranglerPath, wranglerContent);
396
609
  }
397
610
 
398
611
  // Create R2 bucket
399
612
  s.start("Creating R2 bucket");
400
613
  try {
401
- execSync(`${pm.exec} wrangler r2 bucket create ${projectName}-assets`, { cwd: projectDir, stdio: "pipe" });
614
+ execSync(
615
+ `${pm.exec} wrangler r2 bucket create ${projectName}-assets`,
616
+ { cwd: projectDir, stdio: "pipe" },
617
+ );
402
618
  cf.r2Created = true;
403
619
  s.stop("R2 bucket created");
404
620
  } catch {
@@ -410,7 +626,10 @@ async function main() {
410
626
  if (databaseId) {
411
627
  s.start("Generating database migrations");
412
628
  try {
413
- execSync(`${pm.exec} drizzle-kit generate`, { cwd: projectDir, stdio: "pipe" });
629
+ execSync(`${pm.exec} drizzle-kit generate`, {
630
+ cwd: projectDir,
631
+ stdio: "pipe",
632
+ });
414
633
  s.stop("Migrations generated");
415
634
  } catch (err) {
416
635
  s.stop("Migration generation failed");
@@ -420,15 +639,20 @@ async function main() {
420
639
 
421
640
  s.start("Applying migrations to remote D1");
422
641
  try {
423
- execSync(`${pm.exec} wrangler d1 migrations apply ${projectName}-db --remote`, {
424
- cwd: projectDir,
425
- stdio: "pipe",
426
- input: "y\n",
427
- });
642
+ execSync(
643
+ `${pm.exec} wrangler d1 migrations apply ${projectName}-db --remote`,
644
+ {
645
+ cwd: projectDir,
646
+ stdio: "pipe",
647
+ input: "y\n",
648
+ },
649
+ );
428
650
  cf.migrationsApplied = true;
429
651
  s.stop("Migrations applied");
430
652
  } catch {
431
- s.stop("Migration apply failed — run manually with: wrangler d1 migrations apply --remote");
653
+ s.stop(
654
+ "Migration apply failed — run manually with: wrangler d1 migrations apply --remote",
655
+ );
432
656
  }
433
657
  }
434
658
 
@@ -441,8 +665,13 @@ async function main() {
441
665
  if (!p.isCancel(doDeploy) && doDeploy) {
442
666
  s.start("Building and deploying to Cloudflare");
443
667
  try {
444
- const deployOutput = await runAsync(`${pm.run} run deploy`, projectDir);
445
- const urlMatch = deployOutput.match(/https:\/\/[^\s]+\.workers\.dev/);
668
+ const deployOutput = await runAsync(
669
+ `${pm.run} run deploy`,
670
+ projectDir,
671
+ );
672
+ const urlMatch = deployOutput.match(
673
+ /https:\/\/[^\s]+\.workers\.dev/,
674
+ );
446
675
  if (urlMatch) cf.url = urlMatch[0];
447
676
  cf.deployed = true;
448
677
  s.stop("Deployed to Cloudflare");
@@ -489,13 +718,20 @@ async function main() {
489
718
  const lines = [`cd ${projectName}`];
490
719
  const remaining = [];
491
720
  if (!cf.d1Created) {
492
- remaining.push(` ${pm.dlx} wrangler d1 create ${projectName}-db`, " # Copy the database_id to wrangler.toml");
721
+ remaining.push(
722
+ ` ${pm.dlx} wrangler d1 create ${projectName}-db`,
723
+ " # then replace the placeholder database_id in wrangler.toml",
724
+ );
493
725
  }
494
726
  if (!cf.r2Created) {
495
- remaining.push(` ${pm.dlx} wrangler r2 bucket create ${projectName}-assets`);
727
+ remaining.push(
728
+ ` ${pm.dlx} wrangler r2 bucket create ${projectName}-assets`,
729
+ );
496
730
  }
497
731
  if (!cf.migrationsApplied) {
498
- remaining.push(` ${pm.dlx} wrangler d1 migrations apply ${projectName}-db --remote`);
732
+ remaining.push(
733
+ ` ${pm.dlx} wrangler d1 migrations apply ${projectName}-db --remote`,
734
+ );
499
735
  }
500
736
  if (!cf.deployed) {
501
737
  remaining.push(` ${pm.run} run deploy`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kide-app",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Scaffold a new Kide CMS project",
5
5
  "author": "Matti Hernesniemi",
6
6
  "license": "MIT",