mirror of
https://github.com/seepine/hash-files.git
synced 2025-04-02 21:40:13 +08:00
chore(release): v1.0.0
This commit is contained in:
parent
848c6fc5e6
commit
6da8771096
16
.editorconfig
Normal file
16
.editorconfig
Normal 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
|
@ -2,3 +2,4 @@ dist/
|
||||
lib/
|
||||
node_modules/
|
||||
jest.config.js
|
||||
__tests__/
|
||||
|
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@ -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
12
.vscode/settings.json
vendored
Normal 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
142
README.md
@ -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
|
||||
|
@ -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 = {
|
||||
|
36
action.yml
36
action.yml
@ -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
260
dist/index.js
generated
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
3
dist/licenses.txt
generated
vendored
3
dist/licenses.txt
generated
vendored
@ -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
5010
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@ -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
12
src/constants.ts
Normal 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
22
src/files.ts
Normal 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')
|
||||
}
|
37
src/main.ts
37
src/main.ts
@ -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
40
src/utils.ts
Normal 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')
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user