From ffca16d01f060b1236881b6a1130ac131c20bf67 Mon Sep 17 00:00:00 2001 From: Conor Sloan Date: Wed, 22 Nov 2023 12:37:21 +0000 Subject: [PATCH] fix low hanging fruit linter errors --- __tests__/ghcr-client.test.ts | 47 +++++++++++++++------------------ __tests__/main.test.ts | 8 ------ __tests__/oci-container.test.ts | 16 +++++------ package.json | 2 +- src/fs-helper.ts | 29 +++++++++----------- src/ghcr-client.ts | 35 +++++++++++------------- src/main.ts | 7 +++-- src/oci-container.ts | 5 ++-- 8 files changed, 64 insertions(+), 85 deletions(-) diff --git a/__tests__/ghcr-client.test.ts b/__tests__/ghcr-client.test.ts index 6489353..4a0e431 100644 --- a/__tests__/ghcr-client.test.ts +++ b/__tests__/ghcr-client.test.ts @@ -1,5 +1,5 @@ import { publishOCIArtifact } from '../src/ghcr-client' -import axios, { AxiosRequestConfig } from 'axios' +import axios from 'axios' import * as fsHelper from '../src/fs-helper' import * as ociContainer from '../src/oci-container' @@ -88,14 +88,12 @@ describe('publishOCIArtifact', () => { it('publishes layer blobs & then a manifest to the provided registry', async () => { // Simulate none of the blobs existing currently - axiosHeadMock.mockImplementation( - async (url: string, config: AxiosRequestConfig) => { - validateRequestConfig(404, url, config) - return { - status: 404 - } + axiosHeadMock.mockImplementation(async (url, config) => { + validateRequestConfig(404, url, config) + return { + status: 404 } - ) + }) // Simulate successful initiation of uploads for all blobs & return location axiosPostMock.mockImplementation(async (url, data, config) => { @@ -109,7 +107,7 @@ describe('publishOCIArtifact', () => { }) // Simulate successful reading of all the files - fsReadFileSyncMock.mockImplementation(async _ => { + fsReadFileSyncMock.mockImplementation(() => { return Buffer.from('test') }) @@ -160,7 +158,7 @@ describe('publishOCIArtifact', () => { }) // Simulate successful reading of all the files - fsReadFileSyncMock.mockImplementation(async _ => { + fsReadFileSyncMock.mockImplementation(() => { return Buffer.from('test') }) @@ -198,7 +196,7 @@ describe('publishOCIArtifact', () => { } }) - expect( + await expect( publishOCIArtifact( token, registry, @@ -229,7 +227,7 @@ describe('publishOCIArtifact', () => { } }) - expect( + await expect( publishOCIArtifact( token, registry, @@ -261,7 +259,7 @@ describe('publishOCIArtifact', () => { } }) - expect( + await expect( publishOCIArtifact( token, registry, @@ -296,7 +294,7 @@ describe('publishOCIArtifact', () => { }) // Simulate successful reading of all the files - fsReadFileSyncMock.mockImplementation(async path => { + fsReadFileSyncMock.mockImplementation(() => { return Buffer.from('test') }) @@ -308,7 +306,7 @@ describe('publishOCIArtifact', () => { } }) - expect( + await expect( publishOCIArtifact( token, registry, @@ -343,7 +341,7 @@ describe('publishOCIArtifact', () => { }) // Simulate successful reading of all the files - fsReadFileSyncMock.mockImplementation(async path => { + fsReadFileSyncMock.mockImplementation(() => { return Buffer.from('test') }) @@ -362,7 +360,7 @@ describe('publishOCIArtifact', () => { } }) - expect( + await expect( publishOCIArtifact( token, registry, @@ -397,7 +395,7 @@ describe('publishOCIArtifact', () => { }) // Simulate successful reading of all the files - fsReadFileSyncMock.mockImplementation(path => { + fsReadFileSyncMock.mockImplementation(() => { throw new Error('failed to read a file: test') }) @@ -409,7 +407,7 @@ describe('publishOCIArtifact', () => { } }) - expect( + await expect( publishOCIArtifact( token, registry, @@ -424,10 +422,10 @@ describe('publishOCIArtifact', () => { }) it('throws an error if one of the layers has the wrong media type', async () => { - let modifiedTestManifest = testManifest + const modifiedTestManifest = testManifest modifiedTestManifest.layers[0].mediaType = 'application/json' - expect( + await expect( publishOCIArtifact( token, registry, @@ -444,11 +442,8 @@ describe('publishOCIArtifact', () => { // We expect all axios calls to have auth headers set and to not intercept any status codes so we can handle them. // This function verifies that given an axios request config. -function validateRequestConfig( - status: number, - url: string, - config: AxiosRequestConfig -) { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function validateRequestConfig(status: number, url: string, config: any): void { // Basic URL checks expect(url).toBeDefined() diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 0e33bc7..3b838f8 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -11,15 +11,9 @@ import * as main from '../src/main' import * as github from '@actions/github' import * as fsHelper from '../src/fs-helper' -import * as ociContainer from '../src/oci-container' import * as ghcr from '../src/ghcr-client' -// Mock the action's main function -const runMock = jest.spyOn(main, 'run') - // Mock the GitHub Actions core library -let debugMock: jest.SpyInstance -let errorMock: jest.SpyInstance let getInputMock: jest.SpyInstance let setFailedMock: jest.SpyInstance let setOutputMock: jest.SpyInstance @@ -38,8 +32,6 @@ describe('action', () => { jest.clearAllMocks() // Core mocks - debugMock = jest.spyOn(core, 'debug').mockImplementation() - errorMock = jest.spyOn(core, 'error').mockImplementation() getInputMock = jest.spyOn(core, 'getInput').mockImplementation() setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation() setOutputMock = jest.spyOn(core, 'setOutput').mockImplementation() diff --git a/__tests__/oci-container.test.ts b/__tests__/oci-container.test.ts index 136c7dd..dbaf031 100644 --- a/__tests__/oci-container.test.ts +++ b/__tests__/oci-container.test.ts @@ -3,21 +3,21 @@ import { FileMetadata } from '../src/fs-helper' describe('createActionPackageManigest', () => { it('creates a manifest containing the provided information', () => { - let date = new Date() - let repo = 'test-repo' - let version = '1.0.0' - let tarFile: FileMetadata = { + const date = new Date() + const repo = 'test-repo' + const version = '1.0.0' + const tarFile: FileMetadata = { path: '/test/test/test', sha256: '1234567890', size: 100 } - let zipFile: FileMetadata = { + const zipFile: FileMetadata = { path: '/test/test/test', sha256: '1234567890', size: 100 } - let expectedJSON: String = `{ + const expectedJSON = `{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.oci.image.manifest.v1+json", @@ -63,7 +63,7 @@ describe('createActionPackageManigest', () => { } }` - let manifest = createActionPackageManifest( + const manifest = createActionPackageManifest( { path: 'test.tar.gz', size: 100, @@ -79,7 +79,7 @@ describe('createActionPackageManigest', () => { date ) - let manifestJSON = JSON.stringify(manifest) + const manifestJSON = JSON.stringify(manifest) expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, '')) }) }) diff --git a/package.json b/package.json index b5f3a93..73d2cf1 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "@actions/core": "^1.10.1", "@actions/exec": "^1.1.1", "@actions/github": "^6.0.0", - "@types/axios": "^0.14.0", "archiver": "^6.0.1", "axios": "^1.6.2", "axios-debug-log": "^1.0.0", @@ -77,6 +76,7 @@ }, "devDependencies": { "@types/archiver": "^6.0.1", + "@types/axios": "^0.14.0", "@types/jest": "^29.5.8", "@types/node": "^20.9.1", "@types/tar": "^6.1.9", diff --git a/src/fs-helper.ts b/src/fs-helper.ts index ec84a26..9b1ea0d 100644 --- a/src/fs-helper.ts +++ b/src/fs-helper.ts @@ -1,14 +1,11 @@ -import * as core from '@actions/core' -import * as exec from '@actions/exec' import * as fs from 'fs' import * as path from 'path' import * as tar from 'tar' import * as archiver from 'archiver' import * as crypto from 'crypto' import * as os from 'os' -import * as zlib from 'zlib' -export function createTempDir() { +export function createTempDir(): string { const randomDirName = crypto.randomBytes(4).toString('hex') const tempDir = path.join(os.tmpdir(), randomDirName) @@ -19,7 +16,7 @@ export function createTempDir() { return tempDir } -export function removeDir(dir: string) { +export function removeDir(dir: string): void { fs.rmSync(dir, { recursive: true }) } @@ -60,7 +57,7 @@ export async function createArchives( archive.finalize() }), new Promise((resolve, reject) => { - const tarStream = tar + tar .c( { file: tarPath, @@ -77,20 +74,20 @@ export async function createArchives( ]).then(([zipFile, tarFile]) => ({ zipFile, tarFile })) } -export function isDirectory(path: string): boolean { - return fs.existsSync(path) && fs.lstatSync(path).isDirectory() +export function isDirectory(dirPath: string): boolean { + return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory() } -export function readFileContents(path: string): Buffer { - return fs.readFileSync(path) +export function readFileContents(filePath: string): Buffer { + return fs.readFileSync(filePath) } // Converts a file path to a filemetadata object by querying the fs for relevant metadata. -async function fileMetadata(path: string): Promise { - const stats = fs.statSync(path) +async function fileMetadata(filePath: string): Promise { + const stats = fs.statSync(filePath) const size = stats.size const hash = crypto.createHash('sha256') - const fileStream = fs.createReadStream(path) + const fileStream = fs.createReadStream(filePath) return new Promise((resolve, reject) => { fileStream.on('data', data => { hash.update(data) @@ -98,9 +95,9 @@ async function fileMetadata(path: string): Promise { fileStream.on('end', () => { const sha256 = hash.digest('hex') resolve({ - path: path, - size: size, - sha256: 'sha256:' + sha256 + path: filePath, + size, + sha256: `sha256:${sha256}` }) }) fileStream.on('error', err => { diff --git a/src/ghcr-client.ts b/src/ghcr-client.ts index 95beb22..3aab6cd 100644 --- a/src/ghcr-client.ts +++ b/src/ghcr-client.ts @@ -2,9 +2,6 @@ import * as core from '@actions/core' import { FileMetadata } from './fs-helper' import * as ociContainer from './oci-container' import axios from 'axios' -import { fieldEnds } from 'tar' -import * as fs from 'fs' -import { promiseHooks } from 'v8' import * as fsHelper from './fs-helper' import axiosDebugLog from 'axios-debug-log' @@ -18,7 +15,7 @@ export async function publishOCIArtifact( zipFile: FileMetadata, tarFile: FileMetadata, manifest: ociContainer.Manifest, - debugRequests: boolean = false + debugRequests = false ): Promise { if (debugRequests) { configureRequestDebugLogging() @@ -43,7 +40,7 @@ export async function publishOCIArtifact( `Creating GHCR package for release with semver:${semver} with path:"${zipFile.path}" and "${tarFile.path}".` ) - let layerUploads: Promise[] = manifest.layers.map(layer => { + const layerUploads: Promise[] = manifest.layers.map(async layer => { switch (layer.mediaType) { case 'application/vnd.github.actions.package.layer.v1.tar+gzip': return uploadLayer( @@ -98,7 +95,7 @@ async function uploadLayer( headers: { Authorization: `Bearer ${b64Token}` }, - validateStatus: function (status: number) { + validateStatus: () => { return true // Allow non 2xx responses } } @@ -124,12 +121,12 @@ async function uploadLayer( headers: { Authorization: `Bearer ${b64Token}` }, - validateStatus: function (status: number) { + validateStatus: () => { return true // Allow non 2xx responses } }) - if (initiateUploadResponse.status != 202) { + if (initiateUploadResponse.status !== 202) { core.error( `Unexpected response from upload post ${uploadBlobEndpoint}: ${initiateUploadResponse.status}` ) @@ -139,17 +136,17 @@ async function uploadLayer( } const locationResponseHeader = initiateUploadResponse.headers['location'] - if (locationResponseHeader == undefined) { + if (locationResponseHeader === undefined) { throw new Error( `No location header in response from upload post ${uploadBlobEndpoint} for layer ${layer.digest}` ) } - let pathname = (locationResponseHeader as string) + '?digest=' + layer.digest + const pathname = `${locationResponseHeader}?digest=${layer.digest}` const uploadBlobUrl = new URL(pathname, registryURL).toString() // TODO: must we handle the empty config layer? Maybe we can just skip calling this at all - var data: Buffer + let data: Buffer if (file.size === 0) { data = Buffer.alloc(0) } else { @@ -163,12 +160,12 @@ async function uploadLayer( 'Accept-Encoding': 'gzip', // TODO: What about for the config layer? 'Content-Length': layer.size.toString() }, - validateStatus: function (status: number) { + validateStatus: () => { return true // Allow non 2xx responses } }) - if (putResponse.status != 201) { + if (putResponse.status !== 201) { throw new Error( `Unexpected response from PUT upload ${putResponse.status} for layer ${layer.digest}` ) @@ -187,27 +184,27 @@ async function uploadManifest( Authorization: `Bearer ${b64Token}`, 'Content-Type': 'application/vnd.oci.image.manifest.v1+json' }, - validateStatus: function (status: number) { + validateStatus: () => { return true // Allow non 2xx responses } }) - if (putResponse.status != 201) { + if (putResponse.status !== 201) { throw new Error( `Unexpected response from PUT manifest ${putResponse.status}` ) } } -function configureRequestDebugLogging() { +function configureRequestDebugLogging(): void { axiosDebugLog({ - request: function (debug, config) { + request: (debug, config) => { core.debug(`Request with ${config}`) }, - response: function (debug, response) { + response: (debug, response) => { core.debug(`Response with ${response}`) }, - error: function (debug, error) { + error: (debug, error) => { core.debug(`Error with ${error}`) } }) diff --git a/src/main.ts b/src/main.ts index 746f971..1024bda 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,14 +4,13 @@ import * as fsHelper from './fs-helper' import * as ociContainer from './oci-container' import * as ghcr from './ghcr-client' import semver from 'semver' -import { url } from 'inspector' /** * The main function for the action. * @returns {Promise} Resolves when the action is complete. */ export async function run(): Promise { - let tmpDir: string = '' + let tmpDir = '' try { // Parse and validate Actions execution context, including the repository name, release name and event type @@ -29,7 +28,7 @@ export async function run(): Promise { // Strip any leading 'v' from the tag in case the release format is e.g. 'v1.0.0' as recommended by GitHub docs // https://docs.github.com/en/actions/creating-actions/releasing-and-maintaining-actions - let targetVersion = semver.parse(releaseTag.replace(/^v/, '')) + const targetVersion = semver.parse(releaseTag.replace(/^v/, '')) if (!targetVersion) { core.setFailed( `${releaseTag} is not a valid semantic version, and so cannot be uploaded as an Immutable Action.` @@ -62,7 +61,7 @@ export async function run(): Promise { new Date() ) - let packageURL = await ghcr.publishOCIArtifact( + const packageURL = await ghcr.publishOCIArtifact( token, registryURL, repository, diff --git a/src/oci-container.ts b/src/oci-container.ts index 49a1f41..2c743e9 100644 --- a/src/oci-container.ts +++ b/src/oci-container.ts @@ -1,4 +1,3 @@ -import { Tracing } from 'trace_events' import { FileMetadata } from './fs-helper' export interface Manifest { @@ -7,14 +6,14 @@ export interface Manifest { artifactType: string config: Layer layers: Layer[] - annotations: {} + annotations: { [key: string]: string } } export interface Layer { mediaType: string size: number digest: string - annotations: {} + annotations: { [key: string]: string } } // Given a name and archive metadata, creates a manifest in the format expected by GHCR for an Actions Package.