create-stardrive 1.1.0 → 1.2.1

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/bin/index.js +248 -22
  2. package/package.json +4 -4
package/bin/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import path3 from "node:path";
4
+ import path4 from "node:path";
5
5
 
6
6
  // src/args.ts
7
7
  function parseArgs(argv) {
@@ -84,27 +84,245 @@ var devCommands = {
84
84
  bun: "bun run dev"
85
85
  };
86
86
 
87
- // src/project-name.ts
87
+ // src/features.ts
88
88
  import fs from "node:fs";
89
89
  import path from "node:path";
90
90
  import readline from "node:readline/promises";
91
91
  import { stdin as input, stdout as output2 } from "node:process";
92
+ function removePaths(targetDir2, entries) {
93
+ for (const entry of entries) {
94
+ fs.rmSync(path.join(targetDir2, entry), {
95
+ recursive: true,
96
+ force: true
97
+ });
98
+ }
99
+ }
100
+ function editFile(targetDir2, relativePath, transform) {
101
+ const filePath = path.join(targetDir2, relativePath);
102
+ if (!fs.existsSync(filePath)) return;
103
+ const before = fs.readFileSync(filePath, "utf8");
104
+ const after = transform(before);
105
+ if (after !== before) fs.writeFileSync(filePath, after);
106
+ }
107
+ function removeLines(source, pattern) {
108
+ return source.split("\n").filter((line) => !pattern.test(line)).join("\n");
109
+ }
110
+ function removeBraceBlock(source, startPattern) {
111
+ const match = startPattern.exec(source);
112
+ if (!match) return source;
113
+ const start = match.index;
114
+ let i = source.indexOf("{", start);
115
+ if (i === -1) return source;
116
+ let depth = 0;
117
+ let end = -1;
118
+ for (; i < source.length; i++) {
119
+ const ch = source[i];
120
+ if (ch === "{") depth++;
121
+ else if (ch === "}") {
122
+ depth--;
123
+ if (depth === 0) {
124
+ end = i;
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ if (end === -1) return source;
130
+ let after = end + 1;
131
+ while (after < source.length && /[);,]/.test(source[after])) after++;
132
+ if (source[after] === "\n") after++;
133
+ let head = start;
134
+ while (head > 0 && /[ \t]/.test(source[head - 1])) head--;
135
+ return source.slice(0, head) + source.slice(after);
136
+ }
137
+ function removeCollectionFromExport(source, name) {
138
+ return source.replace(
139
+ /(export const collections\s*=\s*\{)([^}]*)(\};?)/,
140
+ (_full, open, body, close) => {
141
+ const items = body.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0 && entry !== name);
142
+ return `${open} ${items.join(", ")} ${close}`;
143
+ }
144
+ );
145
+ }
146
+ function removeBlog(targetDir2) {
147
+ removePaths(targetDir2, [
148
+ "src/utils/blog.ts",
149
+ "src/utils/reading-time.ts",
150
+ "src/styles/blog.css",
151
+ "src/pages/rss.xml.js",
152
+ "src/pages/[lang]/rss.xml.js",
153
+ "src/pages/blog",
154
+ "src/pages/[lang]/blog",
155
+ "src/layouts/article.astro",
156
+ "src/images/content/articles-fallback.jpg",
157
+ "src/images/content/articles",
158
+ "src/content/articles",
159
+ "src/components/structured/article.astro",
160
+ "src/components/blog",
161
+ "scripts/processSocialImages.js",
162
+ "public/data/articles"
163
+ ]);
164
+ editFile(
165
+ targetDir2,
166
+ "scripts/postbuild.js",
167
+ (content) => removeLines(content, /await import\(['"]\.\/processSocialImages\.js['"]\);/)
168
+ );
169
+ editFile(targetDir2, "src/content.config.ts", (content) => {
170
+ const withoutDecl = removeBraceBlock(content, /const articles = defineCollection\(/);
171
+ return removeCollectionFromExport(withoutDecl, "articles");
172
+ });
173
+ editFile(targetDir2, "theme.config.ts", (content) => {
174
+ const withoutSection = removeBraceBlock(content, /^[ \t]*articles:\s*\{/m);
175
+ const withoutComment = removeLines(withoutSection, /^\s*\/\/ content\/article settings\s*$/);
176
+ return removeLines(withoutComment, /^\s*addArticles:/);
177
+ });
178
+ }
179
+ function removeFaq(targetDir2) {
180
+ removePaths(targetDir2, [
181
+ "src/content/faq-answers",
182
+ "src/pages/faq.astro",
183
+ "src/pages/[lang]/faq.astro"
184
+ ]);
185
+ editFile(targetDir2, "src/content.config.ts", (content) => {
186
+ const withoutDecl = removeBraceBlock(content, /const faq_answers = defineCollection\(/);
187
+ return removeCollectionFromExport(withoutDecl, "faq_answers");
188
+ });
189
+ editFile(
190
+ targetDir2,
191
+ "theme.config.ts",
192
+ (content) => removeLines(content, /^\s*addFAQ:/)
193
+ );
194
+ }
195
+ function removeIntegration(targetDir2) {
196
+ removePaths(targetDir2, [
197
+ "src/images/content/integration",
198
+ "src/content/integration-options",
199
+ "src/pages/integration",
200
+ "src/pages/[lang]/integration",
201
+ "src/components/integration-list.astro"
202
+ ]);
203
+ editFile(targetDir2, "src/content.config.ts", (content) => {
204
+ const withoutDecl = removeBraceBlock(
205
+ content,
206
+ /const integration_options = defineCollection\(/
207
+ );
208
+ return removeCollectionFromExport(withoutDecl, "integration_options");
209
+ });
210
+ }
211
+ function cleanupNav(targetDir2, removed) {
212
+ const routes = [
213
+ removed.blog ? "blog" : null,
214
+ removed.faq ? "faq" : null,
215
+ removed.integration ? "integration" : null
216
+ ].filter((route) => route !== null);
217
+ if (routes.length === 0) return;
218
+ const pattern = new RegExp(
219
+ `getLocaleUrl\\(['"](?:${routes.join("|")})['"]`
220
+ );
221
+ editFile(
222
+ targetDir2,
223
+ "src/components/layout/nav/footer-nav.astro",
224
+ (content) => removeLines(content, pattern)
225
+ );
226
+ }
227
+ function removeCloudflare(targetDir2) {
228
+ removePaths(targetDir2, [
229
+ "scripts/purgeCloudflareCache.js",
230
+ "worker-configuration.d.ts",
231
+ "wrangler.jsonc",
232
+ "public/_headers"
233
+ ]);
234
+ editFile(targetDir2, "astro.config.ts", (content) => {
235
+ const withoutImport = removeLines(
236
+ content,
237
+ /^import cloudflare from ['"]@astrojs\/cloudflare['"];/
238
+ );
239
+ return removeLines(withoutImport, /^\s*adapter: cloudflare\(/);
240
+ });
241
+ const packageJsonPath = path.join(targetDir2, "package.json");
242
+ if (fs.existsSync(packageJsonPath)) {
243
+ const pkg = JSON.parse(
244
+ fs.readFileSync(packageJsonPath, "utf8")
245
+ );
246
+ if (pkg.scripts) delete pkg.scripts["purge:cloudflare"];
247
+ if (pkg.dependencies) {
248
+ delete pkg.dependencies["@astrojs/cloudflare"];
249
+ delete pkg.dependencies["wrangler"];
250
+ }
251
+ if (pkg.devDependencies) {
252
+ delete pkg.devDependencies["@astrojs/cloudflare"];
253
+ delete pkg.devDependencies["wrangler"];
254
+ }
255
+ fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
256
+ }
257
+ }
258
+ async function confirm(rl, question, defaultYes = true) {
259
+ const hint = defaultYes ? "(Y/n)" : "(y/N)";
260
+ const answer = (await rl.question(`${c.cyan("?")} ${c.bold(question)} ${c.dim(`${hint} `)}`)).trim().toLowerCase();
261
+ if (!answer) return defaultYes;
262
+ return answer === "y" || answer === "yes";
263
+ }
264
+ async function configureFeatures(targetDir2) {
265
+ step("Configuring optional features");
266
+ if (!input.isTTY) {
267
+ info("Non-interactive shell detected; keeping all features.");
268
+ return;
269
+ }
270
+ const rl = readline.createInterface({ input, output: output2 });
271
+ try {
272
+ const keepBlog = await confirm(rl, "Keep the blog feature?");
273
+ if (!keepBlog) {
274
+ removeBlog(targetDir2);
275
+ ok("Removed the blog feature");
276
+ }
277
+ const keepFaq = await confirm(rl, "Keep the FAQ feature?");
278
+ if (!keepFaq) {
279
+ removeFaq(targetDir2);
280
+ ok("Removed the FAQ feature");
281
+ }
282
+ const keepIntegration = await confirm(rl, "Keep the integration catalog?");
283
+ if (!keepIntegration) {
284
+ removeIntegration(targetDir2);
285
+ ok("Removed the integration catalog");
286
+ }
287
+ cleanupNav(targetDir2, {
288
+ blog: !keepBlog,
289
+ faq: !keepFaq,
290
+ integration: !keepIntegration
291
+ });
292
+ const useCloudflare = await confirm(
293
+ rl,
294
+ "Will you host on Cloudflare Workers?"
295
+ );
296
+ if (!useCloudflare) {
297
+ removeCloudflare(targetDir2);
298
+ ok("Removed Cloudflare-specific setup");
299
+ }
300
+ } finally {
301
+ rl.close();
302
+ }
303
+ }
304
+
305
+ // src/project-name.ts
306
+ import fs2 from "node:fs";
307
+ import path2 from "node:path";
308
+ import readline2 from "node:readline/promises";
309
+ import { stdin as input2, stdout as output3 } from "node:process";
92
310
  function isValidProjectName(name) {
93
311
  return /^[a-z0-9._-]+$/i.test(name) && !/^[._]/.test(name);
94
312
  }
95
313
  async function askProjectName(initial) {
96
314
  if (initial && isValidProjectName(initial)) {
97
- const targetDir2 = path.resolve(initial);
98
- if (!fs.existsSync(targetDir2)) return initial;
315
+ const targetDir2 = path2.resolve(initial);
316
+ if (!fs2.existsSync(targetDir2)) return initial;
99
317
  fail(`Directory "${initial}" already exists.`);
100
318
  }
101
- if (!input.isTTY) {
319
+ if (!input2.isTTY) {
102
320
  fail(
103
321
  `Project name "${initial ?? ""}" is missing or invalid and stdin is not interactive.`
104
322
  );
105
323
  process.exit(1);
106
324
  }
107
- const rl = readline.createInterface({ input, output: output2 });
325
+ const rl = readline2.createInterface({ input: input2, output: output3 });
108
326
  try {
109
327
  while (true) {
110
328
  const answer = (await rl.question(
@@ -117,8 +335,8 @@ async function askProjectName(initial) {
117
335
  );
118
336
  continue;
119
337
  }
120
- const targetDir2 = path.resolve(name);
121
- if (fs.existsSync(targetDir2)) {
338
+ const targetDir2 = path2.resolve(name);
339
+ if (fs2.existsSync(targetDir2)) {
122
340
  console.log(
123
341
  ` ${c.yellow("!")} "${name}" already exists. Try another.`
124
342
  );
@@ -147,10 +365,10 @@ function compareSemver(a, b) {
147
365
  return 0;
148
366
  }
149
367
  function resolveTag(requestedVersion2) {
150
- const output3 = execSync(`git ls-remote --tags --refs ${STARDRIVE_REPO}`, {
368
+ const output4 = execSync(`git ls-remote --tags --refs ${STARDRIVE_REPO}`, {
151
369
  encoding: "utf8"
152
370
  });
153
- const tags = output3.split("\n").map((line) => line.split("refs/tags/")[1]).filter((t) => Boolean(t));
371
+ const tags = output4.split("\n").map((line) => line.split("refs/tags/")[1]).filter((t) => Boolean(t));
154
372
  if (requestedVersion2) {
155
373
  const wanted = normalizeTag(requestedVersion2);
156
374
  if (!tags.includes(wanted)) {
@@ -168,11 +386,18 @@ function resolveTag(requestedVersion2) {
168
386
  }
169
387
 
170
388
  // src/scaffold.ts
171
- import fs2 from "node:fs";
389
+ import fs3 from "node:fs";
172
390
  import os from "node:os";
173
- import path2 from "node:path";
391
+ import path3 from "node:path";
174
392
  import { execSync as execSync2 } from "node:child_process";
175
- var TRIM_ENTRIES = ["scripts/syncVersion.js", "SECURITY.md", ".github"];
393
+ var TRIM_ENTRIES = [
394
+ "scripts/syncVersion.js",
395
+ ".ai/TRIMMING_GUIDE.md",
396
+ "SECURITY.md",
397
+ "CHANGELOG.md",
398
+ "repository-header.png",
399
+ ".github"
400
+ ];
176
401
  var STALE_LOCKFILES = [
177
402
  "package-lock.json",
178
403
  "pnpm-lock.yaml",
@@ -180,7 +405,7 @@ var STALE_LOCKFILES = [
180
405
  "bun.lockb"
181
406
  ];
182
407
  function cloneRepo(tag2, targetDir2, projectName2) {
183
- const tempDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "create-app-"));
408
+ const tempDir = fs3.mkdtempSync(path3.join(os.tmpdir(), "create-app-"));
184
409
  step(`Beaming ${c.bold(tag2)} down from orbit`);
185
410
  info(STARDRIVE_REPO);
186
411
  execSync2(
@@ -189,11 +414,11 @@ function cloneRepo(tag2, targetDir2, projectName2) {
189
414
  stdio: ["ignore", "ignore", "inherit"]
190
415
  }
191
416
  );
192
- fs2.rmSync(path2.join(tempDir, ".git"), {
417
+ fs3.rmSync(path3.join(tempDir, ".git"), {
193
418
  recursive: true,
194
419
  force: true
195
420
  });
196
- fs2.cpSync(tempDir, targetDir2, {
421
+ fs3.cpSync(tempDir, targetDir2, {
197
422
  recursive: true
198
423
  });
199
424
  ok(`Landed in ${c.bold(projectName2)}/`);
@@ -201,13 +426,13 @@ function cloneRepo(tag2, targetDir2, projectName2) {
201
426
  function trimProject(targetDir2) {
202
427
  step("Trimming unused boosters");
203
428
  for (const entry of TRIM_ENTRIES) {
204
- fs2.rmSync(path2.join(targetDir2, entry), {
429
+ fs3.rmSync(path3.join(targetDir2, entry), {
205
430
  recursive: true,
206
431
  force: true
207
432
  });
208
433
  }
209
434
  for (const file of STALE_LOCKFILES) {
210
- fs2.rmSync(path2.join(targetDir2, file), {
435
+ fs3.rmSync(path3.join(targetDir2, file), {
211
436
  force: true
212
437
  });
213
438
  }
@@ -215,9 +440,9 @@ function trimProject(targetDir2) {
215
440
  }
216
441
  function calibratePackageJson(targetDir2, projectName2, pm2) {
217
442
  step("Calibrating package.json");
218
- const packageJsonPath = path2.join(targetDir2, "package.json");
443
+ const packageJsonPath = path3.join(targetDir2, "package.json");
219
444
  const pkg = JSON.parse(
220
- fs2.readFileSync(packageJsonPath, "utf8")
445
+ fs3.readFileSync(packageJsonPath, "utf8")
221
446
  );
222
447
  pkg.name = projectName2;
223
448
  if (pkg.scripts) {
@@ -230,7 +455,7 @@ function calibratePackageJson(targetDir2, projectName2, pm2) {
230
455
  if (pm2 === "bun") {
231
456
  pkg.packageManager = `bun@${execSync2("bun --version").toString().trim()}`;
232
457
  }
233
- fs2.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
458
+ fs3.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
234
459
  ok(`Named your ship ${c.bold(projectName2)}`);
235
460
  }
236
461
  function installDependencies(targetDir2, projectName2, pm2, skipInstall2) {
@@ -256,13 +481,14 @@ if (!commandExists(pm)) {
256
481
  }
257
482
  console.log(banner);
258
483
  var projectName = await askProjectName(positional[0]);
259
- var targetDir = path3.resolve(projectName);
484
+ var targetDir = path4.resolve(projectName);
260
485
  step("Locating the latest Stardrive release");
261
486
  var tag = resolveTag(requestedVersion);
262
487
  ok(`Selected ${c.bold(tag)}`);
263
488
  cloneRepo(tag, targetDir, projectName);
264
489
  trimProject(targetDir);
265
490
  calibratePackageJson(targetDir, projectName, pm);
491
+ await configureFeatures(targetDir);
266
492
  installDependencies(targetDir, projectName, pm, skipInstall);
267
493
  console.log(`
268
494
  ${c.green("All systems go.")} ${c.dim("Pre-flight checklist complete.")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-stardrive",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "This is the launchpad application to create the Astro Stardrive boilerplate.",
5
5
  "bin": {
6
6
  "create-stardrive": "bin/index.js"
@@ -51,8 +51,8 @@
51
51
  "typecheck": "tsc --noEmit"
52
52
  },
53
53
  "devDependencies": {
54
- "@types/node": "^22.10.2",
55
- "esbuild": "^0.24.2",
56
- "typescript": "^5.7.2"
54
+ "@types/node": "^26.0.0",
55
+ "esbuild": "^0.28.1",
56
+ "typescript": "^6.0.3"
57
57
  }
58
58
  }