create-astro 4.0.0-beta.0 → 4.0.0-rc.2

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/dist/index.js +155 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -177,7 +177,6 @@ import detectPackageManager2 from "which-pm-runs";
177
177
  // src/messages.ts
178
178
  import { color, say as houston, label, spinner as load } from "@astrojs/cli-kit";
179
179
  import { align, sleep } from "@astrojs/cli-kit/utils";
180
- import { execa } from "execa";
181
180
  import fetch from "node-fetch-native";
182
181
  import { exec } from "node:child_process";
183
182
 
@@ -201,10 +200,43 @@ function stripAnsi(string) {
201
200
 
202
201
  // src/messages.ts
203
202
  import detectPackageManager from "which-pm-runs";
203
+
204
+ // src/shell.ts
205
+ import { spawn } from "node:child_process";
206
+ import { text as textFromStream } from "node:stream/consumers";
207
+ var text = (stream) => stream ? textFromStream(stream).then((t) => t.trimEnd()) : "";
208
+ async function shell(command, flags, opts = {}) {
209
+ let child;
210
+ let stdout2 = "";
211
+ let stderr = "";
212
+ try {
213
+ child = spawn(command, flags, {
214
+ cwd: opts.cwd,
215
+ shell: true,
216
+ stdio: opts.stdio,
217
+ timeout: opts.timeout
218
+ });
219
+ const done = new Promise((resolve) => child.on("close", resolve));
220
+ [stdout2, stderr] = await Promise.all([text(child.stdout), text(child.stderr)]);
221
+ await done;
222
+ } catch (e) {
223
+ throw { stdout: stdout2, stderr, exitCode: 1 };
224
+ }
225
+ const { exitCode } = child;
226
+ if (exitCode === null) {
227
+ throw new Error("Timeout");
228
+ }
229
+ if (exitCode !== 0) {
230
+ throw new Error(stderr);
231
+ }
232
+ return { stdout: stdout2, stderr, exitCode };
233
+ }
234
+
235
+ // src/messages.ts
204
236
  async function getRegistry() {
205
237
  const packageManager = detectPackageManager()?.name || "npm";
206
238
  try {
207
- const { stdout: stdout2 } = await execa(packageManager, ["config", "get", "registry"]);
239
+ const { stdout: stdout2 } = await shell(packageManager, ["config", "get", "registry"]);
208
240
  return stdout2?.trim()?.replace(/\/$/, "") || "https://registry.npmjs.org";
209
241
  } catch (e) {
210
242
  return "https://registry.npmjs.org";
@@ -220,7 +252,7 @@ async function say(messages, { clear = false, hat = "" } = {}) {
220
252
  async function spinner(args) {
221
253
  await load(args, { stdout });
222
254
  }
223
- var title = (text) => align(label(text), "end", 7) + " ";
255
+ var title = (text2) => align(label(text2), "end", 7) + " ";
224
256
  var welcome = [
225
257
  `Let's claim your corner of the internet.`,
226
258
  `I'll be your assistant today.`,
@@ -260,7 +292,8 @@ var getVersion = () => new Promise(async (resolve) => {
260
292
  return resolve(v);
261
293
  let registry = await getRegistry();
262
294
  const { version } = await fetch(`${registry}/astro/latest`, { redirect: "follow" }).then(
263
- (res) => res.json()
295
+ (res) => res.json(),
296
+ () => ({ version: "" })
264
297
  );
265
298
  v = version;
266
299
  resolve(version);
@@ -268,25 +301,25 @@ var getVersion = () => new Promise(async (resolve) => {
268
301
  var log = (message) => stdout.write(message + "\n");
269
302
  var banner = async (version) => log(
270
303
  `
271
- ${label("astro", color.bgGreen, color.black)} ${color.green(
272
- color.bold(`v${version}`)
273
- )} ${color.bold("Launch sequence initiated.")}`
304
+ ${label("astro", color.bgGreen, color.black)}${version ? " " + color.green(color.bold(`v${version}`)) : ""} ${color.bold("Launch sequence initiated.")}`
274
305
  );
275
- var info = async (prefix, text) => {
306
+ var bannerAbort = () => log(`
307
+ ${label("astro", color.bgRed)} ${color.bold("Launch sequence aborted.")}`);
308
+ var info = async (prefix, text2) => {
276
309
  await sleep(100);
277
310
  if (stdout.columns < 80) {
278
311
  log(`${" ".repeat(5)} ${color.cyan("\u25FC")} ${color.cyan(prefix)}`);
279
- log(`${" ".repeat(9)}${color.dim(text)}`);
312
+ log(`${" ".repeat(9)}${color.dim(text2)}`);
280
313
  } else {
281
- log(`${" ".repeat(5)} ${color.cyan("\u25FC")} ${color.cyan(prefix)} ${color.dim(text)}`);
314
+ log(`${" ".repeat(5)} ${color.cyan("\u25FC")} ${color.cyan(prefix)} ${color.dim(text2)}`);
282
315
  }
283
316
  };
284
- var error = async (prefix, text) => {
317
+ var error = async (prefix, text2) => {
285
318
  if (stdout.columns < 80) {
286
319
  log(`${" ".repeat(5)} ${color.red("\u25B2")} ${color.red(prefix)}`);
287
- log(`${" ".repeat(9)}${color.dim(text)}`);
320
+ log(`${" ".repeat(9)}${color.dim(text2)}`);
288
321
  } else {
289
- log(`${" ".repeat(5)} ${color.red("\u25B2")} ${color.red(prefix)} ${color.dim(text)}`);
322
+ log(`${" ".repeat(5)} ${color.red("\u25B2")} ${color.red(prefix)} ${color.dim(text2)}`);
290
323
  }
291
324
  };
292
325
  var typescriptByDefault = async () => {
@@ -355,7 +388,7 @@ function printHelp({
355
388
  if (headline) {
356
389
  message.push(
357
390
  linebreak(),
358
- `${title(commandName)} ${color.green(`v${"4.0.0-beta.0"}`)} ${headline}`
391
+ `${title(commandName)} ${color.green(`v${"4.0.0-rc.2"}`)} ${headline}`
359
392
  );
360
393
  }
361
394
  if (usage) {
@@ -455,7 +488,8 @@ async function getContext(argv) {
455
488
 
456
489
  // src/actions/dependencies.ts
457
490
  import { color as color2 } from "@astrojs/cli-kit";
458
- import { execa as execa2 } from "execa";
491
+ import fs from "node:fs";
492
+ import path from "node:path";
459
493
  async function dependencies(ctx) {
460
494
  let deps = ctx.install ?? ctx.yes;
461
495
  if (deps === void 0) {
@@ -495,21 +529,23 @@ async function dependencies(ctx) {
495
529
  }
496
530
  }
497
531
  async function install({ pkgManager, cwd }) {
498
- const installExec = execa2(pkgManager, ["install"], { cwd });
499
- return new Promise((resolve, reject) => {
500
- setTimeout(() => reject(`Request timed out after one minute`), 6e4);
501
- installExec.on("error", (e) => reject(e));
502
- installExec.on("close", () => resolve());
503
- });
532
+ if (pkgManager === "yarn")
533
+ await ensureYarnLock({ cwd });
534
+ return shell(pkgManager, ["install"], { cwd, timeout: 9e4, stdio: "ignore" });
535
+ }
536
+ async function ensureYarnLock({ cwd }) {
537
+ const yarnLock = path.join(cwd, "yarn.lock");
538
+ if (fs.existsSync(yarnLock))
539
+ return;
540
+ return fs.promises.writeFile(yarnLock, "", { encoding: "utf-8" });
504
541
  }
505
542
 
506
543
  // src/actions/git.ts
507
- import fs from "node:fs";
508
- import path from "node:path";
544
+ import fs2 from "node:fs";
545
+ import path2 from "node:path";
509
546
  import { color as color3 } from "@astrojs/cli-kit";
510
- import { execa as execa3 } from "execa";
511
547
  async function git(ctx) {
512
- if (fs.existsSync(path.join(ctx.cwd, ".git"))) {
548
+ if (fs2.existsSync(path2.join(ctx.cwd, ".git"))) {
513
549
  await info("Nice!", `Git has already been initialized`);
514
550
  return;
515
551
  }
@@ -544,9 +580,9 @@ async function git(ctx) {
544
580
  }
545
581
  async function init({ cwd }) {
546
582
  try {
547
- await execa3("git", ["init"], { cwd, stdio: "ignore" });
548
- await execa3("git", ["add", "-A"], { cwd, stdio: "ignore" });
549
- await execa3(
583
+ await shell("git", ["init"], { cwd, stdio: "ignore" });
584
+ await shell("git", ["add", "-A"], { cwd, stdio: "ignore" });
585
+ await shell(
550
586
  "git",
551
587
  [
552
588
  "commit",
@@ -594,7 +630,7 @@ async function intro(ctx) {
594
630
  "Welcome",
595
631
  "to",
596
632
  label2("astro", color4.bgGreen, color4.black),
597
- color4.green(`v${ctx.version}`) + ",",
633
+ (ctx.version ? color4.green(`v${ctx.version}`) : "") + ",",
598
634
  `${ctx.username}!`
599
635
  ],
600
636
  random(welcome)
@@ -606,10 +642,10 @@ async function intro(ctx) {
606
642
  }
607
643
 
608
644
  // src/actions/next-steps.ts
609
- import path2 from "node:path";
645
+ import path3 from "node:path";
610
646
  async function next(ctx) {
611
- let projectDir = path2.relative(process.cwd(), ctx.cwd);
612
- const devCmd = ctx.pkgManager === "npm" ? "npm run dev" : `${ctx.pkgManager} dev`;
647
+ let projectDir = path3.relative(process.cwd(), ctx.cwd);
648
+ const devCmd = ctx.pkgManager === "npm" ? "npm run dev" : ctx.pkgManager === "bun" ? "bun run dev" : `${ctx.pkgManager} dev`;
613
649
  await nextSteps({ projectDir, devCmd });
614
650
  if (!ctx.skipHouston) {
615
651
  await say(["Good luck out there, astronaut! \u{1F680}"]);
@@ -619,10 +655,10 @@ async function next(ctx) {
619
655
 
620
656
  // src/actions/project-name.ts
621
657
  import { color as color5, generateProjectName } from "@astrojs/cli-kit";
622
- import path3 from "node:path";
658
+ import path4 from "node:path";
623
659
 
624
660
  // src/actions/shared.ts
625
- import fs2 from "node:fs";
661
+ import fs3 from "node:fs";
626
662
  var VALID_PROJECT_DIRECTORY_SAFE_LIST = [
627
663
  ".DS_Store",
628
664
  ".git",
@@ -648,10 +684,10 @@ var VALID_PROJECT_DIRECTORY_SAFE_LIST = [
648
684
  /^yarn-error\.log/
649
685
  ];
650
686
  function isEmpty(dirPath) {
651
- if (!fs2.existsSync(dirPath)) {
687
+ if (!fs3.existsSync(dirPath)) {
652
688
  return true;
653
689
  }
654
- const conflicts = fs2.readdirSync(dirPath).filter((content) => {
690
+ const conflicts = fs3.readdirSync(dirPath).filter((content) => {
655
691
  return !VALID_PROJECT_DIRECTORY_SAFE_LIST.some((safeContent) => {
656
692
  return typeof safeContent === "string" ? content === safeContent : safeContent.test(content);
657
693
  });
@@ -694,7 +730,7 @@ async function projectName(ctx) {
694
730
  } else {
695
731
  let name = ctx.cwd;
696
732
  if (name === "." || name === "./") {
697
- const parts = process.cwd().split(path3.sep);
733
+ const parts = process.cwd().split(path4.sep);
698
734
  name = parts[parts.length - 1];
699
735
  } else if (name.startsWith("./") || name.startsWith("../")) {
700
736
  const parts = name.split("/");
@@ -718,8 +754,8 @@ async function checkCwd(cwd) {
718
754
  // src/actions/template.ts
719
755
  import { color as color6 } from "@astrojs/cli-kit";
720
756
  import { downloadTemplate } from "giget";
721
- import fs3 from "node:fs";
722
- import path4 from "node:path";
757
+ import fs4 from "node:fs";
758
+ import path5 from "node:path";
723
759
  async function template(ctx) {
724
760
  if (!ctx.template) {
725
761
  const { template: tmpl } = await ctx.prompt({
@@ -760,9 +796,9 @@ async function template(ctx) {
760
796
  }
761
797
  var FILES_TO_REMOVE = ["sandbox.config.json", "CHANGELOG.md"];
762
798
  var FILES_TO_UPDATE = {
763
- "package.json": (file, overrides) => fs3.promises.readFile(file, "utf-8").then((value) => {
799
+ "package.json": (file, overrides) => fs4.promises.readFile(file, "utf-8").then((value) => {
764
800
  const indent = /(^\s+)/m.exec(value)?.[1] ?? " ";
765
- fs3.promises.writeFile(
801
+ fs4.promises.writeFile(
766
802
  file,
767
803
  JSON.stringify(
768
804
  Object.assign(JSON.parse(value), Object.assign(overrides, { private: void 0 })),
@@ -774,11 +810,13 @@ var FILES_TO_UPDATE = {
774
810
  })
775
811
  };
776
812
  function getTemplateTarget(tmpl, ref = "latest") {
813
+ if (tmpl.startsWith("starlight")) {
814
+ const [, starter = "basics"] = tmpl.split("/");
815
+ return `withastro/starlight/examples/${starter}`;
816
+ }
777
817
  const isThirdParty = tmpl.includes("/");
778
818
  if (isThirdParty)
779
819
  return tmpl;
780
- if (tmpl === "starlight")
781
- return `withastro/starlight/examples/basics`;
782
820
  return `github:withastro/astro/examples/${tmpl}#${ref}`;
783
821
  }
784
822
  async function copyTemplate(tmpl, ctx) {
@@ -792,25 +830,25 @@ async function copyTemplate(tmpl, ctx) {
792
830
  dir: "."
793
831
  });
794
832
  } catch (err) {
795
- fs3.rmdirSync(ctx.cwd);
833
+ fs4.rmdirSync(ctx.cwd);
796
834
  if (err.message.includes("404")) {
797
835
  throw new Error(`Template ${color6.reset(tmpl)} ${color6.dim("does not exist!")}`);
798
836
  } else {
799
837
  throw new Error(err.message);
800
838
  }
801
839
  }
802
- if (fs3.readdirSync(ctx.cwd).length === 0) {
840
+ if (fs4.readdirSync(ctx.cwd).length === 0) {
803
841
  throw new Error(`Template ${color6.reset(tmpl)} ${color6.dim("is empty!")}`);
804
842
  }
805
843
  const removeFiles = FILES_TO_REMOVE.map(async (file) => {
806
- const fileLoc = path4.resolve(path4.join(ctx.cwd, file));
807
- if (fs3.existsSync(fileLoc)) {
808
- return fs3.promises.rm(fileLoc, { recursive: true });
844
+ const fileLoc = path5.resolve(path5.join(ctx.cwd, file));
845
+ if (fs4.existsSync(fileLoc)) {
846
+ return fs4.promises.rm(fileLoc, { recursive: true });
809
847
  }
810
848
  });
811
849
  const updateFiles = Object.entries(FILES_TO_UPDATE).map(async ([file, update]) => {
812
- const fileLoc = path4.resolve(path4.join(ctx.cwd, file));
813
- if (fs3.existsSync(fileLoc)) {
850
+ const fileLoc = path5.resolve(path5.join(ctx.cwd, file));
851
+ if (fs4.existsSync(fileLoc)) {
814
852
  return update(fileLoc, { name: ctx.projectName });
815
853
  }
816
854
  });
@@ -820,9 +858,9 @@ async function copyTemplate(tmpl, ctx) {
820
858
 
821
859
  // src/actions/typescript.ts
822
860
  import { color as color7 } from "@astrojs/cli-kit";
823
- import fs4 from "node:fs";
861
+ import fs5 from "node:fs";
824
862
  import { readFile } from "node:fs/promises";
825
- import path5 from "node:path";
863
+ import path6 from "node:path";
826
864
 
827
865
  // ../../node_modules/.pnpm/strip-json-comments@5.0.0/node_modules/strip-json-comments/index.js
828
866
  var singleComment = Symbol("singleComment");
@@ -942,7 +980,7 @@ async function typescript(ctx) {
942
980
  } else {
943
981
  if (!["strict", "strictest", "relaxed", "default", "base"].includes(ts)) {
944
982
  if (!ctx.dryRun) {
945
- fs4.rmSync(ctx.cwd, { recursive: true, force: true });
983
+ fs5.rmSync(ctx.cwd, { recursive: true, force: true });
946
984
  }
947
985
  error(
948
986
  "Error",
@@ -972,7 +1010,7 @@ async function typescript(ctx) {
972
1010
  }
973
1011
  }
974
1012
  async function setupTypeScript(value, { cwd }) {
975
- const templateTSConfigPath = path5.join(cwd, "tsconfig.json");
1013
+ const templateTSConfigPath = path6.join(cwd, "tsconfig.json");
976
1014
  try {
977
1015
  const data = await readFile(templateTSConfigPath, { encoding: "utf-8" });
978
1016
  const templateTSConfig = JSON.parse(stripJsonComments(data));
@@ -980,7 +1018,7 @@ async function setupTypeScript(value, { cwd }) {
980
1018
  const result = Object.assign(templateTSConfig, {
981
1019
  extends: `astro/tsconfigs/${value}`
982
1020
  });
983
- fs4.writeFileSync(templateTSConfigPath, JSON.stringify(result, null, 2));
1021
+ fs5.writeFileSync(templateTSConfigPath, JSON.stringify(result, null, 2));
984
1022
  } else {
985
1023
  throw new Error(
986
1024
  "There was an error applying the requested TypeScript settings. This could be because the template's tsconfig.json is malformed"
@@ -988,7 +1026,7 @@ async function setupTypeScript(value, { cwd }) {
988
1026
  }
989
1027
  } catch (err) {
990
1028
  if (err && err.code === "ENOENT") {
991
- fs4.writeFileSync(
1029
+ fs5.writeFileSync(
992
1030
  templateTSConfigPath,
993
1031
  JSON.stringify({ extends: `astro/tsconfigs/${value}` }, null, 2)
994
1032
  );
@@ -996,6 +1034,64 @@ async function setupTypeScript(value, { cwd }) {
996
1034
  }
997
1035
  }
998
1036
 
1037
+ // src/actions/verify.ts
1038
+ import { color as color8 } from "@astrojs/cli-kit";
1039
+ import fetch2 from "node-fetch-native";
1040
+ import dns from "node:dns/promises";
1041
+ async function verify(ctx) {
1042
+ if (!ctx.dryRun) {
1043
+ const online = await isOnline();
1044
+ if (!online) {
1045
+ bannerAbort();
1046
+ log("");
1047
+ error("error", `Unable to connect to the internet.`);
1048
+ ctx.exit(1);
1049
+ }
1050
+ }
1051
+ if (ctx.template) {
1052
+ const ok = await verifyTemplate(ctx.template, ctx.ref);
1053
+ if (!ok) {
1054
+ bannerAbort();
1055
+ log("");
1056
+ error("error", `Template ${color8.reset(ctx.template)} ${color8.dim("could not be found!")}`);
1057
+ await info("check", "https://astro.build/examples");
1058
+ ctx.exit(1);
1059
+ }
1060
+ }
1061
+ }
1062
+ function isOnline() {
1063
+ return dns.lookup("github.com").then(
1064
+ () => true,
1065
+ () => false
1066
+ );
1067
+ }
1068
+ async function verifyTemplate(tmpl, ref) {
1069
+ const target = getTemplateTarget(tmpl, ref);
1070
+ const { repo, subdir, ref: branch } = parseGitURI(target.replace("github:", ""));
1071
+ const url = new URL(`/repos/${repo}/contents${subdir}?ref=${branch}`, "https://api.github.com/");
1072
+ let res = await fetch2(url.toString(), {
1073
+ headers: {
1074
+ Accept: "application/vnd.github+json",
1075
+ "X-GitHub-Api-Version": "2022-11-28"
1076
+ }
1077
+ });
1078
+ if (res.status === 403) {
1079
+ res = await fetch2(`https://github.com/${repo}/tree/${branch}${subdir}`);
1080
+ }
1081
+ return res.status === 200;
1082
+ }
1083
+ var GIT_RE = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w.-]+)?/;
1084
+ function parseGitURI(input) {
1085
+ const m = input.match(GIT_RE)?.groups;
1086
+ if (!m)
1087
+ throw new Error(`Unable to parse "${input}"`);
1088
+ return {
1089
+ repo: m.repo,
1090
+ subdir: m.subdir || "/",
1091
+ ref: m.ref ? m.ref.slice(1) : "main"
1092
+ };
1093
+ }
1094
+
999
1095
  // src/index.ts
1000
1096
  var exit = () => process.exit(0);
1001
1097
  process.on("SIGINT", exit);
@@ -1008,6 +1104,7 @@ async function main() {
1008
1104
  return;
1009
1105
  }
1010
1106
  const steps = [
1107
+ verify,
1011
1108
  intro,
1012
1109
  projectName,
1013
1110
  template,
@@ -1033,5 +1130,6 @@ export {
1033
1130
  setStdout,
1034
1131
  setupTypeScript,
1035
1132
  template,
1036
- typescript
1133
+ typescript,
1134
+ verify
1037
1135
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-astro",
3
- "version": "4.0.0-beta.0",
3
+ "version": "4.0.0-rc.2",
4
4
  "type": "module",
5
5
  "author": "withastro",
6
6
  "license": "MIT",