chore(release): v1.0.0

This commit is contained in:
seepine 2023-08-01 23:31:36 +08:00
parent 848c6fc5e6
commit 6da8771096
17 changed files with 617 additions and 5049 deletions

16
.editorconfig Normal file
View File

@ -0,0 +1,16 @@
# Editor configuration, see http://editorconfig.org
# 表示是最顶层的 EditorConfig 配置文件
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格tab | space
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false

View File

@ -2,3 +2,4 @@ dist/
lib/
node_modules/
jest.config.js
__tests__/

View File

@ -15,10 +15,17 @@ jobs:
npm install
- run: |
npm run all
test: # make sure the action works on a clean machine without building
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./
id: get-hash
with:
milliseconds: 1000
patterns: |
**/package-lock.json
- name: Echo hash
run: echo ${{ steps.get-hash.outputs.hash }}

12
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
// eslint
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
//
"emmet.showExpandedAbbreviation": "never",
//
"editor.minimap.enabled": false,
//
"editor.wordWrap": "on"
}

142
README.md
View File

@ -1,105 +1,57 @@
<p align="center">
<a href="https://github.com/actions/typescript-action/actions"><img alt="typescript-action status" src="https://github.com/actions/typescript-action/workflows/build-test/badge.svg"></a>
</p>
# hash-files
# Create a JavaScript Action using TypeScript
This action is to compute the SHA256 hash of specified files.
Use this template to bootstrap the creation of a TypeScript action.:rocket:
The hash function is based on [nektos/act](https://github.com/nektos/act/blob/ac5dd8feb876d37ae483376a137c57383577dace/pkg/exprparser/functions.go#L183). Thanks!
This template includes compilation support, tests, a validation workflow, publishing, and versioning guidance.
**NOTE:** This action is written in Go. Please setup the Go environment before running this action or use a runner with Go environment installed.
If you are new, there's also a simpler introduction. See the [Hello World JavaScript Action](https://github.com/actions/hello-world-javascript-action)
## Usage
## Create an action from this template
``` yml
- uses: actions/go-hashfiles@v0.0.1
with:
# The working dir for the action.
# Default: ${{ github.workspace }}
workdir: ''
Click the `Use this Template` and provide the new repo details for your action
# The patterns used to match files.
patterns: '**/package-lock.json'
## Code in Main
> First, you'll need to have a reasonably modern version of `node` handy. This won't work with versions older than 9, for instance.
Install the dependencies
```bash
$ npm install
# Multiple patterns should be seperated by `\n`
patterns: |
**/package-lock.json
**/yarn.lock
```
Build the typescript and package it for distribution
```bash
$ npm run build && npm run package
## Input
|Output Item|Description|Required|Default|
|---|---|---|---|
|workdir|The working dir for the action|false|${{ github.workspace }}|
|patterns|The patterns used to match files|true||
|gitignore|Respect ignore patterns in .gitignore files that apply to the globbed files.|false|true
|ignoreFiles|Glob patterns to look for ignore files, which are then used to ignore globbed files.|false|
## Output
|Output Item|Description|
|---|---|
|hash|The computed hash result|
|matched-files|The files matched by the patterns|
## Example
``` yml
# Setup the Node environment. This step can be skipped if Node has been installed.
- uses: actions/setup-node@v3
- uses: actions/go-hashfiles@v1
id: get-hash
with:
patterns: |
**/package-lock.json
**/yarn.lock
- name: Echo hash
run: echo ${{ steps.get-hash.outputs.hash }}
```
Run the tests :heavy_check_mark:
```bash
$ npm test
PASS ./index.test.js
✓ throws invalid number (3ms)
✓ wait 500 ms (504ms)
✓ test runs (95ms)
...
```
## Change action.yml
The action.yml defines the inputs and output for your action.
Update the action.yml with your name, description, inputs and outputs for your action.
See the [documentation](https://help.github.com/en/articles/metadata-syntax-for-github-actions)
## Change the Code
Most toolkit and CI/CD operations involve async operations so the action is run in an async function.
```javascript
import * as core from '@actions/core';
...
async function run() {
try {
...
}
catch (error) {
core.setFailed(error.message);
}
}
run()
```
See the [toolkit documentation](https://github.com/actions/toolkit/blob/master/README.md#packages) for the various packages.
## Publish to a distribution branch
Actions are run from GitHub repos so we will checkin the packed dist folder.
Then run [ncc](https://github.com/zeit/ncc) and push the results:
```bash
$ npm run package
$ git add dist
$ git commit -a -m "prod dependencies"
$ git push origin releases/v1
```
Note: We recommend using the `--license` option for ncc, which will create a license file for all of the production node modules used in your project.
Your action is now published! :rocket:
See the [versioning documentation](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md)
## Validate
You can now validate the action by referencing `./` in a workflow in your repo (see [test.yml](.github/workflows/test.yml))
```yaml
uses: ./
with:
milliseconds: 1000
```
See the [actions tab](https://github.com/actions/typescript-action/actions) for runs of this action! :rocket:
## Usage:
After testing you can [create a v1 tag](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md) to reference the stable and latest V1 action

View File

@ -1,25 +1,34 @@
import {wait} from '../src/wait'
import {getFiles, readFile} from '../src/files'
import {hashHex} from '../src/utils'
import * as process from 'process'
import * as cp from 'child_process'
import * as path from 'path'
import {expect, test} from '@jest/globals'
import {test} from '@jest/globals'
test('throws invalid number', async () => {
const input = parseInt('foo', 10)
await expect(wait(input)).rejects.toThrow('milliseconds not a number')
test('test sha256', async () => {
console.log(hashHex('this is content'))
})
test('wait 500 ms', async () => {
const start = new Date()
await wait(500)
const end = new Date()
var delta = Math.abs(end.getTime() - start.getTime())
expect(delta).toBeGreaterThan(450)
test('test sha512', async () => {
console.log(hashHex('this is content', 'sha512'))
})
test('test readFile', async () => {
const content = await readFile('./.prettierignore')
console.log(content)
})
test('test getFiles', async () => {
const paths = await getFiles('./', ['**/*.ts', '**/package-lock.json'], {
gitignore: true
})
console.log(paths)
})
// shows how the runner will run a javascript action with env / stdout protocol
test('test runs', () => {
process.env['INPUT_MILLISECONDS'] = '500'
process.env['INPUT_WORKDIR'] = './'
process.env['INPUT_PATTERNS'] = '**/*.ts\n**/package-lock.json'
process.env['INPUT_GITIGNORE'] = 'true'
const np = process.execPath
const ip = path.join(__dirname, '..', 'lib', 'main.js')
const options: cp.ExecFileSyncOptions = {

View File

@ -1,11 +1,33 @@
name: 'Your name here'
description: 'Provide a description here'
author: 'Your name or organization here'
name: 'hash-files'
description: 'Compute the SHA256 hash of specified files'
author: 'seepine'
inputs:
milliseconds: # change this
workdir:
description: >
The working directory for the action.
default: ${{ github.workspace }}
required: false
patterns:
description: >
The patterns used to match files. You can input multiple patterns seperated by `\n`.
We recommand using `|` to concat the patterns in `.yml` files.
required: true
description: 'input description here'
default: 'default value if applicable'
gitignore:
description: >
Respect ignore patterns in .gitignore files that apply to the globbed files.
default: true
required: false
ignoreFiles:
descruption: >
Glob patterns to look for ignore files, which are then used to ignore globbed files.
This is a more generic form of the gitignore option, allowing you to find ignore files with a compatible syntax.
For instance, this works with Babel's .babelignore, Prettier's .prettierignore, or ESLint's .eslintignore files.
required: false
outputs:
hash:
description: 'The computed hash result'
matched-files:
description: 'The files matched by the patterns'
runs:
using: 'node16'
using: 'node'
main: 'dist/index.js'

260
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

3
dist/licenses.txt generated vendored
View File

@ -35,6 +35,9 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@cjs-exporter/globby
MIT
tunnel
MIT
The MIT License (MIT)

5010
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{
"name": "typescript-action",
"version": "0.0.0",
"name": "hash-files",
"version": "1.0.0",
"private": true,
"description": "TypeScript template action",
"description": "Actions of Compute the SHA256 hash of specified files",
"main": "lib/main.js",
"scripts": {
"build": "tsc",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts",
"package": "ncc build --source-map --license licenses.txt",
"test": "jest",
@ -15,17 +15,19 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/typescript-action.git"
"url": "git+https://github.com/seepine/hash-files.git"
},
"keywords": [
"actions",
"node",
"setup"
"hash-files"
],
"author": "",
"author": "seepine",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0"
"@actions/core": "^1.10.0",
"@cjs-exporter/globby": "^13.1.3",
"crypto": "^1.0.1"
},
"devDependencies": {
"@types/node": "^18.16.3",

12
src/constants.ts Normal file
View File

@ -0,0 +1,12 @@
/* eslint-disable no-shadow */
export enum Inputs {
Workdir = 'workdir', // Input for cache, restore, save action
Patterns = 'patterns', // Input for cache, restore, save action
Gitignore = 'gitignore', // Input for cache, restore action
IgnoreFiles = 'ignoreFiles' // Input for cache, save action
}
export enum Outputs {
Hash = 'hash', // Output from cache, restore action
MatchedFiles = 'matched-files' // Output from restore action
}

22
src/files.ts Normal file
View File

@ -0,0 +1,22 @@
import {Options, globby} from '@cjs-exporter/globby'
import * as fs from 'fs'
export async function getFiles(
workdir: string,
patterns: string[],
options?: Options
): Promise<string[]> {
return new Promise(async RES => {
const paths = await globby(
patterns.map(item => {
return workdir + item
}),
options
)
RES(paths)
})
}
export async function readFile(path: string): Promise<string> {
return fs.promises.readFile(path, 'utf-8')
}

View File

@ -1,16 +1,37 @@
import * as core from '@actions/core'
import {wait} from './wait'
import {Inputs, Outputs} from './constants'
import * as utils from './utils'
import {getFiles, readFile} from './files'
async function run(): Promise<void> {
try {
const ms: string = core.getInput('milliseconds')
core.debug(`Waiting ${ms} milliseconds ...`) // debug is only output if you set the secret `ACTIONS_STEP_DEBUG` to true
const workdir: string = utils.getInput(Inputs.Workdir, {required: true})
const patterns = utils.getInputAsArray(Inputs.Patterns, {
required: true
})
const gitignore = utils.getInputAsBool(Inputs.Gitignore) || true
const ignoreFiles = utils.getInputAsArray(Inputs.IgnoreFiles)
core.debug(`workdir: ${workdir}`)
core.debug(`patterns: ${patterns}`)
core.debug(`gitignore: ${gitignore}`)
core.debug(`ignoreFiles: ${ignoreFiles}`)
core.debug(new Date().toTimeString())
await wait(parseInt(ms, 10))
core.debug(new Date().toTimeString())
core.setOutput('time', new Date().toTimeString())
const files = await getFiles(workdir, patterns, {gitignore, ignoreFiles})
let hash = ''
core.debug(files.toString())
const reads = files.map(async file => readFile(file))
const contents = await Promise.all(reads)
if (contents.length === 1) {
hash = contents[0]
} else if (contents.length > 1) {
let hashStr = ''
for (const content of contents) {
hashStr += content
}
hash = utils.hashHex(hashStr)
}
core.setOutput(Outputs.Hash, hash)
core.setOutput(Outputs.MatchedFiles, files)
} catch (error) {
if (error instanceof Error) core.setFailed(error.message)
}

40
src/utils.ts Normal file
View File

@ -0,0 +1,40 @@
import * as core from '@actions/core'
import crypto, {BinaryLike} from 'crypto'
export function getInput(name: string, options?: core.InputOptions): string {
return core.getInput(name, options)
}
export function getInputAsArray(
name: string,
options?: core.InputOptions
): string[] {
return core
.getInput(name, options)
.split('\n')
.map(s => s.replace(/^!\s+/, '!').trim())
.filter(x => x !== '')
}
export function getInputAsInt(
name: string,
options?: core.InputOptions
): number | undefined {
const value = parseInt(core.getInput(name, options))
if (isNaN(value) || value < 0) {
return undefined
}
return value
}
export function getInputAsBool(
name: string,
options?: core.InputOptions
): boolean {
const result = core.getInput(name, options)
return result.toLowerCase() === 'true'
}
export function hashHex(content: BinaryLike, shaAlgorithm = 'sha256'): string {
return crypto.createHash(shaAlgorithm).update(content).digest('hex')
}

View File

@ -1,9 +0,0 @@
export async function wait(milliseconds: number): Promise<string> {
return new Promise(resolve => {
if (isNaN(milliseconds)) {
throw new Error('milliseconds not a number')
}
setTimeout(() => resolve('done!'), milliseconds)
})
}