Skip to content

Commit aa62967

Browse files
feat: migrate to ESM and vitest (#4461)
## Description This PR migrates the TypeScript testing framework from Jest to Vitest and transitions the codebase from CommonJS (CJS) to ECMAScript Modules (ESM). **Motivation: ** * **Moving from Jest to Vitest:** * **Performance:** Vitest leverages Vite's build pipeline, resulting in significantly faster test execution, especially in larger projects. This speed improvement enhances developer productivity by providing quicker feedback loops. * **Vite Integration:** For projects already using Vite, Vitest offers seamless integration, reducing configuration overhead and providing a unified development experience. * **Modern tooling:** Vitest is a modern test framework that is actively being developed and provides a better developer experience. * **Migrating from CJS to ESM:** * **Modern JavaScript:** ESM is the official standard for module systems in modern JavaScript, offering better static analysis, tree-shaking, and overall performance. * **Improved Compatibility:** ESM is the future of JavaScript modules and ensures better compatibility with modern tooling and libraries. * **Simplified Dependency Management:** ESM's static imports allow for more efficient dependency management and better optimization by bundlers. **Changes:** * Replaced Jest with Vitest for unit testing. * Updated test files to adhere to Vitest syntax. * Updated configuration files to reflect the changes (e.g., `package.json`, `tsconfig.json`). **Testing:** * The changes should not introduce any functional regressions. Thoroughly verify existing test suites using Vitest. * It is advisable to clear any local cache, such as node\_modules and dist folders, before building the lambda functions yourself. * Chages are tested with * Default example * Multi runner example **Related Issues:** * Fixes #3964 * Fixes #4366 **Potential Risks:** * While the migration aims for seamless transition, there's a possibility of encountering minor compatibility issues during the initial build or test runs. Thorough testing and cache clearing should mitigate these risks. **Additional Notes:** * This migration modernizes the project's tooling and module system, laying a foundation for future improvements and maintainability. --------- Co-authored-by: Nadav Sinai <[email protected]>
1 parent ce458fe commit aa62967

File tree

82 files changed

+2778
-3330
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+2778
-3330
lines changed

Diff for: .devcontainer/devcontainer.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
"dbaeumer.vscode-eslint",
1717
"editorconfig.editorconfig",
1818
"esbenp.prettier-vscode",
19-
"firsttris.vscode-jest-runner",
2019
"hashicorp.hcl",
2120
"hashicorp.terraform",
2221
"hashicorp.terraform",
23-
"orta.vscode-jest",
22+
"vitest.explorer",
2423
"yzhang.markdown-all-in-one"
2524
]
2625
}

Diff for: .vscode/extensions.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
"editorconfig.editorconfig",
77
"yzhang.markdown-all-in-one",
88
"hashicorp.terraform",
9-
"firsttris.vscode-jest-runner"
9+
"vitest.explorer"
1010
]
1111
}

Diff for: .vscode/gh-runners.code-workspace

+1-7
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,5 @@
88
"name": "🚀 lambdas",
99
"path": "../lambdas"
1010
}
11-
],
12-
"settings": {
13-
"jest.autoRun": "on",
14-
"jest.disabledWorkspaceFolders": [
15-
"✨ root"
16-
]
17-
}
11+
]
1812
}

Diff for: .vscode/settings.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
{}
1+
{
2+
"jest.enable": false
3+
}

Diff for: lambdas/.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node_modules/
22
build/
3-
dist/
3+
**/dist/
44
*.log
55

66
# Ignore all yarn.lock files except the one in the root

Diff for: lambdas/.vscode/settings.json

-5
This file was deleted.

Diff for: lambdas/aws-vitest-setup.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Setup AWS SDK client mock matchers
2+
import 'aws-sdk-client-mock-jest/vitest';

Diff for: lambdas/functions/ami-housekeeper/jest.config.ts

-17
This file was deleted.

Diff for: lambdas/functions/ami-housekeeper/package.json

+8-23
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,33 @@
22
"name": "@aws-github-runner/ami-housekeeper",
33
"version": "1.0.0",
44
"main": "lambda.ts",
5+
"type": "module",
56
"license": "MIT",
67
"scripts": {
78
"start": "ts-node-dev src/local.ts",
89
"test": "NODE_ENV=test nx test",
910
"test:watch": "NODE_ENV=test nx test --watch",
10-
"lint": "yarn eslint src",
11+
"lint": "eslint src",
1112
"watch": "ts-node-dev --respawn --exit-child src/local.ts",
1213
"build": "ncc build src/lambda.ts -o dist",
13-
"dist": "yarn build && cd dist && zip ../ami-housekeeper.zip index.js",
14+
"dist": "yarn build && cp package.json dist/ && cd dist && zip ../ami-housekeeper.zip *",
1415
"format": "prettier --write \"**/*.ts\"",
1516
"format-check": "prettier --check \"**/*.ts\"",
1617
"all": "yarn build && yarn format && yarn lint && yarn test"
1718
},
1819
"devDependencies": {
19-
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
20-
"@types/aws-lambda": "^8.10.146",
21-
"@types/jest": "^29.5.14",
22-
"@types/node": "^22.13.9",
23-
"@typescript-eslint/eslint-plugin": "^8.25.0",
24-
"@typescript-eslint/parser": "^8.25.0",
20+
"@aws-sdk/types": "^3.734.0",
21+
"@types/aws-lambda": "^8.10.147",
2522
"@vercel/ncc": "^0.38.3",
2623
"aws-sdk-client-mock": "^4.1.0",
27-
"aws-sdk-client-mock-jest": "^4.1.0",
28-
"eslint": "^8.57.0",
29-
"eslint-plugin-prettier": "5.2.3",
30-
"jest": "^29.7.0",
31-
"jest-mock": "^29.7.0",
32-
"jest-mock-extended": "^3.0.7",
33-
"nock": "^14.0.1",
34-
"prettier": "3.4.2",
35-
"ts-jest": "^29.2.5",
36-
"ts-node": "^10.9.2",
37-
"ts-node-dev": "^2.0.0"
24+
"aws-sdk-client-mock-jest": "^4.1.0"
3825
},
3926
"dependencies": {
4027
"@aws-github-runner/aws-powertools-util": "*",
4128
"@aws-github-runner/aws-ssm-util": "*",
42-
"@aws-sdk/client-ec2": "^3.764.0",
29+
"@aws-sdk/client-ec2": "^3.767.0",
4330
"@aws-sdk/client-ssm": "^3.759.0",
44-
"@aws-sdk/types": "^3.734.0",
45-
"cron-parser": "^4.9.0",
46-
"typescript": "^5.7.3"
31+
"cron-parser": "^4.9.0"
4732
},
4833
"nx": {
4934
"includedScripts": [

Diff for: lambdas/functions/ami-housekeeper/src/ami.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import {
1414
SSMClient,
1515
} from '@aws-sdk/client-ssm';
1616
import { mockClient } from 'aws-sdk-client-mock';
17-
import 'aws-sdk-client-mock-jest';
17+
import 'aws-sdk-client-mock-jest/vitest';
1818

1919
import { AmiCleanupOptions, amiCleanup, defaultAmiCleanupOptions } from './ami';
20+
import { describe, it, expect, beforeEach, vi } from 'vitest';
2021

2122
process.env.AWS_REGION = 'eu-east-1';
2223
const deleteAmisOlderThenDays = 30;
@@ -76,7 +77,7 @@ const ssmParameters: DescribeParametersCommandOutput = {
7677

7778
describe("delete AMI's", () => {
7879
beforeEach(() => {
79-
jest.resetAllMocks();
80+
vi.resetAllMocks();
8081
mockEC2Client.reset();
8182
mockSSMClient.reset();
8283

Diff for: lambdas/functions/ami-housekeeper/src/lambda.test.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { logger } from '@aws-github-runner/aws-powertools-util';
22
import { Context } from 'aws-lambda';
3-
import { mocked } from 'jest-mock';
43

54
import { AmiCleanupOptions, amiCleanup } from './ami';
65
import { handler } from './lambda';
6+
import { describe, it, expect, beforeAll, vi } from 'vitest';
77

8-
jest.mock('./ami');
9-
jest.mock('@aws-github-runner/aws-powertools-util');
8+
vi.mock('./ami');
9+
vi.mock('@aws-github-runner/aws-powertools-util');
1010

1111
const amiCleanupOptions: AmiCleanupOptions = {
1212
minimumDaysOld: undefined,
@@ -39,14 +39,13 @@ const context: Context = {
3939
},
4040
};
4141

42-
// Docs for testing async with jest: https://jestjs.io/docs/tutorial-async
4342
describe('Housekeeper ami', () => {
4443
beforeAll(() => {
45-
jest.resetAllMocks();
44+
vi.resetAllMocks();
4645
});
4746

4847
it('should not throw or log in error.', async () => {
49-
const mock = mocked(amiCleanup);
48+
const mock = vi.mocked(amiCleanup);
5049
mock.mockImplementation(() => {
5150
return new Promise((resolve) => {
5251
resolve();
@@ -56,10 +55,10 @@ describe('Housekeeper ami', () => {
5655
});
5756

5857
it('should not thow only log in error in case of an exception.', async () => {
59-
const logSpy = jest.spyOn(logger, 'error');
58+
const logSpy = vi.spyOn(logger, 'error');
6059

6160
const error = new Error('An error.');
62-
const mock = mocked(amiCleanup);
61+
const mock = vi.mocked(amiCleanup);
6362
mock.mockRejectedValue(error);
6463
await expect(handler(undefined, context)).resolves.toBeUndefined();
6564

Diff for: lambdas/functions/ami-housekeeper/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
"extends" : "../../tsconfig.json",
33
"include": [
44
"src/**/*"
5+
],
6+
"exclude": [
7+
"src/**/*.test.ts"
58
]
69
}

Diff for: lambdas/functions/ami-housekeeper/vitest.config.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { mergeConfig } from 'vitest/config';
2+
import defaultConfig from '../../vitest.base.config';
3+
4+
export default mergeConfig(defaultConfig, {
5+
test: {
6+
setupFiles: ['../../aws-vitest-setup.ts'],
7+
coverage: {
8+
include: ['src/**/*.ts'],
9+
exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'],
10+
thresholds: {
11+
statements: 100,
12+
branches: 100,
13+
functions: 100,
14+
lines: 100,
15+
},
16+
},
17+
},
18+
});

Diff for: lambdas/functions/control-plane/jest.config.ts

-17
This file was deleted.

Diff for: lambdas/functions/control-plane/package.json

+13-24
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,45 @@
22
"name": "@aws-github-runner/control-plane",
33
"version": "1.0.0",
44
"main": "lambda.ts",
5+
"type": "module",
56
"license": "MIT",
67
"scripts": {
78
"start": "ts-node-dev src/local.ts",
89
"test": "NODE_ENV=test nx test",
910
"test:watch": "NODE_ENV=test nx test --watch",
10-
"lint": "yarn eslint src",
11+
"lint": "eslint src",
1112
"watch": "ts-node-dev --respawn --exit-child --files src/local-down.ts",
1213
"build": "ncc build src/lambda.ts -o dist",
13-
"dist": "yarn build && cd dist && zip ../runners.zip index.js",
14+
"dist": "yarn build && cp package.json dist/ && cd dist && zip ../runners.zip *",
1415
"format": "prettier --write \"**/*.ts\"",
1516
"format-check": "prettier --check \"**/*.ts\"",
1617
"all": "yarn build && yarn format && yarn lint && yarn test"
1718
},
1819
"devDependencies": {
19-
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
20-
"@types/aws-lambda": "^8.10.146",
21-
"@types/jest": "^29.5.14",
22-
"@types/node": "^22.13.9",
23-
"@typescript-eslint/eslint-plugin": "^8.25.0",
24-
"@typescript-eslint/parser": "^8.25.0",
20+
"@aws-sdk/types": "^3.734.0",
21+
"@octokit/types": "^13.8.0",
22+
"@types/aws-lambda": "^8.10.147",
23+
"@types/node": "^22.13.10",
2524
"@vercel/ncc": "^0.38.3",
2625
"aws-sdk-client-mock": "^4.1.0",
2726
"aws-sdk-client-mock-jest": "^4.1.0",
28-
"eslint": "^8.57.0",
29-
"eslint-plugin-prettier": "5.2.3",
30-
"jest": "^29.7.0",
31-
"jest-mock": "^29.7.0",
32-
"jest-mock-extended": "^3.0.7",
3327
"moment-timezone": "^0.5.47",
3428
"nock": "^14.0.1",
35-
"prettier": "3.4.2",
36-
"ts-jest": "^29.2.5",
3729
"ts-node": "^10.9.2",
3830
"ts-node-dev": "^2.0.0"
3931
},
4032
"dependencies": {
4133
"@aws-github-runner/aws-powertools-util": "*",
4234
"@aws-github-runner/aws-ssm-util": "*",
4335
"@aws-lambda-powertools/parameters": "^2.16.0",
44-
"@aws-sdk/client-ec2": "^3.764.0",
36+
"@aws-sdk/client-ec2": "^3.767.0",
4537
"@aws-sdk/client-sqs": "^3.758.0",
46-
"@aws-sdk/types": "^3.734.0",
4738
"@middy/core": "^4.7.0",
48-
"@octokit/auth-app": "6.1.3",
49-
"@octokit/core": "5.2.0",
50-
"@octokit/plugin-throttling": "8.2.0",
51-
"@octokit/rest": "20.1.2",
52-
"@octokit/types": "^13.8.0",
53-
"cron-parser": "^4.9.0",
54-
"typescript": "^5.7.3"
39+
"@octokit/auth-app": "7.1.5",
40+
"@octokit/core": "6.1.4",
41+
"@octokit/plugin-throttling": "9.4.0",
42+
"@octokit/rest": "21.1.1",
43+
"cron-parser": "^4.9.0"
5544
},
5645
"nx": {
5746
"includedScripts": [

Diff for: lambdas/functions/control-plane/src/aws/runners.test.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ import {
1414
import { GetParameterCommand, GetParameterResult, PutParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
1515
import { tracer } from '@aws-github-runner/aws-powertools-util';
1616
import { mockClient } from 'aws-sdk-client-mock';
17-
import 'aws-sdk-client-mock-jest';
17+
import 'aws-sdk-client-mock-jest/vitest';
1818

1919
import ScaleError from './../scale-runners/ScaleError';
2020
import { createRunner, listEC2Runners, tag, terminateRunner } from './runners';
2121
import { RunnerInfo, RunnerInputParameters, RunnerType } from './runners.d';
22+
import { describe, it, expect, beforeEach, vi } from 'vitest';
2223

2324
process.env.AWS_REGION = 'eu-east-1';
2425
const mockEC2Client = mockClient(EC2Client);
@@ -55,8 +56,8 @@ const mockRunningInstances: DescribeInstancesResult = {
5556

5657
describe('list instances', () => {
5758
beforeEach(() => {
58-
jest.resetModules();
59-
jest.clearAllMocks();
59+
vi.resetModules();
60+
vi.clearAllMocks();
6061
});
6162

6263
it('returns a list of instances', async () => {
@@ -202,7 +203,7 @@ describe('list instances', () => {
202203

203204
describe('terminate runner', () => {
204205
beforeEach(() => {
205-
jest.clearAllMocks();
206+
vi.clearAllMocks();
206207
});
207208
it('calls terminate instances with the right instance ids', async () => {
208209
mockEC2Client.on(TerminateInstancesCommand).resolves({});
@@ -219,7 +220,7 @@ describe('terminate runner', () => {
219220

220221
describe('tag runner', () => {
221222
beforeEach(() => {
222-
jest.clearAllMocks();
223+
vi.clearAllMocks();
223224
});
224225
it('adding extra tag', async () => {
225226
mockEC2Client.on(CreateTagsCommand).resolves({});
@@ -252,7 +253,7 @@ describe('create runner', () => {
252253
};
253254

254255
beforeEach(() => {
255-
jest.clearAllMocks();
256+
vi.clearAllMocks();
256257
mockEC2Client.reset();
257258
mockSSMClient.reset();
258259

@@ -315,7 +316,7 @@ describe('create runner', () => {
315316
});
316317
});
317318
it('calls create fleet of 1 instance with runner tracing enabled', async () => {
318-
tracer.getRootXrayTraceId = jest.fn().mockReturnValue('123');
319+
tracer.getRootXrayTraceId = vi.fn().mockReturnValue('123');
319320

320321
await createRunner(createRunnerConfig({ ...defaultRunnerConfig, tracingEnabled: true }));
321322

@@ -338,7 +339,7 @@ describe('create runner with errors', () => {
338339
totalTargetCapacity: 1,
339340
};
340341
beforeEach(() => {
341-
jest.clearAllMocks();
342+
vi.clearAllMocks();
342343
mockEC2Client.reset();
343344
mockSSMClient.reset();
344345

@@ -443,7 +444,7 @@ describe('create runner with errors fail over to OnDemand', () => {
443444
totalTargetCapacity: 1,
444445
};
445446
beforeEach(() => {
446-
jest.clearAllMocks();
447+
vi.clearAllMocks();
447448
mockEC2Client.reset();
448449
mockSSMClient.reset();
449450

0 commit comments

Comments
 (0)