create-kide-app 0.1.7 → 0.1.8

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 +27 -3
  2. package/package.json +1 -1
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,8 +166,10 @@ 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}"`, {
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("git", ["clone", "--depth", "1", ...branchArgs, REPO, projectDir], {
149
173
  stdio: "pipe",
150
174
  });
151
175
  try {
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.8",
4
4
  "description": "Scaffold a new Kide CMS project",
5
5
  "author": "Matti Hernesniemi",
6
6
  "license": "MIT",