create-kide-app 0.0.36 → 0.1.1
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 +12 -11
- package/index.js +81 -445
- package/package.json +2 -3
- package/templates/base/.env.example +0 -2
- package/templates/base/CLAUDE.md +0 -73
- package/templates/base/gitignore +0 -8
- package/templates/base/package.json +0 -39
- package/templates/base/src/cms/adapters/db.ts +0 -49
- package/templates/base/src/cms/adapters/email.ts +0 -34
- package/templates/base/src/cms/adapters/storage.ts +0 -38
- package/templates/base/src/cms/cms.config.ts +0 -7
- package/templates/base/src/cms/collections/users.ts +0 -20
- package/templates/base/src/cms/internals/create-admin.ts +0 -40
- package/templates/base/src/cms/internals/generator.ts +0 -10
- package/templates/base/src/cms/internals/runtime.ts +0 -76
- package/templates/base/src/components/BlockRenderer.astro +0 -28
- package/templates/base/src/env.d.ts +0 -12
- package/templates/base/src/pages/index.astro +0 -11
- package/templates/base/src/styles/admin.css +0 -253
- package/templates/base/src/styles/public.css +0 -2
- package/templates/base/tsconfig.json +0 -11
- package/templates/cloudflare/astro.config.mjs +0 -16
- package/templates/cloudflare/db.ts +0 -23
- package/templates/cloudflare/drizzle.config.ts +0 -24
- package/templates/cloudflare/storage.ts +0 -25
- package/templates/cloudflare/uploads-route.ts +0 -36
- package/templates/cloudflare/wrangler.toml +0 -17
- package/templates/demo/src/cms/cms.config.ts +0 -17
- package/templates/demo/src/cms/collections/authors.ts +0 -20
- package/templates/demo/src/cms/collections/front-page.ts +0 -37
- package/templates/demo/src/cms/collections/menus.ts +0 -18
- package/templates/demo/src/cms/collections/pages.ts +0 -59
- package/templates/demo/src/cms/collections/posts.ts +0 -55
- package/templates/demo/src/cms/collections/taxonomies.ts +0 -18
- package/templates/demo/src/cms/collections/users.ts +0 -20
- package/templates/demo/src/cms/internals/seed.data.ts +0 -506
- package/templates/demo/src/cms/internals/seed.ts +0 -7
- package/templates/demo/src/components/BlockRenderer.astro +0 -32
- package/templates/demo/src/components/RichTextContent.astro +0 -18
- package/templates/demo/src/components/blocks/Faq.astro +0 -22
- package/templates/demo/src/components/blocks/Hero.astro +0 -19
- package/templates/demo/src/components/blocks/Image.astro +0 -25
- package/templates/demo/src/components/blocks/Text.astro +0 -16
- package/templates/demo/src/layouts/PublicLayout.astro +0 -53
- package/templates/demo/src/pages/[...slug].astro +0 -31
- package/templates/demo/src/pages/blog/[slug].astro +0 -41
- package/templates/demo/src/pages/index.astro +0 -51
- package/templates/local/astro.config.mjs +0 -23
- package/templates/local/db.ts +0 -48
- package/templates/local/drizzle.config.ts +0 -15
package/README.md
CHANGED
|
@@ -5,27 +5,28 @@ 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
8
|
pnpm create kide-app my-project
|
|
11
9
|
# or
|
|
12
|
-
|
|
10
|
+
npm create kide-app my-project
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
The CLI
|
|
13
|
+
The CLI asks for:
|
|
16
14
|
|
|
17
15
|
1. **Project name** — directory to create
|
|
18
|
-
2. **Deploy target** —
|
|
19
|
-
3. **Demo content** — optionally seed the database
|
|
16
|
+
2. **Deploy target** — Node.js (default) or Cloudflare
|
|
20
17
|
|
|
21
18
|
## What it does
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
1. Clones the latest `kide-cms` from GitHub (shallow).
|
|
21
|
+
2. Removes the embedded `.git` and renames the project in `package.json`.
|
|
22
|
+
3. If Cloudflare is selected, overlays the files from `adapters/cloudflare/` and patches `package.json` (swaps `@astrojs/node` → `@astrojs/cloudflare`, adds `wrangler`, removes `sharp`, etc.).
|
|
23
|
+
4. Removes the `adapters/` scaffolding directory.
|
|
24
|
+
5. Runs `pnpm install`.
|
|
25
|
+
|
|
26
|
+
For Cloudflare projects, the CLI prints the remaining manual steps (create D1/R2, apply migrations).
|
|
28
27
|
|
|
29
28
|
## Requirements
|
|
30
29
|
|
|
31
30
|
- Node.js >= 22.12.0
|
|
31
|
+
- `git` on PATH
|
|
32
|
+
- `pnpm`
|
package/index.js
CHANGED
|
@@ -1,51 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
|
-
import { execSync
|
|
5
|
-
import { cpSync, existsSync,
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { cpSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
7
|
|
|
9
|
-
const
|
|
10
|
-
const TEMPLATES_DIR = path.join(__dirname, "templates");
|
|
8
|
+
const REPO = "https://github.com/mhernesniemi/kide-cms.git";
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const pm = { name: "pnpm", exec: "pnpm exec", dlx: "pnpm dlx", run: "pnpm", install: "pnpm install" };
|
|
34
|
-
|
|
35
|
-
// --- Main ---
|
|
10
|
+
function applyPackagePatch(pkg, patch) {
|
|
11
|
+
for (const [k, v] of Object.entries(patch.dependencies?.add ?? {})) {
|
|
12
|
+
pkg.dependencies[k] = v;
|
|
13
|
+
}
|
|
14
|
+
for (const k of patch.dependencies?.remove ?? []) {
|
|
15
|
+
delete pkg.dependencies[k];
|
|
16
|
+
}
|
|
17
|
+
pkg.devDependencies ??= {};
|
|
18
|
+
for (const [k, v] of Object.entries(patch.devDependencies?.add ?? {})) {
|
|
19
|
+
pkg.devDependencies[k] = v;
|
|
20
|
+
}
|
|
21
|
+
for (const k of patch.devDependencies?.moveFromDependencies ?? []) {
|
|
22
|
+
if (pkg.dependencies[k]) {
|
|
23
|
+
pkg.devDependencies[k] = pkg.dependencies[k];
|
|
24
|
+
delete pkg.dependencies[k];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const [k, v] of Object.entries(patch.scripts ?? {})) {
|
|
28
|
+
pkg.scripts[k] = v;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
36
31
|
|
|
37
32
|
async function main() {
|
|
38
|
-
p.intro("
|
|
33
|
+
p.intro("Create Kide CMS Project");
|
|
39
34
|
|
|
40
|
-
// 1. Project name
|
|
41
35
|
const projectName =
|
|
42
36
|
process.argv[2] ||
|
|
43
37
|
(await p.text({
|
|
44
38
|
message: "Project name",
|
|
45
39
|
placeholder: "my-cms-app",
|
|
46
|
-
validate: (value) =>
|
|
47
|
-
if (!value) return "Project name is required";
|
|
48
|
-
},
|
|
40
|
+
validate: (value) => (value ? undefined : "Project name is required"),
|
|
49
41
|
}));
|
|
50
42
|
|
|
51
43
|
if (p.isCancel(projectName)) {
|
|
@@ -59,448 +51,92 @@ async function main() {
|
|
|
59
51
|
process.exit(1);
|
|
60
52
|
}
|
|
61
53
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
options: [
|
|
66
|
-
{ label: "Local / Node.js", value: "local" },
|
|
67
|
-
{ label: "Cloudflare", value: "cloudflare" },
|
|
68
|
-
],
|
|
54
|
+
const cloudflare = await p.confirm({
|
|
55
|
+
message: "Deploy to Cloudflare? (otherwise Node.js)",
|
|
56
|
+
initialValue: false,
|
|
69
57
|
});
|
|
70
58
|
|
|
71
|
-
if (p.isCancel(
|
|
59
|
+
if (p.isCancel(cloudflare)) {
|
|
72
60
|
p.cancel("Setup cancelled.");
|
|
73
61
|
process.exit(0);
|
|
74
62
|
}
|
|
75
63
|
|
|
76
|
-
// 3. Demo content (local only — Cloudflare uses remote D1)
|
|
77
|
-
let seedDemo = false;
|
|
78
|
-
if (target === "local") {
|
|
79
|
-
const seed = await p.confirm({
|
|
80
|
-
message: "Seed database with demo content?",
|
|
81
|
-
initialValue: false,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
if (p.isCancel(seed)) {
|
|
85
|
-
p.cancel("Setup cancelled.");
|
|
86
|
-
process.exit(0);
|
|
87
|
-
}
|
|
88
|
-
seedDemo = seed;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
64
|
const s = p.spinner();
|
|
92
65
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
cpSync(path.join(TEMPLATES_DIR, "base"), projectDir, { recursive: true });
|
|
98
|
-
|
|
99
|
-
// Rename gitignore → .gitignore (npm strips dotfiles from published tarballs)
|
|
100
|
-
const gitignoreSrc = path.join(projectDir, "gitignore");
|
|
101
|
-
if (existsSync(gitignoreSrc)) {
|
|
102
|
-
cpSync(gitignoreSrc, path.join(projectDir, ".gitignore"));
|
|
103
|
-
rmSync(gitignoreSrc);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Apply demo schema and seed data if selected
|
|
107
|
-
if (seedDemo) {
|
|
108
|
-
cpSync(path.join(TEMPLATES_DIR, "demo"), projectDir, { recursive: true });
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
s.stop("Project scaffolded");
|
|
112
|
-
|
|
113
|
-
// --- Apply target-specific files ---
|
|
114
|
-
|
|
115
|
-
s.start(`Applying ${target} configuration`);
|
|
116
|
-
|
|
117
|
-
const targetDir = path.join(TEMPLATES_DIR, target);
|
|
118
|
-
|
|
119
|
-
cpSync(path.join(targetDir, "astro.config.mjs"), path.join(projectDir, "astro.config.mjs"));
|
|
120
|
-
cpSync(path.join(targetDir, "db.ts"), path.join(projectDir, "src/cms/adapters/db.ts"));
|
|
121
|
-
cpSync(path.join(targetDir, "drizzle.config.ts"), path.join(projectDir, "drizzle.config.ts"));
|
|
122
|
-
|
|
123
|
-
if (target === "cloudflare") {
|
|
124
|
-
cpSync(path.join(targetDir, "storage.ts"), path.join(projectDir, "src/cms/adapters/storage.ts"));
|
|
125
|
-
const uploadsRouteDir = path.join(projectDir, "src/pages/uploads");
|
|
126
|
-
mkdirSync(uploadsRouteDir, { recursive: true });
|
|
127
|
-
cpSync(path.join(targetDir, "uploads-route.ts"), path.join(uploadsRouteDir, "[...path].ts"));
|
|
128
|
-
}
|
|
66
|
+
s.start("Cloning kide-cms");
|
|
67
|
+
execSync(`git clone --depth 1 ${REPO} "${projectDir}"`, { stdio: "pipe" });
|
|
68
|
+
rmSync(path.join(projectDir, ".git"), { recursive: true, force: true });
|
|
69
|
+
s.stop("Cloned");
|
|
129
70
|
|
|
130
71
|
const pkgPath = path.join(projectDir, "package.json");
|
|
131
72
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
132
|
-
|
|
133
73
|
pkg.name = projectName;
|
|
74
|
+
pkg.version = "0.0.1";
|
|
75
|
+
|
|
76
|
+
const adaptersDir = path.join(projectDir, "adapters");
|
|
77
|
+
|
|
78
|
+
if (cloudflare) {
|
|
79
|
+
s.start("Applying Cloudflare adapter");
|
|
80
|
+
const cfDir = path.join(adaptersDir, "cloudflare");
|
|
81
|
+
|
|
82
|
+
const overlayFiles = [
|
|
83
|
+
"astro.config.mjs",
|
|
84
|
+
"drizzle.config.ts",
|
|
85
|
+
"src/cms/adapters/db.ts",
|
|
86
|
+
"src/cms/adapters/storage.ts",
|
|
87
|
+
"src/pages/uploads/[...path].ts",
|
|
88
|
+
];
|
|
89
|
+
for (const f of overlayFiles) {
|
|
90
|
+
cpSync(path.join(cfDir, f), path.join(projectDir, f));
|
|
91
|
+
}
|
|
134
92
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
delete pkg.dependencies["@astrojs/node"];
|
|
141
|
-
pkg.dependencies["@astrojs/cloudflare"] = "^13.0.0";
|
|
142
|
-
// libsql is for local target only; Cloudflare uses D1 directly
|
|
143
|
-
delete pkg.dependencies["@libsql/client"];
|
|
144
|
-
delete pkg.dependencies["sharp"];
|
|
145
|
-
|
|
146
|
-
let wranglerContent = readFileSync(path.join(targetDir, "wrangler.toml"), "utf-8");
|
|
147
|
-
wranglerContent = wranglerContent.replaceAll("{{PROJECT_NAME}}", projectName);
|
|
148
|
-
writeFileSync(path.join(projectDir, "wrangler.toml"), wranglerContent);
|
|
93
|
+
const wrangler = readFileSync(path.join(cfDir, "wrangler.toml"), "utf-8").replaceAll(
|
|
94
|
+
"{{PROJECT_NAME}}",
|
|
95
|
+
projectName,
|
|
96
|
+
);
|
|
97
|
+
writeFileSync(path.join(projectDir, "wrangler.toml"), wrangler);
|
|
149
98
|
|
|
150
|
-
|
|
99
|
+
const patch = JSON.parse(readFileSync(path.join(cfDir, "package.patch.json"), "utf-8"));
|
|
100
|
+
applyPackagePatch(pkg, patch);
|
|
151
101
|
|
|
152
|
-
|
|
153
|
-
pkg.scripts.build = "astro build";
|
|
154
|
-
pkg.scripts.preview = "astro build && wrangler dev --config dist/server/wrangler.json";
|
|
155
|
-
pkg.scripts.deploy = "astro build && wrangler deploy --config dist/server/wrangler.json";
|
|
102
|
+
s.stop("Cloudflare adapter applied");
|
|
156
103
|
}
|
|
157
104
|
|
|
105
|
+
rmSync(adaptersDir, { recursive: true, force: true });
|
|
158
106
|
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
159
107
|
|
|
160
|
-
if (target === "cloudflare") {
|
|
161
|
-
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
162
|
-
const gitignore = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : "";
|
|
163
|
-
writeFileSync(gitignorePath, gitignore + "\n# Cloudflare\n.wrangler/\n");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
s.stop("Configuration applied");
|
|
167
|
-
|
|
168
|
-
// --- Install dependencies ---
|
|
169
|
-
|
|
170
108
|
s.start("Installing dependencies");
|
|
171
109
|
try {
|
|
172
|
-
|
|
110
|
+
execSync("pnpm install", { cwd: projectDir, stdio: "pipe" });
|
|
173
111
|
s.stop("Dependencies installed");
|
|
174
112
|
} catch {
|
|
175
|
-
s.stop(
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// --- Initialize git repository ---
|
|
179
|
-
|
|
180
|
-
let gitInitialized = false;
|
|
181
|
-
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
|
-
});
|
|
186
|
-
gitInitialized = true;
|
|
187
|
-
} catch {
|
|
188
|
-
// git not available — silently skip
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// --- Optional: create GitHub repository ---
|
|
192
|
-
|
|
193
|
-
if (gitInitialized) {
|
|
194
|
-
let ghAvailable = false;
|
|
195
|
-
try {
|
|
196
|
-
execSync("gh --version", { stdio: "pipe" });
|
|
197
|
-
execSync("gh auth status", { stdio: "pipe" });
|
|
198
|
-
ghAvailable = true;
|
|
199
|
-
} catch {
|
|
200
|
-
// gh not installed or not authenticated — skip the prompt
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (ghAvailable) {
|
|
204
|
-
const createRepo = await p.confirm({
|
|
205
|
-
message: "Create a GitHub repository for this project?",
|
|
206
|
-
initialValue: false,
|
|
207
|
-
});
|
|
208
|
-
if (!p.isCancel(createRepo) && createRepo) {
|
|
209
|
-
// Get the GitHub username so we can check repo availability
|
|
210
|
-
let ghUser = "";
|
|
211
|
-
try {
|
|
212
|
-
ghUser = execSync("gh api user --jq .login", { stdio: "pipe" }).toString().trim();
|
|
213
|
-
} catch {}
|
|
214
|
-
|
|
215
|
-
// Prompt for repo name, validate it doesn't already exist
|
|
216
|
-
let repoName = null;
|
|
217
|
-
while (true) {
|
|
218
|
-
const input = await p.text({
|
|
219
|
-
message: "Repository name",
|
|
220
|
-
initialValue: projectName,
|
|
221
|
-
validate: (value) => {
|
|
222
|
-
if (!value) return "Repository name is required";
|
|
223
|
-
if (!/^[a-zA-Z0-9._-]+$/.test(value)) return "Only letters, numbers, dots, hyphens, and underscores";
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
if (p.isCancel(input)) break;
|
|
227
|
-
|
|
228
|
-
// Check if repo already exists under the user's account
|
|
229
|
-
if (ghUser) {
|
|
230
|
-
try {
|
|
231
|
-
execSync(`gh repo view ${ghUser}/${input}`, { stdio: "pipe" });
|
|
232
|
-
p.note(`A repository named "${input}" already exists. Pick a different name.`, "Name taken");
|
|
233
|
-
continue;
|
|
234
|
-
} catch {
|
|
235
|
-
// Repo doesn't exist — name is free
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
repoName = input;
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (repoName) {
|
|
243
|
-
const visibility = await p.select({
|
|
244
|
-
message: "Repository visibility",
|
|
245
|
-
options: [
|
|
246
|
-
{ label: "Private", value: "--private" },
|
|
247
|
-
{ label: "Public", value: "--public" },
|
|
248
|
-
],
|
|
249
|
-
});
|
|
250
|
-
if (!p.isCancel(visibility)) {
|
|
251
|
-
s.start("Creating GitHub repository");
|
|
252
|
-
try {
|
|
253
|
-
execSync(`gh repo create ${repoName} ${visibility} --source=. --push`, {
|
|
254
|
-
cwd: projectDir,
|
|
255
|
-
stdio: "pipe",
|
|
256
|
-
});
|
|
257
|
-
s.stop("GitHub repository created and pushed");
|
|
258
|
-
} catch (err) {
|
|
259
|
-
s.stop("GitHub repository creation failed");
|
|
260
|
-
if (err.stderr) console.error(err.stderr.toString().slice(-500));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// --- Generate schema ---
|
|
269
|
-
|
|
270
|
-
s.start("Generating CMS schema");
|
|
271
|
-
try {
|
|
272
|
-
execSync(`${pm.run} cms:generate`, { cwd: projectDir, stdio: "pipe" });
|
|
273
|
-
s.stop("Schema generated");
|
|
274
|
-
} catch {
|
|
275
|
-
s.stop("Schema generation failed — run `cms:generate` manually");
|
|
113
|
+
s.stop("pnpm install failed — run it manually");
|
|
276
114
|
}
|
|
277
115
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (seedDemo && target === "local") {
|
|
281
|
-
s.start("Pushing schema to database");
|
|
282
|
-
// Ensure data/ directory exists — drizzle-kit push silently exits 0 if it can't open the file
|
|
283
|
-
mkdirSync(path.join(projectDir, "data"), { recursive: true });
|
|
284
|
-
let pushOk = false;
|
|
285
|
-
try {
|
|
286
|
-
const out = execSync(`${pm.exec} drizzle-kit push --force`, {
|
|
287
|
-
cwd: projectDir,
|
|
288
|
-
stdio: "pipe",
|
|
289
|
-
}).toString();
|
|
290
|
-
// drizzle-kit exits 0 even on connection errors, so verify by checking output
|
|
291
|
-
pushOk = !out.includes("Error:") && out.includes("Changes applied");
|
|
292
|
-
s.stop(pushOk ? "Schema pushed" : "Schema push failed — run `pnpm exec drizzle-kit push` manually");
|
|
293
|
-
} catch {
|
|
294
|
-
s.stop("Schema will be set up on first dev start");
|
|
295
|
-
}
|
|
296
|
-
s.start("Seeding demo content");
|
|
297
|
-
try {
|
|
298
|
-
execSync(`${pm.run} cms:seed`, { cwd: projectDir, stdio: "pipe" });
|
|
299
|
-
s.stop("Demo content seeded");
|
|
300
|
-
} catch (err) {
|
|
301
|
-
s.stop("Seeding failed — run `pnpm cms:seed` manually");
|
|
302
|
-
if (err.stderr) console.error(err.stderr.toString());
|
|
303
|
-
if (err.stdout) console.error(err.stdout.toString());
|
|
304
|
-
}
|
|
305
|
-
} else if (seedDemo && target === "cloudflare") {
|
|
116
|
+
if (cloudflare) {
|
|
306
117
|
p.note(
|
|
307
118
|
[
|
|
308
|
-
|
|
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`,
|
|
309
129
|
"",
|
|
310
|
-
|
|
311
|
-
"
|
|
312
|
-
|
|
313
|
-
` ${pm.run} cms:seed`,
|
|
130
|
+
"3. Run locally or deploy:",
|
|
131
|
+
" pnpm dev",
|
|
132
|
+
" pnpm deploy",
|
|
314
133
|
].join("\n"),
|
|
315
|
-
"
|
|
134
|
+
"Next steps",
|
|
316
135
|
);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// --- Cloudflare resource setup ---
|
|
320
|
-
|
|
321
|
-
const cf = { d1Created: false, r2Created: false, migrationsApplied: false, deployed: false, url: null };
|
|
322
|
-
if (target === "cloudflare") {
|
|
323
|
-
const setupNow = await p.confirm({
|
|
324
|
-
message: "Set up Cloudflare resources now? (creates D1 database and R2 bucket)",
|
|
325
|
-
initialValue: true,
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
if (!p.isCancel(setupNow) && setupNow) {
|
|
329
|
-
// Check wrangler authentication
|
|
330
|
-
let authenticated = false;
|
|
331
|
-
try {
|
|
332
|
-
execSync(`${pm.exec} wrangler whoami`, { cwd: projectDir, stdio: "pipe" });
|
|
333
|
-
authenticated = true;
|
|
334
|
-
} catch {
|
|
335
|
-
p.note("You need to log in to Cloudflare first.", "Wrangler login required");
|
|
336
|
-
const doLogin = await p.confirm({ message: "Open browser to log in?", initialValue: true });
|
|
337
|
-
if (!p.isCancel(doLogin) && doLogin) {
|
|
338
|
-
try {
|
|
339
|
-
execSync(`${pm.exec} wrangler login`, { cwd: projectDir, stdio: "inherit" });
|
|
340
|
-
authenticated = true;
|
|
341
|
-
} catch {
|
|
342
|
-
s.stop("Login failed");
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (authenticated) {
|
|
348
|
-
// Create D1 database
|
|
349
|
-
let databaseId = null;
|
|
350
|
-
s.start("Creating D1 database");
|
|
351
|
-
try {
|
|
352
|
-
const output = execSync(`${pm.exec} wrangler d1 create ${projectName}-db`, {
|
|
353
|
-
cwd: projectDir,
|
|
354
|
-
stdio: "pipe",
|
|
355
|
-
}).toString();
|
|
356
|
-
// Parse database_id from output (looks for UUID-like string)
|
|
357
|
-
const match = output.match(/database_id\s*=\s*"([^"]+)"/);
|
|
358
|
-
if (match) databaseId = match[1];
|
|
359
|
-
cf.d1Created = true;
|
|
360
|
-
s.stop("D1 database created");
|
|
361
|
-
} catch (err) {
|
|
362
|
-
// Already exists — look it up
|
|
363
|
-
try {
|
|
364
|
-
const listOutput = execSync(`${pm.exec} wrangler d1 list`, { cwd: projectDir, stdio: "pipe" }).toString();
|
|
365
|
-
const lines = listOutput.split("\n");
|
|
366
|
-
const dbLine = lines.find((l) => l.includes(`${projectName}-db`));
|
|
367
|
-
if (dbLine) {
|
|
368
|
-
const idMatch = dbLine.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
|
|
369
|
-
if (idMatch) databaseId = idMatch[0];
|
|
370
|
-
}
|
|
371
|
-
if (databaseId) {
|
|
372
|
-
cf.d1Created = true;
|
|
373
|
-
s.stop("D1 database already exists — using existing");
|
|
374
|
-
} else {
|
|
375
|
-
s.stop("D1 setup failed");
|
|
376
|
-
if (err.stderr) console.error(err.stderr.toString());
|
|
377
|
-
}
|
|
378
|
-
} catch {
|
|
379
|
-
s.stop("D1 setup failed");
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Update wrangler.toml with database_id
|
|
384
|
-
if (databaseId) {
|
|
385
|
-
const wranglerPath = path.join(projectDir, "wrangler.toml");
|
|
386
|
-
let wranglerContent = readFileSync(wranglerPath, "utf-8");
|
|
387
|
-
wranglerContent = wranglerContent.replace(/database_id = "" #[^\n]*/, `database_id = "${databaseId}"`);
|
|
388
|
-
writeFileSync(wranglerPath, wranglerContent);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Create R2 bucket
|
|
392
|
-
s.start("Creating R2 bucket");
|
|
393
|
-
try {
|
|
394
|
-
execSync(`${pm.exec} wrangler r2 bucket create ${projectName}-assets`, { cwd: projectDir, stdio: "pipe" });
|
|
395
|
-
cf.r2Created = true;
|
|
396
|
-
s.stop("R2 bucket created");
|
|
397
|
-
} catch {
|
|
398
|
-
// Already exists is fine — assume it's there
|
|
399
|
-
cf.r2Created = true;
|
|
400
|
-
s.stop("R2 bucket already exists");
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Generate migrations and apply to remote D1
|
|
404
|
-
if (databaseId) {
|
|
405
|
-
s.start("Generating database migrations");
|
|
406
|
-
try {
|
|
407
|
-
execSync(`${pm.exec} drizzle-kit generate`, { cwd: projectDir, stdio: "pipe" });
|
|
408
|
-
s.stop("Migrations generated");
|
|
409
|
-
} catch (err) {
|
|
410
|
-
s.stop("Migration generation failed");
|
|
411
|
-
if (err.stderr) console.error(err.stderr.toString().slice(-800));
|
|
412
|
-
if (err.stdout) console.error(err.stdout.toString().slice(-800));
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
s.start("Applying migrations to remote D1");
|
|
416
|
-
try {
|
|
417
|
-
execSync(`${pm.exec} wrangler d1 migrations apply ${projectName}-db --remote`, {
|
|
418
|
-
cwd: projectDir,
|
|
419
|
-
stdio: "pipe",
|
|
420
|
-
input: "y\n",
|
|
421
|
-
});
|
|
422
|
-
cf.migrationsApplied = true;
|
|
423
|
-
s.stop("Migrations applied");
|
|
424
|
-
} catch {
|
|
425
|
-
s.stop("Migration apply failed — run manually with: wrangler d1 migrations apply --remote");
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Deploy to Cloudflare
|
|
430
|
-
if (cf.migrationsApplied) {
|
|
431
|
-
const doDeploy = await p.confirm({
|
|
432
|
-
message: "Deploy to Cloudflare now?",
|
|
433
|
-
initialValue: true,
|
|
434
|
-
});
|
|
435
|
-
if (!p.isCancel(doDeploy) && doDeploy) {
|
|
436
|
-
s.start("Building and deploying to Cloudflare");
|
|
437
|
-
try {
|
|
438
|
-
const deployOutput = await runAsync(`${pm.run} run deploy`, projectDir);
|
|
439
|
-
const urlMatch = deployOutput.match(/https:\/\/[^\s]+\.workers\.dev/);
|
|
440
|
-
if (urlMatch) cf.url = urlMatch[0];
|
|
441
|
-
cf.deployed = true;
|
|
442
|
-
s.stop("Deployed to Cloudflare");
|
|
443
|
-
} catch (err) {
|
|
444
|
-
s.stop("Deploy failed — run manually with: pnpm run deploy");
|
|
445
|
-
if (err.stderr) console.error(err.stderr.slice(-1500));
|
|
446
|
-
if (err.stdout) console.error(err.stdout.slice(-1500));
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// --- Done ---
|
|
455
|
-
|
|
456
|
-
if (target === "local") {
|
|
457
|
-
p.outro("Starting dev server...");
|
|
458
|
-
try {
|
|
459
|
-
execSync(`${pm.run} dev`, { cwd: projectDir, stdio: "inherit" });
|
|
460
|
-
} catch {
|
|
461
|
-
console.log(`\n Project directory: ${projectDir}`);
|
|
462
|
-
console.log(` To start again: cd ${projectName} && pnpm dev\n`);
|
|
463
|
-
}
|
|
136
|
+
p.outro("Project created!");
|
|
464
137
|
} else {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
[
|
|
468
|
-
`Live at: ${cf.url}`,
|
|
469
|
-
`Admin: ${cf.url}/admin`,
|
|
470
|
-
"",
|
|
471
|
-
`cd ${projectName}`,
|
|
472
|
-
"",
|
|
473
|
-
"Local development:",
|
|
474
|
-
` ${pm.run} dev`,
|
|
475
|
-
"",
|
|
476
|
-
"Redeploy:",
|
|
477
|
-
" pnpm run deploy",
|
|
478
|
-
].join("\n"),
|
|
479
|
-
"🎉 Your Kide CMS is live",
|
|
480
|
-
);
|
|
481
|
-
p.outro("Project created!");
|
|
482
|
-
} else {
|
|
483
|
-
const lines = [`cd ${projectName}`];
|
|
484
|
-
const remaining = [];
|
|
485
|
-
if (!cf.d1Created) {
|
|
486
|
-
remaining.push(` ${pm.dlx} wrangler d1 create ${projectName}-db`, " # Copy the database_id to wrangler.toml");
|
|
487
|
-
}
|
|
488
|
-
if (!cf.r2Created) {
|
|
489
|
-
remaining.push(` ${pm.dlx} wrangler r2 bucket create ${projectName}-assets`);
|
|
490
|
-
}
|
|
491
|
-
if (!cf.migrationsApplied) {
|
|
492
|
-
remaining.push(` ${pm.dlx} wrangler d1 migrations apply ${projectName}-db --remote`);
|
|
493
|
-
}
|
|
494
|
-
if (!cf.deployed) {
|
|
495
|
-
remaining.push(` ${pm.run} run deploy`);
|
|
496
|
-
}
|
|
497
|
-
if (remaining.length > 0) {
|
|
498
|
-
lines.push("", "Remaining setup:", ...remaining);
|
|
499
|
-
}
|
|
500
|
-
lines.push("", "Local development:", ` ${pm.run} dev`);
|
|
501
|
-
p.note(lines.join("\n"), "Next steps");
|
|
502
|
-
p.outro("Project created!");
|
|
503
|
-
}
|
|
138
|
+
p.note([`cd ${projectName}`, "pnpm dev"].join("\n"), "Next steps");
|
|
139
|
+
p.outro("Project created!");
|
|
504
140
|
}
|
|
505
141
|
}
|
|
506
142
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kide-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Scaffold a new Kide CMS project",
|
|
5
5
|
"author": "Matti Hernesniemi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"create-kide-app": "./index.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"index.js"
|
|
13
|
-
"templates"
|
|
12
|
+
"index.js"
|
|
14
13
|
],
|
|
15
14
|
"repository": {
|
|
16
15
|
"type": "git",
|