update deps, linting, test cases, etc.
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
MD004:
|
||||
style: dash
|
||||
|
||||
# Increase the max line length limit
|
||||
MD013:
|
||||
line_length: 200
|
||||
|
||||
# Ordered list item prefix
|
||||
MD029:
|
||||
style: one
|
||||
|
||||
|
||||
@@ -42,22 +42,4 @@ jobs:
|
||||
- name: Test
|
||||
id: npm-ci-test
|
||||
run: npm run ci-test
|
||||
|
||||
test-action:
|
||||
name: GitHub Actions Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test Local Action
|
||||
id: test-action
|
||||
uses: ./
|
||||
with:
|
||||
milliseconds: 1000
|
||||
|
||||
- name: Print Output
|
||||
id: output
|
||||
run: echo "${{ steps.test-action.outputs.time }}"
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: '31 7 * * 3'
|
||||
workflow_dispatch:
|
||||
# Disable until this is a public repo since advanced security is not enabled
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
# schedule:
|
||||
# - cron: '31 7 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
|
||||
@@ -41,3 +41,4 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TYPESCRIPT_DEFAULT_STYLE: prettier
|
||||
VALIDATE_JSCPD: false
|
||||
FILTER_REGEX_EXCLUDE: .*/licenses\.txt$
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
name: 'release'
|
||||
on: # rebuild any PRs and main branch changes
|
||||
release:
|
||||
types: [created]
|
||||
jobs:
|
||||
package-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checking out!
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Publish action package
|
||||
uses: ./
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
## Initial Setup
|
||||
# Initial Setup
|
||||
|
||||
After you've cloned the repository to your local machine or codespace, you'll
|
||||
need to perform some initial setup steps before you can develop your action.
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/ruchika-org/package-action"></a>
|
||||
</p>
|
||||
# Publish Action Package
|
||||
|
||||
# Package and Publish
|
||||
This action packages your action repository as OCI artifacts and publishes it to [GHCR](ghcr.io).
|
||||
|
||||
This action packages your action repository as OCI artifacts and publishes it to [GHCR](ghcr.io), so your action can then be consumed as a package to make the actions ecosystem more secure.
|
||||
This allows your action to be consumed as an immutable package to make the actions ecosystem more secure.
|
||||
|
||||
The whole action repository is packaged by default. Set `path` input to specify which path you want to package if you want only a few folders (for eg. dist) to be packaged.
|
||||
The whole action repository is packaged by default. Set `path` input to specify which path you want to package.
|
||||
|
||||
Make sure you use the [Starter Workflow] (https://github.com/actions-on-packages/.github) (TODO) to run the action and ensure you have the release trigger in the workflow where you use this action.
|
||||
Make sure you use the [Starter Workflow](https://github.com/actions-on-packages/.github) (TODO) to run the action.
|
||||
Please also ensure you have the release trigger in the workflow where you use this action.
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
<!-- start usage -->
|
||||
```yaml
|
||||
@@ -42,19 +41,33 @@ on:
|
||||
```
|
||||
<!-- end usage -->
|
||||
|
||||
# License
|
||||
## License
|
||||
|
||||
The scripts and documentation in this project are released under the [MIT License](LICENSE)
|
||||
|
||||
## [Internal] Differences from previous implementation
|
||||
|
||||
# [Internal] Differences from previous implementation
|
||||
This is a new implementation of an Action which publishes a given release to ghcr.io (GitHub Packages).
|
||||
|
||||
This is a new implementation of an Action which publishes a given release to ghcr.io (GitHub Packages). It will eventually be moved to https://github.com/actions-on-packages/package-action and replace the existing implementation.
|
||||
It will eventually be moved to our `actions`` org.
|
||||
|
||||
The key differences are:
|
||||
* This Action goes directly to GitHub Packages rather than using an API endpoint to pass a bundle to.
|
||||
* This Action uses Node.js libraries to create both a `zip` and `tar.gz` of the content as layers.
|
||||
* This Action creates and publishes the OCI manifest which houses those archives, which was previously done on the backend.
|
||||
* This Action has the goal of generating provenance attestations for any release that is created.
|
||||
* This Action parses and validates that the release tag which triggered it is in a valid semver format, either `1.0.3-prerelease` or `v1.0.0-prerelease`.
|
||||
|
||||
- This Action goes directly to GitHub Packages rather than using an API endpoint to pass a bundle to.
|
||||
- This Action uses Node.js libraries to create both a `zip` and `tar.gz` of the content as layers.
|
||||
- This Action creates and publishes the OCI manifest which houses those archives, which was previously done on the backend.
|
||||
- This Action has the goal of generating provenance attestations for any release that is created.
|
||||
- This Action parses and validates that the release tag which triggered it is in a valid SemVer format, either `1.0.3-prerelease` or `v1.0.0-prerelease`.
|
||||
|
||||
## [Internal] Features to consider adding to this Action
|
||||
|
||||
These are some features which should likely be part of this implementation.
|
||||
|
||||
- [ ] Parsing SemVer more strictly, based on what formats we decide to support (e.g. we might not support prerelease tags)
|
||||
- [ ] Checking that there is a valid `action.yml` file in the repository running the action, failing if it is not an Action repository
|
||||
- Could be as simple as just checking for the file, or could be validating it is yml with expected actions components
|
||||
- [ ] Sanitising and validating relative paths passed into the `path` arg
|
||||
- For example, checking that they're inside the repository of the action and not other places on the filsystem
|
||||
- [ ] Integration with Attestations API (speak to `phillmv` about this)
|
||||
- [ ] A way to dynamically work out the Container Registry's URL rather than relying on the user to provide it
|
||||
- Options seem to be an API endpoint or an environment variable in the runner, but this might be difficult for backwards compatibility
|
||||
|
||||
@@ -41,8 +41,8 @@ describe('createArchives', () => {
|
||||
expect(tarFile.sha256.startsWith('sha256:')).toEqual(true)
|
||||
|
||||
// Validate the hashes by comparing to the output of the system's hashing utility
|
||||
let zipSHA = zipFile.sha256.substring(7) // remove "sha256:" prefix
|
||||
let tarSHA = tarFile.sha256.substring(7) // remove "sha256:" prefix
|
||||
const zipSHA = zipFile.sha256.substring(7) // remove "sha256:" prefix
|
||||
const tarSHA = tarFile.sha256.substring(7) // remove "sha256:" prefix
|
||||
|
||||
// sha256 hash is 64 characters long
|
||||
expect(zipSHA).toHaveLength(64)
|
||||
@@ -86,13 +86,13 @@ describe('createTempDir', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
dirs.forEach(dir => {
|
||||
for (const dir of dirs) {
|
||||
fs.rmSync(dir, { recursive: true })
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('creates a temporary directory in the OS temporary dir', () => {
|
||||
let tmpDir = fsHelper.createTempDir()
|
||||
const tmpDir = fsHelper.createTempDir()
|
||||
dirs.push(tmpDir)
|
||||
|
||||
expect(fs.existsSync(tmpDir)).toEqual(true)
|
||||
@@ -101,10 +101,10 @@ describe('createTempDir', () => {
|
||||
})
|
||||
|
||||
it('creates a unique temporary directory', () => {
|
||||
let dir1 = fsHelper.createTempDir()
|
||||
const dir1 = fsHelper.createTempDir()
|
||||
dirs.push(dir1)
|
||||
|
||||
let dir2 = fsHelper.createTempDir()
|
||||
const dir2 = fsHelper.createTempDir()
|
||||
dirs.push(dir2)
|
||||
|
||||
expect(dir1).not.toEqual(dir2)
|
||||
@@ -170,3 +170,47 @@ describe('removeDir', () => {
|
||||
expect(fs.existsSync(dir)).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('bundleFilesintoDirectory', () => {
|
||||
let sourceDir: string
|
||||
let targetDir: string
|
||||
|
||||
beforeEach(() => {
|
||||
sourceDir = fsHelper.createTempDir()
|
||||
targetDir = fsHelper.createTempDir()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(sourceDir, { recursive: true })
|
||||
fs.rmSync(targetDir, { recursive: true })
|
||||
})
|
||||
|
||||
it('bundles files and folders into a directory', () => {
|
||||
// Create some test files and folders in the sourceDir
|
||||
const file1 = `${sourceDir}/file1.txt`
|
||||
const folder1 = `${sourceDir}/folder1`
|
||||
const file2 = `${folder1}/file3.txt`
|
||||
|
||||
fs.mkdirSync(folder1)
|
||||
fs.writeFileSync(file1, fileContent)
|
||||
fs.writeFileSync(file2, fileContent)
|
||||
|
||||
// Bundle the files and folders into the targetDir
|
||||
fsHelper.bundleFilesintoDirectory([file1, folder1], targetDir)
|
||||
|
||||
// Check that the files and folders were copied
|
||||
expect(fs.existsSync(file1)).toEqual(true)
|
||||
expect(fsHelper.readFileContents(file1).toString()).toEqual(fileContent)
|
||||
|
||||
expect(fs.existsSync(`${targetDir}/folder1`)).toEqual(true)
|
||||
|
||||
expect(fs.existsSync(file2)).toEqual(true)
|
||||
expect(fsHelper.readFileContents(file2).toString()).toEqual(fileContent)
|
||||
})
|
||||
|
||||
it('throws an error if a file or directory does not exist', () => {
|
||||
expect(() => {
|
||||
fsHelper.bundleFilesintoDirectory(['/does/not/exist'], targetDir)
|
||||
}).toThrow('File /does/not/exist does not exist')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { publishOCIArtifact } from '../src/ghcr-client'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import * as fs from 'fs'
|
||||
import axios from 'axios'
|
||||
import * as fsHelper from '../src/fs-helper'
|
||||
import * as ociContainer from '../src/oci-container'
|
||||
|
||||
@@ -89,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) => {
|
||||
@@ -110,7 +107,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(async path => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
return Buffer.from('test')
|
||||
})
|
||||
|
||||
@@ -161,7 +158,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(async path => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
return Buffer.from('test')
|
||||
})
|
||||
|
||||
@@ -199,7 +196,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -230,7 +227,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -262,7 +259,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -297,7 +294,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(async path => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
return Buffer.from('test')
|
||||
})
|
||||
|
||||
@@ -309,7 +306,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -344,7 +341,7 @@ describe('publishOCIArtifact', () => {
|
||||
})
|
||||
|
||||
// Simulate successful reading of all the files
|
||||
fsReadFileSyncMock.mockImplementation(async path => {
|
||||
fsReadFileSyncMock.mockImplementation(() => {
|
||||
return Buffer.from('test')
|
||||
})
|
||||
|
||||
@@ -363,7 +360,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -398,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')
|
||||
})
|
||||
|
||||
@@ -410,7 +407,7 @@ describe('publishOCIArtifact', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
await expect(
|
||||
publishOCIArtifact(
|
||||
token,
|
||||
registry,
|
||||
@@ -425,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,
|
||||
@@ -445,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()
|
||||
|
||||
|
||||
+102
-85
@@ -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
|
||||
@@ -29,6 +23,7 @@ let createTempDirMock: jest.SpyInstance
|
||||
let isDirectoryMock: jest.SpyInstance
|
||||
let createArchivesMock: jest.SpyInstance
|
||||
let removeDirMock: jest.SpyInstance
|
||||
let bundleFilesintoDirectoryMock: jest.SpyInstance
|
||||
|
||||
// Mock the GHCR Client
|
||||
let publishOCIArtifactMock: jest.SpyInstance
|
||||
@@ -38,8 +33,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()
|
||||
@@ -53,6 +46,9 @@ describe('action', () => {
|
||||
.spyOn(fsHelper, 'createArchives')
|
||||
.mockImplementation()
|
||||
removeDirMock = jest.spyOn(fsHelper, 'removeDir').mockImplementation()
|
||||
bundleFilesintoDirectoryMock = jest
|
||||
.spyOn(fsHelper, 'bundleFilesintoDirectory')
|
||||
.mockImplementation()
|
||||
|
||||
// GHCR Client mocks
|
||||
publishOCIArtifactMock = jest
|
||||
@@ -105,7 +101,7 @@ describe('action', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('fails if path is not a directory', async () => {
|
||||
it('fails if multiple paths are provided and staging files fails', async () => {
|
||||
// Mock the environment
|
||||
process.env.GITHUB_REPOSITORY = 'test/test'
|
||||
github.context.eventName = 'release'
|
||||
@@ -117,23 +113,24 @@ describe('action', () => {
|
||||
}
|
||||
getInputMock.mockImplementation((name: string) => {
|
||||
if (name === 'path') {
|
||||
return 'not-a-directory'
|
||||
return 'directory1 directory2'
|
||||
} else if (name === 'registry') {
|
||||
return 'https://ghcr.io'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
isDirectoryMock.mockImplementation(() => false)
|
||||
isDirectoryMock.mockImplementation(() => true)
|
||||
|
||||
bundleFilesintoDirectoryMock.mockImplementation(() => {
|
||||
throw new Error('Something went wrong')
|
||||
})
|
||||
|
||||
// Run the action
|
||||
await main.run()
|
||||
|
||||
// Check the results
|
||||
expect(isDirectoryMock).toHaveBeenCalledWith('not-a-directory')
|
||||
expect(setFailedMock).toHaveBeenCalledWith(
|
||||
'The path not-a-directory is not a directory. Please provide a path to a valid directory.'
|
||||
)
|
||||
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
|
||||
})
|
||||
|
||||
it('fails if an error is thrown from dependent code', async () => {
|
||||
@@ -174,77 +171,97 @@ describe('action', () => {
|
||||
expect(removeDirMock).toHaveBeenCalledWith('/tmp/test')
|
||||
})
|
||||
|
||||
it('uploads and returns the manifest & package URL if all succeeds', async () => {
|
||||
// Mock the environment
|
||||
process.env.GITHUB_REPOSITORY = 'test/test'
|
||||
github.context.eventName = 'release'
|
||||
github.context.payload = {
|
||||
release: {
|
||||
id: '123',
|
||||
tag_name: 'v1.0.0'
|
||||
}
|
||||
}
|
||||
getInputMock.mockImplementation((name: string) => {
|
||||
if (name === 'path') {
|
||||
return 'test'
|
||||
} else if (name === 'registry') {
|
||||
return 'https://ghcr.io'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
it('successfully uploads if the release tag is a semver without v prefix', async () => {
|
||||
await testHappyPath('1.0.0', 'test')
|
||||
})
|
||||
|
||||
isDirectoryMock.mockImplementation(() => true)
|
||||
it('successfully uploads if the release tag is a semver with v prefix', async () => {
|
||||
await testHappyPath('v1.0.0', 'test')
|
||||
})
|
||||
|
||||
createTempDirMock.mockImplementation(() => '/tmp/test')
|
||||
|
||||
createArchivesMock.mockImplementation(() => {
|
||||
return {
|
||||
zipFile: {
|
||||
path: 'test',
|
||||
size: 5,
|
||||
sha256: '123'
|
||||
},
|
||||
tarFile: {
|
||||
path: 'test2',
|
||||
size: 52,
|
||||
sha256: '1234'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
publishOCIArtifactMock.mockImplementation(() => {
|
||||
return new URL('https://ghcr.io/v2/test/test:1.0.0')
|
||||
})
|
||||
|
||||
// Run the action
|
||||
await main.run()
|
||||
|
||||
expect(publishOCIArtifactMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Check manifest is in output
|
||||
expect(setOutputMock).toHaveBeenCalledWith(
|
||||
'package-url',
|
||||
'https://ghcr.io/v2/test/test:1.0.0'
|
||||
)
|
||||
expect(setOutputMock).toHaveBeenCalledWith(
|
||||
'package-manifest',
|
||||
expect.any(String)
|
||||
)
|
||||
|
||||
// Validate the manifest
|
||||
const manifest = JSON.parse(setOutputMock.mock.calls[1][1])
|
||||
expect(manifest.mediaType).toEqual(
|
||||
'application/vnd.oci.image.manifest.v1+json'
|
||||
)
|
||||
expect(manifest.config.mediaType).toEqual(
|
||||
'application/vnd.github.actions.package.config.v1+json'
|
||||
)
|
||||
expect(manifest.layers.length).toEqual(3)
|
||||
expect(manifest.annotations['com.github.package.type']).toEqual(
|
||||
'actions_oci_pkg'
|
||||
)
|
||||
|
||||
// Expect the files to be cleaned up
|
||||
expect(removeDirMock).toHaveBeenCalledWith('/tmp/test')
|
||||
it('successfully uploads if multiple paths are provided', async () => {
|
||||
await testHappyPath('v1.0.0', 'test test2')
|
||||
})
|
||||
})
|
||||
|
||||
// Test that main successfully uploads and returns the manifest & package URL
|
||||
async function testHappyPath(version: string, path: string): Promise<void> {
|
||||
// Mock the environment
|
||||
process.env.GITHUB_REPOSITORY = 'test/test'
|
||||
github.context.eventName = 'release'
|
||||
github.context.payload = {
|
||||
release: {
|
||||
id: '123',
|
||||
tag_name: version
|
||||
}
|
||||
}
|
||||
getInputMock.mockImplementation((name: string) => {
|
||||
if (name === 'path') {
|
||||
return path
|
||||
} else if (name === 'registry') {
|
||||
return 'https://ghcr.io'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
isDirectoryMock.mockImplementation(() => true)
|
||||
|
||||
bundleFilesintoDirectoryMock.mockImplementation(() => {
|
||||
return '/tmp/test'
|
||||
})
|
||||
|
||||
createTempDirMock.mockImplementation(() => '/tmp/test')
|
||||
|
||||
createArchivesMock.mockImplementation(() => {
|
||||
return {
|
||||
zipFile: {
|
||||
path: 'test',
|
||||
size: 5,
|
||||
sha256: '123'
|
||||
},
|
||||
tarFile: {
|
||||
path: 'test2',
|
||||
size: 52,
|
||||
sha256: '1234'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
publishOCIArtifactMock.mockImplementation(() => {
|
||||
return new URL('https://ghcr.io/v2/test/test:1.0.0')
|
||||
})
|
||||
|
||||
// Run the action
|
||||
await main.run()
|
||||
|
||||
expect(publishOCIArtifactMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Check manifest is in output
|
||||
expect(setOutputMock).toHaveBeenCalledWith(
|
||||
'package-url',
|
||||
'https://ghcr.io/v2/test/test:1.0.0'
|
||||
)
|
||||
expect(setOutputMock).toHaveBeenCalledWith(
|
||||
'package-manifest',
|
||||
expect.any(String)
|
||||
)
|
||||
|
||||
// Validate the manifest
|
||||
const manifest = JSON.parse(setOutputMock.mock.calls[1][1])
|
||||
expect(manifest.mediaType).toEqual(
|
||||
'application/vnd.oci.image.manifest.v1+json'
|
||||
)
|
||||
expect(manifest.config.mediaType).toEqual(
|
||||
'application/vnd.github.actions.package.config.v1+json'
|
||||
)
|
||||
expect(manifest.layers.length).toEqual(3)
|
||||
expect(manifest.annotations['com.github.package.type']).toEqual(
|
||||
'actions_oci_pkg'
|
||||
)
|
||||
|
||||
// Expect all the temp files to be cleaned up
|
||||
expect(removeDirMock).toHaveBeenCalledWith('/tmp/test')
|
||||
expect(removeDirMock).toHaveBeenCalledTimes(
|
||||
createTempDirMock.mock.calls.length
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,21 +3,22 @@ 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-org/test-repo'
|
||||
const sanitizedRepo = 'test-org-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",
|
||||
@@ -43,7 +44,7 @@ describe('createActionPackageManigest', () => {
|
||||
"size":${tarFile.size},
|
||||
"digest":"${tarFile.sha256}",
|
||||
"annotations":{
|
||||
"org.opencontainers.image.title":"${repo}-${version}.tar.gz"
|
||||
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.tar.gz"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -51,7 +52,7 @@ describe('createActionPackageManigest', () => {
|
||||
"size":${tarFile.size},
|
||||
"digest":"${tarFile.sha256}",
|
||||
"annotations":{
|
||||
"org.opencontainers.image.title":"${repo}-${version}.zip"
|
||||
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.zip"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -63,7 +64,7 @@ describe('createActionPackageManigest', () => {
|
||||
}
|
||||
}`
|
||||
|
||||
let manifest = createActionPackageManifest(
|
||||
const manifest = createActionPackageManifest(
|
||||
{
|
||||
path: 'test.tar.gz',
|
||||
size: 100,
|
||||
@@ -74,12 +75,12 @@ describe('createActionPackageManigest', () => {
|
||||
size: 100,
|
||||
sha256: '1234567890'
|
||||
},
|
||||
'test-repo',
|
||||
'1.0.0',
|
||||
repo,
|
||||
version,
|
||||
date
|
||||
)
|
||||
|
||||
let manifestJSON = JSON.stringify(manifest)
|
||||
const manifestJSON = JSON.stringify(manifest)
|
||||
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
|
||||
})
|
||||
})
|
||||
|
||||
+3526
-1650
File diff suppressed because one or more lines are too long
+62
@@ -1136,6 +1136,25 @@ Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
fs-extra
|
||||
MIT
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2011-2017 JP Richardson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
|
||||
(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
fs-minipass
|
||||
ISC
|
||||
The ISC License
|
||||
@@ -1320,6 +1339,25 @@ THE SOFTWARE.
|
||||
isarray
|
||||
MIT
|
||||
|
||||
jsonfile
|
||||
MIT
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2012-2015, JP Richardson <jprichardson@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
|
||||
(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
lazystream
|
||||
MIT
|
||||
Copyright (c) 2013 J. Pommerening, contributors.
|
||||
@@ -2198,6 +2236,30 @@ Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
universalify
|
||||
MIT
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017, Ryan Zimmerman <opensrc@ryanzim.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the 'Software'), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
util-deprecate
|
||||
MIT
|
||||
(The MIT License)
|
||||
|
||||
Generated
+131
-71
@@ -12,20 +12,23 @@
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"archiver": "^6.0.1",
|
||||
"axios": "^1.6.1",
|
||||
"axios": "^1.6.2",
|
||||
"axios-debug-log": "^1.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"tar": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^6.0.1",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/jest": "^29.5.8",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/node": "^20.9.4",
|
||||
"@types/tar": "^6.1.9",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-github": "^4.10.1",
|
||||
"eslint-plugin-jest": "^27.6.0",
|
||||
"eslint-plugin-jsonc": "^2.10.0",
|
||||
@@ -33,10 +36,10 @@
|
||||
"jest": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"make-coverage-badge": "^1.2.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-eslint": "^16.1.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -821,9 +824,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.53.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
|
||||
"integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
|
||||
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@@ -1518,6 +1521,16 @@
|
||||
"@types/readdir-glob": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/axios": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
|
||||
"integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==",
|
||||
"deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"axios": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz",
|
||||
@@ -1587,6 +1600,15 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/fs-extra": {
|
||||
"version": "11.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
|
||||
"integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
|
||||
"dependencies": {
|
||||
"@types/jsonfile": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz",
|
||||
@@ -1642,16 +1664,23 @@
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/jsonfile": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
|
||||
"integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "0.7.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
|
||||
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
|
||||
"integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
|
||||
"dev": true,
|
||||
"version": "20.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
|
||||
"integrity": "sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA==",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
@@ -1712,16 +1741,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz",
|
||||
"integrity": "sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.12.0.tgz",
|
||||
"integrity": "sha512-XOpZ3IyJUIV1b15M7HVOpgQxPPF7lGXgsfcEIu3yDxFPaf/xZKt7s9QO/pbk7vpWQyVulpJbu4E5LwpZiQo4kA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.5.1",
|
||||
"@typescript-eslint/scope-manager": "6.10.0",
|
||||
"@typescript-eslint/type-utils": "6.10.0",
|
||||
"@typescript-eslint/utils": "6.10.0",
|
||||
"@typescript-eslint/visitor-keys": "6.10.0",
|
||||
"@typescript-eslint/scope-manager": "6.12.0",
|
||||
"@typescript-eslint/type-utils": "6.12.0",
|
||||
"@typescript-eslint/utils": "6.12.0",
|
||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
||||
"debug": "^4.3.4",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.2.4",
|
||||
@@ -1747,15 +1776,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz",
|
||||
"integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz",
|
||||
"integrity": "sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "6.10.0",
|
||||
"@typescript-eslint/types": "6.10.0",
|
||||
"@typescript-eslint/typescript-estree": "6.10.0",
|
||||
"@typescript-eslint/visitor-keys": "6.10.0",
|
||||
"@typescript-eslint/scope-manager": "6.12.0",
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1775,13 +1804,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz",
|
||||
"integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.12.0.tgz",
|
||||
"integrity": "sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.10.0",
|
||||
"@typescript-eslint/visitor-keys": "6.10.0"
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"@typescript-eslint/visitor-keys": "6.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.0.0 || >=18.0.0"
|
||||
@@ -1792,13 +1821,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz",
|
||||
"integrity": "sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.12.0.tgz",
|
||||
"integrity": "sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "6.10.0",
|
||||
"@typescript-eslint/utils": "6.10.0",
|
||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
||||
"@typescript-eslint/utils": "6.12.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.0.1"
|
||||
},
|
||||
@@ -1819,9 +1848,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz",
|
||||
"integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.12.0.tgz",
|
||||
"integrity": "sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^16.0.0 || >=18.0.0"
|
||||
@@ -1832,13 +1861,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz",
|
||||
"integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.12.0.tgz",
|
||||
"integrity": "sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.10.0",
|
||||
"@typescript-eslint/visitor-keys": "6.10.0",
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -1859,17 +1888,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.10.0.tgz",
|
||||
"integrity": "sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.12.0.tgz",
|
||||
"integrity": "sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@types/json-schema": "^7.0.12",
|
||||
"@types/semver": "^7.5.0",
|
||||
"@typescript-eslint/scope-manager": "6.10.0",
|
||||
"@typescript-eslint/types": "6.10.0",
|
||||
"@typescript-eslint/typescript-estree": "6.10.0",
|
||||
"@typescript-eslint/scope-manager": "6.12.0",
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1884,12 +1913,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz",
|
||||
"integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.12.0.tgz",
|
||||
"integrity": "sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.10.0",
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"eslint-visitor-keys": "^3.4.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2255,9 +2284,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",
|
||||
"integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -3244,15 +3273,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.53.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz",
|
||||
"integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
|
||||
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
"@eslint/eslintrc": "^2.1.3",
|
||||
"@eslint/js": "8.53.0",
|
||||
"@eslint/js": "8.54.0",
|
||||
"@humanwhocodes/config-array": "^0.11.13",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@@ -4102,6 +4131,19 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
|
||||
"integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
@@ -5616,6 +5658,17 @@
|
||||
"url": "https://github.com/sponsors/ota-meshi"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsx-ast-utils": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||
@@ -6475,9 +6528,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
||||
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
|
||||
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
@@ -7511,9 +7564,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
|
||||
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -7552,14 +7605,21 @@
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/untildify": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
|
||||
|
||||
+10
-7
@@ -69,20 +69,23 @@
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"archiver": "^6.0.1",
|
||||
"axios": "^1.6.1",
|
||||
"axios": "^1.6.2",
|
||||
"axios-debug-log": "^1.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"tar": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^6.0.1",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/jest": "^29.5.8",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/node": "^20.9.4",
|
||||
"@types/tar": "^6.1.9",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-github": "^4.10.1",
|
||||
"eslint-plugin-jest": "^27.6.0",
|
||||
"eslint-plugin-jsonc": "^2.10.0",
|
||||
@@ -90,9 +93,9 @@
|
||||
"jest": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"make-coverage-badge": "^1.2.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-eslint": "^16.1.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
+82
-51
@@ -1,14 +1,12 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as exec from '@actions/exec'
|
||||
import * as fs from 'fs'
|
||||
import fsExtra from 'fs-extra'
|
||||
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,8 +17,10 @@ export function createTempDir() {
|
||||
return tempDir
|
||||
}
|
||||
|
||||
export function removeDir(dir: string) {
|
||||
fs.rmSync(dir, { recursive: true })
|
||||
export function removeDir(dir: string): void {
|
||||
if (fs.existsSync(dir)) {
|
||||
fs.rmSync(dir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
export interface FileMetadata {
|
||||
@@ -38,59 +38,90 @@ export async function createArchives(
|
||||
const zipPath = path.join(archiveTargetPath, `archive.zip`)
|
||||
const tarPath = path.join(archiveTargetPath, `archive.tar.gz`)
|
||||
|
||||
return Promise.all([
|
||||
new Promise<FileMetadata>((resolve, reject) => {
|
||||
const output = fs.createWriteStream(zipPath)
|
||||
const archive = archiver.create('zip')
|
||||
const createZipPromise = new Promise<FileMetadata>((resolve, reject) => {
|
||||
const output = fs.createWriteStream(zipPath)
|
||||
const archive = archiver.create('zip')
|
||||
|
||||
output.on('error', (err: Error) => {
|
||||
reject(err)
|
||||
})
|
||||
|
||||
archive.on('error', (err: Error) => {
|
||||
reject(err)
|
||||
})
|
||||
|
||||
output.on('close', () => {
|
||||
resolve(fileMetadata(zipPath))
|
||||
})
|
||||
|
||||
archive.pipe(output)
|
||||
archive.directory(distPath, false)
|
||||
archive.finalize()
|
||||
}),
|
||||
new Promise<FileMetadata>((resolve, reject) => {
|
||||
const tarStream = tar
|
||||
.c(
|
||||
{
|
||||
file: tarPath,
|
||||
C: distPath, // Change to the source directory for relative paths (TODO)
|
||||
gzip: true
|
||||
},
|
||||
['.']
|
||||
)
|
||||
.then(() => {
|
||||
resolve(fileMetadata(tarPath))
|
||||
})
|
||||
.catch((err: Error) => reject(err))
|
||||
output.on('error', (err: Error) => {
|
||||
reject(err)
|
||||
})
|
||||
]).then(([zipFile, tarFile]) => ({ zipFile, tarFile }))
|
||||
|
||||
archive.on('error', (err: Error) => {
|
||||
reject(err)
|
||||
})
|
||||
|
||||
output.on('close', () => {
|
||||
resolve(fileMetadata(zipPath))
|
||||
})
|
||||
|
||||
archive.pipe(output)
|
||||
archive.directory(distPath, false)
|
||||
archive.finalize()
|
||||
})
|
||||
|
||||
const createTarPromise = new Promise<FileMetadata>((resolve, reject) => {
|
||||
tar
|
||||
.c(
|
||||
{
|
||||
file: tarPath,
|
||||
C: distPath, // Change to the source directory for relative paths (TODO)
|
||||
gzip: true
|
||||
},
|
||||
['.']
|
||||
)
|
||||
// eslint-disable-next-line github/no-then
|
||||
.catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
// eslint-disable-next-line github/no-then
|
||||
.then(() => {
|
||||
resolve(fileMetadata(tarPath))
|
||||
})
|
||||
})
|
||||
|
||||
const [zipFile, tarFile] = await Promise.all([
|
||||
createZipPromise,
|
||||
createTarPromise
|
||||
])
|
||||
|
||||
return { 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)
|
||||
}
|
||||
|
||||
export function bundleFilesintoDirectory(
|
||||
files: string[],
|
||||
targetDir: string = createTempDir()
|
||||
): string {
|
||||
for (const file of files) {
|
||||
if (!fs.existsSync(file)) {
|
||||
throw new Error(`File ${file} does not exist`)
|
||||
}
|
||||
|
||||
if (isDirectory(file)) {
|
||||
const targetFolder = path.join(targetDir, path.basename(file))
|
||||
fsExtra.copySync(file, targetFolder)
|
||||
} else {
|
||||
const targetFile = path.join(targetDir, path.basename(file))
|
||||
fs.copyFileSync(file, targetFile)
|
||||
}
|
||||
}
|
||||
|
||||
return targetDir
|
||||
}
|
||||
|
||||
// Converts a file path to a filemetadata object by querying the fs for relevant metadata.
|
||||
async function fileMetadata(path: string): Promise<FileMetadata> {
|
||||
const stats = fs.statSync(path)
|
||||
async function fileMetadata(filePath: string): Promise<FileMetadata> {
|
||||
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 +129,9 @@ async function fileMetadata(path: string): Promise<FileMetadata> {
|
||||
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 => {
|
||||
|
||||
+16
-19
@@ -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<URL> {
|
||||
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<void>[] = manifest.layers.map(layer => {
|
||||
const layerUploads: Promise<void>[] = 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}`)
|
||||
}
|
||||
})
|
||||
|
||||
+24
-15
@@ -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<void>} Resolves when the action is complete.
|
||||
*/
|
||||
export async function run(): Promise<void> {
|
||||
let tmpDir: string = ''
|
||||
const tmpDirs: string[] = []
|
||||
|
||||
try {
|
||||
// Parse and validate Actions execution context, including the repository name, release name and event type
|
||||
@@ -29,8 +28,9 @@ export async function run(): Promise<void> {
|
||||
|
||||
// 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) {
|
||||
// TODO: We may want to limit semvers to only x.x.x, without the pre-release tags, but for now we'll allow them.
|
||||
core.setFailed(
|
||||
`${releaseTag} is not a valid semantic version, and so cannot be uploaded as an Immutable Action.`
|
||||
)
|
||||
@@ -39,20 +39,27 @@ export async function run(): Promise<void> {
|
||||
|
||||
// Gather & validate user inputs
|
||||
const token: string = core.getInput('token')
|
||||
const path: string = core.getInput('path')
|
||||
const registryURL: URL = new URL(core.getInput('registry')) // TODO: Should this be dynamic? Maybe an API endpoint to grab the registry for GHES/proxima purposes.
|
||||
|
||||
if (!fsHelper.isDirectory(path)) {
|
||||
core.setFailed(
|
||||
`The path ${path} is not a directory. Please provide a path to a valid directory.`
|
||||
)
|
||||
return
|
||||
// Paths to be included in the OCI image
|
||||
const paths: string[] = core.getInput('path').split(' ')
|
||||
let path = ''
|
||||
|
||||
if (paths.length === 1 && fsHelper.isDirectory(paths[0])) {
|
||||
// If the path is a single directory, we can skip the bundling step
|
||||
path = paths[0]
|
||||
} else {
|
||||
// Otherwise, we need to bundle the files & folders into a temporary directory
|
||||
const bundleDir = fsHelper.createTempDir()
|
||||
tmpDirs.push(bundleDir)
|
||||
path = fsHelper.bundleFilesintoDirectory(paths, bundleDir)
|
||||
}
|
||||
|
||||
// Create a temporary directory to store the archives
|
||||
tmpDir = fsHelper.createTempDir()
|
||||
const archiveDir = fsHelper.createTempDir()
|
||||
tmpDirs.push(archiveDir)
|
||||
|
||||
const archives = await fsHelper.createArchives(path)
|
||||
const archives = await fsHelper.createArchives(path, archiveDir)
|
||||
|
||||
const manifest = ociContainer.createActionPackageManifest(
|
||||
archives.tarFile,
|
||||
@@ -62,7 +69,7 @@ export async function run(): Promise<void> {
|
||||
new Date()
|
||||
)
|
||||
|
||||
let packageURL = await ghcr.publishOCIArtifact(
|
||||
const packageURL = await ghcr.publishOCIArtifact(
|
||||
token,
|
||||
registryURL,
|
||||
repository,
|
||||
@@ -84,9 +91,11 @@ export async function run(): Promise<void> {
|
||||
// Fail the workflow run if an error occurs
|
||||
if (error instanceof Error) core.setFailed(error.message)
|
||||
} finally {
|
||||
// Clean up the temporary directory if it exists
|
||||
if (tmpDir !== '') {
|
||||
fsHelper.removeDir(tmpDir)
|
||||
// Clean up any temporary directories that exist
|
||||
for (const tmpDir of tmpDirs) {
|
||||
if (tmpDir !== '') {
|
||||
fsHelper.removeDir(tmpDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-7
@@ -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.
|
||||
@@ -26,8 +25,9 @@ export function createActionPackageManifest(
|
||||
created: Date
|
||||
): Manifest {
|
||||
const configLayer = createConfigLayer()
|
||||
const tarLayer = createTarLayer(tarFile, repository, version)
|
||||
const zipLayer = createZipLayer(zipFile, repository, version)
|
||||
const sanitizedRepo = sanitizeRepository(repository)
|
||||
const tarLayer = createTarLayer(tarFile, sanitizedRepo, version)
|
||||
const zipLayer = createZipLayer(zipFile, sanitizedRepo, version)
|
||||
|
||||
const manifest: Manifest = {
|
||||
schemaVersion: 2,
|
||||
@@ -71,7 +71,7 @@ function createZipLayer(
|
||||
size: zipFile.size,
|
||||
digest: zipFile.sha256,
|
||||
annotations: {
|
||||
'org.opencontainers.image.title': `${repository}-${version}.zip`
|
||||
'org.opencontainers.image.title': `${repository}_${version}.zip`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +88,15 @@ function createTarLayer(
|
||||
size: tarFile.size,
|
||||
digest: tarFile.sha256,
|
||||
annotations: {
|
||||
'org.opencontainers.image.title': `${repository}-${version}.tar.gz`
|
||||
'org.opencontainers.image.title': `${repository}_${version}.tar.gz`
|
||||
}
|
||||
}
|
||||
|
||||
return tarLayer
|
||||
}
|
||||
|
||||
// Remove slashes so we can use the repository in a filename
|
||||
// repository usually includes the namespace too, e.g. my-org/my-repo
|
||||
function sanitizeRepository(repository: string): string {
|
||||
return repository.replace('/', '-')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user