create-kide-app 0.0.36 → 0.1.2
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 +95 -420
- 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,28 +51,22 @@ 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
64
|
let seedDemo = false;
|
|
78
|
-
if (
|
|
65
|
+
if (!cloudflare) {
|
|
79
66
|
const seed = await p.confirm({
|
|
80
67
|
message: "Seed database with demo content?",
|
|
81
68
|
initialValue: false,
|
|
82
69
|
});
|
|
83
|
-
|
|
84
70
|
if (p.isCancel(seed)) {
|
|
85
71
|
p.cancel("Setup cancelled.");
|
|
86
72
|
process.exit(0);
|
|
@@ -90,417 +76,106 @@ async function main() {
|
|
|
90
76
|
|
|
91
77
|
const s = p.spinner();
|
|
92
78
|
|
|
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
|
-
}
|
|
79
|
+
s.start("Cloning kide-cms");
|
|
80
|
+
execSync(`git clone --depth 1 ${REPO} "${projectDir}"`, { stdio: "pipe" });
|
|
81
|
+
rmSync(path.join(projectDir, ".git"), { recursive: true, force: true });
|
|
82
|
+
s.stop("Cloned");
|
|
129
83
|
|
|
130
84
|
const pkgPath = path.join(projectDir, "package.json");
|
|
131
85
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
132
|
-
|
|
133
86
|
pkg.name = projectName;
|
|
87
|
+
pkg.version = "0.0.1";
|
|
88
|
+
|
|
89
|
+
const adaptersDir = path.join(projectDir, "adapters");
|
|
90
|
+
|
|
91
|
+
if (cloudflare) {
|
|
92
|
+
s.start("Applying Cloudflare adapter");
|
|
93
|
+
const cfDir = path.join(adaptersDir, "cloudflare");
|
|
94
|
+
|
|
95
|
+
const overlayFiles = [
|
|
96
|
+
"astro.config.mjs",
|
|
97
|
+
"drizzle.config.ts",
|
|
98
|
+
"src/cms/adapters/db.ts",
|
|
99
|
+
"src/cms/adapters/storage.ts",
|
|
100
|
+
"src/pages/uploads/[...path].ts",
|
|
101
|
+
];
|
|
102
|
+
for (const f of overlayFiles) {
|
|
103
|
+
cpSync(path.join(cfDir, f), path.join(projectDir, f));
|
|
104
|
+
}
|
|
134
105
|
|
|
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);
|
|
106
|
+
const wrangler = readFileSync(path.join(cfDir, "wrangler.toml"), "utf-8").replaceAll(
|
|
107
|
+
"{{PROJECT_NAME}}",
|
|
108
|
+
projectName,
|
|
109
|
+
);
|
|
110
|
+
writeFileSync(path.join(projectDir, "wrangler.toml"), wrangler);
|
|
149
111
|
|
|
150
|
-
|
|
112
|
+
const patch = JSON.parse(readFileSync(path.join(cfDir, "package.patch.json"), "utf-8"));
|
|
113
|
+
applyPackagePatch(pkg, patch);
|
|
151
114
|
|
|
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";
|
|
115
|
+
s.stop("Cloudflare adapter applied");
|
|
156
116
|
}
|
|
157
117
|
|
|
118
|
+
rmSync(adaptersDir, { recursive: true, force: true });
|
|
158
119
|
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
159
120
|
|
|
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
121
|
s.start("Installing dependencies");
|
|
171
122
|
try {
|
|
172
|
-
|
|
123
|
+
execSync("pnpm install", { cwd: projectDir, stdio: "pipe" });
|
|
173
124
|
s.stop("Dependencies installed");
|
|
174
125
|
} catch {
|
|
175
|
-
s.stop(
|
|
126
|
+
s.stop("pnpm install failed — run it manually");
|
|
176
127
|
}
|
|
177
128
|
|
|
178
|
-
|
|
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;
|
|
129
|
+
if (seedDemo) {
|
|
130
|
+
s.start("Generating CMS schema");
|
|
195
131
|
try {
|
|
196
|
-
execSync("
|
|
197
|
-
|
|
198
|
-
ghAvailable = true;
|
|
132
|
+
execSync("pnpm cms:generate", { cwd: projectDir, stdio: "pipe" });
|
|
133
|
+
s.stop("Schema generated");
|
|
199
134
|
} catch {
|
|
200
|
-
|
|
135
|
+
s.stop("Schema generation failed — run `pnpm cms:generate` manually");
|
|
201
136
|
}
|
|
202
137
|
|
|
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");
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// --- Seed demo content ---
|
|
279
|
-
|
|
280
|
-
if (seedDemo && target === "local") {
|
|
281
138
|
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
139
|
try {
|
|
286
|
-
|
|
287
|
-
|
|
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");
|
|
140
|
+
execSync("pnpm exec drizzle-kit push --force", { cwd: projectDir, stdio: "pipe" });
|
|
141
|
+
s.stop("Schema pushed");
|
|
293
142
|
} catch {
|
|
294
|
-
s.stop("Schema will be set up on first dev start");
|
|
143
|
+
s.stop("Schema push failed — will be set up on first dev start");
|
|
295
144
|
}
|
|
145
|
+
|
|
296
146
|
s.start("Seeding demo content");
|
|
297
147
|
try {
|
|
298
|
-
execSync(
|
|
148
|
+
execSync("pnpm cms:seed", { cwd: projectDir, stdio: "pipe" });
|
|
299
149
|
s.stop("Demo content seeded");
|
|
300
|
-
} catch
|
|
150
|
+
} catch {
|
|
301
151
|
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
152
|
}
|
|
305
|
-
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (cloudflare) {
|
|
306
156
|
p.note(
|
|
307
157
|
[
|
|
308
|
-
|
|
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`,
|
|
309
164
|
"",
|
|
310
|
-
|
|
311
|
-
"
|
|
312
|
-
`
|
|
313
|
-
|
|
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",
|
|
314
172
|
].join("\n"),
|
|
315
|
-
"
|
|
173
|
+
"Next steps",
|
|
316
174
|
);
|
|
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
|
-
}
|
|
175
|
+
p.outro("Project created!");
|
|
464
176
|
} 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
|
-
}
|
|
177
|
+
p.note([`cd ${projectName}`, "pnpm dev"].join("\n"), "Next steps");
|
|
178
|
+
p.outro("Project created!");
|
|
504
179
|
}
|
|
505
180
|
}
|
|
506
181
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kide-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|