@adonisjs/assembler
Advanced tools
Comparing version 6.1.3-30 to 7.0.0-0
@@ -29,3 +29,3 @@ // src/bundler.ts | ||
"--loader=ts-node/esm", | ||
// Disable annonying warnings | ||
// Disable annoying warnings | ||
"--no-warnings", | ||
@@ -139,2 +139,20 @@ // Enable expiremental meta resolve for cases where someone uses magic import string | ||
// src/bundler.ts | ||
var SUPPORT_PACKAGE_MANAGERS = { | ||
npm: { | ||
lockFile: "package-lock.json", | ||
installCommand: 'npm ci --omit="dev"' | ||
}, | ||
yarn: { | ||
lockFile: "yarn.lock", | ||
installCommand: "yarn install --production" | ||
}, | ||
pnpm: { | ||
lockFile: "pnpm-lock.yaml", | ||
installCommand: "pnpm i --prod" | ||
}, | ||
bun: { | ||
lockFile: "bun.lockb", | ||
installCommand: "bun install --production" | ||
} | ||
}; | ||
var ui = cliui(); | ||
@@ -159,2 +177,6 @@ var Bundler = class { | ||
} | ||
/** | ||
* Returns the relative unix path for an absolute | ||
* file path | ||
*/ | ||
#getRelativeName(filePath) { | ||
@@ -170,7 +192,7 @@ return slash(relative2(this.#cwdPath, filePath)); | ||
/** | ||
* Runs assets bundler command to build assets. | ||
* Runs assets bundler command to build assets | ||
*/ | ||
async #buildAssets() { | ||
const assetsBundler = this.#options.assets; | ||
if (!assetsBundler?.serve) { | ||
if (!assetsBundler?.enabled) { | ||
return true; | ||
@@ -218,21 +240,13 @@ } | ||
async #getPackageManager(client) { | ||
const pkgManagerInfo = { | ||
npm: { | ||
lockFile: "package-lock.json", | ||
installCommand: 'npm ci --omit="dev"' | ||
}, | ||
yarn: { | ||
lockFile: "yarn.lock", | ||
installCommand: "yarn install --production" | ||
}, | ||
pnpm: { | ||
lockFile: "pnpm-lock.yaml", | ||
installCommand: "pnpm i --prod" | ||
} | ||
}; | ||
const pkgManager = client || await detectPackageManager(this.#cwdPath) || "npm"; | ||
if (!["npm", "yarn", "pnpm"].includes(pkgManager)) { | ||
throw new Error(`Unsupported package manager "${pkgManager}"`); | ||
let pkgManager = client; | ||
if (!pkgManager) { | ||
pkgManager = await detectPackageManager(this.#cwdPath); | ||
} | ||
return pkgManagerInfo[pkgManager]; | ||
if (!pkgManager) { | ||
pkgManager = "npm"; | ||
} | ||
if (!Object.keys(SUPPORT_PACKAGE_MANAGERS).includes(pkgManager)) { | ||
return null; | ||
} | ||
return SUPPORT_PACKAGE_MANAGERS[pkgManager]; | ||
} | ||
@@ -278,3 +292,3 @@ /** | ||
const pkgManager = await this.#getPackageManager(client); | ||
const pkgFiles = ["package.json", pkgManager.lockFile]; | ||
const pkgFiles = pkgManager ? ["package.json", pkgManager.lockFile] : ["package.json"]; | ||
this.#logger.info("copying meta files to the output directory"); | ||
@@ -284,3 +298,7 @@ await this.#copyMetaFiles(outDir, pkgFiles); | ||
this.#logger.log(""); | ||
ui.instructions().useRenderer(this.#logger.getRenderer()).heading("Run the following commands to start the server in production").add(this.#colors.cyan(`cd ${this.#getRelativeName(outDir)}`)).add(this.#colors.cyan(pkgManager.installCommand)).add(this.#colors.cyan("node bin/server.js")).render(); | ||
ui.instructions().useRenderer(this.#logger.getRenderer()).heading("Run the following commands to start the server in production").add(this.#colors.cyan(`cd ${this.#getRelativeName(outDir)}`)).add( | ||
this.#colors.cyan( | ||
pkgManager ? pkgManager.installCommand : "Install production dependencies" | ||
) | ||
).add(this.#colors.cyan("node bin/server.js")).render(); | ||
return true; | ||
@@ -319,7 +337,2 @@ } | ||
const lines = dataString.split("\n"); | ||
if (dataString.includes("ready in")) { | ||
console.log(""); | ||
console.log(dataString.trim()); | ||
return; | ||
} | ||
if (dataString.includes("Local") && dataString.includes("Network")) { | ||
@@ -335,2 +348,7 @@ const sticker = ui2.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer()); | ||
} | ||
if (dataString.includes("ready in")) { | ||
console.log(""); | ||
console.log(dataString.trim()); | ||
return; | ||
} | ||
lines.forEach((line) => { | ||
@@ -367,3 +385,3 @@ if (line.trim()) { | ||
start() { | ||
if (!this.#options?.serve) { | ||
if (!this.#options?.enabled) { | ||
return; | ||
@@ -423,10 +441,38 @@ } | ||
#options; | ||
/** | ||
* Flag to know if the dev server is running in watch | ||
* mode | ||
*/ | ||
#isWatching = false; | ||
/** | ||
* Script file to start the development server | ||
*/ | ||
#scriptFile = "bin/server.js"; | ||
/** | ||
* Picomatch matcher function to know if a file path is a | ||
* meta file with reloadServer option enabled | ||
*/ | ||
#isMetaFileWithReloadsEnabled; | ||
/** | ||
* Picomatch matcher function to know if a file path is a | ||
* meta file with reloadServer option disabled | ||
*/ | ||
#isMetaFileWithReloadsDisabled; | ||
/** | ||
* External listeners that are invoked when child process | ||
* gets an error or closes | ||
*/ | ||
#onError; | ||
#onClose; | ||
/** | ||
* Reference to the child process | ||
*/ | ||
#httpServer; | ||
/** | ||
* Reference to the watcher | ||
*/ | ||
#watcher; | ||
/** | ||
* Reference to the assets server | ||
*/ | ||
#assetsServer; | ||
@@ -514,3 +560,4 @@ /** | ||
/** | ||
* Restarts the HTTP server | ||
* Restarts the HTTP server in the watch mode. Do not call this | ||
* method when not in watch mode | ||
*/ | ||
@@ -657,6 +704,26 @@ #restartHTTPServer(port) { | ||
#options; | ||
/** | ||
* The script file to run as a child process | ||
*/ | ||
#scriptFile = "bin/test.js"; | ||
/** | ||
* Pico matcher function to check if the filepath is | ||
* part of the `metaFiles` glob patterns | ||
*/ | ||
#isMetaFile; | ||
/** | ||
* Pico matcher function to check if the filepath is | ||
* part of a test file. | ||
*/ | ||
#isTestFile; | ||
/** | ||
* Arguments to pass to the "bin/test.js" file. | ||
*/ | ||
#scriptArgs; | ||
/** | ||
* Set of initial filters applied when running the test | ||
* command. In watch mode, we will append an additional | ||
* filter to run tests only for the file that has been | ||
* changed. | ||
*/ | ||
#initialFiltersArgs; | ||
@@ -670,6 +737,19 @@ /** | ||
#isBusy = false; | ||
/** | ||
* External listeners that are invoked when child process | ||
* gets an error or closes | ||
*/ | ||
#onError; | ||
#onClose; | ||
/** | ||
* Reference to the test script child process | ||
*/ | ||
#testScript; | ||
/** | ||
* Reference to the watcher | ||
*/ | ||
#watcher; | ||
/** | ||
* Reference to the assets server | ||
*/ | ||
#assetsServer; | ||
@@ -698,3 +778,3 @@ /** | ||
/** | ||
* Converts options to CLI args | ||
* Convert test runner options to the CLI args | ||
*/ | ||
@@ -785,3 +865,4 @@ #convertOptionsToArgs() { | ||
/** | ||
* Restarts the HTTP server | ||
* Re-run tests with additional inline filters. Should be | ||
* executed in watch mode only. | ||
*/ | ||
@@ -788,0 +869,0 @@ #rerunTests(port, filters) { |
@@ -5,3 +5,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
import type { BundlerOptions } from './types.js'; | ||
type SupportedPackageManager = 'npm' | 'yarn' | 'pnpm'; | ||
type SupportedPackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun'; | ||
/** | ||
@@ -8,0 +8,0 @@ * The bundler class exposes the API to build an AdonisJS project. |
/// <reference types="node" resolution-mode="require"/> | ||
import { installPackage, detectPackageManager } from '@antfu/install-pkg'; | ||
import { RcFileTransformer } from './rc_file_transformer.js'; | ||
import type { AddMiddlewareEntry, EnvValidationDefinition } from '../types.js'; | ||
import type { MiddlewareNode, EnvValidationNode } from '../types.js'; | ||
/** | ||
@@ -21,3 +21,3 @@ * This class is responsible for updating | ||
*/ | ||
defineEnvValidations(definition: EnvValidationDefinition): Promise<void>; | ||
defineEnvValidations(definition: EnvValidationNode): Promise<void>; | ||
/** | ||
@@ -31,3 +31,3 @@ * Define new middlewares inside the `start/kernel.ts` | ||
*/ | ||
addMiddlewareToStack(stack: 'server' | 'router' | 'named', middleware: AddMiddlewareEntry[]): Promise<void>; | ||
addMiddlewareToStack(stack: 'server' | 'router' | 'named', middleware: MiddlewareNode[]): Promise<void>; | ||
/** | ||
@@ -40,7 +40,7 @@ * Update the `adonisrc.ts` file | ||
*/ | ||
addJapaPlugin(pluginCall: string, importDeclaration: { | ||
addJapaPlugin(pluginCall: string, importDeclarations: { | ||
isNamed: boolean; | ||
module: string; | ||
identifier: string; | ||
}): Promise<void>; | ||
}[]): Promise<void>; | ||
} |
@@ -27,3 +27,7 @@ // src/code_transformer/main.ts | ||
convertTabsToSpaces: true, | ||
trimTrailingWhitespace: true | ||
trimTrailingWhitespace: true, | ||
ensureNewLineAtEndOfFile: true, | ||
indentStyle: 2, | ||
// @ts-expect-error SemicolonPreference doesn't seem to be re-exported from ts-morph | ||
semicolons: "remove" | ||
}; | ||
@@ -45,4 +49,5 @@ constructor(cwd, project) { | ||
#isInSpecificEnvironment(environments) { | ||
if (!environments) | ||
if (!environments) { | ||
return false; | ||
} | ||
return !!["web", "console", "test", "repl"].find( | ||
@@ -281,2 +286,4 @@ (env) => !environments.includes(env) | ||
trimTrailingWhitespace: true, | ||
ensureNewLineAtEndOfFile: true, | ||
indentStyle: 2, | ||
// @ts-expect-error SemicolonPreference doesn't seem to be re-exported from ts-morph | ||
@@ -415,12 +422,30 @@ semicolons: "remove" | ||
*/ | ||
async addJapaPlugin(pluginCall, importDeclaration) { | ||
async addJapaPlugin(pluginCall, importDeclarations) { | ||
const testBootstrapUrl = fileURLToPath2(new URL("./tests/bootstrap.ts", this.#cwd)); | ||
const file = this.#project.getSourceFileOrThrow(testBootstrapUrl); | ||
file.addImportDeclaration({ | ||
...importDeclaration.isNamed ? { namedImports: [importDeclaration.identifier] } : { defaultImport: importDeclaration.identifier }, | ||
moduleSpecifier: importDeclaration.module | ||
const existingImports = file.getImportDeclarations(); | ||
importDeclarations.forEach((importDeclaration) => { | ||
const existingImport = existingImports.find( | ||
(mod) => mod.getModuleSpecifierValue() === importDeclaration.module | ||
); | ||
if (existingImport && importDeclaration.isNamed) { | ||
if (!existingImport.getNamedImports().find((namedImport) => namedImport.getName() === importDeclaration.identifier)) { | ||
existingImport.addNamedImport(importDeclaration.identifier); | ||
} | ||
return; | ||
} | ||
if (existingImport) { | ||
return; | ||
} | ||
file.addImportDeclaration({ | ||
...importDeclaration.isNamed ? { namedImports: [importDeclaration.identifier] } : { defaultImport: importDeclaration.identifier }, | ||
moduleSpecifier: importDeclaration.module | ||
}); | ||
}); | ||
const pluginsArray = file.getVariableDeclaration("plugins")?.getInitializerIfKind(SyntaxKind2.ArrayLiteralExpression); | ||
if (pluginsArray) | ||
pluginsArray.addElement(pluginCall); | ||
if (pluginsArray) { | ||
if (!pluginsArray.getElements().find((element) => element.getText() === pluginCall)) { | ||
pluginsArray.addElement(pluginCall); | ||
} | ||
} | ||
file.formatText(this.#editorSettings); | ||
@@ -427,0 +452,0 @@ await file.save(); |
@@ -11,5 +11,5 @@ /// <reference types="node" resolution-mode="require"/> | ||
* | ||
* - Assigns a random PORT, when PORT inside .env file is in use | ||
* - Assigns a random PORT, when PORT inside .env file is in use. | ||
* - Uses tsconfig.json file to collect a list of files to watch. | ||
* - Uses metaFiles from .adonisrc.json file to collect a list of files to watch. | ||
* - Uses metaFiles from adonisrc.ts file to collect a list of files to watch. | ||
* - Restart HTTP server on every file change. | ||
@@ -16,0 +16,0 @@ */ |
@@ -38,4 +38,4 @@ /// <reference types="node" resolution-mode="require"/> | ||
* - The dot-env files are loaded using the "EnvLoader" and the PORT | ||
* value is by iterating over all the loaded files. The iteration | ||
* stops after first find. | ||
* value is used by iterating over all the loaded files. The | ||
* iteration stops after first find. | ||
*/ | ||
@@ -42,0 +42,0 @@ export declare function getPort(cwd: URL): Promise<number>; |
@@ -6,11 +6,12 @@ /// <reference types="node" resolution-mode="require"/> | ||
/** | ||
* Exposes the API to start the development. Optionally, the watch API can be | ||
* used to watch for file changes and restart the development server. | ||
* Exposes the API to run Japa tests and optionally watch for file | ||
* changes to re-run the tests. | ||
* | ||
* The Dev server performs the following actions | ||
* The watch mode functions as follows. | ||
* | ||
* - Assigns a random PORT, when PORT inside .env file is in use | ||
* - Uses tsconfig.json file to collect a list of files to watch. | ||
* - Uses metaFiles from .adonisrc.json file to collect a list of files to watch. | ||
* - Restart HTTP server on every file change. | ||
* - If the changed file is a test file, then only tests for that file | ||
* will be re-run. | ||
* - Otherwise, all tests will re-run with respect to the initial | ||
* filters applied when running the `node ace test` command. | ||
* | ||
*/ | ||
@@ -17,0 +18,0 @@ export declare class TestRunner { |
@@ -6,6 +6,21 @@ /// <reference types="node" resolution-mode="require"/> | ||
export type RunOptions = { | ||
/** | ||
* Script to run | ||
*/ | ||
script: string; | ||
/** | ||
* Arguments to pass to the script | ||
*/ | ||
scriptArgs: string[]; | ||
/** | ||
* Arguments to pass to NodeJS CLI | ||
*/ | ||
nodeArgs: string[]; | ||
/** | ||
* Standard input ouput stream options | ||
*/ | ||
stdio?: 'pipe' | 'inherit'; | ||
/** | ||
* Environment variables to pass to the child process | ||
*/ | ||
env?: NodeJS.ProcessEnv; | ||
@@ -20,3 +35,3 @@ }; | ||
/** | ||
* Meta file config defined in ".adonisrc.json" file | ||
* Meta file config defined in "adonisrc.ts" file | ||
*/ | ||
@@ -28,7 +43,7 @@ export type MetaFile = { | ||
/** | ||
* Test suite defined in ".adonisrc.json" file | ||
* Test suite defined in "adonisrc.ts" file | ||
*/ | ||
export type Suite = { | ||
name: string; | ||
files: string | string[]; | ||
name: string; | ||
}; | ||
@@ -39,21 +54,43 @@ /** | ||
export type AssetsBundlerOptions = { | ||
serve: false; | ||
args?: string[]; | ||
enabled: false; | ||
driver?: string; | ||
cmd?: string; | ||
args?: string[]; | ||
} | { | ||
serve: true; | ||
args: string[]; | ||
enabled: true; | ||
driver: string; | ||
cmd: string; | ||
args: string[]; | ||
}; | ||
/** | ||
* Options accepted by the dev server | ||
* Options accepted when starting the dev | ||
* server | ||
*/ | ||
export type DevServerOptions = { | ||
/** | ||
* Arguments to pass to the "bin/server.js" file | ||
* executed a child process | ||
*/ | ||
scriptArgs: string[]; | ||
/** | ||
* Arguments to pass to Node.js CLI when executing | ||
* the "bin/server.js" file | ||
*/ | ||
nodeArgs: string[]; | ||
/** | ||
* Clear screen after every file change | ||
*/ | ||
clearScreen?: boolean; | ||
/** | ||
* Environment variables to share with the "bin/server.js" | ||
* file. | ||
*/ | ||
env?: NodeJS.ProcessEnv; | ||
/** | ||
* An array of metaFiles glob patterns to watch | ||
*/ | ||
metaFiles?: MetaFile[]; | ||
/** | ||
* Assets bundler options to start its dev server | ||
*/ | ||
assets?: AssetsBundlerOptions; | ||
@@ -66,2 +103,49 @@ }; | ||
/** | ||
* Arguments to pass to the "bin/server.js" file | ||
* executed a child process | ||
*/ | ||
scriptArgs: string[]; | ||
/** | ||
* Arguments to pass to Node.js CLI when executing | ||
* the "bin/server.js" file | ||
*/ | ||
nodeArgs: string[]; | ||
/** | ||
* Clear screen after every file change | ||
*/ | ||
clearScreen?: boolean; | ||
/** | ||
* Environment variables to share with the "bin/server.js" | ||
* file. | ||
*/ | ||
env?: NodeJS.ProcessEnv; | ||
/** | ||
* An array of metaFiles glob patterns to watch | ||
*/ | ||
metaFiles?: MetaFile[]; | ||
/** | ||
* Assets bundler options to start its dev server | ||
*/ | ||
assets?: AssetsBundlerOptions; | ||
/** | ||
* An array of suites for which to run tests | ||
*/ | ||
suites: Suite[]; | ||
/** | ||
* Set the tests runner reporter via the CLI flag | ||
*/ | ||
reporters?: string[]; | ||
/** | ||
* Set the tests global timeout via the CLI flag | ||
*/ | ||
timeout?: number; | ||
/** | ||
* Define retries via the CLI flag | ||
*/ | ||
retries?: number; | ||
/** | ||
* Run only failed tests | ||
*/ | ||
failed?: boolean; | ||
/** | ||
* Filter arguments are provided as a key-value | ||
@@ -77,17 +161,2 @@ * pair, so that we can mutate them (if needed) | ||
}>; | ||
reporters?: string[]; | ||
timeout?: number; | ||
retries?: number; | ||
failed?: boolean; | ||
/** | ||
* All other tags are provided as a collection of | ||
* arguments | ||
*/ | ||
scriptArgs: string[]; | ||
nodeArgs: string[]; | ||
clearScreen?: boolean; | ||
env?: NodeJS.ProcessEnv; | ||
metaFiles?: MetaFile[]; | ||
assets?: AssetsBundlerOptions; | ||
suites: Suite[]; | ||
}; | ||
@@ -98,3 +167,11 @@ /** | ||
export type BundlerOptions = { | ||
/** | ||
* An array of metaFiles glob patterns to copy the | ||
* files to the build folder | ||
*/ | ||
metaFiles?: MetaFile[]; | ||
/** | ||
* Assets bundler options to create the production build | ||
* for assets | ||
*/ | ||
assets?: AssetsBundlerOptions; | ||
@@ -106,3 +183,3 @@ }; | ||
*/ | ||
export type AddMiddlewareEntry = { | ||
export type MiddlewareNode = { | ||
/** | ||
@@ -134,3 +211,3 @@ * If you are adding a named middleware, then you must | ||
*/ | ||
export type EnvValidationDefinition = { | ||
export type EnvValidationNode = { | ||
/** | ||
@@ -137,0 +214,0 @@ * Write a leading comment on top of your variables |
{ | ||
"name": "@adonisjs/assembler", | ||
"description": "Provides utilities to run AdonisJS development server and build project for production", | ||
"version": "6.1.3-30", | ||
"version": "7.0.0-0", | ||
"engines": { | ||
@@ -125,2 +125,4 @@ "node": ">=18.16.0" | ||
"build/**", | ||
"bin/**", | ||
"tmp/**", | ||
"examples/**", | ||
@@ -127,0 +129,0 @@ "src/dev_server.ts", |
390
README.md
@@ -8,9 +8,389 @@ # @adonisjs/assembler | ||
## Introduction | ||
Assembler exports the API for starting the **AdonisJS development server**, **building project for production** and **running tests** in watch mode. Assembler must be used during development only. | ||
AdonisJS Assembler is a development toolkit used by AdonisJS to perform tasks like **starting the dev server in watch mode**, **running tests in watch mode**, and **applying codemods** to modify source files. | ||
## Official Documentation | ||
The documentation is available on the official website | ||
Assembler should always be installed as a development dependency. If your project needs Assembler APIs in production, you must reconsider your approach. | ||
## Goals | ||
Assembler is built around the following goals. | ||
- Expose a coding interface and not a user interface. In other words, Assembler will never expose any CLI commands. | ||
- Encapsulate tasks under a single API. Instead of providing ten different utilities to run a dev server, Assembler will expose one API to run the dev server. | ||
- House all development APIs needed by AdonisJS. Therefore, the scope of the Assembler might increase over time. | ||
## Dev server | ||
You can start the HTTP server of an AdonisJS application using the `node --loader=ts-node/esm bin/server.ts` file. However, this approach has some limitations and may not provide the best DX. | ||
### Using a file watcher | ||
You might be tempted to use the Node.js built-in file watcher with the `--watch` flag. However, the Node.js file watcher does not integrate with TypeScript. As a result, you will be tweaking its configuration options to get an ideal experience. | ||
On the other hand, the Assembler file watcher takes the following approach. | ||
- Parses the `tsconfig.json` file to collect the list of files that are part of your TypeScript project. As a result, if you ever want to ignore any file, you do it directly within the `tsconfig.json` file, and the watcher will pick it up. | ||
- It uses the `metaFiles` array defined inside the `adonisrc.ts` file to watch additional files that are not `.js` or `.ts`. It may be the Edge templates, markdown files, YAML files, etc. | ||
### Starting the asset bundler server | ||
If you create a full-stack application, the chances of using Webpack or Vite are high. Instead of starting your assets bundler inside a separate process, you can also rely on Assembler to start a parallel process for the assets bundler. | ||
The [`node ace serve` command](https://github.com/adonisjs/core/blob/next/commands/serve.ts#L88) detects the assets bundler used by your AdonisJS project and passes it to Assembler. | ||
Therefore, if you run the `serve` command with a `vite.config.js` file, you will notice that the Assembler will start both Vite and the AdonisJS HTTP server. | ||
### Picking a random port | ||
The PORT on which an AdonisJS application should run is configured inside the `.env` file of your AdonisJS application. However, you will often start multiple projects together and have to edit the `.env` file to ensure both projects run on different ports. | ||
With Assembler, you do not have to edit the `.env` files since Assembler will pick a random port of your application if the configured one is already in use. | ||
### Usage | ||
You may import and use the `DevServer` as follows. | ||
```ts | ||
import ts from 'typescript' | ||
import { DevServer } from '@adonisjs/assembler' | ||
const appRoot = new URL('./', import.meta.url) | ||
const devServer = new DevServer(appRoot, { | ||
/** | ||
* Arguments to pass to the "bin/server.ts" file | ||
*/ | ||
scriptArgs: [], | ||
/** | ||
* Arguments to pass to the Node.js CLI | ||
*/ | ||
nodeArgs: [], | ||
/** | ||
* An array of metaFiles to watch and re-start the | ||
* HTTP server only if the "reloadServer" flag is | ||
* true. | ||
*/ | ||
metaFiles: [ | ||
{ | ||
pattern: 'resources/views/**/*.edge', | ||
reloadServer: false, | ||
} | ||
], | ||
/** | ||
* The assets bundler process to start | ||
*/ | ||
assets: { | ||
enabled: true, | ||
name: 'vite', | ||
cmd: 'vite', | ||
args: [] | ||
} | ||
}) | ||
devServer.onError((error) => { | ||
process.exitCode = 1 | ||
}) | ||
devServer.onClose((exitCode) => { | ||
process.exitCode = exitCode | ||
}) | ||
await devServer.runAndWatch(ts) | ||
``` | ||
You may start the dev server and assets bundler dev server using the `start` method. | ||
```ts | ||
await devServer.start() | ||
``` | ||
## Test runner | ||
The `TestRunner` is used to execute the `bin/test.ts` file of your AdonisJS application. Like the `DevServer`, the `TestRunner` allows you to watch for file changes and re-run the tests. The following steps are taken to re-run tests in watch mode. | ||
> [!NOTE] | ||
> Read [Using a file watcher](#using-a-file-watcher) section to understand which files are watched by the file watcher. | ||
- If the changed file is a test file, only tests for that file will be re-run. | ||
- Otherwise, all tests will re-run with respect to the initial filters applied when running the `node ace test` command. | ||
### Usage | ||
You may import and use the `TestRunner` as follows. | ||
```ts | ||
import ts from 'typescript' | ||
import { TestRunner } from '@adonisjs/assembler' | ||
const appRoot = new URL('./', import.meta.url) | ||
const runner = new TestRunner(appRoot, { | ||
/** | ||
* Arguments to pass to the "bin/test.ts" file | ||
*/ | ||
scriptArgs: [], | ||
/** | ||
* Arguments to pass to the Node.js CLI | ||
*/ | ||
nodeArgs: [], | ||
/** | ||
* An array of suites and their glob patterns | ||
*/ | ||
suites: [ | ||
{ | ||
name: 'unit', | ||
files: ['tests/unit/**/*.spec.ts'] | ||
}, | ||
{ | ||
name: 'functional', | ||
files: ['tests/functional/**/*.spec.ts'] | ||
} | ||
], | ||
/** | ||
* Initial set of filters to apply. These filters | ||
* will be re-applied when re-running tests in | ||
* watch mode | ||
*/ | ||
filters: { | ||
suites: ['unit'], | ||
tags: ['@slow'] | ||
} | ||
}) | ||
await runner.runAndWatch(ts) | ||
``` | ||
You can run tests without the watcher using the `run` method. | ||
```ts | ||
await runner.run() | ||
``` | ||
## Bundler | ||
The `Bundler` is used to create the production build of an AdonisJS application. The following steps are performed to generate the build. | ||
- Clean up the existing build directory. | ||
- Compile frontend assets (if an assets bundler is configured). | ||
- Create JavaScript build using `tsc` (The TypeScript's official compiler). | ||
- Copy the `ace.js` file to the build folder. Since the ace file ends with the `.js` extension, it is not compiled by the TypeScript compiler. | ||
- Copy `package.json` and the **lock-file of the package manager** you are using to the `build` folder. This operation only supports `bun | npm | yarn | pnpm`. For other bundlers, you will have to copy the lock file manually. | ||
- The end. | ||
### Usage | ||
You may import and use the `Bundler` as follows. | ||
```ts | ||
import ts from 'typescript' | ||
import { Bundler } from '@adonisjs/assembler' | ||
const appRoot = new URL('./', import.meta.url) | ||
const bundler = new Bundler(appRoot, ts, { | ||
/** | ||
* Metafiles to copy to the build folder | ||
*/ | ||
metaFiles: [ | ||
{ | ||
pattern: 'resources/views/**/*.edge', | ||
reloadServer: false, | ||
} | ||
], | ||
/** | ||
* The assets bundler to use to bundle the frontend | ||
* assets | ||
*/ | ||
assets: { | ||
enabled: true, | ||
name: 'vite', | ||
cmd: 'vite', | ||
args: ['build'] | ||
} | ||
}) | ||
``` | ||
## Codemods | ||
Assembler also exports certain codemods to modify the source files of an AdonisJS project to configure packages. | ||
The codemods relies on the defaults of AdonisJS and will not work if a project does not follow the defaults. This is an intentional limit since we only have limited time to craft codemods that work with every possible setup. | ||
### Usage | ||
You may import and use the `Codemods` as follows. | ||
```ts | ||
import { CodeTransformer } from '@adonisjs/assembler/code_transformer' | ||
const appRoot = new URL('./', import.meta.url) | ||
const transformer = new CodeTransformer(appRoot) | ||
``` | ||
### defineEnvValidations | ||
Define validation rules for environment variables. The method accepts a key-value pair of variables. The `key` is the env variable name, and the `value` is the validation expression as a string. | ||
> [!IMPORTANT] | ||
> This codemod expects the `start/env.ts` file to exist and must have the `export default await Env.create` method call. | ||
> | ||
> Also, the codemod does not overwrite the existing validation rule for a given environment variable. This is done to respect in-app modifications. | ||
```ts | ||
const transformer = new CodeTransformer(appRoot) | ||
try { | ||
await transformer.defineEnvValidations({ | ||
leadingComment: 'App environment variables', | ||
variables: { | ||
PORT: 'Env.schema.number()', | ||
HOST: 'Env.schema.string()', | ||
} | ||
}) | ||
} catch (error) { | ||
console.error('Unable to define env validations') | ||
console.error(error) | ||
} | ||
``` | ||
Output | ||
```ts | ||
import { Env } from '@adonisjs/core/env' | ||
export default await Env.create(new URL('../', import.meta.url), { | ||
PORT: Env.schema.number(), | ||
HOST: Env.schema.string(), | ||
}) | ||
``` | ||
### addMiddlewareToStack | ||
Register AdonisJS middleware to one of the known middleware stacks. The method accepts the middleware stack and an array of middleware to register. | ||
The middleware stack could be one of `server | router | named`. | ||
> [!IMPORTANT] | ||
> This codemod expects the `start/kernel.ts` file to exist and must have a function call for the middleware stack for which you are trying to register a middleware. | ||
```ts | ||
const transformer = new CodeTransformer(appRoot) | ||
try { | ||
await transformer.addMiddlewareToStack('router', [ | ||
{ | ||
path: '@adonisjs/core/bodyparser_middleware' | ||
} | ||
]) | ||
} catch (error) { | ||
console.error('Unable to register middleware') | ||
console.error(error) | ||
} | ||
``` | ||
Output | ||
```ts | ||
import router from '@adonisjs/core/services/router' | ||
router.use([ | ||
() => import('@adonisjs/core/bodyparser_middleware') | ||
]) | ||
``` | ||
You may define named middleware as follows. | ||
```ts | ||
const transformer = new CodeTransformer(appRoot) | ||
try { | ||
await transformer.addMiddlewareToStack('named', [ | ||
{ | ||
name: 'auth', | ||
path: '@adonisjs/auth/auth_middleware' | ||
} | ||
]) | ||
} catch (error) { | ||
console.error('Unable to register middleware') | ||
console.error(error) | ||
} | ||
``` | ||
### updateRcFile | ||
Register `providers`, `commands`, define `metaFiles` and `commandAliases` to the `adonisrc.ts` file. | ||
> [!IMPORTANT] | ||
> This codemod expects the `adonisrc.ts` file to exist and must have an `export default defineConfig` function call. | ||
```ts | ||
const transformer = new CodeTransformer(appRoot) | ||
try { | ||
await transformer.updateRcFile((rcFile) => { | ||
rcFile | ||
.addProvider('@adonisjs/lucid/db_provider') | ||
.addCommand('@adonisjs/lucid/commands'), | ||
.setCommandAlias('migrate', 'migration:run') | ||
}) | ||
} catch (error) { | ||
console.error('Unable to update adonisrc.ts file') | ||
console.error(error) | ||
} | ||
``` | ||
Output | ||
```ts | ||
import { defineConfig } from '@adonisjs/core/app' | ||
export default defineConfig({ | ||
commands: [ | ||
() => import('@adonisjs/lucid/commands') | ||
], | ||
providers: [ | ||
() => import('@adonisjs/lucid/db_provider') | ||
], | ||
commandAliases: { | ||
migrate: 'migration:run' | ||
} | ||
}) | ||
``` | ||
### addJapaPlugin | ||
Register a Japa plugin to the `tests/bootstrap.ts` file. | ||
> [!IMPORTANT] | ||
> This codemod expects the `tests/bootstrap.ts` file to exist and must have the `export const plugins: Config['plugins']` export. | ||
```ts | ||
const transformer = new CodeTransformer(appRoot) | ||
const imports = [ | ||
{ | ||
isNamed: false, | ||
module: '@adonisjs/core/services/app', | ||
identifier: 'app' | ||
}, | ||
{ | ||
isNamed: true, | ||
module: '@adonisjs/session/plugins/api_client', | ||
identifier: 'sessionApiClient' | ||
} | ||
] | ||
const pluginUsage = 'sessionApiClient(app)' | ||
try { | ||
await transformer.addJapaPlugin(pluginUsage, imports) | ||
} catch (error) { | ||
console.error('Unable to register japa plugin') | ||
console.error(error) | ||
} | ||
``` | ||
Output | ||
```ts | ||
import app from '@adonisjs/core/services/app' | ||
import { sessionApiClient } from '@adonisjs/session/plugins/api_client' | ||
export const plugins: Config['plugins'] = [ | ||
sessionApiClient(app) | ||
] | ||
``` | ||
## Contributing | ||
One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believes in the principles of the framework. | ||
One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believe in the framework's principles. | ||
@@ -20,3 +400,3 @@ We encourage you to read the [contribution guide](https://github.com/adonisjs/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework. | ||
## Code of Conduct | ||
In order to ensure that the AdonisJS community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/adonisjs/.github/blob/main/docs/CODE_OF_CONDUCT.md). | ||
To ensure that the AdonisJS community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/adonisjs/.github/blob/main/docs/CODE_OF_CONDUCT.md). | ||
@@ -23,0 +403,0 @@ ## License |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
164697
1942
414