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.
- package/index.js +27 -3
- 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
|
|
148
|
-
execSync
|
|
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 {
|