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.
Files changed (2) hide show
  1. package/index.js +76 -30
  2. 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. Returns null when
99
- // the repo has no tags (falls back to the default branch).
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(/refs\/tags\/(v[0-9][^^\s]*)$/);
108
- if (match) return match[1];
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 templateRef = resolveLatestTag();
198
- let templateCommit = null;
219
+ const resolved = resolveLatestTag();
220
+ const templateRef = resolved?.tag ?? null;
221
+ let templateCommit = resolved?.commit ?? null;
199
222
 
200
- try {
201
- const branchArgs = templateRef ? ["--branch", templateRef] : [];
202
- // execFileSync, not execSync: no shell, so projectDir is passed as one argv entry
203
- // and can never be reinterpreted as a command regardless of what it contains.
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
- templateCommit = execSync("git rev-parse HEAD", {
213
- cwd: projectDir,
214
- stdio: "pipe",
215
- })
216
- .toString()
217
- .trim();
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
- // best-effort stamp without a commit hash
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}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kide-app",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Scaffold a new Kide CMS project",
5
5
  "author": "Matti Hernesniemi",
6
6
  "license": "MIT",