Migrate to esbuild/Vitest and upgrade @actions/* to ESM-only versions (#492)

* Migrate build toolchain from ncc/Jest to esbuild/Vitest

Replace the legacy ncc/Jest/Babel build stack with a modern ESM toolchain:

Build:
- Replace @vercel/ncc with esbuild (--platform=node --target=node20 --format=esm)
- Add createRequire banner for CJS interop in ESM bundle
- Add "type": "module" to package.json
- Add tsc --noEmit typecheck script (esbuild strips types without checking)
- Add typecheck to husky pre-commit hook

Dependencies:
- Bump @actions/core@3, exec@3, io@3, tool-cache@4 (ESM-only)
- Replace jest/ts-jest/@babel/* with vitest@4

Tests:
- Convert 29 test files: jest.fn()→vi.fn(), jest.mock()→vi.mock(), jest.spyOn()→vi.spyOn()
- Fix vitest 4 compat: mockImplementation requires args, mock call tracking, await .rejects

CI:
- Update build step from ncc build → npm run build
- Update composite action to use npm run build

* Switch tsconfig to NodeNext module resolution

Change module/moduleResolution from ES2022/bundler to NodeNext/NodeNext
and target from ES2022 to ES2020.

- Add .js extensions to all relative imports across 59 source/test files
  (required by NodeNext module resolution)
- Add vitest/globals to tsconfig types array for global test API declarations
This commit is contained in:
David Gamero
2026-02-24 14:57:56 -05:00
committed by GitHub
parent 84e2095bf0
commit 01cfe404ef
67 changed files with 1967 additions and 10133 deletions
@@ -1,11 +1,15 @@
import {vi} from 'vitest'
import type {MockInstance} from 'vitest'
vi.mock('@actions/core')
import * as core from '@actions/core'
import {Kubectl} from '../../types/kubectl'
import {Kubectl} from '../../types/kubectl.js'
import {
deployPodCanary,
calculateReplicaCountForCanary
} from './podCanaryHelper'
} from './podCanaryHelper.js'
jest.mock('../../types/kubectl')
vi.mock('../../types/kubectl')
const kc = new Kubectl('')
@@ -35,18 +39,17 @@ const TIMEOUT_300S = '300s'
describe('Pod Canary Helper tests', () => {
let mockFilePaths: string[]
let kubectlApplySpy: jest.SpyInstance
let kubectlApplySpy: MockInstance
beforeEach(() => {
//@ts-ignore
Kubectl.mockClear()
jest.restoreAllMocks()
vi.mocked(Kubectl).mockClear()
vi.restoreAllMocks()
mockFilePaths = testManifestFiles
kubectlApplySpy = jest.spyOn(kc, 'apply')
kubectlApplySpy = vi.spyOn(kc, 'apply')
// Mock core.getInput with default values
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) {
case 'percentage':
return VALID_PERCENTAGE.toString()
@@ -61,7 +64,7 @@ describe('Pod Canary Helper tests', () => {
})
afterEach(() => {
jest.restoreAllMocks()
vi.restoreAllMocks()
kubectlApplySpy.mockClear()
})
@@ -114,7 +117,7 @@ describe('Pod Canary Helper tests', () => {
})
test('should throw error for invalid low percentage', async () => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'percentage') return INVALID_LOW_PERCENTAGE.toString()
return ''
})
@@ -127,7 +130,7 @@ describe('Pod Canary Helper tests', () => {
})
test('should throw error for invalid high percentage', async () => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'percentage') return INVALID_HIGH_PERCENTAGE.toString()
return ''
})
@@ -143,7 +146,7 @@ describe('Pod Canary Helper tests', () => {
kubectlApplySpy.mockResolvedValue(mockSuccessResult)
// Test minimum valid percentage
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'percentage') return MIN_PERCENTAGE.toString()
return ''
})
@@ -152,7 +155,7 @@ describe('Pod Canary Helper tests', () => {
expect(resultMin.execResult).toEqual(mockSuccessResult)
// Test maximum valid percentage
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'percentage') return MAX_PERCENTAGE.toString()
return ''
})
@@ -162,7 +165,7 @@ describe('Pod Canary Helper tests', () => {
})
test('should handle force deployment option', async () => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) {
case 'percentage':
return VALID_PERCENTAGE.toString()
@@ -186,7 +189,7 @@ describe('Pod Canary Helper tests', () => {
})
test('should handle server-side apply option', async () => {
jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
switch (name) {
case 'percentage':
return VALID_PERCENTAGE.toString()