create-kide-app 0.1.7 → 0.1.9

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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.js +63 -29
  3. package/package.json +4 -1
package/README.md CHANGED
@@ -5,7 +5,7 @@ Scaffold a new [Kide CMS](https://github.com/mhernesniemi/kide-cms) project.
5
5
  ## Usage
6
6
 
7
7
  ```bash
8
- pnpx create-kide-app my-project
8
+ pnpm create kide-app my-project
9
9
  ```
10
10
 
11
11
  The CLI asks for:
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import * as p from "@clack/prompts";
4
- import { execSync, spawn } from "node:child_process";
4
+ import { execFileSync, execSync, spawn } from "node:child_process";
5
5
  import {
6
6
  cpSync,
7
7
  existsSync,
@@ -59,6 +59,19 @@ const CLEANUP = [
59
59
  ".DS_Store",
60
60
  ];
61
61
 
62
+ // The project name reaches shell commands (git, wrangler) and path.resolve, so it is
63
+ // validated before either. Restricting it to one path segment of safe characters keeps
64
+ // `$(...)`, backticks, `;` and separators out of those commands and out of the target
65
+ // path. Must start alphanumeric so "." and ".." can never be the whole name.
66
+ const PROJECT_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
67
+
68
+ const validateProjectName = (value) => {
69
+ if (!PROJECT_NAME_PATTERN.test(value)) {
70
+ return "Project name must start with a letter or number and contain only letters, numbers, dots, dashes and underscores.";
71
+ }
72
+ return undefined;
73
+ };
74
+
62
75
  // Resolve the latest release tag (v-prefixed semver) so scaffolds pin to a
63
76
  // deliberate release instead of whatever HEAD happens to be. Returns null when
64
77
  // the repo has no tags (falls back to the default branch).
@@ -91,6 +104,7 @@ async function main() {
91
104
  placeholder: "my-cms-app",
92
105
  validate: (value) => {
93
106
  if (!value) return "Project name is required";
107
+ return validateProjectName(value);
94
108
  },
95
109
  }));
96
110
 
@@ -99,6 +113,14 @@ async function main() {
99
113
  process.exit(0);
100
114
  }
101
115
 
116
+ // Re-check: the interactive path validates above, but a name from argv skips it,
117
+ // and this value reaches shell commands and path.resolve below.
118
+ const nameError = validateProjectName(projectName);
119
+ if (nameError) {
120
+ p.cancel(nameError);
121
+ process.exit(1);
122
+ }
123
+
102
124
  const projectDir = path.resolve(process.cwd(), projectName);
103
125
  if (existsSync(projectDir)) {
104
126
  p.cancel(`Directory "${projectName}" already exists.`);
@@ -144,10 +166,16 @@ async function main() {
144
166
  let templateCommit = null;
145
167
 
146
168
  try {
147
- const branchFlag = templateRef ? `--branch "${templateRef}" ` : "";
148
- execSync(`git clone --depth 1 ${branchFlag}${REPO} "${projectDir}"`, {
149
- stdio: "pipe",
150
- });
169
+ const branchArgs = templateRef ? ["--branch", templateRef] : [];
170
+ // execFileSync, not execSync: no shell, so projectDir is passed as one argv entry
171
+ // and can never be reinterpreted as a command regardless of what it contains.
172
+ execFileSync(
173
+ "git",
174
+ ["clone", "--depth", "1", ...branchArgs, REPO, projectDir],
175
+ {
176
+ stdio: "pipe",
177
+ },
178
+ );
151
179
  try {
152
180
  templateCommit = execSync("git rev-parse HEAD", {
153
181
  cwd: projectDir,
@@ -184,31 +212,24 @@ async function main() {
184
212
  const targetDir = path.join(adaptersDir, target);
185
213
 
186
214
  if (target === "cloudflare") {
215
+ // The Cloudflare adapter/storage/env implementations live in the tree at
216
+ // src/cms/platform/cloudflare. We only copy the target's Astro/Drizzle/wrangler config and
217
+ // flip the two platform selectors to point at that profile — no source files are overwritten.
187
218
  cpSync(
188
219
  path.join(targetDir, "astro.config.mjs"),
189
220
  path.join(projectDir, "astro.config.mjs"),
190
221
  );
191
- cpSync(
192
- path.join(targetDir, "src/cms/adapters/db.ts"),
193
- path.join(projectDir, "src/cms/adapters/db.ts"),
194
- );
195
222
  cpSync(
196
223
  path.join(targetDir, "drizzle.config.ts"),
197
224
  path.join(projectDir, "drizzle.config.ts"),
198
225
  );
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"),
226
+ writeFileSync(
227
+ path.join(projectDir, "src/cms/adapters/db.ts"),
228
+ 'export * from "../platform/cloudflare/database";\n',
206
229
  );
207
- const uploadsRouteDir = path.join(projectDir, "src/pages/uploads");
208
- mkdirSync(uploadsRouteDir, { recursive: true });
209
- cpSync(
210
- path.join(targetDir, "src/pages/uploads/[...path].ts"),
211
- path.join(uploadsRouteDir, "[...path].ts"),
230
+ writeFileSync(
231
+ path.join(projectDir, "src/cms/adapters/storage.ts"),
232
+ 'export * from "../platform/cloudflare/storage";\n',
212
233
  );
213
234
  }
214
235
 
@@ -219,7 +240,7 @@ async function main() {
219
240
 
220
241
  if (target === "cloudflare") {
221
242
  delete pkg.dependencies["@astrojs/node"];
222
- pkg.dependencies["@astrojs/cloudflare"] = "^13.0.0";
243
+ pkg.dependencies["@astrojs/cloudflare"] = "~14.1.7";
223
244
 
224
245
  // Move better-sqlite3 to devDependencies — drizzle-kit needs it to push schema to local D1
225
246
  if (pkg.dependencies["better-sqlite3"]) {
@@ -248,7 +269,7 @@ async function main() {
248
269
  );
249
270
  writeFileSync(path.join(projectDir, "wrangler.toml"), wranglerContent);
250
271
 
251
- pkg.devDependencies.wrangler = "^4.0.0";
272
+ pkg.devDependencies.wrangler = "^4.83.0";
252
273
 
253
274
  pkg.scripts.dev = "astro dev";
254
275
  pkg.scripts.build = "astro build";
@@ -689,12 +710,25 @@ async function main() {
689
710
  // --- Done ---
690
711
 
691
712
  if (target === "local") {
692
- p.outro("Starting dev server...");
693
- try {
694
- execSync(`${pm.run} dev`, { cwd: projectDir, stdio: "inherit" });
695
- } catch {
696
- console.log(`\n Project directory: ${projectDir}`);
697
- console.log(` To start again: cd ${projectName} && pnpm dev\n`);
713
+ const startDev = await p.confirm({
714
+ message: "Start the dev server now?",
715
+ initialValue: true,
716
+ });
717
+
718
+ if (!p.isCancel(startDev) && startDev) {
719
+ p.outro("Starting dev server...");
720
+ try {
721
+ execSync(`${pm.run} dev`, { cwd: projectDir, stdio: "inherit" });
722
+ } catch {
723
+ console.log(`\n Project directory: ${projectDir}`);
724
+ console.log(` To start again: cd ${projectName} && pnpm dev\n`);
725
+ }
726
+ } else {
727
+ p.note(
728
+ [`cd ${projectName}`, "", `${pm.run} dev`].join("\n"),
729
+ "Next steps",
730
+ );
731
+ p.outro("Project created!");
698
732
  }
699
733
  } else {
700
734
  if (cf.deployed && cf.url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kide-app",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Scaffold a new Kide CMS project",
5
5
  "author": "Matti Hernesniemi",
6
6
  "license": "MIT",
@@ -8,6 +8,9 @@
8
8
  "bin": {
9
9
  "create-kide-app": "./index.js"
10
10
  },
11
+ "scripts": {
12
+ "release": "npm version patch && git push --follow-tags && npm publish"
13
+ },
11
14
  "files": [
12
15
  "index.js"
13
16
  ],