1
Fork 0
mirror of https://gitea.com/actions/upload-artifact.git synced 2024-11-26 02:25:58 +01:00

V2 Preview (#54)

* V2 Upload Artifact

* Improve logs

* Update release

* Update test.yml

* Update test.yml

* Update test.yml

* @actions/artifact v0.2.0 package

* Add extra YAML test
This commit is contained in:
Konrad Pabjan 2020-03-13 14:44:44 +01:00 committed by GitHub
parent 8eb149d680
commit c9be818b8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 15724 additions and 13 deletions

3
.eslintignore Normal file
View file

@ -0,0 +1,3 @@
node_modules/
lib/
dist/

19
.eslintrc.json Normal file
View file

@ -0,0 +1,19 @@
{
"env": { "node": true, "jest": true },
"parser": "@typescript-eslint/parser",
"parserOptions": { "ecmaVersion": 9, "sourceType": "module" },
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript",
"plugin:prettier/recommended",
"prettier/@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-empty-function": "off"
},
"plugins": ["@typescript-eslint", "jest"]
}

142
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,142 @@
name: Test
on:
push:
branches:
- v2-preview
paths-ignore:
- '**.md'
pull_request:
branches:
- v2-preview
paths-ignore:
- '**.md'
jobs:
build:
name: Build
strategy:
matrix:
runs-on: [ubuntu-latest, macOS-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.runs-on }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: npm install
run: npm install
- name: Compile
run: npm run build
- name: npm test
run: npm test
- name: Lint
run: npm run lint
- name: Format
run: npm run format-check
# Test end-to-end by uploading two artifacts and then downloading them
- name: Create artifact files
run: |
mkdir -p path/to/dir-1
mkdir -p path/to/dir-2
mkdir -p path/to/dir-3
echo "Lorem ipsum dolor sit amet" > path/to/dir-1/file1.txt
echo "Hello world from file #2" > path/to/dir-2/file2.txt
echo "This is a going to be a test for a large enough file that should get compressed with GZip. The @actions/artifact package uses GZip to upload files. This text should have a compression ratio greater than 100% so it should get uploaded using GZip" > path/to/dir-3/gzip.txt
# Upload a single file artifact
- name: 'Upload artifact #1'
uses: ./
with:
name: 'Artifact-A'
path: path/to/dir-1/file1.txt
# Upload using a wildcard pattern, name should default to 'artifact' if not provided
- name: 'Upload artifact #2'
uses: ./
with:
path: path/**/dir*/
# Upload a directory that contains a file that will be uploaded with GZip
- name: 'Upload artifact #3'
uses: ./
with:
name: 'GZip-Artifact'
path: path/to/dir-3/
# Verify artifacts. Switch to download-artifact@v2 once it's out of preview
# Download Artifact #1 and verify the correctness of the content
- name: 'Download artifact #1'
uses: actions/download-artifact@v1
with:
name: 'Artifact-A'
path: some/new/path
- name: 'Verify Artifact #1'
run: |
$file = "some/new/path/file1.txt"
if(!(Test-Path -path $file))
{
Write-Error "Expected file does not exist"
}
if(!((Get-Content $file) -ceq "Lorem ipsum dolor sit amet"))
{
Write-Error "File contents of downloaded artifact are incorrect"
}
shell: pwsh
# Download Artifact #2 and verify the correctness of the content
- name: 'Download artifact #2'
uses: actions/download-artifact@v1
with:
name: 'artifact'
path: some/other/path
- name: 'Verify Artifact #2'
run: |
$file1 = "some/other/path/to/dir-1/file1.txt"
$file2 = "some/other/path/to/dir-2/file2.txt"
if(!(Test-Path -path $file1) -or !(Test-Path -path $file2))
{
Write-Error "Expected files do not exist"
}
if(!((Get-Content $file1) -ceq "Lorem ipsum dolor sit amet") -or !((Get-Content $file2) -ceq "Hello world from file #2"))
{
Write-Error "File contents of downloaded artifacts are incorrect"
}
shell: pwsh
# Download Artifact #3 and verify the correctness of the content
- name: 'Download artifact #3'
uses: actions/download-artifact@v1
with:
name: 'GZip-Artifact'
path: gzip/artifact/path
# Because a directory was used as input during the upload the parent directories, path/to/dir-3/, should not be included in the uploaded artifact
- name: 'Verify Artifact #3'
run: |
$gzipFile = "gzip/artifact/path/gzip.txt"
if(!(Test-Path -path $gzipFile))
{
Write-Error "Expected file do not exist"
}
if(!((Get-Content $gzipFile) -ceq "This is a going to be a test for a large enough file that should get compressed with GZip. The @actions/artifact package uses GZip to upload files. This text should have a compression ratio greater than 100% so it should get uploaded using GZip"))
{
Write-Error "File contents of downloaded artifact is incorrect"
}
shell: pwsh

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules/
lib/
__tests__/_temp/

3
.prettierignore Normal file
View file

@ -0,0 +1,3 @@
dist/
lib/
node_modules/

11
.prettierrc.json Normal file
View file

@ -0,0 +1,11 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript"
}

View file

@ -1,6 +1,6 @@
# upload-artifact
# Upload-Artifact v2 Preview
This uploads artifacts from your workflow.
This uploads artifacts from your workflow allowing you to share data between jobs and store data once a workflow is complete.
See also [download-artifact](https://github.com/actions/download-artifact).
@ -8,31 +8,86 @@ See also [download-artifact](https://github.com/actions/download-artifact).
See [action.yml](action.yml)
Basic:
### Upload an Individual File
```yaml
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- run: mkdir -p path/to/artifact
- run: echo hello > path/to/artifact/world.txt
- uses: actions/upload-artifact@v1
- uses: actions/upload-artifact@v2-preview
with:
name: my-artifact
path: path/to/artifact
path: path/to/artifact/world.txt
```
### Upload an Entire Directory
```yaml
- uses: actions/upload-artifact@v2-preview
with:
name: my-artifact
path: path/to/artifact/ # or path/to/artifact
```
### Upload using a Wildcard Pattern:
```yaml
- uses: actions/upload-artifact@v2-preview
with:
name: my-artifact
path: path/**/[abc]rtifac?/*
```
For supported wildcards along with behavior and documentation, see [@actions/glob](https://github.com/actions/toolkit/tree/master/packages/glob) which is used internally to search for files.
Relative and absolute file paths are both allowed. Relative paths are rooted against the current working directory.
### Conditional Artifact Upload
To upload artifacts only when the previous step of a job failed, use [`if: failure()`](https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions#job-status-check-functions):
```yaml
- uses: actions/upload-artifact@v1
- uses: actions/upload-artifact@v2-preview
if: failure()
with:
name: my-artifact
path: path/to/artifact
path: path/to/artifact/
```
### Uploading without an artifact name
You can upload an artifact without specifying a name
```yaml
- uses: actions/upload-artifact@v2-preview
with:
path: path/to/artifact/world.txt
```
If not provided, `artifact` will be used as the default name which will manifest itself in the UI after upload.
### Uploading to the same artifact
Each artifact behaves as a file share. Uploading to the same artifact multiple times in the same workflow can overwrite and append already uploaded files
```yaml
- run: echo hi > world.txt
- uses: actions/upload-artifact@v2-preview
with:
path: world.txt
- run: echo howdy > extra-file.txt
- uses: actions/upload-artifact@v2-preview
with:
path: extra-file.txt
- run: echo hello > world.txt
- uses: actions/upload-artifact@v2-preview
with:
path: world.txt
```
With the following example, the available artifact (named `artifact`) would contain both `world.txt` (`hello`) and `extra-file.txt` (`howdy`).
## Where does the upload go?
In the top right corner of a workflow run, once the run is over, if you used this action, there will be a `Artifacts` dropdown which you can download items from. Here's a screenshot of what it looks like<br/>
@ -40,6 +95,10 @@ In the top right corner of a workflow run, once the run is over, if you used thi
There is a trash can icon that can be used to delete the artifact. This icon will only appear for users who have write permissions to the repository.
## Additional Documentation
See [persisting workflow data using artifacts](https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts) for additional examples and tips.
# License

289
__tests__/search.test.ts Normal file
View file

@ -0,0 +1,289 @@
import * as core from '@actions/core'
import * as path from 'path'
import * as io from '@actions/io'
import {promises as fs} from 'fs'
import {findFilesToUpload} from '../src/search'
const root = path.join(__dirname, '_temp', 'search')
const searchItem1Path = path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'search-item1.txt'
)
const searchItem2Path = path.join(root, 'folder-d', 'search-item2.txt')
const searchItem3Path = path.join(root, 'folder-d', 'search-item3.txt')
const searchItem4Path = path.join(root, 'folder-d', 'search-item4.txt')
const searchItem5Path = path.join(root, 'search-item5.txt')
const extraSearchItem1Path = path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'extraSearch-item1.txt'
)
const extraSearchItem2Path = path.join(
root,
'folder-d',
'extraSearch-item2.txt'
)
const extraSearchItem3Path = path.join(
root,
'folder-f',
'extraSearch-item3.txt'
)
const extraSearchItem4Path = path.join(
root,
'folder-h',
'folder-i',
'extraSearch-item4.txt'
)
const extraSearchItem5Path = path.join(
root,
'folder-h',
'folder-i',
'extraSearch-item5.txt'
)
const extraFileInFolderCPath = path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'extra-file-in-folder-c.txt'
)
const amazingFileInFolderHPath = path.join(root, 'folder-h', 'amazing-item.txt')
const lonelyFilePath = path.join(
root,
'folder-h',
'folder-j',
'folder-k',
'lonely-file.txt'
)
describe('Search', () => {
beforeAll(async () => {
// mock all output so that there is less noise when running tests
jest.spyOn(console, 'log').mockImplementation(() => {})
jest.spyOn(core, 'debug').mockImplementation(() => {})
jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {})
// clear temp directory
await io.rmRF(root)
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-c'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-e'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-d'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-f'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-g'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-h', 'folder-i'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-h', 'folder-j', 'folder-k'), {
recursive: true
})
await fs.writeFile(searchItem1Path, 'search item1 file')
await fs.writeFile(searchItem2Path, 'search item2 file')
await fs.writeFile(searchItem3Path, 'search item3 file')
await fs.writeFile(searchItem4Path, 'search item4 file')
await fs.writeFile(searchItem5Path, 'search item5 file')
await fs.writeFile(extraSearchItem1Path, 'extraSearch item1 file')
await fs.writeFile(extraSearchItem2Path, 'extraSearch item2 file')
await fs.writeFile(extraSearchItem3Path, 'extraSearch item3 file')
await fs.writeFile(extraSearchItem4Path, 'extraSearch item4 file')
await fs.writeFile(extraSearchItem5Path, 'extraSearch item5 file')
await fs.writeFile(extraFileInFolderCPath, 'extra file')
await fs.writeFile(amazingFileInFolderHPath, 'amazing file')
await fs.writeFile(lonelyFilePath, 'all by itself')
/*
Directory structure of files that get created:
root/
folder-a/
folder-b/
folder-c/
search-item1.txt
extraSearch-item1.txt
extra-file-in-folder-c.txt
folder-e/
folder-d/
search-item2.txt
search-item3.txt
search-item4.txt
extraSearch-item2.txt
folder-f/
extraSearch-item3.txt
folder-g/
folder-h/
amazing-item.txt
folder-i/
extraSearch-item4.txt
extraSearch-item5.txt
folder-j/
folder-k/
lonely-file.txt
search-item5.txt
*/
})
it('Single file search - Absolute Path', async () => {
const searchResult = await findFilesToUpload(extraFileInFolderCPath)
expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(extraFileInFolderCPath)
expect(searchResult.rootDirectory).toEqual(
path.join(root, 'folder-a', 'folder-b', 'folder-c')
)
})
it('Single file search - Relative Path', async () => {
const relativePath = path.join(
'__tests__',
'_temp',
'search',
'folder-a',
'folder-b',
'folder-c',
'search-item1.txt'
)
const searchResult = await findFilesToUpload(relativePath)
expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(searchItem1Path)
expect(searchResult.rootDirectory).toEqual(
path.join(root, 'folder-a', 'folder-b', 'folder-c')
)
})
it('Single file using wildcard', async () => {
const expectedRoot = path.join(root, 'folder-h')
const searchPath = path.join(root, 'folder-h', '**/*lonely*')
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(lonelyFilePath)
expect(searchResult.rootDirectory).toEqual(expectedRoot)
})
it('Single file using directory', async () => {
const searchPath = path.join(root, 'folder-h', 'folder-j')
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(lonelyFilePath)
expect(searchResult.rootDirectory).toEqual(searchPath)
})
it('Directory search - Absolute Path', async () => {
const searchPath = path.join(root, 'folder-h')
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(4)
expect(
searchResult.filesToUpload.includes(amazingFileInFolderHPath)
).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem4Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
expect(searchResult.rootDirectory).toEqual(searchPath)
})
it('Directory search - Relative Path', async () => {
const searchPath = path.join('__tests__', '_temp', 'search', 'folder-h')
const expectedRootDirectory = path.join(root, 'folder-h')
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(4)
expect(
searchResult.filesToUpload.includes(amazingFileInFolderHPath)
).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem4Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
expect(searchResult.rootDirectory).toEqual(expectedRootDirectory)
})
it('Wildcard search - Absolute Path', async () => {
const searchPath = path.join(root, '**/*[Ss]earch*')
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(10)
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem4Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem5Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem1Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem2Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem3Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem4Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true
)
expect(searchResult.rootDirectory).toEqual(root)
})
it('Wildcard search - Relative Path', async () => {
const searchPath = path.join(
'__tests__',
'_temp',
'search',
'**/*[Ss]earch*'
)
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(10)
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem4Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem5Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem1Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem2Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem3Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem4Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true
)
expect(searchResult.rootDirectory).toEqual(root)
})
})

View file

@ -4,10 +4,10 @@ author: 'GitHub'
inputs:
name:
description: 'Artifact name'
required: true
required: false
path:
description: 'Directory containing files to upload'
description: 'A file, directory or wildcard pattern that describes what to upload'
required: true
runs:
# Plugins live on the runner and are only available to a certain set of first party actions.
plugin: 'publish'
using: 'node12'
main: 'dist/index.js'

8314
dist/index.js vendored Normal file

File diff suppressed because it is too large Load diff

12
jest.config.js Normal file
View file

@ -0,0 +1,12 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
roots: ['<rootDir>'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}

6671
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

51
package.json Normal file
View file

@ -0,0 +1,51 @@
{
"name": "upload-artifact",
"version": "2.0.0",
"description": "Upload a build artifact that can be used by subsequent workflow steps",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"release": "ncc build src/upload-artifact.ts && git add -f dist/",
"check-all": "concurrently \"npm:format-check\" \"npm:lint\" \"npm:test\" \"npm:build\"",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint **/*.ts",
"test": "jest --testTimeout 10000"
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/upload-artifact.git"
},
"keywords": [
"Actions",
"GitHub",
"Artifacts",
"Upload"
],
"author": "GitHub",
"license": "MIT",
"bugs": {
"url": "https://github.com/actions/upload-artifact/issues"
},
"homepage": "https://github.com/actions/upload-artifact#readme",
"devDependencies": {
"@actions/artifact": "^0.2.0",
"@actions/core": "^1.2.3",
"@actions/glob": "^0.1.0",
"@actions/io": "^1.0.2",
"@types/jest": "^25.1.4",
"@types/node": "^12.12.30",
"@typescript-eslint/parser": "^2.23.0",
"@zeit/ncc": "^0.20.5",
"concurrently": "^5.1.0",
"eslint": "^6.8.0",
"eslint-plugin-github": "^3.4.1",
"eslint-plugin-jest": "^23.8.2",
"glob": "^7.1.6",
"jest": "^25.1.0",
"jest-circus": "^25.1.0",
"prettier": "^1.19.1",
"ts-jest": "^25.2.1",
"typescript": "^3.8.3"
}
}

8
src/constants.ts Normal file
View file

@ -0,0 +1,8 @@
export enum Inputs {
Name = 'name',
Path = 'path'
}
export function getDefaultArtifactName(): string {
return 'artifact'
}

69
src/search.ts Normal file
View file

@ -0,0 +1,69 @@
import * as glob from '@actions/glob'
import {debug} from '@actions/core'
import {lstatSync} from 'fs'
import {dirname} from 'path'
export interface SearchResult {
filesToUpload: string[]
rootDirectory: string
}
function getDefaultGlobOptions(): glob.GlobOptions {
return {
followSymbolicLinks: true,
implicitDescendants: true,
omitBrokenSymbolicLinks: true
}
}
export async function findFilesToUpload(
searchPath: string,
globOptions?: glob.GlobOptions
): Promise<SearchResult> {
const searchResults: string[] = []
const globber = await glob.create(
searchPath,
globOptions || getDefaultGlobOptions()
)
const rawSearchResults: string[] = await globber.glob()
/*
Directories will be rejected if attempted to be uploaded. This includes just empty
directories so filter any directories out from the raw search results
*/
for (const searchResult of rawSearchResults) {
if (!lstatSync(searchResult).isDirectory()) {
debug(`File:${searchResult} was found using the provided searchPath`)
searchResults.push(searchResult)
} else {
debug(
`Removing ${searchResult} from rawSearchResults because it is a directory`
)
}
}
/*
Only a single search pattern is being included so only 1 searchResult is expected. In the future if multiple search patterns are
simultaneously supported this will change
*/
const searchPaths: string[] = globber.getSearchPaths()
if (searchPaths.length > 1) {
throw new Error('Only 1 search path should be returned')
}
/*
Special case for a single file artifact that is uploaded without a directory or wildcard pattern. The directory structure is
not preserved and the root directory will be the single files parent directory
*/
if (searchResults.length === 1 && searchPaths[0] === searchResults[0]) {
return {
filesToUpload: searchResults,
rootDirectory: dirname(searchResults[0])
}
}
return {
filesToUpload: searchResults,
rootDirectory: searchPaths[0]
}
}

40
src/upload-artifact.ts Normal file
View file

@ -0,0 +1,40 @@
import * as core from '@actions/core'
import {create, UploadOptions} from '@actions/artifact'
import {Inputs, getDefaultArtifactName} from './constants'
import {findFilesToUpload} from './search'
async function run(): Promise<void> {
try {
const name = core.getInput(Inputs.Name, {required: false})
const path = core.getInput(Inputs.Path, {required: true})
const searchResult = await findFilesToUpload(path)
if (searchResult.filesToUpload.length === 0) {
core.warning(
`No files were found for the provided path: ${path}. No artifacts will be uploaded.`
)
} else {
core.info(
`With the provided path, there will be ${searchResult.filesToUpload.length} files uploaded`
)
core.debug(`Root artifact directory is ${searchResult.rootDirectory}`)
const artifactClient = create()
const options: UploadOptions = {
continueOnError: true
}
await artifactClient.uploadArtifact(
name || getDefaultArtifactName(),
searchResult.filesToUpload,
searchResult.rootDirectory,
options
)
core.info('Artifact upload has finished successfully!')
}
} catch (err) {
core.setFailed(err.message)
}
}
run()

17
tsconfig.json Normal file
View file

@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./lib",
"rootDir": "./src",
"strict": true,
"noImplicitAny": false,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"declaration": false,
"sourceMap": true,
"lib": ["es6"]
},
"exclude": ["node_modules", "**/*.test.ts"]
}