mirror of
https://github.com/apache/superset.git
synced 2026-04-25 02:55:07 +00:00
build: inline external Github Actions to unblock CI (#12241)
* build: inline cached-dependencies to unblock CI * Run E2E on pull_request on;y * Inline all external actions * Checkout needed for internal actions Also fixes pre-commit * Add missing files
This commit is contained in:
1
.github/actions/cached-dependencies/.editorconfig
vendored
Normal file
1
.github/actions/cached-dependencies/.editorconfig
vendored
Normal file
@@ -0,0 +1 @@
|
||||
indent_size = 2
|
||||
3
.github/actions/cached-dependencies/.eslintignore
vendored
Normal file
3
.github/actions/cached-dependencies/.eslintignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
lib/
|
||||
node_modules/
|
||||
26
.github/actions/cached-dependencies/.eslintrc.js
vendored
Normal file
26
.github/actions/cached-dependencies/.eslintrc.js
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
plugins: ['jest', '@typescript-eslint'],
|
||||
extends: ['plugin:jest/all'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 9,
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'eslint-comments/no-use': 'off',
|
||||
'import/no-namespace': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'no-console': 'off',
|
||||
'jest/prefer-expect-assertions': 'off',
|
||||
'jest/no-disabled-tests': 'warn',
|
||||
'jest/no-focused-tests': 'error',
|
||||
'jest/no-identical-title': 'error',
|
||||
'jest/prefer-to-have-length': 'warn',
|
||||
'jest/valid-expect': 'error',
|
||||
},
|
||||
env: {
|
||||
node: true,
|
||||
es6: true,
|
||||
'jest/globals': true,
|
||||
},
|
||||
};
|
||||
34
.github/actions/cached-dependencies/.github/workflows/tests.yml
vendored
Normal file
34
.github/actions/cached-dependencies/.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Tests
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest]
|
||||
name: Test on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run prettier format check
|
||||
run: npm run format-check
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
- name: Upload code coverage
|
||||
run: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
6
.github/actions/cached-dependencies/.gitignore
vendored
Normal file
6
.github/actions/cached-dependencies/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
lib
|
||||
coverage
|
||||
node_modules
|
||||
|
||||
!dist
|
||||
!dist/cache
|
||||
3
.github/actions/cached-dependencies/.prettierignore
vendored
Normal file
3
.github/actions/cached-dependencies/.prettierignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
lib/
|
||||
node_modules/
|
||||
11
.github/actions/cached-dependencies/.prettierrc.json
vendored
Normal file
11
.github/actions/cached-dependencies/.prettierrc.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"parser": "typescript"
|
||||
}
|
||||
22
.github/actions/cached-dependencies/LICENSE
vendored
Normal file
22
.github/actions/cached-dependencies/LICENSE
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 GitHub, Inc. and contributors
|
||||
|
||||
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.
|
||||
212
.github/actions/cached-dependencies/README.md
vendored
Normal file
212
.github/actions/cached-dependencies/README.md
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
# cached-dependencies
|
||||
|
||||
[](https://github.com/ktmud/cached-dependencies/actions?query=workflow%3ATests) [](https://codecov.io/gh/ktmud/cached-dependencies)
|
||||
|
||||
Enable **multi-layer cache** and **shortcut commands** in any workflows.
|
||||
|
||||
Manage multiple cache targets in one step. Use either the built-in cache configs for npm, yarn, and pip, or write your own. Create a bash command library to easily reduce redudencies across workflows. Most useful for building webapps that require multi-stage building processes.
|
||||
|
||||
This is your all-in-one action for everything related to setting up dependencies with cache.
|
||||
|
||||
## Inputs
|
||||
|
||||
- **run**: bash commands to run, allows shortcut commands
|
||||
- **caches**: path to a JS module that defines cache targets, defaults to `.github/workflows/caches.js`
|
||||
- **bashlib**: path to a BASH scripts that defines shortcut commands, defaults to `.github/workflows/bashlib.sh`
|
||||
- **parallel**: whether to run the commands in parallel with node subprocesses
|
||||
|
||||
## Examples
|
||||
|
||||
Following workflow sets up dependencies for a typical Python web app with both `~/.pip` and `~/.npm` cache configured in one simple step:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
uses: ktmud/cached-dependencies@v1
|
||||
with:
|
||||
run: |
|
||||
npm-install
|
||||
npm run build
|
||||
|
||||
pip-install
|
||||
python ./bin/manager.py fill_test_data
|
||||
```
|
||||
|
||||
Here we used predefined `npm-install` and `pip-install` commands to install dependencies with correponding caches.
|
||||
|
||||
You may also replace `npm-install` with `yarn-install` to install npm pacakges with `yarn.lock`.
|
||||
|
||||
```yaml
|
||||
- name: Install dependencies
|
||||
uses: ktmud/cached-dependencies@v1
|
||||
with:
|
||||
run: |
|
||||
yarn-install
|
||||
yarn build
|
||||
|
||||
pip-install
|
||||
python ./bin/manager.py fill_test_data
|
||||
```
|
||||
|
||||
See below for more details.
|
||||
|
||||
## Usage
|
||||
|
||||
### Cache configs
|
||||
|
||||
Under the hood, we use [@actions/cache](https://github.com/marketplace/actions/cache) to manage cache storage. But instead of defining only one cache at a time and specify them in workflow YAMLs, you manage all caches in a spearate JS file: `.github/workflows/caches.js`.
|
||||
|
||||
Here is [the default configuration](https://github.com/ktmud/cached-dependencies/blob/master/src/cache/caches.ts) for Linux:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
pip: {
|
||||
path: [`${process.env.HOME}/.cache/pip`],
|
||||
hashFiles: ['requirements*.txt'],
|
||||
keyPrefix: 'pip-',
|
||||
restoreKeys: 'pip-',
|
||||
},
|
||||
npm: {
|
||||
path: [`${HOME}/.npm`],
|
||||
hashFiles: [
|
||||
`package-lock.json`,
|
||||
`*/*/package-lock.json`,
|
||||
`!node_modules/*/package-lock.json`,
|
||||
],
|
||||
},
|
||||
yarn: {
|
||||
path: [`${HOME}/.npm`],
|
||||
// */* is for supporting lerna monorepo with depth=2
|
||||
hashFiles: [`yarn.lock`, `*/*/yarn.lock`, `!node_modules/*/yarn.lock`],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
In which `hashFiles` and `keyPrefix` will be used to compute the primary cache key used in [@actions/cache](https://github.com/marketplace/actions/cache). `keyPrefix` will default to `${cacheName}-` and `restoreKeys` will default to `keyPrefix` if not specified.
|
||||
|
||||
It is recommended to always use absolute paths in these configs so you can share them across different worflows more easily (in case you the action is called from different working directories).
|
||||
|
||||
#### Speficy when to restore and save
|
||||
|
||||
With the predefined `cache-store` and `cache-save` bash commands, you have full flexibility on when to restore and save cache:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ktmud/cached-dependencies@v1
|
||||
with:
|
||||
run: |
|
||||
cache-restore npm
|
||||
npm install
|
||||
cache-save npm
|
||||
|
||||
cache-restore pip
|
||||
pip install -r requirements.txt
|
||||
cache-save pip
|
||||
```
|
||||
|
||||
### Shortcut commands
|
||||
|
||||
All predefined shortcut commands can be found [here](https://github.com/ktmud/cached-dependencies/blob/master/src/scripts/bashlib.sh). You can also customize them or add new ones in `.github/workflows/bashlib.sh`.
|
||||
|
||||
For example, if you want to install additional packages for before saving `pip` cache, simply add this to the `bashlib.sh` file:
|
||||
|
||||
```bash
|
||||
# override the default `pip-install` command
|
||||
pip-install() {
|
||||
cd $GITHUB_WORKSPACE
|
||||
|
||||
cache-restore pip
|
||||
|
||||
echo "::group::pip install"
|
||||
pip install -r requirements.txt # prod requirements
|
||||
pip install -r requirements-dev.txt # dev requirements
|
||||
pip install -e ".[postgres,mysql]" # current pacakge with some extras
|
||||
echo "::endgroup::"
|
||||
|
||||
cache-save pip
|
||||
}
|
||||
```
|
||||
|
||||
### Default setup command
|
||||
|
||||
When `run` is not provided:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
name: Build
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
uses: ktmud/cached-depdencies@v1
|
||||
```
|
||||
|
||||
You must provide a `default-setup-command` in the bashlib. For example,
|
||||
|
||||
```bash
|
||||
default-setup-command() {
|
||||
pip-install & npm-install
|
||||
}
|
||||
```
|
||||
|
||||
This will start installing pip and npm dependencies at the same time.
|
||||
|
||||
### Customize config locations
|
||||
|
||||
Both the two config files, `.github/workflows/bashlib.sh` and `.github/workflows/caches.js`, can be placed in other locations:
|
||||
|
||||
```yaml
|
||||
- uses: ktmud/cached-dependencies@v1
|
||||
with:
|
||||
caches: ${{ github.workspace }}/.github/configs/caches.js
|
||||
bashlib: ${{ github.workspace }}/.github/configs/bashlib.sh
|
||||
```
|
||||
|
||||
### Run commands in parallel
|
||||
|
||||
When `parallel` is set to `true`, the `run` input will be split into an array of commands and passed to `Promise.all(...)` to execute in parallel. For example,
|
||||
|
||||
```yaml
|
||||
- uses: ktmud/cached-dependencies@v1
|
||||
with:
|
||||
parallel: true
|
||||
run: |
|
||||
pip-install
|
||||
npm-install
|
||||
```
|
||||
|
||||
is equivalent to
|
||||
|
||||
```yaml
|
||||
- uses: ktmud/cached-dependencies@v1
|
||||
with:
|
||||
run: |
|
||||
pip-install & npm-install
|
||||
```
|
||||
|
||||
If one or more of your commands must spread across multiple lines, you can add a new line between the parallel commands. Each command within a parallel group will still run sequentially.
|
||||
|
||||
```yaml
|
||||
- uses: ktmud/cached-dependencies@v1
|
||||
with:
|
||||
run: |
|
||||
cache-restore pip
|
||||
pip install requirements*.txt
|
||||
# additional pip packages
|
||||
pip install package1 package2 pacakge2
|
||||
cache-save pip
|
||||
|
||||
npm-install
|
||||
|
||||
cache-restore cypress
|
||||
cd cypress/ && npm install
|
||||
cache-save cypress
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is released under [the MIT License](LICENSE).
|
||||
124
.github/actions/cached-dependencies/__tests__/cache.test.ts
vendored
Normal file
124
.github/actions/cached-dependencies/__tests__/cache.test.ts
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
import path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '../src/cache';
|
||||
import * as inputsUtils from '../src/utils/inputs';
|
||||
import * as actionUtils from '@actions/cache/src/utils/actionUtils';
|
||||
import defaultCaches from '../src/cache/caches';
|
||||
import { setInputs, getInput, maybeArrayToString } from '../src/utils/inputs';
|
||||
import { Inputs, InputName, GitHubEvent, EnvVariable } from '../src/constants';
|
||||
import caches, { npmExpectedHash } from './fixtures/caches';
|
||||
|
||||
describe('patch core states', () => {
|
||||
it('should log error if states file invalid', () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, 'logWarning');
|
||||
fs.writeFileSync(`${os.tmpdir()}/cached--states.json`, 'INVALID_JSON', {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
core.getState('haha');
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
it('should persist state', () => {
|
||||
core.saveState('test', '100');
|
||||
expect(core.getState('test')).toStrictEqual('100');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache runner', () => {
|
||||
it('should use default cache config', async () => {
|
||||
await cache.loadCustomCacheConfigs();
|
||||
// but `npm` actually come from `src/cache/caches.ts`
|
||||
const inputs = await cache.getCacheInputs('npm');
|
||||
expect(inputs?.[InputName.Path]).toStrictEqual(
|
||||
maybeArrayToString(defaultCaches.npm.path),
|
||||
);
|
||||
expect(inputs?.[InputName.RestoreKeys]).toStrictEqual('npm-');
|
||||
});
|
||||
|
||||
it('should override cache config', async () => {
|
||||
setInputs({
|
||||
[InputName.Caches]: path.resolve(__dirname, 'fixtures/caches'),
|
||||
});
|
||||
await cache.loadCustomCacheConfigs();
|
||||
|
||||
const inputs = await cache.getCacheInputs('npm');
|
||||
expect(inputs?.[InputName.Path]).toStrictEqual(
|
||||
maybeArrayToString(caches.npm.path),
|
||||
);
|
||||
expect(inputs?.[InputName.Key]).toStrictEqual(`npm-${npmExpectedHash}`);
|
||||
expect(inputs?.[InputName.RestoreKeys]).toStrictEqual(
|
||||
maybeArrayToString(caches.npm.restoreKeys),
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply inputs and restore cache', async () => {
|
||||
setInputs({
|
||||
[InputName.Caches]: path.resolve(__dirname, 'fixtures/caches'),
|
||||
[EnvVariable.GitHubEventName]: GitHubEvent.PullRequest,
|
||||
});
|
||||
|
||||
const setInputsMock = jest.spyOn(inputsUtils, 'setInputs');
|
||||
const inputs = await cache.getCacheInputs('npm');
|
||||
const result = await cache.run('restore', 'npm');
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
// before run
|
||||
expect(setInputsMock).toHaveBeenNthCalledWith(1, inputs);
|
||||
|
||||
// after run
|
||||
expect(setInputsMock).toHaveBeenNthCalledWith(2, {
|
||||
[InputName.Key]: '',
|
||||
[InputName.Path]: '',
|
||||
[InputName.RestoreKeys]: '',
|
||||
});
|
||||
|
||||
// inputs actually restored to original value
|
||||
expect(getInput(InputName.Key)).toStrictEqual('');
|
||||
|
||||
// pretend still in execution context
|
||||
setInputs(inputs as Inputs);
|
||||
|
||||
// `core.getState` should return the primary key
|
||||
expect(core.getState('CACHE_KEY')).toStrictEqual(inputs?.[InputName.Key]);
|
||||
|
||||
setInputsMock.mockRestore();
|
||||
});
|
||||
|
||||
it('should run saveCache', async () => {
|
||||
// call to save should also work
|
||||
const logWarningMock = jest.spyOn(actionUtils, 'logWarning');
|
||||
|
||||
setInputs({
|
||||
[InputName.Parallel]: 'true',
|
||||
});
|
||||
await cache.run('save', 'npm');
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
'Cache Service Url not found, unable to restore cache.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should exit on invalid args', async () => {
|
||||
// other calls do generate errors
|
||||
const processExitMock = jest
|
||||
.spyOn(process, 'exit')
|
||||
// @ts-ignore
|
||||
.mockImplementation(() => {});
|
||||
|
||||
// incomplete arguments
|
||||
await cache.run();
|
||||
await cache.run('save');
|
||||
|
||||
// bad arguments
|
||||
await cache.run('save', 'unknown-cache');
|
||||
await cache.run('unknown-action', 'unknown-cache');
|
||||
|
||||
setInputs({
|
||||
[InputName.Caches]: 'non-existent',
|
||||
});
|
||||
await cache.run('save', 'npm');
|
||||
|
||||
expect(processExitMock).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
});
|
||||
5
.github/actions/cached-dependencies/__tests__/fixtures/bashlib.sh
vendored
Normal file
5
.github/actions/cached-dependencies/__tests__/fixtures/bashlib.sh
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
default-setup-command() {
|
||||
print-cachescript-path
|
||||
}
|
||||
14
.github/actions/cached-dependencies/__tests__/fixtures/caches.ts
vendored
Normal file
14
.github/actions/cached-dependencies/__tests__/fixtures/caches.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Example cache config.
|
||||
*/
|
||||
export const npmHashFiles = ['.*ignore'];
|
||||
export const npmExpectedHash =
|
||||
'13ed29a1c7ec906e7dcb20626957ebfcd3f0f2174bd2685a012105792bf1ff55';
|
||||
|
||||
export default {
|
||||
npm: {
|
||||
path: [`~/.npm`],
|
||||
hashFiles: npmHashFiles,
|
||||
restoreKeys: 'node-npm-',
|
||||
},
|
||||
};
|
||||
101
.github/actions/cached-dependencies/__tests__/setup.test.ts
vendored
Normal file
101
.github/actions/cached-dependencies/__tests__/setup.test.ts
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Test default runner.
|
||||
*/
|
||||
import { setInputs } from '../src/utils/inputs';
|
||||
import { InputName, DefaultInputs } from '../src/constants';
|
||||
import * as setup from '../src/setup';
|
||||
import path from 'path';
|
||||
|
||||
const extraBashlib = path.resolve(__dirname, './fixtures/bashlib.sh');
|
||||
|
||||
describe('setup runner', () => {
|
||||
// don't actually run the bash script
|
||||
const runCommandMock = jest.spyOn(setup, 'runCommand');
|
||||
|
||||
it('should allow custom bashlib', async () => {
|
||||
setInputs({
|
||||
[InputName.Bashlib]: extraBashlib,
|
||||
});
|
||||
await setup.run();
|
||||
expect(runCommandMock).toHaveBeenCalledTimes(1);
|
||||
expect(runCommandMock).toHaveBeenCalledWith(
|
||||
DefaultInputs[InputName.Run],
|
||||
extraBashlib,
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow inline bash overrides', async () => {
|
||||
const processExitMock = jest
|
||||
.spyOn(process, 'exit')
|
||||
// @ts-ignore
|
||||
.mockImplementation(() => {});
|
||||
|
||||
setInputs({
|
||||
[InputName.Bashlib]: '',
|
||||
[InputName.Parallel]: 'false',
|
||||
[InputName.Run]: `
|
||||
${DefaultInputs[InputName.Run]}() {
|
||||
echo "It works!"
|
||||
exit 202
|
||||
}
|
||||
${DefaultInputs[InputName.Run]}
|
||||
`,
|
||||
});
|
||||
// allow the bash script to run for one test, but override the default
|
||||
await setup.run();
|
||||
expect(runCommandMock).toHaveBeenCalledTimes(1);
|
||||
expect(processExitMock).toHaveBeenCalledTimes(1);
|
||||
expect(processExitMock).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should use run commands', async () => {
|
||||
// don't run the commands when there is no overrides
|
||||
runCommandMock.mockImplementation(async () => {});
|
||||
|
||||
setInputs({
|
||||
[InputName.Bashlib]: 'non-existent',
|
||||
[InputName.Run]: 'print-cachescript-path',
|
||||
});
|
||||
|
||||
await setup.run();
|
||||
|
||||
expect(runCommandMock).toHaveBeenCalledTimes(1);
|
||||
expect(runCommandMock).toHaveBeenCalledWith('print-cachescript-path', '');
|
||||
});
|
||||
|
||||
it('should handle single-new-line parallel commands', async () => {
|
||||
setInputs({
|
||||
[InputName.Run]: `
|
||||
test-command-1
|
||||
test-command-2
|
||||
`,
|
||||
[InputName.Parallel]: 'true',
|
||||
});
|
||||
|
||||
await setup.run();
|
||||
|
||||
expect(runCommandMock).toHaveBeenNthCalledWith(1, 'test-command-1', '');
|
||||
expect(runCommandMock).toHaveBeenNthCalledWith(2, 'test-command-2', '');
|
||||
});
|
||||
|
||||
it('should handle multi-new-line parallel commands', async () => {
|
||||
setInputs({
|
||||
[InputName.Run]: `
|
||||
test-1-1
|
||||
test-1-2
|
||||
|
||||
test-2
|
||||
`,
|
||||
[InputName.Parallel]: 'true',
|
||||
});
|
||||
|
||||
await setup.run();
|
||||
|
||||
expect(runCommandMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'test-1-1\n test-1-2',
|
||||
'',
|
||||
);
|
||||
expect(runCommandMock).toHaveBeenNthCalledWith(2, 'test-2', '');
|
||||
});
|
||||
});
|
||||
10
.github/actions/cached-dependencies/__tests__/tsconfig.json
vendored
Normal file
10
.github/actions/cached-dependencies/__tests__/tsconfig.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "../build",
|
||||
"noEmit": true,
|
||||
"rootDir": "../"
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
25
.github/actions/cached-dependencies/action.yml
vendored
Normal file
25
.github/actions/cached-dependencies/action.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Cached Dependencies
|
||||
description: Setup multi-layered cache and dependencies in one step, share predefined commands across workflows
|
||||
author: Jesse Yang <hello@yjc.me>
|
||||
branding:
|
||||
icon: layers
|
||||
color: yellow
|
||||
inputs:
|
||||
caches:
|
||||
required: false
|
||||
description: Path to a JS file with cache configs
|
||||
default: ${{ github.workspace }}/.github/workflows/caches.js
|
||||
bashlib:
|
||||
required: false
|
||||
description: Path to a Bash script with command shortcuts
|
||||
default: ${{ github.workspace }}/.github/workflows/bashlib.sh
|
||||
run:
|
||||
required: false
|
||||
description: Setup commands to run, can use shortcuts defined in bashlib
|
||||
default: default-setup-command
|
||||
parallel:
|
||||
required: false
|
||||
description: Whether to run commands in parallel
|
||||
runs:
|
||||
using: node12
|
||||
main: dist/index.js
|
||||
1757
.github/actions/cached-dependencies/dist/index.js
vendored
Normal file
1757
.github/actions/cached-dependencies/dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6125
.github/actions/cached-dependencies/dist/scripts/cache/index.js
vendored
Normal file
6125
.github/actions/cached-dependencies/dist/scripts/cache/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
57
.github/actions/cached-dependencies/dist/scripts/cache/thread.js
vendored
Normal file
57
.github/actions/cached-dependencies/dist/scripts/cache/thread.js
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const {parentPort} = require('worker_threads');
|
||||
|
||||
const handlers = {
|
||||
hashFile: (algorithm, filePath) => new Promise((resolve, reject) => {
|
||||
const hasher = crypto.createHash(algorithm);
|
||||
fs.createReadStream(filePath)
|
||||
// TODO: Use `Stream.pipeline` when targeting Node.js 12.
|
||||
.on('error', reject)
|
||||
.pipe(hasher)
|
||||
.on('error', reject)
|
||||
.on('finish', () => {
|
||||
const {buffer} = hasher.read();
|
||||
resolve({value: buffer, transferList: [buffer]});
|
||||
});
|
||||
}),
|
||||
hash: async (algorithm, input) => {
|
||||
const hasher = crypto.createHash(algorithm);
|
||||
|
||||
if (Array.isArray(input)) {
|
||||
for (const part of input) {
|
||||
hasher.update(part);
|
||||
}
|
||||
} else {
|
||||
hasher.update(input);
|
||||
}
|
||||
|
||||
const hash = hasher.digest().buffer;
|
||||
return {value: hash, transferList: [hash]};
|
||||
}
|
||||
};
|
||||
|
||||
parentPort.on('message', async message => {
|
||||
try {
|
||||
const {method, args} = message;
|
||||
const handler = handlers[method];
|
||||
|
||||
if (handler === undefined) {
|
||||
throw new Error(`Unknown method '${method}'`);
|
||||
}
|
||||
|
||||
const {value, transferList} = await handler(...args);
|
||||
parentPort.postMessage({id: message.id, value}, transferList);
|
||||
} catch (error) {
|
||||
const newError = {message: error.message, stack: error.stack};
|
||||
|
||||
for (const [key, value] of Object.entries(error)) {
|
||||
if (typeof value !== 'object') {
|
||||
newError[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
parentPort.postMessage({id: message.id, error: newError});
|
||||
}
|
||||
});
|
||||
21
.github/actions/cached-dependencies/jest.config.js
vendored
Normal file
21
.github/actions/cached-dependencies/jest.config.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'/node_modules/(?!@actions).+\\.js$',
|
||||
],
|
||||
verbose: true,
|
||||
};
|
||||
|
||||
// suppress debug messages
|
||||
const processStdoutWrite = process.stdout.write.bind(process.stdout);
|
||||
process.stdout.write = (str, encoding, cb) => {
|
||||
processStdoutWrite(str.split('\n').filter(x => {
|
||||
return !/^::debug::/.test(x);
|
||||
}).join('\n'), encoding, cb);
|
||||
};
|
||||
8197
.github/actions/cached-dependencies/package-lock.json
generated
vendored
Normal file
8197
.github/actions/cached-dependencies/package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
47
.github/actions/cached-dependencies/package.json
vendored
Normal file
47
.github/actions/cached-dependencies/package.json
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "setup-superset-action",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"actions",
|
||||
"node",
|
||||
"setup",
|
||||
"superset"
|
||||
],
|
||||
"main": "dist/run",
|
||||
"scripts": {
|
||||
"all": "npm run format && npm run lint && npm run test && npm run build",
|
||||
"build": "npm run clean && tsc && ncc build -o dist src/run.ts && ncc build -o dist/scripts/cache src/scripts/cache.ts",
|
||||
"clean": "rm -rf ./lib ./dist",
|
||||
"coverage": "npm run test && open ./coverage/lcov-report/index.html",
|
||||
"format": "prettier --write **/*.ts",
|
||||
"format-check": "prettier --check **/*.ts",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"test": "jest --clearCache && jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/cache": "actions/cache#d29c1df198dd38ac88e0ae23a2881b99c2d20e68",
|
||||
"@actions/core": "1.2.4",
|
||||
"@actions/exec": "1.0.4",
|
||||
"@actions/glob": "0.1.0",
|
||||
"@types/uuid": "7.0.4",
|
||||
"hasha": "5.2.0",
|
||||
"tempy": "0.6.0",
|
||||
"uuid": "7.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "26.0.7",
|
||||
"@types/node": "12.12.53",
|
||||
"@typescript-eslint/eslint-plugin": "3.7.1",
|
||||
"@typescript-eslint/parser": "3.7.1",
|
||||
"@zeit/ncc": "0.22.3",
|
||||
"eslint": "7.5.0",
|
||||
"eslint-plugin-jest": "23.19.0",
|
||||
"jest": "26.1.0",
|
||||
"js-yaml": "3.14.0",
|
||||
"prettier": "2.0.5",
|
||||
"prettier-plugin-packagejson": "2.2.5",
|
||||
"ts-jest": "26.1.4",
|
||||
"typescript": "3.9.7"
|
||||
}
|
||||
}
|
||||
5
.github/actions/cached-dependencies/renovate.json
vendored
Normal file
5
.github/actions/cached-dependencies/renovate.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
||||
49
.github/actions/cached-dependencies/src/cache/caches.ts
vendored
Normal file
49
.github/actions/cached-dependencies/src/cache/caches.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Default cache configs
|
||||
*/
|
||||
import * as os from 'os';
|
||||
|
||||
export interface CacheConfig {
|
||||
path: string[] | string;
|
||||
hashFiles: string[] | string;
|
||||
keyPrefix?: string;
|
||||
restoreKeys?: string[] | string;
|
||||
}
|
||||
|
||||
export interface CacheConfigs {
|
||||
[cacheName: string]: CacheConfig;
|
||||
}
|
||||
|
||||
const { HOME = '~' } = process.env;
|
||||
const platform = os.platform() as 'linux' | 'darwin' | 'win32';
|
||||
const pathByPlatform = {
|
||||
linux: {
|
||||
pip: `${HOME}/.cache/pip`,
|
||||
},
|
||||
darwin: {
|
||||
pip: `${HOME}/Library/Caches/pip`,
|
||||
},
|
||||
win32: {
|
||||
pip: `${HOME}\\AppData\\Local\\pip\\Cache`,
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
pip: {
|
||||
path: pathByPlatform[platform].pip,
|
||||
hashFiles: 'requirements*.txt',
|
||||
},
|
||||
npm: {
|
||||
path: `${HOME}/.npm`,
|
||||
hashFiles: [
|
||||
`package-lock.json`,
|
||||
// support lerna monorepo with depth=2
|
||||
`*/*/package-lock.json`,
|
||||
`!node_modules/*/package-lock.json`,
|
||||
],
|
||||
},
|
||||
yarn: {
|
||||
path: `${HOME}/.npm`,
|
||||
hashFiles: [`yarn.lock`, `*/*/yarn.lock`, `!node_modules/*/yarn.lock`],
|
||||
},
|
||||
} as CacheConfigs;
|
||||
146
.github/actions/cached-dependencies/src/cache/index.ts
vendored
Normal file
146
.github/actions/cached-dependencies/src/cache/index.ts
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Execute @actions/cache with predefined cache configs.
|
||||
*/
|
||||
import { beginImport, doneImport } from './patch'; // monkey patch @actions modules
|
||||
|
||||
beginImport();
|
||||
import saveCache from '@actions/cache/src/save';
|
||||
import restoreCache from '@actions/cache/src/restore';
|
||||
doneImport();
|
||||
|
||||
import hasha from 'hasha';
|
||||
import * as fs from 'fs';
|
||||
import * as core from '@actions/core';
|
||||
import * as glob from '@actions/glob';
|
||||
import { Inputs, InputName, DefaultInputs } from '../constants';
|
||||
import { applyInputs, getInput, maybeArrayToString } from '../utils/inputs';
|
||||
import caches from './caches'; // default cache configs
|
||||
|
||||
// GitHub uses `sha256` for the built-in `${{ hashFiles(...) }}` expression
|
||||
// https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#hashfiles
|
||||
const HASH_OPTION = { algorithm: 'sha256' };
|
||||
|
||||
/**
|
||||
* Load custom cache configs from the `caches` path defined in inputs.
|
||||
*
|
||||
* @returns Whether the loading is successfull.
|
||||
*/
|
||||
export async function loadCustomCacheConfigs() {
|
||||
const customCachePath = getInput(InputName.Caches);
|
||||
try {
|
||||
core.debug(`Reading cache configs from '${customCachePath}'`);
|
||||
const customCache = await import(customCachePath);
|
||||
Object.assign(caches, customCache.default);
|
||||
} catch (error) {
|
||||
if (
|
||||
customCachePath !== DefaultInputs[InputName.Caches] ||
|
||||
!error.message.includes('Cannot find module')
|
||||
) {
|
||||
core.error(error.message);
|
||||
core.setFailed(
|
||||
`Failed to load custom cache configs: '${customCachePath}'`,
|
||||
);
|
||||
return process.exit(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SHA256 hash for a list of files matched by glob patterns.
|
||||
*
|
||||
* @param {string[]} patterns - The glob pattern.
|
||||
* @param {string} extra - The extra string to append to the file hashes to
|
||||
* comptue the final hash.
|
||||
*/
|
||||
export async function hashFiles(
|
||||
patterns: string[] | string,
|
||||
extra: string = '',
|
||||
) {
|
||||
const globber = await glob.create(maybeArrayToString(patterns));
|
||||
let hash = '';
|
||||
let counter = 0;
|
||||
for await (const file of globber.globGenerator()) {
|
||||
if (!fs.statSync(file).isDirectory()) {
|
||||
hash += hasha.fromFileSync(file, HASH_OPTION);
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
core.debug(`Computed hash for ${counter} files. Pattern: ${patterns}`);
|
||||
return hasha(hash + extra, HASH_OPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate GitHub Action inputs based on predefined cache config. Will be used
|
||||
* to override env variables.
|
||||
*
|
||||
* @param {string} cacheName - Name of the predefined cache config.
|
||||
*/
|
||||
export async function getCacheInputs(
|
||||
cacheName: string,
|
||||
): Promise<Inputs | null> {
|
||||
if (!(cacheName in caches)) {
|
||||
return null;
|
||||
}
|
||||
const { keyPrefix, restoreKeys, path, hashFiles: patterns } = caches[
|
||||
cacheName
|
||||
];
|
||||
const pathString = maybeArrayToString(path);
|
||||
const prefix = keyPrefix || `${cacheName}-`;
|
||||
// include `path` to hash, too, so to burse caches in case users change
|
||||
// the path definition.
|
||||
const hash = await hashFiles(patterns, pathString);
|
||||
return {
|
||||
[InputName.Key]: `${prefix}${hash}`,
|
||||
[InputName.Path]: pathString,
|
||||
// only use prefix as restore key if it is never defined
|
||||
[InputName.RestoreKeys]:
|
||||
restoreKeys === undefined ? prefix : maybeArrayToString(restoreKeys),
|
||||
};
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
restore(inputs: Inputs) {
|
||||
return applyInputs(inputs, restoreCache);
|
||||
},
|
||||
save(inputs: Inputs) {
|
||||
return applyInputs(inputs, saveCache);
|
||||
},
|
||||
};
|
||||
|
||||
export type ActionChoice = keyof typeof actions;
|
||||
|
||||
export async function run(
|
||||
action: string | undefined = undefined,
|
||||
cacheName: string | undefined = undefined,
|
||||
) {
|
||||
if (!action || !(action in actions)) {
|
||||
core.setFailed(`Choose a cache action from: [restore, save]`);
|
||||
return process.exit(1);
|
||||
}
|
||||
if (!cacheName) {
|
||||
core.setFailed(`Must provide a cache name.`);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
const runInParallel = getInput(InputName.Parallel);
|
||||
|
||||
if (await loadCustomCacheConfigs()) {
|
||||
if (runInParallel) {
|
||||
core.info(`${action.toUpperCase()} cache for ${cacheName}`);
|
||||
} else {
|
||||
core.startGroup(`${action.toUpperCase()} cache for ${cacheName}`);
|
||||
}
|
||||
const inputs = await getCacheInputs(cacheName);
|
||||
if (inputs) {
|
||||
core.info(JSON.stringify(inputs, null, 2));
|
||||
await actions[action as ActionChoice](inputs);
|
||||
} else {
|
||||
core.setFailed(`Cache '${cacheName}' not defined, failed to ${action}.`);
|
||||
return process.exit(1);
|
||||
}
|
||||
if (!runInParallel) {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
95
.github/actions/cached-dependencies/src/cache/patch.ts
vendored
Normal file
95
.github/actions/cached-dependencies/src/cache/patch.ts
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Monkey patch to safely import and use @action/cache modules
|
||||
*/
|
||||
import * as utils from '@actions/cache/src/utils/actionUtils';
|
||||
import * as core from '@actions/core';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { InputName } from '../constants';
|
||||
import { getInput } from '../utils/inputs';
|
||||
|
||||
interface KeyValueStore {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const { logWarning, isValidEvent } = utils;
|
||||
const { getState, saveState } = core;
|
||||
|
||||
function getStateStoreFile() {
|
||||
const cacheName = getInput(InputName.Key);
|
||||
return `${os.tmpdir()}/cached-${cacheName}-states.json`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load states from the persistent store.
|
||||
*
|
||||
* The default `core.saveState` only writes states as command output, and
|
||||
* `core.getState` is only possible to read the state in a later step via ENV
|
||||
* variables.
|
||||
*
|
||||
* So we use a temp file to save and load states, so to allow persistent
|
||||
* states within the same step.
|
||||
*
|
||||
* Since the state output is not uniq to caches, each cache should have their
|
||||
* own file for persistent states.
|
||||
*/
|
||||
function loadStates() {
|
||||
const stateStore = getStateStoreFile();
|
||||
const states: KeyValueStore = {};
|
||||
try {
|
||||
Object.assign(
|
||||
states,
|
||||
JSON.parse(fs.readFileSync(stateStore, { encoding: 'utf-8' })),
|
||||
);
|
||||
core.debug(`Loaded states from: ${stateStore}`)
|
||||
} catch (error) {
|
||||
// pass
|
||||
if (error.code !== 'ENOENT') {
|
||||
utils.logWarning(`Could not load states: ${stateStore}`)
|
||||
utils.logWarning(error.message);
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save states to the persistent storage.
|
||||
*/
|
||||
function persistState(name: string, value: any) {
|
||||
const states = loadStates();
|
||||
const stateStore = getStateStoreFile();
|
||||
const valueString = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
|
||||
// make sure value is always string
|
||||
states[name] = valueString;
|
||||
|
||||
// persist state in the temp file
|
||||
fs.writeFileSync(stateStore, JSON.stringify(states, null, 2), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
core.debug(`Persist state "${name}=${valueString}" to ${stateStore}`);
|
||||
|
||||
// still pass the original value to the original function, though
|
||||
return saveState(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get states from persistent store, fallback to "official" states.
|
||||
*/
|
||||
function obtainState(name: string) {
|
||||
const states = loadStates();
|
||||
return states[name] || getState(name);
|
||||
}
|
||||
|
||||
export function beginImport() {
|
||||
Object.defineProperty(utils, 'isValidEvent', { value: () => false });
|
||||
Object.defineProperty(utils, 'logWarning', { value: () => {} });
|
||||
}
|
||||
|
||||
export function doneImport() {
|
||||
Object.defineProperty(utils, 'isValidEvent', { value: isValidEvent });
|
||||
Object.defineProperty(utils, 'logWarning', { value: logWarning });
|
||||
|
||||
Object.defineProperty(core, 'saveState', { value: persistState });
|
||||
Object.defineProperty(core, 'getState', { value: obtainState });
|
||||
}
|
||||
43
.github/actions/cached-dependencies/src/constants.ts
vendored
Normal file
43
.github/actions/cached-dependencies/src/constants.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Possible input names
|
||||
export enum InputName {
|
||||
// @actions/cache specific inputs
|
||||
Key = 'key',
|
||||
Path = 'path',
|
||||
RestoreKeys = 'restore-keys',
|
||||
|
||||
// setup-webapp specific inputs
|
||||
Run = 'run',
|
||||
Caches = 'caches',
|
||||
Bashlib = 'bashlib',
|
||||
Parallel = 'parallel',
|
||||
}
|
||||
|
||||
// Possible GitHub event names
|
||||
export enum GitHubEvent {
|
||||
Push = 'push',
|
||||
PullRequest = 'pull_request',
|
||||
}
|
||||
|
||||
// Directly available environment variables
|
||||
export enum EnvVariable {
|
||||
GitHubEventName = 'GITHUB_EVENT_NAME',
|
||||
}
|
||||
|
||||
export const EnvVariableNames = new Set(Object.values(EnvVariable) as string[]);
|
||||
|
||||
export interface Inputs {
|
||||
[EnvVariable.GitHubEventName]?: string;
|
||||
[InputName.Key]?: string;
|
||||
[InputName.RestoreKeys]?: string;
|
||||
[InputName.Path]?: string;
|
||||
[InputName.Caches]?: string;
|
||||
[InputName.Bashlib]?: string;
|
||||
[InputName.Run]?: string;
|
||||
[InputName.Parallel]?: string;
|
||||
}
|
||||
|
||||
export const DefaultInputs = {
|
||||
[InputName.Caches]: '.github/workflows/caches.js',
|
||||
[InputName.Bashlib]: '.github/workflows/bashlib.sh',
|
||||
[InputName.Run]: 'default-setup-command',
|
||||
} as Inputs;
|
||||
3
.github/actions/cached-dependencies/src/run.ts
vendored
Normal file
3
.github/actions/cached-dependencies/src/run.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { run } from './setup';
|
||||
|
||||
run();
|
||||
61
.github/actions/cached-dependencies/src/scripts/bashlib.sh
vendored
Normal file
61
.github/actions/cached-dependencies/src/scripts/bashlib.sh
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
# -----------------------------------------------
|
||||
# Predefined command shortcuts
|
||||
# -----------------------------------------------
|
||||
|
||||
# Exit on any command fails
|
||||
set -e
|
||||
|
||||
bashSource=${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]:-${(%):-%x}}
|
||||
cacheScript="$(dirname $(dirname $(dirname $bashSource)))/dist/scripts/cache"
|
||||
|
||||
print-cachescript-path() {
|
||||
echo $cacheScript
|
||||
}
|
||||
|
||||
cache-restore() {
|
||||
node $cacheScript restore $1
|
||||
}
|
||||
|
||||
cache-save() {
|
||||
node $cacheScript save $1
|
||||
}
|
||||
|
||||
# install python packages
|
||||
pip-install() {
|
||||
cache-restore pip
|
||||
echo "::group::Install Python pacakges"
|
||||
pip install -r requirements.txt # install dependencies
|
||||
pip install -e . # install current directory as editable python package
|
||||
echo "::endgroup"
|
||||
cache-save pip
|
||||
}
|
||||
|
||||
# install npm packages
|
||||
npm-install() {
|
||||
cache-restore npm
|
||||
echo "::group::Install npm pacakges"
|
||||
echo "npm: $(npm --version)"
|
||||
echo "node: $(node --version)"
|
||||
npm ci
|
||||
echo "::endgroup::"
|
||||
cache-save npm
|
||||
}
|
||||
|
||||
# install npm packages via yarn
|
||||
yarn-install() {
|
||||
cache-restore yarn
|
||||
echo "::group::Install npm pacakges via yarn"
|
||||
echo "npm: $(npm --version)"
|
||||
echo "node: $(node --version)"
|
||||
echo "yarn: $(yarn --version)"
|
||||
yarn
|
||||
echo "::endgroup::"
|
||||
cache-save yarn
|
||||
}
|
||||
|
||||
# default setup will install both pip and npm pacakges at the same time
|
||||
default-setup-command() {
|
||||
echo 'Please provide `run` commands or configure `default-setup-command`.'
|
||||
exit 1
|
||||
}
|
||||
18
.github/actions/cached-dependencies/src/scripts/cache.ts
vendored
Normal file
18
.github/actions/cached-dependencies/src/scripts/cache.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Runner script to store/save caches by predefined configs.
|
||||
* Used in `scripts/bashlib.sh`.
|
||||
*/
|
||||
import { EnvVariable } from '../constants';
|
||||
|
||||
// To import `@actions/cache` modules safely, we must set GitHub event name to
|
||||
// a invalid value, so actual runner code doesn't execute.
|
||||
const originalEvent = process.env[EnvVariable.GitHubEventName];
|
||||
process.env[EnvVariable.GitHubEventName] = 'CACHE_HACK';
|
||||
|
||||
import { run } from '../cache';
|
||||
|
||||
// then we restore the event name before the job actually runs
|
||||
process.env[EnvVariable.GitHubEventName] = originalEvent;
|
||||
|
||||
// @ts-ignore
|
||||
run(...process.argv.slice(2));
|
||||
66
.github/actions/cached-dependencies/src/setup.ts
vendored
Normal file
66
.github/actions/cached-dependencies/src/setup.ts
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Load inputs and execute.
|
||||
*/
|
||||
import * as core from '@actions/core';
|
||||
import { exec } from '@actions/exec';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { DefaultInputs, InputName } from './constants';
|
||||
import { getInput } from './utils/inputs';
|
||||
|
||||
const SHARED_BASHLIB = path.resolve(__dirname, '../src/scripts/bashlib.sh');
|
||||
|
||||
/**
|
||||
* Run bash commands with predefined lib functions.
|
||||
*
|
||||
* @param {string} cmd - The bash commands to execute.
|
||||
*/
|
||||
export async function runCommand(
|
||||
cmd: string,
|
||||
extraBashlib: string,
|
||||
): Promise<void> {
|
||||
const bashlibCommands = [`source ${SHARED_BASHLIB}`];
|
||||
if (extraBashlib) {
|
||||
bashlibCommands.push(`source ${extraBashlib}`);
|
||||
}
|
||||
try {
|
||||
await exec('bash', ['-c', [...bashlibCommands, cmd].join('\n ')]);
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
let bashlib = getInput(InputName.Bashlib);
|
||||
const rawCommands = getInput(InputName.Run);
|
||||
const runInParallel = getInput(InputName.Parallel);
|
||||
|
||||
if (!fs.existsSync(bashlib)) {
|
||||
if (bashlib !== DefaultInputs[InputName.Bashlib]) {
|
||||
core.error(`Custom bashlib "${bashlib}" does not exist.`);
|
||||
}
|
||||
// don't add bashlib to runCommand
|
||||
bashlib = '';
|
||||
}
|
||||
|
||||
if (runInParallel) {
|
||||
// Attempt to split by two or more new lines first, if there is still only
|
||||
// one command, attempt to split by one new line. This is because users
|
||||
// asked for parallelization, so we make our best efforts to get multiple
|
||||
// commands.
|
||||
let commands = rawCommands.split(/\n{2,}/);
|
||||
if (commands.length === 1) {
|
||||
commands = rawCommands.split('\n');
|
||||
}
|
||||
core.debug(`>> Run ${commands.length} commands in parallel...`);
|
||||
await Promise.all(
|
||||
commands
|
||||
.map(x => x.trim())
|
||||
.filter(x => !!x)
|
||||
.map(cmd => exports.runCommand(cmd, bashlib)),
|
||||
);
|
||||
} else if (rawCommands) {
|
||||
await exports.runCommand(rawCommands, bashlib);
|
||||
}
|
||||
}
|
||||
2
.github/actions/cached-dependencies/src/types/external.d.ts
vendored
Normal file
2
.github/actions/cached-dependencies/src/types/external.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare module '@actions/cache/dist/restore';
|
||||
declare module '@actions/cache/dist/save';
|
||||
61
.github/actions/cached-dependencies/src/utils/inputs.ts
vendored
Normal file
61
.github/actions/cached-dependencies/src/utils/inputs.ts
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Manage inputs and env variables.
|
||||
*/
|
||||
import * as core from '@actions/core';
|
||||
import {
|
||||
Inputs,
|
||||
EnvVariableNames,
|
||||
InputName,
|
||||
DefaultInputs,
|
||||
} from '../constants';
|
||||
|
||||
export function getInput(name: keyof Inputs): string {
|
||||
const value = core.getInput(name);
|
||||
if (name === InputName.Parallel) {
|
||||
return value.toUpperCase() === 'TRUE' ? value : '';
|
||||
}
|
||||
return value || DefaultInputs[name] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update env variables associated with some inputs.
|
||||
* See: https://github.com/actions/toolkit/blob/5b940ebda7e7b86545fe9741903c930bc1191eb0/packages/core/src/core.ts#L69-L77 .
|
||||
*
|
||||
* @param {Inputs} inputs - The new inputs to apply to the env variables.
|
||||
*/
|
||||
export function setInputs(inputs: Inputs): void {
|
||||
for (const [name, value] of Object.entries(inputs)) {
|
||||
const envName = EnvVariableNames.has(name)
|
||||
? name
|
||||
: `INPUT_${name.replace(/ /g, '_').toUpperCase()}`;
|
||||
process.env[envName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply new inputs and execute a runner function, restore them when done.
|
||||
*
|
||||
* @param {Inputs} inputs - The new inputs to apply to the env variables before
|
||||
* excuting the runner.
|
||||
* @param {runner} runner - The runner function that returns a promise.
|
||||
* @returns {Promise<any>} - The result from the runner function.
|
||||
*/
|
||||
export async function applyInputs(
|
||||
inputs: Inputs,
|
||||
runner: () => Promise<void>,
|
||||
): Promise<any> {
|
||||
const originalInputs: Inputs = Object.fromEntries(
|
||||
Object.keys(inputs).map(name => [
|
||||
name,
|
||||
EnvVariableNames.has(name) ? process.env[name] : core.getInput(name),
|
||||
]),
|
||||
);
|
||||
exports.setInputs(inputs);
|
||||
const result = await runner();
|
||||
exports.setInputs(originalInputs);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function maybeArrayToString(input: string[] | string) {
|
||||
return Array.isArray(input) ? input.join('\n') : input;
|
||||
}
|
||||
19
.github/actions/cached-dependencies/tsconfig.json
vendored
Normal file
19
.github/actions/cached-dependencies/tsconfig.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"lib": ["esnext"],
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./lib",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"preserveSymlinks": true
|
||||
},
|
||||
"include": [
|
||||
"./src",
|
||||
"./node_modules/@actions"
|
||||
],
|
||||
"exclude": ["**/*.test.ts", "__tests__"]
|
||||
}
|
||||
Reference in New Issue
Block a user