Package migration

Migrate a package to v6

If you are the maintainer of an AdonisJS package, this guide is definitely for you. We will try to cover the major changes that you need to make to your package to make it compatible with AdonisJS v6.

Update Tooling

Make sure to keep an eye on pkg-starter-kit repo for the tooling configuration as inspiration for the following steps.

ESM

Just add type: module to your package.json

{
"type": "module"
}

mrm

We used to use mrm to update configuration files. We've decided to stop using mrm as it's no longer really maintained and was causing some problems. So make sure you remove all references to mrm from your package.json.

Eslint / Prettier

If you were using the default configuration, you would probably be using eslint-plugin-adonis to lint the codebase of your package. From now, we recommend using @adonisjs/eslint-config and @adonisjs/prettier-config. Make sure you install them as devDependencies.

pnpm add -D @adonisjs/eslint-config @adonisjs/prettier-config

Then add this to your package.json file

{
"eslintConfig": {
"extends": "@adonisjs/eslint-config/package"
},
"prettier": "@adonisjs/prettier-config"
}

Make sure you also remove .eslintrc.json and .eslintignore from your project if they exist.

Typescript

Install the following packages:

pnpm add -D ts-node @swc/core @adonisjs/tsconfig

You can also delete @adonisjs/require-ts from your package.json.

Then update your tsconfig.json like this:

{
"extends": "@adonisjs/tsconfig/tsconfig.package.json",
"compilerOptions": {
"rootDir": "./",
"outDir": "./build"
}
}

Then create a tsnode.esm.js file at the root of your project with the following contents:

/*
|--------------------------------------------------------------------------
| TS-Node ESM hook
|--------------------------------------------------------------------------
|
| Importing this file before any other file will allow you to run TypeScript
| code directly using TS-Node + SWC. For example
|
| node --import="./tsnode.esm.js" bin/test.ts
| node --import="./tsnode.esm.js" index.ts
|
|
| Why not use "--loader=ts-node/esm"?
| Because, loaders have been deprecated.
*/
import { register } from 'node:module'
register('ts-node/esm', import.meta.url)

Then make sure you run your scripts/tests/code with this script rather than @adonisjs/require-ts.

For example, to launch your tests, you can define this script in your package.json :

{
"scripts": {
"test": "node --import=./tsnode.esm.js --enable-source-maps bin/test.ts"
}
}

Another topic, but note the --enable-source-maps which allows you to have stack traces with the correct line numbers. This is a ts-node bug

Updating dependencies

Make sure you update all dependencies to @adonis and @japa in your package.json. You probably also want to add @adonisjs/core as peerDependencies :

{
"peerDependencies": {
"@adonisjs/core": "^6.2.0"
}
}

Package configuration

Previously, we used an instructions.ts to allow the user to configure the package at installation via node ace configure my-package. We've changed the way it works.

You can delete the adonisjs field from your package.json which is no longer in use, also as the instructions.ts and instructions.md files. ( Keep instruction.ts though, as part of it can be re-used )

Now, to have this configuration logic, you'll need to define a configure method, and re-export it from the entrypoint ( index.ts ) of your package like this :

This configure method will be called by Adonis when the user launches node ace configure my-package. One point to note is that @adonisjs/sink is deprecated. Instead, the configure method receives a ConfigureCommand object as a parameter, which does the same thing as @adonisjs/sink but with a more powerful API.

There's a lot to say about the new API, so I recommend that you:

Stubs/templates

Before, we used to use a templates folder in which we put .txt files which were copied with sink via instructions.ts file. Now you need to :

  • Create a stubs folder at the root of your project.

  • Add a main.ts to this folder containing this code:

    import { dirname } from 'node:path'
    import { fileURLToPath } from 'node:url'
    /**
    * Path to the root directory where the stubs are stored. We use
    * this path within commands and the configure hook
    */
    export const stubsRoot = dirname(fileURLToPath(import.meta.url))
  • Then you can create a .stub file in this folder. Let's imagine a config.stub that should be copied into the config folder of the user's project. You can do it like this:

    {
    {
    {
    exports({ to: app.configPath('my_package_config.ts') })
    }
    }
    }
    import { defineConfig } from 'my-package'
    export default defineConfig({
    // ...
    myProperty: '{{ myProperty }}',
    })

    You can read more about stub syntax here: https://docs.adonisjs.com/guides/scaffolding#using-stubs

  • Next, make sure you copy your stub files to the final build folder at build time. You can do this as follows:

    {
    "scripts": {
    "copy:templates": "copyfiles \"stubs/**/*.stub\" build",
    "build": "tsc",
    "postbuild": "npm run copy:templates"
    }
    }

And that's it. Now, in your configure.ts file, you can do like this to publish the config file in the user's project:

import { stubsRoot } from './stubs/main.js'
import type Configure from '@adonisjs/core/commands/configure'
export async function configure(command: Configure) {
const codemods = await command.createCodemods()
await codemods.makeUsingStub(stubsRoot, 'config.stub', {
myProperty: 'foo', // Will be passed to the stub template
})
}

Providers / Services

The major change here will be the way dependencies are injected and resolved via the IoC container. Be sure to read the @adonisjs/fold changelog before continuing. There's a specific section for package maintainers:

Make sure you also read the following documentation:

If you ever maintain a package that works with a driver system, I also invite you to read this post on Config Providers which will be essential for you:

You should know everything you need to know to migrate your provider.

adonis-typings

If you've been following the previous section, you'll notice that we no longer use @ioc imports at all. The user will import your functions/services/classes directly from your package. As a result, the adonis-typings folder and its contents are no longer needed, which is a real time-saver for you, and also much simpler to maintain, as there's no additional place to update just for having correct typings.

Commands

In case your package provides some commands to the user's application, you have a few modifications to make.

Previously:

  • You defined your commands in adonisjs.commands of your package.json. As a reminder, the adonisjs property in package.json is now deprecated. You can delete it.
  • You had a commands/index.ts which exported an array of commands. This file is no longer used. You can delete it.

With V6, your package will have to expose a commands export path as follows in the package.json :

{
"exports": {
".": "./build/index.js",
"./commands": "./build/commands/main.js",
}
}

Then, in your postbuild, you now need to use adonis-kit, which is a CLI tool exposed by @adonisjs/core that will index your commands. You can do it like this:

{
"scripts": {
"copy:templates": "copyfiles \"stubs/**/*.stub\" build",
"build": "tsc",
"postbuild": "npm run copy:templates && npm run index:commands",
"index:commands": "adonis-kit index build/commands"
}
}

And that's it.

Tests

Make sure you upgrade to Japa V3. Read the migration guide here: https://japa.dev/docs/uprade-guide

Otherwise, I invite you to take a look at the tests of different official package to see how they are now written. Here are a few key points: