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/ lib/
node_modules/ node_modules/
jest.config.js jest.config.js
__tests__/

View File

@ -15,10 +15,17 @@ jobs:
npm install npm install
- run: | - run: |
npm run all npm run all
test: # make sure the action works on a clean machine without building test: # make sure the action works on a clean machine without building
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: ./ - uses: ./
id: get-hash
with: 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"> # hash-files
<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>
# 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 # Multiple patterns should be seperated by `\n`
patterns: |
> First, you'll need to have a reasonably modern version of `node` handy. This won't work with versions older than 9, for instance. **/package-lock.json
**/yarn.lock
Install the dependencies
```bash
$ npm install
``` ```
Build the typescript and package it for distribution ## Input
```bash
$ npm run build && npm run package |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 process from 'process'
import * as cp from 'child_process' import * as cp from 'child_process'
import * as path from 'path' import * as path from 'path'
import {expect, test} from '@jest/globals' import {test} from '@jest/globals'
test('throws invalid number', async () => { test('test sha256', async () => {
const input = parseInt('foo', 10) console.log(hashHex('this is content'))
await expect(wait(input)).rejects.toThrow('milliseconds not a number')
}) })
test('wait 500 ms', async () => { test('test sha512', async () => {
const start = new Date() console.log(hashHex('this is content', 'sha512'))
await wait(500) })
const end = new Date()
var delta = Math.abs(end.getTime() - start.getTime()) test('test readFile', async () => {
expect(delta).toBeGreaterThan(450) 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', () => { 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 np = process.execPath
const ip = path.join(__dirname, '..', 'lib', 'main.js') const ip = path.join(__dirname, '..', 'lib', 'main.js')
const options: cp.ExecFileSyncOptions = { const options: cp.ExecFileSyncOptions = {

View File

@ -1,11 +1,33 @@
name: 'Your name here' name: 'hash-files'
description: 'Provide a description here' description: 'Compute the SHA256 hash of specified files'
author: 'Your name or organization here' author: 'seepine'
inputs: 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 required: true
description: 'input description here' gitignore:
default: 'default value if applicable' 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: runs:
using: 'node16' using: 'node'
main: 'dist/index.js' 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. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@cjs-exporter/globby
MIT
tunnel tunnel
MIT MIT
The MIT License (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", "name": "hash-files",
"version": "0.0.0", "version": "1.0.0",
"private": true, "private": true,
"description": "TypeScript template action", "description": "Actions of Compute the SHA256 hash of specified files",
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"format": "prettier --write '**/*.ts'", "format": "prettier --write **/*.ts",
"format-check": "prettier --check '**/*.ts'", "format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts", "lint": "eslint src/**/*.ts",
"package": "ncc build --source-map --license licenses.txt", "package": "ncc build --source-map --license licenses.txt",
"test": "jest", "test": "jest",
@ -15,17 +15,19 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/actions/typescript-action.git" "url": "git+https://github.com/seepine/hash-files.git"
}, },
"keywords": [ "keywords": [
"actions", "actions",
"node", "node",
"setup" "hash-files"
], ],
"author": "", "author": "seepine",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0" "@actions/core": "^1.10.0",
"@cjs-exporter/globby": "^13.1.3",
"crypto": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.16.3", "@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 * 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> { async function run(): Promise<void> {
try { try {
const ms: string = core.getInput('milliseconds') const workdir: string = utils.getInput(Inputs.Workdir, {required: true})
core.debug(`Waiting ${ms} milliseconds ...`) // debug is only output if you set the secret `ACTIONS_STEP_DEBUG` to 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()) const files = await getFiles(workdir, patterns, {gitignore, ignoreFiles})
await wait(parseInt(ms, 10)) let hash = ''
core.debug(new Date().toTimeString()) core.debug(files.toString())
const reads = files.map(async file => readFile(file))
core.setOutput('time', new Date().toTimeString()) 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) { } catch (error) {
if (error instanceof Error) core.setFailed(error.message) 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)
})
}