Migrate
skuba migrate help
Echoes the available skuba migrations.
skuba migrate help
skuba migrate node
skuba includes migrations to upgrade your project to the active LTS version of Node.js. This is intended to minimise effort required to keep up with annual Node.js releases.
The following files are scanned:
.node-version.nvmrcpackage.jsonstsconfig.jsons- Buildkite pipelines in
.buildkite/directories - CDK files in
infra/directories - Dockerfiles & Docker Compose files
- Serverless files
skuba may not be able to upgrade all projects, and typically works best when a project closely matches a built-in template. Check your project for files that may have been missed, review and test the modified code as appropriate before releasing to production, and open an issue if your project files were corrupted by the migration. Exercise particular caution with monorepos, as some may have employed unique configurations that the migration has not accounted for.
The migration will attempt to proceed if your project:
-
Specifies a Node.js version in
.node-version,.nvmrc, and/orpackage.json#/engines/node -
Does not include a
package.json#/filesfieldThis field implies that your project is an npm package. See below for considerations when manually upgrading npm packages.
-
Specifies a project type in
package.json#/skuba/typethat is notpackageWell-known project types currently include
applicationandpackage. While we intend to improve support for monorepo projects in a future version, you may enable migrations in the interim by setting your root/package.jsonproject type toroot.
skuba upgrades your tsconfig.jsons in line with the official Node Target Mapping guidance. tsconfig.jsons contain two options that are linked to Node.js versions:
-
libconfigures the language features available to your source code.For example, including
ES2024allows you to use theObject.groupBy()static method. The features available in each new ECMAScript version are summarised on node.green. -
targetconfigures the transpilation behaviour of the TypeScript compiler.Back-end applications typically synchronise
libwith their Node.js runtime. In this scenario, there is no need to transpile language features andtargetcan match the ECMAScript version inlib.On the other hand, you may wish to use recent language features when authoring your npm packages while retaining support for package consumers on older Node.js runtimes. In this scenario, see the note below on transpilation for npm packages.
As of skuba 14, for npm packages, we will attempt to upgrade your targets to be 1 major version behind the current LTS Node.js version.
For example, when upgrading a project to Node.js 24, we will upgrade npm packages to target Node.js 22.
To ensure accurate detection of npm packages, set the skuba.type field in your package.json to package for npm packages and application for applications.
The following fields are modified for npm packages:
-
package.json#/engines/nodeThe
enginesproperty propagates to your package consumers. For example, if you specify a minimum Node.js version of 22, it will prevent your package from being installed in a Node.js 20 environment:{ "engines": { "node": ">=22" } }Take care with the
enginesproperty of an npm package; modifications typically necessitate a new major release per semantic versioning. -
tsconfig.json#/targetRefer to the official Node Target Mapping guidance and ensure that the transpilation target corresponds to the minimum Node.js version in
engines.For monorepo projects, check whether your npm packages inherit from another
tsconfig.json. You may need to define explicit overrides for npm packages like so:{ + "compilerOptions": { + "removeComments": false, + "target": "ES2023" // Continue to support package consumers on Node.js 20 + }, "extends": "../../tsconfig.json" }
skuba format and skuba lint will automatically run these migrations as patches.
As of skuba 14, skuba migrate attempts to upgrade underlying infrastructure packages for compatibility with the new Node.js version. These include aws-cdk-lib, datadog-cdk-constructs-v2, osls, serverless, serverless-plugin-datadog and @types/node.
skuba migrate node24
Attempts to automatically upgrade your project to Node.js 24 and your package targets to Node.js 22.14.0+.
skuba migrate node24
Node.js 24 includes breaking changes. For more information on the upgrade, refer to:
- The Node.js release notes
- The AWS release announcement for the Lambda
nodejs24.xruntime update
skuba migrate node22
Attempts to automatically upgrade your project to Node.js 22.
skuba migrate node22
Node.js 22 includes breaking changes. For more information on the upgrade, refer to:
- The Node.js release notes
- The AWS release announcement for the Lambda
nodejs22.xruntime update
You may need to manually upgrade CDK and Serverless package versions as appropriate to support nodejs22.x, and @types/node to major version 22.
skuba migrate node20
Attempts to automatically upgrade your project to Node.js 20.
skuba migrate node20
Node.js 20 includes breaking changes. For more information on the upgrade, refer to:
- The Node.js release notes
- The AWS release announcement for the Lambda
nodejs20.xruntime update
You may need to manually upgrade CDK and Serverless package versions as appropriate to support nodejs20.x, and @types/node to major version 20.
skuba migrate esm
Attempts to automatically migrate your project from CommonJS to ESM. Before running the migration, follow the migration steps.
skuba migrate esm
The following changes are made:
- type
moduleis added topackage.jsonfiles - CommonJS syntax is replaced with ESM syntax in source files, test files, and configuration files
- AWS CDK worker and Serverless files are migrated to ESM format
- ESLint config files and Prettier config files are migrated to ESM format
- Jest is replaced with Vitest as the test runner
- The sku codemod is run along with additional transformations to fix additional cases
aws-sdk-client-mock-jest→aws-sdk-client-mock-vitest+@types/node@shopify/jest-koa-mocks→@skuba-lib/vitest-koa-mocks+@types/node--runInBand→--maxWorkers=1inpackage.jsontest scripts and Buildkite pipelinesjest.config.*tsfiles are migrated tovitest.config.tson a best-effort basis- Jest hooks are migrated to Vitest hooks on a best-effort basis
Due to the complexities of test code and configurations, the migration may not be able to modify all files in your project.
If you are running this migration for a non-skuba application, you will need to manually install vitest, and @vitest/coverage-istanbul as dev dependencies.
Post Migration Steps
- Run
skuba lintand attempt to address any lint errors that may be caused by the migration. The most common failure points withskuba testruns can normally be addressed by fixing the lint errors first.
If you notice there are changes you can make prior to running the skuba migration, we suggest making those changes first and then re-running the migration for the ease of reviewing the migration changes.
If you notice any repeatable issues that the migration has not accounted for, please open an issue or reach out in #skuba-support.
- Review
vitest.config.tsmigrations
Review your generated vitest.config.ts and Vitest setup files against the original jest.config.ts and Jest setup files to verify all configuration has been carried across, paying close attention to any custom settings or patterns that the migration may have missed. Once satisfied, delete the jest.config.ts and any Jest setup files.
The migration may also leave some manual steps for you to complete within your vitest.config.ts files, such as updating existing regexp patterns to glob patterns in your test configuration.
// vitest.config.ts
export default defineConfig({
test: {
exclude: ['\\.int\\.test'], // TODO: Update these regexp pattern strings to globs
},
});
These should be easily migrated by hand or with the assistance of an AI agent such as Copilot with a prompt such as
Address the TODO comments in vitest.config files
-
Run
skuba testand attempt to address any test errors that may be caused by the migration -
Run and deploy your project as normal, and monitor for any issues that may be caused by the migration.
-
If your project deploys a package, ensure you test the published package in a downstream project to confirm it works as expected.
FAQ and Tips
Config consolidation
If you have multiple jest.config.ts files, you may be able to consolidate these into a single vitest.config.ts file with multiple projects
Example:
If you have the following Jest config files:
jest.config.tsjest.config.int.tswhere integration tests must be run with--runInBanddue to shared resources
You may be able to consolidate these into a single vitest.config.ts file with multiple projects like so:
// vitest.config.ts
export default defineConfig(
Vitest.mergePreset({
ssr: {
resolve: {
conditions: ['@seek/YOUR_REPO/source'],
},
},
test: {
env: {
ENVIRONMENT: 'test',
},
projects: [
{
extends: true,
test: {
name: 'unit',
exclude: ['**/*.int.test.ts'],
},
},
{
extends: true,
test: {
name: 'integration',
fileParallelism: false, // Equivalent to --runInBand
setupFiles: ['vitest.setup.int.ts'],
include: ['**/*.int.test.ts'],
},
},
],
},
}),
);
Performance
By default, Vitest runs every test in isolation to provide a side-effect free testing environment. However, this is not always necessary and can lead to slower test runs compared to Jest.
Follow the Vitest improving performance guide to optimise your Vitest configuration
Matchers not matching on errors
Vitest matches deeper than Jest so you may need to adjust your test assertions. Previously, you were able to match on error messages with Jest like so but you may need to adjust your tests to match on the error object instead of just the message with Vitest:
- await expect(someFunction()).rejects.toThrow(new Error('some error message'));
+ await expect(someFunction()).rejects.toThrow(expect.objectContaining(new Error('some error message')));
// or
+ await expect(someFunction()).rejects.toThrow(new ActualError('some error message'));
Jest spies no longer work after the migration
- Run
pnpm dlx @skuba-lib/detect-invalid-spies .
Spies work differently in Vitest compared to Jest. You can read more about the differences here in our @skuba-lib/detect-invalid-spies documentation.
This will identify any spies in your code that may be broken by the migration. If there are any issues detected, you will need to address these before proceeding with the migration.
For other Jest-specific patterns, you can refer to the migration guide provided by Vitest for more information on how to migrate your tests.
Cannot find module ‘some-module/type’ or its corresponding type declarations.ts(2307)
The ESLint rule introduced in previous skuba versions would quit evaluating imports very early to avoid long ESLint run times which means a few imports may be now invalid imports in ESM. The fix is as simple as adding a .js extension to the end of the import path:
- import { type } from '@seek/some-module/lib-types/types/type.generated';
+ import { type } from '@seek/some-module/lib-types/types/type.generated.js';
Jest Dynalite
If you were using jest-dynalite for testing DynamoDB interactions, you will need to switch to Vitest dynalite lite which provides similar functionality for Vitest.
The recommended setupFiles can slow down your test suite when run against all tests. To avoid this, configure a dedicated Vitest project for test files that use Dynalite.
Example:
// vitest.config.ts
export default defineConfig(
Vitest.mergePreset({
ssr: {
resolve: {
conditions: ['@seek/YOUR_REPO/source'],
},
},
test: {
env: {
ENVIRONMENT: 'test',
},
projects: [
{
extends: true,
test: {
name: 'unit',
exclude: ['**/*.dynalite.test.ts'],
},
},
{
extends: true,
test: {
name: 'dynalite',
setupFiles: ['vitest-dynamodb-lite'],
include: ['**/*.dynalite.test.ts'],
},
},
],
},
}),
);
DataDog Trace Headers
You may notice Datadog trace headers being emitted in your test output after the migration. This is because Vitest runs tests in a more realistic environment which may cause some of your code to execute differently compared to Jest.
+ "x-datadog-parent-id": "6421394243863276142",
+ "x-datadog-sampling-priority": "-1",
+ "x-datadog-tags": "_dd.p.tid=69f895eb00000000,_dd.p.ksr=0",
+ "x-datadog-trace-id": "6421394243863276142",
You can suppress these headers by adding the following to your Vitest setup file:
export default defineConfig({
test: {
env: {
ENVIRONMENT: 'test',
+ DD_TRACE_ENABLED: 'false',
},
},
});
Esbuild
If you were using esbuild directly in your project, you may need to update your esbuild configuration to ensure it is compatible with ESM.
Of note, you may need to update the conditions, mainFields, format and external or plugins options in your esbuild configuration to ensure that it correctly resolves ESM modules.
esbuild.build({
// ...
conditions: [
'@seek/YOUR_REPO/source',
+ 'module'
],
+ mainFields: ['module', 'main'],
+ format: 'esm',
+ external: ['pino']
// or
+ plugins: [esbuildPluginPino()]
});
Coverage reports are different after the migration
Vitest transforms your code differently to Jest which may result in different coverage reports after the migration. You may need to experiment with placing /* istanbul ignore */ comments in different places in your code to achieve the desired coverage report.
transport:
- /* istanbul ignore next */
config.environment === 'local'
+ ? /* istanbul ignore next */ { target: 'pino-pretty' }
: undefined,
We are unsure whether this is intended behaviour or if there is a bug in the Vitest Istanbul and v8 coverage providers.
You may also find some luck with using the /* istanbul ignore start */ and /* istanbul ignore end */ comments
For the keen observers, we have decided to ease the migration by firstly adopting the istanbul provider for coverage in Vitest instead of the default v8 provider. The v8 provider will be made the default in a future release once we have mostly migrated our codebase to ESM and can confirm it works as expected.