create-kide-app 0.3.1 → 0.3.3
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 +78 -30
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { execFileSync, execSync, spawn } from "node:child_process";
|
|
|
5
5
|
import {
|
|
6
6
|
cpSync,
|
|
7
7
|
existsSync,
|
|
8
|
+
mkdirSync,
|
|
8
9
|
readdirSync,
|
|
9
10
|
readFileSync,
|
|
10
11
|
rmSync,
|
|
@@ -55,9 +56,12 @@ const REPO =
|
|
|
55
56
|
const CLEANUP = [
|
|
56
57
|
"docs",
|
|
57
58
|
"CLAUDE.md",
|
|
59
|
+
"CONTRIBUTING.md",
|
|
58
60
|
".claude/settings.local.json",
|
|
59
61
|
".github/workflows", // upstream CI + @kidecms/core release pipeline — wrong in any scaffold
|
|
62
|
+
".github/pull_request_template.md", // Kide's own contribution checklist — wrong in any scaffold
|
|
60
63
|
"scripts/verify-cloudflare.mjs", // needs the adapters/ overlay, which every scaffold deletes
|
|
64
|
+
"scripts/dev-preview.mjs", // needs starters/ (deleted below) and symlinks back into this repo
|
|
61
65
|
"data",
|
|
62
66
|
".cms-data",
|
|
63
67
|
"dist",
|
|
@@ -93,24 +97,44 @@ const validateProjectName = (value) => {
|
|
|
93
97
|
};
|
|
94
98
|
|
|
95
99
|
// Resolve the latest release tag (v-prefixed semver) so scaffolds pin to a
|
|
96
|
-
// deliberate release instead of whatever HEAD happens to be.
|
|
97
|
-
//
|
|
100
|
+
// deliberate release instead of whatever HEAD happens to be. Also captures the
|
|
101
|
+
// tag's commit SHA from the same listing (peeled `^{}` entries win, so annotated
|
|
102
|
+
// tags resolve to the commit, not the tag object). Returns null when the repo
|
|
103
|
+
// has no tags (falls back to the default branch).
|
|
98
104
|
const resolveLatestTag = () => {
|
|
99
105
|
try {
|
|
100
106
|
const output = execSync(
|
|
101
107
|
`git ls-remote --tags --sort=-v:refname ${REPO} "v*"`,
|
|
102
108
|
{ stdio: "pipe" },
|
|
103
109
|
).toString();
|
|
110
|
+
let tag = null;
|
|
111
|
+
let commit = null;
|
|
104
112
|
for (const line of output.split("\n")) {
|
|
105
|
-
const match = line.match(
|
|
106
|
-
if (match)
|
|
113
|
+
const match = line.match(/^([0-9a-f]{40})\trefs\/tags\/(v[0-9][^\s^]*)(\^\{\})?$/);
|
|
114
|
+
if (!match) continue;
|
|
115
|
+
if (!tag) {
|
|
116
|
+
tag = match[2];
|
|
117
|
+
commit = match[1];
|
|
118
|
+
}
|
|
119
|
+
if (match[2] === tag && match[3]) commit = match[1];
|
|
107
120
|
}
|
|
121
|
+
if (tag) return { tag, commit };
|
|
108
122
|
} catch {
|
|
109
123
|
// Network/git hiccup — fall back to default branch
|
|
110
124
|
}
|
|
111
125
|
return null;
|
|
112
126
|
};
|
|
113
127
|
|
|
128
|
+
// GitHub serves any tag as a single gzipped tarball — one HTTP request instead of
|
|
129
|
+
// git protocol negotiation, and no .git dir to delete afterwards. Only applicable
|
|
130
|
+
// for github.com remotes (KIDE_TEMPLATE_REPO may point anywhere, incl. file://).
|
|
131
|
+
const githubTarballUrl = (tag) => {
|
|
132
|
+
const match = REPO.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
133
|
+
return match && tag
|
|
134
|
+
? `https://codeload.github.com/${match[1]}/${match[2]}/tar.gz/refs/tags/${tag}`
|
|
135
|
+
: null;
|
|
136
|
+
};
|
|
137
|
+
|
|
114
138
|
// --- CLI flags (non-interactive use: CI, agents, testing) ---
|
|
115
139
|
// Any prompt whose answer is supplied by a flag is skipped. Example:
|
|
116
140
|
// create-kide-app my-app --starter=marketing --seed --mode=embedded --target=local --no-github --no-dev
|
|
@@ -192,36 +216,58 @@ async function main() {
|
|
|
192
216
|
|
|
193
217
|
s.start("Downloading template");
|
|
194
218
|
|
|
195
|
-
const
|
|
196
|
-
|
|
219
|
+
const resolved = resolveLatestTag();
|
|
220
|
+
const templateRef = resolved?.tag ?? null;
|
|
221
|
+
let templateCommit = resolved?.commit ?? null;
|
|
197
222
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
execFileSync(
|
|
203
|
-
"git",
|
|
204
|
-
["clone", "--depth", "1", ...branchArgs, REPO, projectDir],
|
|
205
|
-
{
|
|
206
|
-
stdio: "pipe",
|
|
207
|
-
},
|
|
208
|
-
);
|
|
223
|
+
// Fast path: single tarball request. Falls back to git clone on any failure.
|
|
224
|
+
let downloaded = false;
|
|
225
|
+
const tarballUrl = githubTarballUrl(templateRef);
|
|
226
|
+
if (tarballUrl) {
|
|
209
227
|
try {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
const response = await fetch(tarballUrl);
|
|
229
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
230
|
+
const tarball = Buffer.from(await response.arrayBuffer());
|
|
231
|
+
mkdirSync(projectDir, { recursive: true });
|
|
232
|
+
execFileSync("tar", ["-xzf", "-", "--strip-components=1", "-C", projectDir], {
|
|
233
|
+
input: tarball,
|
|
234
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
235
|
+
});
|
|
236
|
+
downloaded = true;
|
|
216
237
|
} catch {
|
|
217
|
-
|
|
238
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!downloaded) {
|
|
243
|
+
try {
|
|
244
|
+
const branchArgs = templateRef ? ["--branch", templateRef] : [];
|
|
245
|
+
// execFileSync, not execSync: no shell, so projectDir is passed as one argv entry
|
|
246
|
+
// and can never be reinterpreted as a command regardless of what it contains.
|
|
247
|
+
execFileSync(
|
|
248
|
+
"git",
|
|
249
|
+
["clone", "--depth", "1", ...branchArgs, REPO, projectDir],
|
|
250
|
+
{
|
|
251
|
+
stdio: "pipe",
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
try {
|
|
255
|
+
templateCommit = execSync("git rev-parse HEAD", {
|
|
256
|
+
cwd: projectDir,
|
|
257
|
+
stdio: "pipe",
|
|
258
|
+
})
|
|
259
|
+
.toString()
|
|
260
|
+
.trim();
|
|
261
|
+
} catch {
|
|
262
|
+
// best-effort — stamp without a commit hash
|
|
263
|
+
}
|
|
264
|
+
rmSync(path.join(projectDir, ".git"), { recursive: true, force: true });
|
|
265
|
+
} catch {
|
|
266
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
267
|
+
s.stop("Failed to download template.");
|
|
268
|
+
p.cancel("Check your network connection.");
|
|
269
|
+
process.exit(1);
|
|
218
270
|
}
|
|
219
|
-
rmSync(path.join(projectDir, ".git"), { recursive: true, force: true });
|
|
220
|
-
} catch {
|
|
221
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
222
|
-
s.stop("Failed to download template.");
|
|
223
|
-
p.cancel("Check your network connection.");
|
|
224
|
-
process.exit(1);
|
|
225
271
|
}
|
|
226
272
|
|
|
227
273
|
// Remove files that shouldn't be in the scaffold
|
|
@@ -455,6 +501,8 @@ async function main() {
|
|
|
455
501
|
|
|
456
502
|
// Upstream-repo tooling that no scaffold can run (needs the deleted adapters/ overlay).
|
|
457
503
|
delete pkg.scripts["verify:cloudflare"];
|
|
504
|
+
// Needs starters/ (deleted above) and symlinks back into the template repo — repo-only.
|
|
505
|
+
delete pkg.scripts["dev:preview"];
|
|
458
506
|
|
|
459
507
|
if (mode === "package" && pkg.dependencies["@kidecms/core"]) {
|
|
460
508
|
pkg.dependencies["@kidecms/core"] = `^${coreVersion}`;
|