create-stardrive 1.1.0 → 1.2.0

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 +241 -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,11 @@ 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 = ["scripts/syncVersion.js", ".ai/TRIMMING_GUIDE.md", "SECURITY.md", ".github"];
176
394
  var STALE_LOCKFILES = [
177
395
  "package-lock.json",
178
396
  "pnpm-lock.yaml",
@@ -180,7 +398,7 @@ var STALE_LOCKFILES = [
180
398
  "bun.lockb"
181
399
  ];
182
400
  function cloneRepo(tag2, targetDir2, projectName2) {
183
- const tempDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "create-app-"));
401
+ const tempDir = fs3.mkdtempSync(path3.join(os.tmpdir(), "create-app-"));
184
402
  step(`Beaming ${c.bold(tag2)} down from orbit`);
185
403
  info(STARDRIVE_REPO);
186
404
  execSync2(
@@ -189,11 +407,11 @@ function cloneRepo(tag2, targetDir2, projectName2) {
189
407
  stdio: ["ignore", "ignore", "inherit"]
190
408
  }
191
409
  );
192
- fs2.rmSync(path2.join(tempDir, ".git"), {
410
+ fs3.rmSync(path3.join(tempDir, ".git"), {
193
411
  recursive: true,
194
412
  force: true
195
413
  });
196
- fs2.cpSync(tempDir, targetDir2, {
414
+ fs3.cpSync(tempDir, targetDir2, {
197
415
  recursive: true
198
416
  });
199
417
  ok(`Landed in ${c.bold(projectName2)}/`);
@@ -201,13 +419,13 @@ function cloneRepo(tag2, targetDir2, projectName2) {
201
419
  function trimProject(targetDir2) {
202
420
  step("Trimming unused boosters");
203
421
  for (const entry of TRIM_ENTRIES) {
204
- fs2.rmSync(path2.join(targetDir2, entry), {
422
+ fs3.rmSync(path3.join(targetDir2, entry), {
205
423
  recursive: true,
206
424
  force: true
207
425
  });
208
426
  }
209
427
  for (const file of STALE_LOCKFILES) {
210
- fs2.rmSync(path2.join(targetDir2, file), {
428
+ fs3.rmSync(path3.join(targetDir2, file), {
211
429
  force: true
212
430
  });
213
431
  }
@@ -215,9 +433,9 @@ function trimProject(targetDir2) {
215
433
  }
216
434
  function calibratePackageJson(targetDir2, projectName2, pm2) {
217
435
  step("Calibrating package.json");
218
- const packageJsonPath = path2.join(targetDir2, "package.json");
436
+ const packageJsonPath = path3.join(targetDir2, "package.json");
219
437
  const pkg = JSON.parse(
220
- fs2.readFileSync(packageJsonPath, "utf8")
438
+ fs3.readFileSync(packageJsonPath, "utf8")
221
439
  );
222
440
  pkg.name = projectName2;
223
441
  if (pkg.scripts) {
@@ -230,7 +448,7 @@ function calibratePackageJson(targetDir2, projectName2, pm2) {
230
448
  if (pm2 === "bun") {
231
449
  pkg.packageManager = `bun@${execSync2("bun --version").toString().trim()}`;
232
450
  }
233
- fs2.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
451
+ fs3.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
234
452
  ok(`Named your ship ${c.bold(projectName2)}`);
235
453
  }
236
454
  function installDependencies(targetDir2, projectName2, pm2, skipInstall2) {
@@ -256,13 +474,14 @@ if (!commandExists(pm)) {
256
474
  }
257
475
  console.log(banner);
258
476
  var projectName = await askProjectName(positional[0]);
259
- var targetDir = path3.resolve(projectName);
477
+ var targetDir = path4.resolve(projectName);
260
478
  step("Locating the latest Stardrive release");
261
479
  var tag = resolveTag(requestedVersion);
262
480
  ok(`Selected ${c.bold(tag)}`);
263
481
  cloneRepo(tag, targetDir, projectName);
264
482
  trimProject(targetDir);
265
483
  calibratePackageJson(targetDir, projectName, pm);
484
+ await configureFeatures(targetDir);
266
485
  installDependencies(targetDir, projectName, pm, skipInstall);
267
486
  console.log(`
268
487
  ${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.0",
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
  }