create-kide-app 0.1.1 → 0.1.3

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 (2) hide show
  1. package/index.js +200 -28
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import * as p from "@clack/prompts";
4
- import { execSync } from "node:child_process";
4
+ import { exec as execCb, execSync } from "node:child_process";
5
5
  import { cpSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
6
6
  import path from "node:path";
7
+ import { promisify } from "node:util";
7
8
 
9
+ const exec = promisify(execCb);
8
10
  const REPO = "https://github.com/mhernesniemi/kide-cms.git";
9
11
 
12
+ function run(cmd, cwd) {
13
+ return exec(cmd, { cwd });
14
+ }
15
+
10
16
  function applyPackagePatch(pkg, patch) {
11
17
  for (const [k, v] of Object.entries(patch.dependencies?.add ?? {})) {
12
18
  pkg.dependencies[k] = v;
@@ -61,13 +67,32 @@ async function main() {
61
67
  process.exit(0);
62
68
  }
63
69
 
70
+ let seedDemo = false;
71
+ if (!cloudflare) {
72
+ const seed = await p.confirm({
73
+ message: "Seed database with demo content?",
74
+ initialValue: false,
75
+ });
76
+ if (p.isCancel(seed)) {
77
+ p.cancel("Setup cancelled.");
78
+ process.exit(0);
79
+ }
80
+ seedDemo = seed;
81
+ }
82
+
64
83
  const s = p.spinner();
65
84
 
85
+ // --- Clone kide-cms ---
86
+
66
87
  s.start("Cloning kide-cms");
67
- execSync(`git clone --depth 1 ${REPO} "${projectDir}"`, { stdio: "pipe" });
88
+ await run(`git clone --depth 1 ${REPO} "${projectDir}"`);
68
89
  rmSync(path.join(projectDir, ".git"), { recursive: true, force: true });
69
90
  s.stop("Cloned");
70
91
 
92
+ // --- Adjust package.json and apply adapter overlay ---
93
+
94
+ s.start(cloudflare ? "Applying Cloudflare adapter" : "Configuring project");
95
+
71
96
  const pkgPath = path.join(projectDir, "package.json");
72
97
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
73
98
  pkg.name = projectName;
@@ -76,9 +101,7 @@ async function main() {
76
101
  const adaptersDir = path.join(projectDir, "adapters");
77
102
 
78
103
  if (cloudflare) {
79
- s.start("Applying Cloudflare adapter");
80
104
  const cfDir = path.join(adaptersDir, "cloudflare");
81
-
82
105
  const overlayFiles = [
83
106
  "astro.config.mjs",
84
107
  "drizzle.config.ts",
@@ -98,45 +121,194 @@ async function main() {
98
121
 
99
122
  const patch = JSON.parse(readFileSync(path.join(cfDir, "package.patch.json"), "utf-8"));
100
123
  applyPackagePatch(pkg, patch);
101
-
102
- s.stop("Cloudflare adapter applied");
103
124
  }
104
125
 
105
126
  rmSync(adaptersDir, { recursive: true, force: true });
106
127
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
107
128
 
129
+ s.stop(cloudflare ? "Cloudflare adapter applied" : "Project configured");
130
+
131
+ // --- Install dependencies ---
132
+
108
133
  s.start("Installing dependencies");
109
134
  try {
110
- execSync("pnpm install", { cwd: projectDir, stdio: "pipe" });
135
+ await run("pnpm install", projectDir);
111
136
  s.stop("Dependencies installed");
112
137
  } catch {
113
138
  s.stop("pnpm install failed — run it manually");
114
139
  }
115
140
 
141
+ // --- Seed demo content (local only) ---
142
+
143
+ if (seedDemo) {
144
+ s.start("Generating CMS schema");
145
+ try {
146
+ await run("pnpm cms:generate", projectDir);
147
+ s.stop("Schema generated");
148
+ } catch {
149
+ s.stop("Schema generation failed — run `pnpm cms:generate` manually");
150
+ }
151
+
152
+ s.start("Pushing schema to database");
153
+ try {
154
+ await run("pnpm exec drizzle-kit push --force", projectDir);
155
+ s.stop("Schema pushed");
156
+ } catch {
157
+ s.stop("Schema push failed — will be set up on first dev start");
158
+ }
159
+
160
+ s.start("Seeding demo content");
161
+ try {
162
+ await run("pnpm cms:seed", projectDir);
163
+ s.stop("Demo content seeded");
164
+ } catch {
165
+ s.stop("Seeding failed — run `pnpm cms:seed` manually");
166
+ }
167
+ }
168
+
169
+ // --- Cloudflare resource setup ---
170
+
171
+ const cf = { d1Created: false, r2Created: false, migrationsApplied: false };
116
172
  if (cloudflare) {
117
- p.note(
118
- [
119
- `cd ${projectName}`,
120
- "",
121
- "1. Create Cloudflare resources:",
122
- ` pnpm wrangler d1 create ${projectName}-db`,
123
- " # Paste the database_id into wrangler.toml",
124
- ` pnpm wrangler r2 bucket create ${projectName}-assets`,
125
- "",
126
- "2. Generate and apply migrations:",
127
- " pnpm cms:generate && pnpm exec drizzle-kit generate",
128
- ` pnpm wrangler d1 migrations apply ${projectName}-db --remote`,
129
- "",
130
- "3. Run locally or deploy:",
131
- " pnpm dev",
132
- " pnpm deploy",
133
- ].join("\n"),
134
- "Next steps",
135
- );
173
+ const setupNow = await p.confirm({
174
+ message: "Set up Cloudflare resources now? (creates D1 database and R2 bucket)",
175
+ initialValue: true,
176
+ });
177
+
178
+ if (!p.isCancel(setupNow) && setupNow) {
179
+ // Check wrangler authentication
180
+ let authenticated = false;
181
+ try {
182
+ await run("pnpm wrangler whoami", projectDir);
183
+ authenticated = true;
184
+ } catch {
185
+ p.note("You need to log in to Cloudflare first.", "Wrangler login required");
186
+ const doLogin = await p.confirm({ message: "Open browser to log in?", initialValue: true });
187
+ if (!p.isCancel(doLogin) && doLogin) {
188
+ try {
189
+ execSync("pnpm wrangler login", { cwd: projectDir, stdio: "inherit" });
190
+ authenticated = true;
191
+ } catch {
192
+ // Login failed
193
+ }
194
+ }
195
+ }
196
+
197
+ if (authenticated) {
198
+ // Create D1 database
199
+ let databaseId = null;
200
+ s.start("Creating D1 database");
201
+ try {
202
+ const { stdout } = await run(`pnpm wrangler d1 create ${projectName}-db`, projectDir);
203
+ const match = stdout.match(/database_id\s*=\s*"([^"]+)"/);
204
+ if (match) databaseId = match[1];
205
+ cf.d1Created = true;
206
+ s.stop("D1 database created");
207
+ } catch {
208
+ // Already exists — look it up
209
+ try {
210
+ const { stdout } = await run("pnpm wrangler d1 list", projectDir);
211
+ const dbLine = stdout.split("\n").find((l) => l.includes(`${projectName}-db`));
212
+ if (dbLine) {
213
+ const idMatch = dbLine.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
214
+ if (idMatch) databaseId = idMatch[0];
215
+ }
216
+ if (databaseId) {
217
+ cf.d1Created = true;
218
+ s.stop("D1 database already exists — using existing");
219
+ } else {
220
+ s.stop("D1 setup failed");
221
+ }
222
+ } catch {
223
+ s.stop("D1 setup failed");
224
+ }
225
+ }
226
+
227
+ // Update wrangler.toml with database_id
228
+ if (databaseId) {
229
+ const wranglerPath = path.join(projectDir, "wrangler.toml");
230
+ let wranglerContent = readFileSync(wranglerPath, "utf-8");
231
+ wranglerContent = wranglerContent.replace(
232
+ /database_id = "" #[^\n]*/,
233
+ `database_id = "${databaseId}"`,
234
+ );
235
+ writeFileSync(wranglerPath, wranglerContent);
236
+ }
237
+
238
+ // Create R2 bucket
239
+ s.start("Creating R2 bucket");
240
+ try {
241
+ await run(`pnpm wrangler r2 bucket create ${projectName}-assets`, projectDir);
242
+ cf.r2Created = true;
243
+ s.stop("R2 bucket created");
244
+ } catch {
245
+ // Already exists is fine
246
+ cf.r2Created = true;
247
+ s.stop("R2 bucket already exists");
248
+ }
249
+
250
+ // Generate schema + migrations, apply to remote D1
251
+ if (databaseId) {
252
+ s.start("Generating CMS schema");
253
+ try {
254
+ await run("pnpm cms:generate", projectDir);
255
+ s.stop("Schema generated");
256
+ } catch {
257
+ s.stop("Schema generation failed");
258
+ }
259
+
260
+ s.start("Generating database migrations");
261
+ try {
262
+ await run("pnpm exec drizzle-kit generate", projectDir);
263
+ s.stop("Migrations generated");
264
+ } catch {
265
+ s.stop("Migration generation failed");
266
+ }
267
+
268
+ s.start("Applying migrations to remote D1");
269
+ try {
270
+ await run(`pnpm wrangler d1 migrations apply ${projectName}-db --remote`, projectDir);
271
+ cf.migrationsApplied = true;
272
+ s.stop("Migrations applied");
273
+ } catch {
274
+ s.stop("Migration apply failed — run manually with: wrangler d1 migrations apply --remote");
275
+ }
276
+ }
277
+ }
278
+ }
279
+ }
280
+
281
+ // --- Done ---
282
+
283
+ if (cloudflare) {
284
+ const lines = [`cd ${projectName}`];
285
+ const remaining = [];
286
+ if (!cf.d1Created) {
287
+ remaining.push(
288
+ ` pnpm wrangler d1 create ${projectName}-db`,
289
+ " # Paste the database_id into wrangler.toml",
290
+ );
291
+ }
292
+ if (!cf.r2Created) {
293
+ remaining.push(` pnpm wrangler r2 bucket create ${projectName}-assets`);
294
+ }
295
+ if (!cf.migrationsApplied) {
296
+ remaining.push(` pnpm wrangler d1 migrations apply ${projectName}-db --remote`);
297
+ }
298
+ if (remaining.length > 0) {
299
+ lines.push("", "Remaining setup:", ...remaining);
300
+ }
301
+ lines.push("", "Local development:", " pnpm dev", "", "Deploy:", " pnpm deploy");
302
+ p.note(lines.join("\n"), "Next steps");
136
303
  p.outro("Project created!");
137
304
  } else {
138
- p.note([`cd ${projectName}`, "pnpm dev"].join("\n"), "Next steps");
139
- p.outro("Project created!");
305
+ p.outro("Starting dev server...");
306
+ try {
307
+ execSync("pnpm dev", { cwd: projectDir, stdio: "inherit" });
308
+ } catch {
309
+ console.log(`\n Project directory: ${projectDir}`);
310
+ console.log(` To start again: cd ${projectName} && pnpm dev\n`);
311
+ }
140
312
  }
141
313
  }
142
314
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kide-app",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Scaffold a new Kide CMS project",
5
5
  "author": "Matti Hernesniemi",
6
6
  "license": "MIT",