├── .editorconfig ├── .github ├── lock.yml ├── stale.yml └── workflows │ └── test.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── LICENSE.md ├── README.md ├── bin ├── run.ts └── test.ts ├── commands └── main.ts ├── index.ts ├── package.json ├── src ├── auth_guards.ts ├── databases.ts ├── inertia_adapters.ts └── templates.ts ├── tests └── install_adonis.spec.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | insert_final_newline = ignore 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ignoreUnless: { { STALE_BOT } } 3 | --- 4 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads-app 5 | 6 | # Number of days of inactivity before a closed issue or pull request is locked 7 | daysUntilLock: 60 8 | 9 | # Skip issues and pull requests created before a given timestamp. Timestamp must 10 | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable 11 | skipCreatedBefore: false 12 | 13 | # Issues and pull requests with these labels will be ignored. Set to `[]` to disable 14 | exemptLabels: ['Type: Security'] 15 | 16 | # Label to add before locking, such as `outdated`. Set to `false` to disable 17 | lockLabel: false 18 | 19 | # Comment to post before locking. Set to `false` to disable 20 | lockComment: > 21 | This thread has been automatically locked since there has not been 22 | any recent activity after it was closed. Please open a new issue for 23 | related bugs. 24 | 25 | # Assign `resolved` as the reason for locking. Set to `false` to disable 26 | setLockReason: false 27 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ignoreUnless: { { STALE_BOT } } 3 | --- 4 | # Number of days of inactivity before an issue becomes stale 5 | daysUntilStale: 60 6 | 7 | # Number of days of inactivity before a stale issue is closed 8 | daysUntilClose: 7 9 | 10 | # Issues with these labels will never be considered stale 11 | exemptLabels: 12 | - 'Type: Security' 13 | 14 | # Label to use when marking an issue as stale 15 | staleLabel: 'Status: Abandoned' 16 | 17 | # Comment to post when marking an issue as stale. Set to `false` to disable 18 | markComment: > 19 | This issue has been automatically marked as stale because it has not had 20 | recent activity. It will be closed if no further activity occurs. Thank you 21 | for your contributions. 22 | 23 | # Comment to post when closing a stale issue. Set to `false` to disable 24 | closeComment: false 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | # Note : i didn't inherited from the org workflow since we need to install pnpm in this 7 | # workflow. we may need to add a new input to the org workflow to enable/disable pnpm 8 | # installation 9 | 10 | test_linux: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [20.x, 21.x] 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Install pnpm 26 | run: npm install -g pnpm 27 | 28 | - name: Install dependencies 29 | run: npm install 30 | 31 | - name: Run tests 32 | run: npm test 33 | 34 | test_windows: 35 | if: ${{ !inputs.disable-windows }} 36 | runs-on: windows-latest 37 | strategy: 38 | matrix: 39 | node-version: [20.x, 21.x] 40 | 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v3 44 | 45 | - name: Setup Node.js 46 | uses: actions/setup-node@v3 47 | with: 48 | node-version: ${{ matrix.node-version }} 49 | 50 | - name: Install pnpm 51 | run: npm install -g pnpm 52 | 53 | - name: Install dependencies 54 | run: npm install 55 | 56 | - name: Run tests 57 | run: npm test 58 | 59 | lint: 60 | uses: adonisjs/.github/.github/workflows/lint.yml@main 61 | 62 | typecheck: 63 | uses: adonisjs/.github/.github/workflows/typecheck.yml@main 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | .env 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | docs 3 | coverage 4 | *.html 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright (c) 2023 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-adonisjs 2 | 3 | Scaffold a new AdonisJS application using starter kits 4 | 5 |
6 | 7 | [![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url] 8 | 9 | ## Starter kits 10 | 11 | You can use between one of the following official starter kits, or bring your own using the `--kit` flag. 12 | 13 | - `api` : AdonisJS application tailored for building HTTP APIs. 14 | - `web` : AdonisJS application tailored for building server-side rendered applications. 15 | - `slim` : Smallest possible AdonisJS application. Still way more powerful and batteries included than a express application. 16 | - `inertia`: AdonisJS application tailored for building applications using InertiaJS and your favorite frontend framework (Vue, React, Svelte, Solid). 17 | 18 | ## Usage 19 | 20 | ```sh 21 | # Using npm 22 | npm init adonisjs 23 | 24 | # Using yarn 25 | yarn create adonisjs 26 | 27 | # Using pnpm 28 | pnpm create adonisjs 29 | ``` 30 | 31 | ## Options 32 | 33 | ### `destination` 34 | 35 | You can pass the destination directory as the first argument to the command. For example: 36 | 37 | ```sh 38 | npm init adonisjs my-app 39 | ``` 40 | 41 | This argument is optional and the command will prompt you to enter the directory name if not provided. 42 | 43 | > **Note** - The directory must be empty otherwise the command will fail. 44 | 45 | ### `--kit` (Default: Triggers prompt for selection) 46 | 47 | If you want to use your own starter kit hosted on Github, Gitlab, or Bitbucket, you can use the `--kit` flag to define the repo URL. 48 | 49 | ```sh 50 | # Download from GitHub 51 | npm init adonisjs -- --kit="github:github_user/repo" 52 | 53 | # Github is the default provider, so if not specified, it will be assumed as github 54 | npm init adonisjs -- --kit="github_user/repo" 55 | 56 | # Download from GitLab 57 | npm init adonisjs -- --kit="gitlab:user/repo" 58 | 59 | # Download from BitBucket 60 | npm init adonisjs -- --kit="bitbucket:user/repo" 61 | ``` 62 | 63 | You can also pass specify the branch or tag name as follows: 64 | 65 | ```sh 66 | # Branch name 67 | npm init adonisjs -- --kit="github:github_user/repo#branch-name" 68 | 69 | # Tag name 70 | npm init adonisjs -- --kit="github:github_user/repo#v1.0.0" 71 | ``` 72 | 73 | ### `--token` (Default: undefined) 74 | 75 | If you are using a custom starter kit hosted on a private repository, then you can pass the authentication token as follows: 76 | 77 | ```sh 78 | npm init adonisjs -- --kit="github:github_user/repo" --token="github_token" 79 | ``` 80 | 81 | ### `--pkg` (Default: Auto detects) 82 | 83 | We are trying to detect the package manager used by your project. However, if you want to force a specific package manager, then you can pass it as follows: 84 | 85 | ```sh 86 | npm init adonisjs -- --pkg="yarn" 87 | ``` 88 | 89 | ### `--auth-guard` (Default: Triggers prompt for selection) 90 | 91 | Specify a custom auth guard to use when using the `api` stater kit. One of the following options are allowed 92 | 93 | - `session` 94 | - `access_tokens` 95 | 96 | ```sh 97 | npm init adonisjs -- --kit="api" --auth-guard="access_tokens" 98 | ``` 99 | 100 | ### `--db` (Default: Triggers prompt for selection) 101 | 102 | Specify the database dialect to configure with Lucid. One of the following options are allowd. 103 | 104 | - `sqlite` 105 | - `mysql` 106 | - `mssql` 107 | - `postgres` 108 | 109 | ```sh 110 | npm init adonisjs -- --kit="web" --db="mysql" 111 | ``` 112 | 113 | ### Other options 114 | 115 | | Option | Description | 116 | | ------------ | --------------------------------------- | 117 | | `--git-init` | Initialize git repository. | 118 | | `--verbose` | Enable verbose mode to display all logs | 119 | 120 | ## Debugging errors 121 | 122 | If creating a new project fails, then you must re-run the same command with the `--verbose` flag to view all the logs. 123 | 124 | ```sh 125 | npm init adonisjs -- --verbose 126 | ``` 127 | 128 | ## Contributing 129 | 130 | One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believes in the principles of the framework. 131 | 132 | We encourage you to read the [contribution guide](https://github.com/adonisjs/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework. 133 | 134 | ## Code of Conduct 135 | 136 | In order to ensure that the AdonisJS community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/adonisjs/.github/blob/main/docs/CODE_OF_CONDUCT.md). 137 | 138 | ## License 139 | 140 | create-adonisjs is open-sourced software licensed under the [MIT license](LICENSE.md). 141 | 142 | [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/create-adonisjs/test.yml?style=for-the-badge 143 | [gh-workflow-url]: https://github.com/adonisjs/create-adonisjs/actions/workflows/test.yml 'Github action' 144 | [npm-image]: https://img.shields.io/npm/v/create-adonisjs/latest.svg?style=for-the-badge&logo=npm 145 | [npm-url]: https://www.npmjs.com/package/create-adonisjs/v/latest 'npm' 146 | [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript 147 | [license-url]: LICENSE.md 148 | [license-image]: https://img.shields.io/github/license/adonisjs/create-adonisjs?style=for-the-badge 149 | -------------------------------------------------------------------------------- /bin/run.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { kernel } from '../index.js' 4 | 5 | kernel.handle(['create-adonisjs', ...process.argv.slice(2)]).catch(console.error) 6 | -------------------------------------------------------------------------------- /bin/test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { processCLIArgs, configure, run } from '@japa/runner' 3 | import { fileSystem } from '@japa/file-system' 4 | 5 | /* 6 | |-------------------------------------------------------------------------- 7 | | Configure tests 8 | |-------------------------------------------------------------------------- 9 | | 10 | | The configure method accepts the configuration to configure the Japa 11 | | tests runner. 12 | | 13 | | The first method call "processCLIArgs" process the command line arguments 14 | | and turns them into a config object. Using this method is not mandatory. 15 | | 16 | | Please consult japa.dev/runner-config for the config docs. 17 | */ 18 | processCLIArgs(process.argv.slice(2)) 19 | configure({ 20 | files: ['tests/**/*.spec.ts'], 21 | plugins: [assert(), fileSystem({ basePath: 'tmp', autoClean: true })], 22 | timeout: 30 * 1000, 23 | }) 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Run tests 28 | |-------------------------------------------------------------------------- 29 | | 30 | | The following "run" method is required to execute all the tests. 31 | | 32 | */ 33 | run() 34 | -------------------------------------------------------------------------------- /commands/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * create-adonisjs 3 | * 4 | * (c) AdonisJS 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { cwd } from 'node:process' 11 | import { existsSync } from 'node:fs' 12 | import gradient from 'gradient-string' 13 | import { downloadTemplate } from 'giget' 14 | import { type Options, execa } from 'execa' 15 | import detectPackageManager from 'which-pm-runs' 16 | import { installPackage } from '@antfu/install-pkg' 17 | import { BaseCommand, args, flags } from '@adonisjs/ace' 18 | import { basename, isAbsolute, join, relative } from 'node:path' 19 | import { copyFile, readFile, unlink, writeFile } from 'node:fs/promises' 20 | 21 | import { templates } from '../src/templates.js' 22 | import { databases } from '../src/databases.js' 23 | import { authGuards } from '../src/auth_guards.js' 24 | import { adapters } from '../src/inertia_adapters.js' 25 | 26 | const API_STARTER_KIT = 'github:adonisjs/api-starter-kit' 27 | const WEB_STARTER_KIT = 'github:adonisjs/web-starter-kit' 28 | const INERTIA_STARTER_KIT = 'github:adonisjs/inertia-starter-kit' 29 | 30 | /** 31 | * Creates a new AdonisJS application and configures it 32 | */ 33 | export class CreateNewApp extends BaseCommand { 34 | static commandName = 'create-adonisjs' 35 | static description = 'Create a new AdonisJS application' 36 | 37 | /** 38 | * The directory where the project will be created 39 | */ 40 | @args.string({ description: 'Destination directory', required: false }) 41 | declare destination: string 42 | 43 | /** 44 | * The starter kit to use 45 | * 46 | * @example 47 | * --kit github_user/repo 48 | * --kit gitlab_user/repo#develop 49 | * --kit bitbucket_user/repo#2.0.0 50 | */ 51 | @flags.string({ 52 | description: 'Define path to a custom git repository to download the starter kit', 53 | alias: 'K', 54 | }) 55 | declare kit?: string 56 | 57 | /** 58 | * Authentication token to download private templates 59 | */ 60 | @flags.string({ 61 | description: 'Auth token to download private repositories', 62 | alias: 't', 63 | }) 64 | declare token?: string 65 | 66 | /** 67 | * Init git repository. Do not init when flag is not mentioned. 68 | */ 69 | @flags.boolean({ 70 | description: 'Init git repository', 71 | }) 72 | declare gitInit?: boolean 73 | 74 | /** 75 | * Package manager to use. Detect package manager when flag is not 76 | * mentioned. 77 | */ 78 | @flags.string({ 79 | description: 'Define the package manager to install dependencies', 80 | flagName: 'pkg', 81 | }) 82 | declare packageManager: string 83 | 84 | /** 85 | * Database dialect for Lucid. Defaults to "sqlite" 86 | */ 87 | @flags.string({ 88 | description: 'Define the database dialect to use with Lucid', 89 | }) 90 | declare db?: string 91 | 92 | /** 93 | * Auth guard for auth package. 94 | */ 95 | @flags.string({ 96 | description: 'Define the authentication guard with the Auth package', 97 | }) 98 | declare authGuard?: string 99 | 100 | /** 101 | * Inertia adapter to use 102 | */ 103 | @flags.string({ 104 | description: 'Define the Inertia frontend adapter', 105 | }) 106 | declare adapter?: string 107 | 108 | /** 109 | * Inertia adapter to use 110 | */ 111 | @flags.boolean({ 112 | description: 'Enable SSR for Inertia', 113 | }) 114 | declare ssr?: boolean 115 | 116 | /** 117 | * Execute tasks in verbose mode. Defaults to false. 118 | */ 119 | @flags.boolean({ 120 | description: 'Execute tasks in verbose mode', 121 | alias: 'v', 122 | }) 123 | declare verbose?: boolean 124 | 125 | /** 126 | * Runs bash command using execa with shared defaults 127 | */ 128 | async #runBashCommand(file: string, cliArgs: string[], options?: Options) { 129 | await execa(file, cliArgs, { 130 | cwd: this.destination, 131 | preferLocal: true, 132 | windowsHide: false, 133 | buffer: false, 134 | stdio: this.verbose === true ? 'inherit' : 'ignore', 135 | ...options, 136 | }) 137 | } 138 | 139 | /** 140 | * Prints AdonisJS as ASCII art 141 | */ 142 | #printBannerArt() { 143 | const title = Buffer.from( 144 | 'ICAgICBfICAgICAgIF8gICAgICAgICAgICAgXyAgICAgICAgIF8gX19fXyAgCiAgICAvIFwgICBfX3wgfCBfX18gIF8gX18gKF8pX19fICAgIHwgLyBfX198IAogICAvIF8gXCAvIF9gIHwvIF8gXHwgJ18gXHwgLyBfX3xfICB8IFxfX18gXCAKICAvIF9fXyBcIChffCB8IChfKSB8IHwgfCB8IFxfXyBcIHxffCB8X19fKSB8CiAvXy8gICBcX1xfXyxffFxfX18vfF98IHxffF98X19fL1xfX18vfF9fX18vIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA=', 145 | 'base64' 146 | ).toString() 147 | 148 | this.logger.log('') 149 | this.logger.log(`${gradient.mind.multiline(title)}`) 150 | this.logger.log('') 151 | } 152 | 153 | /** 154 | * Print the success message 155 | */ 156 | #printSuccessMessage() { 157 | this.logger.log('') 158 | 159 | this.ui 160 | .instructions() 161 | .heading('Your AdonisJS project has been created successfully!') 162 | .add(this.colors.cyan('cd ' + relative(cwd(), this.destination))) 163 | .add(this.colors.cyan(`${this.packageManager} run dev`)) 164 | .add(this.colors.cyan('Open http://localhost:3333')) 165 | .add('') 166 | .add(`Have any questions?`) 167 | .add(`Join our Discord server - ${this.colors.yellow('https://discord.gg/vDcEjq6')}`) 168 | .render() 169 | } 170 | 171 | /** 172 | * Prompt for the destination directory 173 | */ 174 | async #promptForDestination() { 175 | if (!this.destination) { 176 | this.destination = await this.prompt.ask('Where should we create your new project', { 177 | default: './my-adonisjs-app', 178 | }) 179 | } 180 | 181 | this.destination = isAbsolute(this.destination) 182 | ? this.destination 183 | : join(cwd(), this.destination) 184 | } 185 | 186 | /** 187 | * Prompt to configure a starter kit 188 | */ 189 | async #promptForStarterKit() { 190 | if (!this.kit) { 191 | /** 192 | * Display prompt when "kit" flag is not used. 193 | */ 194 | const template = await this.prompt.choice( 195 | 'Which starter kit would you like to use', 196 | templates 197 | ) 198 | this.kit = templates.find((t) => t.name === template)!.source 199 | } else { 200 | /** 201 | * Allowing users to mention aliases via the CLI flag. 202 | */ 203 | const matchingTemplatingFromAlias = templates.find((t) => t.alias === this.kit) 204 | if (matchingTemplatingFromAlias) { 205 | this.kit = matchingTemplatingFromAlias.source 206 | } 207 | } 208 | } 209 | 210 | /** 211 | * Prompt to select a database driver 212 | */ 213 | async #promptForDatabaseDriver() { 214 | if (!this.db) { 215 | /** 216 | * Display prompt when "db" flag is not used. 217 | */ 218 | const database = await this.prompt.choice('Which database driver you want to use', databases) 219 | this.db = database 220 | } 221 | } 222 | 223 | /** 224 | * Prompt to select a auth guard 225 | */ 226 | async #promptForAuthGuard() { 227 | if (!this.authGuard) { 228 | /** 229 | * Display prompt when "authGuard" flag is not used. 230 | */ 231 | const guard = await this.prompt.choice( 232 | 'Which authentication guard you want to use', 233 | authGuards 234 | ) 235 | this.authGuard = guard 236 | } 237 | } 238 | 239 | /** 240 | * Prompt to select the Inertia adapter 241 | */ 242 | async #promptForInertiaAdapter() { 243 | if (!this.adapter) { 244 | const adapter = await this.prompt.choice( 245 | 'Which frontend adapter you want to use with Inertia', 246 | adapters 247 | ) 248 | this.adapter = adapter 249 | } 250 | } 251 | 252 | /** 253 | * Prompt to select the Inertia adapter 254 | */ 255 | async #promptForInertiaSsr() { 256 | if (this.ssr === undefined) { 257 | this.ssr = await this.prompt.confirm( 258 | 'Do you want to setup server-side rendering with Inertia' 259 | ) 260 | } 261 | } 262 | 263 | /** 264 | * Replace the package.json name with the destination directory name. 265 | * Errors are ignored. 266 | */ 267 | async #replacePackageJsonName() { 268 | const pkgJsonPath = join(this.destination, 'package.json') 269 | 270 | const pkgJson = await readFile(pkgJsonPath, 'utf-8').then(JSON.parse) 271 | pkgJson.name = basename(this.destination) 272 | 273 | await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2)) 274 | } 275 | 276 | /** 277 | * Optionally removes readme file. Errors are ignored 278 | */ 279 | async #removeReadmeFile() { 280 | await unlink(join(this.destination, 'README.md')) 281 | } 282 | 283 | /** 284 | * Optionally remove existing lock file. Errors are ignored 285 | */ 286 | async #removeLockFile() { 287 | await Promise.allSettled([ 288 | unlink(join(this.destination, 'package-lock.json')), 289 | unlink(join(this.destination, 'yarn.lock')), 290 | unlink(join(this.destination, 'pnpm-lock.yaml')), 291 | ]) 292 | } 293 | 294 | /** 295 | * If starter template has an `.env.example` file, then copy it to `.env` 296 | */ 297 | async #copyEnvExampleFile() { 298 | const envPath = join(this.destination, '.env') 299 | const envExamplePath = join(this.destination, '.env.example') 300 | 301 | if (existsSync(envExamplePath)) { 302 | await copyFile(envExamplePath, envPath) 303 | } 304 | } 305 | 306 | /** 307 | * Generate a fresh app key. Errors are ignored 308 | */ 309 | async #generateFreshAppKey() { 310 | await this.#runBashCommand('node', ['ace', 'generate:key']) 311 | } 312 | 313 | /** 314 | * Configures the Lucid package 315 | */ 316 | async #configureLucid() { 317 | const argv = ['ace', 'configure', '@adonisjs/lucid', '--db', this.db!, '--install'] 318 | if (this.verbose) { 319 | argv.push('--verbose') 320 | } 321 | 322 | await this.#runBashCommand('node', argv) 323 | } 324 | 325 | /** 326 | * Configures the session package 327 | */ 328 | async #configureSession() { 329 | await installPackage(['@adonisjs/session@latest'], { 330 | cwd: this.destination, 331 | packageManager: this.packageManager, 332 | silent: !this.verbose, 333 | }) 334 | 335 | const argv = ['ace', 'configure', '@adonisjs/session'] 336 | if (this.verbose) { 337 | argv.push('--verbose') 338 | } 339 | 340 | await this.#runBashCommand('node', argv) 341 | } 342 | 343 | /** 344 | * Configures the Auth package 345 | */ 346 | async #configureAuth() { 347 | /** 348 | * Install the session package when using api starter kit with session 349 | * guard. This needs to be done, since the api starter kit does 350 | * not install the session package by default. 351 | */ 352 | if (this.authGuard === 'session' && this.kit === API_STARTER_KIT) { 353 | await this.#configureSession() 354 | } 355 | 356 | /** 357 | * Next configure the auth package 358 | */ 359 | const argv = ['ace', 'configure', '@adonisjs/auth', '--guard', this.authGuard!] 360 | if (this.verbose) { 361 | argv.push('--verbose') 362 | } 363 | 364 | await this.#runBashCommand('node', argv) 365 | } 366 | 367 | /** 368 | * Configures the Inertia package 369 | */ 370 | async #configureInertia() { 371 | const argv = [ 372 | 'ace', 373 | 'configure', 374 | '@adonisjs/inertia', 375 | '--adapter', 376 | this.adapter!, 377 | this.ssr ? '--ssr' : '--no-ssr', 378 | '--install', 379 | ] 380 | 381 | if (this.verbose) { 382 | argv.push('--verbose') 383 | } 384 | 385 | await this.#runBashCommand('node', argv) 386 | } 387 | 388 | /** 389 | * Main method 390 | */ 391 | async run() { 392 | this.packageManager = this.packageManager || detectPackageManager()?.name || 'npm' 393 | 394 | /** 395 | * Print ASCII art 396 | */ 397 | this.#printBannerArt() 398 | 399 | /** 400 | * Display prompts 401 | */ 402 | await this.#promptForDestination() 403 | await this.#promptForStarterKit() 404 | if ( 405 | this.kit === WEB_STARTER_KIT || 406 | this.kit === API_STARTER_KIT || 407 | this.kit === INERTIA_STARTER_KIT 408 | ) { 409 | await this.#promptForAuthGuard() 410 | await this.#promptForDatabaseDriver() 411 | } 412 | if (this.kit === INERTIA_STARTER_KIT) { 413 | await this.#promptForInertiaAdapter() 414 | await this.#promptForInertiaSsr() 415 | } 416 | 417 | /** 418 | * Create tasks instance for displaying 419 | * actions as tasks 420 | */ 421 | const tasks = this.ui.tasks({ verbose: this.verbose === true }) 422 | 423 | /** 424 | * Configure lucid when using our own starter kits 425 | * and installing dependencies 426 | */ 427 | const configureLucid = 428 | [WEB_STARTER_KIT, API_STARTER_KIT, INERTIA_STARTER_KIT].includes(this.kit || '') && 429 | this.db !== 'skip' 430 | 431 | /** 432 | * Configure auth when using our own starter kits 433 | * and installing dependencies 434 | */ 435 | const configureAuth = 436 | [WEB_STARTER_KIT, API_STARTER_KIT, INERTIA_STARTER_KIT].includes(this.kit || '') && 437 | this.authGuard !== 'skip' 438 | 439 | /** 440 | * Configure inertia when using our inertia starter kit 441 | */ 442 | const configureInertia = this.kit === INERTIA_STARTER_KIT && this.adapter !== 'skip' 443 | 444 | tasks 445 | .add('Download starter kit', async (task) => { 446 | task.update(`Downloading "${this.kit}"`) 447 | await downloadTemplate(this.kit!, { 448 | dir: this.destination, 449 | auth: this.token, 450 | registry: false, 451 | }) 452 | await this.#removeLockFile() 453 | return `Downloaded "${this.kit}"` 454 | }) 455 | .addIf(this.gitInit === true, 'Initialize git repository', async () => { 456 | await this.#runBashCommand('git', ['init']) 457 | return 'Initialized git repository' 458 | }) 459 | .add('Install packages', async (task) => { 460 | const spinner = this.logger.await('installing dependencies', { 461 | silent: this.verbose, 462 | }) 463 | 464 | spinner.tap((line) => task.update(line)) 465 | spinner.start() 466 | 467 | try { 468 | await this.#runBashCommand(this.packageManager, ['install']) 469 | return `Packages installed using "${this.packageManager}"` 470 | } finally { 471 | spinner.stop() 472 | } 473 | }) 474 | .add('Prepare application', async () => { 475 | try { 476 | await this.#replacePackageJsonName() 477 | await this.#removeReadmeFile() 478 | await this.#copyEnvExampleFile() 479 | await this.#generateFreshAppKey() 480 | return 'Application ready' 481 | } catch (error) { 482 | if (this.verbose) { 483 | this.logger.fatal(error) 484 | } 485 | return 'Unable to prepare application' 486 | } 487 | }) 488 | .addIf(configureLucid, 'Configure Lucid', async (task) => { 489 | const spinner = this.logger.await('configuring @adonisjs/lucid', { 490 | silent: this.verbose, 491 | }) 492 | 493 | spinner.tap((line) => task.update(line)) 494 | spinner.start() 495 | 496 | try { 497 | await this.#configureLucid() 498 | spinner.stop() 499 | return `Lucid configured to use "${this.db}" database` 500 | } catch (error) { 501 | spinner.stop() 502 | if (this.verbose) { 503 | this.logger.fatal(error) 504 | } 505 | return `Unable to configure "@adonisjs/lucid"` 506 | } 507 | }) 508 | .addIf(configureAuth, 'Configure Auth', async (task) => { 509 | const spinner = this.logger.await('configuring @adonisjs/auth', { 510 | silent: this.verbose, 511 | }) 512 | 513 | spinner.tap((line) => task.update(line)) 514 | spinner.start() 515 | 516 | try { 517 | await this.#configureAuth() 518 | spinner.stop() 519 | return `Auth configured to use "${this.authGuard}" guard` 520 | } catch (error) { 521 | spinner.stop() 522 | 523 | if (this.verbose) { 524 | this.logger.fatal(error) 525 | } 526 | return `Unable to configure "@adonisjs/auth"` 527 | } 528 | }) 529 | .addIf(configureInertia, 'Configure Inertia', async (task) => { 530 | const spinner = this.logger.await('configuring @adonisjs/inertia', { 531 | silent: this.verbose, 532 | }) 533 | 534 | spinner.tap((line) => task.update(line)) 535 | spinner.start() 536 | 537 | try { 538 | await this.#configureInertia() 539 | spinner.stop() 540 | return 'Inertia configured' 541 | } catch (error) { 542 | spinner.stop() 543 | 544 | if (this.verbose) { 545 | this.logger.fatal(error) 546 | } 547 | 548 | return `Unable to configure "@adonisjs/inertia"` 549 | } 550 | }) 551 | 552 | await tasks.run() 553 | if (tasks.getState() === 'succeeded') { 554 | this.#printSuccessMessage() 555 | } else { 556 | this.exitCode = 1 557 | } 558 | } 559 | } 560 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * create-adonisjs 3 | * 4 | * (c) AdonisJS 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { HelpCommand, Kernel } from '@adonisjs/ace' 11 | import { CreateNewApp } from './commands/main.js' 12 | 13 | Kernel.defaultCommand = CreateNewApp 14 | 15 | export const kernel = Kernel.create() 16 | 17 | kernel.defineFlag('help', { 18 | type: 'boolean', 19 | description: HelpCommand.description, 20 | }) 21 | 22 | kernel.on('help', async (command, $kernel, parsed) => { 23 | parsed.args.unshift(command.commandName) 24 | await new HelpCommand($kernel, parsed, kernel.ui, kernel.prompt).exec() 25 | return $kernel.shortcircuit() 26 | }) 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-adonisjs", 3 | "description": "Scaffold new AdonisJS applications using starter kits", 4 | "version": "2.4.0", 5 | "engines": { 6 | "node": ">=18.16.0" 7 | }, 8 | "main": "build/index.js", 9 | "type": "module", 10 | "files": [ 11 | "build/bin", 12 | "build/commands", 13 | "build/src", 14 | "build/index.d.ts", 15 | "build/index.js" 16 | ], 17 | "bin": { 18 | "create-adonisjs": "build/bin/run.js" 19 | }, 20 | "exports": { 21 | ".": "./build/index.js" 22 | }, 23 | "scripts": { 24 | "clean": "del-cli build", 25 | "typecheck": "tsc --noEmit", 26 | "lint": "eslint . --ext=.ts", 27 | "format": "prettier --write .", 28 | "quick:test": "node --enable-source-maps --loader=ts-node/esm bin/test.ts", 29 | "pretest": "npm run lint", 30 | "test": "c8 npm run quick:test", 31 | "prebuild": "npm run lint && npm run clean", 32 | "build": "tsc", 33 | "release": "release-it", 34 | "version": "npm run build", 35 | "prepublishOnly": "npm run build" 36 | }, 37 | "devDependencies": { 38 | "@adonisjs/eslint-config": "^1.3.0", 39 | "@adonisjs/prettier-config": "^1.3.0", 40 | "@adonisjs/tsconfig": "^1.3.0", 41 | "@japa/assert": "^3.0.0", 42 | "@japa/file-system": "^2.3.0", 43 | "@japa/runner": "^3.1.4", 44 | "@swc/core": "^1.6.1", 45 | "@types/gradient-string": "^1.1.6", 46 | "@types/node": "^20.14.5", 47 | "@types/which-pm-runs": "^1.0.2", 48 | "c8": "^10.1.2", 49 | "copyfiles": "^2.4.1", 50 | "del-cli": "^5.0.0", 51 | "eslint": "^8.56.0", 52 | "prettier": "^3.3.2", 53 | "release-it": "^17.3.0", 54 | "ts-node": "^10.9.2", 55 | "typescript": "^5.4.5" 56 | }, 57 | "dependencies": { 58 | "@adonisjs/ace": "^13.1.0", 59 | "@adonisjs/presets": "^2.6.1", 60 | "@antfu/install-pkg": "^0.3.3", 61 | "execa": "^9.2.0", 62 | "giget": "^1.2.3", 63 | "gradient-string": "^2.0.2", 64 | "which-pm-runs": "^1.1.0" 65 | }, 66 | "author": "julien-r44,virk", 67 | "license": "MIT", 68 | "homepage": "https://github.com/adonisjs/create-adonisjs#readme", 69 | "repository": { 70 | "type": "git", 71 | "url": "git+https://github.com/adonisjs/create-adonisjs.git" 72 | }, 73 | "bugs": { 74 | "url": "https://github.com/adonisjs/create-adonisjs/issues" 75 | }, 76 | "keywords": [ 77 | "adonisjs", 78 | "create-adonisjs-app" 79 | ], 80 | "directories": { 81 | "test": "tests" 82 | }, 83 | "eslintConfig": { 84 | "extends": "@adonisjs/eslint-config/package" 85 | }, 86 | "prettier": "@adonisjs/prettier-config", 87 | "publishConfig": { 88 | "access": "public", 89 | "tag": "latest" 90 | }, 91 | "c8": { 92 | "reporter": [ 93 | "text", 94 | "html" 95 | ], 96 | "exclude": [ 97 | "tests/**", 98 | "tmp/**", 99 | "bin/**" 100 | ] 101 | }, 102 | "release-it": { 103 | "git": { 104 | "commitMessage": "chore(release): ${version}", 105 | "tagAnnotation": "v${version}", 106 | "tagName": "v${version}" 107 | }, 108 | "github": { 109 | "release": true, 110 | "releaseName": "v${version}", 111 | "web": true 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/auth_guards.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * create-adonisjs 3 | * 4 | * (c) AdonisJS 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { GUARDS } from '@adonisjs/presets/auth' 11 | 12 | /** 13 | * List of known authentication guards 14 | */ 15 | export const authGuards = [ 16 | ...Object.keys(GUARDS).map((guard) => { 17 | return { 18 | name: guard as keyof typeof GUARDS, 19 | hint: GUARDS[guard as keyof typeof GUARDS].description, 20 | message: GUARDS[guard as keyof typeof GUARDS].name, 21 | } 22 | }), 23 | { 24 | name: 'skip', 25 | message: 'Skip', 26 | hint: 'I want to configure the Auth package manually', 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /src/databases.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * create-adonisjs 3 | * 4 | * (c) AdonisJS 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { DIALECTS } from '@adonisjs/presets/lucid' 11 | 12 | /** 13 | * List of known databases that can be used with Lucid 14 | */ 15 | export const databases = [ 16 | ...Object.keys(DIALECTS).map((dialect) => { 17 | return { 18 | name: dialect as keyof typeof DIALECTS, 19 | message: DIALECTS[dialect as keyof typeof DIALECTS].name, 20 | } 21 | }), 22 | { 23 | name: 'skip', 24 | message: 'Skip', 25 | hint: 'I want to configure Lucid manually', 26 | }, 27 | ] 28 | -------------------------------------------------------------------------------- /src/inertia_adapters.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * create-adonisjs 3 | * 4 | * (c) AdonisJS 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * List of adapters for configuring Inertia 12 | */ 13 | export const adapters = [ 14 | { 15 | message: 'Vue 3', 16 | name: 'vue', 17 | }, 18 | { 19 | message: 'React', 20 | name: 'react', 21 | }, 22 | { 23 | message: 'Svelte', 24 | name: 'svelte', 25 | }, 26 | { 27 | message: 'Solid.js', 28 | name: 'solid', 29 | }, 30 | { 31 | name: 'skip', 32 | message: 'Skip', 33 | hint: 'I want to configure Inertia manually', 34 | }, 35 | ] 36 | -------------------------------------------------------------------------------- /src/templates.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * create-adonisjs 3 | * 4 | * (c) AdonisJS 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * List of first party templates 12 | */ 13 | export const templates = [ 14 | { 15 | name: 'Slim Starter Kit', 16 | alias: 'slim', 17 | hint: 'A lean AdonisJS application with just the framework core', 18 | source: 'github:adonisjs/slim-starter-kit', 19 | }, 20 | { 21 | name: 'Web Starter Kit', 22 | alias: 'web', 23 | hint: 'Everything you need to build a server render app', 24 | source: 'github:adonisjs/web-starter-kit', 25 | }, 26 | { 27 | name: 'API Starter Kit', 28 | alias: 'api', 29 | hint: 'AdonisJS app tailored for creating JSON APIs', 30 | source: 'github:adonisjs/api-starter-kit', 31 | }, 32 | { 33 | name: 'Inertia Starter Kit', 34 | alias: 'inertia', 35 | hint: 'Inertia app with a frontend framework of your choice', 36 | source: 'github:adonisjs/inertia-starter-kit', 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /tests/install_adonis.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * create-adonisjs 3 | * 4 | * (c) AdonisJS 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { execa } from 'execa' 11 | import { join } from 'node:path' 12 | import { test } from '@japa/runner' 13 | 14 | import { kernel } from '../index.js' 15 | import { CreateNewApp } from '../commands/main.js' 16 | import { databases } from '../src/databases.js' 17 | import { authGuards } from '../src/auth_guards.js' 18 | 19 | const VERBOSE = !!process.env.CI 20 | 21 | test.group('Create new app', (group) => { 22 | group.each.setup(() => { 23 | kernel.ui.switchMode('raw') 24 | return () => { 25 | kernel.ui.switchMode('normal') 26 | } 27 | }) 28 | 29 | group.each.disableTimeout() 30 | 31 | test('clone template to destination', async ({ assert, fs }) => { 32 | const command = await kernel.create(CreateNewApp, [ 33 | join(fs.basePath, 'foo'), 34 | '--db=sqlite', 35 | '--auth-guard=session', 36 | '--kit="github:samuelmarina/is-even"', 37 | ]) 38 | 39 | command.verbose = VERBOSE 40 | await command.exec() 41 | 42 | await assert.dirIsNotEmpty('foo') 43 | await assert.fileExists('foo/package.json') 44 | }) 45 | 46 | test('use github as default provider', async ({ assert, fs }) => { 47 | const command = await kernel.create(CreateNewApp, [ 48 | join(fs.basePath, 'foo'), 49 | '--kit="samuelmarina/is-even"', 50 | ]) 51 | 52 | command.verbose = VERBOSE 53 | await command.exec() 54 | 55 | await assert.dirIsNotEmpty('foo') 56 | await assert.fileExists('foo/package.json') 57 | }) 58 | 59 | test('prompt for destination when not provided', async ({ assert }) => { 60 | const command = await kernel.create(CreateNewApp, [ 61 | '--db=sqlite', 62 | '--auth-guard=session', 63 | '--kit="github:samuelmarina/is-even"', 64 | ]) 65 | 66 | command.verbose = VERBOSE 67 | command.prompt.trap('Where should we create your new project').replyWith('tmp/foo') 68 | await command.exec() 69 | 70 | await assert.dirIsNotEmpty('foo') 71 | await assert.fileExists('foo/package.json') 72 | }) 73 | 74 | test('prompt for kit selection when not pre-defined', async ({ assert, fs }) => { 75 | const command = await kernel.create(CreateNewApp, [ 76 | join(fs.basePath, 'foo'), 77 | '--pkg="npm"', 78 | // not provide `--db` and `--auth-guard` to test that it will not prompt for slim kit 79 | ]) 80 | 81 | command.verbose = VERBOSE 82 | command.prompt.trap('Which starter kit would you like to use').chooseOption(0) 83 | 84 | await command.exec() 85 | 86 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 87 | 88 | assert.deepEqual(result.exitCode, 0) 89 | assert.deepInclude(result.stdout, 'View list of available commands') 90 | }) 91 | 92 | test('prompt for auth guard when not pre-defined and selected api/web kit', async ({ 93 | assert, 94 | fs, 95 | }) => { 96 | const command = await kernel.create(CreateNewApp, [ 97 | join(fs.basePath, 'foo'), 98 | '--pkg="npm"', 99 | '-K=web', 100 | '--db=sqlite', 101 | ]) 102 | 103 | command.verbose = VERBOSE 104 | command.prompt.trap('Which authentication guard you want to use').chooseOption(1) 105 | 106 | await command.exec() 107 | 108 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 109 | 110 | assert.deepEqual(result.exitCode, 0) 111 | assert.deepInclude(result.stdout, 'View list of available commands') 112 | 113 | await assert.fileExists('foo/config/auth.ts') 114 | }) 115 | 116 | test('prompt for database driver when not pre-defined and selected api/web kit', async ({ 117 | assert, 118 | fs, 119 | }) => { 120 | const command = await kernel.create(CreateNewApp, [ 121 | join(fs.basePath, 'foo'), 122 | '--pkg="npm"', 123 | '-K=api', 124 | '--auth-guard=session', 125 | ]) 126 | 127 | command.verbose = VERBOSE 128 | command.prompt.trap('Which database driver you want to use').chooseOption(1) 129 | 130 | await command.exec() 131 | 132 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 133 | 134 | assert.deepEqual(result.exitCode, 0) 135 | assert.deepInclude(result.stdout, 'View list of available commands') 136 | 137 | await assert.fileExists('foo/config/database.ts') 138 | }) 139 | 140 | test('do not configure lucid when user skip database driver selection', async ({ 141 | assert, 142 | fs, 143 | }) => { 144 | const command = await kernel.create(CreateNewApp, [ 145 | join(fs.basePath, 'foo'), 146 | '--pkg="npm"', 147 | '-K=web', 148 | '--auth-guard=session', 149 | ]) 150 | 151 | command.verbose = VERBOSE 152 | 153 | const skippingIndex = databases.findIndex((db) => db.name === 'skip') 154 | command.prompt.trap('Which database driver you want to use').chooseOption(skippingIndex) 155 | 156 | await command.exec() 157 | 158 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 159 | assert.deepEqual(result.exitCode, 0) 160 | assert.deepInclude(result.stdout, 'View list of available commands') 161 | 162 | await assert.fileNotExists('foo/config/database.ts') 163 | }) 164 | 165 | test('do not configure auth when user skip auth guard selection', async ({ assert, fs }) => { 166 | const command = await kernel.create(CreateNewApp, [ 167 | join(fs.basePath, 'foo'), 168 | '--pkg="npm"', 169 | '-K=web', 170 | '--db=sqlite', 171 | ]) 172 | 173 | command.verbose = VERBOSE 174 | 175 | const skippingIndex = authGuards.findIndex((db) => db.name === 'skip') 176 | command.prompt.trap('Which authentication guard you want to use').chooseOption(skippingIndex) 177 | 178 | await command.exec() 179 | 180 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 181 | assert.deepEqual(result.exitCode, 0) 182 | assert.deepInclude(result.stdout, 'View list of available commands') 183 | 184 | await assert.fileNotExists('foo/config/auth.ts') 185 | }) 186 | 187 | test('fail if destination directory already exists', async ({ assert, fs }) => { 188 | await fs.create('foo/bar.txt', '') 189 | 190 | const command = await kernel.create(CreateNewApp, [ 191 | join(fs.basePath, 'foo'), 192 | '--kit="github:samuelmarina/is-even"', 193 | ]) 194 | 195 | command.verbose = VERBOSE 196 | await command.exec() 197 | 198 | await assert.dirIsNotEmpty('foo') 199 | await assert.fileNotExists('foo/package.json') 200 | 201 | command.assertFailed() 202 | command.assertLogMatches(/Destination .* already exists/, 'stderr') 203 | }) 204 | 205 | test('install dependencies using detected package manager - {agent}') 206 | .with([ 207 | { agent: 'npm/7.0.0 node/v15.0.0 darwin x64', lockFile: 'package-lock.json' }, 208 | { agent: 'pnpm/5.0.0 node/v15.0.0 darwin x64', lockFile: 'pnpm-lock.yaml' }, 209 | { agent: 'yarn/1.22.5 npm/? node/v15.0.0 darwin x64', lockFile: 'yarn.lock' }, 210 | ]) 211 | .run(async ({ assert, fs }, { agent, lockFile }) => { 212 | process.env.npm_config_user_agent = agent 213 | 214 | const command = await kernel.create(CreateNewApp, [ 215 | join(fs.basePath, 'foo'), 216 | '--db=sqlite', 217 | '--auth-guard=session', 218 | '--kit="github:samuelmarina/is-even"', 219 | ]) 220 | 221 | command.verbose = VERBOSE 222 | await command.exec() 223 | 224 | await assert.fileExists(`foo/${lockFile}`) 225 | 226 | process.env.npm_config_user_agent = undefined 227 | }) 228 | 229 | test('initialize git repo when --git-init flag is provided', async ({ assert, fs }) => { 230 | const command = await kernel.create(CreateNewApp, [ 231 | join(fs.basePath, 'foo'), 232 | '--git-init', 233 | '--kit="github:samuelmarina/is-even"', 234 | ]) 235 | 236 | command.verbose = VERBOSE 237 | await command.exec() 238 | 239 | await assert.dirExists('foo/.git') 240 | }) 241 | 242 | test('force package manager', async ({ assert, fs }) => { 243 | const command = await kernel.create(CreateNewApp, [ 244 | join(fs.basePath, 'foo'), 245 | '--pkg="yarn"', 246 | '--db=sqlite', 247 | '--auth-guard=session', 248 | '--kit="github:samuelmarina/is-even"', 249 | ]) 250 | 251 | command.verbose = VERBOSE 252 | await command.exec() 253 | 254 | await assert.fileExists('foo/yarn.lock') 255 | }) 256 | 257 | test('configure slim starter kit', async ({ assert, fs }) => { 258 | const command = await kernel.create(CreateNewApp, [ 259 | join(fs.basePath, 'foo'), 260 | '--pkg="npm"', 261 | '--kit="github:adonisjs/slim-starter-kit"', 262 | ]) 263 | 264 | command.verbose = VERBOSE 265 | await command.exec() 266 | 267 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 268 | 269 | assert.deepEqual(result.exitCode, 0) 270 | assert.deepInclude(result.stdout, 'View list of available commands') 271 | }) 272 | 273 | test('create .env file', async ({ assert, fs }) => { 274 | const command = await kernel.create(CreateNewApp, [ 275 | join(fs.basePath, 'foo'), 276 | '--pkg="npm"', 277 | '--kit="github:adonisjs/slim-starter-kit"', 278 | ]) 279 | 280 | command.verbose = VERBOSE 281 | await command.exec() 282 | await assert.fileExists('foo/.env') 283 | }) 284 | 285 | test('remove README file', async ({ assert, fs }) => { 286 | const command = await kernel.create(CreateNewApp, [ 287 | join(fs.basePath, 'foo'), 288 | '--pkg="npm"', 289 | '--kit="github:adonisjs/slim-starter-kit"', 290 | ]) 291 | 292 | command.verbose = VERBOSE 293 | await command.exec() 294 | await assert.fileNotExists('foo/README.md') 295 | }) 296 | 297 | test('rename package name inside package.json file', async ({ assert, fs }) => { 298 | const command = await kernel.create(CreateNewApp, [ 299 | join(fs.basePath, 'foo/bar'), 300 | '--pkg="npm"', 301 | '--kit="github:adonisjs/slim-starter-kit"', 302 | ]) 303 | 304 | command.verbose = VERBOSE 305 | await command.exec() 306 | await assert.fileContains('foo/bar/package.json', `"name": "bar"`) 307 | }) 308 | }) 309 | 310 | test.group('Configure | Web starter kit', (group) => { 311 | group.each.disableTimeout() 312 | 313 | test('configure lucid and auth when using web starter kit', async ({ assert, fs }) => { 314 | const command = await kernel.create(CreateNewApp, [ 315 | join(fs.basePath, 'foo'), 316 | '--pkg="npm"', 317 | '--db=sqlite', 318 | '--auth-guard=session', 319 | '-K=web', 320 | ]) 321 | 322 | command.verbose = VERBOSE 323 | await command.exec() 324 | 325 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 326 | assert.deepEqual(result.exitCode, 0) 327 | assert.deepInclude(result.stdout, 'View list of available commands') 328 | 329 | await assert.fileContains('foo/adonisrc.ts', [ 330 | `() => import('@adonisjs/lucid/database_provider')`, 331 | `() => import('@adonisjs/auth/auth_provider')`, 332 | `() => import('@adonisjs/lucid/commands')`, 333 | ]) 334 | await assert.fileExists('foo/config/database.ts') 335 | await assert.fileExists('foo/config/auth.ts') 336 | await assert.fileExists('foo/app/models/user.ts') 337 | await assert.fileContains('foo/package.json', [ 338 | '@adonisjs/lucid', 339 | '@adonisjs/auth', 340 | 'luxon', 341 | '@types/luxon', 342 | 'better-sqlite', 343 | ]) 344 | }) 345 | 346 | test('configure lucid package to use postgresql', async ({ assert, fs }) => { 347 | const command = await kernel.create(CreateNewApp, [ 348 | join(fs.basePath, 'foo'), 349 | '--pkg="npm"', 350 | '-K=web', 351 | '--auth-guard=session', 352 | '--db=postgres', 353 | ]) 354 | 355 | command.verbose = VERBOSE 356 | await command.exec() 357 | 358 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 359 | assert.deepEqual(result.exitCode, 0) 360 | assert.deepInclude(result.stdout, 'View list of available commands') 361 | 362 | await assert.fileContains('foo/config/database.ts', [`client: 'pg'`]) 363 | await assert.fileContains('foo/package.json', ['pg']) 364 | }) 365 | }) 366 | 367 | test.group('Configure | API starter kit', (group) => { 368 | group.each.disableTimeout() 369 | 370 | test('configure lucid and auth when using api starter kit', async ({ assert, fs }) => { 371 | const command = await kernel.create(CreateNewApp, [ 372 | join(fs.basePath, 'foo'), 373 | '--pkg="npm"', 374 | '-K=api', 375 | '--db=sqlite', 376 | '--auth-guard=session', 377 | ]) 378 | 379 | command.verbose = VERBOSE 380 | await command.exec() 381 | 382 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 383 | assert.deepEqual(result.exitCode, 0) 384 | assert.deepInclude(result.stdout, 'View list of available commands') 385 | 386 | await assert.fileContains('foo/adonisrc.ts', [ 387 | `() => import('@adonisjs/lucid/database_provider')`, 388 | `() => import('@adonisjs/auth/auth_provider')`, 389 | `() => import('@adonisjs/session/session_provider')`, 390 | `() => import('@adonisjs/lucid/commands')`, 391 | ]) 392 | await assert.fileContains('foo/start/kernel.ts', [ 393 | `() => import('@adonisjs/session/session_middleware')`, 394 | ]) 395 | await assert.fileExists('foo/config/database.ts') 396 | await assert.fileExists('foo/config/auth.ts') 397 | await assert.fileExists('foo/config/session.ts') 398 | await assert.fileExists('foo/app/models/user.ts') 399 | await assert.fileContains('foo/package.json', [ 400 | '@adonisjs/session', 401 | '@adonisjs/lucid', 402 | '@adonisjs/auth', 403 | 'luxon', 404 | '@types/luxon', 405 | 'better-sqlite', 406 | ]) 407 | }) 408 | 409 | test('do not configure session package when using access tokens guard', async ({ 410 | assert, 411 | fs, 412 | }) => { 413 | const command = await kernel.create(CreateNewApp, [ 414 | join(fs.basePath, 'foo'), 415 | '--pkg="npm"', 416 | '-K=api', 417 | '--db=sqlite', 418 | '--auth-guard=access_tokens', 419 | ]) 420 | 421 | command.verbose = VERBOSE 422 | await command.exec() 423 | 424 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 425 | assert.deepEqual(result.exitCode, 0) 426 | assert.deepInclude(result.stdout, 'View list of available commands') 427 | 428 | await assert.fileNotContains('foo/adonisrc.ts', [ 429 | `() => import('@adonisjs/session/session_provider')`, 430 | ]) 431 | await assert.fileNotContains('foo/start/kernel.ts', [ 432 | `() => import('@adonisjs/session/session_middleware')`, 433 | ]) 434 | await assert.fileNotExists('foo/config/session.ts') 435 | await assert.fileNotContains('foo/package.json', ['@adonisjs/session']) 436 | }) 437 | }) 438 | 439 | test.group('Configure | Inertia Starter Kit', (group) => { 440 | group.each.disableTimeout() 441 | 442 | test('configure lucid/auth/inertia when using inertia starter kit', async ({ assert, fs }) => { 443 | const command = await kernel.create(CreateNewApp, [ 444 | join(fs.basePath, 'foo'), 445 | '--pkg="npm"', 446 | '-K=inertia', 447 | '--db=sqlite', 448 | '--auth-guard=session', 449 | '--ssr', 450 | '--adapter=solid', 451 | ]) 452 | 453 | command.verbose = VERBOSE 454 | await command.exec() 455 | 456 | const result = await execa('node', ['ace', '--help'], { cwd: join(fs.basePath, 'foo') }) 457 | assert.deepEqual(result.exitCode, 0) 458 | assert.deepInclude(result.stdout, 'View list of available commands') 459 | 460 | await assert.fileContains('foo/adonisrc.ts', [ 461 | `() => import('@adonisjs/lucid/database_provider')`, 462 | `() => import('@adonisjs/auth/auth_provider')`, 463 | `() => import('@adonisjs/session/session_provider')`, 464 | `() => import('@adonisjs/lucid/commands')`, 465 | ]) 466 | await assert.fileContains('foo/start/kernel.ts', [ 467 | `() => import('@adonisjs/session/session_middleware')`, 468 | ]) 469 | await assert.fileExists('foo/config/database.ts') 470 | await assert.fileExists('foo/config/auth.ts') 471 | await assert.fileExists('foo/inertia/app/app.tsx') 472 | await assert.fileExists('foo/inertia/app/ssr.tsx') 473 | await assert.fileExists('foo/config/inertia.ts') 474 | await assert.fileExists('foo/config/session.ts') 475 | await assert.fileExists('foo/app/models/user.ts') 476 | await assert.fileContains('foo/package.json', [ 477 | '@adonisjs/session', 478 | '@adonisjs/lucid', 479 | '@adonisjs/inertia', 480 | 'solid-js', 481 | 'vite-plugin-solid', 482 | '@adonisjs/auth', 483 | 'luxon', 484 | '@types/luxon', 485 | 'better-sqlite', 486 | ]) 487 | }) 488 | }) 489 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@adonisjs/tsconfig/tsconfig.package.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "./build" 6 | } 7 | } 8 | --------------------------------------------------------------------------------