summaryrefslogtreecommitdiff
path: root/packages/create-astro
diff options
context:
space:
mode:
Diffstat (limited to 'packages/create-astro')
-rw-r--r--packages/create-astro/CHANGELOG.md798
-rw-r--r--packages/create-astro/README.md63
-rwxr-xr-xpackages/create-astro/create-astro.mjs15
-rw-r--r--packages/create-astro/package.json48
-rw-r--r--packages/create-astro/src/actions/context.ts124
-rw-r--r--packages/create-astro/src/actions/dependencies.ts109
-rw-r--r--packages/create-astro/src/actions/git.ts64
-rw-r--r--packages/create-astro/src/actions/help.ts24
-rw-r--r--packages/create-astro/src/actions/intro.ts29
-rw-r--r--packages/create-astro/src/actions/next-steps.ts25
-rw-r--r--packages/create-astro/src/actions/project-name.ts74
-rw-r--r--packages/create-astro/src/actions/shared.ts59
-rw-r--r--packages/create-astro/src/actions/template.ts155
-rw-r--r--packages/create-astro/src/actions/verify.ts40
-rw-r--r--packages/create-astro/src/data/seasonal.ts112
-rw-r--r--packages/create-astro/src/index.ts62
-rw-r--r--packages/create-astro/src/messages.ts201
-rw-r--r--packages/create-astro/src/shell.ts51
-rw-r--r--packages/create-astro/test/context.test.js73
-rw-r--r--packages/create-astro/test/dependencies.test.js80
-rw-r--r--packages/create-astro/test/fixtures/empty/.gitkeep0
-rw-r--r--packages/create-astro/test/fixtures/not-empty/git.json1
-rw-r--r--packages/create-astro/test/fixtures/not-empty/package.json9
-rw-r--r--packages/create-astro/test/fixtures/not-empty/tsconfig.json1
-rw-r--r--packages/create-astro/test/git.test.js57
-rw-r--r--packages/create-astro/test/integrations.test.js64
-rw-r--r--packages/create-astro/test/intro.test.js20
-rw-r--r--packages/create-astro/test/next.test.js20
-rw-r--r--packages/create-astro/test/project-name.test.js124
-rw-r--r--packages/create-astro/test/template.test.js39
-rw-r--r--packages/create-astro/test/utils.js63
-rw-r--r--packages/create-astro/test/verify.test.js41
-rw-r--r--packages/create-astro/tsconfig.json7
33 files changed, 2652 insertions, 0 deletions
diff --git a/packages/create-astro/CHANGELOG.md b/packages/create-astro/CHANGELOG.md
new file mode 100644
index 000000000..9ab723ac5
--- /dev/null
+++ b/packages/create-astro/CHANGELOG.md
@@ -0,0 +1,798 @@
+# create-astro
+
+## 4.11.0
+
+### Minor Changes
+
+- [#12539](https://github.com/withastro/astro/pull/12539) [`827093e`](https://github.com/withastro/astro/commit/827093e6175549771f9d93ddf3f2be4c2c60f0b7) Thanks [@bluwy](https://github.com/bluwy)! - Drops node 21 support
+
+- [#12083](https://github.com/withastro/astro/pull/12083) [`9263e96`](https://github.com/withastro/astro/commit/9263e965932b9a6a116801c063c6b7105c39643e) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Reworks the experience of creating a new Astro project using the `create astro` CLI command.
+
+ - Updates the list of templates to include Starlight and combines the "minimal" and "basics" templates into a new, refreshed "Basics" template to serve as the single, minimal Astro project starter.
+ - Removes the TypeScript question. Astro is TypeScript-only, so this question was often misleading. The "Strict" preset is now the default, but it can still be changed manually in `tsconfig.json`.
+ - `astro check` is no longer automatically added to the build script.
+ - Added a new `--add` flag to install additional integrations after creating a project. For example, `pnpm create astro --add react` will create a new Astro project and install the React integration.
+
+## 4.11.0-beta.1
+
+### Minor Changes
+
+- [#12539](https://github.com/withastro/astro/pull/12539) [`827093e`](https://github.com/withastro/astro/commit/827093e6175549771f9d93ddf3f2be4c2c60f0b7) Thanks [@bluwy](https://github.com/bluwy)! - Drops node 21 support
+
+## 4.11.0-beta.0
+
+### Minor Changes
+
+- [#12083](https://github.com/withastro/astro/pull/12083) [`9263e96`](https://github.com/withastro/astro/commit/9263e965932b9a6a116801c063c6b7105c39643e) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Reworks the experience of creating a new Astro project using the `create astro` CLI command.
+
+ - Updates the list of templates to include Starlight and combines the "minimal" and "basics" templates into a new, refreshed "Basics" template to serve as the single, minimal Astro project starter.
+ - Removes the TypeScript question. Astro is TypeScript-only, so this question was often misleading. The "Strict" preset is now the default, but it can still be changed manually in `tsconfig.json`.
+ - `astro check` is no longer automatically added to the build script.
+ - Added a new `--add` flag to install additional integrations after creating a project. For example, `pnpm create astro --add react` will create a new Astro project and install the React integration.
+
+## 4.10.0
+
+### Minor Changes
+
+- [#12154](https://github.com/withastro/astro/pull/12154) [`9988dd6`](https://github.com/withastro/astro/commit/9988dd67e2e4647c974979470d2e63d80433b611) Thanks [@bluwy](https://github.com/bluwy)! - Improves default template download speed by downloading from a branch containing the template only
+
+- [#12186](https://github.com/withastro/astro/pull/12186) [`49c4f64`](https://github.com/withastro/astro/commit/49c4f64673390f7035258d662755988f0fb71a94) Thanks [@Terfno](https://github.com/Terfno)! - Ensures new line at the end of the generated `package.json` and `tsconfig.json` files
+
+## 4.9.2
+
+### Patch Changes
+
+- [#12143](https://github.com/withastro/astro/pull/12143) [`2385d58`](https://github.com/withastro/astro/commit/2385d58389ee975a53f4089f2a7220d97cf3cdff) Thanks [@bluwy](https://github.com/bluwy)! - Uses `@bluwy/giget-core` instead of `giget` for smaller installation size when downloading the CLI
+
+## 4.9.1
+
+### Patch Changes
+
+- [#12118](https://github.com/withastro/astro/pull/12118) [`f47b347`](https://github.com/withastro/astro/commit/f47b347da899c6e1dcd0b2e7887f7fce6ec8e270) Thanks [@Namchee](https://github.com/Namchee)! - Removes the `strip-ansi` dependency in favor of the native Node API
+
+## 4.9.0
+
+### Minor Changes
+
+- [#11924](https://github.com/withastro/astro/pull/11924) [`7d70ba3`](https://github.com/withastro/astro/commit/7d70ba317889b9281c7891038779a68fcb8f0778) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Updates the default Astro config with `// @ts-check` if the Typescript preset is `strict` or `strictest`
+
+## 4.8.4
+
+### Patch Changes
+
+- [#11766](https://github.com/withastro/astro/pull/11766) [`d12dcbf`](https://github.com/withastro/astro/commit/d12dcbff606dd8330075ba77d73ed3cbc79d7421) Thanks [@bluwy](https://github.com/bluwy)! - Fixes initial git commit when initializing git
+
+## 4.8.3
+
+### Patch Changes
+
+- [#11733](https://github.com/withastro/astro/pull/11733) [`391324d`](https://github.com/withastro/astro/commit/391324df969db71d1c7ca25c2ed14c9eb6eea5ee) Thanks [@bluwy](https://github.com/bluwy)! - Reverts back to `arg` package for CLI argument parsing
+
+## 4.8.2
+
+### Patch Changes
+
+- [#11645](https://github.com/withastro/astro/pull/11645) [`849e4c6`](https://github.com/withastro/astro/commit/849e4c6c23e61f7fa59f583419048b998bef2475) Thanks [@bluwy](https://github.com/bluwy)! - Refactors internally to use `node:util` `parseArgs` instead of `arg`
+
+## 4.8.1
+
+### Patch Changes
+
+- [#11567](https://github.com/withastro/astro/pull/11567) [`d27cf6d`](https://github.com/withastro/astro/commit/d27cf6df7bd612642a1e8da5948333b00b70e8bd) Thanks [@ascorbic](https://github.com/ascorbic)! - Logs underlying error when a template cannot be downloaded
+
+## 4.8.0
+
+### Minor Changes
+
+- [#10689](https://github.com/withastro/astro/pull/10689) [`683d51a5eecafbbfbfed3910a3f1fbf0b3531b99`](https://github.com/withastro/astro/commit/683d51a5eecafbbfbfed3910a3f1fbf0b3531b99) Thanks [@ematipico](https://github.com/ematipico)! - Deprecate support for versions of Node.js older than `v18.17.1` for Node.js 18, older than `v20.0.3` for Node.js 20, and the complete Node.js v19 release line.
+
+ This change is in line with Astro's [Node.js support policy](https://docs.astro.build/en/upgrade-astro/#support).
+
+## 4.7.5
+
+### Patch Changes
+
+- [#10487](https://github.com/withastro/astro/pull/10487) [`2330f22d6cf8cd150c19ec40359aed4d6b43ddec`](https://github.com/withastro/astro/commit/2330f22d6cf8cd150c19ec40359aed4d6b43ddec) Thanks [@satyarohith](https://github.com/satyarohith)! - Fixes a case where a promise wasn't awaited, causing an issue in Deno.
+
+## 4.7.4
+
+### Patch Changes
+
+- [#10255](https://github.com/withastro/astro/pull/10255) [`2aec2cdc21f48f9b4f1dd82e2fd16fa3d653ccc5`](https://github.com/withastro/astro/commit/2aec2cdc21f48f9b4f1dd82e2fd16fa3d653ccc5) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Fixes an issue where TypeScript and `@astrojs/check` versions would occasionally print as `undefined`.
+
+## 4.7.3
+
+### Patch Changes
+
+- [#10117](https://github.com/withastro/astro/pull/10117) [`51b6ff7403c1223b1c399e88373075972c82c24c`](https://github.com/withastro/astro/commit/51b6ff7403c1223b1c399e88373075972c82c24c) Thanks [@hippotastic](https://github.com/hippotastic)! - Fixes an issue where `create astro`, `astro add` and `@astrojs/upgrade` would fail due to unexpected package manager CLI output.
+
+## 4.7.2
+
+### Patch Changes
+
+- [#9813](https://github.com/withastro/astro/pull/9813) [`fecba30a1abb7ca65dfb8f506dde77117fa447d1`](https://github.com/withastro/astro/commit/fecba30a1abb7ca65dfb8f506dde77117fa447d1) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Fixes `@astrojs/check` and `typescript` addition to `package.json` dependencies when the user has decided not to auto-install dependencies
+
+## 4.7.1
+
+### Patch Changes
+
+- [#9476](https://github.com/withastro/astro/pull/9476) [`651f45b4010ad9b8d9f61fdc748618e220fe5375`](https://github.com/withastro/astro/commit/651f45b4010ad9b8d9f61fdc748618e220fe5375) Thanks [@ElianCodes](https://github.com/ElianCodes)! - Improves seasonal message handling by automatically detecting the local date
+
+## 4.7.0
+
+### Minor Changes
+
+- [#9470](https://github.com/withastro/astro/pull/9470) [`607303be198931825dac9f3bc97867b4886feaf3`](https://github.com/withastro/astro/commit/607303be198931825dac9f3bc97867b4886feaf3) Thanks [@onsclom](https://github.com/onsclom)! - Improves the `create astro` CLI experience by asking all the questions upfront, then creating your new Astro project based on your responses.
+
+## 4.6.0
+
+### Minor Changes
+
+- [#9358](https://github.com/withastro/astro/pull/9358) [`35e4c17fe`](https://github.com/withastro/astro/commit/35e4c17fe245179dd88af679a5f0a08785b7bfef) Thanks [@xiBread](https://github.com/xiBread)! - feat: make Houston festive for the holiday season
+
+## 4.5.2
+
+### Patch Changes
+
+- [#9105](https://github.com/withastro/astro/pull/9105) [`6201bbe96`](https://github.com/withastro/astro/commit/6201bbe96c2a083fb201e4a43a9bd88499821a3e) Thanks [@FredKSchott](https://github.com/FredKSchott)! - Stop clearing the console on start
+
+## 4.5.2-beta.0
+
+### Patch Changes
+
+- [#9105](https://github.com/withastro/astro/pull/9105) [`6201bbe96`](https://github.com/withastro/astro/commit/6201bbe96c2a083fb201e4a43a9bd88499821a3e) Thanks [@FredKSchott](https://github.com/FredKSchott)! - Stop clearing the console on start
+
+## 4.5.1
+
+### Patch Changes
+
+- [#9048](https://github.com/withastro/astro/pull/9048) [`1e97708cd`](https://github.com/withastro/astro/commit/1e97708cda779510d638abaefdb4abf707b697e3) Thanks [@skirianov](https://github.com/skirianov)! - Fixes an issue where a successful "Dependencies installed" message is displayed even when installing dependencies fails.
+
+## 4.5.0
+
+### Minor Changes
+
+- [#8959](https://github.com/withastro/astro/pull/8959) [`6169b6e56`](https://github.com/withastro/astro/commit/6169b6e56152b1ba944416d85551994788cfb887) Thanks [@ElianCodes](https://github.com/ElianCodes)! - Undo the halloween theme and restore `fancy` behaviour
+
+### Patch Changes
+
+- [#8939](https://github.com/withastro/astro/pull/8939) [`71455c16c`](https://github.com/withastro/astro/commit/71455c16c371aaca4b5a551b713a77c6820efd78) Thanks [@wktk](https://github.com/wktk)! - Fixes TypeScript installation issue with yarn
+
+## 4.4.1
+
+### Patch Changes
+
+- [#8911](https://github.com/withastro/astro/pull/8911) [`b236d88ad`](https://github.com/withastro/astro/commit/b236d88addc48d784bd60119fe45750dda900f16) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Ensure an existing template's `package.json` `scripts` are respected when modifying `build`.
+
+## 4.4.0
+
+### Minor Changes
+
+- [#8853](https://github.com/withastro/astro/pull/8853) [`ce807a2bf`](https://github.com/withastro/astro/commit/ce807a2bfef325683bfdb01065a73c4e2b0a5fe5) Thanks [@rayriffy](https://github.com/rayriffy)! - Automatically installs the required dependencies to run the astro check command when the user indicates they plan to write TypeScript.
+
+### Patch Changes
+
+- [#8841](https://github.com/withastro/astro/pull/8841) [`f2dd895d7`](https://github.com/withastro/astro/commit/f2dd895d71e0fccfbc1b98890ceefb69f32524d5) Thanks [@Genteure](https://github.com/Genteure)! - No longer attempts to delete the directory after a template download fails if the path is `.`, `./` or starts with `../`.
+
+## 4.3.0
+
+### Minor Changes
+
+- [#8846](https://github.com/withastro/astro/pull/8846) [`3baab3d93`](https://github.com/withastro/astro/commit/3baab3d93b8d16517cb089b0fa2c4028f21e780f) Thanks [@ElianCodes](https://github.com/ElianCodes)! - feat: make Houston wear scary hats and say new things for spooky season
+
+## 4.2.1
+
+### Patch Changes
+
+- [#8634](https://github.com/withastro/astro/pull/8634) [`b64dd45c0`](https://github.com/withastro/astro/commit/b64dd45c0d641f9f2ed997e2cbdf8a6b0193195f) Thanks [@TheOtterlord](https://github.com/TheOtterlord)! - Fix `--yes` behaviour to prevent it overriding `--template`
+
+## 4.2.0
+
+### Minor Changes
+
+- [#8551](https://github.com/withastro/astro/pull/8551) [`1d5b3f079`](https://github.com/withastro/astro/commit/1d5b3f079d0b4aa5a5c46f97b8b724ab88497fbe) Thanks [@jacobthesheep](https://github.com/jacobthesheep)! - Adds `--yes` and `dry-run` flags to project-name and the `yes` flag to template.
+
+## 4.1.0
+
+### Minor Changes
+
+- [#8456](https://github.com/withastro/astro/pull/8456) [`ed952b4ce`](https://github.com/withastro/astro/commit/ed952b4cea6f60a4e158a5b20cc36f5e91a6b07f) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve startup performance by removing dependencies, lazily initializing async contextual values
+
+## 4.0.2
+
+### Patch Changes
+
+- [#8427](https://github.com/withastro/astro/pull/8427) [`b81ff8fce`](https://github.com/withastro/astro/commit/b81ff8fcefe6c30312d7b2050a63b1520d79b25f) Thanks [@aswind7](https://github.com/aswind7)! - trim project name of the user input
+
+- [#8306](https://github.com/withastro/astro/pull/8306) [`d2f2a11cd`](https://github.com/withastro/astro/commit/d2f2a11cdb42b0de79be21c798eda8e7e7b2a277) Thanks [@jacobthesheep](https://github.com/jacobthesheep)! - Support detecting Bun when logging messages with package manager information.
+
+## 4.0.1
+
+### Patch Changes
+
+- [#8292](https://github.com/withastro/astro/pull/8292) [`4e88ffd81`](https://github.com/withastro/astro/commit/4e88ffd813a3a9fa37b2ddd1a2eff181d4a99c0f) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Correctly remove new `.codesandbox` folder when copying template
+
+## 4.0.0
+
+### Major Changes
+
+- [#8188](https://github.com/withastro/astro/pull/8188) [`d0679a666`](https://github.com/withastro/astro/commit/d0679a666f37da0fca396d42b9b32bbb25d29312) Thanks [@ematipico](https://github.com/ematipico)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023.
+
+- [#8179](https://github.com/withastro/astro/pull/8179) [`6011d52d3`](https://github.com/withastro/astro/commit/6011d52d38e43c3e3d52bc3bc41a60e36061b7b7) Thanks [@matthewp](https://github.com/matthewp)! - Astro 3.0 Release Candidate
+
+## 4.0.0-rc.2
+
+### Major Changes
+
+- [#8179](https://github.com/withastro/astro/pull/8179) [`6011d52d3`](https://github.com/withastro/astro/commit/6011d52d38e43c3e3d52bc3bc41a60e36061b7b7) Thanks [@matthewp](https://github.com/matthewp)! - Astro 3.0 Release Candidate
+
+## 4.0.0-beta.1
+
+### Patch Changes
+
+- [#7944](https://github.com/withastro/astro/pull/7944) [`dff0f0f8d`](https://github.com/withastro/astro/commit/dff0f0f8ddd531c5d92a90ac00fdb86d71f77509) Thanks [@colinhacks](https://github.com/colinhacks)! - Update 'dev' command for Bun users
+
+- [#8102](https://github.com/withastro/astro/pull/8102) [`e6e1de4f0`](https://github.com/withastro/astro/commit/e6e1de4f08ddba3a7703136a81f275de1976dc9e) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Verify internet connection and that `--template` exists before continuing
+
+## 4.0.0-beta.0
+
+### Major Changes
+
+- [`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023.
+
+## 3.2.2
+
+### Patch Changes
+
+- [#7944](https://github.com/withastro/astro/pull/7944) [`dff0f0f8d`](https://github.com/withastro/astro/commit/dff0f0f8ddd531c5d92a90ac00fdb86d71f77509) Thanks [@colinhacks](https://github.com/colinhacks)! - Update 'dev' command for Bun users
+
+- [#8102](https://github.com/withastro/astro/pull/8102) [`e6e1de4f0`](https://github.com/withastro/astro/commit/e6e1de4f08ddba3a7703136a81f275de1976dc9e) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Verify internet connection and that `--template` exists before continuing
+
+## 3.2.1
+
+### Patch Changes
+
+- [#8089](https://github.com/withastro/astro/pull/8089) [`04755e846`](https://github.com/withastro/astro/commit/04755e84658ea10914a09f3d07f302267326d610) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Fix install step to avoid uncaught errors
+
+## 3.2.0
+
+### Minor Changes
+
+- [#8077](https://github.com/withastro/astro/pull/8077) [`44cf30a25`](https://github.com/withastro/astro/commit/44cf30a25209b331e6e8a95a4b40a768ede3604a) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Reduce dependency installation size, swap `execa` for light `node:child_process` wrapper
+
+## 3.1.13
+
+### Patch Changes
+
+- [#8028](https://github.com/withastro/astro/pull/8028) [`8292c4131`](https://github.com/withastro/astro/commit/8292c41311ec41d9d50921fbb2bdeed69e039443) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve yarn berry support
+
+## 3.1.12
+
+### Patch Changes
+
+- [#7993](https://github.com/withastro/astro/pull/7993) [`315d58f27`](https://github.com/withastro/astro/commit/315d58f27b022c9d4285cf13f445ed18c26c327e) Thanks [@delucis](https://github.com/delucis)! - Add support for more Starlight templates
+
+## 3.1.11
+
+### Patch Changes
+
+- [#7939](https://github.com/withastro/astro/pull/7939) [`89cd4b877`](https://github.com/withastro/astro/commit/89cd4b877e870ce4a263dd45f42f818fd2c4d5a6) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Handle error state for version number
+
+## 3.1.10
+
+### Patch Changes
+
+- [#7580](https://github.com/withastro/astro/pull/7580) [`2ca5bdde2`](https://github.com/withastro/astro/commit/2ca5bdde2b1acc2be1586a99686a9a48cdef65dc) Thanks [@sankethchebbi](https://github.com/sankethchebbi)! - Update dependency installation grammar
+
+## 3.1.9
+
+### Patch Changes
+
+- [#7527](https://github.com/withastro/astro/pull/7527) [`9e2426f75`](https://github.com/withastro/astro/commit/9e2426f75637a6318961f483de90b635f3fdadeb) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Default registry logic to fallback to NPM if registry command fails (sorry, Bun users!)
+
+- [#7539](https://github.com/withastro/astro/pull/7539) [`1170877b5`](https://github.com/withastro/astro/commit/1170877b51aaa13203e8c488dcf4e39d1b5553ee) Thanks [@jc1144096387](https://github.com/jc1144096387)! - Update registry logic, improving edge cases (http support, redirects, registries ending with '/')
+
+## 3.1.8
+
+### Patch Changes
+
+- [#7435](https://github.com/withastro/astro/pull/7435) [`3f9f5c117`](https://github.com/withastro/astro/commit/3f9f5c117e4e9e4a0c0a648cb6db9a3073cd5727) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix registry failures using unexpected package managers when running create-astro
+
+## 3.1.7
+
+### Patch Changes
+
+- [#7326](https://github.com/withastro/astro/pull/7326) [`1430ffb47`](https://github.com/withastro/astro/commit/1430ffb4734edbb67cbeaaee7e89a9f78e00473c) Thanks [@calebdwilliams](https://github.com/calebdwilliams)! - Ensure create-astro respects package manager registry configuration
+
+## 3.1.6
+
+### Patch Changes
+
+- [#7277](https://github.com/withastro/astro/pull/7277) [`229affca4`](https://github.com/withastro/astro/commit/229affca405ce77bf80bcea6a91891f689a3161b) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Add `starlight` template alias
+
+## 3.1.5
+
+### Patch Changes
+
+- [#7086](https://github.com/withastro/astro/pull/7086) [`c5f1275e9`](https://github.com/withastro/astro/commit/c5f1275e9d2f212a08e56bc25e0b59c7d7e9f11d) Thanks [@MoustaphaDev](https://github.com/MoustaphaDev)! - Fix create astro regression
+
+## 3.1.4
+
+### Patch Changes
+
+- [#7052](https://github.com/withastro/astro/pull/7052) [`8c14bffbd`](https://github.com/withastro/astro/commit/8c14bffbd9ea63bc4b4e9f9417352fdf4e7e65b4) Thanks [@ematipico](https://github.com/ematipico)! - Don't exit if dependencies fail to install
+
+## 3.1.3
+
+### Patch Changes
+
+- [#6682](https://github.com/withastro/astro/pull/6682) [`335602344`](https://github.com/withastro/astro/commit/33560234437647f2d768578e7b285c858bff7898) Thanks [@andremralves](https://github.com/andremralves)! - add validation for non-printable characters
+
+## 3.1.2
+
+### Patch Changes
+
+- [#6677](https://github.com/withastro/astro/pull/6677) [`4a3262060`](https://github.com/withastro/astro/commit/4a32620600966ea89ddb5e1669d89a53e85ccf9a) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix: Log an error when passing a `--template` that does not exist
+
+## 3.1.1
+
+### Patch Changes
+
+- [#6594](https://github.com/withastro/astro/pull/6594) [`a661907b4`](https://github.com/withastro/astro/commit/a661907b40e76aa56e7d7bd7e745bb16456b13e7) Thanks [@btea](https://github.com/btea)! - wrap `projectDir` in quotes if it contains spaces
+
+## 3.1.0
+
+### Minor Changes
+
+- [#6213](https://github.com/withastro/astro/pull/6213) [`afbbc4d5b`](https://github.com/withastro/astro/commit/afbbc4d5bfafc1779bac00b41c2a1cb1c90f2808) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Updated compilation settings to disable downlevelling for Node 14
+
+## 3.0.5
+
+### Patch Changes
+
+- [#6375](https://github.com/withastro/astro/pull/6375) [`754c5ca9a`](https://github.com/withastro/astro/commit/754c5ca9aa93d4e8674059ce79f6b694c147db83) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Respect original `package.json` indentation
+
+## 3.0.4
+
+### Patch Changes
+
+- [#6352](https://github.com/withastro/astro/pull/6352) [`c87c16cfa`](https://github.com/withastro/astro/commit/c87c16cfaddea3a05af87c3258d57ef1a31516f7) Thanks [@SerekKiri](https://github.com/SerekKiri)! - Add missing flags to help command
+
+## 3.0.3
+
+### Patch Changes
+
+- [#6314](https://github.com/withastro/astro/pull/6314) [`7f61e8fe3`](https://github.com/withastro/astro/commit/7f61e8fe36b62a1833180c18b6f4304e9a01fce4) Thanks [@MilesPernicious](https://github.com/MilesPernicious)! - Prompt for git initialization last, so all configurations can get added to the initial commit
+
+- [#6294](https://github.com/withastro/astro/pull/6294) [`d0dbee872`](https://github.com/withastro/astro/commit/d0dbee872fd09800fba644ccbf4011ce01149706) Thanks [@liruifengv](https://github.com/liruifengv)! - `create-astro` help info add `--typescript` flag
+
+## 3.0.2
+
+### Patch Changes
+
+- [#6278](https://github.com/withastro/astro/pull/6278) [`0f5d122cd`](https://github.com/withastro/astro/commit/0f5d122cd538b65ec7208ddae5e60cfaddaf4b2c) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Revert to giget 1.0.0 until upstream issue is fixed
+
+## 3.0.1
+
+### Patch Changes
+
+- [#6266](https://github.com/withastro/astro/pull/6266) [`066b4b4ef`](https://github.com/withastro/astro/commit/066b4b4efcde2320d29040c5bd385c67f30c701a) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve error handling during tasks that display a spinner
+
+## 3.0.0
+
+### Major Changes
+
+- [#6082](https://github.com/withastro/astro/pull/6082) [`8d2187d8b`](https://github.com/withastro/astro/commit/8d2187d8b8587b2a3a0207d9ffa8667c43686436) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Redesigned `create-astro` experience
+
+## 2.0.2
+
+### Patch Changes
+
+- [#5953](https://github.com/withastro/astro/pull/5953) [`5c64324c0`](https://github.com/withastro/astro/commit/5c64324c0a1b06e836c3d53668940faca4cb517d) Thanks [@ZermattChris](https://github.com/ZermattChris)! - Check for a pre-existing .git directory and if found, skip trying to create a new one.
+
+## 2.0.1
+
+### Patch Changes
+
+- [#5958](https://github.com/withastro/astro/pull/5958) [`d0d7f6118`](https://github.com/withastro/astro/commit/d0d7f6118299bf328de5abd0b66450d8ac620da3) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Fix typescript prompt handling
+
+## 2.0.0
+
+### Major Changes
+
+- [#5782](https://github.com/withastro/astro/pull/5782) [`1f92d64ea`](https://github.com/withastro/astro/commit/1f92d64ea35c03fec43aff64eaf704dc5a9eb30a) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 14. Minimum supported Node version is now >=16.12.0
+
+### Patch Changes
+
+- [#5898](https://github.com/withastro/astro/pull/5898) [`d8919b1a2`](https://github.com/withastro/astro/commit/d8919b1a2197616b70ec57f0fb00b0bde6943e43) Thanks [@TheOtterlord](https://github.com/TheOtterlord)! - Support headless runs with `-y` / `--yes`
+
+- [#5920](https://github.com/withastro/astro/pull/5920) [`f27bb3d79`](https://github.com/withastro/astro/commit/f27bb3d79f9774f01037e60e656b1f9d8e03367d) Thanks [@delucis](https://github.com/delucis)! - Improve error message for third-party template 404s
+
+## 2.0.0-beta.1
+
+<details>
+<summary>See changes in 2.0.0-beta.1</summary>
+
+### Patch Changes
+
+- [#5898](https://github.com/withastro/astro/pull/5898) [`d8919b1a2`](https://github.com/withastro/astro/commit/d8919b1a2197616b70ec57f0fb00b0bde6943e43) Thanks [@TheOtterlord](https://github.com/TheOtterlord)! - Support headless runs with `-y` / `--yes`
+
+- [#5920](https://github.com/withastro/astro/pull/5920) [`f27bb3d79`](https://github.com/withastro/astro/commit/f27bb3d79f9774f01037e60e656b1f9d8e03367d) Thanks [@delucis](https://github.com/delucis)! - Improve error message for third-party template 404s
+
+</details>
+
+## 2.0.0-beta.0
+
+<details>
+<summary>See changes in 2.0.0-beta.0</summary>
+
+### Major Changes
+
+- [#5782](https://github.com/withastro/astro/pull/5782) [`1f92d64ea`](https://github.com/withastro/astro/commit/1f92d64ea35c03fec43aff64eaf704dc5a9eb30a) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 14. Minimum supported Node version is now >=16.12.0
+
+</details>
+
+## 1.2.4
+
+### Patch Changes
+
+- [#5579](https://github.com/withastro/astro/pull/5579) [`2c2c65297`](https://github.com/withastro/astro/commit/2c2c65297a18c52691f09621ead55144efd601d4) Thanks [@yuhang-dong](https://github.com/yuhang-dong)! - Upgrade giget to support env proxy config
+
+- [#5616](https://github.com/withastro/astro/pull/5616) [`61302ab7a`](https://github.com/withastro/astro/commit/61302ab7a09cc4c298c903d725e35355eb069497) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Skip Houston on Windows until we can debug the prompt issue
+
+## 1.2.3
+
+### Patch Changes
+
+- [#5404](https://github.com/withastro/astro/pull/5404) [`505abfd64`](https://github.com/withastro/astro/commit/505abfd6430b1f71e52d10b02bf9beb5847df8b6) Thanks [@liruifengv](https://github.com/liruifengv)! - fix error when don't have template input
+
+## 1.2.2
+
+### Patch Changes
+
+- [#5319](https://github.com/withastro/astro/pull/5319) [`b211eadef`](https://github.com/withastro/astro/commit/b211eadeffd6260700254c1492c8e6528d279ad1) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Fix bug with `setRawMode`. Respect `--skip-houston` in all cases.
+
+## 1.2.1
+
+### Patch Changes
+
+- [#5240](https://github.com/withastro/astro/pull/5240) [`d9be7e36b`](https://github.com/withastro/astro/commit/d9be7e36b872eb48516dc9d0d5c9d333aac4950b) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve error messages when `create-astro` fails
+
+- [#5226](https://github.com/withastro/astro/pull/5226) [`641b6d7d5`](https://github.com/withastro/astro/commit/641b6d7d583886fde9529f296846d7e0a50e8624) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Allow Windows users to pass `--fancy` to enable full unicode support
+
+## 1.2.0
+
+### Minor Changes
+
+- [#5088](https://github.com/withastro/astro/pull/5088) [`040837628`](https://github.com/withastro/astro/commit/04083762810a1a9e078a7e68edab945c8063b1ab) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Introducing your new automated assistant: Houston! ๐ŸŽ‰
+
+ ```
+ โ•ญโ”€โ”€โ”€โ”€โ”€โ•ฎ Houston:
+ โ”‚ โ—  โ—ก โ—  Initiating launch sequence... right... now!
+ โ•ฐโ”€โ”€โ”€โ”€โ”€โ•ฏ
+ ```
+
+ Updates template and TypeScript prompts for clarity and friendliness.
+
+ Migrates template copying from [`degit`](https://github.com/Rich-Harris/degit) (unmaintained) to [`giget`](https://github.com/unjs/giget) for stability.
+
+## 1.1.0
+
+### Minor Changes
+
+- [#4810](https://github.com/withastro/astro/pull/4810) [`7481ffda0`](https://github.com/withastro/astro/commit/7481ffda028d9028d8e28bc7c6e9960ab80acf0f) Thanks [@mrienstra](https://github.com/mrienstra)! - Always write chosen config to `tsconfig.json`.
+
+ - Before: Only when `strict` & `strictest` was selected
+ - After: Also when `base` is selected (via "Relaxed" or "I prefer not to use TypeScript")
+
+## 1.0.2
+
+### Patch Changes
+
+- [#4805](https://github.com/withastro/astro/pull/4805) [`c84d85ba4`](https://github.com/withastro/astro/commit/c84d85ba4d85f250d87bbc98c74665992f6c2768) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Add support for running in cloned empty git repository
+
+## 1.0.1
+
+### Patch Changes
+
+- [#4439](https://github.com/withastro/astro/pull/4439) [`77ce6be30`](https://github.com/withastro/astro/commit/77ce6be30c9cb8054ebf69a4943b984eed90152e) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add tsconfig templates for users to extend from
+
+## 1.0.1-next.0
+
+### Patch Changes
+
+- [#4439](https://github.com/withastro/astro/pull/4439) [`77ce6be30`](https://github.com/withastro/astro/commit/77ce6be30c9cb8054ebf69a4943b984eed90152e) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add tsconfig templates for users to extend from
+
+## 1.0.0
+
+### Major Changes
+
+- [`04ad44563`](https://github.com/withastro/astro/commit/04ad445632c67bdd60c1704e1e0dcbcaa27b9308) - > Astro v1.0 is out! Read the [official announcement post](https://astro.build/blog/astro-1/).
+
+ **No breaking changes**. This package is now officially stable and compatible with `astro@1.0.0`!
+
+## 0.15.1
+
+### Patch Changes
+
+- [#4183](https://github.com/withastro/astro/pull/4183) [`77c018e51`](https://github.com/withastro/astro/commit/77c018e5159e9084304ca650487b6e99c828d3cf) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fix relaxed and default TypeScript settings not working
+
+## 0.15.0
+
+### Minor Changes
+
+- [#4179](https://github.com/withastro/astro/pull/4179) [`d344f9e3e`](https://github.com/withastro/astro/commit/d344f9e3ec1f69ad4d7efd433b3523ad5413b726) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add a step to configure how strict TypeScript should be
+
+## 0.14.3
+
+### Patch Changes
+
+- [#4075](https://github.com/withastro/astro/pull/4075) [`cc10a5c8e`](https://github.com/withastro/astro/commit/cc10a5c8e03683e64514de75e535169c187ab847) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Added better error handling when cancelling operations, providing bad templates and when there's a degit cache issue
+
+## 0.14.2
+
+### Patch Changes
+
+- [#3971](https://github.com/withastro/astro/pull/3971) [`e6e216061`](https://github.com/withastro/astro/commit/e6e2160614c9af320419a599c42211d0147760f4) Thanks [@tony-sull](https://github.com/tony-sull)! - Fixes support for using templates from any GitHub repository
+
+## 0.14.1
+
+### Patch Changes
+
+- [#3937](https://github.com/withastro/astro/pull/3937) [`31f9c0bf0`](https://github.com/withastro/astro/commit/31f9c0bf029ffa4b470e620f2c32e1370643e81e) Thanks [@delucis](https://github.com/delucis)! - Roll back supported Node engines
+
+## 0.14.0
+
+### Minor Changes
+
+- [#3914](https://github.com/withastro/astro/pull/3914) [`b48767985`](https://github.com/withastro/astro/commit/b48767985359bd359df8071324952ea5f2bc0d86) Thanks [@ran-dall](https://github.com/ran-dall)! - Rollback supported `node@16` version. Minimum versions are now `node@14.20.0` or `node@16.14.0`.
+
+## 0.13.0
+
+### Minor Changes
+
+- [#3871](https://github.com/withastro/astro/pull/3871) [`1cc5b7890`](https://github.com/withastro/astro/commit/1cc5b78905633608e5b07ad291f916f54e67feb1) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Update supported `node` versions. Minimum versions are now `node@14.20.0` or `node@16.16.0`.
+
+### Patch Changes
+
+- [#3886](https://github.com/withastro/astro/pull/3886) [`cb6a97383`](https://github.com/withastro/astro/commit/cb6a973839450dea1705407e1060919c946cca99) Thanks [@QuiiBz](https://github.com/QuiiBz)! - Fix portfolio example JSX error
+
+## 0.12.5
+
+### Patch Changes
+
+- [#3831](https://github.com/withastro/astro/pull/3831) [`4fb08502`](https://github.com/withastro/astro/commit/4fb08502a99396723b9eb671099482cd619b3564) Thanks [@FredKSchott](https://github.com/FredKSchott)! - Small wording updates
+
+## 0.12.4
+
+### Patch Changes
+
+- [#3756](https://github.com/withastro/astro/pull/3756) [`507cd5c8`](https://github.com/withastro/astro/commit/507cd5c868448971c6265d97f22e786263dd5a77) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Chore: remove create-astro install step test
+
+## 0.12.3
+
+### Patch Changes
+
+- [#3748](https://github.com/withastro/astro/pull/3748) [`012f093e`](https://github.com/withastro/astro/commit/012f093eeb771b42b4e9d1e0cbb0d9a9605e0514) Thanks [@delucis](https://github.com/delucis)! - Remove `astro add` step & tweak wording (PR #3715)
+
+## 0.12.2
+
+### Patch Changes
+
+- [#3391](https://github.com/withastro/astro/pull/3391) [`cf8015ea`](https://github.com/withastro/astro/commit/cf8015eaa2b756f4ec399e8fd7071dee7dfa9ab6) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Fix [#3309](https://github.com/withastro/astro/issues/3309) default logger locale behavior.
+
+## 0.12.1
+
+### Patch Changes
+
+- [#3313](https://github.com/withastro/astro/pull/3313) [`1a5335ed`](https://github.com/withastro/astro/commit/1a5335ed9abaef397ee9543a3b4ad7a3fddcf024) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Update "next steps" with more informative text on each CLI command. Oh, and gradients. A lot more gradients.
+
+## 0.12.0
+
+### Minor Changes
+
+- [#3227](https://github.com/withastro/astro/pull/3227) [`c8f5fa35`](https://github.com/withastro/astro/commit/c8f5fa35c4c3cf08df45e6bd6cb78960782ae08b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add "initialize git repository" step to simplify our next steps suggestion. We now give you a one-liner to easily paste in your terminal and start the dev server!
+
+## 0.11.0
+
+### Minor Changes
+
+- [#3223](https://github.com/withastro/astro/pull/3223) [`b7cd6958`](https://github.com/withastro/astro/commit/b7cd69588453cf874346bf2f14c41accd183129e) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Replace the component framework selector with a new "run astro add" option. This unlocks integrations beyond components during your create-astro setup, including TailwindCSS and Partytown. This also replaces our previous "starter" template with a simplified "Just the basics" option.
+
+## 0.10.1
+
+### Patch Changes
+
+- [#3212](https://github.com/withastro/astro/pull/3212) [`00fc1326`](https://github.com/withastro/astro/commit/00fc1326ed526974cc4aca9faec410df91b4bcbd) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Simplify logging during welcome message and directory selection
+
+## 0.10.0
+
+### Minor Changes
+
+- [#3190](https://github.com/withastro/astro/pull/3190) [`38e5e9e9`](https://github.com/withastro/astro/commit/38e5e9e9825876cd0ae14a648b51bdf397e81169) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Feat: add option to install dependencies during setup. This respects the package manager used to run create-astro (ex. "yarn create astro" vs "pnpm create astro@latest").
+
+## 0.9.0
+
+### Minor Changes
+
+- [#3168](https://github.com/withastro/astro/pull/3168) [`7c49194c`](https://github.com/withastro/astro/commit/7c49194ca2161a09cc304ba8327533f8176ae0da) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add prompt to choose a directory, now defaulting to a separate "./my-astro-site" instead of "." (current directory)
+
+## 0.8.0
+
+### Minor Changes
+
+- [#2843](https://github.com/withastro/astro/pull/2843) [`1fdb63b5`](https://github.com/withastro/astro/commit/1fdb63b5d000d17edca77e870ce721e616a9c64a) Thanks [@JuanM04](https://github.com/JuanM04)! - Automatically add integration `peerDependencies` to scaffolded `package.json` files
+
+## 0.8.0-next.0
+
+### Minor Changes
+
+- [#2843](https://github.com/withastro/astro/pull/2843) [`1fdb63b5`](https://github.com/withastro/astro/commit/1fdb63b5d000d17edca77e870ce721e616a9c64a) Thanks [@JuanM04](https://github.com/JuanM04)! - Automatically add integration `peerDependencies` to scaffolded `package.json` files
+
+## 0.7.1
+
+### Patch Changes
+
+- [#2429](https://github.com/withastro/astro/pull/2429) [`fda857eb`](https://github.com/withastro/astro/commit/fda857eb22508f55233e297a887b356ea7b87398) Thanks [@Mikkel-T](https://github.com/Mikkel-T)! - Added an option to create-astro to use verbose logging which should help debug degit issues
+
+## 0.7.0
+
+### Minor Changes
+
+- [#2202](https://github.com/withastro/astro/pull/2202) [`45cea6ae`](https://github.com/withastro/astro/commit/45cea6aec5a310fed4cb8da0d96670d6b99a2539) Thanks [@jonathantneal](https://github.com/jonathantneal)! - Officially drop support for Node v12. The minimum supported version is now Node v14.15+,
+
+## 0.6.10
+
+### Patch Changes
+
+- [#2150](https://github.com/withastro/astro/pull/2150) [`d5ebd9d1`](https://github.com/withastro/astro/commit/d5ebd9d178ed4e5d15ef43f32217c16d44f19151) Thanks [@FredKSchott](https://github.com/FredKSchott)! - Fix create-astro export map entry
+
+## 0.6.9
+
+### Patch Changes
+
+- [#2124](https://github.com/withastro/astro/pull/2124) [`c0f29bcf`](https://github.com/withastro/astro/commit/c0f29bcf8c2b943e4a8101cae4f893b13a4b832c) Thanks [@leosvelperez](https://github.com/leosvelperez)! - Parse --renderers flag correctly when passed to the create-astro cli
+
+## 0.6.8
+
+### Patch Changes
+
+- 3e1bdb1a: Add a helpful message for the "could not find commit hash for ..." error
+
+## 0.6.7
+
+## 0.6.7-next.1
+
+### Patch Changes
+
+- 6c66d483: Fix issue with v7.x+ versions of npm init, which changed default flag handling
+
+## 0.6.7-next.0
+
+### Patch Changes
+
+- 6c66d483: Fix issue with v7.x+ versions of npm init, which changed default flag handling
+
+## 0.6.6
+
+### Patch Changes
+
+- d5fdeefe: Changes create-astro to pull examples from the latest branch
+
+## 0.6.5
+
+### Patch Changes
+
+- 025f5e3f: Fix to revert change pointing create-astro at the latest branch
+
+## 0.6.4
+
+### Patch Changes
+
+- 28f00566: Updates create-astro to use the latest branch
+
+## 0.6.3
+
+### Patch Changes
+
+- 0eeb2534: change rm to unlink for node 12 compatibility
+
+## 0.6.2
+
+### Patch Changes
+
+- 11a6f884: Added a check to see if the renderers array is empty and only show the message about using the templates default renderers if it isn't
+
+## 0.6.1
+
+### Patch Changes
+
+- 24dce41c: Adds a new template 'minimal' which does not include a framework
+
+## 0.6.0
+
+### Minor Changes
+
+- cf4c97cf: forced degit template extraction in case of non empty installation directory
+
+## 0.5.2
+
+### Patch Changes
+
+- 6c52c92: Add warning when encountering 'zlib: unexpected end of file' error
+
+## 0.5.1
+
+### Patch Changes
+
+- a7e6666: compile javascript to target Node v12.x
+- bd18e14: Add support for [Solid](https://www.solidjs.com/)
+- d45431d: create-astro does not fail when removing subdirectories
+
+## 0.5.1-next.1
+
+### Patch Changes
+
+- bd18e14: Add support for [Solid](https://www.solidjs.com/)
+
+## 0.5.1-next.0
+
+### Patch Changes
+
+- a7e6666: compile javascript to target Node v12.x
+- d45431d: create-astro does not fail when removing subdirectories
+
+## 0.5.0
+
+### Minor Changes
+
+- 36e104b: Use new client: prefix for component examples
+
+## 0.4.0
+
+### Minor Changes
+
+- 5d5d67c: Update `create-astro` to handle framework-specific logic based on user preference
+
+## 0.3.5
+
+### Patch Changes
+
+- d8ceff5: Allows using an external repo as a template
+
+ You can do this with the `--template` flag:
+
+ ```bash
+ npm init astro my-shopify --template cassidoo/shopify-react-astro
+ ```
+
+## 0.3.4
+
+### Patch Changes
+
+- b0e41ea: fix small output bugs
+
+## 0.3.3
+
+### Patch Changes
+
+- f9f2da4: Add repository key to package.json for create-astro
+
+## 0.3.2
+
+### Patch Changes
+
+- ab2972b: Update package.json engines for esm support
+
+## 0.3.1
+
+### Patch Changes
+
+- d6a7349: fix issue with empty prompt
+
+## 0.3.0
+
+### Minor Changes
+
+- 6bca7c8: Redesigned create-astro internals
+- 6bca7c8: New UI
diff --git a/packages/create-astro/README.md b/packages/create-astro/README.md
new file mode 100644
index 000000000..260922b0a
--- /dev/null
+++ b/packages/create-astro/README.md
@@ -0,0 +1,63 @@
+# create-astro
+
+## Scaffolding for Astro projects
+
+**With NPM:**
+
+```bash
+npm create astro@latest
+```
+
+**With Yarn:**
+
+```bash
+yarn create astro
+```
+
+**With PNPM:**
+
+```bash
+pnpm create astro
+```
+
+`create-astro` automatically runs in _interactive_ mode, but you can also specify your project name and template with command line arguments.
+
+```bash
+# npm
+npm create astro@latest my-astro-project -- --template minimal
+
+# yarn
+yarn create astro my-astro-project --template minimal
+
+# pnpm
+pnpm create astro my-astro-project --template minimal
+```
+
+[Check out the full list][examples] of example templates, available on GitHub.
+
+You can also use any GitHub repo as a template:
+
+```bash
+npm create astro@latest my-astro-project -- --template cassidoo/shopify-react-astro
+```
+
+### CLI Flags
+
+May be provided in place of prompts
+
+| Name | Description |
+| :--------------------------- | :----------------------------------------- |
+| `--help` (`-h`) | Display available flags. |
+| `--template <name>` | Specify your template. |
+| `--install` / `--no-install` | Install dependencies (or not). |
+| `--add <integrations>` | Add integrations. |
+| `--git` / `--no-git` | Initialize git repo (or not). |
+| `--yes` (`-y`) | Skip all prompts by accepting defaults. |
+| `--no` (`-n`) | Skip all prompts by declining defaults. |
+| `--dry-run` | Walk through steps without executing. |
+| `--skip-houston` | Skip Houston animation. |
+| `--ref` | Specify an Astro branch (default: latest). |
+| `--fancy` | Enable full Unicode support for Windows. |
+
+[examples]: https://github.com/withastro/astro/tree/main/examples
+[typescript]: https://github.com/withastro/astro/tree/main/packages/astro/tsconfigs
diff --git a/packages/create-astro/create-astro.mjs b/packages/create-astro/create-astro.mjs
new file mode 100755
index 000000000..5e25e5e94
--- /dev/null
+++ b/packages/create-astro/create-astro.mjs
@@ -0,0 +1,15 @@
+#!/usr/bin/env node
+
+'use strict';
+
+const currentVersion = process.versions.node;
+const requiredMajorVersion = parseInt(currentVersion.split('.')[0], 10);
+const minimumMajorVersion = 18;
+
+if (requiredMajorVersion < minimumMajorVersion) {
+ console.error(`Node.js v${currentVersion} is out of date and unsupported!`);
+ console.error(`Please use Node.js v${minimumMajorVersion} or higher.`);
+ process.exit(1);
+}
+
+import('./dist/index.js').then(({ main }) => main());
diff --git a/packages/create-astro/package.json b/packages/create-astro/package.json
new file mode 100644
index 000000000..16fd3c898
--- /dev/null
+++ b/packages/create-astro/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "create-astro",
+ "version": "4.11.0",
+ "type": "module",
+ "author": "withastro",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/withastro/astro.git",
+ "directory": "packages/create-astro"
+ },
+ "bugs": "https://github.com/withastro/astro/issues",
+ "homepage": "https://astro.build",
+ "exports": {
+ ".": "./create-astro.mjs"
+ },
+ "main": "./create-astro.mjs",
+ "bin": {
+ "create-astro": "./create-astro.mjs"
+ },
+ "scripts": {
+ "build": "astro-scripts build \"src/index.ts\" --bundle && tsc",
+ "build:ci": "astro-scripts build \"src/index.ts\" --bundle",
+ "dev": "astro-scripts dev \"src/**/*.ts\"",
+ "test": "astro-scripts test \"test/**/*.test.js\""
+ },
+ "files": [
+ "dist",
+ "create-astro.js"
+ ],
+ "//a": "MOST PACKAGES SHOULD GO IN DEV_DEPENDENCIES! THEY WILL BE BUNDLED.",
+ "//b": "DEPENDENCIES IS FOR UNBUNDLED PACKAGES",
+ "dependencies": {
+ "@astrojs/cli-kit": "^0.4.1",
+ "@bluwy/giget-core": "^0.1.2"
+ },
+ "devDependencies": {
+ "arg": "^5.0.2",
+ "astro-scripts": "workspace:*",
+ "strip-json-comments": "^5.0.1"
+ },
+ "engines": {
+ "node": "^18.17.1 || ^20.3.0 || >=22.0.0"
+ },
+ "publishConfig": {
+ "provenance": true
+ }
+}
diff --git a/packages/create-astro/src/actions/context.ts b/packages/create-astro/src/actions/context.ts
new file mode 100644
index 000000000..59f85f88a
--- /dev/null
+++ b/packages/create-astro/src/actions/context.ts
@@ -0,0 +1,124 @@
+import os from 'node:os';
+import { type Task, prompt } from '@astrojs/cli-kit';
+import { random } from '@astrojs/cli-kit/utils';
+import arg from 'arg';
+
+import getSeasonalData from '../data/seasonal.js';
+import { getName, getVersion } from '../messages.js';
+
+export interface Context {
+ help: boolean;
+ prompt: typeof prompt;
+ cwd: string;
+ packageManager: string;
+ username: Promise<string>;
+ version: Promise<string>;
+ skipHouston: boolean;
+ fancy?: boolean;
+ add?: string[];
+ dryRun?: boolean;
+ yes?: boolean;
+ projectName?: string;
+ template?: string;
+ ref: string;
+ install?: boolean;
+ git?: boolean;
+ typescript?: string;
+ stdin?: typeof process.stdin;
+ stdout?: typeof process.stdout;
+ exit(code: number): never;
+ welcome?: string;
+ hat?: string;
+ tie?: string;
+ tasks: Task[];
+}
+
+export async function getContext(argv: string[]): Promise<Context> {
+ const flags = arg(
+ {
+ '--template': String,
+ '--ref': String,
+ '--yes': Boolean,
+ '--no': Boolean,
+ '--install': Boolean,
+ '--no-install': Boolean,
+ '--git': Boolean,
+ '--no-git': Boolean,
+ '--skip-houston': Boolean,
+ '--dry-run': Boolean,
+ '--help': Boolean,
+ '--fancy': Boolean,
+ '--add': [String],
+
+ '-y': '--yes',
+ '-n': '--no',
+ '-h': '--help',
+ },
+ { argv, permissive: true },
+ );
+
+ const packageManager = detectPackageManager() ?? 'npm';
+ let cwd = flags['_'][0];
+ let {
+ '--help': help = false,
+ '--template': template,
+ '--no': no,
+ '--yes': yes,
+ '--install': install,
+ '--no-install': noInstall,
+ '--git': git,
+ '--no-git': noGit,
+ '--fancy': fancy,
+ '--skip-houston': skipHouston,
+ '--dry-run': dryRun,
+ '--ref': ref,
+ '--add': add,
+ } = flags;
+ let projectName = cwd;
+
+ if (no) {
+ yes = false;
+ if (install == undefined) install = false;
+ if (git == undefined) git = false;
+ }
+
+ skipHouston =
+ ((os.platform() === 'win32' && !fancy) || skipHouston) ??
+ [yes, no, install, git].some((v) => v !== undefined);
+
+ const { messages, hats, ties } = getSeasonalData({ fancy });
+
+ const context: Context = {
+ help,
+ prompt,
+ packageManager,
+ username: getName(),
+ version: getVersion(packageManager, 'astro', process.env.ASTRO_VERSION),
+ skipHouston,
+ fancy,
+ add,
+ dryRun,
+ projectName,
+ template,
+ ref: ref ?? 'latest',
+ welcome: random(messages),
+ hat: hats ? random(hats) : undefined,
+ tie: ties ? random(ties) : undefined,
+ yes,
+ install: install ?? (noInstall ? false : undefined),
+ git: git ?? (noGit ? false : undefined),
+ cwd,
+ exit(code) {
+ process.exit(code);
+ },
+ tasks: [],
+ };
+ return context;
+}
+
+function detectPackageManager() {
+ if (!process.env.npm_config_user_agent) return;
+ const specifier = process.env.npm_config_user_agent.split(' ')[0];
+ const name = specifier.substring(0, specifier.lastIndexOf('/'));
+ return name === 'npminstall' ? 'cnpm' : name;
+}
diff --git a/packages/create-astro/src/actions/dependencies.ts b/packages/create-astro/src/actions/dependencies.ts
new file mode 100644
index 000000000..d4990a8fb
--- /dev/null
+++ b/packages/create-astro/src/actions/dependencies.ts
@@ -0,0 +1,109 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { color } from '@astrojs/cli-kit';
+import { error, info, title } from '../messages.js';
+import { shell } from '../shell.js';
+import type { Context } from './context.js';
+
+export async function dependencies(
+ ctx: Pick<
+ Context,
+ 'install' | 'yes' | 'prompt' | 'packageManager' | 'cwd' | 'dryRun' | 'tasks' | 'add'
+ >,
+) {
+ let deps = ctx.install ?? ctx.yes;
+ if (deps === undefined) {
+ ({ deps } = await ctx.prompt({
+ name: 'deps',
+ type: 'confirm',
+ label: title('deps'),
+ message: `Install dependencies?`,
+ hint: 'recommended',
+ initial: true,
+ }));
+ ctx.install = deps;
+ }
+
+ ctx.add = ctx.add?.reduce<string[]>((acc, item) => acc.concat(item.split(',')), []);
+
+ if (ctx.dryRun) {
+ await info(
+ '--dry-run',
+ `Skipping dependency installation${ctx.add ? ` and adding ${ctx.add.join(', ')}` : ''}`,
+ );
+ } else if (deps) {
+ ctx.tasks.push({
+ pending: 'Dependencies',
+ start: `Dependencies installing with ${ctx.packageManager}...`,
+ end: 'Dependencies installed',
+ onError: (e) => {
+ error('error', e);
+ error(
+ 'error',
+ `Dependencies failed to install, please run ${color.bold(
+ ctx.packageManager + ' install',
+ )} to install them manually after setup.`,
+ );
+ },
+ while: () => install({ packageManager: ctx.packageManager, cwd: ctx.cwd }),
+ });
+
+ let add = ctx.add;
+
+ if (add) {
+ ctx.tasks.push({
+ pending: 'Integrations',
+ start: `Adding integrations with astro add`,
+ end: 'Integrations added',
+ onError: (e) => {
+ error('error', e);
+ error(
+ 'error',
+ `Failed to add integrations, please run ${color.bold(
+ `astro add ${add.join(' ')}`,
+ )} to install them manually after setup.`,
+ );
+ },
+ while: () =>
+ astroAdd({ integrations: add, packageManager: ctx.packageManager, cwd: ctx.cwd }),
+ });
+ }
+ } else {
+ await info(
+ ctx.yes === false ? 'deps [skip]' : 'No problem!',
+ 'Remember to install dependencies after setup.',
+ );
+ }
+}
+
+async function astroAdd({
+ integrations,
+ packageManager,
+ cwd,
+}: { integrations: string[]; packageManager: string; cwd: string }) {
+ if (packageManager === 'yarn') await ensureYarnLock({ cwd });
+ return shell(
+ packageManager === 'npm' ? 'npx' : `${packageManager} dlx`,
+ ['astro add', integrations.join(' '), '-y'],
+ { cwd, timeout: 90_000, stdio: 'ignore' },
+ );
+}
+
+async function install({ packageManager, cwd }: { packageManager: string; cwd: string }) {
+ if (packageManager === 'yarn') await ensureYarnLock({ cwd });
+ return shell(packageManager, ['install'], { cwd, timeout: 90_000, stdio: 'ignore' });
+}
+
+/**
+ * Yarn Berry (PnP) versions will throw an error if there isn't an existing `yarn.lock` file
+ * If a `yarn.lock` file doesn't exist, this function writes an empty `yarn.lock` one.
+ * Unfortunately this hack is required to run `yarn install`.
+ *
+ * The empty `yarn.lock` file is immediately overwritten by the installation process.
+ * See https://github.com/withastro/astro/pull/8028
+ */
+async function ensureYarnLock({ cwd }: { cwd: string }) {
+ const yarnLock = path.join(cwd, 'yarn.lock');
+ if (fs.existsSync(yarnLock)) return;
+ return fs.promises.writeFile(yarnLock, '', { encoding: 'utf-8' });
+}
diff --git a/packages/create-astro/src/actions/git.ts b/packages/create-astro/src/actions/git.ts
new file mode 100644
index 000000000..ebedb8701
--- /dev/null
+++ b/packages/create-astro/src/actions/git.ts
@@ -0,0 +1,64 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import type { Context } from './context.js';
+
+import { color } from '@astrojs/cli-kit';
+import { error, info, title } from '../messages.js';
+import { shell } from '../shell.js';
+
+export async function git(
+ ctx: Pick<Context, 'cwd' | 'git' | 'yes' | 'prompt' | 'dryRun' | 'tasks'>,
+) {
+ if (fs.existsSync(path.join(ctx.cwd, '.git'))) {
+ await info('Nice!', `Git has already been initialized`);
+ return;
+ }
+ let _git = ctx.git ?? ctx.yes;
+ if (_git === undefined) {
+ ({ git: _git } = await ctx.prompt({
+ name: 'git',
+ type: 'confirm',
+ label: title('git'),
+ message: `Initialize a new git repository?`,
+ hint: 'optional',
+ initial: true,
+ }));
+ }
+
+ if (ctx.dryRun) {
+ await info('--dry-run', `Skipping Git initialization`);
+ } else if (_git) {
+ ctx.tasks.push({
+ pending: 'Git',
+ start: 'Git initializing...',
+ end: 'Git initialized',
+ while: () =>
+ init({ cwd: ctx.cwd }).catch((e) => {
+ error('error', e);
+ process.exit(1);
+ }),
+ });
+ } else {
+ await info(
+ ctx.yes === false ? 'git [skip]' : 'Sounds good!',
+ `You can always run ${color.reset('git init')}${color.dim(' manually.')}`,
+ );
+ }
+}
+
+async function init({ cwd }: { cwd: string }) {
+ try {
+ await shell('git', ['init'], { cwd, stdio: 'ignore' });
+ await shell('git', ['add', '-A'], { cwd, stdio: 'ignore' });
+ await shell(
+ 'git',
+ [
+ 'commit',
+ '-m',
+ '"Initial commit from Astro"',
+ '--author="houston[bot] <astrobot-houston@users.noreply.github.com>"',
+ ],
+ { cwd, stdio: 'ignore' },
+ );
+ } catch {}
+}
diff --git a/packages/create-astro/src/actions/help.ts b/packages/create-astro/src/actions/help.ts
new file mode 100644
index 000000000..1d5c7f609
--- /dev/null
+++ b/packages/create-astro/src/actions/help.ts
@@ -0,0 +1,24 @@
+import { printHelp } from '../messages.js';
+
+export function help() {
+ printHelp({
+ commandName: 'create-astro',
+ usage: '[dir] [...flags]',
+ headline: 'Scaffold Astro projects.',
+ tables: {
+ Flags: [
+ ['--help (-h)', 'See all available flags.'],
+ ['--template <name>', 'Specify your template.'],
+ ['--install / --no-install', 'Install dependencies (or not).'],
+ ['--add <integrations>', 'Add integrations.'],
+ ['--git / --no-git', 'Initialize git repo (or not).'],
+ ['--yes (-y)', 'Skip all prompts by accepting defaults.'],
+ ['--no (-n)', 'Skip all prompts by declining defaults.'],
+ ['--dry-run', 'Walk through steps without executing.'],
+ ['--skip-houston', 'Skip Houston animation.'],
+ ['--ref', 'Choose astro branch (default: latest).'],
+ ['--fancy', 'Enable full Unicode support for Windows.'],
+ ],
+ },
+ });
+}
diff --git a/packages/create-astro/src/actions/intro.ts b/packages/create-astro/src/actions/intro.ts
new file mode 100644
index 000000000..0249e63c8
--- /dev/null
+++ b/packages/create-astro/src/actions/intro.ts
@@ -0,0 +1,29 @@
+import type { Context } from './context.js';
+
+import { color, label } from '@astrojs/cli-kit';
+import { banner, say } from '../messages.js';
+
+export async function intro(
+ ctx: Pick<Context, 'skipHouston' | 'welcome' | 'hat' | 'tie' | 'version' | 'username' | 'fancy'>,
+) {
+ banner();
+
+ if (!ctx.skipHouston) {
+ const { welcome, hat, tie } = ctx;
+ await say(
+ [
+ [
+ 'Welcome',
+ 'to',
+ label('astro', color.bgGreen, color.black),
+ Promise.resolve(ctx.version).then(
+ (version) => (version ? color.green(`v${version}`) : '') + ',',
+ ),
+ Promise.resolve(ctx.username).then((username) => `${username}!`),
+ ],
+ welcome ?? "Let's build something awesome!",
+ ] as string[],
+ { clear: true, hat, tie },
+ );
+ }
+}
diff --git a/packages/create-astro/src/actions/next-steps.ts b/packages/create-astro/src/actions/next-steps.ts
new file mode 100644
index 000000000..91536cc46
--- /dev/null
+++ b/packages/create-astro/src/actions/next-steps.ts
@@ -0,0 +1,25 @@
+import path from 'node:path';
+import type { Context } from './context.js';
+
+import { nextSteps, say } from '../messages.js';
+
+export async function next(
+ ctx: Pick<Context, 'hat' | 'tie' | 'cwd' | 'packageManager' | 'skipHouston'>,
+) {
+ let projectDir = path.relative(process.cwd(), ctx.cwd);
+
+ const commandMap: { [key: string]: string } = {
+ npm: 'npm run dev',
+ bun: 'bun run dev',
+ yarn: 'yarn dev',
+ pnpm: 'pnpm dev',
+ };
+
+ const devCmd = commandMap[ctx.packageManager as keyof typeof commandMap] || 'npm run dev';
+ await nextSteps({ projectDir, devCmd });
+
+ if (!ctx.skipHouston) {
+ await say(['Good luck out there, astronaut! ๐Ÿš€'], { hat: ctx.hat, tie: ctx.tie });
+ }
+ return;
+}
diff --git a/packages/create-astro/src/actions/project-name.ts b/packages/create-astro/src/actions/project-name.ts
new file mode 100644
index 000000000..26938de80
--- /dev/null
+++ b/packages/create-astro/src/actions/project-name.ts
@@ -0,0 +1,74 @@
+import type { Context } from './context.js';
+
+import path from 'node:path';
+import { color, generateProjectName } from '@astrojs/cli-kit';
+import { info, log, title } from '../messages.js';
+
+import { isEmpty, toValidName } from './shared.js';
+
+export async function projectName(
+ ctx: Pick<Context, 'cwd' | 'yes' | 'dryRun' | 'prompt' | 'projectName' | 'exit'>,
+) {
+ await checkCwd(ctx.cwd);
+
+ if (!ctx.cwd || !isEmpty(ctx.cwd)) {
+ if (!isEmpty(ctx.cwd)) {
+ await info('Hmm...', `${color.reset(`"${ctx.cwd}"`)}${color.dim(` is not empty!`)}`);
+ }
+
+ if (ctx.yes) {
+ ctx.projectName = generateProjectName();
+ ctx.cwd = `./${ctx.projectName}`;
+ await info('dir', `Project created at ./${ctx.projectName}`);
+ return;
+ }
+
+ const { name } = await ctx.prompt({
+ name: 'name',
+ type: 'text',
+ label: title('dir'),
+ message: 'Where should we create your new project?',
+ initial: `./${generateProjectName()}`,
+ validate(value: string) {
+ if (!isEmpty(value)) {
+ return `Directory is not empty!`;
+ }
+ // Check for non-printable characters
+ if (value.match(/[^\x20-\x7E]/g) !== null)
+ return `Invalid non-printable character present!`;
+ return true;
+ },
+ });
+
+ ctx.cwd = name!.trim();
+ ctx.projectName = toValidName(name!);
+ if (ctx.dryRun) {
+ await info('--dry-run', 'Skipping project naming');
+ return;
+ }
+ } else {
+ let name = ctx.cwd;
+ if (name === '.' || name === './') {
+ const parts = process.cwd().split(path.sep);
+ name = parts[parts.length - 1];
+ } else if (name.startsWith('./') || name.startsWith('../')) {
+ const parts = name.split('/');
+ name = parts[parts.length - 1];
+ }
+ ctx.projectName = toValidName(name);
+ }
+
+ if (!ctx.cwd) {
+ ctx.exit(1);
+ }
+}
+
+async function checkCwd(cwd: string | undefined) {
+ const empty = cwd && isEmpty(cwd);
+ if (empty) {
+ log('');
+ await info('dir', `Using ${color.reset(cwd)}${color.dim(' as project directory')}`);
+ }
+
+ return empty;
+}
diff --git a/packages/create-astro/src/actions/shared.ts b/packages/create-astro/src/actions/shared.ts
new file mode 100644
index 000000000..4bd852529
--- /dev/null
+++ b/packages/create-astro/src/actions/shared.ts
@@ -0,0 +1,59 @@
+import fs from 'node:fs';
+
+// Some existing files and directories can be safely ignored when checking if a directory is a valid project directory.
+// https://github.com/facebook/create-react-app/blob/d960b9e38c062584ff6cfb1a70e1512509a966e7/packages/create-react-app/createReactApp.js#L907-L934
+const VALID_PROJECT_DIRECTORY_SAFE_LIST = [
+ '.DS_Store',
+ '.git',
+ '.gitkeep',
+ '.gitattributes',
+ '.gitignore',
+ '.gitlab-ci.yml',
+ '.hg',
+ '.hgcheck',
+ '.hgignore',
+ '.idea',
+ '.npmignore',
+ '.travis.yml',
+ '.yarn',
+ '.yarnrc.yml',
+ 'docs',
+ 'LICENSE',
+ 'mkdocs.yml',
+ 'Thumbs.db',
+ /\.iml$/,
+ /^npm-debug\.log/,
+ /^yarn-debug\.log/,
+ /^yarn-error\.log/,
+];
+
+export function isEmpty(dirPath: string) {
+ if (!fs.existsSync(dirPath)) {
+ return true;
+ }
+
+ const conflicts = fs.readdirSync(dirPath).filter((content) => {
+ return !VALID_PROJECT_DIRECTORY_SAFE_LIST.some((safeContent) => {
+ return typeof safeContent === 'string' ? content === safeContent : safeContent.test(content);
+ });
+ });
+
+ return conflicts.length === 0;
+}
+
+export function isValidName(projectName: string) {
+ return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(projectName);
+}
+
+export function toValidName(projectName: string) {
+ if (isValidName(projectName)) return projectName;
+
+ return projectName
+ .trim()
+ .toLowerCase()
+ .replace(/\s+/g, '-')
+ .replace(/^[._]/, '')
+ .replace(/[^a-z\d\-~]+/g, '-')
+ .replace(/^-+/, '')
+ .replace(/-+$/, '');
+}
diff --git a/packages/create-astro/src/actions/template.ts b/packages/create-astro/src/actions/template.ts
new file mode 100644
index 000000000..512e1f921
--- /dev/null
+++ b/packages/create-astro/src/actions/template.ts
@@ -0,0 +1,155 @@
+import type { Context } from './context.js';
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { color } from '@astrojs/cli-kit';
+import { downloadTemplate } from '@bluwy/giget-core';
+import { error, info, title } from '../messages.js';
+
+export async function template(
+ ctx: Pick<Context, 'template' | 'prompt' | 'yes' | 'dryRun' | 'exit' | 'tasks'>,
+) {
+ if (!ctx.template && ctx.yes) ctx.template = 'basics';
+
+ if (ctx.template) {
+ await info('tmpl', `Using ${color.reset(ctx.template)}${color.dim(' as project template')}`);
+ } else {
+ const { template: tmpl } = await ctx.prompt({
+ name: 'template',
+ type: 'select',
+ label: title('tmpl'),
+ message: 'How would you like to start your new project?',
+ initial: 'basics',
+ choices: [
+ { value: 'basics', label: 'A basic, minimal starter', hint: '(recommended)' },
+ { value: 'blog', label: 'Use blog template' },
+ { value: 'starlight', label: 'Use docs (Starlight) template' },
+ ],
+ });
+ ctx.template = tmpl;
+ }
+
+ if (ctx.dryRun) {
+ await info('--dry-run', `Skipping template copying`);
+ } else if (ctx.template) {
+ ctx.tasks.push({
+ pending: 'Template',
+ start: 'Template copying...',
+ end: 'Template copied',
+ while: () =>
+ copyTemplate(ctx.template!, ctx as Context).catch((e) => {
+ if (e instanceof Error) {
+ error('error', e.message);
+ process.exit(1);
+ } else {
+ error('error', 'Unable to clone template.');
+ process.exit(1);
+ }
+ }),
+ });
+ } else {
+ ctx.exit(1);
+ }
+}
+
+// some files are only needed for online editors when using astro.new. Remove for create-astro installs.
+const FILES_TO_REMOVE = ['CHANGELOG.md', '.codesandbox'];
+const FILES_TO_UPDATE = {
+ 'package.json': (file: string, overrides: { name: string }) =>
+ fs.promises.readFile(file, 'utf-8').then((value) => {
+ // Match first indent in the file or fallback to `\t`
+ const indent = /(^\s+)/m.exec(value)?.[1] ?? '\t';
+ return fs.promises.writeFile(
+ file,
+ JSON.stringify(
+ Object.assign(JSON.parse(value), Object.assign(overrides, { private: undefined })),
+ null,
+ indent,
+ ),
+ 'utf-8',
+ );
+ }),
+};
+
+export function getTemplateTarget(tmpl: string, ref = 'latest') {
+ // Handle Starlight templates
+ if (tmpl.startsWith('starlight')) {
+ const [, starter = 'basics'] = tmpl.split('/');
+ return `github:withastro/starlight/examples/${starter}`;
+ }
+
+ // Handle third-party templates
+ const isThirdParty = tmpl.includes('/');
+ if (isThirdParty) return tmpl;
+
+ // Handle Astro templates
+ if (ref === 'latest') {
+ // `latest` ref is specially handled to route to a branch specifically
+ // to allow faster downloads. Otherwise giget has to download the entire
+ // repo and only copy a sub directory
+ return `github:withastro/astro#examples/${tmpl}`;
+ } else {
+ return `github:withastro/astro/examples/${tmpl}#${ref}`;
+ }
+}
+
+export default async function copyTemplate(tmpl: string, ctx: Context) {
+ const templateTarget = getTemplateTarget(tmpl, ctx.ref);
+ // Copy
+ if (!ctx.dryRun) {
+ try {
+ await downloadTemplate(templateTarget, {
+ force: true,
+ cwd: ctx.cwd,
+ dir: '.',
+ });
+ } catch (err: any) {
+ // Only remove the directory if it's most likely created by us.
+ if (ctx.cwd !== '.' && ctx.cwd !== './' && !ctx.cwd.startsWith('../')) {
+ try {
+ fs.rmdirSync(ctx.cwd);
+ } catch (_) {
+ // Ignore any errors from removing the directory,
+ // make sure we throw and display the original error.
+ }
+ }
+
+ if (err.message?.includes('404')) {
+ throw new Error(`Template ${color.reset(tmpl)} ${color.dim('does not exist!')}`);
+ }
+
+ if (err.message) {
+ error('error', err.message);
+ }
+ try {
+ // The underlying error is often buried deep in the `cause` property
+ // This is in a try/catch block in case of weirdnesses in accessing the `cause` property
+ if ('cause' in err) {
+ // This is probably included in err.message, but we can log it just in case it has extra info
+ error('error', err.cause);
+ if ('cause' in err.cause) {
+ // Hopefully the actual fetch error message
+ error('error', err.cause?.cause);
+ }
+ }
+ } catch {}
+ throw new Error(`Unable to download template ${color.reset(tmpl)}`);
+ }
+
+ // Post-process in parallel
+ const removeFiles = FILES_TO_REMOVE.map(async (file) => {
+ const fileLoc = path.resolve(path.join(ctx.cwd, file));
+ if (fs.existsSync(fileLoc)) {
+ return fs.promises.rm(fileLoc, { recursive: true });
+ }
+ });
+ const updateFiles = Object.entries(FILES_TO_UPDATE).map(async ([file, update]) => {
+ const fileLoc = path.resolve(path.join(ctx.cwd, file));
+ if (fs.existsSync(fileLoc)) {
+ return update(fileLoc, { name: ctx.projectName! });
+ }
+ });
+
+ await Promise.all([...removeFiles, ...updateFiles]);
+ }
+}
diff --git a/packages/create-astro/src/actions/verify.ts b/packages/create-astro/src/actions/verify.ts
new file mode 100644
index 000000000..7f446c869
--- /dev/null
+++ b/packages/create-astro/src/actions/verify.ts
@@ -0,0 +1,40 @@
+import type { Context } from './context.js';
+
+import dns from 'node:dns/promises';
+import { color } from '@astrojs/cli-kit';
+import { verifyTemplate } from '@bluwy/giget-core';
+import { bannerAbort, error, info, log } from '../messages.js';
+import { getTemplateTarget } from './template.js';
+
+export async function verify(
+ ctx: Pick<Context, 'version' | 'dryRun' | 'template' | 'ref' | 'exit'>,
+) {
+ if (!ctx.dryRun) {
+ const online = await isOnline();
+ if (!online) {
+ bannerAbort();
+ log('');
+ error('error', `Unable to connect to the internet.`);
+ ctx.exit(1);
+ }
+ }
+
+ if (ctx.template) {
+ const target = getTemplateTarget(ctx.template, ctx.ref);
+ const ok = await verifyTemplate(target);
+ if (!ok) {
+ bannerAbort();
+ log('');
+ error('error', `Template ${color.reset(ctx.template)} ${color.dim('could not be found!')}`);
+ await info('check', 'https://astro.build/examples');
+ ctx.exit(1);
+ }
+ }
+}
+
+function isOnline(): Promise<boolean> {
+ return dns.lookup('github.com').then(
+ () => true,
+ () => false,
+ );
+}
diff --git a/packages/create-astro/src/data/seasonal.ts b/packages/create-astro/src/data/seasonal.ts
new file mode 100644
index 000000000..c333affa0
--- /dev/null
+++ b/packages/create-astro/src/data/seasonal.ts
@@ -0,0 +1,112 @@
+interface SeasonalHouston {
+ hats?: string[];
+ ties?: string[];
+ messages: string[];
+}
+
+export default function getSeasonalHouston({ fancy }: { fancy?: boolean }): SeasonalHouston {
+ const season = getSeason();
+ switch (season) {
+ case 'new-year': {
+ const year = new Date().getFullYear();
+ return {
+ hats: rarity(0.5, ['๐ŸŽฉ']),
+ ties: rarity(0.25, ['๐ŸŽŠ', '๐ŸŽ€', '๐ŸŽ‰']),
+ messages: [
+ `New year, new Astro site!`,
+ `Kicking ${year} off with Astro?! What an honor!`,
+ `Happy ${year}! Let's make something cool.`,
+ `${year} is your year! Let's build something awesome.`,
+ `${year} is the year of Astro!`,
+ `${year} is clearly off to a great start!`,
+ `Thanks for starting ${year} with Astro!`,
+ ],
+ };
+ }
+ case 'spooky':
+ return {
+ hats: rarity(0.5, ['๐ŸŽƒ', '๐Ÿ‘ป', 'โ˜ ๏ธ', '๐Ÿ’€', '๐Ÿ•ท๏ธ', '๐Ÿ”ฎ']),
+ ties: rarity(0.25, ['๐Ÿฆด', '๐Ÿฌ', '๐Ÿซ']),
+ messages: [
+ `I'm afraid I can't help you... Just kidding!`,
+ `Boo! Just kidding. Let's make a website!`,
+ `Let's haunt the internet. OooOooOOoo!`,
+ `No tricks here. Seeing you is always treat!`,
+ `Spiders aren't the only ones building the web!`,
+ `Let's conjure up some web magic!`,
+ `Let's harness the power of Astro to build a frightful new site!`,
+ `We're conjuring up a spooktacular website!`,
+ `Prepare for a web of spooky wonders to be woven.`,
+ `Chills and thrills await you on your new project!`,
+ ],
+ };
+ case 'holiday':
+ return {
+ hats: rarity(0.75, ['๐ŸŽ', '๐ŸŽ„', '๐ŸŒฒ']),
+ ties: rarity(0.75, ['๐Ÿงฃ']),
+ messages: [
+ `'Tis the season to code and create.`,
+ `Jingle all the way through your web creation journey!`,
+ `Bells are ringing, and so are your creative ideas!`,
+ `Let's make the internet our own winter wonderland!`,
+ `It's time to decorate a brand new website!`,
+ `Let's unwrap the magic of the web together!`,
+ `Hope you're enjoying the holiday season!`,
+ `I'm dreaming of a brand new website!`,
+ `No better holiday gift than a new site!`,
+ `Your creativity is the gift that keeps on giving!`,
+ ],
+ };
+ case undefined:
+ default:
+ return {
+ hats: fancy ? ['๐ŸŽฉ', '๐ŸŽฉ', '๐ŸŽฉ', '๐ŸŽฉ', '๐ŸŽ“', '๐Ÿ‘‘', '๐Ÿงข', '๐Ÿฆ'] : undefined,
+ ties: fancy ? rarity(0.33, ['๐ŸŽ€', '๐Ÿงฃ']) : undefined,
+ messages: [
+ `Let's claim your corner of the internet.`,
+ `I'll be your assistant today.`,
+ `Let's build something awesome!`,
+ `Let's build something great!`,
+ `Let's build something fast!`,
+ `Let's build the web we want.`,
+ `Let's make the web weird!`,
+ `Let's make the web a better place!`,
+ `Let's create a new project!`,
+ `Let's create something unique!`,
+ `Time to build a new website.`,
+ `Time to build a faster website.`,
+ `Time to build a sweet new website.`,
+ `We're glad to have you on board.`,
+ `Keeping the internet weird since 2021.`,
+ `Initiating launch sequence...`,
+ `Initiating launch sequence... right... now!`,
+ `Awaiting further instructions.`,
+ ],
+ };
+ }
+}
+
+type Season = 'spooky' | 'holiday' | 'new-year';
+function getSeason(): Season | undefined {
+ const date = new Date();
+ const month = date.getMonth() + 1;
+ const day = date.getDate() + 1;
+
+ if (month === 1 && day <= 7) {
+ return 'new-year';
+ }
+ if (month === 10 && day > 7) {
+ return 'spooky';
+ }
+ if (month === 12 && day > 7 && day < 25) {
+ return 'holiday';
+ }
+}
+
+// Generates an array padded with empty strings to make decorations more rare
+function rarity(frequency: number, emoji: string[]) {
+ if (frequency === 1) return emoji;
+ if (frequency === 0) return [''];
+ const empty = Array.from({ length: Math.round(emoji.length * frequency) }, () => '');
+ return [...emoji, ...empty];
+}
diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts
new file mode 100644
index 000000000..60816f75d
--- /dev/null
+++ b/packages/create-astro/src/index.ts
@@ -0,0 +1,62 @@
+import { getContext } from './actions/context.js';
+
+import { tasks } from '@astrojs/cli-kit';
+import { dependencies } from './actions/dependencies.js';
+import { git } from './actions/git.js';
+import { help } from './actions/help.js';
+import { intro } from './actions/intro.js';
+import { next } from './actions/next-steps.js';
+import { projectName } from './actions/project-name.js';
+import { template } from './actions/template.js';
+import { verify } from './actions/verify.js';
+import { setStdout } from './messages.js';
+
+const exit = () => process.exit(0);
+process.on('SIGINT', exit);
+process.on('SIGTERM', exit);
+
+export async function main() {
+ // Add some extra spacing from the noisy npm/pnpm init output
+ // biome-ignore lint/suspicious/noConsoleLog: allowed
+ console.log('');
+ // NOTE: In the v7.x version of npm, the default behavior of `npm init` was changed
+ // to no longer require `--` to pass args and instead pass `--` directly to us. This
+ // broke our arg parser, since `--` is a special kind of flag. Filtering for `--` here
+ // fixes the issue so that create-astro now works on all npm versions.
+ const cleanArgv = process.argv.slice(2).filter((arg) => arg !== '--');
+ const ctx = await getContext(cleanArgv);
+ if (ctx.help) {
+ help();
+ return;
+ }
+
+ const steps = [
+ verify,
+ intro,
+ projectName,
+ template,
+ dependencies,
+
+ // Steps which write to files need to go above git
+ git,
+ ];
+
+ for (const step of steps) {
+ await step(ctx);
+ }
+
+ // biome-ignore lint/suspicious/noConsoleLog: allowed
+ console.log('');
+
+ const labels = {
+ start: 'Project initializing...',
+ end: 'Project initialized!',
+ };
+ await tasks(labels, ctx.tasks);
+
+ await next(ctx);
+
+ process.exit(0);
+}
+
+export { dependencies, getContext, git, intro, next, projectName, setStdout, template, verify };
diff --git a/packages/create-astro/src/messages.ts b/packages/create-astro/src/messages.ts
new file mode 100644
index 000000000..898c9c728
--- /dev/null
+++ b/packages/create-astro/src/messages.ts
@@ -0,0 +1,201 @@
+import { exec } from 'node:child_process';
+import { stripVTControlCharacters } from 'node:util';
+/* eslint no-console: 'off' */
+import { color, say as houston, label, spinner as load } from '@astrojs/cli-kit';
+import { align, sleep } from '@astrojs/cli-kit/utils';
+import { shell } from './shell.js';
+
+// Users might lack access to the global npm registry, this function
+// checks the user's project type and will return the proper npm registry
+//
+// A copy of this function also exists in the astro package
+let _registry: string;
+async function getRegistry(packageManager: string): Promise<string> {
+ if (_registry) return _registry;
+ const fallback = 'https://registry.npmjs.org';
+ try {
+ const { stdout } = await shell(packageManager, ['config', 'get', 'registry']);
+ _registry = stdout?.trim()?.replace(/\/$/, '') || fallback;
+ // Detect cases where the shell command returned a non-URL (e.g. a warning)
+ if (!new URL(_registry).host) _registry = fallback;
+ } catch {
+ _registry = fallback;
+ }
+ return _registry;
+}
+
+let stdout = process.stdout;
+/** @internal Used to mock `process.stdout.write` for testing purposes */
+export function setStdout(writable: typeof process.stdout) {
+ stdout = writable;
+}
+
+export async function say(messages: string | string[], { clear = false, hat = '', tie = '' } = {}) {
+ return houston(messages, { clear, hat, tie, stdout });
+}
+
+export async function spinner(args: {
+ start: string;
+ end: string;
+ onError?: (error: any) => void;
+ while: (...args: any) => Promise<any>;
+}) {
+ await load(args, { stdout });
+}
+
+export const title = (text: string) => align(label(text), 'end', 7) + ' ';
+
+export const getName = () =>
+ new Promise<string>((resolve) => {
+ exec('git config user.name', { encoding: 'utf-8' }, (_1, gitName) => {
+ if (gitName.trim()) {
+ return resolve(gitName.split(' ')[0].trim());
+ }
+ exec('whoami', { encoding: 'utf-8' }, (_3, whoami) => {
+ if (whoami.trim()) {
+ return resolve(whoami.split(' ')[0].trim());
+ }
+ return resolve('astronaut');
+ });
+ });
+ });
+
+export const getVersion = (packageManager: string, packageName: string, fallback = '') =>
+ new Promise<string>(async (resolve) => {
+ let registry = await getRegistry(packageManager);
+ const { version } = await fetch(`${registry}/${packageName}/latest`, {
+ redirect: 'follow',
+ })
+ .then((res) => res.json())
+ .catch(() => ({ version: fallback }));
+ return resolve(version);
+ });
+
+export const log = (message: string) => stdout.write(message + '\n');
+export const banner = () => {
+ const prefix = `astro`;
+ const suffix = `Launch sequence initiated.`;
+ log(`${label(prefix, color.bgGreen, color.black)} ${suffix}`);
+};
+
+export const bannerAbort = () =>
+ log(`\n${label('astro', color.bgRed)} ${color.bold('Launch sequence aborted.')}`);
+
+export const info = async (prefix: string, text: string) => {
+ await sleep(100);
+ if (stdout.columns < 80) {
+ log(`${' '.repeat(5)} ${color.cyan('โ—ผ')} ${color.cyan(prefix)}`);
+ log(`${' '.repeat(9)}${color.dim(text)}`);
+ } else {
+ log(`${' '.repeat(5)} ${color.cyan('โ—ผ')} ${color.cyan(prefix)} ${color.dim(text)}`);
+ }
+};
+export const error = async (prefix: string, text: string) => {
+ if (stdout.columns < 80) {
+ log(`${' '.repeat(5)} ${color.red('โ–ฒ')} ${color.red(prefix)}`);
+ log(`${' '.repeat(9)}${color.dim(text)}`);
+ } else {
+ log(`${' '.repeat(5)} ${color.red('โ–ฒ')} ${color.red(prefix)} ${color.dim(text)}`);
+ }
+};
+
+export const typescriptByDefault = async () => {
+ await info(`No worries!`, 'TypeScript is supported in Astro by default,');
+ log(`${' '.repeat(9)}${color.dim('but you are free to continue writing JavaScript instead.')}`);
+ await sleep(1000);
+};
+
+export const nextSteps = async ({ projectDir, devCmd }: { projectDir: string; devCmd: string }) => {
+ const max = stdout.columns;
+ const prefix = max < 80 ? ' ' : ' '.repeat(9);
+ await sleep(200);
+ log(
+ `\n ${color.bgCyan(` ${color.black('next')} `)} ${color.bold(
+ 'Liftoff confirmed. Explore your project!',
+ )}`,
+ );
+
+ await sleep(100);
+ if (projectDir !== '') {
+ projectDir = projectDir.includes(' ') ? `"./${projectDir}"` : `./${projectDir}`;
+ const enter = [
+ `\n${prefix}Enter your project directory using`,
+ color.cyan(`cd ${projectDir}`, ''),
+ ];
+ const len = enter[0].length + stripVTControlCharacters(enter[1]).length;
+ log(enter.join(len > max ? '\n' + prefix : ' '));
+ }
+ log(
+ `${prefix}Run ${color.cyan(devCmd)} to start the dev server. ${color.cyan('CTRL+C')} to stop.`,
+ );
+ await sleep(100);
+ log(
+ `${prefix}Add frameworks like ${color.cyan(`react`)} or ${color.cyan(
+ 'tailwind',
+ )} using ${color.cyan('astro add')}.`,
+ );
+ await sleep(100);
+ log(`\n${prefix}Stuck? Join us at ${color.cyan(`https://astro.build/chat`)}`);
+ await sleep(200);
+};
+
+export function printHelp({
+ commandName,
+ headline,
+ usage,
+ tables,
+ description,
+}: {
+ commandName: string;
+ headline?: string;
+ usage?: string;
+ tables?: Record<string, [command: string, help: string][]>;
+ description?: string;
+}) {
+ const linebreak = () => '';
+ const table = (rows: [string, string][], { padding }: { padding: number }) => {
+ const split = stdout.columns < 60;
+ let raw = '';
+
+ for (const row of rows) {
+ if (split) {
+ raw += ` ${row[0]}\n `;
+ } else {
+ raw += `${`${row[0]}`.padStart(padding)}`;
+ }
+ raw += ' ' + color.dim(row[1]) + '\n';
+ }
+
+ return raw.slice(0, -1); // remove latest \n
+ };
+
+ let message = [];
+
+ if (headline) {
+ message.push(
+ linebreak(),
+ `${title(commandName)} ${color.green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${headline}`,
+ );
+ }
+
+ if (usage) {
+ message.push(linebreak(), `${color.green(commandName)} ${color.bold(usage)}`);
+ }
+
+ if (tables) {
+ function calculateTablePadding(rows: [string, string][]) {
+ return rows.reduce((val, [first]) => Math.max(val, first.length), 0);
+ }
+ const tableEntries = Object.entries(tables);
+ const padding = Math.max(...tableEntries.map(([, rows]) => calculateTablePadding(rows)));
+ for (const [, tableRows] of tableEntries) {
+ message.push(linebreak(), table(tableRows, { padding }));
+ }
+ }
+
+ if (description) {
+ message.push(linebreak(), `${description}`);
+ }
+
+ log(message.join('\n') + '\n');
+}
diff --git a/packages/create-astro/src/shell.ts b/packages/create-astro/src/shell.ts
new file mode 100644
index 000000000..7c3e22622
--- /dev/null
+++ b/packages/create-astro/src/shell.ts
@@ -0,0 +1,51 @@
+// This is an extremely simplified version of [`execa`](https://github.com/sindresorhus/execa)
+// intended to keep our dependency size down
+import type { ChildProcess, StdioOptions } from 'node:child_process';
+import type { Readable } from 'node:stream';
+
+import { spawn } from 'node:child_process';
+import { text as textFromStream } from 'node:stream/consumers';
+
+export interface ExecaOptions {
+ cwd?: string | URL;
+ stdio?: StdioOptions;
+ timeout?: number;
+}
+export interface Output {
+ stdout: string;
+ stderr: string;
+ exitCode: number;
+}
+const text = (stream: NodeJS.ReadableStream | Readable | null) =>
+ stream ? textFromStream(stream).then((t) => t.trimEnd()) : '';
+
+export async function shell(
+ command: string,
+ flags: string[],
+ opts: ExecaOptions = {},
+): Promise<Output> {
+ let child: ChildProcess;
+ let stdout = '';
+ let stderr = '';
+ try {
+ child = spawn(command, flags, {
+ cwd: opts.cwd,
+ shell: true,
+ stdio: opts.stdio,
+ timeout: opts.timeout,
+ });
+ const done = new Promise((resolve) => child.on('close', resolve));
+ [stdout, stderr] = await Promise.all([text(child.stdout), text(child.stderr)]);
+ await done;
+ } catch {
+ throw { stdout, stderr, exitCode: 1 };
+ }
+ const { exitCode } = child;
+ if (exitCode === null) {
+ throw new Error('Timeout');
+ }
+ if (exitCode !== 0) {
+ throw new Error(stderr);
+ }
+ return { stdout, stderr, exitCode };
+}
diff --git a/packages/create-astro/test/context.test.js b/packages/create-astro/test/context.test.js
new file mode 100644
index 000000000..b4e67a8c6
--- /dev/null
+++ b/packages/create-astro/test/context.test.js
@@ -0,0 +1,73 @@
+import assert from 'node:assert/strict';
+import os from 'node:os';
+import { describe, it } from 'node:test';
+import { getContext } from '../dist/index.js';
+describe('context', () => {
+ it('no arguments', async () => {
+ const ctx = await getContext([]);
+ assert.ok(!ctx.projectName);
+ assert.ok(!ctx.template);
+ assert.deepEqual(ctx.skipHouston, os.platform() === 'win32');
+ assert.ok(!ctx.dryRun);
+ });
+
+ it('project name', async () => {
+ const ctx = await getContext(['foobar']);
+ assert.deepEqual(ctx.projectName, 'foobar');
+ });
+
+ it('template', async () => {
+ const ctx = await getContext(['--template', 'minimal']);
+ assert.deepEqual(ctx.template, 'minimal');
+ });
+
+ it('skip houston (explicit)', async () => {
+ const ctx = await getContext(['--skip-houston']);
+ assert.deepEqual(ctx.skipHouston, true);
+ });
+
+ it('skip houston (yes)', async () => {
+ const ctx = await getContext(['-y']);
+ assert.deepEqual(ctx.skipHouston, true);
+ });
+
+ it('skip houston (no)', async () => {
+ const ctx = await getContext(['-n']);
+ assert.deepEqual(ctx.skipHouston, true);
+ });
+
+ it('skip houston (install)', async () => {
+ const ctx = await getContext(['--install']);
+ assert.deepEqual(ctx.skipHouston, true);
+ });
+
+ it('dry run', async () => {
+ const ctx = await getContext(['--dry-run']);
+ assert.deepEqual(ctx.dryRun, true);
+ });
+
+ it('install', async () => {
+ const ctx = await getContext(['--install']);
+ assert.deepEqual(ctx.install, true);
+ });
+
+ it('add', async () => {
+ const ctx = await getContext(['--add', 'node']);
+ assert.deepEqual(ctx.add, ['node']);
+ });
+
+ it('no install', async () => {
+ const ctx = await getContext(['--no-install']);
+ assert.deepEqual(ctx.install, false);
+ });
+
+ it('git', async () => {
+ const ctx = await getContext(['--git']);
+ assert.deepEqual(ctx.git, true);
+ });
+
+ it('no git', async () => {
+ const ctx = await getContext(['--no-git']);
+ assert.deepEqual(ctx.git, false);
+ });
+});
diff --git a/packages/create-astro/test/dependencies.test.js b/packages/create-astro/test/dependencies.test.js
new file mode 100644
index 000000000..e4eb3d1f4
--- /dev/null
+++ b/packages/create-astro/test/dependencies.test.js
@@ -0,0 +1,80 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { dependencies } from '../dist/index.js';
+import { setup } from './utils.js';
+describe('dependencies', () => {
+ const fixture = setup();
+
+ it('--yes', async () => {
+ const context = {
+ cwd: '',
+ yes: true,
+ packageManager: 'npm',
+ dryRun: true,
+ prompt: () => ({ deps: true }),
+ };
+
+ await dependencies(context);
+
+ assert.ok(fixture.hasMessage('Skipping dependency installation'));
+ });
+
+ it('prompt yes', async () => {
+ const context = {
+ cwd: '',
+ packageManager: 'npm',
+ dryRun: true,
+ prompt: () => ({ deps: true }),
+ install: undefined,
+ };
+
+ await dependencies(context);
+
+ assert.ok(fixture.hasMessage('Skipping dependency installation'));
+ assert.equal(context.install, true);
+ });
+
+ it('prompt no', async () => {
+ const context = {
+ cwd: '',
+ install: true,
+ packageManager: 'npm',
+ dryRun: true,
+ prompt: () => ({ deps: false }),
+ install: undefined,
+ };
+
+ await dependencies(context);
+
+ assert.ok(fixture.hasMessage('Skipping dependency installation'));
+ assert.equal(context.install, false);
+ });
+
+ it('--install', async () => {
+ const context = {
+ cwd: '',
+ install: true,
+ packageManager: 'npm',
+ dryRun: true,
+ prompt: () => ({ deps: false }),
+ };
+ await dependencies(context);
+ assert.ok(fixture.hasMessage('Skipping dependency installation'));
+ assert.equal(context.install, true);
+ });
+
+ it('--no-install ', async () => {
+ const context = {
+ cwd: '',
+ install: false,
+ packageManager: 'npm',
+ dryRun: true,
+ prompt: () => ({ deps: false }),
+ };
+
+ await dependencies(context);
+
+ assert.ok(fixture.hasMessage('Skipping dependency installation'));
+ assert.equal(context.install, false);
+ });
+});
diff --git a/packages/create-astro/test/fixtures/empty/.gitkeep b/packages/create-astro/test/fixtures/empty/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/packages/create-astro/test/fixtures/empty/.gitkeep
diff --git a/packages/create-astro/test/fixtures/not-empty/git.json b/packages/create-astro/test/fixtures/not-empty/git.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/packages/create-astro/test/fixtures/not-empty/git.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/packages/create-astro/test/fixtures/not-empty/package.json b/packages/create-astro/test/fixtures/not-empty/package.json
new file mode 100644
index 000000000..d3f61d640
--- /dev/null
+++ b/packages/create-astro/test/fixtures/not-empty/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/create-astro-not-empty",
+ "private": true,
+ "scripts": {
+ "dev": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview"
+ }
+} \ No newline at end of file
diff --git a/packages/create-astro/test/fixtures/not-empty/tsconfig.json b/packages/create-astro/test/fixtures/not-empty/tsconfig.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/packages/create-astro/test/fixtures/not-empty/tsconfig.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/packages/create-astro/test/git.test.js b/packages/create-astro/test/git.test.js
new file mode 100644
index 000000000..85854f0d5
--- /dev/null
+++ b/packages/create-astro/test/git.test.js
@@ -0,0 +1,57 @@
+import assert from 'node:assert/strict';
+import { rmSync } from 'node:fs';
+import { mkdir, writeFile } from 'node:fs/promises';
+import { after, before, describe, it } from 'node:test';
+
+import { git } from '../dist/index.js';
+import { setup } from './utils.js';
+
+describe('git', () => {
+ const fixture = setup();
+
+ it('none', async () => {
+ const context = { cwd: '', dryRun: true, prompt: () => ({ git: false }) };
+ await git(context);
+
+ assert.ok(fixture.hasMessage('Skipping Git initialization'));
+ });
+
+ it('yes (--dry-run)', async () => {
+ const context = { cwd: '', dryRun: true, prompt: () => ({ git: true }) };
+ await git(context);
+ assert.ok(fixture.hasMessage('Skipping Git initialization'));
+ });
+
+ it('no (--dry-run)', async () => {
+ const context = { cwd: '', dryRun: true, prompt: () => ({ git: false }) };
+ await git(context);
+
+ assert.ok(fixture.hasMessage('Skipping Git initialization'));
+ });
+});
+
+describe('git initialized', () => {
+ const fixture = setup();
+ const dir = new URL(new URL('./fixtures/not-empty/.git', import.meta.url));
+
+ before(async () => {
+ await mkdir(dir, { recursive: true });
+ await writeFile(new URL('./git.json', dir), '{}', { encoding: 'utf8' });
+ });
+
+ it('already initialized', async () => {
+ const context = {
+ git: true,
+ cwd: './test/fixtures/not-empty',
+ dryRun: false,
+ prompt: () => ({ git: false }),
+ };
+ await git(context);
+
+ assert.ok(fixture.hasMessage('Git has already been initialized'));
+ });
+
+ after(() => {
+ rmSync(dir, { recursive: true, force: true });
+ });
+});
diff --git a/packages/create-astro/test/integrations.test.js b/packages/create-astro/test/integrations.test.js
new file mode 100644
index 000000000..412285223
--- /dev/null
+++ b/packages/create-astro/test/integrations.test.js
@@ -0,0 +1,64 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { dependencies } from '../dist/index.js';
+import { setup } from './utils.js';
+describe('integrations', () => {
+ const fixture = setup();
+
+ it('--add node', async () => {
+ const context = {
+ cwd: '',
+ yes: true,
+ packageManager: 'npm',
+ dryRun: true,
+ add: ['node'],
+ };
+
+ await dependencies(context);
+
+ assert.ok(fixture.hasMessage('--dry-run Skipping dependency installation and adding node'));
+ });
+
+ it('--add node --add react', async () => {
+ const context = {
+ cwd: '',
+ yes: true,
+ packageManager: 'npm',
+ dryRun: true,
+ add: ['node', 'react'],
+ };
+
+ await dependencies(context);
+
+ assert.ok(
+ fixture.hasMessage('--dry-run Skipping dependency installation and adding node, react'),
+ );
+ });
+
+ it('--add node,react', async () => {
+ const context = {
+ cwd: '',
+ yes: true,
+ packageManager: 'npm',
+ dryRun: true,
+ add: ['node,react'],
+ };
+
+ await dependencies(context);
+
+ assert.ok(
+ fixture.hasMessage('--dry-run Skipping dependency installation and adding node, react'),
+ );
+ });
+
+ it('-y', async () => {
+ const context = {
+ cwd: '',
+ yes: true,
+ packageManager: 'npm',
+ dryRun: true,
+ };
+ await dependencies(context);
+ assert.ok(fixture.hasMessage('--dry-run Skipping dependency installation'));
+ });
+});
diff --git a/packages/create-astro/test/intro.test.js b/packages/create-astro/test/intro.test.js
new file mode 100644
index 000000000..d042dad7f
--- /dev/null
+++ b/packages/create-astro/test/intro.test.js
@@ -0,0 +1,20 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { intro } from '../dist/index.js';
+import { setup } from './utils.js';
+
+describe('intro', () => {
+ const fixture = setup();
+
+ it('no arguments', async () => {
+ await intro({ skipHouston: false, version: '0.0.0', username: 'user' });
+ assert.ok(fixture.hasMessage('Houston:'));
+ assert.ok(fixture.hasMessage('Welcome to astro v0.0.0'));
+ });
+ it('--skip-houston', async () => {
+ await intro({ skipHouston: true, version: '0.0.0', username: 'user' });
+ assert.equal(fixture.length(), 1);
+ assert.ok(!fixture.hasMessage('Houston:'));
+ assert.ok(fixture.hasMessage('Launch sequence initiated'));
+ });
+});
diff --git a/packages/create-astro/test/next.test.js b/packages/create-astro/test/next.test.js
new file mode 100644
index 000000000..5b9b22b30
--- /dev/null
+++ b/packages/create-astro/test/next.test.js
@@ -0,0 +1,20 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { next } from '../dist/index.js';
+import { setup } from './utils.js';
+
+describe('next steps', () => {
+ const fixture = setup();
+
+ it('no arguments', async () => {
+ await next({ skipHouston: false, cwd: './it/fixtures/not-empty', packageManager: 'npm' });
+ assert.ok(fixture.hasMessage('Liftoff confirmed.'));
+ assert.ok(fixture.hasMessage('npm run dev'));
+ assert.ok(fixture.hasMessage('Good luck out there, astronaut!'));
+ });
+
+ it('--skip-houston', async () => {
+ await next({ skipHouston: true, cwd: './it/fixtures/not-empty', packageManager: 'npm' });
+ assert.ok(!fixture.hasMessage('Good luck out there, astronaut!'));
+ });
+});
diff --git a/packages/create-astro/test/project-name.test.js b/packages/create-astro/test/project-name.test.js
new file mode 100644
index 000000000..0aebd1c79
--- /dev/null
+++ b/packages/create-astro/test/project-name.test.js
@@ -0,0 +1,124 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { projectName } from '../dist/index.js';
+import { setup } from './utils.js';
+
+describe('project name', async () => {
+ const fixture = setup();
+
+ it('pass in name', async () => {
+ const context = { projectName: '', cwd: './foo/bar/baz', prompt: () => {} };
+ await projectName(context);
+ assert.equal(context.cwd, './foo/bar/baz');
+ assert.equal(context.projectName, 'baz');
+ });
+
+ it('dot', async () => {
+ const context = { projectName: '', cwd: '.', prompt: () => ({ name: 'foobar' }) };
+ await projectName(context);
+ assert.ok(fixture.hasMessage('"." is not empty!'));
+ assert.equal(context.projectName, 'foobar');
+ });
+
+ it('dot slash', async () => {
+ const context = { projectName: '', cwd: './', prompt: () => ({ name: 'foobar' }) };
+ await projectName(context);
+ assert.ok(fixture.hasMessage('"./" is not empty!'));
+ assert.equal(context.projectName, 'foobar');
+ });
+
+ it('empty', async () => {
+ const context = {
+ projectName: '',
+ cwd: './test/fixtures/empty',
+ prompt: () => ({ name: 'foobar' }),
+ };
+ await projectName(context);
+ assert.ok(!fixture.hasMessage('"./test/fixtures/empty" is not empty!'));
+ assert.equal(context.projectName, 'empty');
+ });
+
+ it('not empty', async () => {
+ const context = {
+ projectName: '',
+ cwd: './test/fixtures/not-empty',
+ prompt: () => ({ name: 'foobar' }),
+ };
+ await projectName(context);
+ assert.ok(fixture.hasMessage('"./test/fixtures/not-empty" is not empty!'));
+ assert.equal(context.projectName, 'foobar');
+ });
+
+ it('basic', async () => {
+ const context = { projectName: '', cwd: '', prompt: () => ({ name: 'foobar' }) };
+ await projectName(context);
+ assert.equal(context.cwd, 'foobar');
+ assert.equal(context.projectName, 'foobar');
+ });
+
+ it('head and tail blank spaces should be trimmed', async () => {
+ const context = { projectName: '', cwd: '', prompt: () => ({ name: ' foobar ' }) };
+ await projectName(context);
+ assert.equal(context.cwd, 'foobar');
+ assert.equal(context.projectName, 'foobar');
+ });
+
+ it('normalize', async () => {
+ const context = { projectName: '', cwd: '', prompt: () => ({ name: 'Invalid Name' }) };
+ await projectName(context);
+ assert.equal(context.cwd, 'Invalid Name');
+ assert.equal(context.projectName, 'invalid-name');
+ });
+
+ it('remove leading/trailing dashes', async () => {
+ const context = { projectName: '', cwd: '', prompt: () => ({ name: '(invalid)' }) };
+ await projectName(context);
+ assert.equal(context.projectName, 'invalid');
+ });
+
+ it('handles scoped packages', async () => {
+ const context = { projectName: '', cwd: '', prompt: () => ({ name: '@astro/site' }) };
+ await projectName(context);
+ assert.equal(context.cwd, '@astro/site');
+ assert.equal(context.projectName, '@astro/site');
+ });
+
+ it('--yes', async () => {
+ const context = { projectName: '', cwd: './foo/bar/baz', yes: true, prompt: () => {} };
+ await projectName(context);
+ assert.equal(context.projectName, 'baz');
+ });
+
+ it('dry run with name', async () => {
+ const context = {
+ projectName: '',
+ cwd: './foo/bar/baz',
+ dryRun: true,
+ prompt: () => {},
+ };
+ await projectName(context);
+ assert.equal(context.projectName, 'baz');
+ });
+
+ it('dry run with dot', async () => {
+ const context = {
+ projectName: '',
+ cwd: '.',
+ dryRun: true,
+ prompt: () => ({ name: 'foobar' }),
+ };
+ await projectName(context);
+ assert.equal(context.projectName, 'foobar');
+ });
+
+ it('dry run with empty', async () => {
+ const context = {
+ projectName: '',
+ cwd: './test/fixtures/empty',
+ dryRun: true,
+ prompt: () => ({ name: 'foobar' }),
+ };
+ await projectName(context);
+ assert.equal(context.projectName, 'empty');
+ });
+});
diff --git a/packages/create-astro/test/template.test.js b/packages/create-astro/test/template.test.js
new file mode 100644
index 000000000..821ac9c2e
--- /dev/null
+++ b/packages/create-astro/test/template.test.js
@@ -0,0 +1,39 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { template } from '../dist/index.js';
+import { setup } from './utils.js';
+
+describe('template', async () => {
+ const fixture = setup();
+
+ it('none', async () => {
+ const context = { template: '', cwd: '', dryRun: true, prompt: () => ({ template: 'blog' }) };
+ await template(context);
+ assert.ok(fixture.hasMessage('Skipping template copying'));
+ assert.equal(context.template, 'blog');
+ });
+
+ it('minimal (--dry-run)', async () => {
+ const context = { template: 'minimal', cwd: '', dryRun: true, prompt: () => {} };
+ await template(context);
+ assert.ok(fixture.hasMessage('Using minimal as project template'));
+ });
+
+ it('basics (--dry-run)', async () => {
+ const context = { template: 'basics', cwd: '', dryRun: true, prompt: () => {} };
+ await template(context);
+ assert.ok(fixture.hasMessage('Using basics as project template'));
+ });
+
+ it('blog (--dry-run)', async () => {
+ const context = { template: 'blog', cwd: '', dryRun: true, prompt: () => {} };
+ await template(context);
+ assert.ok(fixture.hasMessage('Using blog as project template'));
+ });
+
+ it('minimal (--yes)', async () => {
+ const context = { template: 'minimal', cwd: '', dryRun: true, yes: true, prompt: () => {} };
+ await template(context);
+ assert.ok(fixture.hasMessage('Using minimal as project template'));
+ });
+});
diff --git a/packages/create-astro/test/utils.js b/packages/create-astro/test/utils.js
new file mode 100644
index 000000000..dfae93c33
--- /dev/null
+++ b/packages/create-astro/test/utils.js
@@ -0,0 +1,63 @@
+import fs from 'node:fs';
+import { before, beforeEach } from 'node:test';
+import { stripVTControlCharacters } from 'node:util';
+import { setStdout } from '../dist/index.js';
+
+export function setup() {
+ const ctx = { messages: [] };
+ before(() => {
+ setStdout(
+ Object.assign({}, process.stdout, {
+ write(buf) {
+ ctx.messages.push(stripVTControlCharacters(String(buf)).trim());
+ return true;
+ },
+ }),
+ );
+ });
+ beforeEach(() => {
+ ctx.messages = [];
+ });
+
+ return {
+ messages() {
+ return ctx.messages;
+ },
+ length() {
+ return ctx.messages.length;
+ },
+ hasMessage(content) {
+ return !!ctx.messages.find((msg) => msg.includes(content));
+ },
+ };
+}
+
+const resetEmptyFixture = () =>
+ fs.promises.rm(new URL('./fixtures/empty/tsconfig.json', import.meta.url));
+
+const resetNotEmptyFixture = async () => {
+ const packagePath = new URL('./fixtures/not-empty/package.json', import.meta.url);
+ const tsconfigPath = new URL('./fixtures/not-empty/tsconfig.json', import.meta.url);
+
+ const packageJsonData = JSON.parse(
+ await fs.promises.readFile(packagePath, { encoding: 'utf-8' }),
+ );
+ const overriddenPackageJson = Object.assign(packageJsonData, {
+ scripts: {
+ dev: 'astro dev',
+ build: 'astro build',
+ preview: 'astro preview',
+ },
+ dependencies: undefined,
+ });
+
+ return Promise.all([
+ fs.promises.writeFile(packagePath, JSON.stringify(overriddenPackageJson, null, 2), {
+ encoding: 'utf-8',
+ }),
+ fs.promises.writeFile(tsconfigPath, '{}', { encoding: 'utf-8' }),
+ ]);
+};
+
+export const resetFixtures = () =>
+ Promise.allSettled([resetEmptyFixture(), resetNotEmptyFixture()]);
diff --git a/packages/create-astro/test/verify.test.js b/packages/create-astro/test/verify.test.js
new file mode 100644
index 000000000..ff3350145
--- /dev/null
+++ b/packages/create-astro/test/verify.test.js
@@ -0,0 +1,41 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { verify } from '../dist/index.js';
+import { setup } from './utils.js';
+
+describe('verify', async () => {
+ const fixture = setup();
+ const exit = (code) => {
+ throw code;
+ };
+
+ it('basics', async () => {
+ const context = { template: 'basics', exit };
+ await verify(context);
+ assert.equal(fixture.messages().length, 0, 'Did not expect `verify` to log any messages');
+ });
+
+ it('missing', async () => {
+ const context = { template: 'missing', exit };
+ let err = null;
+ try {
+ await verify(context);
+ } catch (e) {
+ err = e;
+ }
+ assert.equal(err, 1);
+ assert.ok(!fixture.hasMessage('Template missing does not exist!'));
+ });
+
+ it('starlight', async () => {
+ const context = { template: 'starlight', exit };
+ await verify(context);
+ assert.equal(fixture.messages().length, 0, 'Did not expect `verify` to log any messages');
+ });
+
+ it('starlight/tailwind', async () => {
+ const context = { template: 'starlight/tailwind', exit };
+ await verify(context);
+ assert.equal(fixture.messages().length, 0, 'Did not expect `verify` to log any messages');
+ });
+});
diff --git a/packages/create-astro/tsconfig.json b/packages/create-astro/tsconfig.json
new file mode 100644
index 000000000..18443cddf
--- /dev/null
+++ b/packages/create-astro/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "outDir": "./dist"
+ }
+}