Repository: adonisjs/core
Branch: 7.x
Commit: a28022d699b9
Files: 226
Total size: 648.3 KB
Directory structure:
gitextract_zov6uehz/
├── .editorconfig
├── .github/
│ └── workflows/
│ ├── checks.yml
│ ├── labels.yml
│ ├── release.yml
│ ├── stale.yml
│ └── test.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── LICENSE.md
├── README.md
├── bin/
│ └── test.ts
├── commands/
│ ├── add.ts
│ ├── build.ts
│ ├── configure.ts
│ ├── eject.ts
│ ├── env/
│ │ └── add.ts
│ ├── generate_key.ts
│ ├── inspect_rcfile.ts
│ ├── list/
│ │ └── routes.ts
│ ├── make/
│ │ ├── command.ts
│ │ ├── controller.ts
│ │ ├── event.ts
│ │ ├── exception.ts
│ │ ├── listener.ts
│ │ ├── middleware.ts
│ │ ├── preload.ts
│ │ ├── provider.ts
│ │ ├── service.ts
│ │ ├── test.ts
│ │ ├── transformer.ts
│ │ ├── validator.ts
│ │ └── view.ts
│ ├── repl.ts
│ ├── serve.ts
│ └── test.ts
├── eslint.config.js
├── factories/
│ ├── app.ts
│ ├── bodyparser.ts
│ ├── core/
│ │ ├── ace.ts
│ │ ├── ignitor.ts
│ │ ├── main.ts
│ │ └── test_utils.ts
│ ├── encryption.ts
│ ├── events.ts
│ ├── hash.ts
│ ├── http.ts
│ ├── logger.ts
│ └── stubs.ts
├── funding.json
├── index.ts
├── modules/
│ ├── ace/
│ │ ├── codemods.ts
│ │ ├── commands.ts
│ │ ├── create_kernel.ts
│ │ ├── kernel.ts
│ │ └── main.ts
│ ├── app.ts
│ ├── bodyparser/
│ │ ├── bodyparser_middleware.ts
│ │ └── main.ts
│ ├── config.ts
│ ├── container.ts
│ ├── dumper/
│ │ ├── define_config.ts
│ │ ├── dumper.ts
│ │ ├── errors.ts
│ │ ├── main.ts
│ │ └── plugins/
│ │ └── edge.ts
│ ├── encryption/
│ │ ├── define_config.ts
│ │ ├── drivers/
│ │ │ ├── aes_256_cbc.ts
│ │ │ ├── aes_256_gcm.ts
│ │ │ ├── aes_siv.ts
│ │ │ ├── chacha20_poly1305.ts
│ │ │ └── legacy.ts
│ │ ├── errors.ts
│ │ └── main.ts
│ ├── env/
│ │ ├── editor.ts
│ │ └── main.ts
│ ├── events.ts
│ ├── hash/
│ │ ├── define_config.ts
│ │ ├── drivers/
│ │ │ ├── argon.ts
│ │ │ ├── bcrypt.ts
│ │ │ └── scrypt.ts
│ │ ├── main.ts
│ │ └── phc_formatter.ts
│ ├── health.ts
│ ├── http/
│ │ ├── helpers.ts
│ │ ├── main.ts
│ │ ├── request_validator.ts
│ │ └── url_builder_client.ts
│ ├── logger.ts
│ ├── repl.ts
│ └── transformers/
│ └── main.ts
├── package.json
├── providers/
│ ├── app_provider.ts
│ ├── edge_provider.ts
│ ├── hash_provider.ts
│ ├── repl_provider.ts
│ └── vinejs_provider.ts
├── services/
│ ├── ace.ts
│ ├── app.ts
│ ├── config.ts
│ ├── dumper.ts
│ ├── emitter.ts
│ ├── encryption.ts
│ ├── hash.ts
│ ├── logger.ts
│ ├── repl.ts
│ ├── router.ts
│ ├── server.ts
│ ├── test_utils.ts
│ └── url_builder.ts
├── src/
│ ├── assembler_hooks/
│ │ └── index_entities.ts
│ ├── cli_formatters/
│ │ └── routes_list.ts
│ ├── config_provider.ts
│ ├── debug.ts
│ ├── exceptions.ts
│ ├── helpers/
│ │ ├── assert.ts
│ │ ├── http.ts
│ │ ├── is.ts
│ │ ├── main.ts
│ │ ├── string.ts
│ │ ├── types.ts
│ │ └── verification_token.ts
│ ├── ignitor/
│ │ ├── ace.ts
│ │ ├── http.ts
│ │ ├── main.ts
│ │ └── test.ts
│ ├── test_utils/
│ │ ├── http.ts
│ │ └── main.ts
│ ├── types.ts
│ ├── utils.ts
│ └── vine.ts
├── stubs/
│ ├── main.ts
│ └── make/
│ ├── command/
│ │ └── main.stub
│ ├── controller/
│ │ ├── actions.stub
│ │ ├── api.stub
│ │ ├── main.stub
│ │ └── resource.stub
│ ├── event/
│ │ └── main.stub
│ ├── exception/
│ │ └── main.stub
│ ├── health/
│ │ ├── controller.stub
│ │ └── main.stub
│ ├── listener/
│ │ ├── for_event.stub
│ │ └── main.stub
│ ├── middleware/
│ │ └── main.stub
│ ├── preload/
│ │ └── main.stub
│ ├── provider/
│ │ └── main.stub
│ ├── service/
│ │ └── main.stub
│ ├── test/
│ │ └── main.stub
│ ├── transformer/
│ │ └── main.stub
│ ├── validator/
│ │ ├── main.stub
│ │ └── resource.stub
│ └── view/
│ └── main.stub
├── tests/
│ ├── ace/
│ │ ├── base_command.spec.ts
│ │ ├── codemods.spec.ts
│ │ └── kernel.spec.ts
│ ├── bindings/
│ │ ├── edge.spec.ts
│ │ ├── repl.spec.ts
│ │ └── vinejs.spec.ts
│ ├── cli_formatters/
│ │ └── routes_list.spec.ts
│ ├── commands/
│ │ ├── add.spec.ts
│ │ ├── build.spec.ts
│ │ ├── configure.spec.ts
│ │ ├── eject.spec.ts
│ │ ├── env_add.spec.ts
│ │ ├── generate_key.spec.ts
│ │ ├── inspect_rcfile.spec.ts
│ │ ├── make_command.spec.ts
│ │ ├── make_controller.spec.ts
│ │ ├── make_event.spec.ts
│ │ ├── make_exception.spec.ts
│ │ ├── make_listener.spec.ts
│ │ ├── make_middleware.spec.ts
│ │ ├── make_preload.spec.ts
│ │ ├── make_provider.spec.ts
│ │ ├── make_service.spec.ts
│ │ ├── make_test.spec.ts
│ │ ├── make_transformer.spec.ts
│ │ ├── make_validator.spec.ts
│ │ ├── make_view.spec.ts
│ │ ├── serve.spec.ts
│ │ └── test.spec.ts
│ ├── dumper/
│ │ └── dumper.spec.ts
│ ├── encryption/
│ │ └── legacy.spec.ts
│ ├── hash.spec.ts
│ ├── helpers.spec.ts
│ ├── helpers.ts
│ ├── ignitor/
│ │ ├── ace_process.spec.ts
│ │ ├── http_server_process.spec.ts
│ │ ├── main.spec.ts
│ │ └── test_process.spec.ts
│ ├── index_generator.spec.ts
│ ├── pretty_print_error.spec.ts
│ ├── providers.spec.ts
│ ├── request_validator.spec.ts
│ ├── stubs/
│ │ ├── make_command.spec.ts
│ │ ├── make_controller.spec.ts
│ │ ├── make_event.spec.ts
│ │ ├── make_exception.spec.ts
│ │ ├── make_listener.spec.ts
│ │ ├── make_middleware.spec.ts
│ │ ├── make_prldfile.spec.ts
│ │ ├── make_provider.spec.ts
│ │ ├── make_service.spec.ts
│ │ ├── make_test.spec.ts
│ │ ├── make_validator.spec.ts
│ │ └── make_view.spec.ts
│ ├── test_utils/
│ │ └── http.spec.ts
│ └── verification_token.spec.ts
├── toolkit/
│ ├── commands/
│ │ └── index_commands.ts
│ └── main.ts
├── tsconfig.json
├── typedoc.json
└── types/
├── ace.ts
├── app.ts
├── bodyparser.ts
├── common.ts
├── container.ts
├── encryption.ts
├── events.ts
├── hash.ts
├── health.ts
├── helpers.ts
├── http.ts
├── logger.ts
├── repl.ts
└── transformers.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.json]
insert_final_newline = ignore
[**.min.js]
indent_style = ignore
insert_final_newline = ignore
[MakeFile]
indent_style = space
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .github/workflows/checks.yml
================================================
name: checks
on:
- push
- pull_request
- workflow_call
jobs:
test:
uses: adonisjs/core/.github/workflows/test.yml@7.x
with:
install-pnpm: true
lint:
uses: adonisjs/.github/.github/workflows/lint.yml@main
typecheck:
uses: adonisjs/.github/.github/workflows/typecheck.yml@main
================================================
FILE: .github/workflows/labels.yml
================================================
name: Sync labels
on:
workflow_dispatch:
permissions:
issues: write
jobs:
labels:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EndBug/label-sync@v2
with:
config-file: 'https://raw.githubusercontent.com/thetutlage/static/main/labels.yml'
delete-other-labels: true
token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/release.yml
================================================
name: release
on: workflow_dispatch
permissions:
contents: write
id-token: write
jobs:
checks:
uses: ./.github/workflows/checks.yml
release:
needs: checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 24
- name: git config
run: |
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
- name: Init npm config
run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm install
- run: npm run release -- --ci
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .github/workflows/stale.yml
================================================
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 0 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue has been marked as stale because it has been inactive for more than 21 days. Please reopen if you still need help on this issue'
stale-pr-message: 'This pull request has been marked as stale because it has been inactive for more than 21 days. Please reopen if you still intend to submit this pull request'
close-issue-message: 'This issue has been automatically closed because it has been inactive for more than 4 weeks. Please reopen if you still need help on this issue'
close-pr-message: 'This pull request has been automatically closed because it has been inactive for more than 4 weeks. Please reopen if you still intend to submit this pull request'
exempt-issue-labels: 'Status: On Hold,Pinned'
days-before-stale: 21
days-before-close: 5
================================================
FILE: .github/workflows/test.yml
================================================
on:
workflow_call:
inputs:
disable-windows:
description: Disable running tests on Windows
type: boolean
default: false
required: false
install-pnpm:
description: Install pnpm before running tests
type: boolean
default: false
required: false
jobs:
test_linux:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['lts/krypton', 'latest']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install pnpm
if: ${{ inputs.install-pnpm }}
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
test_windows:
if: ${{ !inputs.disable-windows }}
runs-on: windows-latest
strategy:
matrix:
node-version: ['lts/krypton', 'latest']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install pnpm
if: ${{ inputs.install-pnpm }}
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
================================================
FILE: .gitignore
================================================
node_modules
coverage
.DS_STORE
.nyc_output
.idea
.vscode/
*.sublime-project
*.sublime-workspace
*.log
build
dist
yarn.lock
shrinkwrap.yaml
package-lock.json
test/__app
.env
backup
================================================
FILE: .npmrc
================================================
package-lock=false
================================================
FILE: .prettierignore
================================================
build
docs
coverage
================================================
FILE: LICENSE.md
================================================
# The MIT License
Copyright 2022 Harminder Virk, contributors
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
================================================
FILE: README.md
================================================
# @adonisjs/core
**Featured sponsors**

Fullstack MVC framework for Node.js
AdonisJs is a fullstack Web framework with focus on ergonomics and speed . It takes care of much of the Web development hassles, offering you a clean and stable API to build Web apps and micro services.
[![gh-workflow-image]][gh-workflow-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url]
[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/core/checks.yml?label=Tests&style=for-the-badge
[gh-workflow-url]: https://github.com/adonisjs/core/actions/workflows/checks.yml 'Github action'
[npm-image]: https://img.shields.io/npm/v/@adonisjs/core/latest.svg?style=for-the-badge&logo=npm
[npm-url]: https://www.npmjs.com/package/@adonisjs/core/v/latest 'npm'
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[license-url]: LICENSE.md
[license-image]: https://img.shields.io/github/license/adonisjs/adonis-framework?style=for-the-badge
================================================
FILE: bin/test.ts
================================================
import { assert } from '@japa/assert'
import { snapshot } from '@japa/snapshot'
import { fileSystem } from '@japa/file-system'
import { expectTypeOf } from '@japa/expect-type'
import { processCLIArgs, configure, run } from '@japa/runner'
/*
|--------------------------------------------------------------------------
| Configure tests
|--------------------------------------------------------------------------
|
| The configure method accepts the configuration to configure the Japa
| tests runner.
|
| The first method call "processCLIArgs" process the command line arguments
| and turns them into a config object. Using this method is not mandatory.
|
| Please consult japa.dev/runner-config for the config docs.
*/
processCLIArgs(process.argv.slice(2))
configure({
files: ['tests/**/*.spec.ts'],
plugins: [assert(), expectTypeOf(), fileSystem(), snapshot()],
})
/*
|--------------------------------------------------------------------------
| Run tests
|--------------------------------------------------------------------------
|
| The following "run" method is required to execute all the tests.
|
*/
run()
================================================
FILE: commands/add.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { prettyPrintError } from '../index.ts'
import { type CommandOptions } from '../types/ace.ts'
import { args, BaseCommand, flags } from '../modules/ace/main.ts'
import { type SupportedPackageManager } from '@adonisjs/assembler/types'
/**
* The install command is used to `npm install` and `node ace configure` one or more packages
* in one go.
*
* @example
* ```
* ace add @adonisjs/lucid
* ace add @adonisjs/lucid @adonisjs/auth @adonisjs/session
* ace add @adonisjs/session --dev
* ace add vinejs --force
* ace add edge --package-manager=pnpm
* ```
*/
export default class Add extends BaseCommand {
/**
* The command name
*/
static commandName = 'add'
/**
* The command description
*/
static description =
'Install and configure one or more AdonisJS packages. Runs npm install followed by the package configure hook'
static help = [
'Use this command instead of manually running npm install + configure separately.',
'Accepts shorthand names: "vinejs" for @vinejs/vine, "edge" for edge.js.',
'```',
'{{ binaryName }} add @adonisjs/lucid',
'{{ binaryName }} add @adonisjs/auth @adonisjs/session',
'```',
]
/**
* Command options configuration
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Package names to install and configure
*/
@args.spread({ description: 'Package names to install and configure', required: true })
declare names: string[]
/**
* Display logs in verbose mode
*/
@flags.boolean({ description: 'Display logs in verbose mode' })
declare verbose?: boolean
/**
* Define the package manager you want to use
*/
@flags.string({ description: 'Define the package manager you want to use' })
declare packageManager?: SupportedPackageManager
/**
* Should we install the package as a dev dependency
*/
@flags.boolean({ description: 'Should we install the package as a dev dependency', alias: 'D' })
declare dev?: boolean
/**
* Forcefully overwrite existing files
*/
@flags.boolean({ description: 'Forcefully overwrite existing files' })
declare force?: boolean
/**
* Resolve the npm package name from the user-provided name
*/
#resolveNpmPackageName(name: string): string {
if (name === 'vinejs') {
return '@vinejs/vine'
}
if (name === 'edge') {
return 'edge.js'
}
return name
}
/**
* Configure the package by delegating the work to the `node ace configure` command
*/
async #configurePackage(packageName: string) {
const flagValueArray = this.parsed.unknownFlags
.filter((flag) => !!this.parsed.flags[flag])
.map((flag) => [`--${flag}`, this.parsed.flags[flag].toString()])
const configureArgs = [
packageName,
this.force ? '--force' : undefined,
this.verbose ? '--verbose' : undefined,
...flagValueArray.flat(),
].filter(Boolean) as string[]
return await this.kernel.exec('configure', configureArgs)
}
/**
* Run method is invoked by ace automatically
*/
async run() {
const packages = this.names.map((name) => ({
name,
npmName: this.#resolveNpmPackageName(name),
}))
/**
* Install all packages
*/
const codemods = await this.createCodemods()
codemods.verboseInstallOutput = !!this.verbose
const packagesWereInstalled = await codemods.installPackages(
packages.map((pkg) => ({ name: pkg.npmName, isDevDependency: !!this.dev })),
this.packageManager
)
if (!packagesWereInstalled) {
return
}
/**
* Configure each package sequentially
*/
const succeeded: string[] = []
const failed: { name: string; error?: Error }[] = []
for (const pkg of packages) {
const { exitCode, error } = await this.#configurePackage(pkg.name)
if (exitCode === 0) {
succeeded.push(pkg.name)
} else {
failed.push({ name: pkg.name, error })
}
}
/**
* Report results
*/
if (succeeded.length > 0) {
const names = succeeded.map((name) => this.colors.green(name)).join(', ')
this.logger.success(`Installed and configured ${names}`)
}
if (failed.length > 0) {
this.exitCode = 1
for (const pkg of failed) {
this.logger.error(`Unable to configure ${this.colors.green(pkg.name)}`)
if (pkg.error) {
await prettyPrintError(pkg.error.cause || pkg.error)
}
}
}
}
}
================================================
FILE: commands/build.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { BaseCommand, flags } from '../modules/ace/main.ts'
import { importAssembler, importTypeScript } from '../src/utils.ts'
/**
* Create the production build by compiling TypeScript source and the
* frontend assets
*
* @example
* ```
* ace build
* ace build --ignore-ts-errors
* ace build --package-manager=pnpm
* ```
*/
export default class Build extends BaseCommand {
/**
* The command name
*/
static commandName = 'build'
/**
* The command description
*/
static description =
'Build application for production by compiling frontend assets and TypeScript source to JavaScript'
/**
* Help text for the command
*/
static help = [
'Create the production build using the following command.',
'```',
'{{ binaryName }} build',
'```',
'',
'The assets bundler dev server runs automatically after detecting vite config or webpack config files',
'You may pass vite CLI args using the --assets-args command line flag.',
'```',
'{{ binaryName }} build --assets-args="--debug --base=/public"',
'```',
]
/**
* Ignore TypeScript errors and continue with the build process
*/
@flags.boolean({ description: 'Ignore TypeScript errors and continue with the build process' })
declare ignoreTsErrors?: boolean
/**
* Define the package manager to copy the appropriate lock file
*/
@flags.string({
description: 'Define the package manager to copy the appropriate lock file',
})
declare packageManager?: 'npm' | 'pnpm' | 'yarn' | 'yarn@berry' | 'bun'
/**
* Log a development dependency is missing
*
* @param dependency - The name of the missing dependency
*/
#logMissingDevelopmentDependency(dependency: string) {
this.logger.error(
[
`Cannot find package "${dependency}"`,
'',
`The "${dependency}" package is a development dependency and therefore you should use the build command with development dependencies installed.`,
'',
'If you are using the build command inside a CI or with a deployment platform, make sure the NODE_ENV is set to "development"',
].join('\n')
)
}
/**
* Build application
*/
async run() {
const assembler = await importAssembler(this.app)
if (!assembler) {
this.#logMissingDevelopmentDependency('@adonisjs/assembler')
this.exitCode = 1
return
}
const ts = await importTypeScript(this.app)
if (!ts) {
this.#logMissingDevelopmentDependency('typescript')
this.exitCode = 1
return
}
const bundler = new assembler.Bundler(this.app.appRoot, ts, {
metaFiles: this.app.rcFile.metaFiles,
hooks: this.app.rcFile.hooks,
})
/**
* Share command logger with assembler, so that CLI flags like --no-ansi has
* similar impact for assembler logs as well.
*/
bundler.ui.logger = this.logger
/**
* Bundle project for production
*/
const stopOnError = this.ignoreTsErrors === true ? false : true
const builtSuccessfully = await bundler.bundle(stopOnError, this.packageManager)
if (!builtSuccessfully) {
this.exitCode = 1
}
}
}
================================================
FILE: commands/configure.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { RuntimeException } from '@poppinss/utils/exception'
import { stubsRoot } from '../stubs/main.ts'
import type { CommandOptions } from '../types/ace.ts'
import { args, BaseCommand, flags } from '../modules/ace/main.ts'
/**
* Command to configure packages after installation by running their configuration hooks.
* Supports built-in configurations for VineJS, Edge, and health checks, or can execute
* custom configure functions exported by packages.
*
* @example
* ```
* ace configure @adonisjs/lucid
* ace configure vinejs
* ace configure edge
* ace configure health_checks
* ace configure @adonisjs/auth --force --verbose
* ```
*/
export default class Configure extends BaseCommand {
/**
* The command name
*/
static commandName = 'configure'
/**
* The command description
*/
static description =
'Run the configure hook of an already-installed package. Use "add" command instead to both install and configure in one step'
/**
* Command options configuration.
* Allows unknown flags to be passed to package configure functions.
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Expose all flags from the protected property "parsed" for access by package configure functions
*/
get parsedFlags() {
return this.parsed.flags
}
/**
* Expose all arguments from the protected property "parsed" for access by package configure functions
*/
get parsedArgs() {
return this.parsed._
}
/**
* Name of the package to configure
*/
@args.string({ description: 'Package name' })
declare name: string
/**
* Enable verbose logging during package installation and configuration
*/
@flags.boolean({ description: 'Display logs in verbose mode', alias: 'v' })
declare verbose?: boolean
/**
* Forcefully overwrite existing files during configuration
*/
@flags.boolean({ description: 'Forcefully overwrite existing files', alias: 'f' })
declare force?: boolean
/**
* The root directory path of the package's stubs.
* Set automatically when the package exports a stubsRoot property.
*/
declare stubsRoot: string
/**
* Import and return the main exports of a package.
* Returns null if the package is not found, rethrows other errors.
*
* @param packageName - The name of the package to import
* @returns The package exports or null if not found
*/
async #getPackageSource(packageName: string) {
try {
const packageExports = await this.app.import(packageName)
return packageExports
} catch (error) {
if (
(error.code && error.code === 'ERR_MODULE_NOT_FOUND') ||
error.message.startsWith('Cannot find module')
) {
return null
}
throw error
}
}
/**
* Configure VineJS validation library by registering its provider in the RC file
*/
async #configureVineJS() {
const codemods = await this.createCodemods()
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider('@adonisjs/core/providers/vinejs_provider')
})
}
/**
* Configure Edge template engine by registering its provider and adding view meta files
*/
async #configureEdge() {
const codemods = await this.createCodemods()
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider('@adonisjs/core/providers/edge_provider')
rcFile.addMetaFile('resources/views/**/*.edge', false)
})
}
/**
* Configure health checks feature by generating the main health file and controller
*/
async #configureHealthChecks() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(stubsRoot, 'make/health/main.stub', {
flags: this.parsed.flags,
entity: this.app.generators.createEntity('health'),
})
await codemods.makeUsingStub(stubsRoot, 'make/health/controller.stub', {
flags: this.parsed.flags,
entity: this.app.generators.createEntity('health_checks'),
})
}
/**
* Create a codemods instance configured with command options.
* Sets overwrite and verbose flags based on command arguments.
*/
async createCodemods() {
const codemods = await super.createCodemods()
codemods.overwriteExisting = this.force === true
codemods.verboseInstallOutput = this.verbose === true
return codemods
}
/**
* Execute the configure command. Handles built-in configurations for VineJS, Edge,
* and health checks, or imports and executes the configure function from the specified package.
*/
async run() {
if (this.name === 'vinejs') {
return this.#configureVineJS()
}
if (this.name === 'edge') {
return this.#configureEdge()
}
if (this.name === 'health_checks') {
return this.#configureHealthChecks()
}
const packageExports = await this.#getPackageSource(this.name)
if (!packageExports) {
this.logger.error(`Cannot find module "${this.name}". Make sure to install it`)
this.exitCode = 1
return
}
/**
* Warn, there are not instructions to run
*/
if (!packageExports.configure) {
this.logger.warning(
`Cannot configure module "${this.name}". The module does not export the configure hook`
)
return
}
/**
* Set stubsRoot property when package exports it
*/
if (packageExports.stubsRoot) {
this.stubsRoot = packageExports.stubsRoot
}
/**
* Run instructions
*/
try {
await packageExports.configure(this)
} catch (error) {
throw new RuntimeException(`Unable to configure package "${this.name}"`, {
cause: error,
})
}
}
}
================================================
FILE: commands/eject.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { args, BaseCommand, flags } from '../modules/ace/main.ts'
import stringHelpers from '../src/helpers/string.ts'
/**
* Command to eject scaffolding stubs from packages to your application root.
* This allows you to customize templates used by make commands and other
* code generation features by copying them to your local application.
*
* @example
* ```
* ace eject make/controller
* ace eject make/controller --pkg=@adonisjs/lucid
* ace eject stubs/
* ```
*/
export default class Eject extends BaseCommand {
/**
* The command name
*/
static commandName = 'eject'
/**
* The command description
*/
static description =
'Copy scaffolding stubs from a package to your application for customization. Stubs are templates used by make:* commands'
/**
* Path to the stubs directory or a single stub file to eject
*/
@args.string({ description: 'Path to the stubs directory or a single stub file' })
declare stubPath: string
/**
* Package name to search for stubs. Defaults to @adonisjs/core
*/
@flags.string({
description: 'Mention package name for searching stubs',
default: '@adonisjs/core',
})
declare pkg: string
/**
* Execute the command to eject stubs from the specified package.
* Copies the stubs to the application root and logs success messages
* for each ejected file.
*/
async run() {
const stubs = await this.app.stubs.create()
const copied = await stubs.copy(this.stubPath, {
pkg: this.pkg,
})
copied.forEach((stubPath) => {
this.logger.success(`eject ${stringHelpers.toUnixSlash(this.app.relativePath(stubPath))}`)
})
}
}
================================================
FILE: commands/env/add.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type CommandOptions } from '../../types/ace.ts'
import stringHelpers from '../../src/helpers/string.ts'
import { args, BaseCommand, flags } from '../../modules/ace/main.ts'
const ALLOWED_TYPES = ['string', 'boolean', 'number', 'enum'] as const
type AllowedTypes = (typeof ALLOWED_TYPES)[number]
/**
* Command to add a new environment variable to the application.
* Updates .env, .env.example, and start/env.ts files with the new variable,
* including appropriate validation schema based on the variable type.
*
* @example
* ```
* ace env:add
* ace env:add DATABASE_URL postgres://localhost:5432/mydb
* ace env:add API_KEY secret --type=string
* ace env:add PORT 3333 --type=number
* ace env:add DEBUG true --type=boolean
* ace env:add LOG_LEVEL info --type=enum --enum-values=debug,info,warn,error
* ```
*/
export default class EnvAdd extends BaseCommand {
/**
* The command name
*/
static commandName = 'env:add'
/**
* The command description
*/
static description =
'Add a new environment variable to .env, .env.example, and its validation rule to start/env.ts'
/**
* Command options configuration.
* Allows unknown flags to be passed through.
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Environment variable name (will be converted to SCREAMING_SNAKE_CASE)
*/
@args.string({
description: 'Variable name. Will be converted to screaming snake case',
required: false,
})
declare name: string
/**
* Environment variable value
*/
@args.string({ description: 'Variable value', required: false })
declare value: string
/**
* Data type of the environment variable (string, boolean, number, enum)
*/
@flags.string({ description: 'Type of the variable' })
declare type: AllowedTypes
/**
* Allowed values for enum type variables
*/
@flags.array({
description: 'Allowed values for the enum type in a comma-separated list',
default: [''],
required: false,
})
declare enumValues: string[]
/**
* Validate that the provided type is one of the allowed types.
*
* @returns True if the type is valid, false otherwise
*/
#isTypeFlagValid() {
return ALLOWED_TYPES.includes(this.type)
}
/**
* Execute the command to add a new environment variable.
* Prompts for missing values, validates inputs, and updates all relevant files.
*/
async run() {
/**
* Prompt for missing name
*/
if (!this.name) {
this.name = await this.prompt.ask('Enter the variable name', {
validate: (value) => !!value,
format: (value) => stringHelpers.snakeCase(value).toUpperCase(),
})
}
/**
* Prompt for missing value
*/
if (!this.value) {
this.value = await this.prompt.ask('Enter the variable value')
}
/**
* Prompt for missing type
*/
if (!this.type) {
this.type = await this.prompt.choice('Select the variable type', ALLOWED_TYPES)
}
/**
* Prompt for missing enum values if the selected env type is `enum`
*/
if (this.type === 'enum' && !this.enumValues) {
this.enumValues = await this.prompt.ask('Enter the enum values separated by a comma', {
result: (value) => value.split(',').map((one) => one.trim()),
})
}
/**
* Validate inputs
*/
if (!this.#isTypeFlagValid()) {
this.logger.error(`Invalid type "${this.type}". Must be one of ${ALLOWED_TYPES.join(', ')}`)
return
}
/**
* Add the environment variable to the `.env` and `.env.example` files
*/
const codemods = await this.createCodemods()
const transformedName = stringHelpers.snakeCase(this.name).toUpperCase()
await codemods.defineEnvVariables(
{ [transformedName]: this.value },
{ omitFromExample: [transformedName] }
)
/**
* Add the environment variable to the `start/env.ts` file
*/
const validation = {
string: 'Env.schema.string()',
number: 'Env.schema.number()',
boolean: 'Env.schema.boolean()',
enum: `Env.schema.enum(['${this.enumValues.join("','")}'] as const)`,
}[this.type]
await codemods.defineEnvValidations({ variables: { [transformedName]: validation } })
this.logger.success('Environment variable added successfully')
}
}
================================================
FILE: commands/generate_key.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import string from '@poppinss/utils/string'
import { EnvEditor } from '@adonisjs/env/editor'
import { BaseCommand, flags } from '../modules/ace/main.ts'
/**
* The generate key command is used to generate the app key
* and write it inside the .env file.
*
* @example
* ```
* ace generate:key
* ace generate:key --show
* ace generate:key --force
* ```
*/
export default class GenerateKey extends BaseCommand {
/**
* The command name
*/
static commandName = 'generate:key'
/**
* The command description
*/
static description =
'Generate a cryptographically secure APP_KEY and write it to the .env file. Use --show to print without writing'
/**
* Display the key on the terminal, instead of writing it to .env file
*/
@flags.boolean({
description: 'Display the key on the terminal, instead of writing it to .env file',
})
declare show: boolean
/**
* Force update .env file in production environment
*/
@flags.boolean({
description: 'Force update .env file in production environment',
})
declare force: boolean
async run() {
let writeToFile = process.env.NODE_ENV !== 'production'
if (this.force) {
writeToFile = true
}
if (this.show) {
writeToFile = false
}
const secureKey = string.random(32)
if (writeToFile) {
const editor = await EnvEditor.create(this.app.appRoot)
editor.add('APP_KEY', secureKey, true)
await editor.save()
this.logger.action('add APP_KEY to .env').succeeded()
} else {
this.logger.log(`APP_KEY = ${secureKey}`)
}
}
}
================================================
FILE: commands/inspect_rcfile.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { BaseCommand } from '../modules/ace/main.ts'
/**
* Command to inspect and display the AdonisJS RC file contents with default values.
* The RC file contains configuration for providers, preloads, commands, and other
* application settings. This command formats and displays the contents in a readable JSON format.
*
* @example
* ```
* ace inspect:rcfile
* ```
*/
export default class InspectRCFile extends BaseCommand {
/**
* The command name
*/
static commandName = 'inspect:rcfile'
/**
* The command description
*/
static description =
'Display the resolved adonisrc.ts configuration as JSON, including providers, preloads, commands, and meta files'
/**
* Execute the command to display RC file contents.
* Transforms provider, preload, and command entries to display their file paths
* as strings and formats the output as readable JSON.
*/
async run() {
const { raw, providers, preloads, commands, ...rest } = this.app.rcFile
this.logger.log(
JSON.stringify(
{
...rest,
providers: providers.map((provider) => {
return {
...provider,
file: provider.file.toString(),
}
}),
preloads: preloads.map((preload) => {
return {
...preload,
file: preload.file.toString(),
}
}),
commands: commands.map((command) => {
return command.toString()
}),
},
null,
2
)
)
}
}
================================================
FILE: commands/list/routes.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import type { CommandOptions } from '../../types/ace.ts'
import { args, BaseCommand, flags } from '../../modules/ace/main.ts'
import { RoutesListFormatter } from '../../src/cli_formatters/routes_list.ts'
/**
* Command to display a list of all registered routes in the application.
* Supports filtering by keywords, middleware, and output formatting options.
* Routes can be displayed as a formatted list, table, or JSON.
*
* @example
* ```
* ace list:routes
* ace list:routes user
* ace list:routes --middleware=auth
* ace list:routes --ignore-middleware=guest
* ace list:routes --json
* ace list:routes --table
* ```
*/
export default class ListRoutes extends BaseCommand {
/**
* The command name
*/
static commandName = 'list:routes'
/**
* The command description
*/
static description =
'List all registered routes with their HTTP methods, URL patterns, handlers, and middleware'
/**
* Command options configuration.
* Requires the application to be started so routes are loaded.
*/
static options: CommandOptions = {
startApp: true,
}
/**
* Keyword to match against route names, patterns, and controller names
*/
@args.string({
description:
'Find routes matching the given keyword. Route name, pattern and controller name will be searched against the keyword',
required: false,
})
declare match: string
/**
* Filter routes that include all specified middleware names
*/
@flags.array({
description:
'View routes that includes all the mentioned middleware names. Use * to see routes that are using one or more middleware',
})
declare middleware: string[]
/**
* Filter routes that do not include all specified middleware names
*/
@flags.array({
description:
'View routes that does not include all the mentioned middleware names. Use * to see routes that are using zero middleware',
})
declare ignoreMiddleware: string[]
/**
* Output routes as JSON format
*/
@flags.boolean({ description: 'Get routes list as a JSON string' })
declare json: boolean
/**
* Output routes as a CLI table format
*/
@flags.boolean({ description: 'View list of routes as a table' })
declare table: boolean
/**
* Output routes as JSONL (one JSON object per line), optimized for
* machine consumption by AI agents and CLI tools
*/
@flags.boolean({
description: 'Get routes as JSONL, one JSON object per line (optimized for AI agents)',
})
declare jsonl: boolean
/**
* Execute the command to list application routes.
* Creates a formatter with the specified filters and outputs routes
* in the requested format (JSON, table, or formatted list).
*/
async run() {
const router = await this.app.container.make('router')
const formatter = new RoutesListFormatter(
router,
this.ui,
{},
{
ignoreMiddleware: this.ignoreMiddleware,
middleware: this.middleware,
match: this.match,
}
)
/**
* Display as JSONL (one JSON object per line).
* Auto-selected when running inside an AI agent and no
* explicit format flag is provided.
*/
if (this.jsonl || (!this.json && !this.table && this.app.runningInAIAgent)) {
const lines = await formatter.formatAsJSONL()
for (const line of lines) {
this.logger.log(line)
}
return
}
/**
* Display as JSON
*/
if (this.json) {
this.logger.log(JSON.stringify(await formatter.formatAsJSON(), null, 2))
return
}
/**
* Display as a standard table
*/
if (this.table) {
const tables = await formatter.formatAsAnsiTable()
tables.forEach((table) => {
this.logger.log('')
if (table.heading) {
this.logger.log(table.heading)
this.logger.log('')
}
table.table.render()
})
return
}
/**
* Display as a list
*/
const list = await formatter.formatAsAnsiList()
list.forEach((item) => {
this.logger.log('')
if (item.heading) {
this.logger.log(item.heading)
this.logger.log('')
}
this.logger.log(item.rows.join('\n'))
})
}
}
================================================
FILE: commands/make/command.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags } from '../../modules/ace/main.ts'
import { BaseCommand } from '../../modules/ace/main.ts'
/**
* Command to create a new Ace command class.
* Ace commands are CLI commands that can be executed via the `ace` binary,
* allowing you to create custom functionality for your application's command line interface.
*
* @example
* ```
* ace make:command SendEmails
* ace make:command ProcessPayments
* ace make:command GenerateReports
* ace make:command CleanupFiles
* ```
*/
export default class MakeCommand extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:command'
/**
* The command description
*/
static description = 'Create a new Ace CLI command class in commands/'
/**
* Name of the command class to create
*/
@args.string({ description: 'Name of the command' })
declare name: string
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the command class
*/
protected stubPath: string = 'make/command/main.stub'
/**
* Execute the command to create a new Ace command class.
* Generates the command file with proper CLI command structure.
*/
async run() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/controller.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import string from '@poppinss/utils/string'
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
import { type CommandOptions } from '../../types/ace.ts'
/**
* The make controller command to create an HTTP controller
*
* @example
* ```
* ace make:controller User
* ace make:controller User store update
* ace make:controller User --resource
* ace make:controller User --api
* ace make:controller User --singular
* ```
*/
export default class MakeController extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:controller'
/**
* The command description
*/
static description =
'Create a new HTTP controller class in app/controllers. Use --resource for CRUD methods or --api for API-only CRUD (no create/edit)'
/**
* Command options configuration
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* The name of the controller
*/
@args.string({ description: 'The name of the controller' })
declare name: string
/**
* Create controller with custom method names
*/
@args.spread({ description: 'Create controller with custom method names', required: false })
declare actions?: string[]
/**
* Generate controller in singular form
*/
@flags.boolean({
description: 'Generate controller in singular form',
alias: 's',
})
declare singular: boolean
/**
* Generate resourceful controller with methods to perform CRUD actions on a resource
*/
@flags.boolean({
description:
'Generate resourceful controller with methods to perform CRUD actions on a resource',
alias: 'r',
})
declare resource: boolean
/**
* Generate resourceful controller without the "edit" and the "create" methods
*/
@flags.boolean({
description: 'Generate resourceful controller without the "edit" and the "create" methods',
alias: 'a',
})
declare api: boolean
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub to use for generating the controller
*/
protected stubPath: string = 'make/controller/main.stub'
/**
* Preparing the command state
*/
async prepare() {
/**
* Use actions stub
*/
if (this.actions) {
this.stubPath = 'make/controller/actions.stub'
}
/**
* Use resource stub
*/
if (this.resource) {
if (this.actions) {
this.logger.warning('Cannot use --resource flag with actions. Ignoring --resource')
} else {
this.stubPath = 'make/controller/resource.stub'
}
}
/**
* Use api stub
*/
if (this.api) {
if (this.actions) {
this.logger.warning('Cannot use --api flag with actions. Ignoring --api')
} else {
this.stubPath = 'make/controller/api.stub'
}
}
/**
* Log warning when both flags are used together
*/
if (this.resource && this.api && !this.actions) {
this.logger.warning('--api and --resource flags cannot be used together. Ignoring --resource')
}
}
async run() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
actions: this.actions?.map((action) => string.camelCase(action)),
entity: this.app.generators.createEntity(this.name),
singular: this.singular,
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/event.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
import { type CommandOptions } from '../../types/ace.ts'
/**
* Command to create a new event class.
* Events are data objects that encapsulate information about something
* that happened in your application and can be dispatched to listeners.
*
* @example
* ```
* ace make:event UserRegistered
* ace make:event OrderCompleted
* ace make:event EmailSent
* ```
*/
export default class MakeEvent extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:event'
/**
* The command description
*/
static description =
'Create a new event class in app/events. Events are dispatched via emitter and handled by listeners'
/**
* Command options configuration.
* Allows unknown flags to be passed through.
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Name of the event class to create
*/
@args.string({ description: 'Name of the event' })
declare name: string
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the event class
*/
protected stubPath: string = 'make/event/main.stub'
/**
* Execute the command to create a new event class.
* Generates the event file with proper event structure.
*/
async run() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/exception.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
import { type CommandOptions } from '../../types/ace.ts'
/**
* Command to create a new custom exception class.
* Custom exceptions allow you to define specific error types for your application
* with custom error messages, status codes, and error handling logic.
*
* @example
* ```
* ace make:exception ValidationException
* ace make:exception UnauthorizedException
* ace make:exception ResourceNotFoundException
* ```
*/
export default class MakeException extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:exception'
/**
* The command description
*/
static description =
'Create a new custom exception class in app/exceptions with handle and report methods'
/**
* Command options configuration.
* Allows unknown flags to be passed through.
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Name of the exception class to create
*/
@args.string({ description: 'Name of the exception' })
declare name: string
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the exception class
*/
protected stubPath: string = 'make/exception/main.stub'
/**
* Execute the command to create a new custom exception class.
* Generates the exception file with proper error handling structure.
*/
async run() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/listener.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
import { type CommandOptions } from '../../types/ace.ts'
/**
* Command to create a new event listener class.
* Event listeners handle events dispatched by the application and can optionally
* generate the corresponding event class automatically.
*
* @example
* ```
* ace make:listener UserRegistered
* ace make:listener EmailSent --event=EmailSent
* ace make:listener OrderCompleted --event=OrderEvent
* ```
*/
export default class MakeListener extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:listener'
/**
* The command description
*/
static description =
'Create a new event listener class in app/listeners. Use --event to also generate the event class and bind them together'
/**
* Command options configuration.
* Allows unknown flags to be passed through.
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Name of the event listener class to create
*/
@args.string({ description: 'Name of the event listener' })
declare name: string
/**
* Generate an event class alongside the listener and bind them together
*/
@flags.string({
description: 'Generate an event class alongside the listener',
alias: 'e',
})
declare event: string
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the event listener
*/
protected stubPath: string = 'make/listener/main.stub'
/**
* Prepare the command by selecting the appropriate stub based on options.
* Uses a different stub when generating a listener for a specific event.
*/
prepare() {
if (this.event) {
this.stubPath = 'make/listener/for_event.stub'
}
}
/**
* Execute the command to create a new event listener.
* If an event is specified, creates the event class first,
* then generates the listener with proper event binding.
*/
async run() {
const codemods = await this.createCodemods()
if (this.event) {
const { exitCode } = await this.kernel.exec('make:event', [this.event])
/**
* Create listener only when make:event is completed successfully
*/
if (exitCode === 0) {
const eventEntity = this.app.generators.createEntity(this.event)
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
event: eventEntity,
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
return
}
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/middleware.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import string from '@poppinss/utils/string'
import { basename, extname, relative } from 'node:path'
import { stubsRoot } from '../../stubs/main.ts'
import { type CommandOptions } from '../../types/ace.ts'
import stringHelpers from '../../src/helpers/string.ts'
import { args, BaseCommand, flags } from '../../modules/ace/main.ts'
/**
* The make middleware command to create a new middleware
* class.
*
* @example
* ```
* ace make:middleware Auth
* ace make:middleware Auth --stack=server
* ace make:middleware Auth --stack=named
* ace make:middleware Auth --stack=router
* ```
*/
export default class MakeMiddleware extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:middleware'
/**
* The command description
*/
static description =
'Create a new middleware class in app/middleware and register it in start/kernel.ts under the chosen stack (server, router, or named)'
/**
* Command options configuration
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Name of the middleware
*/
@args.string({ description: 'Name of the middleware' })
declare name: string
/**
* The stack in which to register the middleware
*/
@flags.string({ description: 'The stack in which to register the middleware', alias: 's' })
declare stack?: 'server' | 'named' | 'router'
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub to use for generating the middleware
*/
protected stubPath: string = 'make/middleware/main.stub'
async run() {
const stackChoices = ['server', 'router', 'named']
/**
* Prompt to select the stack under which to register
* the middleware
*/
if (!this.stack) {
this.stack = await this.prompt.choice(
'Under which stack you want to register the middleware?',
stackChoices
)
}
/**
* Error out when mentioned stack is invalid
*/
if (!stackChoices.includes(this.stack)) {
this.exitCode = 1
this.logger.error(
`Invalid middleware stack "${this.stack}". Select from "${stackChoices.join(', ')}"`
)
return
}
/**
* Create middleware
*/
const codemods = await this.createCodemods()
const { destination } = await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
/**
* Creative relative path for the middleware file from
* the "./app/middleware" directory
*/
const middlewareRelativePath = stringHelpers.toUnixSlash(
relative(this.app.middlewarePath(), destination).replace(extname(destination), '')
)
/**
* Take the middleware relative path, remove `_middleware` prefix from it
* and convert everything to camelcase
*/
const name = string.camelCase(basename(middlewareRelativePath).replace(/_middleware$/, ''))
/**
* Register middleware
*/
await codemods.registerMiddleware(this.stack, [
{
name: name,
path: `#middleware/${middlewareRelativePath}`,
},
])
}
}
================================================
FILE: commands/make/preload.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { extname, relative } from 'node:path'
import type { AppEnvironments } from '@adonisjs/application/types'
import { stubsRoot } from '../../stubs/main.ts'
import stringHelpers from '../../src/helpers/string.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
const ALLOWED_ENVIRONMENTS = ['web', 'console', 'test', 'repl'] satisfies AppEnvironments[]
type AllowedAppEnvironments = typeof ALLOWED_ENVIRONMENTS
/**
* Command to create a new preload file in the start directory.
* Preload files are executed during application startup and can be used
* to set up global configurations, register global bindings, or perform
* application-wide initialization tasks.
*
* @example
* ```
* ace make:preload routes
* ace make:preload database --register
* ace make:preload events --no-register
* ace make:preload kernel --environments=web,console
* ```
*/
export default class MakePreload extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:preload'
/**
* The command description
*/
static description =
'Create a new preload file in start/ and optionally register it in adonisrc.ts. Preload files run during app boot'
/**
* Name of the preload file to create
*/
@args.string({ description: 'Name of the preload file' })
declare name: string
/**
* Automatically register the preload file in the .adonisrc.ts file
*/
@flags.boolean({
description: 'Auto register the preload file inside the .adonisrc.ts file',
showNegatedVariantInHelp: true,
alias: 'r',
})
declare register?: boolean
/**
* Application environments where the preload file should be loaded
*/
@flags.array({
description: `Define the preload file's environment. Accepted values are "${ALLOWED_ENVIRONMENTS}"`,
alias: 'e',
})
declare environments?: AllowedAppEnvironments
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the preload file
*/
protected stubPath: string = 'make/preload/main.stub'
/**
* Validate that all specified environments are valid application environments.
*
* @returns True if all environments are valid or none specified, false otherwise
*/
#isEnvironmentsFlagValid() {
if (!this.environments || !this.environments.length) {
return true
}
return this.environments.every((one) => ALLOWED_ENVIRONMENTS.includes(one))
}
/**
* Execute the command to create a new preload file.
* Validates inputs, generates the preload file, and optionally registers it in .adonisrc.ts.
*/
async run() {
/**
* Ensure the environments are valid when provided via flag
*/
if (!this.#isEnvironmentsFlagValid()) {
this.logger.error(
`Invalid environment(s) "${this.environments}". Only "${ALLOWED_ENVIRONMENTS}" are allowed`
)
return
}
/**
* Display prompt to know if we should register the preload
* file inside the ".adonisrc.ts" file.
*/
if (this.register === undefined) {
this.register = await this.prompt.confirm(
'Do you want to register the preload file in .adonisrc.ts file?'
)
}
const codemods = await this.createCodemods()
const { destination } = await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
/**
* Do not register when prompt has been denied or "--no-register"
* flag was used
*/
if (!this.register) {
return
}
/**
* Creative relative path for the preload file from
* the "./start" directory
*/
const preloadFileRelativePath = stringHelpers.toUnixSlash(
relative(this.app.startPath(), destination).replace(extname(destination), '')
)
await codemods.updateRcFile((rcFile) => {
rcFile.addPreloadFile(`#start/${preloadFileRelativePath}`, this.environments)
})
}
}
================================================
FILE: commands/make/provider.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { extname, relative } from 'node:path'
import { stubsRoot } from '../../stubs/main.ts'
import type { AppEnvironments } from '../../types/app.ts'
import stringHelpers from '../../src/helpers/string.ts'
import { args, BaseCommand, flags } from '../../modules/ace/main.ts'
const ALLOWED_ENVIRONMENTS = ['web', 'console', 'test', 'repl'] satisfies AppEnvironments[]
type AllowedAppEnvironments = typeof ALLOWED_ENVIRONMENTS
/**
* Command to create a new service provider class.
* Service providers are used to register bindings, configure services,
* and bootstrap application components during startup.
*
* @example
* ```
* ace make:provider AuthProvider
* ace make:provider DatabaseProvider --register
* ace make:provider AppProvider --no-register
* ace make:provider CacheProvider --environments=web,console
* ```
*/
export default class MakeProvider extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:provider'
/**
* The command description
*/
static description =
'Create a new service provider in providers/ and optionally register it in adonisrc.ts. Providers register IoC container bindings'
/**
* Name of the service provider to create
*/
@args.string({ description: 'Name of the provider' })
declare name: string
/**
* Automatically register the provider in the .adonisrc.ts file
*/
@flags.boolean({
description: 'Auto register the provider inside the .adonisrc.ts file',
showNegatedVariantInHelp: true,
alias: 'r',
})
declare register?: boolean
/**
* Application environments where the provider should be loaded
*/
@flags.array({
description: `Define the provider environment. Accepted values are "${ALLOWED_ENVIRONMENTS}"`,
alias: 'e',
})
declare environments?: AllowedAppEnvironments
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the provider class
*/
protected stubPath: string = 'make/provider/main.stub'
/**
* Validate that all specified environments are valid application environments.
*
* @returns True if all environments are valid or none specified, false otherwise
*/
#isEnvironmentsFlagValid() {
if (!this.environments || !this.environments.length) {
return true
}
return this.environments.every((one) => ALLOWED_ENVIRONMENTS.includes(one))
}
/**
* Execute the command to create a new service provider.
* Validates inputs, generates the provider file, and optionally registers it in .adonisrc.ts.
*/
async run() {
/**
* Ensure the environments are valid when provided via flag
*/
if (!this.#isEnvironmentsFlagValid()) {
this.logger.error(
`Invalid environment(s) "${this.environments}". Only "${ALLOWED_ENVIRONMENTS}" are allowed`
)
return
}
/**
* Display prompt to know if we should register the provider
* file inside the ".adonisrc.ts" file.
*/
if (this.register === undefined) {
this.register = await this.prompt.confirm(
'Do you want to register the provider in .adonisrc.ts file?'
)
}
const codemods = await this.createCodemods()
const { destination } = await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
/**
* Do not register when prompt has been denied or "--no-register"
* flag was used
*/
if (!this.register) {
return
}
/**
* Creative relative path for the provider file from
* the "./start" directory
*/
const providerRelativePath = stringHelpers.toUnixSlash(
relative(this.app.providersPath(), destination).replace(extname(destination), '')
)
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider(`#providers/${providerRelativePath}`, this.environments)
})
}
}
================================================
FILE: commands/make/service.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
import { type CommandOptions } from '../../types/ace.ts'
/**
* Make a new service class
*
* @example
* ```
* ace make:service UserService
* ace make:service AuthService
* ace make:service User/ProfileService
* ```
*/
export default class MakeService extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:service'
/**
* The command description
*/
static description =
'Create a new service class in app/services. Services encapsulate reusable business logic outside of controllers'
/**
* Command options configuration
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Name of the service
*/
@args.string({ description: 'Name of the service' })
declare name: string
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub to use for generating the service class
*/
protected stubPath: string = 'make/service/main.stub'
async run() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/test.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
/**
* Command to create a new Japa test file.
* Supports multiple test suites and automatically detects or prompts for
* the appropriate suite and directory based on application configuration.
*
* @example
* ```
* ace make:test UserController
* ace make:test UserModel --suite=unit
* ace make:test AuthService --suite=integration
* ```
*/
export default class MakeTest extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:test'
/**
* The command description
*/
static description =
'Create a new Japa test file in tests/. Use --suite to specify the test suite (unit, functional, etc.)'
/**
* Name of the test file to create
*/
@args.string({ description: 'Name of the test file' })
declare name: string
/**
* Test suite name where the test file should be created
*/
@flags.string({ description: 'The suite for which to create the test file', alias: 's' })
declare suite?: string
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the test file
*/
protected stubPath: string = 'make/test/main.stub'
/**
* Determine the test suite name for creating the test file.
* Uses the provided suite flag, or automatically selects if only one suite exists,
* or prompts the user to choose from available suites.
*
* @returns The name of the selected test suite
*/
async #getSuite(): Promise {
if (this.suite) {
return this.suite
}
/**
* Use the first suite from the rcFile when there is only
* one suite
*/
const rcFileSuites = this.app.rcFile.tests.suites
if (rcFileSuites.length === 1) {
return rcFileSuites[0].name
}
/**
* Prompt the user to select a suite manually
*/
return this.prompt.choice(
'Select the suite for the test file',
this.app.rcFile.tests.suites.map((suite) => {
return suite.name
}),
{
validate(choice) {
return choice ? true : 'Please select a suite'
},
}
)
}
/**
* Determine the directory path for the test file within the selected suite.
* Automatically selects if only one directory exists, otherwise prompts the user.
*
* @param directories - Array of available directories for the suite
* @returns The selected directory path
*/
async #getSuiteDirectory(directories: string[]): Promise {
if (directories.length === 1) {
return directories[0]
}
return this.prompt.choice('Select directory for the test file', directories, {
validate(choice) {
return choice ? true : 'Please select a directory'
},
})
}
/**
* Find suite configuration from the RC file by name.
*
* @param suiteName - The name of the suite to find
* @returns The suite configuration or undefined if not found
*/
#findSuite(suiteName: string) {
return this.app.rcFile.tests.suites.find((suite) => {
return suite.name === suiteName
})
}
/**
* Execute the command to create a new test file.
* Validates the suite exists, prompts for missing information,
* and generates the test file in the appropriate location.
*/
async run() {
const suite = this.#findSuite(await this.#getSuite())
/**
* Show error when mentioned/selected suite does not exist
*/
if (!suite) {
this.logger.error(`The "${this.suite}" suite is not configured inside the "adonisrc.js" file`)
this.exitCode = 1
return
}
/**
* Generate entity
*/
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
suite: {
directory: await this.#getSuiteDirectory(suite.directories),
},
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/transformer.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
import { type CommandOptions } from '../../types/ace.ts'
/**
* Command to create a new transformer class.
* Transformers are used to serialize data objects (like models) into specific
* formats for API responses, allowing you to control which fields are exposed
* and how data is structured for clients.
*
* @example
* ```
* ace make:transformer User
* ace make:transformer Post
* ace make:transformer ProductTransformer
* ```
*/
export default class MakeTransformer extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:transformer'
/**
* The command description
*/
static description =
'Create a new transformer class in app/transformers for serializing data in API responses'
/**
* Command options configuration.
* Allows unknown flags to be passed through.
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Name of the entity for which to generate the transformer
*/
@args.string({ description: 'Entity name for which to generate the transformer' })
declare name: string
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the transformer class
*/
protected stubPath: string = 'make/transformer/main.stub'
/**
* Execute the command to create a new transformer class.
* Generates the transformer file with proper data serialization structure.
*/
async run() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
model: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/validator.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
import { type CommandOptions } from '../../types/ace.ts'
/**
* Command to create a new VineJS validator file.
* Validators define reusable validation schemas for request validation,
* and can be generated as simple validators or resource-based validators
* with create and update schemas.
*
* @example
* ```
* ace make:validator UserValidator
* ace make:validator PostValidator --resource
* ace make:validator ContactValidator
* ```
*/
export default class MakeValidator extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:validator'
/**
* The command description
*/
static description =
'Create a new VineJS validator file in app/validators. Use --resource to generate both create and update validation schemas'
/**
* Command options configuration.
* Allows unknown flags to be passed through.
*/
static options: CommandOptions = {
allowUnknownFlags: true,
}
/**
* Name of the validator file to create
*/
@args.string({ description: 'Name of the validator file' })
declare name: string
/**
* Generate a resource validator with create and update schemas
*/
@flags.boolean({
description: 'Create a file with pre-defined validators for create and update actions',
})
declare resource: boolean
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the validator
*/
protected stubPath: string = 'make/validator/main.stub'
/**
* Prepare the command by selecting the appropriate stub based on options.
* Uses a resource stub when generating validators for CRUD operations.
*/
async prepare() {
/**
* Use resource stub
*/
if (this.resource) {
this.stubPath = 'make/validator/resource.stub'
}
}
/**
* Execute the command to create a new VineJS validator file.
* Generates the validator with the appropriate stub template.
*/
async run() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/make/view.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { stubsRoot } from '../../stubs/main.ts'
import { args, flags, BaseCommand } from '../../modules/ace/main.ts'
/**
* Command to create a new Edge.js template file.
* Edge templates are used for rendering HTML views in your web application,
* supporting layouts, partials, components, and template inheritance.
*
* @example
* ```
* ace make:view home
* ace make:view users/profile
* ace make:view components/navbar
* ace make:view layouts/app
* ```
*/
export default class MakeView extends BaseCommand {
/**
* The command name
*/
static commandName = 'make:view'
/**
* The command description
*/
static description = 'Create a new Edge.js template file in resources/views'
/**
* Name of the template file to create
*/
@args.string({ description: 'Name of the template' })
declare name: string
/**
* Read the contents from this file (if the flag exists) and use
* it as the raw contents
*/
@flags.string({ description: 'Use the contents of the given file as the generated output' })
declare contentsFrom: string
/**
* The stub template file to use for generating the Edge template
*/
protected stubPath: string = 'make/view/main.stub'
/**
* Execute the command to create a new Edge.js template file.
* Generates the template file in the views directory.
*/
async run() {
const codemods = await this.createCodemods()
await codemods.makeUsingStub(
stubsRoot,
this.stubPath,
{
flags: this.parsed.flags,
entity: this.app.generators.createEntity(this.name),
},
{
contentsFromFile: this.contentsFrom,
}
)
}
}
================================================
FILE: commands/repl.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { BaseCommand } from '../modules/ace/main.ts'
import { type CommandOptions } from '../types/ace.ts'
/**
* Command to start an interactive REPL (Read-Eval-Print Loop) session for AdonisJS.
* The REPL provides a command-line interface where you can execute JavaScript code
* in the context of your AdonisJS application, allowing you to interact with models,
* services, and other application components in real-time.
*
* @example
* ```
* ace repl
* ```
*/
export default class ReplCommand extends BaseCommand {
/**
* The command name
*/
static commandName = 'repl'
/**
* The command description
*/
static description =
'Start an interactive REPL session with the application booted and IoC container available'
/**
* Command options configuration.
* Requires the application to be started and keeps the process alive.
*/
static options: CommandOptions = {
startApp: true,
staysAlive: true,
}
/**
* Execute the command to start the REPL server.
* Creates a REPL instance from the container and sets up an exit handler
* that properly terminates the application when the REPL session ends.
*/
async run() {
const repl = await this.app.container.make('repl')
repl.start()
repl.server!.on('exit', async () => {
await this.terminate()
})
}
}
================================================
FILE: commands/serve.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import type { DevServer } from '@adonisjs/assembler'
import { importAssembler } from '../src/utils.ts'
import type { CommandOptions } from '../types/ace.ts'
import { BaseCommand, flags } from '../modules/ace/main.ts'
/**
* Serve command is used to run the AdonisJS HTTP server during development. The
* command under the hood runs the "bin/server.ts" file and watches for file
* system changes
*
* @example
* ```
* ace serve
* ace serve --watch
* ace serve --hmr
* ace serve --poll
* ace serve --no-clear
* ```
*/
export default class Serve extends BaseCommand {
/**
* The command name
*/
static commandName = 'serve'
/**
* The command description
*/
static description =
'Start the development HTTP server. Use --watch for auto-restart on file change or --hmr for hot module replacement'
/**
* Help text for the command
*/
static help = [
'Start the development server with file watcher using the following command.',
'```',
'{{ binaryName }} serve --watch',
'```',
'',
'You can also start the server with HMR support using the following command.',
'```',
'{{ binaryName }} serve --hmr',
'```',
'',
'The assets bundler dev server runs automatically after detecting vite config or webpack config files',
'You may pass vite CLI args using the --assets-args command line flag.',
'```',
'{{ binaryName }} serve --assets-args="--debug --base=/public"',
'```',
]
/**
* Command options configuration
*/
static options: CommandOptions = {
staysAlive: true,
}
/**
* The development server instance
*/
declare devServer: DevServer
/**
* Start the server with HMR support
*/
@flags.boolean({ description: 'Start the server with HMR support' })
declare hmr?: boolean
/**
* Watch filesystem and restart the HTTP server on file change
*/
@flags.boolean({
description: 'Watch filesystem and restart the HTTP server on file change',
alias: 'w',
})
declare watch?: boolean
/**
* Use polling to detect filesystem changes
*/
@flags.boolean({ description: 'Use polling to detect filesystem changes', alias: 'p' })
declare poll?: boolean
/**
* Clear the terminal for new logs after file change
*/
@flags.boolean({
description: 'Clear the terminal for new logs after file change',
showNegatedVariantInHelp: true,
default: true,
})
declare clear?: boolean
/**
* Log a development dependency is missing
*
* @param dependency - The name of the missing dependency
*/
#logMissingDevelopmentDependency(dependency: string) {
this.logger.error(
[
`Cannot find package "${dependency}"`,
'',
`The "${dependency}" package is a development dependency and therefore you should use the serve command during development only.`,
'',
'If you are running your application in production, then use "node bin/server.js" command to start the HTTP server',
].join('\n')
)
}
/**
* Runs the HTTP server
*/
async run() {
const assembler = await importAssembler(this.app)
if (!assembler) {
this.#logMissingDevelopmentDependency('@adonisjs/assembler')
this.exitCode = 1
return
}
if (this.watch && this.hmr) {
this.logger.error('Cannot use --watch and --hmr flags together. Choose one of them')
this.exitCode = 1
return
}
this.devServer = new assembler.DevServer(this.app.appRoot, {
hmr: this.hmr === true ? true : false,
clearScreen: this.clear === false ? false : true,
nodeArgs: this.parsed.nodeArgs,
scriptArgs: [],
metaFiles: this.app.rcFile.metaFiles,
hooks: this.app.rcFile.hooks,
})
/**
* Share command logger with assembler, so that CLI flags like --no-ansi has
* similar impact for assembler logs as well.
*/
this.devServer.ui.logger = this.logger
/**
* Exit command when the dev server is closed
*/
this.devServer.onClose((exitCode) => {
this.exitCode = exitCode
this.terminate()
})
/**
* Exit command when the dev server crashes
*/
this.devServer.onError(() => {
this.exitCode = 1
this.terminate()
})
/**
* Start the development server
*/
if (this.watch) {
await this.devServer.startAndWatch({ poll: this.poll || false })
} else {
await this.devServer.start()
}
}
}
================================================
FILE: commands/test.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import type { TestRunner } from '@adonisjs/assembler'
import { importAssembler } from '../src/utils.ts'
import type { CommandOptions } from '../types/ace.ts'
import { BaseCommand, flags, args } from '../modules/ace/main.ts'
/**
* Command to run application tests using the Japa test runner.
* Supports filtering tests by suites, files, tags, groups, and individual tests.
* Can run in watch mode to automatically re-run tests when files change.
*
* @example
* ```
* ace test
* ace test unit integration
* ace test --watch
* ace test --files=user.spec.ts
* ace test --tags=slow --groups="User tests"
* ace test --reporters=spec,dot
* ace test --timeout=5000 --retries=2
* ```
*/
export default class Test extends BaseCommand {
/**
* The command name
*/
static commandName = 'test'
/**
* The command description
*/
static description =
'Run tests using Japa test runner. Supports filtering by suite, file, tag, and group. Use --watch for re-runs on file change'
/**
* Command options configuration.
* Allows unknown flags to be passed to Japa and keeps the process alive.
*/
static options: CommandOptions = {
allowUnknownFlags: true,
staysAlive: true,
}
/**
* The test runner instance from the assembler package
*/
declare testsRunner: TestRunner
/**
* Test suite names to run. When provided, only tests from the specified suites will be executed
*/
@args.spread({
description: 'Mention suite names to run tests for selected suites',
required: false,
})
declare suites?: string[]
/**
* Filter tests by filename patterns
*/
@flags.array({ description: 'Filter tests by the filename' })
declare files?: string[]
/**
* Filter tests by tags
*/
@flags.array({ description: 'Filter tests by tags' })
declare tags?: string[]
/**
* Filter tests by parent group title
*/
@flags.array({ description: 'Filter tests by parent group title' })
declare groups?: string[]
/**
* Filter tests by test title
*/
@flags.array({ description: 'Filter tests by test title' })
declare tests?: string[]
/**
* Specify one or more test reporters to use for output formatting
*/
@flags.array({ description: 'Activate one or more test reporters' })
declare reporters?: string[]
/**
* Enable watch mode to automatically re-run tests when files change
*/
@flags.boolean({ description: 'Watch filesystem and re-run tests on file change' })
declare watch?: boolean
/**
* Use polling instead of native filesystem events to detect file changes
*/
@flags.boolean({ description: 'Use polling to detect filesystem changes' })
declare poll?: boolean
/**
* Default timeout in milliseconds for all tests
*/
@flags.number({ description: 'Define default timeout for all tests' })
declare timeout?: number
/**
* Default number of retries for failed tests
*/
@flags.number({ description: 'Define default retries for all tests' })
declare retries?: number
/**
* Execute only tests that failed during the last run
*/
@flags.boolean({ description: 'Execute tests failed during the last run' })
declare failed?: boolean
/**
* Clear the terminal for new logs after file change in watch mode
*/
@flags.boolean({
description: 'Clear the terminal for new logs after file change',
showNegatedVariantInHelp: true,
default: true,
})
declare clear?: boolean
/**
* Log an error message when a required development dependency is missing.
* Provides helpful instructions for resolving the issue.
*
* @param dependency - The name of the missing dependency package
*/
#logMissingDevelopmentDependency(dependency: string) {
this.logger.error(
[
`Cannot find package "${dependency}"`,
'',
`The "${dependency}" package is a development dependency and therefore you should run tests with development dependencies installed.`,
'',
'If you are run tests inside a CI, make sure the NODE_ENV is set to "development"',
].join('\n')
)
}
/**
* Collect unknown flags and format them to pass to the Japa test runner.
* Handles boolean flags, arrays, and single values appropriately.
*
* @returns Array of formatted command-line arguments for Japa
*/
#getPassthroughFlags(): string[] {
return this.parsed.unknownFlags
.map((flag) => {
const value = this.parsed.flags[flag]
/**
* Not mentioning value when value is "true"
*/
if (value === true) {
return [`--${flag}`] as string[]
}
/**
* Repeating flag multiple times when value is an array
*/
if (Array.isArray(value)) {
return value.map((v) => [`--${flag}`, v]) as string[][]
}
return [`--${flag}`, value] as string[]
})
.flat(2)
}
/**
* Execute the test command. Sets up the test runner with all configured options
* and filters, then runs tests either once or in watch mode. Handles missing
* dependencies and properly configures the test environment.
*/
async run() {
process.env.NODE_ENV = 'test'
const assembler = await importAssembler(this.app)
if (!assembler) {
this.#logMissingDevelopmentDependency('@adonisjs/assembler')
this.exitCode = 1
return
}
this.testsRunner = new assembler.TestRunner(this.app.appRoot, {
clearScreen: this.clear === false ? false : true,
nodeArgs: this.parsed.nodeArgs,
scriptArgs: this.#getPassthroughFlags(),
filters: {
suites: this.suites,
files: this.files,
groups: this.groups,
tags: this.tags,
tests: this.tests,
},
failed: this.failed,
retries: this.retries,
timeout: this.timeout,
reporters: this.reporters,
suites: this.app.rcFile.tests.suites.map((suite) => {
return {
name: suite.name,
files: suite.files,
}
}),
env: {
NODE_ENV: 'test',
},
hooks: this.app.rcFile.hooks,
metaFiles: this.app.rcFile.metaFiles,
})
/**
* Share command logger with assembler, so that CLI flags like --no-ansi has
* similar impact for assembler logs as well.
*/
this.testsRunner.ui.logger = this.logger
/**
* Exit command when the test runner is closed
*/
this.testsRunner.onClose((exitCode) => {
this.exitCode = exitCode
this.terminate()
})
/**
* Exit command when the dev server crashes
*/
this.testsRunner.onError(() => {
this.exitCode = 1
this.terminate()
})
/**
* Start the test runner in watch mode
*/
if (this.watch) {
await this.testsRunner.runAndWatch({ poll: this.poll || false })
} else {
await this.testsRunner.run()
}
}
}
================================================
FILE: eslint.config.js
================================================
import { configPkg } from '@adonisjs/eslint-config'
export default configPkg({
ignores: ['coverage'],
})
================================================
FILE: factories/app.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/application/factories'
================================================
FILE: factories/bodyparser.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/bodyparser/factories'
================================================
FILE: factories/core/ace.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { IgnitorFactory } from './ignitor.ts'
import { Ignitor } from '../../src/ignitor/main.ts'
import type { IgnitorOptions } from '../../src/types.ts'
import type { Kernel } from '../../modules/ace/kernel.ts'
import { createAceKernel } from '../../modules/ace/create_kernel.ts'
/**
* Factory for creating and configuring Ace command kernel instances.
* This factory provides a convenient way to create Ace kernels either from
* an existing Ignitor instance or by creating a new one from scratch.
*
* @example
* ```ts
* // Create from URL
* const aceFactory = new AceFactory()
* const kernel = await aceFactory.make(new URL('../', import.meta.url))
*
* // Create from existing ignitor
* const ignitor = new Ignitor(appRoot)
* const kernel = await aceFactory.make(ignitor)
*
* // Run commands
* await kernel.handle(['make:controller', 'UserController'])
* ```
*/
export class AceFactory {
/**
* Create an Ace kernel from an existing Ignitor instance
*
* @param ignitor - Existing Ignitor instance
*/
async make(ignitor: Ignitor): Promise
/**
* Create an Ace kernel from application root URL
*
* @param appRoot - Application root directory URL
* @param options - Optional Ignitor configuration options
*/
async make(appRoot: URL, options?: IgnitorOptions): Promise
async make(ignitorOrAppRoot: URL | Ignitor, options?: IgnitorOptions): Promise {
if (ignitorOrAppRoot instanceof Ignitor) {
const app = ignitorOrAppRoot.createApp('console')
await app.init()
return createAceKernel(app)
}
const app = new IgnitorFactory()
.withCoreConfig()
.withCoreProviders()
.create(ignitorOrAppRoot, options!)
.createApp('console')
await app.init()
return createAceKernel(app)
}
}
================================================
FILE: factories/core/ignitor.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Ignitor } from '../../src/ignitor/main.ts'
import type { ProviderNode } from '../../types/app.ts'
import { drivers as hashDrivers } from '../../modules/hash/define_config.ts'
import type { ApplicationService, IgnitorOptions } from '../../src/types.ts'
import { defineConfig as defineLoggerConfig } from '../../modules/logger.ts'
import { defineConfig as defineHashConfig } from '../../modules/hash/main.ts'
import { defineConfig as defineHttpConfig } from '../../modules/http/main.ts'
import { defineConfig as defineBodyParserConfig } from '../../modules/bodyparser/main.ts'
import {
defineConfig as defineEncryptionConfig,
drivers as encryptionDrivers,
} from '../../modules/encryption/define_config.ts'
type FactoryParameters = {
rcFileContents: Record
config: Record
}
/**
* Factory for creating and configuring AdonisJS Ignitor instances.
* This factory provides a fluent API to set up applications with core providers,
* configurations, and preload actions for testing and development scenarios.
*
* @example
* ```ts
* const ignitor = new IgnitorFactory()
* .withCoreProviders()
* .withCoreConfig()
* .preload((app) => {
* // Custom initialization logic
* })
* .create(new URL('../', import.meta.url))
*
* const app = ignitor.createApp('web')
* await app.boot()
* ```
*/
export class IgnitorFactory {
#preloadActions: ((app: ApplicationService) => Promise | void)[] = []
#parameters: Partial = {}
/**
* A flag to know if we should load the core providers
*/
#loadCoreProviders: boolean = false
/**
* Define preload actions to run during application initialization.
* These actions are executed after the application is booted.
*
* @param action - Function to execute during preload phase
*
* @example
* ```ts
* factory.preload((app) => {
* // Register custom bindings
* app.container.bind('customService', () => new CustomService())
* })
* ```
*/
preload(action: (app: ApplicationService) => void | Promise): this {
this.#preloadActions.push(action)
return this
}
/**
* Merge core providers with user defined providers
*/
#mergeCoreProviders(providers?: ProviderNode['file'][]): ProviderNode['file'][] {
const coreProviders: ProviderNode['file'][] = [
() => import('@adonisjs/core/providers/app_provider'),
() => import('@adonisjs/core/providers/hash_provider'),
() => import('@adonisjs/core/providers/repl_provider'),
]
return coreProviders.concat(providers || [])
}
/**
* Merge custom factory parameters with existing ones.
* This allows you to customize RC file contents and application configuration.
*
* @param params - Parameters to merge
* @param params.config - Application configuration to merge
* @param params.rcFileContents - RC file contents to merge
*
* @example
* ```ts
* factory.merge({
* config: {
* database: { connection: 'mysql' }
* },
* rcFileContents: {
* commands: ['./commands/CustomCommand']
* }
* })
* ```
*/
merge(params: Partial): this {
if (params.config) {
this.#parameters.config = Object.assign(this.#parameters.config || {}, params.config)
}
if (params.rcFileContents) {
this.#parameters.rcFileContents = Object.assign(
this.#parameters.rcFileContents || {},
params.rcFileContents
)
}
return this
}
/**
* Include core AdonisJS providers when booting the application.
* This adds essential providers like app, hash, and REPL providers.
*
* @example
* ```ts
* const ignitor = new IgnitorFactory()
* .withCoreProviders()
* .create(appRoot)
* ```
*/
withCoreProviders(): this {
this.#loadCoreProviders = true
return this
}
/**
* Merge default configuration for core AdonisJS features.
* This includes configurations for HTTP, hash, logger, and bodyparser.
* A shallow merge is performed with existing config.
*
* @example
* ```ts
* const ignitor = new IgnitorFactory()
* .withCoreConfig()
* .create(appRoot)
* ```
*/
withCoreConfig(): this {
this.merge({
config: {
app: {
appUrl: 'http://localhost:3333',
http: defineHttpConfig({}),
},
validator: {},
encryption: defineEncryptionConfig({
default: 'gcm',
list: {
gcm: encryptionDrivers.aes256gcm({
id: 'gcm',
keys: ['averylongrandomsecretkey'],
}),
},
}),
bodyparser: defineBodyParserConfig({}),
hash: defineHashConfig({
default: 'scrypt',
list: {
scrypt: hashDrivers.scrypt({}),
},
}),
logger: defineLoggerConfig({
default: 'app',
loggers: {
app: {},
},
}),
},
})
return this
}
/**
* Create a configured Ignitor instance with all specified parameters.
*
* @param appRoot - Application root directory URL
* @param options - Optional Ignitor configuration options
*
* @example
* ```ts
* const ignitor = new IgnitorFactory()
* .withCoreConfig()
* .withCoreProviders()
* .create(new URL('../', import.meta.url))
* ```
*/
create(appRoot: URL, options?: IgnitorOptions): Ignitor {
return new Ignitor(appRoot, options).tap((app) => {
app.booted(async () => {
for (let action of this.#preloadActions) {
await action(app)
}
})
if (this.#loadCoreProviders) {
this.#parameters.rcFileContents = this.#parameters.rcFileContents || {}
this.#parameters.rcFileContents.providers = this.#mergeCoreProviders(
this.#parameters.rcFileContents.providers
)
}
this.#parameters.rcFileContents && app.rcContents(this.#parameters.rcFileContents)
this.#parameters.config && app.useConfig(this.#parameters.config)
})
}
}
================================================
FILE: factories/core/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export { AceFactory } from './ace.ts'
export { IgnitorFactory } from './ignitor.ts'
export { TestUtilsFactory } from './test_utils.ts'
================================================
FILE: factories/core/test_utils.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Ignitor } from '../../index.ts'
import { IgnitorFactory } from './ignitor.ts'
import type { IgnitorOptions } from '../../src/types.ts'
import { TestUtils } from '../../src/test_utils/main.ts'
/**
* Factory for creating TestUtils instances used in AdonisJS testing scenarios.
* This factory provides convenient methods to set up test utilities either from
* an existing Ignitor instance or by creating a new configured one.
*
* @example
* ```ts
* // Create from URL for testing
* const factory = new TestUtilsFactory()
* const testUtils = factory.create(new URL('../', import.meta.url))
*
* // Use in tests
* const app = testUtils.app()
* await app.boot()
*
* // Create HTTP client
* const client = testUtils.httpClient()
* const response = await client.get('/users')
* ```
*/
export class TestUtilsFactory {
/**
* Create TestUtils from an existing Ignitor instance
*
* @param ignitor - Existing Ignitor instance configured for testing
*/
create(ignitor: Ignitor): TestUtils
/**
* Create TestUtils from application root URL with optional configuration
*
* @param appRoot - Application root directory URL
* @param options - Optional Ignitor configuration options
*/
create(appRoot: URL, options?: IgnitorOptions): TestUtils
create(ignitorOrAppRoot: URL | Ignitor, options?: IgnitorOptions): TestUtils {
if (ignitorOrAppRoot instanceof Ignitor) {
return new TestUtils(ignitorOrAppRoot.createApp('test'))
}
return new TestUtils(
new IgnitorFactory()
.withCoreConfig()
.withCoreProviders()
.create(ignitorOrAppRoot, options!)
.createApp('console')
)
}
}
================================================
FILE: factories/encryption.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@boringnode/encryption/factories'
================================================
FILE: factories/events.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/events/factories'
================================================
FILE: factories/hash.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/hash/factories'
================================================
FILE: factories/http.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/http-server/factories'
================================================
FILE: factories/logger.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/logger/factories'
================================================
FILE: factories/stubs.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { AppFactory } from '@adonisjs/application/factories'
import { stubsRoot } from '../index.ts'
import type { ApplicationService } from '../src/types.ts'
type FactoryParameters = {
app: ApplicationService
}
/**
* Factory for preparing and processing stub templates from the "@adonisjs/core" package.
* This factory is designed for internal testing and development purposes, utilizing
* the core package's stub root directory.
*
* Note: This class is not published as it's intended for internal testing only.
*
* @example
* ```ts
* const stubsFactory = new StubsFactory()
*
* // Prepare a controller stub
* const preparedStub = await stubsFactory.prepare('controller.stub', {
* filename: 'UserController',
* entity: { name: 'User' },
* resourceful: true
* })
*
* console.log(preparedStub.contents)
* ```
*/
export class StubsFactory {
#parameters: Partial = {}
/**
* Returns an instance of application, either from parameters or creates a default one
*/
#getApp() {
return this.#parameters.app || new AppFactory().create(new URL('./', import.meta.url))
}
/**
* Merge custom factory parameters to override defaults.
* This allows you to provide a custom application instance.
*
* @param params - Parameters to merge
* @param params.app - Custom application service instance
*
* @example
* ```ts
* const customApp = new AppFactory().create(appRoot)
* const stubsFactory = new StubsFactory().merge({ app: customApp })
* ```
*/
merge(params: Partial): this {
this.#parameters = Object.assign(this.#parameters, params)
return this
}
/**
* Prepare a stub template with the provided data and return the processed content.
* This method initializes the application, loads the stub, and processes it with
* the given template variables.
*
* @param stubPath - Path to the stub file relative to the stubs root
* @param data - Template data to populate the stub placeholders
*
* @example
* ```ts
* const stubsFactory = new StubsFactory()
* const preparedStub = await stubsFactory.prepare('controller.stub', {
* filename: 'UserController',
* entity: {
* name: 'User',
* modelName: 'User',
* pluralName: 'users'
* },
* resourceful: true
* })
*
* // Access the processed stub content
* console.log(preparedStub.contents)
* ```
*/
async prepare(stubPath: string, data: Record) {
const app = this.#getApp()
await app.init()
const stubs = await app.stubs.create()
const stub = await stubs.build(stubPath, {
source: stubsRoot,
})
return stub.prepare(data)
}
}
================================================
FILE: funding.json
================================================
{
"version": "v1.0.0",
"entity": {
"type": "individual",
"role": "owner",
"name": "Harminder Virk",
"email": "virk@adonisjs.com",
"description": "Hi 👋 I'm Harminder Virk, the creator of a family of open-source tools loved by the Node.js community — including AdonisJS, VineJS, Japa, Edge, Lucid ORM, and Youch.\n\nI pour my heart into building tools that make developers' lives easier — from crafting elegant APIs to sweating the little details that improve your everyday workflow. My goal is to provide you with strong, reliable building blocks, so you can focus on shipping great products instead of fighting with your stack.\n\nBy sponsoring my work, you're helping me keep these projects fast, stable, and well-maintained, while also making room for new ideas that could become your next favorite tool. ❤️\n\nCurrent focus\n\n- AdonisJS v7\n- Improving E2E type-safety in AdonisJS applications\n- Isomorphic VineJS (Validation library)\n- Lucid Rewrite (ORM)\nAdonisJS Plus (Sponsors can access Plus at a discounted rate)",
"webpageUrl": {
"url": "https://adonisjs.com"
}
},
"projects": [
{
"guid": "adonisjs",
"name": "AdonisJS",
"description": "AdonisJS is a fully typed TypeScript framework that includes everything you need to build robust applications. ORM, authentication, caching, rate limiter, file uploads, and support for testing - all in a single cohesive package",
"webpageUrl": {
"url": "https://adonisjs.com"
},
"repositoryUrl": {
"url": "https://github.com/adonisjs/core"
},
"licenses": ["MIT"],
"tags": [
"web-framework",
"batteries-included",
"fullstack",
"typescript",
"nodejs",
"javascript"
]
},
{
"guid": "japa",
"name": "Japa",
"description": "Japa is a testing framework for Node.js that focuses primarily on testing backend applications. Japa does not require using any build tools and is significantly smaller and faster than Jest and Vitest",
"webpageUrl": {
"url": "https://japa.dev"
},
"repositoryUrl": {
"url": "https://github.com/japa/runner"
},
"licenses": ["MIT"],
"tags": ["testing", "nodejs", "typescript", "javascript"]
},
{
"guid": "vinejs",
"name": "VineJS",
"description": "VineJS is a fast and type-safe validation library for Node.js, designed for validating form data and JSON payloads. It offers 50+ built-in rules, 12 schema types, customizable error messages, and first-class support for extending with custom rules or schema types — all while being one of the fastest libraries in the Node.js ecosystem.",
"webpageUrl": {
"url": "https://vinejs.dev"
},
"repositoryUrl": {
"url": "https://github.com/vinejs/vine"
},
"licenses": ["MIT"],
"tags": ["validation", "validator", "javascript", "nodejs", "typescript"]
},
{
"guid": "edgejs",
"name": "EdgeJS",
"description": "Edge is a simple, Modern, and batteries included template engine for Node.js. In the age of frontend frameworks, Edge allows you to embrace the simplicity of server-side templates.",
"webpageUrl": {
"url": "https://edgejs.dev"
},
"repositoryUrl": {
"url": "https://github.com/edge-js/edge"
},
"licenses": ["MIT"],
"tags": ["templating", "template-engine", "nodejs", "javascript"]
},
{
"guid": "youch",
"name": "Youch",
"description": "Youch is an error-parsing library that pretty prints JavaScript errors on a web page or the terminal.",
"webpageUrl": {
"url": "https://github.com/poppinss/youch"
},
"repositoryUrl": {
"url": "https://github.com/poppinss/youch"
},
"licenses": ["MIT"],
"tags": ["error-parser", "error-printer", "pretty-print", "javascript", "typescript"]
}
],
"funding": {
"channels": [
{
"guid": "gh-sponsors",
"type": "payment-provider",
"address": "https://github.com/sponsors/thetutlage",
"description": "Support me through GitHub Sponsors"
},
{
"guid": "my-paypal",
"type": "payment-provider",
"address": "https://paypal.me/adonisjs",
"description": "Support me through Paypal. Supports various payment options"
}
],
"plans": [
{
"guid": "fan",
"status": "active",
"name": "AdonisJS fan",
"description": "You have directly benefited from my work and want to support me continue working on AdonisJS and other related packages.",
"amount": 5,
"currency": "USD",
"frequency": "monthly",
"channels": ["gh-sponsors", "my-paypal"]
},
{
"guid": "insiders-individual",
"status": "active",
"name": "Insiders program - Individual",
"description": "All sponsors under this tier are automatically invited to the Insiders program, which provides them with exclusive benefits.\n\n- Open-source packages: Gain early access to alpha release packages with complete documentation, allowing you to provide feedback and influence features before public release.\n- Example projects: Get early access to the source code of production-ready example apps built using AdonisJS. Ask questions around the design decisions, or contribute features to it.",
"amount": 19,
"currency": "USD",
"frequency": "monthly",
"channels": ["gh-sponsors", "my-paypal"]
},
{
"guid": "insiders-team",
"status": "active",
"name": "Insiders program - Teams",
"description": "Access to Insiders benefits for your entire team (upto 10 members). Along with that you will have access to the following benefits\n\n- Prominent logo placement on the AdonisJS website homepage\n- Featured announcement across AdonisJS social and newsletter channels\n- Thank-you highlight on AdonisJS social channels",
"amount": 499,
"currency": "USD",
"frequency": "monthly",
"channels": ["gh-sponsors", "my-paypal"]
},
{
"guid": "community",
"status": "active",
"name": "Community Sponsor",
"description": "Community Sponsors are companies and teams who use AdonisJS and want to give back to the tools they depend on. Every contribution helps sustain development and community support.\n\nBenefits:\n\n- Prominent logo placement on the AdonisJS website homepage\n- Thank-you highlight on AdonisJS social channels",
"amount": 1000,
"currency": "USD",
"frequency": "yearly",
"channels": ["gh-sponsors", "my-paypal"]
},
{
"guid": "core",
"status": "active",
"name": "Core Sponsor",
"description": "Core Sponsors ensure AdonisJS continues to grow and remain dependable for production use. Their support directly funds ongoing maintenance and new integrations.\n\nBenefits:\n\n- Prominent logo placement on the AdonisJS website homepage\n- Featured announcement across AdonisJS social and newsletter channels\n- Thank-you highlight on AdonisJS social channels",
"amount": 5000,
"currency": "USD",
"frequency": "yearly",
"channels": ["gh-sponsors", "my-paypal"]
},
{
"guid": "prime",
"status": "active",
"name": "Prime Sponsor",
"description": "Prime Sponsors help maintain the momentum behind AdonisJS, supporting consistent releases, improvements, and ecosystem maintenance.\n\nBenefits:\n\n- Logo featured on the AdonisJS sponsor page\n- Mention in annual sponsor announcement post\n- Invitations to private feedback sessions and early previews\n- Thank-you highlight on AdonisJS social channels",
"amount": 10000,
"currency": "USD",
"frequency": "yearly",
"channels": ["gh-sponsors", "my-paypal"]
},
{
"guid": "divine",
"status": "active",
"name": "Divine Sponsor",
"description": "Divine Sponsors play a foundational role in keeping the AdonisJS ecosystem strong and evolving. Their support enables full-time open-source work, new feature development, and community initiatives.\n\nBenefits:\n\n- Prominent logo placement on the AdonisJS website homepage\n- Featured announcement across AdonisJS social and newsletter channels\n- Priority access to roadmap insights and private technical sessions\n- Option to collaborate on ecosystem initiatives",
"amount": 25000,
"currency": "USD",
"frequency": "yearly",
"channels": ["gh-sponsors", "my-paypal"]
}
],
"history": []
}
}
================================================
FILE: index.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { errors as aceErrors } from '@adonisjs/ace'
import { errors as envErrors } from '@adonisjs/env'
import { errors as appErrors } from '@adonisjs/application'
import { errors as encryptionErrors } from '@boringnode/encryption'
import { errors as httpServerErrors } from '@adonisjs/http-server'
export { stubsRoot } from './stubs/main.ts'
export { inject } from './modules/container.ts'
export { Ignitor } from './src/ignitor/main.ts'
export { configProvider } from './src/config_provider.ts'
export { indexEntities } from './src/assembler_hooks/index_entities.ts'
/**
* Aggregated errors from all modules.
*/
export const errors: typeof encryptionErrors &
typeof httpServerErrors &
typeof appErrors &
typeof aceErrors &
typeof envErrors = {
...encryptionErrors,
...httpServerErrors,
...appErrors,
...aceErrors,
...envErrors,
}
/**
* Pretty prints an error with colorful output using
* Youch terminal
*/
export async function prettyPrintError(error: any) {
if (error && typeof error === 'object' && error.code === 'E_DUMP_DIE_EXCEPTION') {
console.error(error)
return
}
try {
const { Youch } = await import('youch')
const youch = new Youch()
console.error(await youch.toANSI(error))
} catch {
console.error(error)
}
}
================================================
FILE: modules/ace/codemods.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { isAbsolute } from 'node:path'
import { EventEmitter } from 'node:events'
import { readFile } from 'node:fs/promises'
import { EnvEditor } from '@adonisjs/env/editor'
import type { UIPrimitives } from '@adonisjs/ace/types'
import type { CodeTransformer } from '@adonisjs/assembler/code_transformer'
import type {
MiddlewareNode,
EnvValidationNode,
BouncerPolicyNode,
SupportedPackageManager,
} from '@adonisjs/assembler/types'
import debug from '../../src/debug.ts'
import type { Application } from '../app.ts'
import stringHelpers from '../../src/helpers/string.ts'
import { type GeneratedStub } from '../../types/app.ts'
/**
* Codemods class for programmatically modifying AdonisJS source files.
* This class provides APIs to modify configuration files, register middleware,
* generate stubs, and install packages.
*
* The codemod APIs rely on the "@adonisjs/assembler" package, which must be
* installed as a dependency in the user application.
*
* @example
* ```ts
* const codemods = new Codemods(app, logger)
*
* // Generate a controller from a stub
* await codemods.makeUsingStub(stubsRoot, 'controller.stub', {
* filename: 'UserController',
* entity: { name: 'User' }
* })
*
* // Install packages
* await codemods.installPackages([
* { name: '@adonisjs/lucid', isDevDependency: false }
* ])
* ```
*/
export class Codemods extends EventEmitter {
/**
* Reference to lazily imported assembler code transformer
*/
#codeTransformer?: CodeTransformer
/**
* Reference to AdonisJS application
*/
#app: Application
/**
* Reference to CLI logger to write logs
*/
#cliLogger: UIPrimitives['logger']
/**
* Overwrite existing files when generating files
* from stubs
*/
overwriteExisting = false
/**
* Display verbose logs for package installation
*/
verboseInstallOutput = false
constructor(app: Application, cliLogger: UIPrimitives['logger']) {
super()
this.#app = app
this.#cliLogger = cliLogger
}
/**
* - Lazily import the code transformer
* - Return a fresh or reused instance of the code transformer
*/
async #getCodeTransformer() {
try {
if (!this.#codeTransformer) {
const { CodeTransformer } = await import('@adonisjs/assembler/code_transformer')
this.#codeTransformer = new CodeTransformer(this.#app.appRoot)
}
return this.#codeTransformer
} catch {
return null
}
}
/**
* Returns the installation command for different
* package managers
*/
#getInstallationCommands(packages: string[], packageManager: string, isDev: boolean) {
if (!packages.length) {
return ''
}
const colors = this.#cliLogger.getColors()
const devFlag = isDev ? ' -D' : ''
switch (packageManager) {
case 'yarn':
case 'yarn@berry':
return `${colors.yellow(`yarn add${devFlag}`)} ${packages.join(' ')}`
case 'pnpm':
return `${colors.yellow(`pnpm add${devFlag}`)} ${packages.join(' ')}`
case 'npm':
default:
return `${colors.yellow(`npm i${devFlag}`)} ${packages.join(' ')}`
}
}
/**
* Define one or more environment variables in the .env file
*
* @param environmentVariables - Key-value pairs of environment variables
* @param options - Configuration options
* @param options.omitFromExample - Keys to exclude from .env.example file
*
* @example
* ```ts
* await codemods.defineEnvVariables({
* DB_CONNECTION: 'mysql',
* DB_HOST: 'localhost',
* SECRET_KEY: 'abc123'
* }, {
* omitFromExample: ['SECRET_KEY']
* })
* ```
*/
async defineEnvVariables>(
environmentVariables: T,
options?: { omitFromExample?: Array }
) {
const editor = new EnvEditor(this.#app.appRoot)
await editor.load()
Object.keys(environmentVariables).forEach((key) => {
const value = environmentVariables[key]
editor.add(key, value, options?.omitFromExample?.includes(key))
})
await editor.save()
this.#cliLogger.action('update .env file').succeeded()
}
/**
* Returns the TsMorph project instance for advanced AST manipulations.
* See https://ts-morph.com/ for documentation.
*
* @example
* ```ts
* const project = await codemods.getTsMorphProject()
* if (project) {
* const sourceFile = project.getSourceFile('app/controllers/user_controller.ts')
* // Perform advanced AST operations
* }
* ```
*/
async getTsMorphProject(): Promise {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot create CodeTransformer. Install "@adonisjs/assembler" to modify source files'
)
return
}
return transformer.project
}
/**
* Define validations for the environment variables in the start/env.ts file.
* This method updates the environment validation schema using the assembler.
*
* @param validations - Validation schema node for environment variables
*
* @example
* ```ts
* await codemods.defineEnvValidations({
* NODE_ENV: 'Env.schema.enum(["development", "production", "test"] as const)',
* PORT: 'Env.schema.number()',
* HOST: 'Env.schema.string({ format: "host" })'
* })
* ```
*/
async defineEnvValidations(validations: EnvValidationNode) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot update "start/env.ts" file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('update start/env.ts file')
try {
await transformer.defineEnvValidations(validations)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Register middleware in the start/kernel.ts file.
* This method adds middleware to the specified stack (server, router, or named).
*
* @param stack - The middleware stack to register to ('server' | 'router' | 'named')
* @param middleware - Array of middleware nodes to register
*
* @example
* ```ts
* await codemods.registerMiddleware('server', [
* {
* name: 'cors',
* path: '@adonisjs/cors/cors_middleware'
* }
* ])
* ```
*/
async registerMiddleware(stack: 'server' | 'router' | 'named', middleware: MiddlewareNode[]) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot update "start/kernel.ts" file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('update start/kernel.ts file')
try {
await transformer.addMiddlewareToStack(stack, middleware)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Register bouncer policies to the list of policies collection exported from
* the "app/policies/main.ts" file. This method adds new policy definitions
* to the policies export.
*
* @param policies - Array of policy nodes to register
*
* @example
* ```ts
* await codemods.registerPolicies([
* {
* name: 'UserPolicy',
* path: '#policies/user_policy'
* }
* ])
* ```
*/
async registerPolicies(policies: BouncerPolicyNode[]) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot update "app/policies/main.ts" file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('update app/policies/main.ts file')
try {
await transformer.addPolicies(policies)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Update the adonisrc.ts file with new configuration settings.
* This method allows modification of the AdonisJS runtime configuration.
*
* @param params - Parameters for updating the RC file (varies based on update type)
*
* @example
* ```ts
* await codemods.updateRcFile((rcFile) => {
* rcFile.addCommand('make:custom')
* rcFile.addPreloadFile('#app/events/main')
* })
* ```
*/
async updateRcFile(...params: Parameters) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot update "adonisrc.ts" file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('update adonisrc.ts file')
try {
await transformer.updateRcFile(...params)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Register a new Vite plugin in the vite.config.ts file.
* This method adds plugin configuration to the Vite build configuration.
*
* @param params - Parameters for adding the Vite plugin (varies based on plugin type)
*
* @example
* ```ts
* await codemods.registerVitePlugin({
* name: 'vue',
* import: 'import vue from "@vitejs/plugin-vue"',
* options: '()'
* })
* ```
*/
async registerVitePlugin(...params: Parameters) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot update "vite.config.ts" file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('update vite.config.ts file')
try {
await transformer.addVitePlugin(...params)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Register a new Japa plugin in the tests/bootstrap.ts file.
* This method adds plugin configuration to the test runner setup.
*
* @param params - Parameters for adding the Japa plugin (varies based on plugin type)
*
* @example
* ```ts
* await codemods.registerJapaPlugin({
* name: 'expect',
* import: 'import { expect } from "@japa/expect"'
* })
* ```
*/
async registerJapaPlugin(...params: Parameters) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot update "tests/bootstrap.ts" file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('update tests/bootstrap.ts file')
try {
await transformer.addJapaPlugin(...params)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Add a new validator file to the validators directory.
* This method creates a new validator file with the provided definition.
*
* @param params - Parameters for adding the validator
*
* @example
* ```ts
* await codemods.addValidator({
* validatorFileName: 'create_user',
* exportName: 'createUserValidator',
* contents: 'export const createUserValidator = vine.compile(...)'
* })
* ```
*/
async addValidator(...params: Parameters) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot create validator file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('create validator file')
try {
await transformer.addValidator(...params)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Add a new rate limiter file to the limiters directory.
* This method creates a new limiter file with the provided definition.
*
* @param params - Parameters for adding the limiter
*
* @example
* ```ts
* await codemods.addLimiter({
* limiterFileName: 'api_throttle',
* exportName: 'apiThrottleLimiter',
* contents: 'export const apiThrottleLimiter = limiter.define(...)'
* })
* ```
*/
async addLimiter(...params: Parameters) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot create limiter file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('create limiter file')
try {
await transformer.addLimiter(...params)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Add mixins to a model class.
* This method adds mixin calls to the specified model file.
*
* @param params - Parameters for adding model mixins (modelFileName, mixins array)
*
* @example
* ```ts
* await codemods.addModelMixins('user', [
* {
* name: 'SoftDeletes',
* importPath: '@adonisjs/lucid/mixins/soft_deletes',
* importType: 'named'
* }
* ])
* ```
*/
async addModelMixins(...params: Parameters) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot update model file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('update model file')
try {
await transformer.addModelMixins(...params)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Add a new method to an existing controller class.
* This method injects a new method into the specified controller file.
*
* @param params - Parameters for adding the controller method
*
* @example
* ```ts
* await codemods.addControllerMethod({
* controllerFileName: 'users_controller',
* className: 'UsersController',
* name: 'destroy',
* contents: 'async destroy({ params, response }: HttpContext) { ... }',
* imports: [
* { isType: false, isNamed: true, name: 'HttpContext', path: '@adonisjs/core/http' }
* ]
* })
* ```
*/
async addControllerMethod(...params: Parameters) {
const transformer = await this.#getCodeTransformer()
if (!transformer) {
this.#cliLogger.warning(
'Cannot update controller file. Install "@adonisjs/assembler" to modify source files'
)
return
}
const action = this.#cliLogger.action('update controller file')
try {
await transformer.addControllerMethod(...params)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
/**
* Generate a file using a stub template
*
* @param stubsRoot - Root directory containing stub files
* @param stubPath - Path to the specific stub file
* @param stubState - Template variables for stub generation
*
* @example
* ```ts
* const result = await codemods.makeUsingStub(
* './stubs',
* 'controller.stub',
* {
* filename: 'UserController',
* entity: { name: 'User', modelName: 'User' },
* resourceful: true
* }
* )
* ```
*/
async makeUsingStub(
stubsRoot: string,
stubPath: string,
stubState: Record,
options?: {
contentsFromFile?: string
}
): Promise {
const stubs = await this.#app.stubs.create()
const stub = await stubs.build(stubPath, { source: stubsRoot })
/**
* Overwrite the contents of the stub output with the contents
* of the provided file.
*/
if (options?.contentsFromFile) {
const source = isAbsolute(options.contentsFromFile)
? options.contentsFromFile
: this.#app.makePath(options.contentsFromFile)
try {
debug('overwriting stub output with contents from file %s', source)
stub.replaceWith(await readFile(source, 'utf-8'))
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(
`Cannot replace stub output with "${options.contentsFromFile}" file contents as the file is missing`,
{ cause: error }
)
}
throw error
}
}
const output = await stub.generate({ force: this.overwriteExisting, ...stubState })
debug('generating file %O', output)
const entityFileName = stringHelpers.toUnixSlash(this.#app.relativePath(output.destination))
const result = { ...output, relativeFileName: entityFileName }
if (output.status === 'skipped') {
this.#cliLogger.action(`create ${entityFileName}`).skipped(output.skipReason)
return result
}
this.#cliLogger.action(`create ${entityFileName}`).succeeded()
return result
}
/**
* Install packages using the detected or specified package manager.
* Automatically detects npm, yarn, or pnpm and installs dependencies accordingly.
* You can specify version of each package by setting it in the name like '@adonisjs/lucid'.
*
* @param packages - Array of packages with their dependency type
* @param packageManager - Optional package manager to use (auto-detected if not provided)
*
* @example
* ```ts
* const success = await codemods.installPackages([
* { name: '@adonisjs/lucid', isDevDependency: false },
* { name: '@types/node', isDevDependency: true }
* ])
* ```
*/
async installPackages(
packages: { name: string; isDevDependency: boolean }[],
packageManager?: SupportedPackageManager | 'pnpm@6' | 'deno'
): Promise {
const transformer = await this.#getCodeTransformer()
const appPath = this.#app.makePath()
const colors = this.#cliLogger.getColors()
const devDependencies = packages.filter((pkg) => pkg.isDevDependency).map(({ name }) => name)
const dependencies = packages.filter((pkg) => !pkg.isDevDependency).map(({ name }) => name)
if (!transformer) {
this.#cliLogger.warning(
'Cannot install packages. Install "@adonisjs/assembler" or manually install following packages'
)
this.#cliLogger.log(`devDependencies: ${devDependencies.join(',')}`)
this.#cliLogger.log(`dependencies: ${dependencies.join(',')}`)
return false
}
packageManager = packageManager ?? (await transformer.detectPackageManager(appPath)) ?? 'npm'
const spinner = this.#cliLogger.await(`installing dependencies using ${packageManager} `)
const silentLogs = !this.verboseInstallOutput
if (silentLogs) {
spinner.start()
}
try {
if (dependencies.length) {
await transformer.installPackage(dependencies, {
cwd: appPath,
silent: silentLogs,
packageManager,
})
}
if (devDependencies.length) {
await transformer.installPackage(devDependencies, {
dev: true,
cwd: appPath,
silent: silentLogs,
packageManager,
})
}
if (silentLogs) {
spinner.stop()
}
this.#cliLogger.success('Packages installed')
this.#cliLogger.log(
devDependencies.map((dependency) => ` ${colors.dim('dev')} ${dependency} `).join('\n')
)
this.#cliLogger.log(
dependencies.map((dependency) => ` ${colors.dim('prod')} ${dependency} `).join('\n')
)
return true
} catch (error) {
if (silentLogs) {
spinner.update('unable to install dependencies')
spinner.stop()
}
this.#cliLogger.fatal(error)
this.emit('error', error)
return false
}
}
/**
* List the packages that should be installed manually.
* This method displays installation commands for different package managers
* when automatic installation is not available or desired.
*
* @param packages - Array of packages with their dependency type
*
* @example
* ```ts
* await codemods.listPackagesToInstall([
* { name: '@adonisjs/lucid', isDevDependency: false },
* { name: '@types/node', isDevDependency: true }
* ])
* // Output:
* // Please install following packages
* // npm i -D @types/node
* // npm i @adonisjs/lucid
* ```
*/
async listPackagesToInstall(packages: { name: string; isDevDependency: boolean }[]) {
const appPath = this.#app.makePath()
const devDependencies = packages.filter((pkg) => pkg.isDevDependency).map(({ name }) => name)
const dependencies = packages.filter((pkg) => !pkg.isDevDependency).map(({ name }) => name)
let packageManager: string | null = null
const transformer = await this.#getCodeTransformer()
if (transformer) packageManager = await transformer.detectPackageManager(appPath)
this.#cliLogger.log('Please install following packages')
this.#cliLogger.log(
this.#getInstallationCommands(devDependencies, packageManager || 'npm', true)
)
this.#cliLogger.log(this.#getInstallationCommands(dependencies, packageManager || 'npm', false))
}
}
================================================
FILE: modules/ace/commands.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { BaseCommand as AceBaseCommand, ListCommand as AceListCommand } from '@adonisjs/ace'
import { type Kernel } from './kernel.ts'
import type { ApplicationService } from '../../src/types.ts'
import type { CommandOptions, ParsedOutput, UIPrimitives } from '../../types/ace.ts'
/**
* The base command class for creating custom Ace commands in AdonisJS applications.
* This class extends the base Ace command with AdonisJS-specific functionality like
* dependency injection and application lifecycle management.
*
* @example
* ```ts
* export default class MakeUser extends BaseCommand {
* static commandName = 'make:user'
* static description = 'Create a new user'
*
* async run() {
* this.logger.info('Creating user...')
* // Command implementation
* }
* }
* ```
*/
export class BaseCommand extends AceBaseCommand {
static options: CommandOptions = {}
get staysAlive() {
return (this.constructor as typeof BaseCommand).options.staysAlive
}
get startApp() {
return (this.constructor as typeof BaseCommand).options.startApp
}
constructor(
public app: ApplicationService,
public kernel: Kernel,
parsed: ParsedOutput,
ui: UIPrimitives,
prompt: Kernel['prompt']
) {
super(kernel, parsed, ui, prompt)
}
/**
* Creates the codemods module to modify source files programmatically.
* This method provides access to AST-based code transformations.
*
* @example
* ```ts
* const codemods = await this.createCodemods()
* await codemods.makeUsingStub(stubsRoot, 'controller.stub', {
* filename: 'UserController',
* entity: { name: 'User' }
* })
* ```
*/
async createCodemods() {
const { Codemods } = await import('./codemods.js')
const codemods = new Codemods(this.app, this.logger)
codemods.on('error', () => {
this.exitCode = 1
})
return codemods
}
/**
* The prepare template method is used to prepare the
* state for the command. This is the first method
* executed on a given command instance.
*/
prepare?(..._: any[]): any
/**
* The interact template method is used to display the prompts
* to the user. The method is called after the prepare
* method.
*/
interact?(..._: any[]): any
/**
* The completed method is the method invoked after the command
* finishes or results in an error.
*
* You can access the command error using the `this.error` property.
* Returning `true` from completed method supresses the error
* reporting to the kernel layer.
*/
completed?(..._: any[]): any
/**
* Executes the lifecycle hooks and the run method from the command
*/
async exec() {
this.hydrate()
try {
/**
* Executing the template methods
*/
this.prepare && (await this.app.container.call(this, 'prepare'))
this.interact && (await this.app.container.call(this, 'interact'))
const result = await this.app.container.call(this, 'run')
/**
* Set exit code
*/
this.result = this.result === undefined ? result : this.result
this.exitCode = this.exitCode ?? 0
} catch (error) {
this.error = error
this.exitCode = this.exitCode ?? 1
}
/**
* Run the completed method (if exists) and check if has handled
* the error
*/
let errorHandled = this.completed
? await this.app.container.call(this, 'completed')
: false
if (this.error && !errorHandled) {
await this.kernel.errorHandler.render(this.error, this.kernel)
}
return this.result
}
/**
* Terminate the application gracefully. This method should be preferred over
* calling `app.terminate()` directly as it only triggers termination when
* the current command is the main command responsible for the process.
*
* @example
* ```ts
* export default class SomeCommand extends BaseCommand {
* async run() {
* // Do some work
* await this.terminate()
* }
* }
* ```
*/
async terminate() {
if (this.kernel.getMainCommand() === this) {
await this.app.terminate()
}
}
}
/**
* The List command is used to display a list of available commands.
* This command extends the base Ace ListCommand with AdonisJS-specific functionality.
*/
export class ListCommand extends AceListCommand implements BaseCommand {
static options: CommandOptions = {}
get staysAlive() {
return (this.constructor as typeof BaseCommand).options.staysAlive
}
get startApp() {
return (this.constructor as typeof BaseCommand).options.startApp
}
constructor(
public app: ApplicationService,
public kernel: Kernel,
parsed: ParsedOutput,
ui: UIPrimitives,
prompt: Kernel['prompt']
) {
super(kernel, parsed, ui, prompt)
}
/**
* Auto-select JSON output when running inside an AI agent
* and no explicit format flag is provided.
*/
async run() {
if (!this.json && this.app.runningInAIAgent) {
this.json = true
}
return super.run()
}
/**
* Creates the codemods module to modify source files programmatically.
* This method provides access to AST-based code transformations.
*/
async createCodemods() {
const { Codemods } = await import('./codemods.js')
return new Codemods(this.app, this.logger)
}
/**
* Terminate the app. A command should prefer calling this method
* over the "app.terminate", because this method only triggers
* app termination when the current command is in the charge
* of the process.
*/
async terminate() {
if (this.kernel.getMainCommand() === this) {
await this.app.terminate()
}
}
}
================================================
FILE: modules/ace/create_kernel.ts
================================================
/*
* @adonisjs/ace
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Kernel } from './main.ts'
import type { ApplicationService } from '../../src/types.ts'
import { FsLoader, HelpCommand, type BaseCommand } from '../../modules/ace/main.ts'
/**
* Create and configure an Ace command kernel for AdonisJS applications.
* This function abstracts the kernel setup logic and can be used in different
* environments with appropriate configurations.
*
* - In console environment, Ace manages the lifecycle of the process
* - In other environments, Ace can be pulled from the container to run commands
*
* @param app - The AdonisJS application service instance
* @param commandName - Optional specific command name for optimized loading
*
* @example
* ```ts
* const app = new Application(new URL('../', import.meta.url))
* const kernel = createAceKernel(app)
*
* // Run a specific command
* await kernel.handle(['make:controller', 'UserController'])
* ```
*/
export function createAceKernel(app: ApplicationService, commandName?: string) {
const kernel = new Kernel(app)
kernel.info.set('binary', 'node ace')
/**
* Lazy import commands mentioned in the "commands" array
* of rcFile
*/
app.rcFile.commands.forEach((commandModule) => {
kernel.addLoader(() =>
typeof commandModule === 'function' ? commandModule() : app.import(commandModule)
)
})
/**
* When we know the command we are running ahead of time, then we
* defer loading the application commands if the command has
* already been registered by other loaders.
*/
const fsLoader = new FsLoader(app.commandsPath())
kernel.addLoader({
async getMetaData() {
if (!commandName || !kernel.getCommand(commandName)) {
return fsLoader.getMetaData()
}
return []
},
getCommand(command) {
return fsLoader.getCommand(command)
},
})
/**
* Custom global flags
*/
kernel.defineFlag('ansi', {
type: 'boolean',
showNegatedVariantInHelp: true,
description: 'Force enable or disable colorful output',
})
kernel.defineFlag('help', {
type: 'boolean',
description: HelpCommand.description,
})
/**
* Flag listener to turn colors on/off
*/
kernel.on('ansi', (_, $kernel, parsed) => {
if (parsed.flags.ansi === false) {
$kernel.ui.switchMode('silent')
}
if (parsed.flags.ansi === true) {
$kernel.ui.switchMode('normal')
}
})
/**
* Flag listener to display the help
*/
kernel.on('help', async (command, $kernel, parsed) => {
parsed.args.unshift(command.commandName)
const help = new HelpCommand($kernel, parsed, kernel.ui, kernel.prompt)
await help.exec()
return $kernel.shortcircuit()
})
return kernel
}
================================================
FILE: modules/ace/kernel.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Kernel as AceKernel } from '@adonisjs/ace'
import { type BaseCommand, ListCommand } from './commands.ts'
import type { ApplicationService } from '../../src/types.ts'
/**
* The Ace command kernel for AdonisJS applications. This kernel extends the base
* Ace kernel with AdonisJS-specific functionality like dependency injection and
* application lifecycle management.
*
* @example
* ```ts
* const app = new Application(new URL('../', import.meta.url))
* const kernel = new Kernel(app)
*
* await kernel.handle(['make:controller', 'UserController'])
* ```
*/
export class Kernel extends AceKernel {
/**
* Create a new Ace kernel instance
*
* @param app - The AdonisJS application instance
*/
constructor(public app: ApplicationService) {
super(ListCommand, {
create: async (command, parsedOutput, $kernel) => {
return app.container.make(command, [app, $kernel, parsedOutput, $kernel.ui, $kernel.prompt])
},
run: (command) => command.exec(),
})
}
}
================================================
FILE: modules/ace/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Ace command line interface module for AdonisJS applications.
* Provides the kernel, base commands, and utilities for building CLI applications.
*
* @example
* // Create and use the Ace kernel
* import { Kernel, BaseCommand } from '@adonisjs/core/ace'
*
* const kernel = new Kernel(app)
* await kernel.handle(['make:controller', 'UserController'])
*
* @example
* // Create a custom command
* import { BaseCommand, args, flags } from '@adonisjs/core/ace'
*
* export default class MakeUser extends BaseCommand {
* static commandName = 'make:user'
* static description = 'Create a new user'
*
* @args.string({ description: 'Name of the user' })
* declare name: string
*
* @flags.boolean({ description: 'Create admin user' })
* declare admin: boolean
*
* async run() {
* this.logger.info(`Creating user: ${this.name}`)
* }
* }
*/
export { Kernel } from './kernel.ts'
export { BaseCommand, ListCommand } from './commands.ts'
export {
args,
flags,
errors,
Parser,
FsLoader,
ListLoader,
cliHelpers,
HelpCommand,
IndexGenerator,
tracingChannels,
} from '@adonisjs/ace'
================================================
FILE: modules/app.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Application module re-exports all functionality from @adonisjs/application.
* This includes the main Application class and related types for managing
* the AdonisJS application lifecycle, container bindings, and service providers.
*
* @example
* // Import the Application class
* import { Application } from '@adonisjs/core/app'
*
* const app = new Application(new URL('../', import.meta.url))
* await app.init()
* await app.boot()
*
* @example
* // Import application types
* import type { ApplicationService, ContainerBindings } from '@adonisjs/core/app'
*/
export * from '@adonisjs/application'
================================================
FILE: modules/bodyparser/bodyparser_middleware.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { BodyParserMiddleware } from '@adonisjs/bodyparser/bodyparser_middleware'
/**
* Default export allows lazy importing middleware with
* destructuring the named exports
*/
export default BodyParserMiddleware
================================================
FILE: modules/bodyparser/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/bodyparser'
================================================
FILE: modules/config.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Configuration module re-exports all functionality from @adonisjs/config.
* This includes the Config class and related types for managing application
* configuration files and environment-specific settings.
*
* @example
* // Import the Config class
* import { Config } from '@adonisjs/core/config'
*
* const config = new Config()
* config.set('database.connection', 'mysql')
* const dbConnection = config.get('database.connection')
*
* @example
* // Import configuration types
* import type { ConfigProvider } from '@adonisjs/core/config'
*/
export * from '@adonisjs/config'
================================================
FILE: modules/container.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/fold'
================================================
FILE: modules/dumper/define_config.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type ConsoleDumpConfig } from '@poppinss/dumper/console/types'
import { type HTMLDumpConfig } from '@poppinss/dumper/html/types'
/**
* Define configuration for the dumper service exported by the
* "@adonisjs/core/services/dumper" module. This function allows
* you to customize HTML and console output formatting options.
*
* @param dumperConfig - Configuration object with HTML and console options
* @param dumperConfig.html - HTML output formatting configuration
* @param dumperConfig.console - Console output formatting configuration
*
* @example
* ```ts
* export default defineConfig({
* html: {
* showHidden: true,
* depth: 5,
* colors: true
* },
* console: {
* showHidden: false,
* depth: 3,
* collapse: ['Date', 'DateTime']
* }
* })
* ```
*/
export function defineConfig(
dumperConfig: Partial<{ html: HTMLDumpConfig; console: ConsoleDumpConfig }>
) {
return dumperConfig
}
================================================
FILE: modules/dumper/dumper.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import useColors from '@poppinss/colors'
import { dump as consoleDump } from '@poppinss/dumper/console'
import type { HTMLDumpConfig } from '@poppinss/dumper/html/types'
import type { ConsoleDumpConfig } from '@poppinss/dumper/console/types'
import { createScript, createStyleSheet, dump } from '@poppinss/dumper/html'
import type { Application } from '../app.ts'
import { E_DUMP_DIE_EXCEPTION } from './errors.ts'
const colors = useColors.ansi()
const DUMP_TITLE_STYLES = `
.adonisjs-dump-header {
font-family: JetBrains Mono, monaspace argon, Menlo, Monaco, Consolas, monospace;
background: #ff1639;
border-radius: 4px;
color: #fff;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
padding: 0.4rem 1.2rem;
font-size: 1em;
display: flex;
justify-content: space-between;
}
.adonisjs-dump-header .adonisjs-dump-header-title {
font-weight: bold;
text-transform: uppercase;
}
.adonisjs-dump-header .adonisjs-dump-header-source {
font-weight: bold;
color: inherit;
text-decoration: underline;
}
.dumper-dump pre {
border-radius: 4px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}`
const IDE = process.env.ADONIS_IDE ?? process.env.EDITOR ?? ''
/**
* Dumper exposes the API to dump or die/dump values in your
* AdonisJS application. An singleton instance of the Dumper
* is shared as a service and may use it follows.
*
* ```ts
* const dumper = container.make('dumper')
*
* dumper.configureHtmlOutput({
* // parser + html formatter config
* })
*
* dumper.configureAnsiOutput({
* // parser + console formatter config
* })
*
* const html = dumper.dumpToHtml(value)
* const ansi = dumper.dumpToAnsi(value)
*
* // Returns style and script tags that must be
* // injeted to the head of the HTML document
*
* const head = dumper.getHeadElements()
* ```
*/
export class Dumper {
#app: Application
/**
* Configuration for the HTML formatter
*/
#htmlConfig: HTMLDumpConfig = {}
/**
* Configuration for the Console formatter
*/
#consoleConfig: ConsoleDumpConfig = {
collapse: ['DateTime', 'Date'],
}
/**
* A collections of known editors to create URLs to open
* them
*/
#editors: Record = {
textmate: 'txmt://open?url=file://%f&line=%l',
macvim: 'mvim://open?url=file://%f&line=%l',
emacs: 'emacs://open?url=file://%f&line=%l',
sublime: 'subl://open?url=file://%f&line=%l',
phpstorm: 'phpstorm://open?file=%f&line=%l',
atom: 'atom://core/open/file?filename=%f&line=%l',
vscode: 'vscode://file/%f:%l',
}
/**
* Creates a new Dumper instance
*
* @param app - The AdonisJS application instance
*/
constructor(app: Application) {
this.#app = app
}
/**
* Returns the link to open the file using dd inside one
* of the known code editors. Constructs a URL that can be used
* to open the file at a specific line in supported editors.
*
* @param source - Optional source file information
* @param source.location - The file path to open
* @param source.line - The line number to jump to
*/
#getEditorLink(source?: {
location: string
line: number
}): { href: string; text: string } | undefined {
const editorURL = this.#editors[IDE] || IDE
if (!editorURL || !source) {
return
}
return {
href: editorURL.replace('%f', source.location).replace('%l', String(source.line)),
text: `${this.#app.relativePath(source.location)}:${source.line}`,
}
}
/**
* Configure the HTML formatter output options
*
* @param config - Configuration options for HTML dump formatting
*
* @example
* ```ts
* dumper.configureHtmlOutput({
* showHidden: true,
* depth: 5,
* colors: false
* })
* ```
*/
configureHtmlOutput(config: HTMLDumpConfig): this {
this.#htmlConfig = config
return this
}
/**
* Configure the ANSI formatter output options for console display
*
* @param config - Configuration options for ANSI console formatting
*
* @example
* ```ts
* dumper.configureAnsiOutput({
* showHidden: true,
* depth: 3,
* collapse: ['Date', 'DateTime']
* })
* ```
*/
configureAnsiOutput(config: ConsoleDumpConfig): this {
this.#consoleConfig = config
return this
}
/**
* Returns the style and script elements that need to be injected into
* the HTML document head for proper dump visualization
*
* @param cspNonce - Optional Content Security Policy nonce for inline scripts
*
* @example
* ```ts
* const headElements = dumper.getHeadElements('abc123')
* // Insert into your HTML head section
* ```
*/
getHeadElements(cspNonce?: string): string {
return (
`' +
`'
)
}
/**
* Dump a value to formatted HTML output
*
* @param value - The value to dump and inspect
* @param options - Options for HTML output formatting
* @param options.cspNonce - Optional Content Security Policy nonce
* @param options.title - Optional title to display in the dump header
* @param options.source - Optional source file information for editor links
*
* @example
* ```ts
* const htmlOutput = dumper.dumpToHtml(user, {
* title: 'User Object',
* source: { location: '/app/controllers/user.ts', line: 42 }
* })
* ```
*/
dumpToHtml(
value: unknown,
options: {
cspNonce?: string
title?: string
source?: {
location: string
line: number
}
} = {}
) {
const link = this.#getEditorLink(options.source) ?? null
const title = options.title || 'DUMP'
return (
'' +
dump(value, { cspNonce: options.cspNonce, ...this.#htmlConfig })
)
}
/**
* Dump a value to formatted ANSI output for console display
*
* @param value - The value to dump and inspect
* @param options - Options for ANSI output formatting
* @param options.title - Optional title to display in the dump header
* @param options.source - Optional source file information for editor links
*
* @example
* ```ts
* const ansiOutput = dumper.dumpToAnsi(user, {
* title: 'User Debug',
* source: { location: '/app/controllers/user.ts', line: 42 }
* })
* console.log(ansiOutput)
* ```
*/
dumpToAnsi(
value: unknown,
options: {
title?: string
source?: {
location: string
line: number
}
} = {}
) {
const columns = process.stdout.columns
/**
* Link to the source file
*/
const link = `${this.#getEditorLink(options.source)?.text ?? ''} `
/**
* Dump title
*/
const title = ` ${options.title || 'DUMP'}`
/**
* Whitespace between the title and the link to align them
* on each side of x axis
*/
const whiteSpaceLength = columns ? columns - link.length - title.length - 4 : 2
const whiteSpace = new Array(whiteSpaceLength <= 0 ? 2 : whiteSpaceLength).join(' ')
/**
* Styled heading with background color and bold text
*/
const heading = colors.bgRed().bold(`${title}${whiteSpace}${link}`)
return `${heading}\n${consoleDump(value, this.#consoleConfig)}`
}
/**
* Dump values and die. This method dumps the provided value and then
* terminates the application. The output format is automatically chosen
* based on the execution context.
*
* - During an HTTP request, HTML output will be sent to the browser
* - Otherwise the value will be logged to the console in ANSI format
*
* @param value - The value to dump before terminating
* @param traceSourceIndex - Stack trace index for source location (default: 1)
*
* @example
* ```ts
* // This will dump the user object and terminate the application
* dumper.dd(user)
*
* // This will never execute
* console.log('This line will not run')
* ```
*/
dd(value: unknown, traceSourceIndex: number = 1) {
const error = new E_DUMP_DIE_EXCEPTION(value, this)
error.setTraceSourceIndex(traceSourceIndex)
throw error
}
}
================================================
FILE: modules/dumper/errors.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { inspect } from 'node:util'
import { parse } from 'error-stack-parser-es'
import { Exception } from '@poppinss/utils/exception'
import type { Dumper } from './dumper.ts'
import type { Kernel } from '../ace/kernel.ts'
import type { HttpContext } from '../http/main.ts'
/**
* DumpDie exception raised by the "dd" (dump and die) function.
* This special exception terminates execution while dumping the provided
* value as HTML (during HTTP requests) or ANSI (in console/CLI).
*
* @example
* ```ts
* // This will dump the user object and terminate
* dumper.dd(user)
*
* // In HTTP context: sends HTML dump to browser
* // In CLI context: prints ANSI dump to console
* ```
*/
class DumpDieException extends Exception {
static status: number = 500
static code: string = 'E_DUMP_DIE_EXCEPTION'
declare fileName: string
declare lineNumber: number
#dumper: Dumper
#traceSourceIndex: number = 1
value: unknown
constructor(value: unknown, dumper: Dumper) {
super('Dump and Die exception')
this.#dumper = dumper
this.value = value
}
/**
* Returns the source file and line number location for the error
*/
#getErrorSource(): { location: string; line: number } | undefined {
if (this.fileName && this.lineNumber) {
return {
location: this.fileName,
line: this.lineNumber,
}
}
const source = parse(this)[this.#traceSourceIndex]
if (!source.fileName || !source.lineNumber) {
return
}
return {
location: source.fileName,
line: source.lineNumber,
}
}
/**
* Set the stack trace index for determining the source location.
* This is useful when building nested helpers on top of dump/die functionality.
*
* @param index - Stack trace index (0 = current function, 1 = caller, etc.)
*/
setTraceSourceIndex(index: number) {
this.#traceSourceIndex = index
return this
}
/**
* Preventing itself from getting reported by the
* AdonisJS exception reporter
*/
report() {}
/**
* HTTP exception handler that renders the dump as HTML output.
* This method is called automatically by AdonisJS when a DumpDieException
* is thrown during an HTTP request.
*
* @param error - The DumpDieException instance
* @param ctx - HTTP context for the current request
*/
async handle(error: DumpDieException, ctx: HttpContext) {
const source = this.#getErrorSource()
/**
* Comes from the shield package
*/
const cspNonce = 'nonce' in ctx.response ? (ctx.response.nonce as string) : undefined
ctx.response
.status(500)
.send(
'' +
'' +
'' +
'' +
'' +
`${this.#dumper.getHeadElements(cspNonce)}` +
'' +
'' +
`${this.#dumper.dumpToHtml(error.value, { cspNonce, source, title: 'DUMP DIE' })}` +
'' +
''
)
}
/**
* Ace command exception handler that renders the dump as ANSI output.
* This method is called automatically by the Ace kernel when a DumpDieException
* is thrown during command execution.
*
* @param error - The DumpDieException instance
* @param kernel - Ace kernel instance
*/
async render(error: DumpDieException, kernel: Kernel) {
const source = this.#getErrorSource()
kernel.ui.logger.log(this.#dumper.dumpToAnsi(error.value, { source, title: 'DUMP DIE' }))
}
/**
* Custom output for the Node.js util inspect
*/
[inspect.custom]() {
const source = this.#getErrorSource()
return this.#dumper.dumpToAnsi(this.value, { source, title: 'DUMP DIE' })
}
}
export const E_DUMP_DIE_EXCEPTION = DumpDieException
================================================
FILE: modules/dumper/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Dumper module provides debugging and inspection utilities for AdonisJS applications.
* Includes the Dumper class for formatting output, error classes, and configuration helpers.
*
* @example
* // Use the dumper service
* import { Dumper } from '@adonisjs/core/dumper'
*
* const dumper = new Dumper(app)
* const htmlOutput = dumper.dumpToHtml(user, { title: 'User Data' })
* const ansiOutput = dumper.dumpToAnsi(user, { title: 'Debug User' })
*
* @example
* // Configure dumper output
* import { defineConfig } from '@adonisjs/core/dumper'
*
* export default defineConfig({
* html: { showHidden: true, depth: 5 },
* console: { collapse: ['Date', 'DateTime'] }
* })
*/
export * as errors from './errors.ts'
export { Dumper } from './dumper.ts'
export { defineConfig } from './define_config.ts'
================================================
FILE: modules/dumper/plugins/edge.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type Edge, Template } from 'edge.js'
import { type Dumper } from '../dumper.ts'
/**
* Returns an edge plugin that integrates with a given
* dumper instance
*/
export function pluginEdgeDumper(dumper: Dumper) {
Template.macro('dumper' as any, dumper)
return (edge: Edge) => {
edge.registerTag({
tagName: 'dump',
block: false,
seekable: true,
noNewLine: true,
compile(parser, buffer, token) {
const parsed = parser.utils.transformAst(
parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename),
token.filename,
parser
)
buffer.writeExpression(
`template.stacks.pushOnceTo('dumper', 'dumper_globals', template.dumper.getHeadElements(state.cspNonce))`,
token.filename,
token.loc.start.line
)
buffer.outputExpression(
`template.dumper.dumpToHtml(${parser.utils.stringify(parsed)}, { cspNonce: state.cspNonce, source: { location: $filename, line: $lineNumber } })`,
token.filename,
token.loc.start.line,
true
)
},
})
edge.registerTag({
tagName: 'dd',
block: false,
seekable: true,
noNewLine: true,
compile(parser, buffer, token) {
const parsed = parser.utils.transformAst(
parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename),
token.filename,
parser
)
/**
* Dump/Die statement to catch error and convert it into
* an Edge error
*/
const ddStatement = [
'try {',
` template.dumper.dd(${parser.utils.stringify(parsed)})`,
'} catch (error) {',
` if (error.code === 'E_DUMP_DIE_EXCEPTION') {`,
' const edgeError = template.createError(error.message, $filename, $lineNumber)',
' error.fileName = $filename',
' error.lineNumber = $lineNumber',
' edgeError.handle = function (_, ctx) {',
' return error.handle(error, ctx)',
' }',
' edgeError.report = function () {',
' return error.report(error)',
' }',
' throw edgeError',
' }',
' throw error',
'}',
].join('\n')
buffer.writeStatement(ddStatement, token.filename, token.loc.start.line)
},
})
}
}
================================================
FILE: modules/encryption/define_config.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import debug from '../../src/debug.ts'
import { configProvider } from '../../src/config_provider.ts'
import { type ConfigProvider } from '../../src/types.ts'
import {
type AESSIVDriverConfig,
type AES256CBCDriverConfig,
type AES256GCMDriverConfig,
type ChaCha20Poly1305DriverConfig,
type LegacyDriverConfig,
} from '../../types/encryption.ts'
import { type EncryptionConfig } from '../../types/encryption.ts'
import { InvalidArgumentsException } from '../../src/exceptions.ts'
/**
* Resolved configuration from the config provider that will be
* accepted by the encryption manager.
*
* This type unwraps ConfigProvider types to their resolved values,
* ensuring all encryptors in the list are concrete EncryptionConfig
* objects rather than providers.
*
* @template KnownEncryptors - Record of encryptor names to their configurations
*/
type ResolvedConfig<
KnownEncryptors extends Record>,
> = {
/**
* The default encryptor name to use when no specific
* encryptor is requested
*/
default?: keyof KnownEncryptors
/**
* Map of encryptor names to their resolved configurations.
* ConfigProvider types are unwrapped to their underlying
* EncryptionConfig values.
*/
list: {
[K in keyof KnownEncryptors]: KnownEncryptors[K] extends ConfigProvider
? A
: KnownEncryptors[K]
}
}
/**
* Defines the encryption configuration for the application.
*
* This function creates a configuration provider that lazily resolves
* encryption drivers. It validates that the default encryptor (if specified)
* exists in the list and resolves any ConfigProvider instances to their
* concrete values.
*
* @template KnownEncryptors - Record of encryptor names to their configurations
*
* @param config - The encryption configuration object
* @param config.default - Optional default encryptor name
* @param config.list - Map of encryptor names to their configurations or providers
*
* @example
* ```ts
* const encryptionConfig = defineConfig({
* default: 'app',
* list: {
* app: drivers.aes256gcm({
* id: 'app',
* keys: [env.get('APP_KEY')]
* }),
* backup: drivers.chacha20({
* id: 'backup',
* keys: [env.get('BACKUP_KEY')]
* })
* }
* })
* ```
*/
export function defineConfig<
KnownEncryptors extends Record>,
>(config: {
default?: keyof KnownEncryptors
list: KnownEncryptors
}): ConfigProvider> {
/**
* Encryption list should always be provided
*/
if (!config.list) {
throw new InvalidArgumentsException('Missing "list" property in encryption config')
}
/**
* The default encryption should be mentioned in the list
*/
if (config.default && !config.list[config.default]) {
throw new InvalidArgumentsException(
`Missing "list.${String(
config.default
)}" in encryption config. It is referenced by the "default" property`
)
}
/**
* Config provider to lazily import drivers as they are used inside
* the user application
*/
return configProvider.create>(async (app) => {
debug('resolving encryption config')
const encryptorsList = Object.keys(config.list)
const encryptors = {} as Record>
for (let encryptorName of encryptorsList) {
const encryptor = config.list[encryptorName]
if ('resolver' in encryptor) {
encryptors[encryptorName] = await encryptor.resolver(app)
} else {
encryptors[encryptorName] = encryptor
}
}
return {
default: config.default,
list: encryptors as ResolvedConfig['list'],
}
})
}
/**
* Collection of encryption driver factory functions.
*
* Each driver factory creates a ConfigProvider that lazily imports
* and configures the corresponding encryption driver. This allows
* for efficient code splitting and on-demand loading of encryption
* algorithms.
*/
export const drivers: {
/**
* Creates a ChaCha20-Poly1305 encryption driver configuration.
*
* ChaCha20-Poly1305 is a modern authenticated encryption algorithm
* that provides excellent performance on systems without AES hardware
* acceleration.
*
* @param config - The ChaCha20-Poly1305 driver configuration
*
* @example
* ```ts
* drivers.chacha20({
* id: 'app',
* keys: [env.get('APP_KEY')]
* })
* ```
*/
chacha20: (config: ChaCha20Poly1305DriverConfig) => ConfigProvider
/**
* Creates an AES-256-CBC encryption driver configuration.
*
* AES-256-CBC is a widely-supported block cipher mode. However,
* consider using AES-256-GCM for new applications as it provides
* authenticated encryption.
*
* @param config - The AES-256-CBC driver configuration
*
* @example
* ```ts
* drivers.aes256cbc({
* id: 'legacy',
* keys: [env.get('LEGACY_KEY')]
* })
* ```
*/
aes256cbc: (config: AES256CBCDriverConfig) => ConfigProvider
/**
* Creates an AES-256-GCM encryption driver configuration.
*
* AES-256-GCM is an authenticated encryption algorithm that provides
* both confidentiality and integrity. It offers excellent performance
* on systems with AES hardware acceleration.
*
* @param config - The AES-256-GCM driver configuration
*
* @example
* ```ts
* drivers.aes256gcm({
* id: 'app',
* keys: [env.get('APP_KEY')]
* })
* ```
*/
aes256gcm: (config: AES256GCMDriverConfig) => ConfigProvider
/**
* Creates an AES-SIV encryption driver configuration.
*
* @param config - The AES-SIV driver configuration
*
* @example
* ```ts
* drivers.aessiv({
* id: 'app',
* key: env.get('APP_KEY')
* })
* ```
*/
aessiv: (config: AESSIVDriverConfig) => ConfigProvider
/**
* Creates a Legacy encryption driver configuration.
*
* The Legacy driver maintains compatibility with the old AdonisJS v6
* encryption format. It uses AES-256-CBC with HMAC SHA-256.
*
* Use this driver to decrypt values encrypted with older versions
* of AdonisJS or when migrating to newer encryption drivers.
*
* @param config - The Legacy driver configuration
*
* @example
* ```ts
* drivers.legacy({
* keys: [env.get('APP_KEY')]
* })
* ```
*/
legacy: (config: LegacyDriverConfig) => ConfigProvider
} = {
chacha20: (config) => {
return configProvider.create(async () => {
const { ChaCha20Poly1305 } = await import('./drivers/chacha20_poly1305.ts')
debug('configuring chacha20 encryption driver')
return {
driver: (key) => new ChaCha20Poly1305({ id: config.id, key }),
keys: config.keys.filter((key) => !!key),
}
})
},
aes256cbc: (config) => {
return configProvider.create(async () => {
const { AES256CBC } = await import('./drivers/aes_256_cbc.ts')
debug('configuring aes256cbc encryption driver')
return {
driver: (key) => new AES256CBC({ id: config.id, key }),
keys: config.keys.filter((key) => !!key),
}
})
},
aes256gcm: (config) => {
return configProvider.create(async () => {
const { AES256GCM } = await import('./drivers/aes_256_gcm.ts')
debug('configuring aes256gcm encryption driver')
return {
driver: (key) => new AES256GCM({ id: config.id, key }),
keys: config.keys.filter((key) => !!key),
}
})
},
aessiv: (config) => {
return configProvider.create(async () => {
const { AESSIV } = await import('./drivers/aes_siv.ts')
debug('configuring aessiv encryption driver')
return {
driver: (key) => new AESSIV({ id: config.id, key }),
keys: [config.key].filter((key) => !!key),
}
})
},
legacy: (config) => {
return configProvider.create(async () => {
const { Legacy } = await import('./drivers/legacy.ts')
debug('configuring legacy encryption driver')
return {
driver: (key) => new Legacy({ key }),
keys: config.keys.filter((key) => !!key),
}
})
},
}
================================================
FILE: modules/encryption/drivers/aes_256_cbc.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* AES-256-CBC encryption driver implementation.
*
* This driver provides encryption and decryption using the AES-256-CBC
* (Advanced Encryption Standard with 256-bit key in Cipher Block Chaining mode)
* algorithm. While widely supported, AES-256-CBC does not provide authenticated
* encryption. Consider using AES-256-GCM for new applications.
*
* @example
* ```ts
* const driver = new AES256CBC({
* id: 'app',
* key: 'your-256-bit-key-here'
* })
*
* const encrypted = driver.encrypt('sensitive data')
* const decrypted = driver.decrypt(encrypted)
* ```
*/
export { AES256CBC } from '@boringnode/encryption/drivers/aes_256_cbc'
================================================
FILE: modules/encryption/drivers/aes_256_gcm.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* AES-256-GCM encryption driver implementation.
*
* This driver provides authenticated encryption and decryption using the
* AES-256-GCM (Advanced Encryption Standard with 256-bit key in Galois/Counter Mode)
* algorithm. GCM mode provides both confidentiality and authenticity, making it
* the recommended choice for modern applications. It offers excellent performance
* on systems with AES hardware acceleration.
*
* @example
* ```ts
* const driver = new AES256GCM({
* id: 'app',
* key: 'your-256-bit-key-here'
* })
*
* const encrypted = driver.encrypt('sensitive data')
* const decrypted = driver.decrypt(encrypted)
* ```
*/
export { AES256GCM } from '@boringnode/encryption/drivers/aes_256_gcm'
================================================
FILE: modules/encryption/drivers/aes_siv.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* AES-SIV encryption driver implementation.
*
* This driver provides deterministic authenticated encryption using AES-SIV
* (Synthetic Initialization Vector). It is useful when you need equality
* queries over encrypted values while preserving authenticity guarantees.
*
* @example
* ```ts
* const driver = new AESSIV({
* id: 'app',
* key: 'your-256-bit-key-here'
* })
*
* const encrypted = driver.encrypt('sensitive data')
* const decrypted = driver.decrypt(encrypted)
* ```
*/
export { AESSIV } from '@boringnode/encryption/drivers/aes_siv'
================================================
FILE: modules/encryption/drivers/chacha20_poly1305.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* ChaCha20-Poly1305 encryption driver implementation.
*
* This driver provides authenticated encryption and decryption using the
* ChaCha20-Poly1305 algorithm. ChaCha20 is a modern stream cipher that
* provides excellent performance on systems without AES hardware acceleration.
* Combined with Poly1305 for authentication, it offers both confidentiality
* and authenticity.
*
* @example
* ```ts
* const driver = new ChaCha20Poly1305({
* id: 'app',
* key: 'your-256-bit-key-here'
* })
*
* const encrypted = driver.encrypt('sensitive data')
* const decrypted = driver.decrypt(encrypted)
* ```
*/
export { ChaCha20Poly1305 } from '@boringnode/encryption/drivers/chacha20_poly1305'
================================================
FILE: modules/encryption/drivers/legacy.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { errors } from '@boringnode/encryption'
import { MessageBuilder, type Secret } from '@poppinss/utils'
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto'
import { BaseDriver, Hmac, base64UrlDecode, base64UrlEncode } from '@boringnode/encryption'
import type {
CypherText,
EncryptOptions,
EncryptionDriverContract,
} from '@boringnode/encryption/types'
import { E_BLIND_INDEX_NOT_SUPPORTED } from '../errors.ts'
/**
* Configuration for the Legacy encryption driver.
*
* The Legacy driver maintains compatibility with the old AdonisJS v6
* encryption format using AES-256-CBC with HMAC SHA-256.
*/
export interface LegacyConfig {
key: string | Secret
}
/**
* Configuration for the Legacy encryption driver factory.
*
* Used when configuring the driver through the defineConfig function.
*/
export interface LegacyDriverConfig {
keys: (string | Secret)[]
}
/**
* Factory function to create a Legacy encryption configuration.
*
* @example
* ```ts
* drivers.legacy({
* keys: [env.get('APP_KEY')]
* })
* ```
*/
export function legacy(config: LegacyDriverConfig) {
return {
driver: (key: string | Secret) => new Legacy({ key }),
keys: config.keys,
}
}
/**
* Legacy encryption driver for AdonisJS.
*
* This driver maintains compatibility with the old AdonisJS v6 encryption
* format. It uses:
* - AES-256-CBC for encryption
* - HMAC SHA-256 for integrity verification
* - MessageBuilder from @poppinss/utils for encoding values
*
* Encrypted format: `[encrypted_base64url].[iv_base64url].[hmac]`
*
* @example
* ```ts
* const driver = new Legacy({ key: 'your-32-character-secret-key!!' })
*
* const encrypted = driver.encrypt('sensitive data')
* const decrypted = driver.decrypt(encrypted)
* ```
*/
export class Legacy extends BaseDriver implements EncryptionDriverContract {
constructor(config: LegacyConfig) {
super(config)
/**
* The key length must be at least 16 characters
*/
if (this.cryptoKey.length < 16) {
throw new errors.E_INSECURE_ENCRYPTER_KEY()
}
}
/**
* Encrypt a given piece of value using the app secret. A wide range of
* data types are supported.
*
* - String
* - Arrays
* - Objects
* - Booleans
* - Numbers
* - Dates
*
* You can optionally define a purpose for which the value was encrypted and
* mentioning a different purpose/no purpose during decrypt will fail.
*/
encrypt(payload: any, options?: EncryptOptions): CypherText
encrypt(payload: any, expiresIn?: string | number, purpose?: string): CypherText
encrypt(
payload: any,
expiresInOrOptions?: string | number | EncryptOptions,
purpose?: string
): CypherText {
let expiresIn: string | number | undefined
let actualPurpose: string | undefined
if (typeof expiresInOrOptions === 'object' && expiresInOrOptions !== null) {
expiresIn = expiresInOrOptions.expiresIn
actualPurpose = expiresInOrOptions.purpose
} else {
expiresIn = expiresInOrOptions
actualPurpose = purpose
}
const iv = randomBytes(16)
/**
* Use the first 32 bytes of the key for AES-256
*/
const encryptionKey = this.cryptoKey.subarray(0, 32)
const cipher = createCipheriv('aes-256-cbc', encryptionKey, iv)
const plainText = new MessageBuilder().build(payload, expiresIn, actualPurpose)
const cipherText = Buffer.concat([cipher.update(plainText), cipher.final()])
const macPayload = `${base64UrlEncode(cipherText)}${this.separator}${base64UrlEncode(iv)}`
const hmac = new Hmac(this.cryptoKey).generate(macPayload)
return this.computeReturns([macPayload, hmac])
}
/**
* Decrypt value and verify it against a purpose
*/
decrypt(value: string, purpose?: string): T | null {
if (typeof value !== 'string') {
return null
}
const [cipherEncoded, ivEncoded, macEncoded] = value.split(this.separator)
if (!cipherEncoded || !ivEncoded || !macEncoded) {
return null
}
const cipherText = base64UrlDecode(cipherEncoded)
if (!cipherText) {
return null
}
const iv = base64UrlDecode(ivEncoded)
if (!iv) {
return null
}
/**
* Verify the HMAC
*/
const isValidHmac = new Hmac(this.cryptoKey).compare(
`${cipherEncoded}${this.separator}${ivEncoded}`,
macEncoded
)
if (!isValidHmac) {
return null
}
try {
/**
* Use the first 32 bytes of the key for AES-256
*/
const encryptionKey = this.cryptoKey.subarray(0, 32)
const decipher = createDecipheriv('aes-256-cbc', encryptionKey, iv)
const plainTextBuffer = Buffer.concat([decipher.update(cipherText), decipher.final()])
return new MessageBuilder().verify(plainTextBuffer, purpose)
} catch {
return null
}
}
/**
* Legacy driver does not support blind indexes.
*/
blindIndex(_payload: any, _purpose: string): string {
throw new E_BLIND_INDEX_NOT_SUPPORTED(['legacy'])
}
/**
* Legacy driver does not support blind indexes.
*/
blindIndexes(_payload: any, _purpose: string): string[] {
throw new E_BLIND_INDEX_NOT_SUPPORTED(['legacy'])
}
}
================================================
FILE: modules/encryption/errors.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { createError } from '../../src/exceptions.ts'
import { errors as boringnodeErrors } from '@boringnode/encryption'
/**
* Raised when attempting to compute blind indexes using the legacy driver.
*/
export const E_BLIND_INDEX_NOT_SUPPORTED = createError<[string]>(
'Blind indexes are not supported by the "%s" encryption driver',
'E_BLIND_INDEX_NOT_SUPPORTED'
)
/**
* Encryption errors exposed by this package.
*/
export const errors = {
...boringnodeErrors,
E_BLIND_INDEX_NOT_SUPPORTED,
}
================================================
FILE: modules/encryption/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Main encryption class for encrypting and decrypting values.
*
* Provides methods to encrypt strings and objects with optional purpose
* binding, decrypt values, and manage encryption keys.
*
* @example
* ```ts
* const encryption = new Encryption({ key: 'secret-key' })
* const encrypted = encryption.encrypt('sensitive data')
* const decrypted = encryption.decrypt(encrypted)
* ```
*/
export { Encryption } from '@boringnode/encryption'
/**
* Manager class for handling multiple encryption instances.
*
* Allows you to configure and manage multiple encryption drivers,
* switch between them, and use different encryption keys for
* different purposes.
*
* @example
* ```ts
* const manager = new EncryptionManager({
* default: 'app',
* list: {
* app: {
* driver: (key) => new AES256GCM({ key }),
* keys: ['app-key']
* }
* }
* })
* const encryptor = manager.use('app')
* ```
*/
export { EncryptionManager } from '@boringnode/encryption'
/**
* HMAC (Hash-based Message Authentication Code) class for creating
* and verifying message authentication codes.
*
* Provides methods to generate cryptographic hashes with a secret key
* and verify them to ensure data integrity and authenticity.
*
* @example
* ```ts
* const hmac = new Hmac('secret-key')
* const signature = hmac.generate('message')
* const isValid = hmac.verify('message', signature)
* ```
*/
export { Hmac } from '@boringnode/encryption'
/**
* Base class for implementing custom encryption drivers.
*
* Extend this class to create custom encryption implementations
* that can be used with the EncryptionManager.
*
* @example
* ```ts
* class CustomDriver extends BaseDriver {
* encrypt(value: string) {
* // Custom encryption logic
* }
* decrypt(payload: string) {
* // Custom decryption logic
* }
* }
* ```
*/
export { BaseDriver } from '@boringnode/encryption'
/**
* Encryption module specific exceptions.
*/
export { errors } from './errors.ts'
/**
* Defines the encryption configuration for the application.
*
* @see {defineConfig} in define_config.ts for detailed documentation
*/
export { defineConfig } from './define_config.ts'
/**
* Collection of built-in encryption driver factory functions.
*
* Includes factories for ChaCha20-Poly1305, AES-256-CBC,
* AES-256-GCM, and AES-SIV encryption algorithms.
*
* @see {drivers} in define_config.ts for detailed documentation
*/
export { drivers } from './define_config.ts'
================================================
FILE: modules/env/editor.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/env/editor'
================================================
FILE: modules/env/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Environment module re-exports all functionality from @adonisjs/env.
* This includes the Env class, validation schemas, and utilities for managing
* environment variables with type safety and validation.
*
* @example
* // Import the Env class and validation
* import { Env } from '@adonisjs/core/env'
*
* const env = await Env.create(new URL('../', import.meta.url), {
* NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
* PORT: Env.schema.number(),
* HOST: Env.schema.string({ format: 'host' })
* })
*
* @example
* // Import environment types and utilities
* import type { EnvParser, EnvEditor } from '@adonisjs/core/env'
*/
export * from '@adonisjs/env'
================================================
FILE: modules/events.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/events'
================================================
FILE: modules/hash/define_config.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { InvalidArgumentsException } from '@poppinss/utils/exception'
import debug from '../../src/debug.ts'
import type { Argon } from './drivers/argon.ts'
import type { Scrypt } from './drivers/scrypt.ts'
import type { Bcrypt } from './drivers/bcrypt.ts'
import type { ConfigProvider } from '../../src/types.ts'
import { configProvider } from '../../src/config_provider.ts'
import type {
ArgonConfig,
BcryptConfig,
ScryptConfig,
ManagerDriverFactory,
} from '../../types/hash.ts'
/**
* Resolved config from the config provider will be
* the config accepted by the hash manager
*/
type ResolvedConfig<
KnownHashers extends Record>,
> = {
default?: keyof KnownHashers
list: {
[K in keyof KnownHashers]: KnownHashers[K] extends ConfigProvider ? A : KnownHashers[K]
}
}
/**
* Define config for the hash service. This function creates a configuration
* provider that lazily imports and resolves hash drivers when needed.
*
* @param config - Configuration object containing default hasher and list of hashers
* @param config.default - Optional default hasher name (must exist in the list)
* @param config.list - Record of hasher configurations or config providers
*
* @example
* ```ts
* const hashConfig = defineConfig({
* default: 'scrypt',
* list: {
* scrypt: drivers.scrypt({
* cost: 16384,
* blockSize: 8,
* parallelization: 1,
* saltSize: 16,
* keyLength: 64,
* }),
* bcrypt: drivers.bcrypt({
* rounds: 10,
* })
* }
* })
* ```
*/
export function defineConfig<
KnownHashers extends Record>,
>(config: {
default?: keyof KnownHashers
list: KnownHashers
}): ConfigProvider> {
/**
* Hashers list should always be provided
*/
if (!config.list) {
throw new InvalidArgumentsException('Missing "list" property in hash config')
}
/**
* The default hasher should be mentioned in the list
*/
if (config.default && !config.list[config.default]) {
throw new InvalidArgumentsException(
`Missing "list.${String(
config.default
)}" in hash config. It is referenced by the "default" property`
)
}
/**
* Config provider to lazily import drivers as they are used inside
* the user application
*/
return configProvider.create>(async (app) => {
debug('resolving hash config')
const hashersList = Object.keys(config.list)
const hashers = {} as Record<
string,
ManagerDriverFactory | ConfigProvider
>
for (let hasherName of hashersList) {
const hasher = config.list[hasherName]
if (typeof hasher === 'function') {
hashers[hasherName] = hasher
} else {
hashers[hasherName] = await hasher.resolver(app)
}
}
return {
default: config.default,
list: hashers as ResolvedConfig['list'],
}
})
}
/**
* Helpers to configure drivers inside the config file. These functions create
* configuration providers that lazily import and instantiate hash drivers.
*
* - Import happens when you first use the hash module
* - Construction of drivers happens when you first use a driver
*
* @example
* ```ts
* const hashConfig = defineConfig({
* default: 'bcrypt',
* list: {
* bcrypt: drivers.bcrypt({ rounds: 12 }),
* argon2: drivers.argon2({
* variant: 'id',
* memory: 65536,
* time: 3,
* parallelism: 4
* }),
* scrypt: drivers.scrypt({
* cost: 16384,
* blockSize: 8,
* parallelization: 1
* })
* }
* })
* ```
*/
export const drivers: {
argon2: (config: ArgonConfig) => ConfigProvider<() => Argon>
bcrypt: (config: BcryptConfig) => ConfigProvider<() => Bcrypt>
scrypt: (config: ScryptConfig) => ConfigProvider<() => Scrypt>
} = {
argon2: (config) => {
return configProvider.create(async () => {
const { Argon } = await import('./drivers/argon.js')
debug('configuring argon driver')
return () => new Argon(config)
})
},
bcrypt: (config) => {
return configProvider.create(async () => {
const { Bcrypt } = await import('./drivers/bcrypt.js')
debug('configuring bcrypt driver')
return () => new Bcrypt(config)
})
},
scrypt: (config) => {
return configProvider.create(async () => {
const { Scrypt } = await import('./drivers/scrypt.js')
debug('configuring scrypt driver')
return () => new Scrypt(config)
})
},
}
================================================
FILE: modules/hash/drivers/argon.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/hash/drivers/argon'
================================================
FILE: modules/hash/drivers/bcrypt.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Bcrypt hash driver re-exports from @adonisjs/hash.
* Provides bcrypt password hashing functionality with configurable rounds.
*
* @example
* import { Bcrypt } from '@adonisjs/core/hash/drivers/bcrypt'
*
* const bcrypt = new Bcrypt({ rounds: 12 })
* const hashed = await bcrypt.make('password')
* const isValid = await bcrypt.verify(hashed, 'password')
*/
export * from '@adonisjs/hash/drivers/bcrypt'
================================================
FILE: modules/hash/drivers/scrypt.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/hash/drivers/scrypt'
================================================
FILE: modules/hash/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Hash module provides password hashing functionality with multiple driver support.
* Re-exports all functionality from @adonisjs/hash along with configuration utilities.
*
* @example
* // Import the Hash manager and drivers
* import { HashManager, Hash } from '@adonisjs/core/hash'
*
* const manager = new HashManager(config)
* const hasher = manager.use('scrypt')
* const hashed = await hasher.make('password')
* const verified = await hasher.verify(hashed, 'password')
*
* @example
* // Import configuration and driver types
* import { defineConfig, drivers } from '@adonisjs/core/hash'
* import type { HashConfig, ScryptConfig } from '@adonisjs/core/types/hash'
*/
export * from '@adonisjs/hash'
export { defineConfig, drivers } from './define_config.ts'
================================================
FILE: modules/hash/phc_formatter.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/hash/phc_formatter'
================================================
FILE: modules/health.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/health'
================================================
FILE: modules/http/helpers.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/http-server/helpers'
================================================
FILE: modules/http/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Bodyparser import is needed to merge types of Request
* class augmented by the bodyparser package
*/
import '@adonisjs/bodyparser'
export * from '@adonisjs/http-server'
export { RequestValidator } from './request_validator.ts'
================================================
FILE: modules/http/request_validator.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import type { ValidationError, VineValidator } from '@vinejs/vine'
import type {
Infer,
SchemaTypes,
ErrorReporterContract,
MessagesProviderContact,
} from '@vinejs/vine/types'
import type { HttpContext } from './main.ts'
import type { RequestValidationOptions } from '../../types/http.ts'
/**
* Request validator for validating HTTP request data using VineJS validators.
* This class provides a convenient way to validate request body, files, cookies,
* headers, and URL parameters in AdonisJS applications.
*
* @example
* ```ts
* // Inside a controller method
* const data = await request.validateUsing(createUserValidator, {
* messagesProvider: customMessages
* })
* ```
*/
export class RequestValidator {
#ctx: HttpContext
constructor(ctx: HttpContext) {
this.#ctx = ctx
}
/**
* The error reporter method returns the error reporter
* to use for reporting errors.
*
* You can use this function to pick a different error reporter
* for each HTTP request
*/
static errorReporter?: (_: HttpContext) => ErrorReporterContract
/**
* The messages provider method returns the messages provider to use
* finding custom error messages
*
* You can use this function to pick a different messages provider for
* each HTTP request
*/
static messagesProvider?: (_: HttpContext) => MessagesProviderContact
#requestData() {
const requestBody = this.#ctx.request.all()
return {
...requestBody,
params: this.#ctx.request.params(),
headers: this.#ctx.request.headers(),
cookies: this.#ctx.request.cookiesList(),
}
}
#processValidatorOptions>(
options: RequestValidationOptions | undefined
): RequestValidationOptions {
const validatorOptions: RequestValidationOptions = options || {}
/**
* Assign request specific error reporter
*/
if (RequestValidator.errorReporter && !validatorOptions.errorReporter) {
const errorReporter = RequestValidator.errorReporter(this.#ctx)
validatorOptions.errorReporter = () => errorReporter
}
/**
* Assign request specific messages provider
*/
if (RequestValidator.messagesProvider && !validatorOptions.messagesProvider) {
validatorOptions.messagesProvider = RequestValidator.messagesProvider(this.#ctx)
}
return validatorOptions
}
/**
* Validate the current HTTP request data using a VineJS validator.
* This method automatically includes request body, files, URL parameters,
* headers, and cookies in the validation data.
*
* @param validator - VineJS validator instance
* @param options - Optional validation options including custom error reporters and messages
*
* @example
* ```ts
* const createUserValidator = vine.compile(
* vine.object({
* email: vine.string().email(),
* name: vine.string().minLength(3)
* })
* )
*
* const data = await request.validateUsing(createUserValidator, {
* errorReporter: () => vine.errors.SimpleErrorReporter,
* messagesProvider: customMessages
* })
* ```
*/
validateUsing>(
validator: VineValidator,
...[options]: [undefined] extends MetaData
? [options?: RequestValidationOptions | undefined]
: [options: RequestValidationOptions]
): Promise> {
/**
* Process the validation options
*/
const validatorOptions = this.#processValidatorOptions(options)
/**
* Data to validate
*/
const data = validatorOptions.data || this.#requestData()
return validator.validate(data, validatorOptions as any)
}
async tryValidateUsing<
Schema extends SchemaTypes,
MetaData extends undefined | Record,
>(
validator: VineValidator,
...[options]: [undefined] extends MetaData
? [options?: RequestValidationOptions | undefined]
: [options: RequestValidationOptions]
): Promise<[ValidationError, null] | [null, Infer]> {
/**
* Process the validation options
*/
const validatorOptions = this.#processValidatorOptions(options)
/**
* Data to validate
*/
const data = validatorOptions.data || this.#requestData()
return validator.tryValidate(data, validatorOptions as any)
}
}
================================================
FILE: modules/http/url_builder_client.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/http-server/client/url_builder'
================================================
FILE: modules/logger.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type LoggerConfig, type PrettyTargetOptions } from '@adonisjs/logger/types'
import { destination } from '@adonisjs/logger'
export * from '@adonisjs/logger'
/**
* Creates a synchronous Pino Pretty stream for formatted log output.
*
* This function is specifically designed for testing and development environments
* where synchronous log output is preferred. By default, Pino uses asynchronous
* logging which can make it difficult to correlate logs with specific actions
* during debugging or testing.
*
* @param options - Configuration options for the pretty printer
*
* @example
* const loggerConfig = defineConfig({
* default: 'app',
* loggers: {
* app: {
* enabled: true,
* name: env.get('APP_NAME'),
* level: env.get('LOG_LEVEL'),
* desination: !app.inProduction && (await syncDestination()),
* transport: {
* targets: [targets.file({ destination: 1 })],
* },
* },
* }
* })
*
* @returns A promise that resolves to a PrettyStream instance
*/
export async function syncDestination(
options?: PrettyTargetOptions
): Promise {
const { default: pinoPretty, isColorSupported } = await import('pino-pretty')
return isColorSupported ? pinoPretty({ ...options, sync: true }) : destination(1)
}
================================================
FILE: modules/repl.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/repl'
================================================
FILE: modules/transformers/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export * from '@adonisjs/http-transformers'
================================================
FILE: package.json
================================================
{
"name": "@adonisjs/core",
"description": "Core of AdonisJS",
"version": "7.1.1",
"engines": {
"node": ">=24.0.0"
},
"main": "build/index.js",
"type": "module",
"files": [
"build",
"!build/bin",
"!build/tests"
],
"bin": {
"adonis-kit": "./build/toolkit/main.js"
},
"exports": {
".": "./build/index.js",
"./commands": "./build/commands/main.js",
"./commands/*": "./build/commands/*.js",
"./factories": "./build/factories/core/main.js",
"./factories/*": "./build/factories/*.js",
"./types": "./build/src/types.js",
"./types/*": "./build/types/*.js",
"./services/*": "./build/services/*.js",
"./providers/*": "./build/providers/*.js",
"./helpers": "./build/src/helpers/main.js",
"./helpers/*": "./build/src/helpers/*.js",
"./ace": "./build/modules/ace/main.js",
"./ace/codemods": "./build/modules/ace/codemods.js",
"./bodyparser": "./build/modules/bodyparser/main.js",
"./bodyparser_middleware": "./build/modules/bodyparser/bodyparser_middleware.js",
"./hash": "./build/modules/hash/main.js",
"./hash/phc_formatter": "./build/modules/hash/phc_formatter.js",
"./hash/drivers/argon": "./build/modules/hash/drivers/argon.js",
"./hash/drivers/bcrypt": "./build/modules/hash/drivers/bcrypt.js",
"./hash/drivers/scrypt": "./build/modules/hash/drivers/scrypt.js",
"./app": "./build/modules/app.js",
"./config": "./build/modules/config.js",
"./container": "./build/modules/container.js",
"./encryption": "./build/modules/encryption/main.js",
"./encryption/drivers/aes_256_cbc": "./build/modules/encryption/drivers/aes_256_cbc.js",
"./encryption/drivers/aes_256_gcm": "./build/modules/encryption/drivers/aes_256_gcm.js",
"./encryption/drivers/aes_siv": "./build/modules/encryption/drivers/aes_siv.js",
"./encryption/drivers/chacha20_poly1305": "./build/modules/encryption/drivers/chacha20_poly1305.js",
"./env": "./build/modules/env/main.js",
"./dumper": "./build/modules/dumper/main.js",
"./dumper/plugin_edge": "./build/modules/dumper/plugins/edge.js",
"./env/editor": "./build/modules/env/editor.js",
"./events": "./build/modules/events.js",
"./http": "./build/modules/http/main.js",
"./http/helpers": "./build/modules/http/helpers.js",
"./http/url_builder_client": "./build/modules/http/url_builder_client.js",
"./logger": "./build/modules/logger.js",
"./repl": "./build/modules/repl.js",
"./transformers": "./build/modules/transformers/main.js",
"./package.json": "./package.json",
"./exceptions": "./build/src/exceptions.js",
"./test_utils": "./build/src/test_utils/main.js",
"./health": "./build/modules/health.js",
"./vine": "./build/src/vine.js"
},
"scripts": {
"pretest": "npm run lint",
"test": "cross-env NODE_DEBUG=adonisjs:core npm run quick:test",
"clean": "del-cli build",
"copy:templates": "copyfiles \"stubs/**/**/*.stub\" --up=1 build",
"precompile": "npm run lint",
"compile": "npm run clean && tsdown && tsc --emitDeclarationOnly --declaration",
"postcompile": "npm run copy:templates && npm run index:commands",
"build": "npm run compile",
"docs": "typedoc",
"release": "npx release-it",
"version": "npm run build",
"prepublishOnly": "npm run build",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"format": "prettier --write .",
"quick:test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts --force-exit",
"citgm": "cross-env FORCE_COLOR=0 node --import=@poppinss/ts-exec bin/test.ts --force-exit",
"index:commands": "node --import=@poppinss/ts-exec toolkit/main.js index build/commands"
},
"devDependencies": {
"@adonisjs/assembler": "^8.0.1",
"@adonisjs/eslint-config": "^3.0.0",
"@adonisjs/prettier-config": "^1.4.5",
"@adonisjs/tsconfig": "^2.0.0",
"@japa/assert": "^4.2.0",
"@japa/expect-type": "^2.0.4",
"@japa/file-system": "^3.0.0",
"@japa/runner": "^5.3.0",
"@japa/snapshot": "^2.0.10",
"@poppinss/ts-exec": "^1.4.4",
"@release-it/conventional-changelog": "^10.0.6",
"@types/node": "~25.5.0",
"@types/pretty-hrtime": "^1.0.3",
"@types/sinon": "^21.0.0",
"@types/supertest": "^7.2.0",
"@types/test-console": "^2.0.3",
"@vinejs/vine": "^4.3.0",
"argon2": "^0.44.0",
"bcrypt": "^6.0.0",
"c8": "^11.0.0",
"copyfiles": "^2.4.1",
"cross-env": "^10.1.0",
"del-cli": "^7.0.0",
"edge.js": "^6.5.0",
"eslint": "^10.0.3",
"execa": "^9.6.1",
"get-port": "^7.1.0",
"pino-pretty": "^13.1.3",
"prettier": "^3.8.1",
"release-it": "^19.2.4",
"sinon": "^21.0.3",
"supertest": "^7.2.2",
"test-console": "^2.0.0",
"timekeeper": "^2.3.1",
"tsdown": "^0.21.4",
"typedoc": "^0.28.17",
"typescript": "^5.9.3",
"youch": "^4.1.0"
},
"dependencies": {
"@adonisjs/ace": "^14.0.1",
"@adonisjs/application": "^9.0.0",
"@adonisjs/bodyparser": "^11.0.0",
"@adonisjs/config": "^6.1.0",
"@adonisjs/env": "^7.0.0",
"@adonisjs/events": "^10.1.0",
"@adonisjs/fold": "^11.0.0",
"@adonisjs/hash": "^10.0.0",
"@adonisjs/health": "^3.1.0",
"@adonisjs/http-server": "^8.0.0",
"@adonisjs/http-transformers": "^2.3.1",
"@adonisjs/logger": "^7.1.1",
"@adonisjs/repl": "^5.0.0",
"@boringnode/encryption": "^1.0.0",
"@poppinss/colors": "^4.1.6",
"@poppinss/dumper": "^0.7.0",
"@poppinss/macroable": "^1.1.1",
"@poppinss/utils": "^7.0.1",
"@sindresorhus/is": "^7.2.0",
"@types/he": "^1.2.3",
"error-stack-parser-es": "^1.0.5",
"he": "^1.2.0",
"pretty-hrtime": "^1.0.3",
"string-width": "^8.2.0"
},
"peerDependencies": {
"@adonisjs/assembler": "^8.0.0-next.23 || ^8.0.0",
"@vinejs/vine": "^4.0.0",
"argon2": "^0.44.0",
"bcrypt": "^6.0.0",
"edge.js": "^6.2.0",
"pino-pretty": "^13.1.3",
"youch": "^4.1.0-beta.13 || ^4.1.0"
},
"peerDependenciesMeta": {
"argon2": {
"optional": true
},
"bcrypt": {
"optional": true
},
"@adonisjs/assembler": {
"optional": true
},
"@vinejs/vine": {
"optional": true
},
"edge.js": {
"optional": true
},
"youch": {
"optional": true
},
"pino-pretty": {
"optional": true
}
},
"homepage": "https://github.com/adonisjs/core#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/adonisjs/core.git"
},
"bugs": {
"url": "https://github.com/adonisjs/core/issues"
},
"keywords": [
"adonisjs",
"framework",
"mvc"
],
"author": "Harminder Virk ",
"contributors": [
"Romain Lanz ",
"Julien Ripouteau ",
"Michaël Zasso"
],
"license": "MIT",
"publishConfig": {
"provenance": true,
"access": "public"
},
"c8": {
"reporter": [
"text",
"html"
],
"exclude": [
"tests/**",
"build/**",
"factories/**",
".yalc/**"
]
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"tsdown": {
"entry": [
"index.ts",
"commands/main.ts",
"commands/**/*.ts",
"factories/core/main.ts",
"factories/**/*.ts",
"src/types.ts",
"toolkit/main.ts",
"types/**/*.ts",
"services/**/*.ts",
"providers/**/*.ts",
"src/helpers/main.ts",
"src/helpers/**/*.ts",
"modules/ace/main.ts",
"modules/ace/codemods.ts",
"modules/bodyparser/main.ts",
"modules/bodyparser/bodyparser_middleware.ts",
"modules/hash/main.ts",
"modules/hash/phc_formatter.ts",
"modules/hash/drivers/*.ts",
"modules/app.ts",
"modules/config.ts",
"modules/container.ts",
"modules/encryption/main.ts",
"modules/encryption/drivers/*.ts",
"modules/env/main.ts",
"modules/dumper/main.ts",
"modules/dumper/plugins/edge.ts",
"modules/env/editor.ts",
"modules/events.ts",
"modules/http/main.ts",
"modules/http/helpers.ts",
"modules/http/url_builder_client.ts",
"modules/logger.ts",
"modules/repl.ts",
"modules/transformers/main.ts",
"src/exceptions.ts",
"src/test_utils/main.ts",
"modules/health.ts",
"src/vine.ts"
],
"outDir": "./build",
"clean": true,
"format": "esm",
"minify": "dce-only",
"fixedExtension": false,
"dts": false,
"treeshake": false,
"sourcemaps": false,
"target": "esnext"
},
"release-it": {
"git": {
"requireCleanWorkingDir": true,
"requireUpstream": true,
"commitMessage": "chore(release): ${version}",
"tagAnnotation": "v${version}",
"push": true,
"tagName": "v${version}"
},
"github": {
"release": true
},
"npm": {
"publish": true,
"skipChecks": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "angular"
}
}
}
},
"prettier": "@adonisjs/prettier-config"
}
================================================
FILE: providers/app_provider.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { dirname } from 'node:path'
import { mkdir, writeFile } from 'node:fs/promises'
import { Config } from '../modules/config.ts'
import { Logger } from '../modules/logger.ts'
import { Application } from '../modules/app.ts'
import { Dumper } from '../modules/dumper/dumper.ts'
import { RuntimeException } from '../src/exceptions.ts'
import { Router, Server } from '../modules/http/main.ts'
import { BaseEvent, Emitter } from '../modules/events.ts'
import { Encryption } from '../modules/encryption/main.ts'
import { configProvider } from '../src/config_provider.ts'
import type { ApplicationService, LoggerService } from '../src/types.ts'
import BodyParserMiddleware from '../modules/bodyparser/bodyparser_middleware.ts'
/**
* The Application Service provider registers all the baseline
* features required to run the framework.
*
* This provider handles the registration of core services including:
* - Application instance
* - Logger and logger manager
* - Configuration
* - Event emitter
* - Encryption service
* - HTTP server and router
* - Body parser middleware
* - Dumper for debugging
* - Test utilities and ACE kernel
*
* @example
* const provider = new AppServiceProvider(app)
* provider.register()
* await provider.boot()
* await provider.ready()
*/
export default class AppServiceProvider {
/**
* Application service provider constructor
*
* @param app - The application service instance
*/
constructor(protected app: ApplicationService) {}
/**
* Registers test utils with the container
*
* Creates a singleton binding for 'testUtils' that lazily imports
* and instantiates the TestUtils class when first accessed.
*
* @example
* const testUtils = await app.container.make('testUtils')
* testUtils.createHttpContext()
*/
protected registerTestUtils() {
this.app.container.singleton('testUtils', async () => {
const { TestUtils } = await import('../src/test_utils/main.js')
return new TestUtils(this.app)
})
}
/**
* Registers ace with the container
*
* Creates a singleton binding for 'ace' that lazily creates
* the ACE kernel for command-line interface functionality.
*
* @example
* const ace = await app.container.make('ace')
* await ace.exec('make:controller', ['UserController'])
*/
protected registerAce() {
this.app.container.singleton('ace', async () => {
const { createAceKernel } = await import('../modules/ace/create_kernel.js')
return createAceKernel(this.app)
})
}
/**
* Registers the application to the container
*
* Binds the application instance as both a class binding and an alias.
* This allows access to the app instance throughout the container.
*
* @example
* const app = await container.make('app')
* const appPath = app.makePath('tmp')
*/
protected registerApp() {
this.app.container.singleton(Application, () => this.app)
this.app.container.alias('app', Application)
}
/**
* Registers the logger class to resolve the default logger
*
* Creates a singleton binding for the Logger class that resolves
* the default logger instance from the logger manager.
*
* @example
* const logger = await container.make(Logger)
* logger.info('Application started')
*/
protected registerLogger() {
this.app.container.singleton(Logger, async (resolver) => {
const loggerManager = await resolver.make('logger')
return loggerManager.use()
})
}
/**
* Registers the logger manager to the container
*
* Creates a singleton binding for 'logger' that instantiates
* the LoggerManager with configuration from config/logger.ts
*
* @example
* const loggerManager = await container.make('logger')
* const fileLogger = loggerManager.use('file')
*/
protected registerLoggerManager() {
this.app.container.singleton('logger', async () => {
const { LoggerManager } = await import('../modules/logger.js')
const config = this.app.config.get('logger')
return new LoggerManager(config) as LoggerService
})
}
/**
* Registers the config to the container
*
* Binds the application's config instance as both a class binding
* and an alias, allowing access to configuration values.
*
* @example
* const config = await container.make('config')
* const appKey = config.get('app.appKey')
*/
protected registerConfig() {
this.app.container.singleton(Config, () => this.app.config)
this.app.container.alias('config', Config)
}
/**
* Registers emitter service to the container
*
* Creates a singleton binding for the event emitter that handles
* application-wide event dispatching and listening.
*
* @example
* const emitter = await container.make('emitter')
* emitter.emit('user:created', { userId: 123 })
*/
protected registerEmitter() {
this.app.container.singleton(Emitter, async () => {
return new Emitter(this.app) as Emitter
})
this.app.container.alias('emitter', Emitter)
}
/**
* Registers the encryption service with the container
*
* Creates singleton bindings for both the encryption manager and
* the default encryption instance. Resolves configuration from
* config/encryption.ts file.
*
* @example
* const encryption = await container.make('encryption')
* const encrypted = encryption.encrypt('secret-data')
*/
protected registerEncryption() {
this.app.container.singleton('encryption', async () => {
const encryptionConfigProvider = this.app.config.get('encryption')
/**
* Resolve config from the provider
*/
const config = await configProvider.resolve(this.app, encryptionConfigProvider)
if (!config) {
throw new RuntimeException(
'Invalid "config/encryption.ts" file. Make sure you are using the "defineConfig" method'
)
}
const { EncryptionManager } = await import('../modules/encryption/main.js')
return new EncryptionManager(config)
})
this.app.container.singleton(Encryption, async (resolver) => {
const encryptionManager = await resolver.make('encryption')
return encryptionManager.use()
})
}
/**
* Registers the HTTP server with the container as a singleton
*
* Creates a singleton binding for the HTTP server that handles
* incoming requests, with dependencies on encryption, emitter,
* logger, and HTTP configuration.
*
* @example
* const server = await container.make('server')
* server.start()
*/
protected registerServer() {
this.app.container.singleton(Server, async (resolver) => {
const encryption = await resolver.make(Encryption)
const emitter = await resolver.make('emitter')
const logger = await resolver.make('logger')
const config = this.app.config.get('app.http')
return new Server(this.app, encryption, emitter, logger, config)
})
this.app.container.alias('server', Server)
}
/**
* Registers router with the container as a singleton
*
* Creates a singleton binding for the router by getting it from
* the HTTP server instance. The router handles URL routing.
*
* @example
* const router = await container.make('router')
* router.get('/', ({ response }) => response.send('Hello'))
*/
protected registerRouter() {
this.app.container.singleton(Router, async (resolver) => {
const server = await resolver.make('server')
return server.getRouter()
})
this.app.container.alias('router', Router)
}
/**
* Self construct bodyparser middleware class, since it needs
* config that cannot be resolved by the container
*
* Binds the BodyParserMiddleware with bodyparser configuration
* and experimental flags for parsing request bodies.
*
* @example
* const middleware = await container.make(BodyParserMiddleware)
* await middleware.handle(ctx, next)
*/
protected registerBodyParserMiddleware() {
this.app.container.singleton(BodyParserMiddleware, () => {
const config = this.app.config.get('bodyparser')
return new BodyParserMiddleware(config, this.app.experimentalFlags)
})
}
/**
* Registeres singleton instance of the "Dumper" module configured
* via the "config/app.ts" file.
*
* The dumper is used for debugging and variable inspection with
* configurable HTML and console output formats.
*
* @example
* const dumper = await container.make('dumper')
* dumper.dump({ user: { name: 'John' } })
*/
protected registerDumper() {
this.app.container.singleton(Dumper, async () => {
const config = this.app.config.get('app.dumper', {})
const dumper = new Dumper(this.app)
if (config.html) {
dumper.configureHtmlOutput(config.html)
}
if (config.console) {
dumper.configureAnsiOutput(config.console)
}
return dumper
})
this.app.container.alias('dumper', Dumper)
}
/**
* Generates TypeScript type definitions and JSON representation of routes
*
* Creates route type definitions for better IDE support and a JSON file
* containing all registered routes. This is used in development mode for
* tooling integration and type-safety.
*
* @param router - The router instance containing registered routes
*
* @example
* const router = await container.make('router')
* await this.emitRoutes(router)
* // Generates .adonisjs/server/routes.d.ts and routes.json
*/
protected async emitRoutes(router: Router) {
try {
const { routes, imports, types } = router.generateTypes(2)
const routesTypesPath = this.app.generatedServerPath('routes.d.ts')
const routesJsonPath = this.app.generatedServerPath('routes.json')
await mkdir(dirname(routesTypesPath), { recursive: true })
await Promise.all([
writeFile(
routesTypesPath,
[
`import '@adonisjs/core/types/http'`,
...imports,
'',
...types,
'',
'export type ScannedRoutes = {',
routes,
'}',
`declare module '@adonisjs/core/types/http' {`,
' export interface RoutesList extends ScannedRoutes {}',
'}',
].join('\n')
),
writeFile(routesJsonPath, JSON.stringify(router.toJSON())),
])
this.app.notify({ isAdonisJS: true, routesFileLocation: routesJsonPath })
} catch (error) {
console.error(
"Unable to generate routes types file due to the following error. This won't impact the dev-server"
)
console.error(error)
}
}
/**
* Registers bindings
*
* Called during the application bootstrap phase to register
* all core service bindings with the IoC container.
*
* @example
* const provider = new AppServiceProvider(app)
* provider.register() // Registers all core services
*/
register() {
this.registerApp()
this.registerAce()
this.registerDumper()
this.registerLoggerManager()
this.registerLogger()
this.registerConfig()
this.registerEmitter()
this.registerEncryption()
this.registerTestUtils()
this.registerServer()
this.registerRouter()
this.registerBodyParserMiddleware()
}
/**
* Boot the service provider
*
* Called after all providers have been registered. Sets up
* event emitter for BaseEvent and adds transform macro to HttpContext.
*
* @example
* await provider.boot()
* // Now HttpContext has transform method available
*/
async boot() {
BaseEvent.useEmitter(await this.app.container.make('emitter'))
}
/**
* Called when the application is ready
*
* In non-production environments, generates route types and
* JSON files for development tooling when router is committed.
*
* @example
* await provider.ready()
* // Route types and JSON generated in development
*/
async ready() {
if (!this.app.inProduction) {
const router = await this.app.container.make('router')
if (router.commited) {
await this.emitRoutes(router)
}
}
}
}
================================================
FILE: providers/edge_provider.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import edge, { type Edge } from 'edge.js'
import { type URLOptions } from '../types/http.ts'
import type { ApplicationService } from '../src/types.ts'
import { pluginEdgeDumper } from '../modules/dumper/plugins/edge.ts'
import { BriskRoute, HttpContext, Qs, type Route, type Router } from '../modules/http/main.ts'
import { type ClientRouteJSON } from '@adonisjs/http-server/client/url_builder'
declare module '@adonisjs/core/http' {
interface HttpContext {
/**
* Reference to the edge renderer to render templates
* during an HTTP request
*/
view: ReturnType
}
interface BriskRoute {
/**
* Render an edge template without defining an
* explicit route handler
*/
render(template: string, data?: Record): Route
}
}
/**
* The Edge service provider configures Edge to work within
* an AdonisJS application environment
*
* This provider integrates EdgeJS template engine with AdonisJS by:
* - Mounting the views directory
* - Configuring template caching for production
* - Adding global helpers for route generation
* - Creating isolated renderer instances for HTTP contexts
* - Adding render macro to BriskRoute for template rendering
*
* @example
* const provider = new EdgeServiceProvider(app)
* await provider.boot()
*/
export default class EdgeServiceProvider {
/**
* Edge service provider constructor
*
* Sets the usingEdgeJS flag to true to indicate EdgeJS is being used.
*
* @param app - The application service instance
*/
constructor(protected app: ApplicationService) {
this.app.usingEdgeJS = true
}
/**
* Bridge AdonisJS and Edge
*
* Configures EdgeJS integration by:
* - Setting up template mounting and caching
* - Defining global helpers (route, signedRoute, app, config)
* - Adding view getter to HttpContext for isolated rendering
* - Adding render macro to BriskRoute
* - Registering dumper plugin
*
* @example
* await provider.boot()
* // Now edge templates can use {{ route('home') }} helper
*/
async boot() {
const app = this.app
const qs = new Qs(app.config.get('app.http.qs', {}))
const router = await this.app.container.make('router')
const dumper = await this.app.container.make('dumper')
/**
* Resolves configuration values for Edge templates
*
* Provides access to application configuration within templates.
* Includes a 'has' method to check for config key existence.
*
* @param key - The configuration key to retrieve
* @param defaultValue - Optional default value if key doesn't exist
*/
function edgeConfigResolver(key: string, defaultValue?: any) {
return app.config.get(key, defaultValue)
}
edgeConfigResolver.has = function (key: string) {
return app.config.has(key)
}
/**
* Generates client-side route definitions for frontend use
*
* Transforms router definitions into a serializable format that
* can be used in client-side JavaScript for route generation.
* Only includes named routes.
*/
function clientRoutes() {
const routes = router.toJSON()
return Object.keys(routes).reduce>((result, domain) => {
result[domain] = routes[domain].reduce((routesResult, route) => {
if (!route.name) {
return routesResult
}
routesResult.push({
domain: route.domain,
methods: route.methods,
pattern: route.pattern,
tokens: route.tokens,
name: route.name,
})
return routesResult
}, [])
return result
}, {})
}
/**
* Mount the default disk
*/
edge.mount(app.viewsPath())
/**
* Cache templates in production
*/
edge.configure({ cache: app.inProduction })
/**
* Define Edge global helpers
* @deprecated
*/
edge.global('route', function (...args: Parameters) {
return router.makeUrl(...args)
})
edge.global('signedRoute', function (...args: Parameters) {
return router.makeSignedUrl(...args)
})
edge.global('app', app)
edge.global('config', edgeConfigResolver)
edge.global('routes', function () {
return clientRoutes()
})
edge.global('routesJSON', function () {
return JSON.stringify(clientRoutes())
})
/**
* Route helpers
*/
edge.global('urlFor', function (...args: any[]) {
return (router.urlBuilder.urlFor as any)(...args)
})
edge.global('signedUrlFor', function (...args: any[]) {
return (router.urlBuilder.signedUrlFor as any)(...args)
})
/**
* Sharing qs parser with templates
*/
edge.global('qs', qs)
edge.global('formAttributes', function (route: string, params: any, options: URLOptions) {
const matchingRoute = router.findOrFail(route)
/**
* Normalize method and keep a reference to the original method
*/
options = options ?? {}
let method = matchingRoute.methods[0].toUpperCase()
const original = method
/**
* In case of HEAD, we must use the GET method
*/
if (method === 'HEAD') {
method = 'GET'
}
/**
* If method if not GET and POST, then use the querystring _method
* to and force update the method to "POST"
*/
if (method !== 'GET' && method !== 'POST') {
method = 'POST'
options = { ...options, qs: { _method: original, ...options.qs } }
}
const { action } = (router.urlBuilder.urlFor.method as any)(
original,
route,
params,
options
).form
return {
action,
method,
}
})
/**
* Creating a isolated instance of edge renderer
*/
HttpContext.getter(
'view',
function (this: HttpContext) {
return edge.createRenderer().share({
request: this.request,
})
},
true
)
/**
* Adding brisk route to render templates without an
* explicit handler
*/
BriskRoute.macro('render', function (this: BriskRoute, template, data) {
function rendersTemplate({ view }: HttpContext) {
return view.render(template, data)
}
Object.defineProperty(rendersTemplate, 'listArgs', { value: template, writable: false })
return this.setHandler(rendersTemplate)
})
edge.use(pluginEdgeDumper(dumper))
}
}
================================================
FILE: providers/hash_provider.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { RuntimeException } from '@poppinss/utils/exception'
import { Hash } from '../modules/hash/main.ts'
import { configProvider } from '../src/config_provider.ts'
import type { ApplicationService } from '../src/types.ts'
/**
* Registers the passwords hasher with the container
*
* This provider sets up password hashing functionality by:
* - Registering the HashManager with configuration from config/hash.ts
* - Providing a Hash class that uses the default hasher
* - Supporting multiple hashing drivers (bcrypt, argon2, etc.)
*
* @example
* const provider = new HashServiceProvider(app)
* provider.register()
* const hash = await app.container.make('hash')
*/
export default class HashServiceProvider {
/**
* Hash service provider constructor
*
* @param app - The application service instance
*/
constructor(protected app: ApplicationService) {}
/**
* Registering the hash class to resolve an instance with the
* default hasher.
*
* Creates a singleton binding for the Hash class that resolves
* the default hasher from the hash manager.
*
* @example
* const hash = await container.make(Hash)
* const hashed = await hash.make('password')
*/
protected registerHash() {
this.app.container.singleton(Hash, async (resolver) => {
const hashManager = await resolver.make('hash')
return hashManager.use()
})
}
/**
* Registers the hash manager with the container
*
* Creates a singleton binding for 'hash' that instantiates
* the HashManager with configuration from config/hash.ts file.
* Throws an error if the configuration is invalid.
*
* @example
* const hashManager = await container.make('hash')
* const bcryptHasher = hashManager.use('bcrypt')
*/
protected registerHashManager() {
this.app.container.singleton('hash', async () => {
const hashConfigProvider = this.app.config.get('hash')
/**
* Resolve config from the provider
*/
const config = await configProvider.resolve(this.app, hashConfigProvider)
if (!config) {
throw new RuntimeException(
'Invalid "config/hash.ts" file. Make sure you are using the "defineConfig" method'
)
}
const { HashManager } = await import('../modules/hash/main.js')
return new HashManager(config)
})
}
/**
* Registers bindings
*
* Called during the application bootstrap phase to register
* the hash manager and hash class with the IoC container.
*
* @example
* const provider = new HashServiceProvider(app)
* provider.register() // Registers hash services
*/
register() {
this.registerHashManager()
this.registerHash()
}
}
================================================
FILE: providers/repl_provider.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { homedir } from 'node:os'
import { fsImportAll } from '@poppinss/utils/fs'
import { Repl } from '../modules/repl.ts'
import type { ApplicationService, ContainerBindings } from '../src/types.ts'
/**
* Resolves a container binding and sets it on the REPL
* context
*
* This helper function makes a service from the container and
* adds it to the REPL context with a notification message.
*
* @param app - The application service instance
* @param repl - The REPL instance to add the binding to
* @param binding - The container binding key to resolve
*
* @example
* await resolveBindingForRepl(app, repl, 'router')
* // Now 'router' variable is available in REPL
*/
async function resolveBindingForRepl(
app: ApplicationService,
repl: Repl,
binding: keyof ContainerBindings
) {
repl.server!.context[binding] = await app.container.make(binding)
repl.notify(
`Loaded "${binding}" service. You can access it using the "${repl.colors.underline(
binding
)}" variable`
)
}
/**
* REPL Service Provider configures the interactive Node.js REPL
* for AdonisJS applications
*
* This provider sets up:
* - REPL instance with history file support
* - Helper methods for importing modules and making container bindings
* - Quick access methods for loading common services (app, router, etc.)
* - Utility methods for development and debugging
*
* @example
* const provider = new ReplServiceProvider(app)
* provider.register()
* await provider.boot()
*/
export default class ReplServiceProvider {
/**
* REPL service provider constructor
*
* @param app - The application service instance
*/
constructor(protected app: ApplicationService) {}
/**
* Registers the REPL binding
*
* Creates a singleton binding for the REPL with history file
* support in the user's home directory.
*
* @example
* const provider = new ReplServiceProvider(app)
* provider.register()
* const repl = await app.container.make('repl')
*/
register() {
this.app.container.singleton(Repl, async () => {
return new Repl({
historyFilePath: join(homedir(), '.adonisjs_v6_repl_history'),
})
})
this.app.container.alias('repl', Repl)
}
/**
* Registering REPL bindings during provider boot
*
* Adds helper methods to the REPL instance including:
* - importDefault: Import default export from modules
* - importAll: Import all files from a directory
* - make: Create instances using container.make
* - load* methods: Quick access to common services
* - loadHelpers: Load utility helper functions
*
* @example
* await provider.boot()
* // REPL now has helper methods available
*/
async boot() {
this.app.container.resolving('repl', (repl) => {
repl.addMethod(
'importDefault',
(_, modulePath: string) => {
return this.app.importDefault(modulePath)
},
{
description: 'Returns the default export for a module',
}
)
repl.addMethod(
'importAll',
(_, dirPath: string) => {
return fsImportAll(this.app.makeURL(dirPath), {
ignoreMissingRoot: false,
})
},
{
description: 'Import all files from a directory and assign them to a variable',
}
)
repl.addMethod(
'make',
(_, service: any, runtimeValues?: any[]) => {
return this.app.container.make(service, runtimeValues)
},
{
description: 'Make class instance using "container.make" method',
}
)
repl.addMethod(
'loadApp',
() => {
return resolveBindingForRepl(this.app, repl, 'app')
},
{
description: 'Load "app" service in the REPL context',
}
)
repl.addMethod(
'loadEncryption',
() => {
return resolveBindingForRepl(this.app, repl, 'encryption')
},
{
description: 'Load "encryption" service in the REPL context',
}
)
repl.addMethod(
'loadHash',
() => {
return resolveBindingForRepl(this.app, repl, 'hash')
},
{
description: 'Load "hash" service in the REPL context',
}
)
repl.addMethod(
'loadRouter',
() => {
return resolveBindingForRepl(this.app, repl, 'router')
},
{
description: 'Load "router" service in the REPL context',
}
)
repl.addMethod(
'loadConfig',
() => {
return resolveBindingForRepl(this.app, repl, 'config')
},
{
description: 'Load "config" service in the REPL context',
}
)
repl.addMethod(
'loadTestUtils',
() => {
return resolveBindingForRepl(this.app, repl, 'testUtils')
},
{
description: 'Load "testUtils" service in the REPL context',
}
)
repl.addMethod(
'loadHelpers',
async () => {
const { default: isModule } = await import('../src/helpers/is.js')
const { default: stringModule } = await import('../src/helpers/string.js')
const helpers = await import('../src/helpers/main.js')
repl.server!.context.helpers = {
string: stringModule,
is: isModule,
...helpers,
}
repl.notify(
`Loaded "helpers" module. You can access it using the "${repl.colors.underline(
'helpers'
)}" variable`
)
},
{
description: 'Load "helpers" module in the REPL context',
}
)
repl.addMethod(
'loadUrlBuilder',
async () => {
const router = await this.app.container.make('router')
repl.server!.context.urlBuilder = router.urlBuilder
repl.notify(
`Loaded "urlBuilder" service. You can access it using the "${repl.colors.underline(
'urlBuilder'
)}" variable`
)
},
{
description: 'Load "urlBuilder" service in the REPL context',
}
)
})
}
}
================================================
FILE: providers/vinejs_provider.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Vine } from '@vinejs/vine'
import type { ApplicationService } from '../src/types.ts'
import { HttpRequest, RequestValidator } from '../modules/http/main.ts'
import { type FileRuleValidationOptions, VineMultipartFile } from '../src/vine.ts'
/**
* Extend VineJS
*/
declare module '@vinejs/vine' {
interface Vine {
file(options?: FileRuleValidationOptions): VineMultipartFile
}
}
/**
* Extend HTTP request class
*/
declare module '@adonisjs/core/http' {
interface HttpRequest extends RequestValidator {}
}
/**
* The VineJS service provider integrates VineJS validation
* library with AdonisJS application environment
*
* This provider sets up:
* - File validation rule for multipart file uploads
* - Request validation macro for easy validation in HTTP contexts
* - Extension of VineJS with AdonisJS-specific validation features
*
* @example
* const provider = new VineJSServiceProvider(app)
* provider.boot()
* // Now Request has validateUsing method
*/
export default class VineJSServiceProvider {
/**
* VineJS service provider constructor
*
* Sets the usingVineJS flag to true to indicate VineJS is being used.
*
* @param app - The application service instance
*/
constructor(protected app: ApplicationService) {
this.app.usingVineJS = true
}
/**
* Boot the VineJS service provider
*
* Extends VineJS with file validation macro and adds validateUsing
* method to the Request class for easy validation in HTTP contexts.
*
* @example
* provider.boot()
* // Now vine.file() and request.validateUsing() are available
*/
boot() {
/**
* The file method is used to validate a field to be a valid
* multipart file.
*/
Vine.macro('file', function (this: Vine, options) {
return new VineMultipartFile(options)
})
/**
* The validate method can be used to validate the request
* data for the current request using VineJS validators
*/
HttpRequest.macro('validateUsing', function (this: HttpRequest, ...args) {
return new RequestValidator(this.ctx!).validateUsing(...args)
})
HttpRequest.macro('tryValidateUsing', function (this: HttpRequest, ...args) {
return new RequestValidator(this.ctx!).tryValidateUsing(...args)
})
}
}
================================================
FILE: services/ace.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { Kernel } from '../modules/ace/main.ts'
let ace: Kernel
/**
* Returns a singleton instance of the ace kernel
* from the container.
*
* ace service is an instance of the "Kernel" class stored inside
* the "modules/ace/kernel.ts" file
*/
await app.booted(async () => {
ace = await app.container.make('ace')
})
export { ace as default }
================================================
FILE: services/app.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import type { ApplicationService } from '../src/types.ts'
let app: ApplicationService
/**
* Set the application instance the app service should
* be using. Other services relies on the same app
* instance as well.
*
* app service is an instance of the "Application" exported from
* the "modules/app.ts" file.
*/
export function setApp(appService: ApplicationService) {
app = appService
}
export { app as default }
================================================
FILE: services/config.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { ApplicationService } from '../src/types.ts'
let config: ApplicationService['config']
/**
* The config service uses the config instance from the app service
*/
await app.booted(() => {
config = app.config
})
export { config as default }
================================================
FILE: services/dumper.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { Dumper } from '../modules/dumper/dumper.ts'
let dumper: Dumper
/**
* dumper service is an instance of the "Dumper" class stored inside
* the "modules/dumper/dumper.ts" file
*/
await app.booted(async () => {
dumper = await app.container.make('dumper')
})
/**
* Dump a value and die. The dumped value will be displayed
* using the HTML printer during an HTTP request or within
* the console otherwise.
*/
export const dd = (value: unknown) => {
dumper.dd(value, 2)
}
================================================
FILE: services/emitter.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { EmitterService } from '../src/types.ts'
let emitter: EmitterService
/**
* Returns a singleton instance of the emitter class
* from the container
*/
await app.booted(async () => {
emitter = await app.container.make('emitter')
})
export { emitter as default }
================================================
FILE: services/encryption.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { EncryptionService } from '../src/types.ts'
let encryption: EncryptionService
/**
* Returns a singleton instance of the encryption class
* from the container
*/
await app.booted(async () => {
encryption = await app.container.make('encryption')
})
export { encryption as default }
================================================
FILE: services/hash.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { HashService } from '../src/types.ts'
let hash: HashService
/**
* Returns a singleton instance of the Hash manager from the
* container
*/
await app.booted(async () => {
hash = await app.container.make('hash')
})
export { hash as default }
================================================
FILE: services/logger.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { LoggerService } from '../src/types.ts'
let logger: LoggerService
/**
* Returns a singleton instance of the logger class
* from the container
*/
await app.booted(async () => {
logger = await app.container.make('logger')
})
export { logger as default }
================================================
FILE: services/repl.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { Repl } from '../modules/repl.ts'
let repl: Repl
/**
* Returns a singleton instance of the Repl class from
* the container
*/
await app.booted(async () => {
repl = await app.container.make('repl')
})
export { repl as default }
================================================
FILE: services/router.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { HttpRouterService } from '../src/types.ts'
let router: HttpRouterService
/**
* Returns a singleton instance of the router class from
* the container
*/
await app.booted(async () => {
router = await app.container.make('router')
})
export { router as default }
================================================
FILE: services/server.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { HttpServerService } from '../src/types.ts'
let server: HttpServerService
/**
* Returns a singleton instance of the HTTP server
* from the container
*/
await app.booted(async () => {
server = await app.container.make('server')
})
export { server as default }
================================================
FILE: services/test_utils.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { TestUtils } from '../src/test_utils/main.ts'
let testUtils: TestUtils
/**
* Returns a singleton instance of the TestUtils class
* from the container.
*
* testUtils service is an instance of the "TestUtils" exported from
* the "src/test_utils/main.ts" file.
*/
await app.booted(async () => {
testUtils = await app.container.make('testUtils')
})
export { testUtils as default }
================================================
FILE: services/url_builder.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import app from './app.ts'
import type { UrlBuilderSignedUrlFor, UrlBuilderUrlFor } from '../src/types.ts'
let urlFor: UrlBuilderUrlFor
let signedUrlFor: UrlBuilderSignedUrlFor
/**
* Returns a singleton instance of the router class from
* the container
*/
await app.booted(async () => {
const router = await app.container.make('router')
urlFor = router.urlBuilder.urlFor
signedUrlFor = router.urlBuilder.signedUrlFor
})
export { urlFor, signedUrlFor }
================================================
FILE: src/assembler_hooks/index_entities.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type CommonHooks } from '@adonisjs/assembler/types'
import stringHelpers from '../helpers/string.ts'
import { type IndexEntitiesConfig } from '../types.ts'
import { outputTransformerDataObjects } from '../utils.ts'
/**
* Configures the IndexGenerator to create barrel files for "controllers", "events",
* and "listeners". This function is used as an assembler hook to automatically generate
* index files that export all modules from specified directories.
*
* @param {IndexEntitiesConfig} entities - Configuration object for entities indexing
*
* @example
* // Basic usage with default configuration
* indexEntities({})
*
* @example
* // Custom configuration for specific entities
* indexEntities({
* events: {
* enabled: true,
* source: 'app/custom-events',
* importAlias: '#custom-events'
* },
* transformers: {
* enabled: true,
* withSharedProps: true,
* inertiaMiddlewareImportPath: '#middleware/inertia_middleware'
* },
* controllers: {
* enabled: false
* }
* })
*
* @example
* // Using custom glob patterns
* indexEntities({
* listeners: {
* source: 'app/handlers',
* glob: '**\/*_handler.ts'
* }
* })
*/
export function indexEntities(entities: IndexEntitiesConfig = {}) {
const events = Object.assign(
{
enabled: true,
source: 'app/events',
importAlias: '#events',
skipSegments: ['events'],
output: '.adonisjs/server/events.ts',
},
entities.events
)
const listeners = Object.assign(
{
enabled: true,
source: 'app/listeners',
importAlias: '#listeners',
skipSegments: ['listeners'],
output: '.adonisjs/server/listeners.ts',
},
entities.listeners
)
const controllers = Object.assign(
{
enabled: true,
source: 'app/controllers',
importAlias: '#controllers',
skipSegments: ['controllers'],
output: '.adonisjs/server/controllers.ts',
},
entities.controllers
)
const transformers = Object.assign(
{
enabled: false,
source: 'app/transformers',
importAlias: '#transformers',
withSharedProps: false,
inertiaMiddlewareImportPath: '#middleware/inertia_middleware',
skipSegments: ['transformers'],
output: '.adonisjs/client/data.d.ts',
},
entities.transformers
)
const manifest = {
enabled: entities.manifest?.enabled ?? transformers.enabled,
source: 'config',
output: '.adonisjs/client/manifest.d.ts',
exclude: entities.manifest?.exclude ?? [
'app.ts',
'bodyparser.ts',
'cors.ts',
'database.ts',
'encryption.ts',
'inertia.ts',
'session.ts',
'shield.ts',
'static.ts',
'vite.ts',
],
}
return {
run(_, __, indexGenerator) {
if (events.enabled) {
indexGenerator.add('events', {
source: events.source,
disableLazyImports: true,
glob: events.glob,
as: 'barrelFile',
exportName: 'events',
importAlias: events.importAlias,
skipSegments: events.skipSegments,
output: events.output,
comment: true,
})
}
if (listeners.enabled) {
indexGenerator.add('listeners', {
source: listeners.source,
glob: listeners.glob,
as: 'barrelFile',
exportName: 'listeners',
importAlias: listeners.importAlias,
skipSegments: listeners.skipSegments,
output: listeners.output,
comment: true,
})
}
if (controllers.enabled) {
indexGenerator.add('controllers', {
source: controllers.source,
glob: controllers.glob,
as: 'barrelFile',
exportName: 'controllers',
importAlias: controllers.importAlias,
skipSegments: controllers.skipSegments,
removeSuffix: 'controller',
output: controllers.output,
comment: true,
})
}
if (transformers.enabled) {
indexGenerator.add('transformers', {
source: transformers.source,
glob: transformers.glob,
as(vfs, buffer, ___, helpers) {
const transformersList = vfs.asTree({
transformKey(key) {
let segments = key.split('/')
const baseName = segments.pop()!
if (transformers.skipSegments?.length) {
segments = segments.filter((s) => !transformers.skipSegments!.includes(s))
}
return [
...segments.map((segment) => stringHelpers.pascalCase(segment)),
stringHelpers.create(baseName).removeSuffix('transformer').pascalCase(),
].join('/')
},
transformValue: helpers.toImportPath,
})
outputTransformerDataObjects(
transformersList,
buffer,
transformers.withSharedProps,
transformers.inertiaMiddlewareImportPath
)
},
importAlias: transformers.importAlias,
output: transformers.output,
comment: true,
})
}
if (manifest.enabled) {
indexGenerator.add('manifest', {
source: manifest.source,
filter: (filePath, isDirectory) => {
if (isDirectory) {
return true
}
if (!manifest.exclude?.length) {
return true
}
return !manifest.exclude.find((include) => filePath.endsWith(include))
},
as(vfs, buffer, ___, helpers) {
const configFilesList = vfs.asList()
buffer.write(`/// `)
Object.values(configFilesList).forEach((value) => {
buffer.write(`/// `)
})
},
output: manifest.output,
comment: true,
})
}
},
} satisfies CommonHooks['init'][number]
}
================================================
FILE: src/cli_formatters/routes_list.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import stringWidth from 'string-width'
import type { UIPrimitives } from '../../types/ace.ts'
import { cliHelpers } from '../../modules/ace/main.ts'
import { type Router } from '../../modules/http/main.ts'
import { middlewareInfo, routeInfo } from '@adonisjs/http-server/helpers'
import type { MiddlewareHandlerInfo, RouteHandlerInfo, RouteJSON } from '../../types/http.ts'
/**
* Shape of the serialized route specific to the formatter
*/
type SerializedRoute = {
name: string
pattern: string
methods: string[]
middleware: MiddlewareHandlerInfo[]
handler: RouteHandlerInfo
}
/**
* Routes list formatter is used to format the routes to JSON or an ANSI string
* with pretty output.
*
* The decisions of colors, padding, alignment are all handled by the lists formatter
*
* @example
* const formatter = new RoutesListFormatter(router, ui, {
* displayHeadRoutes: false,
* maxPrettyPrintWidth: 120
* }, {
* match: 'api',
* middleware: ['auth']
* })
*
* const ansiOutput = await formatter.formatAsAnsiList()
*/
export class RoutesListFormatter {
/**
* Router instance containing all registered routes
*/
#router: Router
/**
* Colors utility for ANSI formatting
*/
#colors: UIPrimitives['colors']
/**
* Table utility for creating formatted tables
*/
#table: UIPrimitives['table']
/**
* Options for printing routes
*/
#options: {
displayHeadRoutes?: boolean
maxPrettyPrintWidth?: number
}
/**
* Filters to apply when finding routes
*/
#filters: {
match?: string
middleware?: string[]
ignoreMiddleware?: string[]
}
/**
* Creates a new instance of the routes list formatter
*
* @param router - Router instance containing routes to format
* @param ui - UI primitives for colors and table formatting
* @param options - Display options for route formatting
* @param filters - Filters to apply when displaying routes
*/
constructor(
router: Router,
ui: UIPrimitives,
options: {
displayHeadRoutes?: boolean
maxPrettyPrintWidth?: number
},
filters: {
match?: string
middleware?: string[]
ignoreMiddleware?: string[]
}
) {
this.#router = router
this.#colors = ui.colors
this.#table = ui.table
this.#filters = filters
this.#options = options
this.#router.commit()
}
/**
* Test if a route clears the applied filters based on middleware, name, pattern, and handler.
*
* @param route - The serialized route to test against filters
*/
#isAllowedByFilters(route: SerializedRoute) {
let allowRoute = true
/**
* Check if the route is allowed by applying the middleware
* filter
*/
if (this.#filters.middleware) {
allowRoute = this.#filters.middleware.every((name) => {
if (name === '*') {
return route.middleware.length > 0
}
return route.middleware.find((middleware) => middleware.name === name)
})
}
/**
* Check if the route has any or the ignored middleware. If yes, do not
* display the route
*/
if (allowRoute && this.#filters.ignoreMiddleware) {
allowRoute = this.#filters.ignoreMiddleware.every((name) => {
if (name === '*') {
return route.middleware.length === 0
}
return !route.middleware.find((middleware) => middleware.name === name)
})
}
/**
* No more filters to be applied
*/
if (!this.#filters.match) {
return allowRoute
}
/**
* Check if the route name has the match keyword
*/
if (route.name.includes(this.#filters.match)) {
return true
}
/**
* Check if the route pattern has the match keyword
*/
if (route.pattern.includes(this.#filters.match)) {
return true
}
/**
* Check if the route handler has the match keyword
*/
if (
route.handler.type === 'controller'
? route.handler.moduleNameOrPath.includes(this.#filters.match)
: route.handler.name.includes(this.#filters.match)
) {
return true
}
/**
* Disallow route
*/
return false
}
/**
* Serializes routes JSON to an object that can be used for pretty printing.
* Converts RouteJSON into a format suitable for display and filtering.
*
* @param route - The route JSON object to serialize
*/
async #serializeRoute(route: RouteJSON): Promise {
let methods = route.methods
if (!this.#options.displayHeadRoutes) {
methods = methods.filter((method) => method !== 'HEAD')
}
const middlewareList = await Promise.all(
[...route.middleware.all()].map((middleware) => {
return middlewareInfo(middleware)
})
)
return {
name: route.name || '',
pattern: route.pattern,
methods: methods,
handler: await routeInfo(route),
middleware: middlewareList.filter((info) => info.type !== 'global'),
}
}
/**
* Formats the route method for the ansi list and table with dim styling.
*
* @param method - The HTTP method to format (GET, POST, etc.)
*/
#formatRouteMethod(method: string) {
return this.#colors.dim(method)
}
/**
* Formats route pattern for the ansi list and table with colored parameters and route name.
*
* @param route - The serialized route containing pattern and name information
*/
#formatRoutePattern(route: SerializedRoute) {
const pattern = this.#router
.parsePattern(route.pattern)
.map((token) => {
if (token.type === 1) {
return this.#colors.yellow(`:${token.val}`)
}
if (token.type === 3) {
return this.#colors.yellow(`:${token.val}?`)
}
if (token.type === 2) {
return this.#colors.red(token.val)
}
return token.val
})
.join('/')
return `${pattern === '/' ? pattern : `/${pattern}`}${
route.name ? ` ${this.#colors.dim(`(${route.name})`)}` : ''
} `
}
/**
* Formats controller name for the ansi list and table with cyan coloring.
*
* @param route - The serialized route containing handler information
*/
#formatControllerName(route: SerializedRoute) {
return route.handler.type === 'controller'
? ` ${this.#colors.cyan(route.handler.moduleNameOrPath)}.`
: ''
}
/**
* Formats action name for the ansi list and table with cyan coloring and arguments.
*
* @param route - The serialized route containing handler information
*/
#formatAction(route: SerializedRoute) {
if (route.handler.type === 'controller') {
return `${this.#colors.cyan(route.handler.method)}`
}
const functionName = ` ${this.#colors.cyan(route.handler.name)}`
if (route.handler.args) {
return ` ${functionName}${this.#colors.dim(`(${route.handler.args})`)}`
}
return functionName
}
/**
* Formats route middleware for the ansi list and table with optional compacting.
*
* @param route - The serialized route containing middleware information
* @param mode - Display mode: 'normal' shows all middleware, 'compact' truncates long lists
*/
#formatMiddleware(route: SerializedRoute, mode: 'normal' | 'compact' = 'normal') {
if (mode === 'compact' && route.middleware.length > 3) {
const firstMiddleware = route.middleware[0].name
const secondMiddleware = route.middleware[1].name
const diff = route.middleware.length - 2
return this.#colors.dim(`${firstMiddleware}, ${secondMiddleware}, and ${diff} more`)
}
return this.#colors.dim(
`${route.middleware
.map((one) => one.name)
.filter((one) => one)
.join(', ')}`
)
}
/**
* Formatting the domain headling to be in green color with
* dots around it
*/
#formatDomainHeadline(domain: string) {
if (domain !== 'root') {
return cliHelpers.justify([`${this.#colors.dim('..')} ${this.#colors.green(domain)} `], {
maxWidth: this.#options.maxPrettyPrintWidth || cliHelpers.TERMINAL_SIZE,
paddingChar: this.#colors.dim('.'),
})[0]
}
return ''
}
/**
* Justify the ansi list
*/
#justifyListTables(tables: { heading: string; rows: [string, string, string, string][] }[]) {
return tables.map((table) => {
/**
* Formatting methods
*/
const methods = table.rows.map((columns) => columns[0])
const largestMethodsLength = Math.max(...methods.map((method) => stringWidth(method)))
const formattedMethods = cliHelpers.justify(methods, {
maxWidth: largestMethodsLength,
})
/**
* Formatting patterns
*/
const patterns = table.rows.map((columns) => columns[1])
const largestPatternLength = Math.max(...patterns.map((pattern) => stringWidth(pattern)))
const formattedPatterns = cliHelpers.justify(patterns, {
maxWidth: largestPatternLength,
paddingChar: this.#colors.dim('.'),
})
/**
* Formatting middleware to be right aligned
*/
const middleware = table.rows.map((columns) => columns[3])
const largestMiddlewareLength = Math.max(...middleware.map((one) => stringWidth(one)))
const formattedMiddleware = cliHelpers.justify(middleware, {
maxWidth: largestMiddlewareLength,
align: 'right',
paddingChar: ' ',
})
/**
* Formatting controllers to be right aligned and take all the remaining
* space after printing route method, pattern and middleware.
*/
const controllers = table.rows.map((columns) => columns[2])
const largestControllerLength =
(this.#options.maxPrettyPrintWidth || cliHelpers.TERMINAL_SIZE) -
(largestPatternLength + largestMethodsLength + largestMiddlewareLength)
const formattedControllers = cliHelpers.truncate(
cliHelpers.justify(controllers, {
maxWidth: largestControllerLength,
align: 'right',
paddingChar: this.#colors.dim('.'),
}),
{
maxWidth: largestControllerLength,
}
)
return {
heading: table.heading,
rows: formattedMethods.reduce((result, method, index) => {
result.push(
`${method}${formattedPatterns[index]}${formattedControllers[index]}${formattedMiddleware[index]}`
)
return result
}, []),
}
})
}
/**
* Formats middleware info into a compact string representation.
* Named middleware with args becomes "name:args", closures use their name as-is.
*/
#formatMiddlewareAsString(middleware: MiddlewareHandlerInfo): string | undefined {
if (middleware.type === 'named' && middleware.args) {
return `${middleware.name}:${middleware.args}`
}
return middleware.name
}
/**
* Formats routes as JSONL (one JSON object per line). Each line is a
* self-contained route object with flattened domain, simplified handler,
* and middleware as a string array. Optimized for machine consumption
* by AI agents and CLI tools.
*/
async formatAsJSONL(): Promise {
const routes = this.#router.toJSON()
const domains = Object.keys(routes)
const lines: string[] = []
for (let domain of domains) {
for (let route of routes[domain]) {
const serializedRoute = await this.#serializeRoute(route)
if (!this.#isAllowedByFilters(serializedRoute)) {
continue
}
const handler =
serializedRoute.handler.type === 'controller'
? {
type: 'controller' as const,
module: serializedRoute.handler.moduleNameOrPath,
method: serializedRoute.handler.method,
}
: {
type:
serializedRoute.handler.name === 'redirectsToRoute'
? ('redirect' as const)
: ('closure' as const),
name: serializedRoute.handler.name,
...(serializedRoute.handler.args ? { args: serializedRoute.handler.args } : {}),
}
const middleware = serializedRoute.middleware
.map((m) => this.#formatMiddlewareAsString(m))
.filter((m) => !!m)
for (let method of serializedRoute.methods) {
const entry: Record = {
method,
pattern: serializedRoute.pattern,
handler,
}
if (serializedRoute.name) {
entry.name = serializedRoute.name
}
if (domain !== 'root') {
entry.domain = domain
}
if (middleware.length > 0) {
entry.middleware = middleware
}
lines.push(JSON.stringify(entry))
}
}
}
return lines
}
/**
* Formats routes as an array of objects. Routes are grouped by
* domain.
*/
async formatAsJSON() {
const routes = this.#router.toJSON()
const domains = Object.keys(routes)
let routesJSON: { domain: string; routes: SerializedRoute[] }[] = []
for (let domain of domains) {
const domainRoutes = await Promise.all(
routes[domain].map((route) => this.#serializeRoute(route))
)
routesJSON.push({
domain,
routes: domainRoutes.filter((route) => this.#isAllowedByFilters(route)),
})
}
return routesJSON
}
/**
* Format routes to ansi list of tables. Each domain has its own table
* with heading and rows. Each row has colums with colors and spacing
* around them.
*/
async formatAsAnsiList() {
const routes = this.#router.toJSON()
const domains = Object.keys(routes)
const tables: { heading: string; rows: [string, string, string, string][] }[] = []
for (let domain of domains) {
const list: (typeof tables)[number] = {
heading: this.#formatDomainHeadline(domain),
rows: [
[
this.#colors.dim('METHOD'),
` ${this.#colors.dim('ROUTE')} `,
` ${this.#colors.dim('HANDLER')}`,
` ${this.#colors.dim('MIDDLEWARE')}`,
],
],
}
/**
* Computing table rows. Each route+method will have its
* own row
*/
for (let route of routes[domain]) {
const serializedRoute = await this.#serializeRoute(route)
if (this.#isAllowedByFilters(serializedRoute)) {
serializedRoute.methods.forEach((method) => {
list.rows.push([
this.#formatRouteMethod(method),
` ${this.#formatRoutePattern(serializedRoute)}`,
`${this.#formatControllerName(serializedRoute)}${this.#formatAction(
serializedRoute
)}`,
` ${this.#formatMiddleware(serializedRoute, 'compact')}`,
])
})
}
}
tables.push(list)
}
return this.#justifyListTables(tables)
}
/**
* Format routes to ansi tables. Each domain has its own table
* with heading and rows. Each row has colums with colors and spacing
* around them.
*/
async formatAsAnsiTable() {
const routes = this.#router.toJSON()
const domains = Object.keys(routes)
const tables: { heading: string; table: ReturnType }[] = []
for (let domain of domains) {
const list: (typeof tables)[number] = {
heading: this.#formatDomainHeadline(domain),
table: this.#table()
.fullWidth()
.fluidColumnIndex(2)
.head([
this.#colors.dim('METHOD'),
this.#colors.dim('ROUTE'),
{ hAlign: 'right', content: this.#colors.dim('HANDLER') },
{ content: this.#colors.dim('MIDDLEWARE'), hAlign: 'right' },
]),
}
/**
* Computing table rows. Each route+method will have its
* own row
*/
for (let route of routes[domain]) {
const serializedRoute = await this.#serializeRoute(route)
if (this.#isAllowedByFilters(serializedRoute)) {
serializedRoute.methods.forEach((method) => {
list.table.row([
this.#formatRouteMethod(method),
this.#formatRoutePattern(serializedRoute),
{
content: `${this.#formatControllerName(serializedRoute)}${this.#formatAction(
serializedRoute
)}`,
hAlign: 'right',
},
{ content: this.#formatMiddleware(serializedRoute), hAlign: 'right' },
])
})
}
}
tables.push(list)
}
return tables
}
}
================================================
FILE: src/config_provider.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type ApplicationService, type ConfigProvider } from './types.ts'
/**
* Helper utilities to create and resolve config providers. Config providers
* are used to defer configuration resolution until the application is booted,
* allowing access to environment variables and other application services.
*
* @example
* // Creating a database config provider
* const databaseConfig = configProvider.create(async (app) => ({
* connection: app.env.get('DB_CONNECTION', 'sqlite'),
* host: app.env.get('DB_HOST', 'localhost'),
* port: app.env.get('DB_PORT', 5432)
* }))
*
* @example
* // Resolving a config provider
* const config = await configProvider.resolve(app, databaseConfig)
* if (config) {
* console.log(`Database connection: ${config.connection}`)
* }
*/
export const configProvider = {
/**
* Creates a new config provider that will resolve configuration
* when the application is booted.
*
* @param resolver - Function that receives the application service and returns the configuration
*
* @example
* const mailConfig = configProvider.create(async (app) => ({
* driver: app.env.get('MAIL_DRIVER', 'smtp'),
* host: app.env.get('SMTP_HOST'),
* port: app.env.get('SMTP_PORT', 587)
* }))
*/
create(resolver: ConfigProvider['resolver']): ConfigProvider {
return {
type: 'provider',
resolver,
}
},
/**
* Resolves a config provider if the provided value is a valid config provider,
* otherwise returns null.
*
* @param app - The application service instance
* @param provider - The potential config provider to resolve
*
* @example
* const resolved = await configProvider.resolve(app, someProvider)
* if (resolved) {
* // Use the resolved configuration
* console.log('Config resolved:', resolved)
* } else {
* console.log('Not a valid config provider')
* }
*/
async resolve(app: ApplicationService, provider: unknown): Promise {
if (provider && typeof provider === 'object' && 'type' in provider) {
return (provider as ConfigProvider).resolver(app)
}
return null
},
}
================================================
FILE: src/debug.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { debuglog } from 'node:util'
/**
* Debug utility for AdonisJS core. This uses Node.js built-in debuglog
* utility to provide debugging information when the NODE_DEBUG environment
* variable includes 'adonisjs:core'.
*
* @example
* // Enable debugging by setting environment variable
* // NODE_DEBUG=adonisjs:core node app.js
*
* @example
* // Usage in code
* import debug from '@adonisjs/core/debug'
* debug('Application started')
* debug('Processing request: %s', req.url)
*/
export default debuglog('adonisjs:core')
================================================
FILE: src/exceptions.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Core exception classes and utilities for AdonisJS. This module re-exports
* commonly used exception classes from @poppinss/utils for creating and
* handling errors in AdonisJS applications.
*
* @example
* // Creating a custom exception
* import { Exception } from '@adonisjs/core/exceptions'
*
* class ValidationException extends Exception {
* static status = 422
* static code = 'E_VALIDATION_FAILURE'
* }
*
* @example
* // Using createError to create custom error classes
* import { createError } from '@adonisjs/core/exceptions'
*
* const UserNotFound = createError('User not found', 'E_USER_NOT_FOUND', 404)
* throw new UserNotFound()
*/
export {
/**
* Base exception class for creating custom exceptions with status codes,
* error codes, and additional context.
*/
Exception,
/**
* Utility function to create custom error classes with predefined
* message, code, and status.
*/
createError,
/**
* Runtime exception class for errors that occur during application runtime.
*/
RuntimeException,
/**
* Exception class for invalid argument errors, typically used in function
* parameter validation.
*/
InvalidArgumentsException,
} from '@poppinss/utils/exception'
================================================
FILE: src/helpers/assert.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Assertion utilities for type-safe programming in AdonisJS. These functions
* provide runtime type assertions that help with TypeScript type narrowing
* and catching potential null/undefined issues early.
*
* @example
* // Type-safe null checking
* import { assertExists } from '@adonisjs/core/helpers'
*
* function processUser(user: User | null) {
* assertExists(user) // TypeScript now knows user is not null
* console.log(user.name) // Safe to access properties
* }
*
* @example
* // Exhaustiveness checking in switch statements
* import { assertUnreachable } from '@adonisjs/core/helpers'
*
* function handleStatus(status: 'pending' | 'completed') {
* switch (status) {
* case 'pending': return 'Processing...'
* case 'completed': return 'Done!'
* default: return assertUnreachable(status)
* }
* }
*/
export {
/**
* Assert that a value exists (is not null or undefined).
* Throws an error if the value is null or undefined.
*/
assertExists,
/**
* Assert that a value is not null.
* Throws an error if the value is null.
*/
assertNotNull,
/**
* Assert that a value is defined (is not undefined).
* Throws an error if the value is undefined.
*/
assertIsDefined,
/**
* Assert that code should never reach this point.
* Useful for exhaustiveness checking in switch statements.
*/
assertUnreachable,
} from '@poppinss/utils/assert'
================================================
FILE: src/helpers/http.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* HTTP helper utilities re-exported from @adonisjs/http-server. This module
* provides a convenient entry point for accessing all HTTP-related utilities
* including route helpers, middleware utilities, and request/response helpers.
*
* @example
* // Import specific HTTP helpers
* import { middlewareInfo, routeInfo } from '@adonisjs/core/helpers'
*
* const middleware = middlewareInfo('cors', CorsMiddleware)
* const route = routeInfo('users.show', '/users/:id')
*
* @example
* // Access all HTTP helpers
* import * as httpHelpers from '@adonisjs/core/helpers/http'
*
* // Use any helper from the http-server package
* const routeData = httpHelpers.routeInfo('api.posts', '/api/posts')
*/
export * from '@adonisjs/http-server/helpers'
================================================
FILE: src/helpers/is.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import is from '@sindresorhus/is'
/**
* Type checking utilities re-exported from @sindresorhus/is. Provides
* a comprehensive set of type-checking functions with TypeScript type guards.
*
* @example
* // Basic type checking
* import { is } from '@adonisjs/core/helpers'
*
* if (is.string(value)) {
* // TypeScript knows value is string
* console.log(value.toUpperCase())
* }
*
* @example
* // Complex type checking
* import { is } from '@adonisjs/core/helpers'
*
* is.array(value) && is.nonEmptyArray(value)
* is.plainObject(obj) && is.hasProperty(obj, 'name')
* is.number(num) && is.integer(num) && is.positive(num)
*/
export default is
================================================
FILE: src/helpers/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Core helper utilities for AdonisJS applications. This module provides
* a collection of commonly used utilities including file system operations,
* cryptographic functions, composition utilities, and HTTP server helpers.
*
* @example
* // File system utilities
* import { fsReadAll, fsImportAll } from '@adonisjs/core/helpers'
*
* const files = await fsReadAll(url('app/controllers'))
* const modules = await fsImportAll(url('app/events'))
*
* @example
* // Cryptographic utilities
* import { base64, safeEqual, Secret } from '@adonisjs/core/helpers'
*
* const encoded = base64.encode('sensitive data')
* const isEqual = safeEqual(hash1, hash2)
* const secret = new Secret('my-secret-key')
*
* @example
* // HTTP server helpers
* import { middlewareInfo, routeInfo } from '@adonisjs/core/helpers'
*
* const middleware = middlewareInfo('cors', () => {})
* const route = routeInfo('users.show', '/users/:id')
*/
/**
* File system utilities for reading and importing files recursively.
*/
export { fsReadAll, fsImportAll } from '@poppinss/utils/fs'
/**
* Base64 encoding and decoding utilities.
*/
export { default as base64 } from '@poppinss/utils/base64'
/**
* Core utilities including function composition, secret management,
* safe equality comparison, and message building.
*/
export { compose, Secret, safeEqual, MessageBuilder, defineStaticProperty } from '@poppinss/utils'
/**
* Verification token utility for creating secure tokens.
*/
export { VerificationToken } from './verification_token.ts'
================================================
FILE: src/helpers/string.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import prettyHrTime from 'pretty-hrtime'
import string from '@poppinss/utils/string'
import he, { type EncodeOptions } from 'he'
import StringBuilder from '@poppinss/utils/string_builder'
/**
* Collection of string helpers to transform a string value. This object extends
* the base string utilities from @poppinss/utils with additional AdonisJS-specific
* string manipulation methods.
*
* @example
* // Basic string transformations
* stringHelpers.camelCase('hello_world') // 'helloWorld'
* stringHelpers.snakeCase('HelloWorld') // 'hello_world'
* stringHelpers.pascalCase('hello world') // 'HelloWorld'
*
* @example
* // HTML escaping and encoding
* stringHelpers.escapeHTML('')
* stringHelpers.encodeSymbols('© 2023 AdonisJS')
*/
const stringHelpers: typeof string & {
/**
* Creates an instance of the StringBuilder for efficient string concatenation.
*
* @param value - Initial string value or existing StringBuilder instance
*
* @example
* const builder = stringHelpers.create('Hello')
* builder.append(' ').append('World')
* console.log(builder.toString()) // 'Hello World'
*/
create(value: string | StringBuilder): StringBuilder
/**
* Convert a number to its ordinal form (1st, 2nd, 3rd, etc.).
* Alias for the `ordinal` method from @poppinss/utils.
*
* @example
* stringHelpers.ordinalize(1) // '1st'
* stringHelpers.ordinalize(22) // '22nd'
*/
ordinalize: (typeof string)['ordinal']
/**
* Convert a string to a readable sentence format.
* Alias for the `sentence` method from @poppinss/utils.
*
* @example
* stringHelpers.toSentence('hello_world') // 'Hello world'
* stringHelpers.toSentence('firstName') // 'First name'
*/
toSentence: (typeof string)['sentence']
/**
* Generate a cryptographically secure random string of specified length.
* Alias for the `random` method from @poppinss/utils.
*
* @example
* stringHelpers.generateRandom(16) // 'a1b2c3d4e5f6g7h8'
* stringHelpers.generateRandom(32) // Long random string
*/
generateRandom: (typeof string)['random']
/**
* Convert high-resolution time difference to a human-readable format.
*
* @param time - High-resolution time tuple from process.hrtime()
* @param options - Formatting options
* @param options.verbose - Use verbose format (e.g., '1 second' vs '1s')
* @param options.precise - Show precise decimal places
*
* @example
* const start = process.hrtime()
* // ... some operation
* const diff = process.hrtime(start)
* stringHelpers.prettyHrTime(diff) // '2.5ms'
* stringHelpers.prettyHrTime(diff, { verbose: true }) // '2 milliseconds'
*/
prettyHrTime(
time: [number, number],
options?: { verbose?: boolean | undefined; precise?: boolean | undefined }
): string
/**
* Check if a string is empty or contains only whitespace characters.
*
* @param value - The string to check
*
* @example
* stringHelpers.isEmpty('') // true
* stringHelpers.isEmpty(' ') // true
* stringHelpers.isEmpty('hello') // false
*/
isEmpty(value: string): boolean
/**
* Escape HTML entities to prevent XSS attacks and display HTML safely.
*
* @param value - The string to escape
* @param options - Escaping options
* @param options.encodeSymbols - Whether to encode symbols as HTML entities
*
* @example
* stringHelpers.escapeHTML('')
* // '<script>alert("xss")</script>'
*
* @example
* stringHelpers.escapeHTML('© 2023', { encodeSymbols: true })
* // '© 2023'
*/
escapeHTML(value: string, options?: { encodeSymbols?: boolean }): string
/**
* Encode Unicode symbols and special characters as HTML entities.
*
* @param value - The string containing symbols to encode
* @param options - Encoding options from the 'he' library
*
* @example
* stringHelpers.encodeSymbols('© 2023 AdonisJS ™')
* // '© 2023 AdonisJS ™'
*
* @example
* stringHelpers.encodeSymbols('Café', { decimal: true })
* // 'Café'
*/
encodeSymbols(value: string, options?: EncodeOptions): string
} = {
...string,
toSentence: string.sentence,
ordinalize: string.ordinal,
generateRandom: string.random,
create(value: string | StringBuilder): StringBuilder {
return new StringBuilder(value)
},
/**
* Formats Node.js hrtime output into a human-readable string.
*
* @param time - Tuple of [seconds, nanoseconds] from process.hrtime()
* @param options - Formatting options for output style and precision
*/
prettyHrTime(time, options) {
return prettyHrTime(time, options)
},
/**
* Check if a string is empty or contains only whitespace characters.
*
* @param value - The string to check for emptiness
*/
isEmpty(value: string): boolean {
return value.trim().length === 0
},
/**
* Escape HTML entities to prevent XSS attacks and display HTML safely.
*
* @param value - The string containing HTML to escape
* @param options - Optional configuration for escaping behavior
* @param options.encodeSymbols - Whether to also encode Unicode symbols as HTML entities
*/
escapeHTML(value: string, options?: { encodeSymbols?: boolean }): string {
value = he.escape(value)
if (options && options.encodeSymbols) {
value = this.encodeSymbols(value, { allowUnsafeSymbols: true })
}
return value
},
/**
* Encode Unicode symbols and special characters as HTML entities.
*
* @param value - The string containing symbols to encode
* @param options - Encoding options from the 'he' library
*/
encodeSymbols(value: string, options?: EncodeOptions): string {
return he.encode(value, options)
},
}
export default stringHelpers
================================================
FILE: src/helpers/types.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import is from '@sindresorhus/is'
/**
* @deprecated
* Use "is" helpers instead. The types helpers exist for backward compatibility.
*
* @example
* // Instead of using types helpers (deprecated)
* types.isString('hello')
* types.isNumber(42)
*
* @example
* // Use the new "is" helpers
* import { is } from '@adonisjs/core/helpers'
* is.string('hello')
* is.number(42)
*/
const types = {
/**
* Direct reference to the is utility for type checking lookups.
* @deprecated Use the is helpers directly instead
*/
lookup: is,
/**
* Check if value is null.
* @deprecated Use is.null instead
*/
isNull: is.null,
/**
* Check if value is a boolean.
* @deprecated Use is.boolean instead
*/
isBoolean: is.boolean,
/**
* Check if value is a Buffer.
* @deprecated Use is.buffer instead
*/
isBuffer: is.buffer,
/**
* Check if value is a number.
* @deprecated Use is.number instead
*/
isNumber: is.number,
/**
* Check if value is a string.
* @deprecated Use is.string instead
*/
isString: is.string,
/**
* Check if value is an arguments object.
* @deprecated Use is.arguments instead
*/
isArguments: is.arguments,
/**
* Check if value is an object.
* @deprecated Use is.object instead
*/
isObject: is.object,
/**
* Check if value is a Date.
* @deprecated Use is.date instead
*/
isDate: is.date,
/**
* Check if value is an array.
* @deprecated Use is.array instead
*/
isArray: is.array,
/**
* Check if value is a regular expression.
* @deprecated Use is.regExp instead
*/
isRegexp: is.regExp,
/**
* Check if value is an error object.
* @deprecated Use is.error instead
*/
isError: is.error,
/**
* Check if value is a function.
* @deprecated Use is.function instead
*/
isFunction: is.function,
/**
* Check if value is a class.
* @deprecated Use is.class instead
*/
isClass: is.class,
/**
* Check if value is an integer.
* @deprecated Use is.integer instead
*/
isInteger: is.integer,
/**
* Check if a number is a float (has decimal places).
*
* @param value - The number to check
* @deprecated Use is.decimal or custom logic instead
*
* @example
* types.isFloat(3.14) // true
* types.isFloat(42) // false
*/
isFloat(value: number): value is number {
return value !== (value | 0)
},
/**
* Check if a value represents a decimal number with specific decimal places.
*
* @param value - The value to check (string or number)
* @param options - Options for decimal validation
* @param options.decimalPlaces - Regex pattern for allowed decimal places (default: '1,')
* @deprecated Use a validation library like Vine or custom logic instead
*
* @example
* types.isDecimal('3.14') // true
* types.isDecimal('42.0') // true
* types.isDecimal('42') // false
* types.isDecimal('3.141', { decimalPlaces: '1,3' }) // true
*/
isDecimal(value: string | number, options?: { decimalPlaces?: string }): boolean {
if (typeof value === 'number') {
value = value.toString()
}
const decimalPlaces = (options && options.decimalPlaces) || '1,'
return new RegExp(`^[-+]?([0-9]+)?(\\.[0-9]{${decimalPlaces}})$`).test(value)
},
}
export default types
================================================
FILE: src/helpers/verification_token.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { createHash } from 'node:crypto'
import string from '@poppinss/utils/string'
import { safeEqual, Secret } from '@poppinss/utils'
import base64 from '@poppinss/utils/base64'
/**
* Verification token class can be used to create tokens publicly
* shareable tokens while storing the token hash within the database.
*
* This class is used by the Auth and the Persona packages to manage
* tokens for authentication and authorization purposes.
*
* @example
* class UserToken extends VerificationToken {
* constructor(user: User, secret: Secret) {
* super()
* this.tokenableId = user.id
* this.computeValue(secret)
* }
* }
*/
export abstract class VerificationToken {
/**
* Decodes a publicly shared token and return the series
* and the token value from it.
*
* Returns null when unable to decode the token because of
* invalid format or encoding.
*
* @param value - The token string to decode
*/
static decode(value: string): null | { identifier: string; secret: Secret } {
/**
* Ensure value is a string and starts with the prefix.
*/
if (typeof value !== 'string') {
return null
}
/**
* Remove prefix from the rest of the token.
*/
if (!value) {
return null
}
const [identifier, ...tokenValue] = value.split('.')
if (!identifier || tokenValue.length === 0) {
return null
}
const decodedIdentifier = base64.urlDecode(identifier)
const decodedSecret = base64.urlDecode(tokenValue.join('.'))
if (!decodedIdentifier || !decodedSecret) {
return null
}
return {
identifier: decodedIdentifier,
secret: new Secret(decodedSecret),
}
}
/**
* Creates a transient token that can be shared with the persistence
* layer.
*
* @param userId - The user ID for whom the token is being created
* @param size - The size of the random token seed
* @param expiresIn - Token expiration time (string like '2h' or number in seconds)
*/
static createTransientToken(
userId: string | number | BigInt,
size: number,
expiresIn: string | number
) {
const expiresAt = new Date()
expiresAt.setSeconds(expiresAt.getSeconds() + string.seconds.parse(expiresIn))
return {
userId,
expiresAt,
...this.seed(size),
}
}
/**
* Creates a secret opaque token and its hash.
*
* @param size - The length of the random token to generate
*/
static seed(size: number) {
const seed = string.random(size)
const secret = new Secret(seed)
const hash = createHash('sha256').update(secret.release()).digest('hex')
return { secret, hash }
}
/**
* Identifer is a unique sequence to identify the
* token within database. It should be the
* primary/unique key
*/
declare identifier: string | number | BigInt
/**
* Reference to the user id for whom the token
* is generated.
*/
declare tokenableId: string | number | BigInt
/**
* Hash is computed from the seed to later verify the validity
* of seed
*/
declare hash: string
/**
* Timestamp at which the token will expire
*/
declare expiresAt: Date
/**
* The value is a public representation of a token. It is created
* by combining the "identifier"."secret" via the "computeValue"
* method
*/
declare value?: Secret
/**
* Compute the value property using the given secret. You can
* get secret via the static "createTransientToken" method.
*
* @param secret - The secret value to compute the public token value from
*/
protected computeValue(secret: Secret) {
this.value = new Secret(
`${base64.urlEncode(String(this.identifier))}.${base64.urlEncode(secret.release())}`
)
}
/**
* Check if the token has been expired. Verifies
* the "expiresAt" timestamp with the current
* date.
*/
isExpired() {
return this.expiresAt < new Date()
}
/**
* Verifies the value of a token against the pre-defined hash
*
* @param secret - The secret to verify against the stored hash
*/
verify(secret: Secret): boolean {
const newHash = createHash('sha256').update(secret.release()).digest('hex')
return safeEqual(this.hash, newHash)
}
}
================================================
FILE: src/ignitor/ace.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type Ignitor } from './main.ts'
import type { ApplicationService } from '../types.ts'
/**
* The Ace process is used to start the application in the
* console environment. It manages the Ace kernel lifecycle
* and command execution.
*
* @example
* const ignitor = new Ignitor()
* const aceProcess = new AceProcess(ignitor)
*
* await aceProcess
* .configure((app) => {
* // Configure ace kernel
* })
* .handle(['make:controller', 'UserController'])
*/
export class AceProcess {
/**
* Ignitor reference
*/
#ignitor: Ignitor
/**
* The callback that configures the ace instance before the
* handle method is called
*/
#configureCallback: (app: ApplicationService) => Promise | void = () => {}
/**
* Creates a new Ace process instance
*
* @param ignitor - The ignitor instance used to create and manage the app
*/
constructor(ignitor: Ignitor) {
this.#ignitor = ignitor
}
/**
* Register a callback that can be used to configure the ace
* kernel before the handle method is called
*
* @param callback - Configuration callback function
*/
configure(callback: (app: ApplicationService) => Promise | void): this {
this.#configureCallback = callback
return this
}
/**
* Handles the command line arguments and executes
* the matching ace commands
*
* @param argv - Command line arguments array
*/
async handle(argv: string[]) {
const app = this.#ignitor.createApp('console')
await app.init()
const { createAceKernel } = await import('../../modules/ace/create_kernel.js')
const commandNameIndex = argv.findIndex((value) => !value.startsWith('-'))
const commandName = argv[commandNameIndex]
const kernel = createAceKernel(app, commandName)
app.container.bindValue('ace', kernel)
/**
* Hook into kernel and start the app when the
* command needs the app.
*
* Since multiple commands can be executed in a single process,
* we add a check to only start the app only once.
*/
kernel.loading(async (metaData) => {
if (metaData.options.startApp && !app.isReady) {
if (metaData.commandName === 'repl') {
app.setEnvironment('repl')
}
await app.boot()
await app.start(() => {})
}
})
await this.#configureCallback(app)
/**
* Register terminating callback BEFORE handling the command.
* This ensures the callback is registered even if a staysAlive
* command calls app.terminate() during its execution.
*/
app.terminating(() => {
const mainCommand = kernel.getMainCommand()
if (mainCommand?.staysAlive) {
process.exitCode = mainCommand.exitCode
}
})
/**
* Handle command line args
*/
await kernel.handle(argv)
/**
* Terminate the app when the command does not want to
* hold a long running process
*/
const mainCommand = kernel.getMainCommand()
if (!mainCommand || !mainCommand.staysAlive) {
process.exitCode = kernel.exitCode
await app.terminate()
}
}
}
================================================
FILE: src/ignitor/http.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import type { Server as NodeHttpsServer } from 'node:https'
import {
type IncomingMessage,
type ServerResponse,
type Server as NodeHttpServer,
createServer,
} from 'node:http'
import debug from '../debug.ts'
import { type Ignitor } from './main.ts'
import type { ApplicationService, EmitterService, LoggerService } from '../types.ts'
/**
* The HTTP server process is used to start the application in the
* web environment. It creates and manages the Node.js HTTP server
* instance, handling lifecycle events and monitoring.
*
* @example
* const ignitor = new Ignitor()
* const httpProcess = new HttpServerProcess(ignitor)
* await httpProcess.start()
*/
export class HttpServerProcess {
/**
* Ignitor reference
*/
#ignitor: Ignitor
/**
* Creates a new HTTP server process instance
*
* @param ignitor - The ignitor instance used to create and manage the app
*/
constructor(ignitor: Ignitor) {
this.#ignitor = ignitor
}
/**
* Calling this method closes the underlying HTTP server gracefully.
*
* @param nodeHttpServer - The Node.js HTTP or HTTPS server instance to close
*/
#close(nodeHttpServer: NodeHttpsServer | NodeHttpServer): Promise {
return new Promise((resolve) => {
debug('closing http server process')
nodeHttpServer.close(() => resolve())
})
}
/**
* Monitors the app and the server to close the HTTP server when
* either one of them goes down. Sets up event listeners for graceful shutdown.
*
* @param nodeHttpServer - The Node.js HTTP or HTTPS server instance to monitor
* @param app - The application service instance
* @param logger - The logger service for error reporting
*/
#monitorAppAndServer(
nodeHttpServer: NodeHttpsServer | NodeHttpServer,
app: ApplicationService,
logger: LoggerService
) {
/**
* Close the HTTP server when the application begins to
* terminate
*/
app.terminating(async () => {
debug('terminating signal received')
await this.#close(nodeHttpServer)
})
/**
* Terminate the app when the HTTP server crashes
*/
nodeHttpServer.once('error', (error: NodeJS.ErrnoException) => {
debug('http server crashed with error "%O"', error)
logger.fatal({ err: error }, error.message)
process.exitCode = 1
app.terminate()
})
}
/**
* Starts the HTTP server on a given host and port using environment variables.
*
* @param nodeHttpServer - The Node.js HTTP or HTTPS server instance to start listening
*/
#listen(
nodeHttpServer: NodeHttpsServer | NodeHttpServer
): Promise<{ port: number; host: string }> {
return new Promise((resolve, reject) => {
const host = process.env.HOST || '0.0.0.0'
const port = Number(process.env.PORT || '3333')
nodeHttpServer.listen(port, host)
nodeHttpServer.once('listening', () => {
debug('listening to http server, host :%s, port: %s', host, port)
resolve({ port, host })
})
nodeHttpServer.once('error', (error: NodeJS.ErrnoException) => {
reject(error)
})
})
}
/**
* Notifies the app and the parent process that the HTTP server is ready.
* Sends notifications through multiple channels: parent process, logger, and event emitter.
*
* @param app - The application service instance for parent process notification
* @param logger - The logger service for console output
* @param emitter - The event emitter for app-level notifications
* @param payload - Server startup information including host, port, and duration
*/
#notifyServerHasStarted(
app: ApplicationService,
logger: LoggerService,
emitter: EmitterService,
payload: { host: string; port: number; duration: [number, number] }
) {
/**
* Notify parent process
*/
app.notify({ isAdonisJS: true, environment: 'web', ...payload })
/**
* Visual notification
*/
logger.info('started HTTP server on %s:%s', payload.host, payload.port)
/**
* Notify app
*/
emitter.emit('http:server_ready', payload)
}
/**
* Start the HTTP server by wiring up the application
*
* @param serverCallback - Optional callback to create custom HTTP server instance
*/
async start(
serverCallback?: (
handler: (req: IncomingMessage, res: ServerResponse) => any
) => NodeHttpsServer | NodeHttpServer
) {
const startTime = process.hrtime()
/**
* Method to create the HTTP server
*/
const createHTTPServer = serverCallback || createServer
const app = this.#ignitor.createApp('web')
await app.init()
await app.boot()
await app.start(async () => {
/**
* Resolve and boot the AdonisJS HTTP server
*/
const server = await app.container.make('server')
await server.boot()
/**
* Create Node.js HTTP server instance and share it with the
* AdonisJS HTTP server
*/
const httpServer = createHTTPServer(server.handle.bind(server))
server.setNodeServer(httpServer)
const logger = await app.container.make('logger')
const emitter = await app.container.make('emitter')
/**
* Start the server by listening on a port of host
*/
const payload = await this.#listen(httpServer)
/**
* Notify
*/
this.#notifyServerHasStarted(app, logger, emitter, {
...payload,
duration: process.hrtime(startTime),
})
/**
* Monitor app and the server (after the server is listening)
*/
this.#monitorAppAndServer(httpServer, app, logger)
})
}
}
================================================
FILE: src/ignitor/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import debug from '../debug.ts'
import { AceProcess } from './ace.ts'
import { TestRunnerProcess } from './test.ts'
import { HttpServerProcess } from './http.ts'
import { setApp } from '../../services/app.ts'
import { Application } from '../../modules/app.ts'
import type { AppEnvironments } from '../../types/app.ts'
import type { ApplicationService, IgnitorOptions } from '../types.ts'
/**
* Ignitor is used to instantiate an AdonisJS application in different
* known environments. It serves as the main entry point for creating
* and managing application processes.
*
* @example
* const ignitor = new Ignitor(new URL(import.meta.url))
*
* // For HTTP server
* await ignitor.httpServer().start()
*
* // For CLI commands
* await ignitor.ace().handle(process.argv.slice(2))
*
* // For tests
* await ignitor.testRunner().run(() => {})
*/
export class Ignitor {
/**
* Ignitor options
*/
#options: IgnitorOptions
/**
* Application root URL
*/
#appRoot: URL
/**
* Reference to the application instance created using
* the "createApp" method.
*
* We store the output of the last call made to "createApp" method
* and assume that in one process only one entrypoint will
* call this method.
*/
#app?: ApplicationService
/**
* Reference to the created application
*/
#tapCallbacks: Set<(app: ApplicationService) => void> = new Set()
/**
* Creates a new Ignitor instance
*
* @param appRoot - The root URL of the application
* @param options - Configuration options for the ignitor
*/
constructor(appRoot: URL, options: IgnitorOptions = {}) {
this.#appRoot = appRoot
this.#options = options
}
/**
* Runs all the tap callbacks
*/
#runTapCallbacks(app: ApplicationService) {
this.#tapCallbacks.forEach((tapCallback) => tapCallback(app))
}
/**
* Get access to the application instance created
* by either the http server process or the ace
* process
*/
getApp() {
return this.#app
}
/**
* Create an instance of AdonisJS application
*
* @param environment - The environment in which to create the app (web, console, test, repl)
*/
createApp(environment: AppEnvironments) {
debug('creating application instance')
this.#app = new Application(this.#appRoot, { environment, importer: this.#options.importer })
setApp(this.#app)
this.#runTapCallbacks(this.#app)
return this.#app
}
/**
* Tap to access the application class instance.
*
* @param callback - Callback function to execute when app is created
*/
tap(callback: (app: ApplicationService) => void): this {
this.#tapCallbacks.add(callback)
return this
}
/**
* Get instance of the HTTPServerProcess
*/
httpServer() {
return new HttpServerProcess(this)
}
/**
* Get an instance of the AceProcess class
*/
ace() {
return new AceProcess(this)
}
/**
* Get an instance of the TestRunnerProcess class
*/
testRunner() {
return new TestRunnerProcess(this)
}
/**
* Terminates the app by calling the "app.terminate"
* method
*/
async terminate() {
await this.#app?.terminate()
}
}
================================================
FILE: src/ignitor/test.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { type Ignitor } from './main.ts'
import type { ApplicationService } from '../types.ts'
/**
* The Test runner process is used to start the tests runner process.
* It provides lifecycle hooks for configuring the test environment
* and running tests within the AdonisJS application context.
*
* @example
* const ignitor = new Ignitor()
* const testProcess = new TestRunnerProcess(ignitor)
*
* await testProcess
* .configure((app) => {
* // Configure test environment
* })
* .run(async (app) => {
* // Run your tests
* })
*/
export class TestRunnerProcess {
/**
* Ignitor reference
*/
#ignitor: Ignitor
/**
* The callback that configures the tests runner. This callback
* runs at the time of starting the app.
*/
#configureCallback: (app: ApplicationService) => Promise | void = () => {}
/**
* Creates a new test runner process instance
*
* @param ignitor - The ignitor instance used to create and manage the app
*/
constructor(ignitor: Ignitor) {
this.#ignitor = ignitor
}
/**
* Register a callback that runs after booting the AdonisJS app
* and just before the provider's ready hook
*
* @param callback - Configuration callback function
*/
configure(callback: (app: ApplicationService) => Promise | void): this {
this.#configureCallback = callback
return this
}
/**
* Runs a callback after starting the app
*
* @param callback - Test execution callback function
*/
async run(callback: (app: ApplicationService) => Promise | void) {
const app = this.#ignitor.createApp('test')
await app.init()
await app.boot()
await app.start(this.#configureCallback)
await callback(app)
}
}
================================================
FILE: src/test_utils/http.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import debug from '../debug.ts'
import type { TestUtils } from './main.ts'
import type { Server as NodeHttpsServer } from 'node:https'
import {
type IncomingMessage,
type ServerResponse,
type Server as NodeHttpServer,
createServer,
} from 'node:http'
/**
* Http server utils are used to start the AdonisJS HTTP server
* during testing. It provides methods to start and stop the server
* for integration testing.
*
* @example
* const testUtils = new TestUtils(app)
* const httpUtils = testUtils.httpServer()
*
* const closeServer = await httpUtils.start()
* // Make HTTP requests to test endpoints
* await closeServer() // Clean up
*/
export class HttpServerUtils {
/**
* Reference to the test utils instance
*/
#utils: TestUtils
/**
* Creates a new HttpServerUtils instance
*
* @param utils - The test utils instance
*/
constructor(utils: TestUtils) {
this.#utils = utils
}
/**
* Starts the http server a given host and port
*
* @param nodeHttpServer - The Node.js HTTP server instance
*/
#listen(
nodeHttpServer: NodeHttpsServer | NodeHttpServer
): Promise<{ port: number; host: string }> {
return new Promise((resolve, reject) => {
const host = process.env.HOST || '0.0.0.0'
const port = Number(process.env.PORT || '3333')
nodeHttpServer.listen(port, host)
nodeHttpServer.once('listening', () => {
debug('listening to utils http server, host :%s, port: %s', host, port)
resolve({ port, host })
})
nodeHttpServer.once('error', (error: NodeJS.ErrnoException) => {
reject(error)
})
})
}
/**
* Testing hook to start the HTTP server to listen for new request.
* The return value is a function to close the HTTP server.
*
* @param serverCallback - Optional callback to create custom HTTP server instance
*/
async start(
serverCallback?: (
handler: (req: IncomingMessage, res: ServerResponse) => any
) => NodeHttpsServer | NodeHttpServer
): Promise<() => Promise> {
const createHTTPServer = serverCallback || createServer
const server = await this.#utils.app.container.make('server')
await server.boot()
const httpServer = createHTTPServer(server.handle.bind(server))
server.setNodeServer(httpServer)
await this.#listen(httpServer)
return () => {
return new Promise((resolve, reject) => {
httpServer.close((error) => {
if (error) {
reject(error)
} else {
resolve()
}
})
})
}
}
}
================================================
FILE: src/test_utils/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Socket } from 'node:net'
import Macroable from '@poppinss/macroable'
import { IncomingMessage, ServerResponse } from 'node:http'
import { HttpServerUtils } from './http.ts'
import type { ApplicationService } from '../types.ts'
import { Encryption } from '../../modules/encryption/main.ts'
import { CookieClient, type HttpContext } from '../../modules/http/main.ts'
/**
* Test utils has a collection of helper methods to make testing
* experience great for AdonisJS applications. It provides utilities
* for HTTP testing, context creation, and cookie handling.
*
* @example
* const testUtils = new TestUtils(app)
* await testUtils.boot()
*
* const ctx = await testUtils.createHttpContext()
* const httpUtils = testUtils.httpServer()
*/
export class TestUtils extends Macroable {
/**
* Flag to track if test utils have been booted
*/
#booted: boolean = false
/**
* Check if utils have been booted
*/
get isBooted() {
return this.#booted
}
/**
* Cookie client instance for handling cookies in tests
*/
declare cookies: CookieClient
/**
* Creates a new TestUtils instance
*
* @param app - The application service instance
*/
constructor(public app: ApplicationService) {
super()
}
/**
* Boot test utils. It requires the app to be booted
* and container to have all the bindings
*/
async boot() {
if (!this.isBooted) {
this.#booted = true
this.cookies = new CookieClient(await this.app.container.make(Encryption))
}
}
/**
* Returns an instance of the HTTP server testing
* utils
*/
httpServer() {
return new HttpServerUtils(this)
}
/**
* Create an instance of HTTP context for testing
*
* @param options - Options for creating HTTP context with custom req/res objects
*/
async createHttpContext(
options: { req?: IncomingMessage; res?: ServerResponse } = {}
): Promise {
const req = options.req || new IncomingMessage(new Socket())
const res = options.res || new ServerResponse(req)
const server = await this.app.container.make('server')
const request = server.createRequest(req, res)
const response = server.createResponse(req, res)
return server.createHttpContext(request, response, this.app.container.createResolver())
}
}
================================================
FILE: src/types.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import type { Repl } from '../modules/repl.ts'
import type { Importer } from '../types/app.ts'
import type { Emitter } from '../modules/events.ts'
import type { Kernel } from '../modules/ace/main.ts'
import type { Application } from '../modules/app.ts'
import type { TestUtils } from './test_utils/main.ts'
import type {
HttpServerEvents,
LookupList,
RoutesList,
SignedURLOptions,
UrlFor,
URLOptions,
} from '../types/http.ts'
import type { Dumper } from '../modules/dumper/dumper.ts'
import type { LoggerManager } from '../modules/logger.ts'
import type { HashManager } from '../modules/hash/main.ts'
import type { ManagerDriverFactory } from '../types/hash.ts'
import type { Router, Server } from '../modules/http/main.ts'
import { type EncryptionConfig } from '../types/encryption.ts'
import type { EncryptionManager } from '../modules/encryption/main.ts'
import type { ContainerResolveEventData } from '../types/container.ts'
import type { LoggerConfig, LoggerManagerConfig } from '../types/logger.ts'
/**
* A config provider waits for the application to get booted
* and then resolves the config. It receives an instance
* of the application service.
*
* @template T - The type of configuration object that the provider resolves
*
* @example
* const databaseProvider: ConfigProvider = {
* type: 'provider',
* resolver: async (app) => {
* return {
* connection: app.env.get('DB_CONNECTION', 'sqlite'),
* host: app.env.get('DB_HOST', 'localhost')
* }
* }
* }
*/
export type ConfigProvider = {
/** Identifies this as a config provider */
type: 'provider'
/** Function that resolves the configuration using the application service */
resolver: (app: ApplicationService) => Promise
}
/**
* Options accepted by ignitor for configuring the application bootstrap process.
*
* @example
* const options: IgnitorOptions = {
* importer: (filePath) => import(filePath)
* }
*/
export type IgnitorOptions = {
/** Optional custom importer function for loading modules */
importer?: Importer
}
/**
* A list of known events. The interface must be extended in
* user land code or packages to register events and their
* types.
*
* @example
* // Extending EventsList in user code
* declare module '@adonisjs/core' {
* interface EventsList {
* 'user:created': { user: User }
* 'order:placed': { orderId: string, amount: number }
* }
* }
*/
export interface EventsList extends HttpServerEvents {
/** Event fired when a container binding is resolved */
'container_binding:resolved': ContainerResolveEventData
/** Event fired when the HTTP server is ready and listening */
'http:server_ready': { port: number; host: string; duration: [number, number] }
}
/**
* The loggers list inferred from the user application
* config. This interface should be extended in user code
* to register custom loggers.
*
* @example
* // Extending LoggersList in user code
* declare module '@adonisjs/core' {
* interface LoggersList {
* default: LoggerConfig
* file: LoggerConfig
* }
* }
*/
export interface LoggersList {}
/**
* Utility type to infer logger configurations from a LoggerManagerConfig.
*
* @template T - The logger manager configuration type
*/
export type InferLoggers> = T['loggers']
/**
* A list of known hashers inferred from the user config.
* This interface should be extended in user code to register
* custom hashers.
*
* @example
* // Extending HashersList in user code
* declare module '@adonisjs/core' {
* interface HashersList {
* scrypt: ManagerDriverFactory
* argon: ManagerDriverFactory
* }
* }
*/
export interface HashersList {}
/**
* Utility type to infer hasher configurations from a config provider.
*
* @template T - The config provider type that resolves to an object with a 'list' property
*/
export type InferHashers }>> =
Awaited>['list']
/**
* A list of known encryptors inferred from the user config.
* This interface should be extended in user code to register
* custom encryptors.
*
* @example
* // Extending EncryptorsList in user code
* declare module '@adonisjs/core' {
* interface EncryptorsList {
* default: EncryptionConfig
* secondary: EncryptionConfig
* }
* }
*/
export interface EncryptorsList {}
/**
* Utility type to infer encryptors configurations from a config provider.
*
* @template T - The config provider type that resolves to an object with a 'list' property
*/
export type InferEncryptors<
T extends ConfigProvider<{
list: Record
}>,
> = Awaited>['list']
/**
* ----------------------------------------------------------------
* Container services
* -----------------------------------------------------------------
*
* Types for the container singleton services. Defining them
* upfront so that we do not have to define them in
* multiple places.
*/
/**
* Application service is a singleton resolved from
* the container. It provides access to the core application
* instance with all registered bindings.
*/
export interface ApplicationService extends Application<
ContainerBindings extends Record ? ContainerBindings : never
> {}
/**
* Logger service is a singleton logger instance registered
* to the container. It provides access to configured loggers.
*/
export interface LoggerService extends LoggerManager<
LoggersList extends Record ? LoggersList : never
> {}
/**
* Emitter service is a singleton emitter instance registered
* to the container. It provides type-safe event emission and listening.
*/
export interface EmitterService extends Emitter {}
/**
* Encryption service is a singleton instance of the EncryptionManager
* registered in the container. It provides encryption and decryption
* functionality with support for multiple encryptors.
*/
export interface EncryptionService extends EncryptionManager<
EncryptorsList extends Record ? EncryptorsList : never
> {}
/**
* Http server service added to the container as a singleton.
* It provides access to the HTTP server instance for handling
* requests and responses.
*/
export interface HttpServerService extends Server {}
/**
* Http router service added to the container as a singleton.
* It provides access to the application's router for defining
* and managing routes.
*/
export interface HttpRouterService extends Router {}
/**
* Url builder service offers a type-safe API for creating URLs
* for pre-registered routes. It ensures type safety when building
* URLs with parameters.
*/
export interface UrlBuilderUrlFor extends UrlFor<
RoutesList extends LookupList ? RoutesList : never,
URLOptions
> {}
/**
* Url builder service offers a type-safe API for creating signed URLs
* for pre-registered routes. Signed URLs include a signature that prevents
* tampering and can have expiration times.
*/
export interface UrlBuilderSignedUrlFor extends UrlFor<
RoutesList extends LookupList ? RoutesList : never,
SignedURLOptions
> {}
/**
* Hash service is a singleton instance of the HashManager
* registered in the container. It provides password hashing
* and verification functionality.
*/
export interface HashService extends HashManager<
HashersList extends Record ? HashersList : never
> {}
/**
* A list of known container bindings. This interface defines
* all the services that are registered in the IoC container
* and available for dependency injection.
*/
export interface ContainerBindings {
/** Ace command-line kernel */
ace: Kernel
/** Database query dumper */
dumper: Dumper
/** Main application instance */
app: ApplicationService
/** Logger manager instance */
logger: LoggerService
/** Application configuration */
config: ApplicationService['config']
/** Event emitter instance */
emitter: EmitterService
/** Encryption service */
encryption: EncryptionService
/** Hash manager for password hashing */
hash: HashService
/** HTTP server instance */
server: HttpServerService
/** HTTP router instance */
router: HttpRouterService
/** Test utilities */
testUtils: TestUtils
/** REPL instance */
repl: Repl
}
/**
* Configuration options for the IndexEntities assembler hook.
* This type defines the settings for automatically generating
* barrel files for controllers, listeners, and events.
*
* @example
* // Basic configuration
* const config: IndexEntitiesConfig = {
* controllers: { enabled: true },
* events: { source: 'app/custom-events' }
* }
*
* @example
* // Detailed configuration with custom paths
* const config: IndexEntitiesConfig = {
* controllers: {
* enabled: true,
* source: 'app/http/controllers',
* importAlias: '#controllers',
* glob: ['**\/*_controller.ts']
* },
* listeners: {
* enabled: false
* }
* }
*/
export type IndexEntitiesConfig = {
/** Configuration for controllers indexing */
controllers?: {
/** Whether to enable controllers indexing */
enabled?: boolean
/** Source directory for controllers */
source?: string
/** Import alias for controllers */
importAlias?: string
/** Glob patterns for matching controller files */
glob?: string[]
/** Path segments to skip from generated keys. Defaults to ['controllers'] */
skipSegments?: string[]
}
/** Configuration for listeners indexing */
listeners?: {
/** Whether to enable listeners indexing */
enabled?: boolean
/** Source directory for listeners */
source?: string
/** Import alias for listeners */
importAlias?: string
/** Glob patterns for matching listener files */
glob?: string[]
/** Path segments to skip from generated keys. Defaults to ['listeners'] */
skipSegments?: string[]
}
/** Configuration for events indexing */
events?: {
/** Whether to enable events indexing */
enabled?: boolean
/** Source directory for events */
source?: string
/** Import alias for events */
importAlias?: string
/** Glob patterns for matching event files */
glob?: string[]
/** Path segments to skip from generated keys. Defaults to ['events'] */
skipSegments?: string[]
}
/** Configuration for transformers indexing */
transformers?: {
/** Whether to enable transformers indexing */
enabled?: boolean
/** Whether to include shared props in transformers */
withSharedProps?: boolean
inertiaMiddlewareImportPath?: string
/** Source directory for transformers */
source?: string
/** Import alias for transformers */
importAlias?: string
/** Glob patterns for matching transformer files */
glob?: string[]
/** Path segments to skip from generated keys. Defaults to ['transformers'] */
skipSegments?: string[]
}
/** Configuration for manifest generation */
manifest?: {
/** Whether to enable manifest generation */
enabled?: boolean
exclude?: string[]
}
}
================================================
FILE: src/utils.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import type typescript from 'typescript'
import type * as Assembler from '@adonisjs/assembler'
import { type RecursiveFileTree } from '@adonisjs/assembler/types'
import { type ApplicationService } from './types.ts'
/**
* Imports the AdonisJS assembler package optionally. This function attempts
* to import the assembler and returns undefined if it's not available,
* making it safe to use in environments where the assembler might not be installed.
*
* @param app - The application service instance used for importing the assembler
*
* @example
* const assembler = await importAssembler(app)
* if (assembler) {
* // Use assembler functionality
* const generator = new assembler.IndexGenerator()
* }
*/
export async function importAssembler(
app: ApplicationService
): Promise {
try {
return await app.import('@adonisjs/assembler')
} catch {}
}
/**
* Imports the TypeScript compiler package optionally. This function attempts
* to import TypeScript and returns undefined if it's not available,
* making it safe to use in environments where TypeScript might not be installed.
*
* @param app - The application service instance used for importing TypeScript
*
* @example
* const ts = await importTypeScript(app)
* if (ts) {
* // Use TypeScript compiler API
* const program = ts.createProgram(['file.ts'], {})
* const sourceFile = program.getSourceFile('file.ts')
* }
*/
export async function importTypeScript(
app: ApplicationService
): Promise {
try {
return await app.importDefault('typescript')
} catch {}
}
/**
* Outputs transformer data objects by generating TypeScript type definitions
* for all transformers in the provided file tree. This function creates
* InferData types for each transformer and organizes them in namespaces.
*
* @param transformersList - A recursive file tree containing transformer file paths
* @param buffer - The file buffer to write the generated types to
*
* @example
* const transformersList = {
* User: '#app/transformers/user_transformer',
* Auth: {
* Login: '#app/transformers/auth/login_transformer'
* }
* }
* await outputTransformerDataObjects(transformersList, buffer)
* // Generates:
* // export namespace Data {
* // export type User = InferData
* // export namespace Auth {
* // export type Login = InferData
* // }
* // }
*/
export async function outputTransformerDataObjects(
transformersList: RecursiveFileTree,
buffer: Assembler.FileBuffer,
withSharedProps: boolean,
inertiaMiddlewareImportPath: string = '#middleware/inertia_middleware'
) {
const importsBuffer = buffer.create()
importsBuffer.write(`/// `)
importsBuffer.write(
`import type { InferData, InferVariants } from '@adonisjs/core/types/transformers'`
)
if (withSharedProps) {
importsBuffer.write(`import type { InferSharedProps } from '@adonisjs/inertia/types'`)
}
buffer.writeLine(importsBuffer)
buffer.write('export namespace Data {').indent()
/**
* Recursively generates namespace tree structure for transformers.
* Creates nested namespaces for directory structures and type exports
* for individual transformer files.
*
* @param input - The current level of the file tree to process
* @param parents - Array of parent namespace names for import naming
*/
function generateNamespaceTree(input: RecursiveFileTree, parents: string[]) {
Object.keys(input).forEach((key) => {
const value = input[key]
if (typeof value === 'string') {
const importName = `${parents.join('')}${key}Transformer`
importsBuffer.write(`import type ${importName} from '${value}'`)
buffer.write(`export type ${key} = InferData<${importName}>`)
buffer.write(`export namespace ${key} {`).indent()
buffer.write(`export type Variants = InferVariants<${importName}>`)
buffer.dedent().write('}')
} else {
buffer.write(`export namespace ${key} {`).indent()
generateNamespaceTree(value, [...parents, key])
buffer.dedent().write(`}`)
}
})
}
generateNamespaceTree(transformersList, [])
if (withSharedProps) {
importsBuffer.write(`import type InertiaMiddleware from '${inertiaMiddlewareImportPath}'`)
buffer.write('export type SharedProps = InferSharedProps')
}
buffer.dedent().write('}')
}
================================================
FILE: src/vine.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import vine, { symbols, BaseLiteralType } from '@vinejs/vine'
import type { Validation, FieldContext, FieldOptions } from '@vinejs/vine/types'
import type { MultipartFile, FileValidationOptions } from '@adonisjs/bodyparser/types'
const MULTIPART_FILE: typeof symbols.SUBTYPE = symbols.SUBTYPE ?? Symbol.for('subtype')
/**
* Validation options accepted by the "file" rule
*/
export type FileRuleValidationOptions =
| Partial
| ((field: FieldContext) => Partial)
/**
* Checks if the value is an instance of multipart file from bodyparser.
* Used internally for type guarding in file validation.
*
* @param file - The value to check for MultipartFile instance
*/
function isBodyParserFile(file: unknown): file is MultipartFile {
return !!(file && typeof file === 'object' && 'isMultipartFile' in file)
}
/**
* VineJS validation rule that validates the file to be an instance of BodyParser
* MultipartFile class and applies size/extension validation if configured.
*
* @param file - The file value to validate
* @param options - Validation options for file size and extensions
* @param field - The field context from VineJS validation
*/
const isMultipartFile = vine.createRule((file, options, field) => {
if (!field.isDefined) {
return false
}
/**
* Report error when value is not a field multipart
* file object
*/
if (!isBodyParserFile(file)) {
field.report('The {{ field }} must be a file', 'file', field)
return false
}
const validationOptions = typeof options === 'function' ? options(field) : options
/**
* Set size when it's defined in the options and missing
* on the file instance
*/
if (file.sizeLimit === undefined && validationOptions.size) {
file.sizeLimit = validationOptions.size
}
/**
* Set extensions when it's defined in the options and missing
* on the file instance
*/
if (file.allowedExtensions === undefined && validationOptions.extnames) {
file.allowedExtensions = validationOptions.extnames
}
/**
* Validate file
*/
file.validate()
/**
* Report errors
*/
file.errors.forEach((error) => {
field.report(error.message, `file.${error.type}`, field, validationOptions)
})
return file.isValid
})
/**
* Represents a multipart file uploaded via multipart/form-data HTTP
* request. This class extends VineJS's BaseLiteralType to provide
* specialized validation for uploaded files.
*
* @example
* const fileSchema = vine.object({
* avatar: vine.file({
* size: '2mb',
* extnames: ['jpg', 'png']
* })
* })
*/
export class VineMultipartFile extends BaseLiteralType<
MultipartFile,
MultipartFile,
MultipartFile
> {
/**
* Private validation options for file validation
*/
#validationOptions?: FileRuleValidationOptions;
/**
* Symbol identifier for multipart file subtype
*/
[MULTIPART_FILE] = 'multipartFile'
/**
* Creates a new VineMultipartFile instance
*
* @param validationOptions - File validation options like size limits and allowed extensions
* @param options - Field options from VineJS
* @param validations - Array of validation functions to apply
*/
constructor(
validationOptions?: FileRuleValidationOptions,
options?: FieldOptions,
validations?: Validation[]
) {
super(options, validations || [])
this.#validationOptions = validationOptions
this.dataTypeValidator = isMultipartFile(validationOptions || {})
}
/**
* Creates a clone of the current VineMultipartFile instance
* with the same validation options and configurations
*/
clone() {
return new VineMultipartFile(
this.#validationOptions,
this.cloneOptions(),
this.cloneValidations()
) as this
}
}
================================================
FILE: stubs/main.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
export const stubsRoot = import.meta.dirname
================================================
FILE: stubs/make/command/main.stub
================================================
{{#var commandName = generators.commandName(entity.name)}}
{{#var commandTerminalName = generators.commandTerminalName(entity.name)}}
{{#var commandFileName = generators.commandFileName(entity.name)}}
{{{
exports({
to: app.commandsPath(entity.path, commandFileName)
})
}}}
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
export default class {{ commandName }} extends BaseCommand {
static commandName = '{{ commandTerminalName }}'
static description = ''
static options: CommandOptions = {}
async run() {
this.logger.info('Hello world from "{{ commandName }}"')
}
}
================================================
FILE: stubs/make/controller/actions.stub
================================================
{{#var controllerName = generators.controllerName(entity.name, singular)}}
{{#var controllerFileName = generators.controllerFileName(entity.name, singular)}}
{{{
exports({
to: app.httpControllersPath(entity.path, controllerFileName)
})
}}}
import type { HttpContext } from '@adonisjs/core/http'
export default class {{ controllerName }} {
{{#each actions as action}}
async {{action}}({}: HttpContext) {}
{{/each}}
}
================================================
FILE: stubs/make/controller/api.stub
================================================
{{#var controllerName = generators.controllerName(entity.name, singular)}}
{{#var controllerFileName = generators.controllerFileName(entity.name, singular)}}
{{{
exports({
to: app.httpControllersPath(entity.path, controllerFileName)
})
}}}
import type { HttpContext } from '@adonisjs/core/http'
export default class {{ controllerName }} {
/**
* Display a list of resource
*/
async index({}: HttpContext) {}
/**
* Handle form submission for the create action
*/
async store({ request }: HttpContext) {}
/**
* Show individual record
*/
async show({ params }: HttpContext) {}
/**
* Handle form submission for the edit action
*/
async update({ params, request }: HttpContext) {}
/**
* Delete record
*/
async destroy({ params }: HttpContext) {}
}
================================================
FILE: stubs/make/controller/main.stub
================================================
{{#var controllerName = generators.controllerName(entity.name, singular)}}
{{#var controllerFileName = generators.controllerFileName(entity.name, singular)}}
{{{
exports({
to: app.httpControllersPath(entity.path, controllerFileName)
})
}}}
// import type { HttpContext } from '@adonisjs/core/http'
export default class {{ controllerName }} {
}
================================================
FILE: stubs/make/controller/resource.stub
================================================
{{#var controllerName = generators.controllerName(entity.name, singular)}}
{{#var controllerFileName = generators.controllerFileName(entity.name, singular)}}
{{{
exports({
to: app.httpControllersPath(entity.path, controllerFileName)
})
}}}
import type { HttpContext } from '@adonisjs/core/http'
export default class {{ controllerName }} {
/**
* Display a list of resource
*/
async index({}: HttpContext) {}
/**
* Display form to create a new record
*/
async create({}: HttpContext) {}
/**
* Handle form submission for the create action
*/
async store({ request }: HttpContext) {}
/**
* Show individual record
*/
async show({ params }: HttpContext) {}
/**
* Edit individual record
*/
async edit({ params }: HttpContext) {}
/**
* Handle form submission for the edit action
*/
async update({ params, request }: HttpContext) {}
/**
* Delete record
*/
async destroy({ params }: HttpContext) {}
}
================================================
FILE: stubs/make/event/main.stub
================================================
{{#var eventName = generators.eventName(entity.name)}}
{{#var eventFileName = generators.eventFileName(entity.name)}}
{{{
exports({
to: app.eventsPath(entity.path, eventFileName)
})
}}}
import { BaseEvent } from '@adonisjs/core/events'
export default class {{ eventName }} extends BaseEvent {
/**
* Accept event data as constructor parameters
*/
constructor() {
super()
}
}
================================================
FILE: stubs/make/exception/main.stub
================================================
{{#var exceptionName = generators.exceptionName(entity.name)}}
{{#var exceptionFileName = generators.exceptionFileName(entity.name)}}
{{{
exports({
to: app.exceptionsPath(entity.path, exceptionFileName)
})
}}}
import { Exception } from '@adonisjs/core/exceptions'
export default class {{ exceptionName }} extends Exception {
static status = 500
}
================================================
FILE: stubs/make/health/controller.stub
================================================
{{#var controllerName = generators.controllerName(entity.name, false)}}
{{#var controllerFileName = generators.controllerFileName(entity.name, false)}}
{{{
exports({
to: app.httpControllersPath(entity.path, controllerFileName)
})
}}}
import { healthChecks } from '#start/health'
import type { HttpContext } from '@adonisjs/core/http'
export default class {{ controllerName }} {
async live({ response }: HttpContext) {
return response.ok()
}
async ready({ response }: HttpContext) {
const report = await healthChecks.run()
if (report.isHealthy) {
return response.ok(report)
}
return response.serviceUnavailable(report)
}
}
================================================
FILE: stubs/make/health/main.stub
================================================
{{#var preloadFileName = string(entity.name).snakeCase().removeExtension().ext('.ts').toString()}}
{{{
exports({
to: app.startPath(entity.path, preloadFileName)
})
}}}
import { HealthChecks, DiskSpaceCheck, MemoryHeapCheck } from '@adonisjs/core/health'
export const healthChecks = new HealthChecks().register([
new DiskSpaceCheck(),
new MemoryHeapCheck(),
])
================================================
FILE: stubs/make/listener/for_event.stub
================================================
{{#var listenerName = generators.listenerName(entity.name)}}
{{#var listenerFileName = generators.listenerFileName(entity.name)}}
{{#var eventName = generators.eventName(event.name)}}
{{#var eventFileName = generators.eventFileName(event.name)}}
{{#var eventImportPath = generators.importPath('#events', event.path, eventFileName)}}
{{{
exports({
to: app.listenersPath(entity.path, listenerFileName)
})
}}}
import type {{ eventName }} from '{{ eventImportPath }}'
export default class {{ listenerName }} {
async handle(event: {{ eventName }}) {}
}
================================================
FILE: stubs/make/listener/main.stub
================================================
{{#var listenerName = generators.listenerName(entity.name)}}
{{#var listenerFileName = generators.listenerFileName(entity.name)}}
{{{
exports({
to: app.listenersPath(entity.path, listenerFileName)
})
}}}
export default class {{ listenerName }} {
}
================================================
FILE: stubs/make/middleware/main.stub
================================================
{{#var middlewareName = generators.middlewareName(entity.name)}}
{{#var middlewareFileName = generators.middlewareFileName(entity.name)}}
{{{
exports({
to: app.middlewarePath(entity.path, middlewareFileName)
})
}}}
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class {{ middlewareName }} {
async handle(ctx: HttpContext, next: NextFn) {
/**
* Middleware logic goes here (before the next call)
*/
console.log(ctx)
/**
* Call next method in the pipeline and return its output
*/
const output = await next()
return output
}
}
================================================
FILE: stubs/make/preload/main.stub
================================================
{{#var preloadFileName = string(entity.name).snakeCase().removeExtension().ext('.ts').toString()}}
{{{
exports({
to: app.startPath(entity.path, preloadFileName)
})
}}}
================================================
FILE: stubs/make/provider/main.stub
================================================
{{#var providerName = generators.providerName(entity.name)}}
{{#var providerFileName = generators.providerFileName(entity.name)}}
{{{
exports({
to: app.providersPath(entity.path, providerFileName)
})
}}}
import type { ApplicationService } from '@adonisjs/core/types'
export default class {{ providerName }} {
constructor(protected app: ApplicationService) {}
/**
* Register bindings to the container
*/
register() {}
/**
* The container bindings have booted
*/
async boot() {}
/**
* The application has been booted
*/
async start() {}
/**
* The process has been started
*/
async ready() {}
/**
* Preparing to shutdown the app
*/
async shutdown() {}
}
================================================
FILE: stubs/make/service/main.stub
================================================
{{#var serviceName = generators.serviceName(entity.name)}}
{{#var serviceFileName = generators.serviceFileName(entity.name)}}
{{{
exports({
to: app.servicesPath(entity.path, serviceFileName)
})
}}}
export class {{ serviceName }} {
// Your code here
}
================================================
FILE: stubs/make/test/main.stub
================================================
{{#var testGroupName = generators.testGroupName(entity)}}
{{#var testFileName = generators.testFileName(entity.name)}}
{{{
exports({
to: app.makePath(suite.directory, entity.path, testFileName)
})
}}}
import { test } from '@japa/runner'
test.group('{{ testGroupName }}', () => {
test('example test', async ({ assert }) => {
})
})
================================================
FILE: stubs/make/transformer/main.stub
================================================
{{#var transformerName = generators.transformerName(entity.name)}}
{{#var transformerFileName = generators.transformerFileName(entity.name)}}
{{#var modelName = generators.modelName(model.name)}}
{{#var modelFileName = generators.modelFileName(model.name)}}
{{#var modelImportPath = generators.importPath('#models', model.path, modelFileName.replace(/\.ts$/, ''))}}
{{{
exports({
to: app.transformersPath(entity.path, transformerFileName)
})
}}}
import { BaseTransformer } from '@adonisjs/core/transformers'
import {{ modelName }} from '{{ modelImportPath }}'
export default class {{ transformerName }} extends BaseTransformer<{{modelName}}> {
toObject() {
return this.pick(this.resource, ['id'])
}
}
================================================
FILE: stubs/make/validator/main.stub
================================================
{{#var validatorFileName = generators.validatorFileName(entity.name)}}
{{{
exports({
to: app.validatorsPath(entity.path, validatorFileName)
})
}}}
import vine from '@vinejs/vine'
================================================
FILE: stubs/make/validator/resource.stub
================================================
{{#var validatorName = string(generators.validatorName(entity.name)).noCase()}}
{{#var validatorFileName = generators.validatorFileName(entity.name)}}
{{#var createAction = generators.validatorActionName(entity.name, 'create')}}
{{#var updateAction = generators.validatorActionName(entity.name, 'update')}}
{{{
exports({
to: app.validatorsPath(entity.path, validatorFileName)
})
}}}
import vine from '@vinejs/vine'
/**
* Validator to validate the payload when creating
* a new {{ validatorName }}.
*/
export const {{ createAction }} = vine.compile(
vine.object({})
)
/**
* Validator to validate the payload when updating
* an existing {{ validatorName }}.
*/
export const {{ updateAction }} = vine.compile(
vine.object({})
)
================================================
FILE: stubs/make/view/main.stub
================================================
{{#var viewFileName = generators.viewFileName(entity.name)}}
{{{
exports({
to: app.viewsPath(entity.path, viewFileName)
})
}}}
================================================
FILE: tests/ace/base_command.spec.ts
================================================
/*
* @adonisjs/ace
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import sinon from 'sinon'
import { test } from '@japa/runner'
import { BaseCommand } from '../../modules/ace/main.ts'
import { ListCommand } from '../../modules/ace/commands.ts'
import { IgnitorFactory } from '../../factories/core/ignitor.ts'
import { createAceKernel } from '../../modules/ace/create_kernel.ts'
const BASE_URL = new URL('./tmp/', import.meta.url)
test.group('Base command', () => {
test('infer staysAlive and startApp flags from command options', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static options = {
startApp: true,
staysAlive: true,
}
}
const kernel = createAceKernel(app)
const command = await kernel.create(MakeController, [])
const listCommand = await kernel.create(ListCommand, [])
assert.isTrue(command.startApp)
assert.isTrue(command.staysAlive)
assert.isUndefined(listCommand.startApp)
assert.isUndefined(listCommand.staysAlive)
})
test('execute command template methods', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static commandName: string = 'make:controller'
stack: string[] = []
async prepare() {
this.stack.push('prepare')
}
async interact() {
this.stack.push('interact')
}
async run() {
this.stack.push('run')
}
async completed() {
this.stack.push('completed')
}
}
const kernel = createAceKernel(app)
const command = await kernel.create(MakeController, [])
await command.exec()
assert.deepEqual(command.stack, ['prepare', 'interact', 'run', 'completed'])
})
test('do not run template methods when do not exists', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static commandName: string = 'make:controller'
stack: string[] = []
async run() {
this.stack.push('run')
}
}
const kernel = createAceKernel(app)
const command = await kernel.create(MakeController, [])
await command.exec()
assert.deepEqual(command.stack, ['run'])
})
test('fail when prepare method raises exception', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static commandName: string = 'make:controller'
async prepare() {
throw new Error('prepare failed')
}
}
const kernel = createAceKernel(app)
kernel.ui.switchMode('raw')
kernel.errorHandler.render = async function (error: Error) {
command.logger.fatal(error)
}
const command = await kernel.create(MakeController, [])
await command.exec()
assert.equal(command.error.message, 'prepare failed')
assert.equal(command.exitCode, 1)
assert.lengthOf(command.logger.getLogs(), 1)
assert.equal(command.logger.getLogs()[0].stream, 'stderr')
})
test('fail when interact method raises exception', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static commandName: string = 'make:controller'
async interact() {
throw new Error('interact failed')
}
}
const kernel = createAceKernel(app)
kernel.ui.switchMode('raw')
kernel.errorHandler.render = async function (error: Error) {
command.logger.fatal(error)
}
const command = await kernel.create(MakeController, [])
await command.exec()
assert.equal(command.error.message, 'interact failed')
assert.equal(command.exitCode, 1)
assert.lengthOf(command.logger.getLogs(), 1)
assert.equal(command.logger.getLogs()[0].stream, 'stderr')
})
test('fail when run method raises exception', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static commandName: string = 'make:controller'
async run() {
throw new Error('run failed')
}
}
const kernel = createAceKernel(app)
kernel.ui.switchMode('raw')
kernel.errorHandler.render = async function (error: Error) {
command.logger.fatal(error)
}
const command = await kernel.create(MakeController, [])
await command.exec()
assert.equal(command.error.message, 'run failed')
assert.equal(command.exitCode, 1)
assert.lengthOf(command.logger.getLogs(), 1)
assert.equal(command.logger.getLogs()[0].stream, 'stderr')
})
test('do not print errors when completed method handles exception', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static commandName: string = 'make:controller'
async run() {
throw new Error('run failed')
}
async completed() {
return true
}
}
const kernel = createAceKernel(app)
kernel.ui.switchMode('raw')
const command = await kernel.create(MakeController, [])
await command.exec()
assert.equal(command.error.message, 'run failed')
assert.equal(command.exitCode, 1)
assert.lengthOf(command.logger.getLogs(), 0)
})
test('print error when completed method does not handles exception', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static commandName: string = 'make:controller'
async run() {
throw new Error('run failed')
}
async completed() {
return false
}
}
const kernel = createAceKernel(app)
kernel.ui.switchMode('raw')
kernel.errorHandler.render = async function (error: Error) {
command.logger.fatal(error)
}
const command = await kernel.create(MakeController, [])
await command.exec()
assert.equal(command.error.message, 'run failed')
assert.equal(command.exitCode, 1)
assert.lengthOf(command.logger.getLogs(), 1)
assert.equal(command.logger.getLogs()[0].stream, 'stderr')
})
test('throw exception when completed method raises exception', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static commandName: string = 'make:controller'
async completed() {
throw new Error('completed failed')
}
}
const kernel = createAceKernel(app)
const command = await kernel.create(MakeController, [])
await assert.rejects(() => command.exec(), 'completed failed')
})
test('call app terminate when main command terminate method is called', async () => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
class MakeController extends BaseCommand {
static options = {
startApp: true,
staysAlive: true,
}
}
const kernel = createAceKernel(app)
const command = await kernel.create(MakeController, [])
const listCommand = await kernel.create(ListCommand, [])
const appMock = sinon.mock(app)
appMock.expects('terminate').twice()
kernel.getMainCommand = () => command
await command.terminate()
kernel.getMainCommand = () => listCommand
await listCommand.terminate()
appMock.verify()
})
})
================================================
FILE: tests/ace/codemods.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import { Codemods } from '../../modules/ace/codemods.ts'
import { AceFactory } from '../../factories/core/ace.ts'
test.group('Codemods', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('get ts morph project', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
const codemods = new Codemods(ace.app, ace.ui.logger)
const project = await codemods.getTsMorphProject()
assert.exists(project)
})
test('reuse the same CodeTransformer instance', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
const codemods = new Codemods(ace.app, ace.ui.logger)
const project1 = await codemods.getTsMorphProject()
const project2 = await codemods.getTsMorphProject()
assert.deepEqual(project1, project2)
})
})
test.group('Codemods | environment variables', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('define env variables', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
/**
* Creating .env file so that we can update it.
*/
await fs.create('.env', '')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.defineEnvVariables({ CORS_MODE: 'strict', CORS_ENABLED: true })
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update .env file',
stream: 'stdout',
},
])
await assert.fileContains('.env', 'CORS_MODE=strict')
await assert.fileContains('.env', 'CORS_ENABLED=true')
})
test('do not insert env value in .env.example if specified', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
/**
* Creating .env file so that we can update it.
*/
await fs.create('.env', '')
await fs.create('.env.example', '')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.defineEnvVariables(
{ SECRET_VALUE: 'secret' },
{ omitFromExample: ['SECRET_VALUE'] }
)
await assert.fileContains('.env', 'SECRET_VALUE=secret')
await assert.fileContains('.env.example', 'SECRET_VALUE=')
})
test('do not define env variables when file does not exists', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.defineEnvVariables({ CORS_MODE: 'strict', CORS_ENABLED: true })
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update .env file',
stream: 'stdout',
},
])
await assert.fileNotExists('.env')
})
test('define env variables validations', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
/**
* Creating .env file so that we can update it.
*/
await fs.create('start/env.ts', `export default Env.create(new URL('./'), {})`)
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.defineEnvValidations({
variables: {
CORS_MODE: 'Env.schema.string()',
},
})
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update start/env.ts file',
stream: 'stdout',
},
])
await assert.fileContains('start/env.ts', 'CORS_MODE: Env.schema.string()')
})
})
test.group('Codemods | rcFile', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('update rcfile', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', 'export default defineConfig({})')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider('@adonisjs/core')
rcFile.addCommand('@adonisjs/core/commands')
})
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update adonisrc.ts file',
stream: 'stdout',
},
])
await assert.fileContains('adonisrc.ts', '@adonisjs/core')
await assert.fileContains('adonisrc.ts', '@adonisjs/core/commands')
})
})
test.group('Codemods | registerMiddleware', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('register middleware', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('start/kernel.ts', 'router.use([])')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.registerMiddleware('router', [{ path: '@adonisjs/core/bodyparser_middleware' }])
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update start/kernel.ts file',
stream: 'stdout',
},
])
await assert.fileContains('start/kernel.ts', '@adonisjs/core/bodyparser_middleware')
})
})
test.group('Codemods | registerPolicies', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('register bouncer policies', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('app/policies/main.ts', 'export const policies = {}')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.registerPolicies([{ name: 'PostPolicy', path: '#policies/post_policy' }])
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update app/policies/main.ts file',
stream: 'stdout',
},
])
await assert.fileContains('app/policies/main.ts', '#policies/post_policy')
})
})
test.group('Codemods | registerVitePlugin', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('register vite plugin', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.createJson('package.json', {})
await fs.create('vite.config.ts', 'export default { plugins: [] }')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.registerVitePlugin('vue()', [
{ identifier: 'vue', module: '@vitejs/plugin-vue', isNamed: false },
])
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update vite.config.ts file',
stream: 'stdout',
},
])
await assert.fileContains('vite.config.ts', 'vue()')
})
})
test.group('Codemods | registerJapaPlugin', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('register japa plugin', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.createJson('package.json', {})
await fs.create('tests/bootstrap.ts', 'export const plugins = []')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.registerJapaPlugin('apiClient()', [
{ identifier: 'apiClient', module: '@japa/api-client', isNamed: true },
])
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update tests/bootstrap.ts file',
stream: 'stdout',
},
])
await assert.fileContains('tests/bootstrap.ts', 'apiClient()')
})
})
test.group('Codemods | install packages', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('install packages', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
await fs.createJson('tsconfig.json', {})
await fs.createJson('package.json', {})
await fs.create('app/policies/main.ts', 'export const policies = {}')
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.installPackages([{ name: '@adonisjs/assembler', isDevDependency: true }])
await assert.dirExists('node_modules/@adonisjs/assembler')
})
test('install packages in verbose mode', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
await fs.createJson('tsconfig.json', {})
await fs.createJson('package.json', {})
await fs.create('app/policies/main.ts', 'export const policies = {}')
const codemods = new Codemods(ace.app, ace.ui.logger)
codemods.verboseInstallOutput = true
await codemods.installPackages([{ name: '@adonisjs/assembler', isDevDependency: true }])
await assert.dirExists('node_modules/@adonisjs/assembler')
})
})
test.group('Codemods | addValidator', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('add validator file', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.addValidator({
validatorFileName: 'create_user.ts',
exportName: 'createUserValidator',
contents: 'export const createUserValidator = vine.compile(vine.object({}))',
})
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create validator file',
stream: 'stdout',
},
])
await assert.fileExists('app/validators/create_user.ts')
await assert.fileContains('app/validators/create_user.ts', 'createUserValidator')
})
})
test.group('Codemods | addLimiter', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('add limiter file', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.addLimiter({
limiterFileName: 'limiter.ts',
exportName: 'apiThrottleLimiter',
contents: 'export const apiThrottleLimiter = limiter.define("api", () => {})',
})
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create limiter file',
stream: 'stdout',
},
])
await assert.fileExists('start/limiter.ts')
await assert.fileContains('start/limiter.ts', 'apiThrottleLimiter')
})
})
test.group('Codemods | addModelMixins', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('add mixins to model', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create(
'app/models/user.ts',
`import { BaseModel } from '@adonisjs/lucid/orm'
export default class User extends BaseModel {}`
)
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.addModelMixins('user.ts', [
{
name: 'SoftDeletes',
importPath: '@adonisjs/lucid/mixins/soft_deletes',
importType: 'named',
},
])
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update model file',
stream: 'stdout',
},
])
await assert.fileContains('app/models/user.ts', 'SoftDeletes')
await assert.fileContains('app/models/user.ts', '@adonisjs/lucid/mixins/soft_deletes')
})
})
test.group('Codemods | addControllerMethod', (group) => {
group.tap((t) => t.timeout(60 * 1000))
test('add method to controller', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create(
'app/controllers/users_controller.ts',
`export default class UsersController {
async index() {}
}`
)
const codemods = new Codemods(ace.app, ace.ui.logger)
await codemods.addControllerMethod({
controllerFileName: 'users_controller.ts',
className: 'UsersController',
name: 'destroy',
contents: 'async destroy({ params }: HttpContext) { return params.id }',
imports: [
{
source: '@adonisjs/core/http',
typeImports: ['HttpContext'],
},
],
})
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) update controller file',
stream: 'stdout',
},
])
await assert.fileContains('app/controllers/users_controller.ts', 'destroy')
await assert.fileContains('app/controllers/users_controller.ts', 'HttpContext')
})
})
================================================
FILE: tests/ace/kernel.spec.ts
================================================
/*
* @adonisjs/ace
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import sinon from 'sinon'
import { test } from '@japa/runner'
import { HelpCommand } from '../../modules/ace/main.ts'
import { IgnitorFactory } from '../../factories/core/ignitor.ts'
import { createAceKernel } from '../../modules/ace/create_kernel.ts'
const BASE_URL = new URL('./tmp/', import.meta.url)
test.group('Kernel', () => {
test('create kernel instance with global flags', async ({ assert }) => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
const kernel = createAceKernel(app)
assert.deepEqual(kernel.flags, [
{
name: 'ansi',
flagName: 'ansi',
required: false,
type: 'boolean',
showNegatedVariantInHelp: true,
description: 'Force enable or disable colorful output',
},
{
name: 'help',
flagName: 'help',
required: false,
type: 'boolean',
description: 'View help for a given command',
},
])
})
test('turn off colors when --no-ansi flag is mentioned', async () => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
const kernel = createAceKernel(app)
const switchMode = sinon.spy(kernel.ui.switchMode)
await kernel.handle(['--no-ansi'])
switchMode.calledWith('silent')
})
test('turn off colors when --no-ansi flag is mentioned', async () => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
const kernel = createAceKernel(app)
const switchMode = sinon.spy(kernel.ui.switchMode)
await kernel.handle(['--no-ansi'])
switchMode.calledWith('silent')
})
test('turn on colors when --ansi flag is mentioned', async () => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
const kernel = createAceKernel(app)
const switchMode = sinon.spy(kernel.ui.switchMode)
await kernel.handle(['--ansi'])
switchMode.calledWith('normal')
})
test('display command help when --help flag is mentioned', async () => {
const ignitor = new IgnitorFactory().withCoreConfig().create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
const kernel = createAceKernel(app)
const execMock = sinon.mock(HelpCommand.prototype)
execMock.expects('exec')
await kernel.handle(['--help'])
execMock.verify()
})
test('load commands from a module identifier', async ({ assert }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
commands: ['../commands'],
},
})
.create(BASE_URL, {
importer: (filePath) => {
if (filePath === '../commands') {
return {
async getMetaData() {
return [
{
commandName: 'make:controller',
aliases: [],
},
]
},
}
}
import(filePath)
},
})
const app = ignitor.createApp('console')
await app.init()
const kernel = createAceKernel(app)
await kernel.boot()
assert.exists(kernel.getCommand('make:controller'))
})
})
================================================
FILE: tests/bindings/edge.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import edge from 'edge.js'
import { test } from '@japa/runner'
import '../../providers/edge_provider.js'
import { Qs } from '../../modules/http/main.ts'
import { HttpContextFactory } from '../../factories/http.ts'
import { IgnitorFactory } from '../../factories/core/ignitor.ts'
const BASE_URL = new URL('./tmp/', import.meta.url)
test.group('Bindings | Edge', () => {
test('register edge globals', async ({ assert }) => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [
() => import('../../providers/app_provider.js'),
() => import('../../providers/edge_provider.js'),
],
},
})
.withCoreConfig()
.create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
assert.equal(edge.globals.config('app.appUrl'), 'http://localhost:3333')
assert.isTrue(edge.globals.config.has('app.appUrl'))
assert.isFalse(edge.globals.config.has('foobar'))
assert.strictEqual(edge.globals.app, app)
assert.instanceOf(edge.globals.qs, Qs)
const router = await app.container.make('router')
router.get('/users/:id', () => {})
router.commit()
assert.equal(edge.globals.route('/users/:id', [1]), '/users/1')
assert.match(edge.globals.signedRoute('/users/:id', [1]), /\/users\/1\?signature=/)
})
test('render template using router', async ({ assert }) => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [
() => import('../../providers/app_provider.js'),
() => import('../../providers/edge_provider.js'),
],
},
})
.withCoreConfig()
.create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
edge.registerTemplate('welcome', {
template: `Hello {{ username }}`,
})
const router = await app.container.make('router')
router.on('/').render('welcome', { username: 'virk' })
router.commit()
const route = router.match('/', 'GET', false)
const ctx = new HttpContextFactory().create()
await route?.route.execute(route.route, app.container.createResolver(), ctx, () => {})
assert.equal(ctx.response.getBody(), 'Hello virk')
})
test('make form action using formAttributes helper', async ({ assert }) => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [
() => import('../../providers/app_provider.js'),
() => import('../../providers/edge_provider.js'),
],
},
})
.withCoreConfig()
.create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
const router = await app.container.make('router')
router.get('/users/:id', () => {}).as('users.show')
router.commit()
assert.deepEqual(edge.globals.formAttributes('users.show', { id: 1 }), {
action: '/users/1',
method: 'GET',
})
})
test('make action via method spoofing', async ({ assert }) => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [
() => import('../../providers/app_provider.js'),
() => import('../../providers/edge_provider.js'),
],
},
})
.withCoreConfig()
.create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
const router = await app.container.make('router')
router.put('/users/:id', () => {}).as('users.update')
router.commit()
assert.deepEqual(edge.globals.formAttributes('users.update', { id: 1 }), {
action: '/users/1?_method=PUT',
method: 'POST',
})
})
test('make action via method spoofing and append to existing query string', async ({
assert,
}) => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [
() => import('../../providers/app_provider.js'),
() => import('../../providers/edge_provider.js'),
],
},
})
.withCoreConfig()
.create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
const router = await app.container.make('router')
router.put('/users/:id', () => {}).as('users.update')
router.commit()
const options = {
qs: {
view: 'card',
},
}
assert.deepEqual(edge.globals.formAttributes('users.update', { id: 1 }, options), {
action: '/users/1?_method=PUT&view=card',
method: 'POST',
})
/**
* Making sure we do not mutate the options internally
*/
assert.deepEqual(options, {
qs: {
view: 'card',
},
})
})
test('share routes with names with edge', async ({ assert }) => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [
() => import('../../providers/app_provider.js'),
() => import('../../providers/edge_provider.js'),
],
},
})
.withCoreConfig()
.create(BASE_URL)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
const UsersController = () => import('#controllers/users_controller' as any)
const router = await app.container.make('router')
router.get('/users/:id', () => {})
router.get('/users', [UsersController, 'index'])
router.commit()
assert
.snapshot(edge.globals.routesJSON())
.matchInline(
'"{\\"root\\":[{\\"domain\\":\\"root\\",\\"methods\\":[\\"GET\\",\\"HEAD\\"],\\"pattern\\":\\"/users\\",\\"tokens\\":[{\\"old\\":\\"/users\\",\\"type\\":0,\\"val\\":\\"users\\",\\"end\\":\\"\\"}],\\"name\\":\\"users.index\\"}]}"'
)
})
})
================================================
FILE: tests/bindings/repl.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import is from '../../src/helpers/is.ts'
import stringHelpers from '../../src/helpers/string.ts'
import { IgnitorFactory } from '../../factories/core/ignitor.ts'
import AppServiceProvider from '../../providers/app_provider.ts'
const BASE_URL = new URL('./tmp/', import.meta.url)
test.group('Bindings | Repl', () => {
test('load services to REPL context', async ({ assert }) => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [
() => import('../../providers/app_provider.js'),
() => import('../../providers/hash_provider.js'),
() => import('../../providers/repl_provider.js'),
],
},
})
.withCoreConfig()
.create(BASE_URL, {
importer(filePath: string) {
return import(new URL(filePath, new URL('../', import.meta.url)).href)
},
})
const app = ignitor.createApp('console')
await app.init()
await app.boot()
/**
* Setting up REPL with fake server
* and context
*/
const repl = await app.container.make('repl')
repl.server = {
context: {},
displayPrompt() {},
} as any
/**
* Define REPL bindings
*/
// await new ReplServiceProvider(app).boot()
const methods = repl.getMethods()
await methods.loadEncryption.handler(repl)
assert.deepEqual(repl.server!.context.encryption, await app.container.make('encryption'))
await methods.loadApp.handler(repl)
assert.deepEqual(repl.server!.context.app, await app.container.make('app'))
await methods.loadHash.handler(repl)
assert.deepEqual(repl.server!.context.hash, await app.container.make('hash'))
await methods.loadRouter.handler(repl)
assert.deepEqual(repl.server!.context.router, await app.container.make('router'))
await methods.loadConfig.handler(repl)
assert.deepEqual(repl.server!.context.config, await app.container.make('config'))
await methods.loadTestUtils.handler(repl)
assert.deepEqual(repl.server!.context.testUtils, await app.container.make('testUtils'))
await methods.loadHelpers.handler(repl)
assert.deepEqual(repl.server!.context.helpers.string, stringHelpers)
assert.deepEqual(repl.server!.context.helpers.is, is)
await methods.loadUrlBuilder.handler(repl)
const router = await app.container.make('router')
assert.strictEqual(repl.server!.context.urlBuilder, router.urlBuilder)
const output = await methods.importDefault.handler(repl, '../providers/app_provider.js')
assert.deepEqual(output, AppServiceProvider)
assert.deepEqual(await methods.make.handler(repl, 'router'), await app.container.make('router'))
const exportedMods = await methods.importAll.handler(repl, '../../../factories')
assert.properties(exportedMods, [
'core',
'app',
'bodyparser',
'encryption',
'events',
'hash',
'logger',
'http',
])
})
})
================================================
FILE: tests/bindings/vinejs.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import vine from '@vinejs/vine'
import { test } from '@japa/runner'
import { IgnitorFactory } from '../../factories/core/ignitor.ts'
import { MultipartFileFactory } from '../../factories/bodyparser.ts'
const BASE_URL = new URL('./tmp/', import.meta.url)
test.group('Bindings | VineJS', (group) => {
group.setup(async () => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [
() => import('../../providers/app_provider.js'),
() => import('../../providers/hash_provider.js'),
() => import('../../providers/vinejs_provider.js'),
],
},
})
.withCoreConfig()
.create(BASE_URL, {
importer(filePath: string) {
return import(new URL(filePath, new URL('../', import.meta.url)).href)
},
})
const app = ignitor.createApp('console')
await app.init()
await app.boot()
})
test('clone schema type', async ({ assert }) => {
const file = vine.file()
assert.notStrictEqual(file, file.clone())
})
test('raise error when value is not a file', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine.file(),
})
)
try {
await validator.validate({
avatar: 'foo',
})
} catch (error) {
assert.deepEqual(error.messages, [
{
field: 'avatar',
message: 'The avatar must be a file',
rule: 'file',
},
])
}
})
test('raise error when file size is greater than the allowed size', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine.file({ size: '2mb' }),
})
)
try {
await validator.validate({
avatar: new MultipartFileFactory()
.merge({
size: 4000000,
})
.create(),
})
} catch (error) {
assert.deepEqual(error.messages, [
{
field: 'avatar',
message: 'File size should be less than 2MB',
rule: 'file.size',
meta: {
size: '2mb',
},
},
])
}
})
test('raise error when file extension is not allowed', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine.file({ extnames: ['jpg'] }),
})
)
try {
await validator.validate({
avatar: new MultipartFileFactory()
.merge({
size: 4000000,
extname: 'png',
})
.create(),
})
} catch (error) {
assert.deepEqual(error.messages, [
{
field: 'avatar',
message: 'Invalid file extension png. Only jpg is allowed',
rule: 'file.extname',
meta: {
extnames: ['jpg'],
},
},
])
}
})
test('compute file options lazily', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine.file(() => {
return { extnames: ['jpg'] }
}),
})
)
try {
await validator.validate({
avatar: new MultipartFileFactory()
.merge({
size: 4000000,
extname: 'png',
})
.create(),
})
} catch (error) {
assert.deepEqual(error.messages, [
{
field: 'avatar',
message: 'Invalid file extension png. Only jpg is allowed',
rule: 'file.extname',
meta: {
extnames: ['jpg'],
},
},
])
}
})
test('pass validation when file is valid', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine.file(() => {
return { extnames: ['jpg'] }
}),
})
)
const { avatar } = await validator.validate({
avatar: new MultipartFileFactory()
.merge({
size: 4000000,
extname: 'jpg',
})
.create(),
})
assert.equal(avatar.size, 4000000)
assert.lengthOf(avatar.errors, 0)
})
test('pass validation when file is null and marked as nullable', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine
.file(() => {
return { extnames: ['jpg'] }
})
.nullable(),
})
)
const payload = await validator.validate({
avatar: null,
})
assert.isNull(payload.avatar)
})
test('pass validation when file is null and marked as optional', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine
.file(() => {
return { extnames: ['jpg'] }
})
.optional(),
})
)
const payload = await validator.validate({
avatar: null,
})
assert.isUndefined(payload.avatar)
})
test('pass validation when file is missing and marked as optional', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine
.file(() => {
return { extnames: ['jpg'] }
})
.optional(),
})
)
const payload = await validator.validate({})
assert.isUndefined(payload.avatar)
})
test('raise error when field is marked as nullable, but missing', async ({ assert }) => {
const validator = vine.create(
vine.object({
avatar: vine
.file(() => {
return { extnames: ['jpg'] }
})
.nullable(),
})
)
try {
await validator.validate({})
} catch (error) {
assert.deepEqual(error.messages, [
{
field: 'avatar',
message: 'The avatar field must be defined',
rule: 'required',
},
])
}
})
})
================================================
FILE: tests/cli_formatters/routes_list.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import type { ApplicationService } from '../../src/types.ts'
import { IgnitorFactory } from '../../factories/core/ignitor.ts'
import { createAceKernel } from '../../modules/ace/create_kernel.ts'
import { RoutesListFormatter } from '../../src/cli_formatters/routes_list.ts'
/**
* Registers routes for testing
*/
async function registerRoutes(app: ApplicationService) {
class AboutController {
async handle() {}
}
class UsersController {
async handle() {}
}
class AuthMiddleware {
async handle() {}
}
const ContactController = () => import('#controllers/contacts_controller' as any)
const router = await app.container.make('router')
const middleware = router.named({
auth: async () => {
return {
default: AuthMiddleware,
}
},
throttle: async () => {
return {
default: class ThrottleMiddleware {
async handle() {}
},
}
},
signed: async () => {
return {
default: class SignedMiddleware {
async handle() {}
},
}
},
acl: async () => import('#middleware/acl_middleware' as any),
})
router.use([
async () => {
return {
default: class BodyParserMiddleware {
async handle() {}
},
}
},
])
router.get('/', () => {})
router.get('/files/:directory/*', () => {})
router.get('/home', '#controllers/home_controller').as('home')
router
.get('/about', [AboutController])
.as('about')
.use(() => {})
router.post('/contact', [ContactController, 'store']).as('contact.store')
router.get('/contact', [ContactController, 'create']).as('contact.create')
router
.get('users', [UsersController, 'handle'])
.use(middleware.auth())
.use(function canViewUsers() {})
.use(() => {})
router
.get('payments', [() => import('#controllers/payments_controller' as any), 'index'])
.use(middleware.auth())
.use(middleware.acl())
.use(middleware.signed())
.use(middleware.throttle())
router
.get('/articles', [() => import('#controllers/articles_controller' as any), 'index'])
.as('articles')
.domain('blog.adonisjs.com')
router
.get('/articles/:id/:slug?', [() => import('#controllers/articles_controller' as any), 'show'])
.as('articles.show')
.domain('blog.adonisjs.com')
/**
* The redirect method is now typed
*/
;(router.on('/blog').redirect as any)('/articles')
}
test.group('Formatters | List routes | toJSON', () => {
test('format routes as JSON', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(router, createAceKernel(app).ui, {}, {})
assert.snapshot(await formatter.formatAsJSON()).matchInline(`
[
{
"domain": "root",
"routes": [
{
"handler": {
"args": undefined,
"name": "closure",
"type": "closure",
},
"methods": [
"GET",
],
"middleware": [],
"name": "",
"pattern": "/",
},
{
"handler": {
"args": undefined,
"name": "closure",
"type": "closure",
},
"methods": [
"GET",
],
"middleware": [],
"name": "",
"pattern": "/files/:directory/*",
},
{
"handler": {
"method": "handle",
"moduleNameOrPath": "#controllers/home_controller",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [],
"name": "home",
"pattern": "/home",
},
{
"handler": {
"method": "handle",
"moduleNameOrPath": "AboutController",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"name": "closure",
"type": "closure",
},
],
"name": "about",
"pattern": "/about",
},
{
"handler": {
"method": "store",
"moduleNameOrPath": "#controllers/contacts_controller",
"type": "controller",
},
"methods": [
"POST",
],
"middleware": [],
"name": "contact.store",
"pattern": "/contact",
},
{
"handler": {
"method": "create",
"moduleNameOrPath": "#controllers/contacts_controller",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [],
"name": "contact.create",
"pattern": "/contact",
},
{
"handler": {
"method": "handle",
"moduleNameOrPath": "UsersController",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"name": "canViewUsers",
"type": "closure",
},
{
"name": "closure",
"type": "closure",
},
],
"name": "users",
"pattern": "/users",
},
{
"handler": {
"method": "index",
"moduleNameOrPath": "#controllers/payments_controller",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "#middleware/acl_middleware",
"name": "acl",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "signed",
"name": "signed",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "throttle",
"name": "throttle",
"type": "named",
},
],
"name": "",
"pattern": "/payments",
},
{
"handler": {
"args": "/articles",
"name": "redirectsToRoute",
"type": "closure",
},
"methods": [
"GET",
],
"middleware": [],
"name": "",
"pattern": "/blog",
},
],
},
{
"domain": "blog.adonisjs.com",
"routes": [
{
"handler": {
"method": "index",
"moduleNameOrPath": "#controllers/articles_controller",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [],
"name": "articles",
"pattern": "/articles",
},
{
"handler": {
"method": "show",
"moduleNameOrPath": "#controllers/articles_controller",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [],
"name": "articles.show",
"pattern": "/articles/:id/:slug?",
},
],
},
]
`)
})
test('show HEAD routes', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{ displayHeadRoutes: true },
{}
)
assert.snapshot(await formatter.formatAsJSON()).matchInline(`
[
{
"domain": "root",
"routes": [
{
"handler": {
"args": undefined,
"name": "closure",
"type": "closure",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [],
"name": "",
"pattern": "/",
},
{
"handler": {
"args": undefined,
"name": "closure",
"type": "closure",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [],
"name": "",
"pattern": "/files/:directory/*",
},
{
"handler": {
"method": "handle",
"moduleNameOrPath": "#controllers/home_controller",
"type": "controller",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [],
"name": "home",
"pattern": "/home",
},
{
"handler": {
"method": "handle",
"moduleNameOrPath": "AboutController",
"type": "controller",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [
{
"name": "closure",
"type": "closure",
},
],
"name": "about",
"pattern": "/about",
},
{
"handler": {
"method": "store",
"moduleNameOrPath": "#controllers/contacts_controller",
"type": "controller",
},
"methods": [
"POST",
],
"middleware": [],
"name": "contact.store",
"pattern": "/contact",
},
{
"handler": {
"method": "create",
"moduleNameOrPath": "#controllers/contacts_controller",
"type": "controller",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [],
"name": "contact.create",
"pattern": "/contact",
},
{
"handler": {
"method": "handle",
"moduleNameOrPath": "UsersController",
"type": "controller",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"name": "canViewUsers",
"type": "closure",
},
{
"name": "closure",
"type": "closure",
},
],
"name": "users",
"pattern": "/users",
},
{
"handler": {
"method": "index",
"moduleNameOrPath": "#controllers/payments_controller",
"type": "controller",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "#middleware/acl_middleware",
"name": "acl",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "signed",
"name": "signed",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "throttle",
"name": "throttle",
"type": "named",
},
],
"name": "",
"pattern": "/payments",
},
{
"handler": {
"args": "/articles",
"name": "redirectsToRoute",
"type": "closure",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [],
"name": "",
"pattern": "/blog",
},
],
},
{
"domain": "blog.adonisjs.com",
"routes": [
{
"handler": {
"method": "index",
"moduleNameOrPath": "#controllers/articles_controller",
"type": "controller",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [],
"name": "articles",
"pattern": "/articles",
},
{
"handler": {
"method": "show",
"moduleNameOrPath": "#controllers/articles_controller",
"type": "controller",
},
"methods": [
"GET",
"HEAD",
],
"middleware": [],
"name": "articles.show",
"pattern": "/articles/:id/:slug?",
},
],
},
]
`)
})
test('format routes as ANSI list', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const cliUi = createAceKernel(app).ui
cliUi.switchMode('silent')
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
cliUi,
{
maxPrettyPrintWidth: 100,
},
{}
)
assert.deepEqual(await formatter.formatAsAnsiList(), [
{
heading: '',
rows: [
`METHOD ROUTE ................................................... HANDLER MIDDLEWARE`,
`GET / ....................................................... closure `,
`GET /files/:directory/* ..................................... closure `,
`GET /home (home) ................ #controllers/home_controller.handle `,
`GET /about (about) ........................... AboutController.handle closure`,
`POST /contact (contact.store) . #controllers/contacts_controller.store `,
`GET /contact (contact.create) #controllers/contacts_controller.crea… `,
`GET /users (users) ........................... UsersController.handle auth, canViewUsers, closure`,
`GET /payments ................ #controllers/payments_controller.index auth, acl, and 2 more`,
`GET /blog .............................. redirectsToRoute(/articles) `,
],
},
{
heading:
'.. blog.adonisjs.com ...............................................................................',
rows: [
`METHOD ROUTE .................................................................... HANDLER MIDDLEWARE`,
`GET /articles (articles) ...................... #controllers/articles_controller.index `,
`GET /articles/:id/:slug? (articles.show) ....... #controllers/articles_controller.show `,
],
},
])
})
test('format routes as ANSI table', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const cliUi = createAceKernel(app).ui
cliUi.switchMode('raw')
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
cliUi,
{
maxPrettyPrintWidth: 100,
},
{}
)
const tables = await formatter.formatAsAnsiTable()
tables[0].table.render()
assert.deepEqual(cliUi.logger.getLogs(), [
{
message: 'dim(METHOD)|dim(ROUTE)|dim(HANDLER)|dim(MIDDLEWARE)',
stream: 'stdout',
},
{
message: `dim(GET)|/ | cyan(closure)|dim()`,
stream: 'stdout',
},
{
message: `dim(GET)|/files/yellow(:directory)/red(*) | cyan(closure)|dim()`,
stream: 'stdout',
},
{
message: `dim(GET)|/home dim((home)) | cyan(#controllers/home_controller).cyan(handle)|dim()`,
stream: 'stdout',
},
{
message: `dim(GET)|/about dim((about)) | cyan(AboutController).cyan(handle)|dim(closure)`,
stream: 'stdout',
},
{
message: `dim(POST)|/contact dim((contact.store)) | cyan(#controllers/contacts_controller).cyan(store)|dim()`,
stream: 'stdout',
},
{
message: `dim(GET)|/contact dim((contact.create)) | cyan(#controllers/contacts_controller).cyan(create)|dim()`,
stream: 'stdout',
},
{
message: `dim(GET)|/users dim((users)) | cyan(UsersController).cyan(handle)|dim(auth, canViewUsers, closure)`,
stream: 'stdout',
},
{
message: `dim(GET)|/payments | cyan(#controllers/payments_controller).cyan(index)|dim(auth, acl, signed, throttle)`,
stream: 'stdout',
},
{
message: `dim(GET)|/blog | cyan(redirectsToRoute)dim((/articles))|dim()`,
stream: 'stdout',
},
])
})
})
test.group('Formatters | List routes | toJSONL', () => {
test('format routes as JSONL', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(router, createAceKernel(app).ui, {}, {})
const lines = await formatter.formatAsJSONL()
const parsed = lines.map((line) => JSON.parse(line))
assert.snapshot(parsed).matchInline(`
[
{
"handler": {
"name": "closure",
"type": "closure",
},
"method": "GET",
"pattern": "/",
},
{
"handler": {
"name": "closure",
"type": "closure",
},
"method": "GET",
"pattern": "/files/:directory/*",
},
{
"handler": {
"method": "handle",
"module": "#controllers/home_controller",
"type": "controller",
},
"method": "GET",
"name": "home",
"pattern": "/home",
},
{
"handler": {
"method": "handle",
"module": "AboutController",
"type": "controller",
},
"method": "GET",
"middleware": [
"closure",
],
"name": "about",
"pattern": "/about",
},
{
"handler": {
"method": "store",
"module": "#controllers/contacts_controller",
"type": "controller",
},
"method": "POST",
"name": "contact.store",
"pattern": "/contact",
},
{
"handler": {
"method": "create",
"module": "#controllers/contacts_controller",
"type": "controller",
},
"method": "GET",
"name": "contact.create",
"pattern": "/contact",
},
{
"handler": {
"method": "handle",
"module": "UsersController",
"type": "controller",
},
"method": "GET",
"middleware": [
"auth",
"canViewUsers",
"closure",
],
"name": "users",
"pattern": "/users",
},
{
"handler": {
"method": "index",
"module": "#controllers/payments_controller",
"type": "controller",
},
"method": "GET",
"middleware": [
"auth",
"acl",
"signed",
"throttle",
],
"pattern": "/payments",
},
{
"handler": {
"args": "/articles",
"name": "redirectsToRoute",
"type": "redirect",
},
"method": "GET",
"pattern": "/blog",
},
{
"domain": "blog.adonisjs.com",
"handler": {
"method": "index",
"module": "#controllers/articles_controller",
"type": "controller",
},
"method": "GET",
"name": "articles",
"pattern": "/articles",
},
{
"domain": "blog.adonisjs.com",
"handler": {
"method": "show",
"module": "#controllers/articles_controller",
"type": "controller",
},
"method": "GET",
"name": "articles.show",
"pattern": "/articles/:id/:slug?",
},
]
`)
})
test('each line is valid JSON', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(router, createAceKernel(app).ui, {}, {})
const lines = await formatter.formatAsJSONL()
for (const line of lines) {
assert.doesNotThrow(() => JSON.parse(line))
}
})
test('omits name, domain, and middleware when empty or default', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(router, createAceKernel(app).ui, {}, {})
const lines = await formatter.formatAsJSONL()
/**
* The root closure route "/" has no name, no middleware, and root domain.
* None of those keys should be present.
*/
const rootRoute = JSON.parse(lines[0])
assert.notProperty(rootRoute, 'name')
assert.notProperty(rootRoute, 'domain')
assert.notProperty(rootRoute, 'middleware')
})
test('includes domain for non-root routes', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(router, createAceKernel(app).ui, {}, {})
const lines = await formatter.formatAsJSONL()
const parsed = lines.map((line) => JSON.parse(line))
const domainRoutes = parsed.filter((r) => r.domain === 'blog.adonisjs.com')
assert.lengthOf(domainRoutes, 2)
})
test('respects filters', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{},
{ middleware: ['auth'] }
)
const lines = await formatter.formatAsJSONL()
const parsed = lines.map((line) => JSON.parse(line))
assert.isTrue(parsed.every((r) => r.middleware && r.middleware.includes('auth')))
})
})
test.group('Formatters | List routes | filters', () => {
test('show routes that has one or more middleware', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{},
{
middleware: ['*'],
}
)
assert.snapshot(await formatter.formatAsJSON()).matchInline(`
[
{
"domain": "root",
"routes": [
{
"handler": {
"method": "handle",
"moduleNameOrPath": "AboutController",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"name": "closure",
"type": "closure",
},
],
"name": "about",
"pattern": "/about",
},
{
"handler": {
"method": "handle",
"moduleNameOrPath": "UsersController",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"name": "canViewUsers",
"type": "closure",
},
{
"name": "closure",
"type": "closure",
},
],
"name": "users",
"pattern": "/users",
},
{
"handler": {
"method": "index",
"moduleNameOrPath": "#controllers/payments_controller",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "#middleware/acl_middleware",
"name": "acl",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "signed",
"name": "signed",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "throttle",
"name": "throttle",
"type": "named",
},
],
"name": "",
"pattern": "/payments",
},
],
},
{
"domain": "blog.adonisjs.com",
"routes": [],
},
]
`)
})
test('show routes that has zero middleware', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{},
{
ignoreMiddleware: ['*'],
}
)
assert.deepEqual(await formatter.formatAsJSON(), [
{
domain: 'root',
routes: [
{
name: '',
pattern: '/',
methods: ['GET'],
handler: {
type: 'closure',
name: 'closure',
args: undefined,
},
middleware: [],
},
{
name: '',
pattern: '/files/:directory/*',
methods: ['GET'],
handler: {
type: 'closure',
name: 'closure',
args: undefined,
},
middleware: [],
},
{
name: 'home',
pattern: '/home',
methods: ['GET'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/home_controller',
method: 'handle',
},
middleware: [],
},
{
name: 'contact.store',
pattern: '/contact',
methods: ['POST'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/contacts_controller',
method: 'store',
},
middleware: [],
},
{
name: 'contact.create',
pattern: '/contact',
methods: ['GET'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/contacts_controller',
method: 'create',
},
middleware: [],
},
{
handler: {
args: '/articles',
name: 'redirectsToRoute',
type: 'closure',
},
methods: ['GET'],
middleware: [],
name: '',
pattern: '/blog',
},
],
},
{
domain: 'blog.adonisjs.com',
routes: [
{
pattern: '/articles',
name: 'articles',
methods: ['GET'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/articles_controller',
method: 'index',
},
middleware: [],
},
{
pattern: '/articles/:id/:slug?',
name: 'articles.show',
methods: ['GET'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/articles_controller',
method: 'show',
},
middleware: [],
},
],
},
])
})
test('show routes that has specific middleware', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{},
{
middleware: ['auth'],
}
)
assert.snapshot(await formatter.formatAsJSON()).matchInline(`
[
{
"domain": "root",
"routes": [
{
"handler": {
"method": "handle",
"moduleNameOrPath": "UsersController",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"name": "canViewUsers",
"type": "closure",
},
{
"name": "closure",
"type": "closure",
},
],
"name": "users",
"pattern": "/users",
},
{
"handler": {
"method": "index",
"moduleNameOrPath": "#controllers/payments_controller",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "#middleware/acl_middleware",
"name": "acl",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "signed",
"name": "signed",
"type": "named",
},
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "throttle",
"name": "throttle",
"type": "named",
},
],
"name": "",
"pattern": "/payments",
},
],
},
{
"domain": "blog.adonisjs.com",
"routes": [],
},
]
`)
})
test('combine middleware and ignoreMiddleware filters', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{},
{
middleware: ['auth'],
ignoreMiddleware: ['acl'],
}
)
assert.snapshot(await formatter.formatAsJSON()).matchInline(`
[
{
"domain": "root",
"routes": [
{
"handler": {
"method": "handle",
"moduleNameOrPath": "UsersController",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"name": "canViewUsers",
"type": "closure",
},
{
"name": "closure",
"type": "closure",
},
],
"name": "users",
"pattern": "/users",
},
],
},
{
"domain": "blog.adonisjs.com",
"routes": [],
},
]
`)
})
test('show routes by controller name', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{},
{
middleware: ['auth'],
match: 'UsersController',
}
)
assert.snapshot(await formatter.formatAsJSON()).matchInline(`
[
{
"domain": "root",
"routes": [
{
"handler": {
"method": "handle",
"moduleNameOrPath": "UsersController",
"type": "controller",
},
"methods": [
"GET",
],
"middleware": [
{
"args": undefined,
"method": "handle",
"moduleNameOrPath": "auth",
"name": "auth",
"type": "named",
},
{
"name": "canViewUsers",
"type": "closure",
},
{
"name": "closure",
"type": "closure",
},
],
"name": "users",
"pattern": "/users",
},
],
},
{
"domain": "blog.adonisjs.com",
"routes": [],
},
]
`)
})
test('show routes by route name', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{},
{
middleware: ['auth'],
match: 'contact.',
}
)
assert.deepEqual(await formatter.formatAsJSON(), [
{
domain: 'root',
routes: [
{
name: 'contact.store',
pattern: '/contact',
methods: ['POST'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/contacts_controller',
method: 'store',
},
middleware: [],
},
{
name: 'contact.create',
pattern: '/contact',
methods: ['GET'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/contacts_controller',
method: 'create',
},
middleware: [],
},
],
},
{
domain: 'blog.adonisjs.com',
routes: [],
},
])
})
test('show routes by pattern name', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.create(fs.baseUrl)
const app = ignitor.createApp('console')
await app.init()
await app.boot()
await registerRoutes(app)
const router = await app.container.make('router')
const formatter = new RoutesListFormatter(
router,
createAceKernel(app).ui,
{},
{
middleware: ['auth'],
match: '/contact',
}
)
assert.deepEqual(await formatter.formatAsJSON(), [
{
domain: 'root',
routes: [
{
name: 'contact.store',
pattern: '/contact',
methods: ['POST'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/contacts_controller',
method: 'store',
},
middleware: [],
},
{
name: 'contact.create',
pattern: '/contact',
methods: ['GET'],
handler: {
type: 'controller',
moduleNameOrPath: '#controllers/contacts_controller',
method: 'create',
},
middleware: [],
},
],
},
{
domain: 'blog.adonisjs.com',
routes: [],
},
])
})
})
================================================
FILE: tests/commands/add.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import { ListLoader } from '@adonisjs/ace'
import Add from '../../commands/add.ts'
import Configure from '../../commands/configure.ts'
import { AceFactory } from '../../factories/core/ace.ts'
import { setupPackage, setupNamedPackage, setupProject } from '../helpers.ts'
const VERBOSE = !!process.env.CI
const createFileImporter = (baseUrl: URL) => {
return function (folder: string) {
return import(new URL(`${folder}/index.js?${Math.random()}`, baseUrl).toString())
}
}
test.group('Install', (group) => {
group.tap((t) => t.disableTimeout())
test('install packages using npm', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(fs)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo'])
command.verbose = VERBOSE
await command.exec()
await assert.fileIsNotEmpty('package-lock.json')
})
test('install package using pnpm', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'pnpm')
await setupPackage(fs)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo'])
command.verbose = VERBOSE
await command.exec()
await assert.fileIsNotEmpty('pnpm-lock.yaml')
})
test('explicitly set the package manager to pnpm', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(fs)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo'])
command.verbose = VERBOSE
command.packageManager = 'pnpm'
await command.exec()
await assert.fileIsNotEmpty('pnpm-lock.yaml')
})
test('install dependencies', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(fs)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo'])
command.verbose = VERBOSE
await command.exec()
await assert.fileContains('package.json', '@adonisjs/foo')
})
test('install dev dependencies', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(fs)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo', '-D'])
command.verbose = VERBOSE
await command.exec()
const pkgJson = await fs.contentsJson('package.json')
assert.deepEqual(pkgJson.devDependencies, { '@adonisjs/foo': 'file:packages/foo' })
})
test('pass unknown args to configure', async ({ fs, assert }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(fs, `command.logger.log(command.parsedFlags)`)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo', '--foo', '--auth=session', '-x'])
command.verbose = VERBOSE
await command.exec()
const logs = command.logger.getLogs()
assert.deepInclude(logs, {
message: { foo: 'true', auth: 'session', x: 'true', ...(VERBOSE ? { verbose: true } : {}) },
stream: 'stdout',
})
})
test('configure package', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(
fs,
` const codemods = await command.createCodemods()
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider('@adonisjs/cache/cache_provider')
})`
)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo'])
command.verbose = VERBOSE
await command.exec()
await assert.fileContains('adonisrc.ts', '@adonisjs/cache/cache_provider')
})
test('display error and stop if package install fail', async ({ fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(fs)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/nonexistent'])
command.verbose = VERBOSE
await command.exec()
command.assertExitCode(1)
command.assertLogMatches(/Process exited with non-zero status/)
})
test('display error if configure command fails', async ({ fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(fs, 'throw new Error("Invalid configure")')
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo'])
command.verbose = VERBOSE
ace.errorHandler.render = async function (error: Error) {
command.logger.fatal(error)
}
await command.exec()
command.assertExitCode(1)
command.assertLogMatches(/Unable to configure/)
})
test('configure edge', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => import(filePath),
})
await setupProject(fs, 'npm')
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['edge'])
command.verbose = VERBOSE
await command.exec()
await assert.fileContains('package.json', 'edge.js')
await assert.fileContains('adonisrc.ts', '@adonisjs/core/providers/edge_provider')
})
test('configure vinejs', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => import(filePath),
})
await setupProject(fs, 'npm')
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
const command = await ace.create(Add, ['vinejs'])
command.verbose = VERBOSE
await command.exec()
await assert.fileContains('package.json', '@vinejs/vine')
await assert.fileContains('adonisrc.ts', '@adonisjs/core/providers/vinejs_provider')
})
test('install adonisjs dependencies as next version', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupPackage(fs)
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['@adonisjs/fold'])
command.verbose = VERBOSE
await command.exec()
await assert.fileContains('package.json', /"@adonisjs\/fold":"\^[\d.]+/)
})
test('install and configure multiple packages', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupNamedPackage(fs, {
name: 'foo',
configureContent: `
const codemods = await command.createCodemods()
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider('@adonisjs/foo/foo_provider')
})
`,
})
await setupNamedPackage(fs, {
name: 'bar',
configureContent: `
const codemods = await command.createCodemods()
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider('@adonisjs/bar/bar_provider')
})
`,
})
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo', './packages/bar'])
command.verbose = VERBOSE
await command.exec()
command.assertExitCode(0)
await assert.fileContains('package.json', '@adonisjs/foo')
await assert.fileContains('package.json', '@adonisjs/bar')
await assert.fileContains('adonisrc.ts', '@adonisjs/foo/foo_provider')
await assert.fileContains('adonisrc.ts', '@adonisjs/bar/bar_provider')
command.assertLogMatches(/Installed and configured/)
})
test('continue configuring other packages when one fails', async ({ fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupNamedPackage(fs, {
name: 'foo',
configureContent: `throw new Error("Configure failed")`,
})
await setupNamedPackage(fs, {
name: 'bar',
configureContent: `
const codemods = await command.createCodemods()
await codemods.updateRcFile((rcFile) => {
rcFile.addProvider('@adonisjs/bar/bar_provider')
})
`,
})
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
ace.errorHandler.render = async () => {}
const command = await ace.create(Add, ['./packages/foo', './packages/bar'])
command.verbose = VERBOSE
await command.exec()
command.assertExitCode(1)
command.assertLogMatches(/Unable to configure.*foo/)
command.assertLogMatches(/Installed and configured.*bar/)
})
test('pass unknown flags to all configure commands', async ({ fs, assert }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: createFileImporter(fs.baseUrl),
})
await setupProject(fs, 'npm')
await setupNamedPackage(fs, {
name: 'foo',
configureContent: `command.logger.log({ pkg: 'foo', flags: command.parsedFlags })`,
})
await setupNamedPackage(fs, {
name: 'bar',
configureContent: `command.logger.log({ pkg: 'bar', flags: command.parsedFlags })`,
})
await ace.app.init()
ace.addLoader(new ListLoader([Configure]))
ace.ui.switchMode('raw')
const command = await ace.create(Add, ['./packages/foo', './packages/bar', '--auth=session'])
command.verbose = VERBOSE
await command.exec()
const logs = command.logger.getLogs()
const fooLog = logs.find((log: any) => log.message?.pkg === 'foo')
const barLog = logs.find((log: any) => log.message?.pkg === 'bar')
assert.equal((fooLog?.message as any)?.flags?.auth, 'session')
assert.equal((barLog?.message as any)?.flags?.auth, 'session')
})
})
================================================
FILE: tests/commands/build.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import ts from 'typescript'
import { execa } from 'execa'
import { test } from '@japa/runner'
import Build from '../../commands/build.ts'
import { AceFactory } from '../../factories/core/ace.ts'
test.group('Build command', (group) => {
group.tap((t) => t.timeout(30 * 1000))
test('show error when assembler is not installed', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
if (filePath === '@adonisjs/assembler') {
return import(new URL(filePath, fs.baseUrl).href)
}
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Build, [])
await command.exec()
assert.equal(command.exitCode, 1)
assert.lengthOf(ace.ui.logger.getLogs(), 1)
assert.equal(ace.ui.logger.getLogs()[0].stream, 'stderr')
assert.match(ace.ui.logger.getLogs()[0].message, /Cannot find package "@adonisjs\/assembler/)
})
test('show error when typescript is not installed', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
if (filePath === 'typescript') {
return import(new URL(filePath, fs.baseUrl).href)
}
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Build, [])
await command.exec()
assert.equal(command.exitCode, 1)
assert.lengthOf(ace.ui.logger.getLogs(), 1)
assert.equal(ace.ui.logger.getLogs()[0].stream, 'stderr')
assert.match(ace.ui.logger.getLogs()[0].message, /Cannot find package "typescript/)
})
test('fail when tsconfig file is missing', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Build, [])
await command.exec()
assert.equal(command.exitCode, 1)
})
test('build project inside build directory', async ({ assert, fs }) => {
await fs.create(
'tsconfig.json',
JSON.stringify({
include: ['**/*'],
compilerOptions: { skipLibCheck: true },
exclude: [],
})
)
await fs.create('adonisrc.ts', `export default {}`)
await fs.create('index.ts', '')
await fs.create(
'package.json',
JSON.stringify({
name: 'app',
dependencies: {
typescript: ts.version,
},
})
)
await execa('npm', ['install'], {
cwd: fs.basePath,
stdio: 'inherit',
})
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Build, [])
await command.exec()
assert.equal(command.exitCode, 0)
await assert.fileExists('build/index.js')
await assert.fileExists('build/adonisrc.js')
})
test('do not output when typescript build has errors', async ({ assert, fs }) => {
await fs.create(
'tsconfig.json',
JSON.stringify({
include: ['**/*'],
exclude: [],
compilerOptions: {
target: 'ESNext',
module: 'NodeNext',
lib: ['ESNext'],
strict: true,
noUnusedLocals: true,
},
})
)
await fs.create('adonisrc.ts', `export default {}`)
await fs.create('index.ts', 'const foo = `a`')
await fs.create(
'package.json',
JSON.stringify({
name: 'app',
dependencies: {
typescript: ts.version,
},
})
)
await execa('npm', ['install'], {
cwd: fs.basePath,
stdio: 'inherit',
})
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Build, [])
await command.exec()
assert.equal(command.exitCode, 1)
await assert.fileNotExists('build/index.js')
})
test('output with --ignore-ts-errors flags when typescript build has errors', async ({
assert,
fs,
}) => {
await fs.create(
'tsconfig.json',
JSON.stringify({
include: ['**/*'],
exclude: [],
compilerOptions: {
target: 'ESNext',
module: 'NodeNext',
lib: ['ESNext'],
strict: true,
noUnusedLocals: true,
},
})
)
await fs.create('adonisrc.ts', `export default {}`)
await fs.create('index.ts', 'const foo = `a`')
await fs.create(
'package.json',
JSON.stringify({
name: 'app',
dependencies: {
typescript: ts.version,
},
})
)
await execa('npm', ['install'], {
cwd: fs.basePath,
stdio: 'inherit',
})
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Build, [])
command.ignoreTsErrors = true
await command.exec()
assert.equal(command.exitCode, 0)
await assert.fileExists('build/index.js')
await assert.fileExists('build/adonisrc.js')
})
test('do not attempt to build assets when assets bundler is not configured', async ({
assert,
fs,
}) => {
await fs.create(
'tsconfig.json',
JSON.stringify({
include: ['**/*'],
exclude: [],
compilerOptions: {
target: 'ESNext',
module: 'NodeNext',
lib: ['ESNext'],
strict: true,
noUnusedLocals: true,
},
})
)
await fs.create('adonisrc.ts', `export default {}`)
await fs.create('index.ts', '')
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
ace.ui.switchMode('raw')
const command = await ace.create(Build, [])
await command.exec()
assert.equal(command.exitCode, 0)
assert.notExists(
ace.ui.logger.getLogs().find((log) => {
return log.message.match(/compiling frontend assets/)
})
)
})
test('correctly pass hooks to the bundler', async ({ assert, fs }) => {
assert.plan(2)
await fs.create(
'tsconfig.json',
JSON.stringify({
include: ['**/*'],
exclude: [],
compilerOptions: {
target: 'ESNext',
module: 'NodeNext',
lib: ['ESNext'],
strict: true,
noUnusedLocals: true,
},
})
)
await fs.create('adonisrc.ts', `export default {}`)
await fs.create('index.ts', '')
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
ace.app.rcFile.hooks = {
buildFinished: [
async () => ({
default: async () => {
assert.isTrue(true)
},
}),
],
buildStarting: [
async () => ({
default: async () => {
assert.isTrue(true)
},
}),
],
}
ace.ui.switchMode('normal')
const command = await ace.create(Build, [])
await command.exec()
})
})
================================================
FILE: tests/commands/configure.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { fileURLToPath } from 'node:url'
import Configure from '../../commands/configure.ts'
import { AceFactory } from '../../factories/core/ace.ts'
const BASE_URL = new URL('./tmp/', import.meta.url)
const BASE_PATH = fileURLToPath(BASE_URL)
test.group('Configure command | list dependencies', (group) => {
group.each.disableTimeout()
test('list development dependencies to install', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Configure, ['../dummy-pkg.js'])
command.stubsRoot = join(fs.basePath, 'stubs')
const codemods = await command.createCodemods()
await codemods.listPackagesToInstall([
{
name: '@japa/runner',
isDevDependency: true,
},
{
name: '@japa/preset-adonis',
isDevDependency: true,
},
{
name: 'playwright',
isDevDependency: true,
},
])
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: ['Please install following packages'].join('\n'),
stream: 'stdout',
},
{
message: ['yellow(npm i -D) @japa/runner @japa/preset-adonis playwright'].join('\n'),
stream: 'stdout',
},
{
message: [''].join('\n'),
stream: 'stdout',
},
])
})
test('list development and prod dependencies to install', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Configure, ['../dummy-pkg.js'])
command.stubsRoot = join(fs.basePath, 'stubs')
const codemods = await command.createCodemods()
await codemods.listPackagesToInstall([
{
name: '@japa/runner',
isDevDependency: true,
},
{
name: '@japa/preset-adonis',
isDevDependency: true,
},
{
name: 'playwright',
isDevDependency: false,
},
])
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: ['Please install following packages'].join('\n'),
stream: 'stdout',
},
{
message: ['yellow(npm i -D) @japa/runner @japa/preset-adonis'].join('\n'),
stream: 'stdout',
},
{
message: ['yellow(npm i) playwright'].join('\n'),
stream: 'stdout',
},
])
})
test('list prod dependencies to install', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Configure, ['../dummy-pkg.js'])
command.stubsRoot = join(fs.basePath, 'stubs')
const codemods = await command.createCodemods()
await codemods.listPackagesToInstall([
{
name: 'playwright',
isDevDependency: false,
},
])
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: ['Please install following packages'].join('\n'),
stream: 'stdout',
},
{
message: [].join('\n'),
stream: 'stdout',
},
{
message: ['yellow(npm i) playwright'].join('\n'),
stream: 'stdout',
},
])
})
})
test.group('Configure command | run', (group) => {
group.each.setup(({ context }) => {
context.fs.baseUrl = BASE_URL
context.fs.basePath = BASE_PATH
})
group.each.disableTimeout()
test('error when unable to import package', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: async (filePath) => {
await import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Configure, ['./dummy-pkg.js'])
await command.exec()
command.assertLog('[ red(error) ] Cannot find module "./dummy-pkg.js". Make sure to install it')
assert.equal(command.exitCode, 1)
})
test('error when package cannot be configured', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(new URL(filePath, fs.baseUrl).href)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('dummy-pkg.js', `export const stubsRoot = './'`)
const command = await ace.create(Configure, ['./dummy-pkg.js?v=1'])
await command.exec()
command.assertLog(
'[ yellow(warn) ] Cannot configure module "./dummy-pkg.js?v=1". The module does not export the configure hook'
)
assert.equal(command.exitCode, 0)
})
test('run package configure method', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(new URL(filePath, fs.baseUrl).href)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create(
'dummy-pkg.js',
`
export const stubsRoot = './'
export function configure (command) {
command.result = 'configured'
}
`
)
const command = await ace.create(Configure, ['./dummy-pkg.js?v=2'])
await command.exec()
assert.equal(command.result, 'configured')
})
test('install packages', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(new URL(filePath, fs.baseUrl).href)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('package.json', { type: 'module' })
await fs.createJson('tsconfig.json', {})
await fs.create(
'dummy-pkg.js',
`
export const stubsRoot = './'
export async function configure (command) {
const codemods = await command.createCodemods()
await codemods.installPackages([
{ name: 'is-odd@2.0.0', isDevDependency: true },
{ name: 'is-even@1.0.0', isDevDependency: false }
])
}
`
)
const command = await ace.create(Configure, ['./dummy-pkg.js?v=3'])
command.verbose = true
await command.exec()
assert.equal(command.exitCode, 0)
const packageJson = await fs.contentsJson('package.json')
assert.deepEqual(packageJson.dependencies, { 'is-even': '^1.0.0' })
assert.deepEqual(packageJson.devDependencies, { 'is-odd': '^2.0.0' })
})
test('install packages using pnpm when pnpm-lock file exists', async ({ fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(new URL(filePath, fs.baseUrl).href)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('pnpm-lock.yaml', '')
await fs.createJson('tsconfig.json', {})
await fs.createJson('package.json', { type: 'module' })
await fs.create(
'dummy-pkg.js',
`
export const stubsRoot = './'
export async function configure (command) {
const codemods = await command.createCodemods()
await codemods.installPackages([
{ name: 'is-odd@2.0.0', isDevDependency: true, },
])
}
`
)
const command = await ace.create(Configure, ['./dummy-pkg.js?v=4'])
await command.exec()
command.assertSucceeded()
command.assertLog('[ cyan(wait) ] installing dependencies using pnpm . ')
})
test('install packages using npm when package-lock file exists', async ({ fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(new URL(filePath, fs.baseUrl).href)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('package-lock.json', {})
await fs.createJson('tsconfig.json', {})
await fs.createJson('package.json', { type: 'module' })
await fs.create(
'dummy-pkg.js',
`
export const stubsRoot = './'
export async function configure (command) {
const codemods = await command.createCodemods()
await codemods.installPackages([
{ name: 'is-odd@2.0.0', isDevDependency: true, },
])
}
`
)
const command = await ace.create(Configure, ['./dummy-pkg.js?v=5'])
await command.exec()
command.assertSucceeded()
command.assertLog('[ cyan(wait) ] installing dependencies using npm . ')
})
test('display error when installation fails', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(new URL(filePath, fs.baseUrl).href)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('package-lock.json', {})
await fs.createJson('tsconfig.json', {})
await fs.createJson('package.json', { type: 'module' })
await fs.create(
'dummy-pkg.js',
`
export const stubsRoot = './'
export async function configure (command) {
const codemods = await command.createCodemods()
await codemods.installPackages([
{ name: 'is-odd@15.0.0', isDevDependency: true, },
])
}
`
)
const command = await ace.create(Configure, ['./dummy-pkg.js?v=6'])
await command.exec()
command.assertFailed()
const logs = ace.ui.logger.getLogs()
assert.deepInclude(logs, {
message: '[ cyan(wait) ] unable to install dependencies ...',
stream: 'stdout',
})
const lastLog = logs[logs.length - 1]
assert.equal(command.exitCode, 1)
console.log(lastLog.message)
assert.include(lastLog.message, '[ red(error) ] Process exited with non-zero status')
})
})
test.group('Configure command | vinejs', (group) => {
group.each.disableTimeout()
test('register vinejs provider', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', 'export default defineConfig({})')
const command = await ace.create(Configure, ['vinejs'])
command.stubsRoot = join(fs.basePath, 'stubs')
await command.run()
assert.deepEqual(command.ui.logger.getLogs(), [
{
message: 'green(DONE:) update adonisrc.ts file',
stream: 'stdout',
},
])
await assert.fileContains('adonisrc.ts', '@adonisjs/core/providers/vinejs_provider')
})
})
test.group('Configure command | edge', (group) => {
group.each.disableTimeout()
test('register edge provider', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', 'export default defineConfig({})')
const command = await ace.create(Configure, ['edge'])
command.stubsRoot = join(fs.basePath, 'stubs')
await command.run()
assert.deepEqual(command.ui.logger.getLogs(), [
{
message: 'green(DONE:) update adonisrc.ts file',
stream: 'stdout',
},
])
await assert.fileContains('adonisrc.ts', '@adonisjs/core/providers/edge_provider')
await assert.fileContains(
'adonisrc.ts',
`metaFiles: [{
pattern: 'resources/views/**/*.edge',
reloadServer: false,
}]`
)
})
})
test.group('Configure command | health checks', (group) => {
group.each.disableTimeout()
test('create start/health file with some default checks', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', 'export default defineConfig({})')
const command = await ace.create(Configure, ['health_checks'])
await command.run()
assert.deepEqual(command.ui.logger.getLogs(), [
{
message: 'green(DONE:) create start/health.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) create app/controllers/health_checks_controller.ts',
stream: 'stdout',
},
])
await assert.fileContains('start/health.ts', [
'new DiskSpaceCheck()',
'new MemoryHeapCheck()',
'export const healthChecks = ',
])
await assert.fileContains('app/controllers/health_checks_controller.ts', [
`import { healthChecks } from '#start/health'`,
'const report = await healthChecks.run()',
'export default class HealthChecksController',
])
})
})
================================================
FILE: tests/commands/eject.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import EjectCommand from '../../commands/eject.ts'
import { AceFactory } from '../../factories/core/ace.ts'
test.group('Eject', () => {
test('eject a single stub', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => import(filePath),
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(EjectCommand, [
'make/controller/main.stub',
'--pkg="../../index.js"',
])
await command.exec()
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: '[ green(success) ] eject stubs/make/controller/main.stub',
stream: 'stdout',
},
])
await assert.hasFiles(['stubs/make/controller/main.stub'])
})
test('eject a directory', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => import(filePath),
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(EjectCommand, ['make/controller', '--pkg="../../index.js"'])
await command.exec()
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: '[ green(success) ] eject stubs/make/controller/actions.stub',
stream: 'stdout',
},
{
message: '[ green(success) ] eject stubs/make/controller/api.stub',
stream: 'stdout',
},
{
message: '[ green(success) ] eject stubs/make/controller/main.stub',
stream: 'stdout',
},
{
message: '[ green(success) ] eject stubs/make/controller/resource.stub',
stream: 'stdout',
},
])
await assert.hasFiles([
'stubs/make/controller/main.stub',
'stubs/make/controller/api.stub',
'stubs/make/controller/resource.stub',
])
})
})
================================================
FILE: tests/commands/env_add.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import EnvAdd from '../../commands/env/add.ts'
import { AceFactory } from '../../factories/core/ace.ts'
test.group('Env Add command', () => {
test('add new env variable to the different files', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('.env', '')
await fs.create('.env.example', '')
await fs.create(
'./start/env.ts',
`import { Env } from '@adonisjs/core/env'
export default await Env.create(new URL('../', import.meta.url), {})`
)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(EnvAdd, ['variable', 'value', '--type=string'])
await command.exec()
await assert.fileContains('.env', 'VARIABLE=value')
await assert.fileContains('.env.example', 'VARIABLE=')
await assert.fileContains('./start/env.ts', 'VARIABLE: Env.schema.string()')
})
test('convert variable to screaming snake case', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('.env', '')
await fs.create('.env.example', '')
await fs.create(
'./start/env.ts',
`import { Env } from '@adonisjs/core/env'
export default await Env.create(new URL('../', import.meta.url), {})`
)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(EnvAdd, ['stripe_ApiKey', 'value', '--type=string'])
await command.exec()
await assert.fileContains('.env', 'STRIPE_API_KEY=value')
await assert.fileContains('.env.example', 'STRIPE_API_KEY=')
await assert.fileContains('./start/env.ts', 'STRIPE_API_KEY: Env.schema.string()')
})
test('enum type with allowed values', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('.env', '')
await fs.create('.env.example', '')
await fs.create(
'./start/env.ts',
`import { Env } from '@adonisjs/core/env'
export default await Env.create(new URL('../', import.meta.url), {})`
)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(EnvAdd, [
'variable',
'bar',
'--type=enum',
'--enum-values=foo',
'--enum-values=bar',
])
await command.exec()
await assert.fileContains('.env', 'VARIABLE=bar')
await assert.fileContains('.env.example', 'VARIABLE=')
await assert.fileContains(
'./start/env.ts',
"VARIABLE: Env.schema.enum(['foo', 'bar'] as const)"
)
})
test('prompt when nothing is passed to the command', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('.env', '')
await fs.create('.env.example', '')
await fs.create(
'./start/env.ts',
`import { Env } from '@adonisjs/core/env'
export default await Env.create(new URL('../', import.meta.url), {})`
)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(EnvAdd, [])
command.prompt.trap('Enter the variable name').replyWith('my_variable_name')
command.prompt.trap('Enter the variable value').replyWith('my_value')
command.prompt.trap('Select the variable type').replyWith('string')
await command.exec()
await assert.fileContains('.env', 'MY_VARIABLE_NAME=my_value')
await assert.fileContains('.env.example', 'MY_VARIABLE_NAME=')
await assert.fileContains('./start/env.ts', 'MY_VARIABLE_NAME: Env.schema.string()')
})
})
================================================
FILE: tests/commands/generate_key.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import GenerateKey from '../../commands/generate_key.ts'
import { AceFactory } from '../../factories/core/ace.ts'
test.group('Generate key', () => {
test('create key and write it to .env file', async ({ assert, fs }) => {
await fs.create('.env', '')
await fs.create('.env.example', '')
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(GenerateKey, [])
await command.exec()
await assert.fileContains('.env', 'APP_KEY=')
await assert.fileContains('.env.example', 'APP_KEY=')
})
test('do not write to the file when --show flag is set', async ({ assert, fs }) => {
await fs.create('.env', '')
await fs.create('.env.example', '')
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(GenerateKey, ['--show'])
await command.exec()
await assert.fileEquals('.env', '')
await assert.fileEquals('.env.example', '')
assert.deepEqual(ace.ui.logger.getLogs()[0].stream, 'stdout')
assert.match(ace.ui.logger.getLogs()[0].message, /APP_KEY =/)
})
test('do not write to the file when in production envionment', async ({
assert,
fs,
cleanup,
}) => {
await fs.create('.env', '')
await fs.create('.env.example', '')
cleanup(() => {
delete process.env.NODE_ENV
})
process.env.NODE_ENV = 'production'
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(GenerateKey, [])
await command.exec()
await assert.fileEquals('.env', '')
await assert.fileEquals('.env.example', '')
assert.deepEqual(ace.ui.logger.getLogs()[0].stream, 'stdout')
assert.match(ace.ui.logger.getLogs()[0].message, /APP_KEY =/)
})
test('write to the file when in production envionment and --force flag is set', async ({
assert,
fs,
cleanup,
}) => {
await fs.create('.env', '')
await fs.create('.env.example', '')
cleanup(() => {
delete process.env.NODE_ENV
})
process.env.NODE_ENV = 'production'
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(GenerateKey, ['--force'])
await command.exec()
await assert.fileContains('.env', 'APP_KEY=')
await assert.fileContains('.env.example', 'APP_KEY=')
})
})
================================================
FILE: tests/commands/inspect_rcfile.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import InspectRCFile from '../../commands/inspect_rcfile.ts'
test.group('Inspect RCFile', () => {
test('inspect rcfile contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const inspect = await ace.create(InspectRCFile, [])
await inspect.exec()
inspect.assertSucceeded()
const { raw, providers, preloads, commands, ...rcContents } = ace.app.rcFile
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: JSON.stringify(
{
...rcContents,
providers: providers.map((provider) => {
return {
...provider,
file: provider.file.toString(),
}
}),
preloads: preloads.map((preload) => {
return {
...preload,
file: preload.file.toString(),
}
}),
commands: commands.map((command) => {
return command.toString()
}),
},
null,
2
),
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_command.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import MakeCommand from '../../commands/make/command.ts'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import { join } from 'node:path'
test.group('Make command', () => {
test('create command class', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeCommand, ['listRoutes'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/command/main.stub', {
entity: ace.app.generators.createEntity('listRoutes'),
})
await assert.fileEquals('commands/list_routes.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create commands/list_routes.ts',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-command.txt', 'export class MyCommand {}')
const command = await ace.create(MakeCommand, [
'listRoutes',
'--contents-from',
join(fs.basePath, 'my-command.txt'),
])
await command.exec()
await assert.fileEquals('commands/list_routes.ts', `export class MyCommand {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create commands/list_routes.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_controller.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import MakeControllerCommand from '../../commands/make/controller.ts'
test.group('Make controller', () => {
test('create controller', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, ['user'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/main.stub', {
entity: ace.app.generators.createEntity('user'),
singular: false,
})
await assert.fileEquals('app/controllers/users_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/users_controller.ts',
stream: 'stdout',
},
])
})
test('skip when controller already exists', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('app/controllers/users_controller.ts', `export default class {}`)
const command = await ace.create(MakeControllerCommand, ['user'])
await command.exec()
await assert.fileEquals('app/controllers/users_controller.ts', `export default class {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message:
'cyan(SKIPPED:) create app/controllers/users_controller.ts dim((File already exists))',
stream: 'stdout',
},
])
})
test('create resource controller', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, ['user', '--resource'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/resource.stub', {
entity: ace.app.generators.createEntity('user'),
singular: false,
})
await assert.fileEquals('app/controllers/users_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/users_controller.ts',
stream: 'stdout',
},
])
})
test('create api controller', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, ['user', '--api'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/api.stub', {
entity: ace.app.generators.createEntity('user'),
singular: false,
})
await assert.fileEquals('app/controllers/users_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/users_controller.ts',
stream: 'stdout',
},
])
})
test('create api controller when both api and resource flags are used', async ({
assert,
fs,
}) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, ['user', '--api', '--resource'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/api.stub', {
entity: ace.app.generators.createEntity('user'),
singular: false,
})
await assert.fileEquals('app/controllers/users_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message:
'[ yellow(warn) ] --api and --resource flags cannot be used together. Ignoring --resource',
stream: 'stdout',
},
{
message: 'green(DONE:) create app/controllers/users_controller.ts',
stream: 'stdout',
},
])
})
test('create controller with actions', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, [
'user',
'index',
'show',
'delete-profile',
])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/actions.stub', {
entity: ace.app.generators.createEntity('user'),
singular: false,
actions: ['index', 'show', 'deleteProfile'],
})
await assert.fileEquals('app/controllers/users_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/users_controller.ts',
stream: 'stdout',
},
])
})
test('warn when using --resource flag with actions', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, [
'user',
'index',
'show',
'delete-profile',
'--resource',
])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/actions.stub', {
entity: ace.app.generators.createEntity('user'),
singular: false,
actions: ['index', 'show', 'deleteProfile'],
})
await assert.fileEquals('app/controllers/users_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: '[ yellow(warn) ] Cannot use --resource flag with actions. Ignoring --resource',
stream: 'stdout',
},
{
message: 'green(DONE:) create app/controllers/users_controller.ts',
stream: 'stdout',
},
])
})
test('warn when using --resource and --api flag with actions', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, [
'user',
'index',
'show',
'delete-profile',
'--resource',
'--api',
])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/actions.stub', {
entity: ace.app.generators.createEntity('user'),
singular: false,
actions: ['index', 'show', 'deleteProfile'],
})
await assert.fileEquals('app/controllers/users_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: '[ yellow(warn) ] Cannot use --resource flag with actions. Ignoring --resource',
stream: 'stdout',
},
{
message: '[ yellow(warn) ] Cannot use --api flag with actions. Ignoring --api',
stream: 'stdout',
},
{
message: 'green(DONE:) create app/controllers/users_controller.ts',
stream: 'stdout',
},
])
})
test('create singular controller', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, ['user', '-s'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/main.stub', {
entity: ace.app.generators.createEntity('user'),
singular: true,
})
await assert.fileEquals('app/controllers/user_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/user_controller.ts',
stream: 'stdout',
},
])
})
test('create singular resource controller', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, ['user', '--resource', '-s'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/resource.stub', {
entity: ace.app.generators.createEntity('user'),
singular: true,
})
await assert.fileEquals('app/controllers/user_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/user_controller.ts',
stream: 'stdout',
},
])
})
test('create singular controller with actions', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, [
'user',
'index',
'show',
'delete-profile',
'-s',
])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/actions.stub', {
entity: ace.app.generators.createEntity('user'),
actions: ['index', 'show', 'deleteProfile'],
singular: true,
})
await assert.fileEquals('app/controllers/user_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/user_controller.ts',
stream: 'stdout',
},
])
})
test('create singular api controller', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeControllerCommand, ['user', '--api', '-s'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/controller/api.stub', {
entity: ace.app.generators.createEntity('user'),
singular: true,
})
await assert.fileEquals('app/controllers/user_controller.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/user_controller.ts',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-controller.txt', 'export class MyController {}')
const command = await ace.create(MakeControllerCommand, [
'user',
'--contents-from',
join(fs.basePath, 'my-controller.txt'),
])
await command.exec()
await assert.fileEquals('app/controllers/users_controller.ts', `export class MyController {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/controllers/users_controller.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_event.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import MakeEventCommand from '../../commands/make/event.ts'
test.group('Make event', () => {
test('create event class', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeEventCommand, ['orderShipped'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/event/main.stub', {
entity: ace.app.generators.createEntity('orderShipped'),
})
await assert.fileEquals('app/events/order_shipped.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/events/order_shipped.ts',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-event.txt', 'export class MyEvent {}')
const command = await ace.create(MakeEventCommand, [
'orderShipped',
'--contents-from',
join(fs.basePath, 'my-event.txt'),
])
await command.exec()
await assert.fileEquals('app/events/order_shipped.ts', `export class MyEvent {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/events/order_shipped.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_exception.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import MakeException from '../../commands/make/exception.ts'
test.group('Make exception command', () => {
test('create exception class', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeException, ['Unauthorized'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/exception/main.stub', {
entity: ace.app.generators.createEntity('Unauthorized'),
})
await assert.fileEquals('app/exceptions/unauthorized_exception.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/exceptions/unauthorized_exception.ts',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-exception.txt', 'export class MyException {}')
const command = await ace.create(MakeException, [
'Unauthorized',
'--contents-from',
join(fs.basePath, 'my-exception.txt'),
])
await command.exec()
await assert.fileEquals(
'app/exceptions/unauthorized_exception.ts',
`export class MyException {}`
)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/exceptions/unauthorized_exception.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_listener.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { ListLoader } from '../../modules/ace/main.ts'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import MakeEventCommand from '../../commands/make/event.ts'
import MakeListenerCommand from '../../commands/make/listener.ts'
test.group('Make listener', () => {
test('create listener class', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeListenerCommand, ['sendEmail'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/listener/main.stub', {
entity: ace.app.generators.createEntity('sendEmail'),
})
await assert.fileEquals('app/listeners/send_email.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/listeners/send_email.ts',
stream: 'stdout',
},
])
})
test('create a listener with an event class', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
ace.addLoader(new ListLoader([MakeEventCommand]))
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeListenerCommand, ['sendEmail', '-e=orderShipped'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/listener/for_event.stub', {
entity: ace.app.generators.createEntity('sendEmail'),
event: ace.app.generators.createEntity('orderShipped'),
})
const { contents: eventContents } = await new StubsFactory().prepare('make/event/main.stub', {
entity: ace.app.generators.createEntity('orderShipped'),
})
await assert.fileEquals('app/listeners/send_email.ts', contents)
await assert.fileEquals('app/events/order_shipped.ts', eventContents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/events/order_shipped.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) create app/listeners/send_email.ts',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-listener.txt', 'export class MyListener {}')
const command = await ace.create(MakeListenerCommand, [
'sendEmail',
'--contents-from',
join(fs.basePath, 'my-listener.txt'),
])
await command.exec()
await assert.fileEquals('app/listeners/send_email.ts', `export class MyListener {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/listeners/send_email.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_middleware.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import MakeMiddleware from '../../commands/make/middleware.ts'
test.group('Make middleware', (group) => {
group.tap((t) => t.disableTimeout())
test('create middleware class', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('start/kernel.ts', 'server.use([])')
const command = await ace.create(MakeMiddleware, ['auth', '--stack=server'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/middleware/main.stub', {
entity: ace.app.generators.createEntity('auth'),
})
await assert.fileEquals('app/middleware/auth_middleware.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/middleware/auth_middleware.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update start/kernel.ts file',
stream: 'stdout',
},
])
await assert.fileContains(
'start/kernel.ts',
`server.use([() => import('#middleware/auth_middleware')])`
)
})
test('register middleware under named stack', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('start/kernel.ts', 'export const middleware = router.named({})')
const command = await ace.create(MakeMiddleware, ['auth', '--stack=named'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/middleware/main.stub', {
entity: ace.app.generators.createEntity('auth'),
})
await assert.fileEquals('app/middleware/auth_middleware.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/middleware/auth_middleware.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update start/kernel.ts file',
stream: 'stdout',
},
])
await assert.fileContains(
'start/kernel.ts',
`auth: () => import('#middleware/auth_middleware')`
)
})
test('create nested middleware', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('start/kernel.ts', 'export const middleware = router.named({})')
const command = await ace.create(MakeMiddleware, ['blog/auth', '--stack=named'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/middleware/main.stub', {
entity: ace.app.generators.createEntity('auth'),
})
await assert.fileEquals('app/middleware/blog/auth_middleware.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/middleware/blog/auth_middleware.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update start/kernel.ts file',
stream: 'stdout',
},
])
await assert.fileContains(
'start/kernel.ts',
`auth: () => import('#middleware/blog/auth_middleware')`
)
})
test('show error when selected middleware stack is invalid', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('start/kernel.ts', 'export const middleware = router.named({})')
const command = await ace.create(MakeMiddleware, ['auth', '--stack=foo'])
await command.exec()
await assert.fileNotExists('app/middleware/auth_middleware.ts')
command.assertFailed()
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message:
'[ red(error) ] Invalid middleware stack "foo". Select from "server, router, named"',
stream: 'stderr',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.createJson('tsconfig.json', {})
await fs.create('start/kernel.ts', 'server.use([])')
await fs.create('my-middleware.txt', 'export class MyMiddleware {}')
const command = await ace.create(MakeMiddleware, [
'auth',
'--stack=server',
'--contents-from',
join(fs.basePath, 'my-middleware.txt'),
])
await command.exec()
await assert.fileEquals('app/middleware/auth_middleware.ts', `export class MyMiddleware {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/middleware/auth_middleware.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update start/kernel.ts file',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_preload.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { StubsFactory } from '../../factories/stubs.ts'
import { AceFactory } from '../../factories/core/ace.ts'
import MakePreload from '../../commands/make/preload.ts'
test.group('Make preload file', () => {
test('create a preload file for all environments', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakePreload, ['app'])
command.prompt.trap('Do you want to register the preload file in .adonisrc.ts file?').accept()
await command.exec()
const { contents } = await new StubsFactory().prepare('make/preload/main.stub', {
entity: ace.app.generators.createEntity('app'),
})
await assert.fileEquals('start/app.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create start/app.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update adonisrc.ts file',
stream: 'stdout',
},
])
await assert.fileContains('adonisrc.ts', `() => import('#start/app')`)
})
test('do not prompt when --register flag is used', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakePreload, ['app', '--register'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/preload/main.stub', {
entity: ace.app.generators.createEntity('app'),
})
await assert.fileEquals('start/app.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create start/app.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update adonisrc.ts file',
stream: 'stdout',
},
])
await assert.fileContains('adonisrc.ts', `() => import('#start/app')`)
})
test('do not register preload file when --no-register flag is used', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakePreload, ['app', '--no-register'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/preload/main.stub', {
entity: ace.app.generators.createEntity('app'),
})
await assert.fileEquals('start/app.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create start/app.ts',
stream: 'stdout',
},
])
await assert.fileEquals('adonisrc.ts', `export default defineConfig({})`)
})
test('use environment flag to make preload file in a specific env', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakePreload, [
'app',
'--environments=web',
'--environments=repl',
])
command.prompt.trap('Do you want to register the preload file in .adonisrc.ts file?').accept()
await command.exec()
await assert.fileContains('adonisrc.ts', [
`() => import('#start/app')`,
`environment: ['web', 'repl']`,
])
})
test('display error when defined environment is not allowed', async ({ fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakePreload, ['app'])
command.environments = ['foo' as any]
command.prompt.trap('Do you want to register the preload file in .adonisrc.ts file?').accept()
await command.exec()
command.assertLog(
'[ red(error) ] Invalid environment(s) "foo". Only "web,console,test,repl" are allowed'
)
})
test('overwrite file contents', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
await fs.create('my-preload.txt', 'export class MyPreload {}')
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakePreload, [
'app',
'--no-register',
'--contents-from',
join(fs.basePath, 'my-preload.txt'),
])
await command.exec()
await assert.fileEquals('start/app.ts', `export class MyPreload {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create start/app.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_provider.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import MakeProvider from '../../commands/make/provider.ts'
import { StubsFactory } from '../../factories/stubs.ts'
test.group('Make provider', () => {
test('create provider class', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeProvider, ['app'])
command.prompt.trap('Do you want to register the provider in .adonisrc.ts file?').accept()
await command.exec()
const { contents } = await new StubsFactory().prepare('make/provider/main.stub', {
entity: ace.app.generators.createEntity('app'),
})
await assert.fileEquals('providers/app_provider.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create providers/app_provider.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update adonisrc.ts file',
stream: 'stdout',
},
])
await assert.fileContains('adonisrc.ts', `() => import('#providers/app_provider')`)
})
test('do not display prompt when --register flag is used', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeProvider, ['app', '--register'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/provider/main.stub', {
entity: ace.app.generators.createEntity('app'),
})
await assert.fileEquals('providers/app_provider.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create providers/app_provider.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update adonisrc.ts file',
stream: 'stdout',
},
])
await assert.fileContains('adonisrc.ts', `() => import('#providers/app_provider')`)
})
test('do not register provider when --no-register flag is used', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeProvider, ['app', '--no-register'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/provider/main.stub', {
entity: ace.app.generators.createEntity('app'),
})
await assert.fileEquals('providers/app_provider.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create providers/app_provider.ts',
stream: 'stdout',
},
])
await assert.fileEquals('adonisrc.ts', `export default defineConfig({})`)
})
test('create provider class for a specific environment', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeProvider, ['app', '-e=web', '-e=repl'])
command.prompt.trap('Do you want to register the provider in .adonisrc.ts file?').accept()
await command.exec()
const { contents } = await new StubsFactory().prepare('make/provider/main.stub', {
entity: ace.app.generators.createEntity('app'),
})
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create providers/app_provider.ts',
stream: 'stdout',
},
{
message: 'green(DONE:) update adonisrc.ts file',
stream: 'stdout',
},
])
await assert.fileEquals('providers/app_provider.ts', contents)
await assert.fileContains('adonisrc.ts', [
`() => import('#providers/app_provider')`,
`environment: ['web', 'repl']`,
])
})
test('show error when selected environment is invalid', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeProvider, ['app', '--environments=foo'])
command.prompt.trap('Do you want to register the provider in .adonisrc.ts file?').accept()
await command.exec()
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message:
'[ red(error) ] Invalid environment(s) "foo". Only "web,console,test,repl" are allowed',
stream: 'stderr',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
await fs.createJson('tsconfig.json', {})
await fs.create('adonisrc.ts', `export default defineConfig({})`)
await fs.create('my-provider.txt', 'export class MyProvider {}')
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeProvider, [
'app',
'--no-register',
'--contents-from',
join(fs.basePath, 'my-provider.txt'),
])
await command.exec()
await assert.fileEquals('providers/app_provider.ts', `export class MyProvider {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create providers/app_provider.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_service.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import MakeService from '../../commands/make/service.ts'
test.group('Make service', () => {
test('create service class', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeService, ['app'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/service/main.stub', {
entity: ace.app.generators.createEntity('app'),
})
await assert.fileEquals('app/services/app_service.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/services/app_service.ts',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-service.txt', 'export class MyService {}')
const command = await ace.create(MakeService, [
'app',
'--contents-from',
join(fs.basePath, 'my-service.txt'),
])
await command.exec()
await assert.fileEquals('app/services/app_service.ts', `export class MyService {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/services/app_service.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_test.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import MakeTest from '../../commands/make/test.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import { AceFactory } from '../../factories/core/ace.ts'
import { IgnitorFactory } from '../../factories/core/ignitor.ts'
test.group('Make test', () => {
test('--suite flag: make inside suite directory', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
tests: {
suites: [
{
name: 'functional',
files: ['tests/functional/**/*.spec.ts'],
},
],
},
},
})
.create(fs.baseUrl)
const ace = await new AceFactory().make(ignitor)
ace.ui.switchMode('raw')
const command = await ace.create(MakeTest, ['posts/create', '--suite', 'functional'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/test/main.stub', {
entity: ace.app.generators.createEntity('posts/create'),
suite: {
directory: 'tests/functional',
},
})
await assert.fileEquals('tests/functional/posts/create.spec.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create tests/functional/posts/create.spec.ts',
stream: 'stdout',
},
])
})
test('--suite flag: show error when mentioned suite does not exists', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
},
})
.withCoreConfig()
.create(fs.baseUrl)
const ace = await new AceFactory().make(ignitor)
ace.ui.switchMode('raw')
const command = await ace.create(MakeTest, ['posts/create', '--suite', 'functional'])
await command.exec()
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message:
'[ red(error) ] The "functional" suite is not configured inside the "adonisrc.js" file',
stream: 'stderr',
},
])
})
test('auto pick first suite when only one suite is configured in rcfile', async ({
assert,
fs,
}) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
tests: {
suites: [
{
name: 'functional',
files: ['tests/functional/**/*.spec.ts'],
},
],
},
},
})
.create(fs.baseUrl)
const ace = await new AceFactory().make(ignitor)
ace.ui.switchMode('raw')
const command = await ace.create(MakeTest, ['posts/create'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/test/main.stub', {
entity: ace.app.generators.createEntity('posts/create'),
suite: {
directory: 'tests/functional',
},
})
await assert.fileEquals('tests/functional/posts/create.spec.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create tests/functional/posts/create.spec.ts',
stream: 'stdout',
},
])
})
test('prompt for suite selection when multiple suites are configured in rc file', async ({
assert,
fs,
}) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
tests: {
suites: [
{
name: 'functional',
files: ['tests/functional/**/*.spec.ts'],
},
{
name: 'unit',
files: ['tests/unit/**/*.spec.ts'],
},
],
},
},
})
.create(fs.baseUrl)
const ace = await new AceFactory().make(ignitor)
ace.ui.switchMode('raw')
const command = await ace.create(MakeTest, ['posts/create'])
command.prompt
.trap('Select the suite for the test file')
.assertFails('', 'Please select a suite')
.assertPasses('functional')
.chooseOption(1)
await command.exec()
const { contents } = await new StubsFactory().prepare('make/test/main.stub', {
entity: ace.app.generators.createEntity('posts/create'),
suite: {
directory: 'tests/unit',
},
})
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create tests/unit/posts/create.spec.ts',
stream: 'stdout',
},
])
await assert.fileEquals('tests/unit/posts/create.spec.ts', contents)
})
test('prompt for directory selection when suite has multiple directories', async ({
assert,
fs,
}) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
tests: {
suites: [
{
name: 'functional',
files: ['tests/functional/**/*.spec.ts', 'features/tests/functional/*.spec.ts'],
},
],
},
},
})
.create(fs.baseUrl)
const ace = await new AceFactory().make(ignitor)
ace.ui.switchMode('raw')
const command = await ace.create(MakeTest, ['posts/create'])
command.prompt
.trap('Select directory for the test file')
.assertPasses('features/tests/functional')
.assertFails('', 'Please select a directory')
.chooseOption(1)
await command.exec()
const { contents } = await new StubsFactory().prepare('make/test/main.stub', {
entity: ace.app.generators.createEntity('posts/create'),
suite: {
directory: 'features/tests/functional',
},
})
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create features/tests/functional/posts/create.spec.ts',
stream: 'stdout',
},
])
await assert.fileEquals('features/tests/functional/posts/create.spec.ts', contents)
})
test('overwrite file contents', async ({ assert, fs }) => {
const ignitor = new IgnitorFactory()
.withCoreConfig()
.merge({
rcFileContents: {
providers: [() => import('../../providers/app_provider.js')],
tests: {
suites: [
{
name: 'functional',
files: ['tests/functional/**/*.spec.ts'],
},
],
},
},
})
.create(fs.baseUrl)
const ace = await new AceFactory().make(ignitor)
ace.ui.switchMode('raw')
await fs.create('my-test.txt', 'export class MyTest {}')
const command = await ace.create(MakeTest, [
'posts/create',
'--suite',
'functional',
'--contents-from',
join(fs.basePath, 'my-test.txt'),
])
await command.exec()
await assert.fileEquals('tests/functional/posts/create.spec.ts', `export class MyTest {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create tests/functional/posts/create.spec.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_transformer.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import MakeTransformer from '../../commands/make/transformer.ts'
test.group('Make transformer', () => {
test('create transformer class', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeTransformer, ['user'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/transformer/main.stub', {
entity: ace.app.generators.createEntity('user'),
model: ace.app.generators.createEntity('user'),
})
await assert.fileEquals('app/transformers/user_transformer.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/transformers/user_transformer.ts',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-transformer.txt', 'export class MyTransformer {}')
const command = await ace.create(MakeTransformer, [
'user',
'--contents-from',
join(fs.basePath, 'my-transformer.txt'),
])
await command.exec()
await assert.fileEquals('app/transformers/user_transformer.ts', `export class MyTransformer {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/transformers/user_transformer.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_validator.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import { AceFactory } from '../../factories/core/ace.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import MakeValidator from '../../commands/make/validator.ts'
test.group('Make validator', () => {
test('create validator file', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeValidator, ['invoice'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/validator/main.stub', {
entity: ace.app.generators.createEntity('invoice'),
})
await assert.fileEquals('app/validators/invoice.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/validators/invoice.ts',
stream: 'stdout',
},
])
})
test('create validator file for a resource', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeValidator, ['invoice', '--resource'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/validator/resource.stub', {
entity: ace.app.generators.createEntity('invoice'),
})
await assert.fileEquals('app/validators/invoice.ts', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/validators/invoice.ts',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-validator.txt', 'export class MyValidator {}')
const command = await ace.create(MakeValidator, [
'invoice',
'--contents-from',
join(fs.basePath, 'my-validator.txt'),
])
await command.exec()
await assert.fileEquals('app/validators/invoice.ts', `export class MyValidator {}`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create app/validators/invoice.ts',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/make_view.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { join } from 'node:path'
import { test } from '@japa/runner'
import MakeView from '../../commands/make/view.ts'
import { StubsFactory } from '../../factories/stubs.ts'
import { AceFactory } from '../../factories/core/ace.ts'
test.group('Make view', () => {
test('create view template', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(MakeView, ['welcome'])
await command.exec()
const { contents } = await new StubsFactory().prepare('make/view/main.stub', {
entity: ace.app.generators.createEntity('welcome'),
})
await assert.fileEquals('resources/views/welcome.edge', contents)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create resources/views/welcome.edge',
stream: 'stdout',
},
])
})
test('overwrite file contents', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl)
await ace.app.init()
ace.ui.switchMode('raw')
await fs.create('my-view.txt', 'My View
')
const command = await ace.create(MakeView, [
'welcome',
'--contents-from',
join(fs.basePath, 'my-view.txt'),
])
await command.exec()
await assert.fileEquals('resources/views/welcome.edge', `My View
`)
assert.deepEqual(ace.ui.logger.getLogs(), [
{
message: 'green(DONE:) create resources/views/welcome.edge',
stream: 'stdout',
},
])
})
})
================================================
FILE: tests/commands/serve.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import Serve from '../../commands/serve.ts'
import { AceFactory } from '../../factories/core/ace.ts'
import { setupTypeScriptProject } from '../helpers.ts'
import { indexEntities } from '../../src/assembler_hooks/index_entities.ts'
const sleep = (duration: number) => new Promise((resolve) => setTimeout(resolve, duration))
test.group('Serve command', () => {
test('show error when assembler is not installed', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
if (filePath === '@adonisjs/assembler') {
return import(new URL(filePath, fs.baseUrl).href)
}
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Serve, ['--no-clear'])
await command.exec()
await sleep(600)
assert.equal(command.exitCode, 1)
assert.lengthOf(ace.ui.logger.getLogs(), 1)
assert.equal(ace.ui.logger.getLogs()[0].stream, 'stderr')
assert.match(ace.ui.logger.getLogs()[0].message, /Cannot find package "@adonisjs\/assembler/)
})
test('fail when bin/server.js file is missing', async ({ assert, fs, cleanup }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
if (filePath === 'typescript') {
return import(new URL(filePath, fs.baseUrl).href)
}
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Serve, ['--no-clear'])
cleanup(() => command.devServer.close())
await command.exec()
await sleep(600)
assert.equal(command.exitCode, 1)
})
test('fail in watch mode when tsconfig file is missing', async ({ assert, fs, cleanup }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Serve, ['--no-clear'])
cleanup(() => command.devServer.close())
command.watch = true
await command.exec()
await sleep(600)
assert.equal(command.exitCode, 1)
})
test('correctly pass hooks to the DevServer', async ({ assert, fs, cleanup }) => {
assert.plan(1)
await fs.create('bin/server.ts', `process.send({ isAdonisJS: true, environment: 'web' });`)
await setupTypeScriptProject()
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => import(filePath),
})
ace.app.rcFile.hooks = {
devServerStarted: [
async () => ({
default: async () => {
assert.isTrue(true)
},
}),
],
}
ace.ui.switchMode('raw')
const command = await ace.create(Serve, ['--no-clear'])
cleanup(() => command.devServer.close())
await command.exec()
await sleep(1200)
})
test('error if --hmr and --watch are used together', async ({ assert, fs }) => {
await fs.create('node_modules/ts-node-maintained/esm.js', '')
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => import(filePath),
})
ace.ui.switchMode('raw')
const command = await ace.create(Serve, ['--hmr', '--watch', '--no-clear'])
await command.exec()
assert.equal(command.exitCode, 1)
assert.lengthOf(ace.ui.logger.getLogs(), 1)
assert.equal(ace.ui.logger.getLogs()[0].stream, 'stderr')
assert.match(ace.ui.logger.getLogs()[0].message, /Cannot use --watch and --hmr flags together/)
})
test('generate barrel file for controllers, events and listeners', async ({
assert,
fs,
cleanup,
}) => {
await fs.create('bin/server.ts', `process.send({ isAdonisJS: true, environment: 'web' });`)
await setupTypeScriptProject()
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => import(filePath),
})
ace.app.rcFile.hooks = {
init: [indexEntities()],
}
ace.ui.switchMode('raw')
const command = await ace.create(Serve, ['--no-clear'])
cleanup(() => command.devServer.close())
await command.exec()
assert.snapshot(await fs.contents('.adonisjs/server/controllers.ts')).matchInline(`
"/**
* This file is automatically generated.
* DO NOT EDIT manually
*/
export const controllers = {}
"
`)
assert.snapshot(await fs.contents('.adonisjs/server/events.ts')).matchInline(`
"/**
* This file is automatically generated.
* DO NOT EDIT manually
*/
export const events = {}
"
`)
assert.snapshot(await fs.contents('.adonisjs/server/listeners.ts')).matchInline(`
"/**
* This file is automatically generated.
* DO NOT EDIT manually
*/
export const listeners = {}
"
`)
})
test('disable listeners and events barrel file generation', async ({ assert, fs, cleanup }) => {
await fs.create('bin/server.ts', `process.send({ isAdonisJS: true, environment: 'web' });`)
await setupTypeScriptProject()
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => import(filePath),
})
ace.app.rcFile.hooks = {
init: [indexEntities({ events: { enabled: false }, listeners: { enabled: false } })],
}
ace.ui.switchMode('raw')
const command = await ace.create(Serve, ['--no-clear'])
cleanup(() => command.devServer.close())
await command.exec()
assert.snapshot(await fs.contents('.adonisjs/server/controllers.ts')).matchInline(`
"/**
* This file is automatically generated.
* DO NOT EDIT manually
*/
export const controllers = {}
"
`)
await assert.fileNotExists('.adonisjs/server/events.ts')
await assert.fileNotExists('.adonisjs/server/listeners.ts')
})
})
================================================
FILE: tests/commands/test.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import Test from '../../commands/test.ts'
import { AceFactory } from '../../factories/core/ace.ts'
import { setupTypeScriptProject } from '../helpers.ts'
const sleep = (duration: number) => new Promise((resolve) => setTimeout(resolve, duration))
test.group('Test command', () => {
test('show error when assembler is not installed', async ({ assert, fs }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
if (filePath === '@adonisjs/assembler') {
return import(new URL(filePath, fs.baseUrl).href)
}
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Test, ['--no-clear'])
await command.exec()
await sleep(600)
assert.equal(command.exitCode, 1)
assert.lengthOf(ace.ui.logger.getLogs(), 1)
assert.equal(ace.ui.logger.getLogs()[0].stream, 'stderr')
assert.match(ace.ui.logger.getLogs()[0].message, /Cannot find package "@adonisjs\/assembler/)
})
test('fail when bin/test.js file is missing', async ({ assert, fs, cleanup }) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
if (filePath === 'typescript') {
return import(new URL(filePath, fs.baseUrl).href)
}
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Test, ['--no-clear'])
cleanup(() => command.testsRunner.close())
await command.exec()
await sleep(600)
assert.equal(command.exitCode, 1)
})
test('show error in watch mode when tsconfig file is missing', async ({
assert,
fs,
cleanup,
}) => {
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
await ace.app.init()
ace.ui.switchMode('raw')
const command = await ace.create(Test, ['--no-clear'])
cleanup(() => command.testsRunner.close())
command.watch = true
await command.exec()
await sleep(600)
assert.equal(command.exitCode, 1)
})
test('pass filters to bin/test.js script', async ({ assert, fs, cleanup }) => {
await fs.create(
'package.json',
JSON.stringify({
type: 'module',
})
)
await fs.create(
'bin/test.ts',
`
import { writeFile } from 'node:fs/promises'
await writeFile('argv.json', JSON.stringify(process.argv.splice(2), null, 2))
`
)
await setupTypeScriptProject()
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
ace.ui.switchMode('raw')
ace.app.rcFile.tests.suites = [
{
name: 'unit',
files: ['tests/unit/**/*.spec(.js|.ts)'],
directories: ['tests/unit'],
},
]
const command = await ace.create(Test, [
'--no-clear',
'--files=math.spec',
'--groups=foo',
'--tags=bar',
'--tests="2 + 2 = 4"',
])
cleanup(() => command.testsRunner.close())
await command.exec()
await sleep(600)
await assert.fileEquals(
'argv.json',
JSON.stringify(
['--files', 'math.spec', '--groups', 'foo', '--tags', 'bar', '--tests', '2 + 2 = 4'],
null,
2
)
)
})
test('pass suites to bin/test.js script', async ({ assert, fs, cleanup }) => {
await fs.create(
'package.json',
JSON.stringify({
type: 'module',
})
)
await fs.create(
'bin/test.ts',
`
import { writeFile } from 'node:fs/promises'
await writeFile('argv.json', JSON.stringify(process.argv.splice(2), null, 2))
`
)
await setupTypeScriptProject()
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
ace.ui.switchMode('raw')
ace.app.rcFile.tests.suites = [
{
name: 'unit',
files: 'tests/unit/**/*.spec(.js|.ts)',
directories: ['tests/unit'],
},
]
const command = await ace.create(Test, ['unit', 'functional', '--no-clear'])
cleanup(() => command.testsRunner.close())
await command.exec()
await sleep(600)
await assert.fileEquals('argv.json', JSON.stringify(['unit', 'functional'], null, 2))
})
test('pass unknown flags to bin/test.js script', async ({ assert, fs, cleanup }) => {
await fs.create(
'package.json',
JSON.stringify({
type: 'module',
})
)
await fs.create(
'bin/test.ts',
`
import { writeFile } from 'node:fs/promises'
await writeFile('argv.json', JSON.stringify(process.argv.splice(2), null, 2))
`
)
await setupTypeScriptProject()
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
ace.ui.switchMode('raw')
ace.app.rcFile.tests.suites = [
{
name: 'unit',
files: 'tests/unit/**/*.spec(.js|.ts)',
directories: ['tests/unit'],
},
]
const command = await ace.create(Test, ['--browser=firefox', '--inspect', '--no-clear'])
cleanup(() => command.testsRunner.close())
await command.exec()
await sleep(600)
await assert.fileEquals(
'argv.json',
JSON.stringify(['--browser', 'firefox', '--inspect'], null, 2)
)
})
test('pass unknown flags with array values to bin/test.js script', async ({
assert,
fs,
cleanup,
}) => {
await fs.create(
'package.json',
JSON.stringify({
type: 'module',
})
)
await fs.create(
'bin/test.ts',
`
import { writeFile } from 'node:fs/promises'
await writeFile('argv.json', JSON.stringify(process.argv.splice(2), null, 2))
`
)
await setupTypeScriptProject()
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
ace.ui.switchMode('raw')
ace.app.rcFile.tests.suites = [
{
name: 'unit',
files: ['tests/unit/**/*.spec(.js|.ts)'],
directories: ['tests/unit'],
},
]
const command = await ace.create(Test, [
'--browser=firefox',
'--browser=chrome',
'--inspect',
'--no-clear',
])
await command.exec()
cleanup(() => command.testsRunner.close())
await sleep(600)
await assert.fileEquals(
'argv.json',
JSON.stringify(['--browser', 'firefox', '--browser', 'chrome', '--inspect'], null, 2)
)
})
test('pass all japa flags to the script', async ({ assert, fs, cleanup }) => {
await fs.create(
'package.json',
JSON.stringify({
type: 'module',
})
)
await fs.create(
'bin/test.ts',
`
import { writeFile } from 'node:fs/promises'
await writeFile('argv.json', JSON.stringify(process.argv.splice(2), null, 2))
`
)
await setupTypeScriptProject()
const ace = await new AceFactory().make(fs.baseUrl, {
importer: (filePath) => {
return import(filePath)
},
})
ace.ui.switchMode('raw')
ace.app.rcFile.tests.suites = [
{
name: 'unit',
files: ['tests/unit/**/*.spec(.js|.ts)'],
directories: ['tests/unit'],
},
]
const command = await ace.create(Test, [
'--no-clear',
'--reporters=ndjson,spec',
'--failed',
'--retries=2',
'--timeout=3000',
])
cleanup(() => command.testsRunner.close())
await command.exec()
await sleep(600)
await assert.fileEquals(
'argv.json',
JSON.stringify(
['--reporters', 'ndjson,spec', '--timeout', '3000', '--failed', '--retries', '2'],
null,
2
)
)
})
})
================================================
FILE: tests/dumper/dumper.spec.ts
================================================
/*
* @adonisjs/core
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { test } from '@japa/runner'
import { fileURLToPath } from 'node:url'
import { AppFactory } from '@adonisjs/application/factories'
import { HttpContextFactory } from '@adonisjs/http-server/factories'
import { Dumper } from '../../modules/dumper/dumper.ts'
import { AceFactory } from '../../factories/core/ace.ts'
import { E_DUMP_DIE_EXCEPTION } from '../../modules/dumper/errors.ts'
test.group('Dumper', () => {
test('dump and die', ({ fs }) => {
const app = new AppFactory().create(fs.baseUrl)
const dumper = new Dumper(app)
dumper.dd('hello')
}).throws('Dump and Die exception', E_DUMP_DIE_EXCEPTION)
test('render dump as HTML', async ({ fs, assert }) => {
assert.plan(3)
const app = new AppFactory().create(fs.baseUrl)
const dumper = new Dumper(app)
const ctx = new HttpContextFactory().create()
try {
dumper.dd('hello')
} catch (error) {
await error.handle(error, ctx)
assert.include(ctx.response.getBody(), '