create-kide-app 0.1.6 → 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.
- package/index.js +294 -58
- package/package.json +1 -1
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 {
|
|
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 = {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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(
|
|
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"] =
|
|
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(
|
|
151
|
-
|
|
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 =
|
|
159
|
-
|
|
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(
|
|
183
|
-
|
|
184
|
-
|
|
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" })
|
|
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))
|
|
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(
|
|
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}`, {
|
|
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(
|
|
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
|
-
" #
|
|
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 = {
|
|
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:
|
|
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`, {
|
|
522
|
+
execSync(`${pm.exec} wrangler whoami`, {
|
|
523
|
+
cwd: projectDir,
|
|
524
|
+
stdio: "pipe",
|
|
525
|
+
});
|
|
341
526
|
authenticated = true;
|
|
342
527
|
} catch {
|
|
343
|
-
p.note(
|
|
344
|
-
|
|
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`, {
|
|
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(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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)
|
|
366
|
-
|
|
367
|
-
|
|
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`, {
|
|
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(
|
|
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(
|
|
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(
|
|
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`, {
|
|
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(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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(
|
|
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(
|
|
445
|
-
|
|
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(
|
|
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(
|
|
727
|
+
remaining.push(
|
|
728
|
+
` ${pm.dlx} wrangler r2 bucket create ${projectName}-assets`,
|
|
729
|
+
);
|
|
496
730
|
}
|
|
497
731
|
if (!cf.migrationsApplied) {
|
|
498
|
-
remaining.push(
|
|
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`);
|