create-kide-app 0.1.2 → 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 +164 -31
  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;
@@ -76,11 +82,17 @@ async function main() {
76
82
 
77
83
  const s = p.spinner();
78
84
 
85
+ // --- Clone kide-cms ---
86
+
79
87
  s.start("Cloning kide-cms");
80
- execSync(`git clone --depth 1 ${REPO} "${projectDir}"`, { stdio: "pipe" });
88
+ await run(`git clone --depth 1 ${REPO} "${projectDir}"`);
81
89
  rmSync(path.join(projectDir, ".git"), { recursive: true, force: true });
82
90
  s.stop("Cloned");
83
91
 
92
+ // --- Adjust package.json and apply adapter overlay ---
93
+
94
+ s.start(cloudflare ? "Applying Cloudflare adapter" : "Configuring project");
95
+
84
96
  const pkgPath = path.join(projectDir, "package.json");
85
97
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
86
98
  pkg.name = projectName;
@@ -89,9 +101,7 @@ async function main() {
89
101
  const adaptersDir = path.join(projectDir, "adapters");
90
102
 
91
103
  if (cloudflare) {
92
- s.start("Applying Cloudflare adapter");
93
104
  const cfDir = path.join(adaptersDir, "cloudflare");
94
-
95
105
  const overlayFiles = [
96
106
  "astro.config.mjs",
97
107
  "drizzle.config.ts",
@@ -111,25 +121,29 @@ async function main() {
111
121
 
112
122
  const patch = JSON.parse(readFileSync(path.join(cfDir, "package.patch.json"), "utf-8"));
113
123
  applyPackagePatch(pkg, patch);
114
-
115
- s.stop("Cloudflare adapter applied");
116
124
  }
117
125
 
118
126
  rmSync(adaptersDir, { recursive: true, force: true });
119
127
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
120
128
 
129
+ s.stop(cloudflare ? "Cloudflare adapter applied" : "Project configured");
130
+
131
+ // --- Install dependencies ---
132
+
121
133
  s.start("Installing dependencies");
122
134
  try {
123
- execSync("pnpm install", { cwd: projectDir, stdio: "pipe" });
135
+ await run("pnpm install", projectDir);
124
136
  s.stop("Dependencies installed");
125
137
  } catch {
126
138
  s.stop("pnpm install failed — run it manually");
127
139
  }
128
140
 
141
+ // --- Seed demo content (local only) ---
142
+
129
143
  if (seedDemo) {
130
144
  s.start("Generating CMS schema");
131
145
  try {
132
- execSync("pnpm cms:generate", { cwd: projectDir, stdio: "pipe" });
146
+ await run("pnpm cms:generate", projectDir);
133
147
  s.stop("Schema generated");
134
148
  } catch {
135
149
  s.stop("Schema generation failed — run `pnpm cms:generate` manually");
@@ -137,7 +151,7 @@ async function main() {
137
151
 
138
152
  s.start("Pushing schema to database");
139
153
  try {
140
- execSync("pnpm exec drizzle-kit push --force", { cwd: projectDir, stdio: "pipe" });
154
+ await run("pnpm exec drizzle-kit push --force", projectDir);
141
155
  s.stop("Schema pushed");
142
156
  } catch {
143
157
  s.stop("Schema push failed — will be set up on first dev start");
@@ -145,37 +159,156 @@ async function main() {
145
159
 
146
160
  s.start("Seeding demo content");
147
161
  try {
148
- execSync("pnpm cms:seed", { cwd: projectDir, stdio: "pipe" });
162
+ await run("pnpm cms:seed", projectDir);
149
163
  s.stop("Demo content seeded");
150
164
  } catch {
151
165
  s.stop("Seeding failed — run `pnpm cms:seed` manually");
152
166
  }
153
167
  }
154
168
 
169
+ // --- Cloudflare resource setup ---
170
+
171
+ const cf = { d1Created: false, r2Created: false, migrationsApplied: false };
155
172
  if (cloudflare) {
156
- p.note(
157
- [
158
- `cd ${projectName}`,
159
- "",
160
- "1. Create Cloudflare resources:",
161
- ` pnpm wrangler d1 create ${projectName}-db`,
162
- " # Paste the database_id into wrangler.toml",
163
- ` pnpm wrangler r2 bucket create ${projectName}-assets`,
164
- "",
165
- "2. Generate and apply migrations:",
166
- " pnpm cms:generate && pnpm exec drizzle-kit generate",
167
- ` pnpm wrangler d1 migrations apply ${projectName}-db --remote`,
168
- "",
169
- "3. Run locally or deploy:",
170
- " pnpm dev",
171
- " pnpm deploy",
172
- ].join("\n"),
173
- "Next steps",
174
- );
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");
175
303
  p.outro("Project created!");
176
304
  } else {
177
- p.note([`cd ${projectName}`, "pnpm dev"].join("\n"), "Next steps");
178
- 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
+ }
179
312
  }
180
313
  }
181
314
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kide-app",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Scaffold a new Kide CMS project",
5
5
  "author": "Matti Hernesniemi",
6
6
  "license": "MIT",