mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-04-04 03:31:05 +08:00
* 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
170 lines
5.0 KiB
TypeScript
170 lines
5.0 KiB
TypeScript
import {Kubectl} from './kubectl.js'
|
|
import minimist from 'minimist'
|
|
import {ExecOptions, ExecOutput, getExecOutput} from '@actions/exec'
|
|
import * as core from '@actions/core'
|
|
import fs from 'node:fs'
|
|
import * as path from 'path'
|
|
import {getTempDirectory} from '../utilities/fileUtils.js'
|
|
|
|
export class PrivateKubectl extends Kubectl {
|
|
protected async execute(args: string[], silent: boolean = false) {
|
|
args.unshift('kubectl')
|
|
let kubectlCmd = args.join(' ')
|
|
let addFileFlag = false
|
|
let eo = <ExecOptions>{
|
|
silent: true,
|
|
failOnStdErr: false,
|
|
ignoreReturnCode: true
|
|
}
|
|
|
|
if (this.containsFilenames(kubectlCmd)) {
|
|
kubectlCmd = replaceFileNamesWithShallowNamesRelativeToTemp(kubectlCmd)
|
|
addFileFlag = true
|
|
}
|
|
|
|
if (this.resourceGroup === '') {
|
|
throw Error('Resource group must be specified for private cluster')
|
|
}
|
|
if (this.name === '') {
|
|
throw Error('Cluster name must be specified for private cluster')
|
|
}
|
|
|
|
const privateClusterArgs = [
|
|
'aks',
|
|
'command',
|
|
'invoke',
|
|
'--resource-group',
|
|
this.resourceGroup,
|
|
'--name',
|
|
this.name,
|
|
'--command',
|
|
`${kubectlCmd}`
|
|
]
|
|
|
|
if (addFileFlag) {
|
|
const tempDirectory = getTempDirectory()
|
|
eo.cwd = path.join(tempDirectory, 'manifests')
|
|
privateClusterArgs.push(...['--file', '.'])
|
|
}
|
|
|
|
core.debug(
|
|
`private cluster Kubectl run with invoke command: ${kubectlCmd}`
|
|
)
|
|
|
|
const allArgs = [...privateClusterArgs, '-o', 'json']
|
|
core.debug(`full form of az command: az ${allArgs.join(' ')}`)
|
|
const runOutput = await getExecOutput('az', allArgs, eo)
|
|
core.debug(
|
|
`from kubectl private cluster command got run output ${JSON.stringify(
|
|
runOutput
|
|
)}`
|
|
)
|
|
|
|
if (runOutput.exitCode !== 0) {
|
|
throw Error(
|
|
`Call to private cluster failed. Command: '${kubectlCmd}', errormessage: ${runOutput.stderr}`
|
|
)
|
|
}
|
|
|
|
const runObj: {logs: string; exitCode: number} = JSON.parse(
|
|
runOutput.stdout
|
|
)
|
|
if (!silent) core.info(runObj.logs)
|
|
if (runObj.exitCode !== 0) {
|
|
throw Error(`failed private cluster Kubectl command: ${kubectlCmd}`)
|
|
}
|
|
|
|
return {
|
|
exitCode: runObj.exitCode,
|
|
stdout: runObj.logs,
|
|
stderr: ''
|
|
} as ExecOutput
|
|
}
|
|
|
|
private containsFilenames(str: string) {
|
|
return str.includes('-f ') || str.includes('filename ')
|
|
}
|
|
}
|
|
|
|
function createTempManifestsDirectory(): string {
|
|
const manifestsDirPath = path.join(getTempDirectory(), 'manifests')
|
|
if (!fs.existsSync(manifestsDirPath)) {
|
|
fs.mkdirSync(manifestsDirPath, {recursive: true})
|
|
}
|
|
|
|
return manifestsDirPath
|
|
}
|
|
|
|
export function replaceFileNamesWithShallowNamesRelativeToTemp(
|
|
kubectlCmd: string
|
|
) {
|
|
let filenames = extractFileNames(kubectlCmd)
|
|
core.debug(`filenames originally provided in kubectl command: ${filenames}`)
|
|
let relativeShallowNames = filenames.map((filename) => {
|
|
const relativeName = path.relative(getTempDirectory(), filename)
|
|
|
|
const relativePathElements = relativeName.split(path.sep)
|
|
|
|
const shallowName = relativePathElements.join('-')
|
|
|
|
// make manifests dir in temp if it doesn't already exist
|
|
const manifestsTempDir = createTempManifestsDirectory()
|
|
|
|
const shallowPath = path.join(manifestsTempDir, shallowName)
|
|
core.debug(
|
|
`moving contents from ${filename} to shallow location at ${shallowPath}`
|
|
)
|
|
|
|
core.debug(`reading contents from ${filename}`)
|
|
const contents = fs.readFileSync(filename).toString()
|
|
|
|
core.debug(`writing contents to new path ${shallowPath}`)
|
|
fs.writeFileSync(shallowPath, contents)
|
|
|
|
return shallowName
|
|
})
|
|
|
|
let result = kubectlCmd
|
|
if (filenames.length != relativeShallowNames.length) {
|
|
throw Error(
|
|
'replacing filenames with relative path from temp dir, ' +
|
|
filenames.length +
|
|
' filenames != ' +
|
|
relativeShallowNames.length +
|
|
'basenames'
|
|
)
|
|
}
|
|
for (let index = 0; index < filenames.length; index++) {
|
|
result = result.replace(filenames[index], relativeShallowNames[index])
|
|
}
|
|
return result
|
|
}
|
|
|
|
export function extractFileNames(strToParse: string) {
|
|
const fileNames: string[] = []
|
|
const argv = minimist(strToParse.split(' '))
|
|
const fArg = 'f'
|
|
const filenameArg = 'filename'
|
|
|
|
fileNames.push(...extractFilesFromMinimist(argv, fArg))
|
|
fileNames.push(...extractFilesFromMinimist(argv, filenameArg))
|
|
|
|
return fileNames
|
|
}
|
|
|
|
export function extractFilesFromMinimist(argv, arg: string): string[] {
|
|
if (!argv[arg]) {
|
|
return []
|
|
}
|
|
const toReturn: string[] = []
|
|
if (typeof argv[arg] === 'string') {
|
|
toReturn.push(...argv[arg].split(','))
|
|
} else {
|
|
for (const value of argv[arg] as string[]) {
|
|
toReturn.push(...value.split(','))
|
|
}
|
|
}
|
|
|
|
return toReturn
|
|
}
|