create-kide-app 0.3.2 → 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 +76 -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,
|
|
@@ -60,6 +61,7 @@ const CLEANUP = [
|
|
|
60
61
|
".github/workflows", // upstream CI + @kidecms/core release pipeline — wrong in any scaffold
|
|
61
62
|
".github/pull_request_template.md", // Kide's own contribution checklist — wrong in any scaffold
|
|
62
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
|
|
63
65
|
"data",
|
|
64
66
|
".cms-data",
|
|
65
67
|
"dist",
|
|
@@ -95,24 +97,44 @@ const validateProjectName = (value) => {
|
|
|
95
97
|
};
|
|
96
98
|
|
|
97
99
|
// Resolve the latest release tag (v-prefixed semver) so scaffolds pin to a
|
|
98
|
-
// deliberate release instead of whatever HEAD happens to be.
|
|
99
|
-
//
|
|
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).
|
|
100
104
|
const resolveLatestTag = () => {
|
|
101
105
|
try {
|
|
102
106
|
const output = execSync(
|
|
103
107
|
`git ls-remote --tags --sort=-v:refname ${REPO} "v*"`,
|
|
104
108
|
{ stdio: "pipe" },
|
|
105
109
|
).toString();
|
|
110
|
+
let tag = null;
|
|
111
|
+
let commit = null;
|
|
106
112
|
for (const line of output.split("\n")) {
|
|
107
|
-
const match = line.match(
|
|
108
|
-
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];
|
|
109
120
|
}
|
|
121
|
+
if (tag) return { tag, commit };
|
|
110
122
|
} catch {
|
|
111
123
|
// Network/git hiccup — fall back to default branch
|
|
112
124
|
}
|
|
113
125
|
return null;
|
|
114
126
|
};
|
|
115
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
|
+
|
|
116
138
|
// --- CLI flags (non-interactive use: CI, agents, testing) ---
|
|
117
139
|
// Any prompt whose answer is supplied by a flag is skipped. Example:
|
|
118
140
|
// create-kide-app my-app --starter=marketing --seed --mode=embedded --target=local --no-github --no-dev
|
|
@@ -194,36 +216,58 @@ async function main() {
|
|
|
194
216
|
|
|
195
217
|
s.start("Downloading template");
|
|
196
218
|
|
|
197
|
-
const
|
|
198
|
-
|
|
219
|
+
const resolved = resolveLatestTag();
|
|
220
|
+
const templateRef = resolved?.tag ?? null;
|
|
221
|
+
let templateCommit = resolved?.commit ?? null;
|
|
199
222
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
execFileSync(
|
|
205
|
-
"git",
|
|
206
|
-
["clone", "--depth", "1", ...branchArgs, REPO, projectDir],
|
|
207
|
-
{
|
|
208
|
-
stdio: "pipe",
|
|
209
|
-
},
|
|
210
|
-
);
|
|
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) {
|
|
211
227
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
|
|
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;
|
|
218
237
|
} catch {
|
|
219
|
-
|
|
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);
|
|
220
270
|
}
|
|
221
|
-
rmSync(path.join(projectDir, ".git"), { recursive: true, force: true });
|
|
222
|
-
} catch {
|
|
223
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
224
|
-
s.stop("Failed to download template.");
|
|
225
|
-
p.cancel("Check your network connection.");
|
|
226
|
-
process.exit(1);
|
|
227
271
|
}
|
|
228
272
|
|
|
229
273
|
// Remove files that shouldn't be in the scaffold
|
|
@@ -457,6 +501,8 @@ async function main() {
|
|
|
457
501
|
|
|
458
502
|
// Upstream-repo tooling that no scaffold can run (needs the deleted adapters/ overlay).
|
|
459
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"];
|
|
460
506
|
|
|
461
507
|
if (mode === "package" && pkg.dependencies["@kidecms/core"]) {
|
|
462
508
|
pkg.dependencies["@kidecms/core"] = `^${coreVersion}`;
|