Version 4 of Tool Kit lets you configure it much more easily for your team's or project's specific use cases, without having to resort to custom plugins as often. Although we've made it as backwards compatible as possible, there are some fundamental changes to its internals and configuration that mean you'll need to update your repo's .toolkitrc.yml
and any custom plugins when migrating to Tool Kit v4.
Note
For a typical repository, this migration will take ~1hr. More complex repositories (e.g. ones with custom plugins or other non-standard use cases) will take longer. If at any point you get stuck or something unexpected happens, reach out to the Platforms team and we'll be happy to help.
Update dotcom-tool-kit
and all the @dotcom-tool-kit/*
packages you have installed to their latest versions. This can be done with the following command (requires jq
):
npm query ":root > [name*=dotcom-tool-kit]" \
| jq '.[] | .name + "@latest"' \
| xargs npm install
If the npm install
fails with an error suggesting the use of --force
, edit the command to call xargs npm install --force
last instead and run again. This should fix npm's version resolution and shouldn't result in any version errors.
what
npm query ":root > [name*=dotcom-tool-kit]"
: get all packages installed as a direct dependency of your repo withdotcom-tool-kit
in their namejq '(.[] | .name + "@latest")'
: output a list of just thename
from the packages, appending@latest
xargs npm install
: pass those as arguments to thenpm install
command
So if you have dotcom-tool-kit
, @dotcom-tool-kit/jest
and @dotcom-tool-kit/webpack
installed, this is equivalent to running:
npm install dotcom-tool-kit@latest @dotcom-tool-kit/jest@latest @dotcom-tool-kit/webpack@latest
(but without you having to manually type that out).
Note
Why? A Tool Kit "command" is a name for a group of tasks to run, like test:local
. In previous versions of Tool Kit, hooks both defined names of commands to run, and managed configuration files. In Tool Kit v4, hooks only manage configuration files, and commands are defined in the Tool Kit configuration files, not in plugin code.
In your .toolkitrc.yml
, you may have a hooks
object, which specifies which Tool Kit tasks to run for commands. Rename this object to commands
:
-hooks:
+commands:
test:local:
- Eslint
- JestLocal
test:ci:
- Eslint
- JestCI
run:local:
- Nodemon
- NextRouter
Note
Why? In previous versions of Tool Kit, options were plugin-wide. In Tool Kit v4, options can be specified separately for plugins, tasks, and hooks. Although Tool Kit v4 can load a .toolkitrc.yml
with the old options structure, some options have moved to task-specific options, and so your old options won't validate against the new schema.
In your .toolkitrc.yml
, you'll have an options
object. Move this object so it's nested another level deeper, under a plugins
key in options
:
options:
+ plugins:
"@dotcom-tool-kit/next-router":
appName: article
"@dotcom-tool-kit/eslint":
files:
- "**/*.{js,jsx,yaml,yml,json}"
"@dotcom-tool-kit/doppler":
project: next-article
"@dotcom-tool-kit/heroku":
pipeline: ft-next-article
systemCode: next-article
scaling:
ft-next-article-eu:
web:
size: standard-2X
quantity: 5
Run npx dotcom-tool-kit --help
to validate your config. With this example, Tool Kit warns us that the @dotcom-tool-kit/eslint
options have completely moved, and some of the @dotcom-tool-kit/heroku
options have moved:
There are options in your
.toolkitrc.yml
that aren't what Tool Kit expected.
⚠️ 1 issue in@dotcom-tool-kit/eslint
:
- options for the
@dotcom-tool-kit/eslint
plugin have moved tooptions.tasks.Eslint
⚠️ 1 issue in@dotcom-tool-kit/heroku
:
- the option
scaling
has moved tooptions.tasks.HerokuProduction.scaling
Please update the options so that they are the expected types. You can refer to the README for the plugin for examples and descriptions of the options used.
So we need to move these options from the sections in options.plugins
to a new options.tasks
section:
options:
plugins:
"@dotcom-tool-kit/next-router":
appName: article
- "@dotcom-tool-kit/eslint":
- files:
- - "**/*.{js,jsx,yaml,yml,json}"
"@dotcom-tool-kit/doppler":
project: next-article
"@dotcom-tool-kit/heroku":
pipeline: ft-next-article
systemCode: next-article
- scaling:
- ft-next-article-eu:
- web:
- size: standard-2X
- quantity: 5
+ tasks:
+ Eslint:
+ files:
+ - "**/*.{js,jsx,yaml,yml,json}"
+ HerokuProduction:
+ scaling:
+ ft-next-article-eu:
+ web:
+ size: standard-2X
+ quantity: 5
Note
As well as options that have completely moved, some options have been removed entirely. It's safe to keep these in your .toolkitrc.yml
, and Tool Kit won't warn about them. Check the readmes for the plugins you're using to see which options are still required.
Note
Why? In previous versions of Tool Kit, options were plugin-wide. In Tool Kit v4, we have task-specific options, and these can be overriden depending on which command is running a task. This means instead of needing to have e.g. separate JestLocal
and JestCi
tasks, we can have a single Jest
task, with a boolean ci
option that can be set to true
for the test:ci
command only.
Run npx dotcom-tool-kit --help
again. If you have any commands in your .toolkitrc.yml
that have been renamed, you'll get a message like:
These tasks don't exist, but are configured to run from commands:
JestLocal
(assigned totest:local
by your app)JestCi
(assigned totest:ci
by your app)They could be misspelt, or defined by a Tool Kit plugin that isn't used by this app.
In your .toolkitrc.yml
, rename the tasks to their v4 equivalents, and specify the appropriate options so you get equivalent behaviour to the old task:
commands:
test:local:
- - JestLocal
+ - Jest
test:ci:
- - JestCi
+ - Jest:
+ ci: true
Full list of renamed tasks
This is a list of all tasks that have been renamed, their v4 equivalents, and the options you need to specify for the same behaviour:
Renamed task | Tool Kit v4 equivalent | Options to set |
---|---|---|
BabelDevelopment |
Babel |
envName: 'development' |
BabelProduction |
Babel |
envName: 'production' |
CypressCi |
Cypress |
{} |
CypressLocal |
Cypress |
{} |
JestCi |
Jest |
ci: true |
JestLocal |
Jest |
{} |
TypeScriptBuild |
TypeScript |
{} |
TypeScriptTest |
TypeScript |
noEmit: true |
TypeScriptWatch |
TypeScript |
watch: true |
WebpackDevelopment |
Webpack |
envName: 'development' |
WebpackProduction |
Webpack |
envName: 'production' |
WebpackWatch |
Webpack |
watch: true, envName: 'development' |
Tip
If you only use built-in Tool Kit plugins, you can skip to step 3.
Built-in plugins are listed in your .toolkitrc.yml
with npm package names, like @dotcom-tool-kit/webpack
; custom plugins are referenced by relative file path, like ./tool-kit/canary
.
Yes, I have custom plugins
A plugin's .toolkitrc.yml
acts as a manifest to tell Tool Kit what to load. To prevent issues when attempting to load an old plugin, there is now a manifest version. A .toolkitrc.yml
without a version is implicitly version 1. Tool Kit v4 will only load plugins with a version 2 manifest.
Add the version
to your plugin's .toolkitrc.yml
(most plugins' .toolkitrc.yml
files will be empty before this):
+version: 2
First, check to see if your custom task can now be provided by a built-in plugin. For example, if you're defining a custom task so you can run the same task multiple times with different options, this can now be done via command-specific task options:
commands:
test:local:
- Jest
test:e2e-local:
- Jest:
configFile: jest.e2e.config.js
The @dotcom-tool-kit/types
package, which previously exported the base classes for Tasks and Hooks, has been split up into multiple packages. Replace this package with @dotcom-tool-kit/base
in your dependencies:
$ npm remove @dotcom-tool-kit/types
$ npm install --save-dev @dotcom-tool-kit/base
And in your plugin module:
-const { Task } = require('@dotcom-tool-kit/types')
+const { Task } = require('@dotcom-tool-kit/base')
Tool Kit no longer eagerly loads your plugin's Javascript entrypoint. The plugin .toolkitrc.yml
must now list the tasks your plugin defines, and you can only export one task per entrypoint.
Let's say your plugin has an index.js
that exports two tasks:
const { Task } = require('@dotcom-tool-kit/base')
class CustomTaskOne extends Task {
run() {}
}
class CustomTaskTwo extends Task {
run() {}
}
exports.tasks = [CustomTaskOne, CustomTaskTwo]
You'll need to split this into multiple files:
custom-task-one.js | custom-task-two.js |
---|---|
const { Task } = require('@dotcom-tool-kit/base')
class CustomTaskOne extends Task {
run() {}
}
module.exports = CustomTaskOne |
const { Task } = require('@dotcom-tool-kit/base')
class CustomTaskTwo extends Task {
run() {}
}
module.exports = CustomTaskTwo |
You'll then need to specify these task entry points in your plugin's .toolkitrc.yml
:
version: 2
tasks:
CustomTaskOne: ./custom-task-one.js
CustomTaskTwo: ./custom-task-two.js
The keys in the .toolkitrc.yml
are what determines the task name visible to Tool Kit, which was previously determined by the actual class name, so make sure they match.
Your existing custom task may have been using this.options
to reference options from your .toolkitrc.yml
. Now that we have separate plugin-wide and task-specific options, this.options
in tasks refers to the task-specific options. You'll need to either change your task to reference this.pluginOptions
to access the plugin-wide options, or move your .toolkitrc.yml
options for your plugin to task-specific options. Which you choose depends on the use cases for your plugin's options:
task-specific options | plugin-wide options | |
---|---|---|
when to use | options that could be different if the task could be reused for different use cases, e.g. a path to a config file | options that would be the same for every task in a plugin no matter when they're being run, e.g. a system code |
|
options:
tasks:
CustomTask:
config: 'path/to/config.js'
commands:
build:local:
- CustomTask
build:ci:
- CustomTask:
config: 'path/to/ci-config.js' |
options:
plugins:
'./tool-kit/custom-plugin':
systemCode: 'next-article' |
your custom task |
class CustomTask extends Task {
run() {
// { config: "path/to/config.js" },
// or, if run from `build:ci`,
// { config: "path/to/ci-config.js" }
this.options
}
} |
class CustomTask extends Task {
run() {
// { systemCode: "next-article" }
this.pluginOptions
}
} |
It's possible to mix and match task-specific and plugin-wide options, so if you have some options that fall under one use case and some under the other, you should split the options up as appropriate.
If you're defining a "placeholder hook" so that you have a custom Tool Kit command you can run, this is no longer required; commands can be defined in your repo's .toolkitrc.yml
. A placeholder hook will look something like this:
const { Hook } = require('@dotcom-tool-kit/types')
class Placeholder extends Hook {
check() {
return true
}
install() {}
}
exports.hooks = {
'custom:command': Placeholder
}
With Tool Kit v4, you no longer need to define this command as a hook to run it; specifying commands that run tasks in your .toolkitrc.yml
is all that's needed. Your placeholder hook can be deleted.
Instead of needing to define new package.json
scripts or CircleCI jobs in plugins, you can now do this via hook configuration options in your repo's main .toolkitrc.yml
.
A custom package.json
hook might look like this:
const { PackageJsonHook } = require('@dotcom-tool-kit/package-json-hook')
class DemoPublishHook extends PackageJsonHook {
constructor() {
super(...arguments)
this.key = 'demo-publish'
this.hook = 'demo:publish'
}
}
exports.hooks = { 'demo:publish': DemoPublishHook }
This manages package.json
to add a scripts["demo-publish"]
that calls dotcom-tool-kit demo:publish
. To replicate this, add this configuration in your repo's .toolkitrc.yml
:
options:
hooks:
- PackageJson:
scripts:
demo-publish: 'demo:publish'
A custom .circleci/config.yml
hook might look like this:
const CircleCiConfigHook = require('@dotcom-tool-kit/circleci/lib/circleci-config');
const { TestCI } = require('@dotcom-tool-kit/circleci/lib/index');
class DeployProduction extends CircleCiConfigHook.default {
constructor () {
super(...arguments);
this.job = 'tool-kit/deploy-production';
this.jobOptions = {
requires: [new TestCI(this.logger).job],
filters: { branches: { only: 'main' } }
};
}
}
exports.hooks = {
'deploy:production': DeployProduction
}
This adds a tool-kit/deploy-production
job that depends on the tool-kit/test-ci
job. To replicate this, add this configuration in your repo's .toolkitrc.yml
:
options:
hooks:
- CircleCi:
jobs:
- name: deploy-production
command: 'deploy:production'
workflows:
- name: 'tool-kit'
jobs:
- name: 'deploy-production'
requires:
- 'test'
custom:
filters:
branches:
only: main
You'll need to talk to the Platforms team to get support with your migration.
Once npx dotcom-tool-kit --help
is no longer reporting any configuration or plugin errors, run npx dotcom-tool-kit --install
. This will regenerate your package.json
and .circleci/config.yml
. If you've previously opted out of having your .circleci/config.yml
, the new version of Tool Kit might not be able to validate it; get in touch with the Platforms team and we'll help you update it. Note that Tool Kit v4 makes CircleCI config file generation much more configurable and flexible, so in many cases you'll no longer have to opt out of config file management.
Try running local tasks such as npm run build
, npm test
, and npm start
. If everything's gone well, they'll run successfully; otherwise, Tool Kit should provide an error message that explains how to fix what's wrong. If you're stuck, talk to us. Commit your changes and open a pull request with them to test your CircleCI workflow is working as expected, and when you merge the PR, keep a close eye on the main
branch build.