mirror of
https://github.com/trympet/nextcloud-artifacts-action.git
synced 2025-07-12 21:25:38 +02:00
Compare commits
72 Commits
Author | SHA1 | Date | |
---|---|---|---|
c80beab5e4 | |||
71897dd340 | |||
7ca2f4d4fb | |||
2b05098faf | |||
78aff342ff | |||
bcd3d376b1 | |||
565777588d | |||
7fe0b3a0b9 | |||
2e7f2059ee | |||
f7ad337105 | |||
b7837011bd | |||
d363b4a3d0 | |||
40bfed39fc | |||
4ab7fcecfe | |||
a5df11eaca | |||
02b35dd9a1 | |||
b52e167ab0 | |||
a48c618622 | |||
8167ea6a3e | |||
7a45cee3de | |||
518c6f09f4 | |||
200441494b | |||
68c752a9df | |||
f07d8c68b0 | |||
ea9ac9cb73 | |||
c1ee7f2095 | |||
cfb60b9a7f | |||
ab2bf8a4ac | |||
51f5e63425 | |||
cbff2d05e7 | |||
6226379dbd | |||
1f2878a134 | |||
a96cb53d3a | |||
4669814861 | |||
4ac99d8b52 | |||
3cf7a462d4 | |||
a54a89d701 | |||
0c1792a993 | |||
e97b799152 | |||
d873964202 | |||
5b01b7a17b | |||
2431c40b14 | |||
c38757acba | |||
ae39220a7a | |||
f5a8d5a548 | |||
ac058f2460 | |||
71d45ee738 | |||
353b719074 | |||
cb9c817940 | |||
80a86a24ab | |||
18228c6e6d | |||
70ba52c79f | |||
4e614273b8 | |||
77a901b885 | |||
03e2a736d6 | |||
438fd898e1 | |||
e8b44e8c3b | |||
75e2916679 | |||
e96c1262aa | |||
83cbe577f4 | |||
146df697b5 | |||
6def34f2d3 | |||
1cb76bcfb4 | |||
a87ab4cf3b | |||
7189094530 | |||
314b7642ee | |||
1259b887e0 | |||
aae7f9ffbb | |||
0141d42d10 | |||
a6f0f86670 | |||
22aca2a29a | |||
b0bfe891cd |
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
57
.eslintrc
Normal file
57
.eslintrc
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["jest", "@typescript-eslint"],
|
||||||
|
"extends": ["plugin:github/recommended"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 9,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"camelcase": "off",
|
||||||
|
"eslint-comments/no-use": "off",
|
||||||
|
"import/no-namespace": "off",
|
||||||
|
"no-shadow": "off",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"prefer-template": "off",
|
||||||
|
"semi": [ "error", "never"],
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||||
|
"@typescript-eslint/array-type": "error",
|
||||||
|
"@typescript-eslint/await-thenable": "error",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "error",
|
||||||
|
"@typescript-eslint/consistent-type-assertions": "error",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||||
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
"@typescript-eslint/no-extraneous-class": "error",
|
||||||
|
"@typescript-eslint/no-for-in-array": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": "error",
|
||||||
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
|
"@typescript-eslint/no-namespace": "error",
|
||||||
|
"@typescript-eslint/no-require-imports": "error",
|
||||||
|
"@typescript-eslint/no-shadow": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||||
|
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "^_"}],
|
||||||
|
"@typescript-eslint/no-useless-constructor": "error",
|
||||||
|
"@typescript-eslint/no-var-requires": "error",
|
||||||
|
"@typescript-eslint/prefer-for-of": "warn",
|
||||||
|
"@typescript-eslint/prefer-function-type": "warn",
|
||||||
|
"@typescript-eslint/prefer-includes": "error",
|
||||||
|
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||||
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
|
"@typescript-eslint/require-array-sort-compare": "error",
|
||||||
|
"@typescript-eslint/restrict-plus-operands": "error",
|
||||||
|
"@typescript-eslint/semi": ["error", "never"],
|
||||||
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
|
"@typescript-eslint/unbound-method": "error"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es6": true,
|
||||||
|
"jest/globals": true
|
||||||
|
}
|
||||||
|
}
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
lib
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
@ -80,7 +82,6 @@ typings/
|
|||||||
|
|
||||||
# Nuxt.js build / generate output
|
# Nuxt.js build / generate output
|
||||||
.nuxt
|
.nuxt
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
# Gatsby files
|
||||||
.cache/
|
.cache/
|
||||||
|
5
.prettierignore
Normal file
5
.prettierignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
|
__tests__/__outputs__
|
||||||
|
__tests__/__snapshots__
|
10
.prettierrc.json
Normal file
10
.prettierrc.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
30
README.md
30
README.md
@ -1 +1,29 @@
|
|||||||
# nextcloud-artifacts-action
|
# Nextcloud Artifact Upload Action
|
||||||
|
Upload artifacts to nextcloud and outputs a shareable URL.
|
||||||
|
|
||||||
|
### How it looks:
|
||||||
|

|
||||||
|
|
||||||
|
### Example:
|
||||||
|
Simple example. Globbing is supported.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
build-test:
|
||||||
|
name: Build & Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2 # checkout the repo
|
||||||
|
|
||||||
|
- name: Nextcloud Artifact
|
||||||
|
uses: trympet/nextcloud-artifacts-action@v2
|
||||||
|
with:
|
||||||
|
name: 'my-artifact' # Name of the artifact
|
||||||
|
path: 'bin/**/*.exe' # Globbing supported
|
||||||
|
nextcloud-url: 'https://nextcloud.example.com' # Format of test results
|
||||||
|
nextcloud-username: ${{ secrets.NEXTCLOUD_USERNAME }} # Username from repository secret
|
||||||
|
nextcloud-password: ${{ secrets.NEXTCLOUD_PASSWORD }} # Password from repository secret
|
||||||
|
```
|
||||||
|
37
__tests__/doubles/InputsDouble.ts
Normal file
37
__tests__/doubles/InputsDouble.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
import { Inputs } from '../../src/Inputs'
|
||||||
|
import { NoFileOption } from '../../src/NoFileOption'
|
||||||
|
|
||||||
|
export class InputsDouble implements Inputs {
|
||||||
|
get ArtifactName(): string {
|
||||||
|
return process.env['ARTIFACT_NAME']!
|
||||||
|
}
|
||||||
|
|
||||||
|
get ArtifactPath(): string {
|
||||||
|
return process.env['ARTIFACT_PATH']!
|
||||||
|
}
|
||||||
|
|
||||||
|
get Retention(): string {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
get Endpoint(): URL {
|
||||||
|
return new URL(process.env['ENDPOINT']!)
|
||||||
|
}
|
||||||
|
|
||||||
|
get Username(): string {
|
||||||
|
return process.env['USERNAME']!
|
||||||
|
}
|
||||||
|
|
||||||
|
get Password(): string {
|
||||||
|
return process.env['PASSWORD']!
|
||||||
|
}
|
||||||
|
|
||||||
|
get Token(): string {
|
||||||
|
return process.env['TOKEN']!
|
||||||
|
}
|
||||||
|
|
||||||
|
get NoFileBehvaior(): NoFileOption {
|
||||||
|
return NoFileOption.error
|
||||||
|
}
|
||||||
|
}
|
0
__tests__/fixtures/test-artifact/a.txt
Normal file
0
__tests__/fixtures/test-artifact/a.txt
Normal file
0
__tests__/fixtures/test-artifact/some-folder/b.txt
Normal file
0
__tests__/fixtures/test-artifact/some-folder/b.txt
Normal file
9
__tests__/integration.test.ts
Normal file
9
__tests__/integration.test.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { NextcloudArtifact } from '../src/nextcloud/NextcloudArtifact'
|
||||||
|
import { InputsDouble } from './doubles/InputsDouble'
|
||||||
|
|
||||||
|
describe('integration tests', () => {
|
||||||
|
it('works', async () => {
|
||||||
|
const artifact = new NextcloudArtifact(new InputsDouble())
|
||||||
|
await artifact.run()
|
||||||
|
})
|
||||||
|
})
|
1
__tests__/setup.ts
Normal file
1
__tests__/setup.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
require('dotenv').config()
|
15
action.yml
15
action.yml
@ -11,6 +11,12 @@ inputs:
|
|||||||
nextcloud-url:
|
nextcloud-url:
|
||||||
description: 'The URL for the nextcloud server'
|
description: 'The URL for the nextcloud server'
|
||||||
required: true
|
required: true
|
||||||
|
nextcloud-username:
|
||||||
|
description: 'The username for the nextcloud user'
|
||||||
|
required: true
|
||||||
|
nextcloud-password:
|
||||||
|
description: 'The password for the nextcloud user'
|
||||||
|
required: true
|
||||||
if-no-files-found:
|
if-no-files-found:
|
||||||
description: >
|
description: >
|
||||||
The desired behavior if no files are found using the provided path.
|
The desired behavior if no files are found using the provided path.
|
||||||
@ -19,11 +25,10 @@ inputs:
|
|||||||
error: Fail the action with an error message
|
error: Fail the action with an error message
|
||||||
ignore: Do not output any warnings or errors, the action does not fail
|
ignore: Do not output any warnings or errors, the action does not fail
|
||||||
default: 'warn'
|
default: 'warn'
|
||||||
retention-days:
|
token:
|
||||||
description: >
|
description: GitHub Access Token
|
||||||
Duration after which artifact will expire in days. 0 means using default retention.
|
required: false
|
||||||
Minimum 1 day.
|
default: ${{ github.token }}
|
||||||
Maximum 90 days unless changed from the repository settings page.
|
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
45407
dist/index.js
vendored
Normal file
45407
dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/index.js.map
vendored
Normal file
1
dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2581
dist/licenses.txt
vendored
Normal file
2581
dist/licenses.txt
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/sourcemap-register.js
vendored
Normal file
1
dist/sourcemap-register.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
jest.config.js
Normal file
12
jest.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module.exports = {
|
||||||
|
clearMocks: true,
|
||||||
|
moduleFileExtensions: ['js', 'ts'],
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testMatch: ['**/*.test.ts'],
|
||||||
|
testRunner: 'jest-circus/runner',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.ts$': 'ts-jest'
|
||||||
|
},
|
||||||
|
verbose: true,
|
||||||
|
setupFiles: ["./__tests__/setup.ts"]
|
||||||
|
}
|
6533
package-lock.json
generated
6533
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@ -1,32 +1,70 @@
|
|||||||
{
|
{
|
||||||
"name": "nextcloud-artifacts-action",
|
"name": "nextcloud-artifacts-action",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "lib/nextcloud-artifacts.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"build": "tsc",
|
||||||
|
"format": "prettier --write **/*.ts",
|
||||||
|
"format-check": "prettier --check **/*.ts",
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"package": "ncc build --source-map --license licenses.txt",
|
||||||
|
"test": "jest --ci"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/trympet/nextcloud-artifacts-action.git"
|
"url": "git+https://github.com/trympet/nextcloud-artifacts-action.git"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "Trym Lund Flogard <trym@flogard.no>",
|
||||||
"license": "ISC",
|
"license": "GPL-2.0-only",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/trympet/nextcloud-artifacts-action/issues"
|
"url": "https://github.com/trympet/nextcloud-artifacts-action/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/trympet/nextcloud-artifacts-action#readme",
|
"homepage": "https://github.com/trympet/nextcloud-artifacts-action#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.3.0",
|
"@actions/core": "^1.3.0",
|
||||||
|
"@actions/exec": "^1.0.4",
|
||||||
|
"@actions/github": "^5.0.0",
|
||||||
"@actions/glob": "^0.1.2",
|
"@actions/glob": "^0.1.2",
|
||||||
"archiver": "^5.3.0",
|
"archiver": "^5.3.0",
|
||||||
"node-fetch": "^2.6.1"
|
"btoa": "^1.2.1",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"webdav": "^4.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@octokit/types": "^6.16.2",
|
||||||
|
"@octokit/webhooks": "^9.6.3",
|
||||||
"@types/archiver": "^5.1.0",
|
"@types/archiver": "^5.1.0",
|
||||||
|
"@types/btoa": "^1.2.3",
|
||||||
|
"@types/jest": "^26.0.23",
|
||||||
"@types/node": "^15.6.2",
|
"@types/node": "^15.6.2",
|
||||||
"@types/node-fetch": "^2.5.10",
|
"@types/node-fetch": "^2.5.10",
|
||||||
|
"@types/uuid": "^8.3.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
||||||
|
"@typescript-eslint/parser": "^4.16.1",
|
||||||
|
"@vercel/ncc": "^0.28.6",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
|
"eslint": "^7.21.0",
|
||||||
|
"eslint-plugin-github": "^4.1.3",
|
||||||
|
"eslint-plugin-jest": "^24.1.7",
|
||||||
|
"jest": "^26.6.3",
|
||||||
|
"jest-circus": "^26.6.3",
|
||||||
|
"jest-junit": "^12.0.0",
|
||||||
|
"js-yaml": "^4.0.0",
|
||||||
|
"prettier": "2.2.1",
|
||||||
|
"ts-jest": "^26.5.3",
|
||||||
"typescript": "^4.3.2"
|
"typescript": "^4.3.2"
|
||||||
|
},
|
||||||
|
"jest-junit": {
|
||||||
|
"suiteName": "jest tests",
|
||||||
|
"outputDirectory": "__tests__/__results__",
|
||||||
|
"outputName": "jest-junit.xml",
|
||||||
|
"ancestorSeparator": " › ",
|
||||||
|
"uniqueOutputName": "false",
|
||||||
|
"suiteNameTemplate": "{filepath}",
|
||||||
|
"classNameTemplate": "{classname}",
|
||||||
|
"titleTemplate": "{title}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
src/ActionInputs.ts
Normal file
45
src/ActionInputs.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import * as core from '@actions/core'
|
||||||
|
import { NoFileOption } from './NoFileOption'
|
||||||
|
import { Inputs } from './Inputs'
|
||||||
|
import { URL } from 'url'
|
||||||
|
|
||||||
|
export class ActionInputs implements Inputs {
|
||||||
|
get ArtifactName(): string {
|
||||||
|
return core.getInput('name', { required: false }) || 'Nextcloud Artifact'
|
||||||
|
}
|
||||||
|
|
||||||
|
get ArtifactPath(): string {
|
||||||
|
return core.getInput('path', { required: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
get Endpoint(): URL {
|
||||||
|
return new URL(core.getInput('nextcloud-url', { required: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
get Username(): string {
|
||||||
|
return core.getInput('nextcloud-username', { required: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
get Password(): string {
|
||||||
|
return core.getInput('nextcloud-password', { required: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
get Token(): string {
|
||||||
|
return core.getInput('token', { required: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
get NoFileBehvaior(): NoFileOption {
|
||||||
|
const notFoundAction = core.getInput('if-no-files-found', { required: false }) || NoFileOption.warn
|
||||||
|
const noFileBehavior: NoFileOption = NoFileOption[notFoundAction as keyof typeof NoFileOption]
|
||||||
|
|
||||||
|
if (!noFileBehavior) {
|
||||||
|
core.setFailed(
|
||||||
|
`Unrecognized ${'ifNoFilesFound'} input. Provided: ${notFoundAction}. Available options: ${Object.keys(
|
||||||
|
NoFileOption
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return noFileBehavior
|
||||||
|
}
|
||||||
|
}
|
@ -1,146 +1,137 @@
|
|||||||
import * as glob from '@actions/glob'
|
import * as glob from '@actions/glob'
|
||||||
import {stat} from 'fs'
|
import { stat } from 'fs'
|
||||||
import {debug, info} from '@actions/core'
|
import { debug, info } from '@actions/core'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import {promisify} from 'util'
|
import { promisify } from 'util'
|
||||||
const stats = promisify(stat)
|
const stats = promisify(stat)
|
||||||
|
|
||||||
export class FileFinder {
|
export class FileFinder {
|
||||||
private static DefaultGlobOptions: glob.GlobOptions = {
|
private static DefaultGlobOptions: glob.GlobOptions = {
|
||||||
followSymbolicLinks: true,
|
followSymbolicLinks: true,
|
||||||
implicitDescendants: true,
|
implicitDescendants: true,
|
||||||
omitBrokenSymbolicLinks: true
|
omitBrokenSymbolicLinks: true
|
||||||
};
|
}
|
||||||
|
|
||||||
private globOptions: glob.GlobOptions
|
private globOptions: glob.GlobOptions
|
||||||
|
|
||||||
public constructor(private searchPath: string, globOptions?: glob.GlobOptions) {
|
constructor(private searchPath: string, globOptions?: glob.GlobOptions) {
|
||||||
this.globOptions = globOptions || FileFinder.DefaultGlobOptions;
|
this.globOptions = globOptions || FileFinder.DefaultGlobOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findFiles() {
|
async findFiles() {
|
||||||
const searchResults: string[] = []
|
const searchResults: string[] = []
|
||||||
const globber = await glob.create(
|
const globber = await glob.create(this.searchPath, this.globOptions)
|
||||||
this.searchPath,
|
|
||||||
this.globOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
const rawSearchResults: string[] = await globber.glob()
|
const rawSearchResults: string[] = await globber.glob()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Files are saved with case insensitivity. Uploading both a.txt and A.txt will files to be overwritten
|
Files are saved with case insensitivity. Uploading both a.txt and A.txt will files to be overwritten
|
||||||
Detect any files that could be overwritten for user awareness
|
Detect any files that could be overwritten for user awareness
|
||||||
*/
|
*/
|
||||||
const set = new Set<string>()
|
const set = new Set<string>()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Directories will be rejected if attempted to be uploaded. This includes just empty
|
Directories will be rejected if attempted to be uploaded. This includes just empty
|
||||||
directories so filter any directories out from the raw search results
|
directories so filter any directories out from the raw search results
|
||||||
*/
|
*/
|
||||||
for (const searchResult of rawSearchResults) {
|
for (const searchResult of rawSearchResults) {
|
||||||
const fileStats = await stats(searchResult)
|
const fileStats = await stats(searchResult)
|
||||||
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
||||||
if (!fileStats.isDirectory()) {
|
if (!fileStats.isDirectory()) {
|
||||||
debug(`File:${searchResult} was found using the provided searchPath`)
|
debug(`File:${searchResult} was found using the provided searchPath`)
|
||||||
searchResults.push(searchResult)
|
searchResults.push(searchResult)
|
||||||
|
|
||||||
// detect any files that would be overwritten because of case insensitivity
|
// detect any files that would be overwritten because of case insensitivity
|
||||||
if (set.has(searchResult.toLowerCase())) {
|
if (set.has(searchResult.toLowerCase())) {
|
||||||
info(
|
info(
|
||||||
`Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path`
|
`Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
set.add(searchResult.toLowerCase())
|
set.add(searchResult.toLowerCase())
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug(
|
|
||||||
`Removing ${searchResult} from rawSearchResults because it is a directory`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
debug(`Removing ${searchResult} from rawSearchResults because it is a directory`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate the root directory for the artifact using the search paths that were utilized
|
// Calculate the root directory for the artifact using the search paths that were utilized
|
||||||
const searchPaths: string[] = globber.getSearchPaths()
|
const searchPaths: string[] = globber.getSearchPaths()
|
||||||
|
|
||||||
if (searchPaths.length > 1) {
|
if (searchPaths.length > 1) {
|
||||||
info(
|
info(`Multiple search paths detected. Calculating the least common ancestor of all paths`)
|
||||||
`Multiple search paths detected. Calculating the least common ancestor of all paths`
|
const lcaSearchPath = this.getMultiPathLCA(searchPaths)
|
||||||
)
|
info(`The least common ancestor is ${lcaSearchPath}. This will be the root directory of the artifact`)
|
||||||
const lcaSearchPath = this.getMultiPathLCA(searchPaths)
|
|
||||||
info(
|
|
||||||
`The least common ancestor is ${lcaSearchPath}. This will be the root directory of the artifact`
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filesToUpload: searchResults,
|
filesToUpload: searchResults,
|
||||||
rootDirectory: lcaSearchPath
|
rootDirectory: lcaSearchPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Special case for a single file artifact that is uploaded without a directory or wildcard pattern. The directory structure is
|
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
|
not preserved and the root directory will be the single files parent directory
|
||||||
*/
|
*/
|
||||||
if (searchResults.length === 1 && searchPaths[0] === searchResults[0]) {
|
if (searchResults.length === 1 && searchPaths[0] === searchResults[0]) {
|
||||||
return {
|
return {
|
||||||
filesToUpload: searchResults,
|
filesToUpload: searchResults,
|
||||||
rootDirectory: path.dirname(searchResults[0])
|
rootDirectory: path.dirname(searchResults[0])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
filesToUpload: searchResults,
|
|
||||||
rootDirectory: searchPaths[0]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMultiPathLCA(searchPaths: string[]): string {
|
return {
|
||||||
if (searchPaths.length < 2) {
|
filesToUpload: searchResults,
|
||||||
throw new Error('At least two search paths must be provided')
|
rootDirectory: searchPaths[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMultiPathLCA(searchPaths: string[]): string {
|
||||||
|
if (searchPaths.length < 2) {
|
||||||
|
throw new Error('At least two search paths must be provided')
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonPaths = new Array<string>()
|
||||||
|
const splitPaths = new Array<string[]>()
|
||||||
|
let smallestPathLength = Number.MAX_SAFE_INTEGER
|
||||||
|
|
||||||
|
// split each of the search paths using the platform specific separator
|
||||||
|
for (const searchPath of searchPaths) {
|
||||||
|
debug(`Using search path ${searchPath}`)
|
||||||
|
|
||||||
|
const splitSearchPath = path.normalize(searchPath).split(path.sep)
|
||||||
|
|
||||||
|
// keep track of the smallest path length so that we don't accidentally later go out of bounds
|
||||||
|
smallestPathLength = Math.min(smallestPathLength, splitSearchPath.length)
|
||||||
|
splitPaths.push(splitSearchPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// on Unix-like file systems, the file separator exists at the beginning of the file path, make sure to preserve it
|
||||||
|
if (searchPaths[0].startsWith(path.sep)) {
|
||||||
|
commonPaths.push(path.sep)
|
||||||
|
}
|
||||||
|
|
||||||
|
let splitIndex = 0
|
||||||
|
// function to check if the paths are the same at a specific index
|
||||||
|
function isPathTheSame(): boolean {
|
||||||
|
const compare = splitPaths[0][splitIndex]
|
||||||
|
for (let i = 1; i < splitPaths.length; i++) {
|
||||||
|
if (compare !== splitPaths[i][splitIndex]) {
|
||||||
|
// a non-common index has been reached
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const commonPaths = new Array<string>()
|
|
||||||
const splitPaths = new Array<string[]>()
|
|
||||||
let smallestPathLength = Number.MAX_SAFE_INTEGER
|
|
||||||
|
|
||||||
// split each of the search paths using the platform specific separator
|
|
||||||
for (const searchPath of searchPaths) {
|
|
||||||
debug(`Using search path ${searchPath}`)
|
|
||||||
|
|
||||||
const splitSearchPath = path.normalize(searchPath).split(path.sep)
|
|
||||||
|
|
||||||
// keep track of the smallest path length so that we don't accidentally later go out of bounds
|
|
||||||
smallestPathLength = Math.min(smallestPathLength, splitSearchPath.length)
|
|
||||||
splitPaths.push(splitSearchPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// on Unix-like file systems, the file separator exists at the beginning of the file path, make sure to preserve it
|
|
||||||
if (searchPaths[0].startsWith(path.sep)) {
|
|
||||||
commonPaths.push(path.sep)
|
|
||||||
}
|
|
||||||
|
|
||||||
let splitIndex = 0
|
|
||||||
// function to check if the paths are the same at a specific index
|
|
||||||
function isPathTheSame(): boolean {
|
|
||||||
const compare = splitPaths[0][splitIndex]
|
|
||||||
for (let i = 1; i < splitPaths.length; i++) {
|
|
||||||
if (compare !== splitPaths[i][splitIndex]) {
|
|
||||||
// a non-common index has been reached
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop over all the search paths until there is a non-common ancestor or we go out of bounds
|
|
||||||
while (splitIndex < smallestPathLength) {
|
|
||||||
if (!isPathTheSame()) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// if all are the same, add to the end result & increment the index
|
|
||||||
commonPaths.push(splitPaths[0][splitIndex])
|
|
||||||
splitIndex++
|
|
||||||
}
|
|
||||||
return path.join(...commonPaths)
|
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop over all the search paths until there is a non-common ancestor or we go out of bounds
|
||||||
|
while (splitIndex < smallestPathLength) {
|
||||||
|
if (!isPathTheSame()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if all are the same, add to the end result & increment the index
|
||||||
|
commonPaths.push(splitPaths[0][splitIndex])
|
||||||
|
splitIndex++
|
||||||
|
}
|
||||||
|
return path.join(...commonPaths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,35 +1,18 @@
|
|||||||
import core from '@actions/core';
|
import { URL } from 'url'
|
||||||
import { NoFileOption } from './NoFileOption';
|
import { NoFileOption } from './NoFileOption'
|
||||||
|
|
||||||
export class Inputs {
|
export interface Inputs {
|
||||||
static get ArtifactName(): string {
|
readonly ArtifactName: string
|
||||||
return core.getInput("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
static get ArtifactPath(): string {
|
readonly ArtifactPath: string
|
||||||
return core.getInput("path");
|
|
||||||
}
|
|
||||||
|
|
||||||
static get Retention(): string {
|
readonly Endpoint: URL
|
||||||
return core.getInput("retention-days");
|
|
||||||
}
|
|
||||||
|
|
||||||
static get Endpoint(): string {
|
readonly Username: string
|
||||||
return core.getInput("nextcloud-url");
|
|
||||||
}
|
|
||||||
|
|
||||||
static get NoFileBehvaior(): NoFileOption {
|
readonly Password: string
|
||||||
const notFoundAction = core.getInput("if-no-files-found");
|
|
||||||
const noFileBehavior: NoFileOption = NoFileOption[notFoundAction as keyof typeof NoFileOption];
|
|
||||||
|
|
||||||
if (!noFileBehavior) {
|
readonly Token: string
|
||||||
core.setFailed(
|
|
||||||
`Unrecognized ${"ifNoFilesFound"} input. Provided: ${notFoundAction}. Available options: ${Object.keys(
|
|
||||||
NoFileOption
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return noFileBehavior;
|
readonly NoFileBehvaior: NoFileOption
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
export enum NoFileOption {
|
export enum NoFileOption {
|
||||||
/**
|
/**
|
||||||
* Default. Output a warning but do not fail the action
|
* Default. Output a warning but do not fail the action
|
||||||
*/
|
*/
|
||||||
warn = 'warn',
|
warn = 'warn',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fail the action with an error message
|
* Fail the action with an error message
|
||||||
*/
|
*/
|
||||||
error = 'error',
|
error = 'error',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not output any warnings or errors, the action does not fail
|
* Do not output any warnings or errors, the action does not fail
|
||||||
*/
|
*/
|
||||||
ignore = 'ignore',
|
ignore = 'ignore'
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { Inputs } from './Inputs';
|
|
||||||
import { NextcloudArtifact } from './nextcloud/NextcloudArtifact';
|
|
||||||
|
|
||||||
var artifact = new NextcloudArtifact(Inputs.ArtifactName, Inputs.ArtifactPath, Inputs.NoFileBehvaior);
|
|
||||||
artifact.run();
|
|
15
src/nextcloud-artifacts.ts
Normal file
15
src/nextcloud-artifacts.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { NextcloudArtifact } from './nextcloud/NextcloudArtifact'
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import { ActionInputs } from './ActionInputs'
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
try {
|
||||||
|
const artifact = new NextcloudArtifact(new ActionInputs())
|
||||||
|
await artifact.run()
|
||||||
|
core.info('Finished')
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
@ -1,64 +1,154 @@
|
|||||||
import core from '@actions/core';
|
import * as core from '@actions/core'
|
||||||
import { FileFinder } from '../FileFinder';
|
import * as github from '@actions/github'
|
||||||
import { Inputs } from '../Inputs';
|
import { GitHub } from '@actions/github/lib/utils'
|
||||||
import { NextcloudClient } from './NextcloudClient';
|
|
||||||
import { NoFileOption } from '../NoFileOption';
|
import { FileFinder } from '../FileFinder'
|
||||||
|
import { Inputs } from '../Inputs'
|
||||||
|
import { NextcloudClient } from './NextcloudClient'
|
||||||
|
import { NoFileOption } from '../NoFileOption'
|
||||||
|
|
||||||
export class NextcloudArtifact {
|
export class NextcloudArtifact {
|
||||||
public constructor(
|
readonly octokit: InstanceType<typeof GitHub>
|
||||||
private name: string,
|
readonly context = NextcloudArtifact.getCheckRunContext()
|
||||||
private path: string,
|
readonly token: string
|
||||||
private errorBehavior: NoFileOption) { }
|
readonly name: string
|
||||||
|
readonly artifactTitle: string
|
||||||
|
readonly path: string
|
||||||
|
readonly errorBehavior: NoFileOption
|
||||||
|
|
||||||
public async run() {
|
constructor(private inputs: Inputs) {
|
||||||
const fileFinder = new FileFinder(this.path);
|
this.token = inputs.Token
|
||||||
const files = await fileFinder.findFiles();
|
this.name = inputs.ArtifactName
|
||||||
|
this.artifactTitle = `Nextcloud - ${this.name}`
|
||||||
|
this.path = inputs.ArtifactPath
|
||||||
|
this.errorBehavior = inputs.NoFileBehvaior
|
||||||
|
this.name = inputs.ArtifactName
|
||||||
|
this.octokit = github.getOctokit(this.token)
|
||||||
|
}
|
||||||
|
|
||||||
if (files.filesToUpload.length > 0) {
|
async run() {
|
||||||
await this.uploadFiles(files);
|
const fileFinder = new FileFinder(this.path)
|
||||||
}
|
const files = await fileFinder.findFiles()
|
||||||
else {
|
|
||||||
this.logNoFilesFound();
|
if (files.filesToUpload.length > 0) {
|
||||||
}
|
await this.uploadFiles(files)
|
||||||
|
} else {
|
||||||
|
this.logNoFilesFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getCheckRunContext(): { sha: string; runId: number } {
|
||||||
|
if (github.context.eventName === 'workflow_run') {
|
||||||
|
core.info('Action was triggered by workflow_run: using SHA and RUN_ID from triggering workflow')
|
||||||
|
const event = github.context.payload
|
||||||
|
if (!event.workflow_run) {
|
||||||
|
throw new Error("Event of type 'workflow_run' is missing 'workflow_run' field")
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sha: event.workflow_run.head_commit.id,
|
||||||
|
runId: event.workflow_run.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadFiles(files: { filesToUpload: string[]; rootDirectory: string; }) {
|
const runId = github.context.runId
|
||||||
this.logUpload(files.filesToUpload.length, files.rootDirectory);
|
if (github.context.payload.pull_request) {
|
||||||
|
core.info(`Action was triggered by ${github.context.eventName}: using SHA from head of source branch`)
|
||||||
const client = new NextcloudClient(Inputs.Endpoint, this.name, files.rootDirectory);
|
const pr = github.context.payload.pull_request
|
||||||
|
return { sha: pr.head.sha, runId }
|
||||||
await client.uploadFiles(files.filesToUpload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private logUpload(fileCount: number, rootDirectory: string) {
|
return { sha: github.context.sha, runId }
|
||||||
const s = fileCount === 1 ? '' : 's';
|
}
|
||||||
core.info(
|
|
||||||
`With the provided path, there will be ${fileCount} file${s} uploaded`
|
|
||||||
);
|
|
||||||
core.debug(`Root artifact directory is ${rootDirectory}`);
|
|
||||||
|
|
||||||
if (fileCount > 10000) {
|
private async uploadFiles(files: { filesToUpload: string[]; rootDirectory: string }) {
|
||||||
core.warning(
|
this.logUpload(files.filesToUpload.length, files.rootDirectory)
|
||||||
`There are over 10,000 files in this artifact, consider create an archive before upload to improve the upload performance.`
|
const createResp = await this.octokit.rest.checks.create({
|
||||||
);
|
head_sha: this.context.sha,
|
||||||
}
|
name: this.artifactTitle,
|
||||||
}
|
status: 'in_progress',
|
||||||
|
output: {
|
||||||
|
title: `Nextcloud - ${this.name}`,
|
||||||
|
summary: 'Uploading...'
|
||||||
|
},
|
||||||
|
...github.context.repo
|
||||||
|
})
|
||||||
|
|
||||||
private logNoFilesFound() {
|
const client = new NextcloudClient(
|
||||||
const errorMessage = `No files were found with the provided path: ${this.path}. No artifacts will be uploaded.`;
|
this.inputs.Endpoint,
|
||||||
switch (this.errorBehavior) {
|
this.name,
|
||||||
case NoFileOption.warn: {
|
files.rootDirectory,
|
||||||
core.warning(errorMessage);
|
this.inputs.Username,
|
||||||
break;
|
this.inputs.Password
|
||||||
}
|
)
|
||||||
case NoFileOption.error: {
|
|
||||||
core.setFailed(errorMessage);
|
try {
|
||||||
break;
|
const shareableUrl = await client.uploadFiles(files.filesToUpload)
|
||||||
}
|
core.info(`Nextcloud shareable URL: ${shareableUrl}`)
|
||||||
case NoFileOption.ignore: {
|
const resp = await this.octokit.rest.checks.update({
|
||||||
core.info(errorMessage);
|
check_run_id: createResp.data.id,
|
||||||
break;
|
conclusion: 'success',
|
||||||
}
|
status: 'completed',
|
||||||
}
|
output: {
|
||||||
|
title: this.artifactTitle,
|
||||||
|
summary: shareableUrl
|
||||||
|
},
|
||||||
|
...github.context.repo
|
||||||
|
})
|
||||||
|
core.info(`Check run create response: ${resp.status}`)
|
||||||
|
core.info(`Check run URL: ${resp.data.url}`)
|
||||||
|
core.info(`Check run HTML: ${resp.data.html_url}`)
|
||||||
|
} catch (error) {
|
||||||
|
await this.trySetFailed(createResp.data.id)
|
||||||
|
core.setFailed(error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async trySetFailed(checkId: number) {
|
||||||
|
try {
|
||||||
|
await this.octokit.rest.checks.update({
|
||||||
|
check_run_id: checkId,
|
||||||
|
conclusion: 'failure',
|
||||||
|
status: 'completed',
|
||||||
|
output: {
|
||||||
|
title: this.artifactTitle,
|
||||||
|
summary: 'Check failed.'
|
||||||
|
},
|
||||||
|
...github.context.repo
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Failed to update check status to failure`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private logUpload(fileCount: number, rootDirectory: string) {
|
||||||
|
const s = fileCount === 1 ? '' : 's'
|
||||||
|
core.info(`With the provided path, there will be ${fileCount} file${s} uploaded`)
|
||||||
|
core.debug(`Root artifact directory is ${rootDirectory}`)
|
||||||
|
|
||||||
|
if (fileCount > 10000) {
|
||||||
|
core.warning(
|
||||||
|
`There are over 10,000 files in this artifact, consider create an archive before upload to improve the upload performance.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private logNoFilesFound() {
|
||||||
|
const errorMessage = `No files were found with the provided path: ${this.path}. No artifacts will be uploaded.`
|
||||||
|
switch (this.errorBehavior) {
|
||||||
|
case NoFileOption.warn: {
|
||||||
|
core.warning(errorMessage)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case NoFileOption.error: {
|
||||||
|
core.setFailed(errorMessage)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case NoFileOption.ignore: {
|
||||||
|
core.info(errorMessage)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
303
src/nextcloud/NextcloudClient.js
Normal file
303
src/nextcloud/NextcloudClient.js
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||||
|
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
||||||
|
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||||
|
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||||
|
function step(op) {
|
||||||
|
if (f) throw new TypeError("Generator is already executing.");
|
||||||
|
while (_) try {
|
||||||
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||||
|
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||||
|
switch (op[0]) {
|
||||||
|
case 0: case 1: t = op; break;
|
||||||
|
case 4: _.label++; return { value: op[1], done: false };
|
||||||
|
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||||
|
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||||
|
default:
|
||||||
|
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||||
|
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||||
|
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||||
|
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||||
|
if (t[2]) _.ops.pop();
|
||||||
|
_.trys.pop(); continue;
|
||||||
|
}
|
||||||
|
op = body.call(thisArg, _);
|
||||||
|
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||||
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.__esModule = true;
|
||||||
|
exports.NextcloudClient = void 0;
|
||||||
|
var fsSync = require("fs");
|
||||||
|
var path = require("path");
|
||||||
|
var core = require("@actions/core");
|
||||||
|
var os = require("os");
|
||||||
|
var archiver = require("archiver");
|
||||||
|
var node_fetch_1 = require("node-fetch");
|
||||||
|
var btoa_1 = require("btoa");
|
||||||
|
var uuid_1 = require("uuid");
|
||||||
|
var webdav = require("webdav");
|
||||||
|
var fs = fsSync.promises;
|
||||||
|
var NextcloudClient = /** @class */ (function () {
|
||||||
|
function NextcloudClient(endpoint, artifact, rootDirectory, username, password) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.artifact = artifact;
|
||||||
|
this.rootDirectory = rootDirectory;
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.guid = uuid_1.v4();
|
||||||
|
this.headers = { 'Authorization': 'Basic ' + btoa_1["default"](this.username + ":" + this.password) };
|
||||||
|
this.davClient = webdav.createClient(this.endpoint + "/remote.php/dav/files/" + this.username, {
|
||||||
|
username: this.username,
|
||||||
|
password: this.password
|
||||||
|
});
|
||||||
|
}
|
||||||
|
NextcloudClient.prototype.uploadFiles = function (files) {
|
||||||
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
|
var spec, zip, path;
|
||||||
|
return __generator(this, function (_a) {
|
||||||
|
switch (_a.label) {
|
||||||
|
case 0:
|
||||||
|
core.info("Preparing upload...");
|
||||||
|
spec = this.uploadSpec(files);
|
||||||
|
core.info("Zipping files...");
|
||||||
|
return [4 /*yield*/, this.zipFiles(spec)];
|
||||||
|
case 1:
|
||||||
|
zip = _a.sent();
|
||||||
|
core.info("Uploading to Nextcloud...");
|
||||||
|
return [4 /*yield*/, this.upload(zip)];
|
||||||
|
case 2:
|
||||||
|
path = _a.sent();
|
||||||
|
core.info("File path: " + path);
|
||||||
|
core.info("Sharing file...");
|
||||||
|
return [4 /*yield*/, this.shareFile(path)];
|
||||||
|
case 3:
|
||||||
|
_a.sent();
|
||||||
|
return [2 /*return*/];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
NextcloudClient.prototype.uploadSpec = function (files) {
|
||||||
|
var specifications = [];
|
||||||
|
if (!fsSync.existsSync(this.rootDirectory)) {
|
||||||
|
throw new Error("this.rootDirectory " + this.rootDirectory + " does not exist");
|
||||||
|
}
|
||||||
|
if (!fsSync.lstatSync(this.rootDirectory).isDirectory()) {
|
||||||
|
throw new Error("this.rootDirectory " + this.rootDirectory + " is not a valid directory");
|
||||||
|
}
|
||||||
|
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
||||||
|
var root = path.normalize(this.rootDirectory);
|
||||||
|
root = path.resolve(root);
|
||||||
|
/*
|
||||||
|
Example to demonstrate behavior
|
||||||
|
|
||||||
|
Input:
|
||||||
|
artifactName: my-artifact
|
||||||
|
rootDirectory: '/home/user/files/plz-upload'
|
||||||
|
artifactFiles: [
|
||||||
|
'/home/user/files/plz-upload/file1.txt',
|
||||||
|
'/home/user/files/plz-upload/file2.txt',
|
||||||
|
'/home/user/files/plz-upload/dir/file3.txt'
|
||||||
|
]
|
||||||
|
|
||||||
|
Output:
|
||||||
|
specifications: [
|
||||||
|
['/home/user/files/plz-upload/file1.txt', 'my-artifact/file1.txt'],
|
||||||
|
['/home/user/files/plz-upload/file1.txt', 'my-artifact/file2.txt'],
|
||||||
|
['/home/user/files/plz-upload/file1.txt', 'my-artifact/dir/file3.txt']
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
for (var _i = 0, files_1 = files; _i < files_1.length; _i++) {
|
||||||
|
var file = files_1[_i];
|
||||||
|
if (!fsSync.existsSync(file)) {
|
||||||
|
throw new Error("File " + file + " does not exist");
|
||||||
|
}
|
||||||
|
if (!fsSync.lstatSync(file).isDirectory()) {
|
||||||
|
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
||||||
|
file = path.normalize(file);
|
||||||
|
file = path.resolve(file);
|
||||||
|
if (!file.startsWith(root)) {
|
||||||
|
throw new Error("The rootDirectory: " + root + " is not a parent directory of the file: " + file);
|
||||||
|
}
|
||||||
|
// Check for forbidden characters in file paths that will be rejected during upload
|
||||||
|
var uploadPath = file.replace(root, '');
|
||||||
|
/*
|
||||||
|
uploadFilePath denotes where the file will be uploaded in the file container on the server. During a run, if multiple artifacts are uploaded, they will all
|
||||||
|
be saved in the same container. The artifact name is used as the root directory in the container to separate and distinguish uploaded artifacts
|
||||||
|
|
||||||
|
path.join handles all the following cases and would return 'artifact-name/file-to-upload.txt
|
||||||
|
join('artifact-name/', 'file-to-upload.txt')
|
||||||
|
join('artifact-name/', '/file-to-upload.txt')
|
||||||
|
join('artifact-name', 'file-to-upload.txt')
|
||||||
|
join('artifact-name', '/file-to-upload.txt')
|
||||||
|
*/
|
||||||
|
specifications.push({
|
||||||
|
absolutePath: file,
|
||||||
|
uploadPath: path.join(this.artifact, uploadPath)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Directories are rejected by the server during upload
|
||||||
|
core.debug("Removing " + file + " from rawSearchResults because it is a directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return specifications;
|
||||||
|
};
|
||||||
|
NextcloudClient.prototype.zipFiles = function (specs) {
|
||||||
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
|
var tempArtifactDir, artifactPath, copies, _i, specs_1, spec, dstpath, dstDir, _a, _b, _c, archivePath, _d, _e, _f;
|
||||||
|
return __generator(this, function (_g) {
|
||||||
|
switch (_g.label) {
|
||||||
|
case 0:
|
||||||
|
tempArtifactDir = path.join(os.tmpdir(), this.guid);
|
||||||
|
artifactPath = path.join(tempArtifactDir, "artifact-" + this.artifact);
|
||||||
|
return [4 /*yield*/, fs.mkdir(path.join(artifactPath, this.artifact), { recursive: true })];
|
||||||
|
case 1:
|
||||||
|
_g.sent();
|
||||||
|
copies = [];
|
||||||
|
_i = 0, specs_1 = specs;
|
||||||
|
_g.label = 2;
|
||||||
|
case 2:
|
||||||
|
if (!(_i < specs_1.length)) return [3 /*break*/, 6];
|
||||||
|
spec = specs_1[_i];
|
||||||
|
dstpath = path.join(artifactPath, spec.uploadPath);
|
||||||
|
dstDir = path.dirname(dstpath);
|
||||||
|
if (!!fsSync.existsSync(dstDir)) return [3 /*break*/, 4];
|
||||||
|
return [4 /*yield*/, fs.mkdir(dstDir, { recursive: true })];
|
||||||
|
case 3:
|
||||||
|
_g.sent();
|
||||||
|
_g.label = 4;
|
||||||
|
case 4:
|
||||||
|
copies.push(fs.copyFile(spec.absolutePath, dstpath));
|
||||||
|
_g.label = 5;
|
||||||
|
case 5:
|
||||||
|
_i++;
|
||||||
|
return [3 /*break*/, 2];
|
||||||
|
case 6: return [4 /*yield*/, Promise.all(copies)];
|
||||||
|
case 7:
|
||||||
|
_g.sent();
|
||||||
|
_b = (_a = core).info;
|
||||||
|
_c = "files: ";
|
||||||
|
return [4 /*yield*/, fs.readdir(path.join(artifactPath, this.artifact))];
|
||||||
|
case 8:
|
||||||
|
_b.apply(_a, [_c + (_g.sent())]);
|
||||||
|
archivePath = path.join(artifactPath, this.artifact + ".zip");
|
||||||
|
return [4 /*yield*/, this.zip(path.join(artifactPath, this.artifact), archivePath)];
|
||||||
|
case 9:
|
||||||
|
_g.sent();
|
||||||
|
_e = (_d = core).info;
|
||||||
|
_f = "archive stat: ";
|
||||||
|
return [4 /*yield*/, fs.stat(archivePath)];
|
||||||
|
case 10:
|
||||||
|
_e.apply(_d, [_f + (_g.sent()).size]);
|
||||||
|
return [2 /*return*/, archivePath];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
NextcloudClient.prototype.zip = function (dirpath, destpath) {
|
||||||
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
|
var archive, stream;
|
||||||
|
return __generator(this, function (_a) {
|
||||||
|
switch (_a.label) {
|
||||||
|
case 0:
|
||||||
|
archive = archiver.create('zip', { zlib: { level: 9 } });
|
||||||
|
stream = archive.directory(dirpath, false)
|
||||||
|
.pipe(fsSync.createWriteStream(destpath));
|
||||||
|
return [4 /*yield*/, archive.finalize()];
|
||||||
|
case 1:
|
||||||
|
_a.sent();
|
||||||
|
return [4 /*yield*/, new Promise(function (resolve, reject) {
|
||||||
|
stream.on('error', function (e) { return reject(e); })
|
||||||
|
.on('close', function () { return resolve(); });
|
||||||
|
})];
|
||||||
|
case 2: return [2 /*return*/, _a.sent()];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
NextcloudClient.prototype.upload = function (file) {
|
||||||
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
|
var remoteFileDir, remoteFilePath;
|
||||||
|
return __generator(this, function (_a) {
|
||||||
|
switch (_a.label) {
|
||||||
|
case 0:
|
||||||
|
remoteFileDir = "/artifacts/" + this.guid;
|
||||||
|
core.info("Checking directory...");
|
||||||
|
return [4 /*yield*/, this.davClient.exists(remoteFileDir)];
|
||||||
|
case 1:
|
||||||
|
if (!!(_a.sent())) return [3 /*break*/, 3];
|
||||||
|
core.info("Creating directory...");
|
||||||
|
return [4 /*yield*/, this.davClient.createDirectory(remoteFileDir, { recursive: true })];
|
||||||
|
case 2:
|
||||||
|
_a.sent();
|
||||||
|
_a.label = 3;
|
||||||
|
case 3:
|
||||||
|
remoteFilePath = remoteFileDir + "/" + this.artifact + ".zip";
|
||||||
|
core.info("Transferring file... (" + file + ")");
|
||||||
|
return [4 /*yield*/, this.transferFile(remoteFilePath, file)];
|
||||||
|
case 4:
|
||||||
|
_a.sent();
|
||||||
|
core.info("finish");
|
||||||
|
return [2 /*return*/, remoteFilePath];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
NextcloudClient.prototype.transferFile = function (remoteFilePath, file) {
|
||||||
|
var fileStream = fsSync.createReadStream(file);
|
||||||
|
var fileStreamPromise = new Promise(function (resolve, reject) {
|
||||||
|
fileStream.on('error', function () { return reject("Failed to read file"); })
|
||||||
|
.on('end', function () { return resolve(); });
|
||||||
|
});
|
||||||
|
var remoteStream = this.davClient.createWriteStream(remoteFilePath);
|
||||||
|
fileStream.pipe(remoteStream);
|
||||||
|
var remoteStreamPromise = new Promise(function (resolve, reject) {
|
||||||
|
remoteStream.on('error', function () { return reject("Failed to upload file"); })
|
||||||
|
.on('close', function () { return resolve(); });
|
||||||
|
});
|
||||||
|
return Promise.all([remoteStreamPromise, fileStreamPromise]);
|
||||||
|
};
|
||||||
|
NextcloudClient.prototype.shareFile = function (remoteFilePath) {
|
||||||
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
|
var url, body, res, _a, _b;
|
||||||
|
return __generator(this, function (_c) {
|
||||||
|
switch (_c.label) {
|
||||||
|
case 0:
|
||||||
|
url = this.endpoint + "/ocs/v2.php/apps/files_sharing/api/v1/shares";
|
||||||
|
body = {
|
||||||
|
path: remoteFilePath,
|
||||||
|
shareType: 3,
|
||||||
|
publicUpload: "false",
|
||||||
|
permissions: 1
|
||||||
|
};
|
||||||
|
return [4 /*yield*/, node_fetch_1["default"](url, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: this.headers,
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})];
|
||||||
|
case 1:
|
||||||
|
res = _c.sent();
|
||||||
|
res.status;
|
||||||
|
_b = (_a = core).info;
|
||||||
|
return [4 /*yield*/, res.text()];
|
||||||
|
case 2:
|
||||||
|
_b.apply(_a, [_c.sent()]);
|
||||||
|
return [2 /*return*/];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return NextcloudClient;
|
||||||
|
}());
|
||||||
|
exports.NextcloudClient = NextcloudClient;
|
@ -1,132 +1,183 @@
|
|||||||
import * as fsSync from 'fs'
|
import * as fsSync from 'fs'
|
||||||
import * as fs from 'fs/promises'
|
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import core from '@actions/core';
|
import * as core from '@actions/core'
|
||||||
import * as os from 'os';
|
import * as os from 'os'
|
||||||
import { randomUUID } from 'crypto';
|
import * as archiver from 'archiver'
|
||||||
import * as archiver from 'archiver';
|
import fetch, { HeadersInit } from 'node-fetch'
|
||||||
import { URL } from 'url';
|
import btoa from 'btoa'
|
||||||
import fetch from 'node-fetch';
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import * as webdav from 'webdav'
|
||||||
|
import { URL } from 'url'
|
||||||
|
|
||||||
|
const fs = fsSync.promises
|
||||||
|
|
||||||
interface FileSpec {
|
interface FileSpec {
|
||||||
absolutePath: string,
|
absolutePath: string
|
||||||
uploadPath: string
|
uploadPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NextcloudClient {
|
export class NextcloudClient {
|
||||||
public constructor(
|
private guid: string
|
||||||
private endpoint: string,
|
private headers: HeadersInit
|
||||||
private artifact: string,
|
private davClient
|
||||||
private rootDirectory: string) { }
|
|
||||||
|
|
||||||
public async uploadFiles(files: string[]) {
|
constructor(
|
||||||
const spec = this.uploadSpec(files);
|
private endpoint: URL,
|
||||||
var zip = await this.zipFiles(spec);
|
private artifact: string,
|
||||||
await this.upload(zip);
|
private rootDirectory: string,
|
||||||
|
private username: string,
|
||||||
|
private password: string
|
||||||
|
) {
|
||||||
|
this.guid = uuidv4()
|
||||||
|
this.headers = { Authorization: 'Basic ' + btoa(`${this.username}:${this.password}`) }
|
||||||
|
this.davClient = webdav.createClient(`${this.endpoint.href}remote.php/dav/files/${this.username}`, {
|
||||||
|
username: this.username,
|
||||||
|
password: this.password
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFiles(files: string[]): Promise<string> {
|
||||||
|
core.info('Preparing upload...')
|
||||||
|
const spec = this.uploadSpec(files)
|
||||||
|
core.info('Zipping files...')
|
||||||
|
const zip = await this.zipFiles(spec)
|
||||||
|
|
||||||
|
core.info('Uploading to Nextcloud...')
|
||||||
|
const filePath = await this.upload(zip)
|
||||||
|
core.info(`Remote file path: ${filePath}`)
|
||||||
|
return await this.shareFile(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private uploadSpec(files: string[]): FileSpec[] {
|
||||||
|
const specifications = []
|
||||||
|
if (!fsSync.existsSync(this.rootDirectory)) {
|
||||||
|
throw new Error(`this.rootDirectory ${this.rootDirectory} does not exist`)
|
||||||
}
|
}
|
||||||
|
if (!fsSync.lstatSync(this.rootDirectory).isDirectory()) {
|
||||||
private uploadSpec(files: string[]): FileSpec[] {
|
throw new Error(`this.rootDirectory ${this.rootDirectory} is not a valid directory`)
|
||||||
const specifications = [];
|
|
||||||
if (!fsSync.existsSync(this.rootDirectory)) {
|
|
||||||
throw new Error(`this.rootDirectory ${this.rootDirectory} does not exist`);
|
|
||||||
}
|
|
||||||
if (!fsSync.lstatSync(this.rootDirectory).isDirectory()) {
|
|
||||||
throw new Error(`this.rootDirectory ${this.rootDirectory} is not a valid directory`);
|
|
||||||
}
|
|
||||||
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
|
||||||
let root = path.normalize(this.rootDirectory);
|
|
||||||
root = path.resolve(root);
|
|
||||||
/*
|
|
||||||
Example to demonstrate behavior
|
|
||||||
|
|
||||||
Input:
|
|
||||||
artifactName: my-artifact
|
|
||||||
rootDirectory: '/home/user/files/plz-upload'
|
|
||||||
artifactFiles: [
|
|
||||||
'/home/user/files/plz-upload/file1.txt',
|
|
||||||
'/home/user/files/plz-upload/file2.txt',
|
|
||||||
'/home/user/files/plz-upload/dir/file3.txt'
|
|
||||||
]
|
|
||||||
|
|
||||||
Output:
|
|
||||||
specifications: [
|
|
||||||
['/home/user/files/plz-upload/file1.txt', 'my-artifact/file1.txt'],
|
|
||||||
['/home/user/files/plz-upload/file1.txt', 'my-artifact/file2.txt'],
|
|
||||||
['/home/user/files/plz-upload/file1.txt', 'my-artifact/dir/file3.txt']
|
|
||||||
]
|
|
||||||
*/
|
|
||||||
for (let file of files) {
|
|
||||||
if (!fsSync.existsSync(file)) {
|
|
||||||
throw new Error(`File ${file} does not exist`);
|
|
||||||
}
|
|
||||||
if (!fsSync.lstatSync(file).isDirectory()) {
|
|
||||||
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
|
||||||
file = path.normalize(file);
|
|
||||||
file = path.resolve(file);
|
|
||||||
if (!file.startsWith(root)) {
|
|
||||||
throw new Error(`The rootDirectory: ${root} is not a parent directory of the file: ${file}`);
|
|
||||||
}
|
|
||||||
// Check for forbidden characters in file paths that will be rejected during upload
|
|
||||||
const uploadPath = file.replace(root, '');
|
|
||||||
/*
|
|
||||||
uploadFilePath denotes where the file will be uploaded in the file container on the server. During a run, if multiple artifacts are uploaded, they will all
|
|
||||||
be saved in the same container. The artifact name is used as the root directory in the container to separate and distinguish uploaded artifacts
|
|
||||||
|
|
||||||
path.join handles all the following cases and would return 'artifact-name/file-to-upload.txt
|
|
||||||
join('artifact-name/', 'file-to-upload.txt')
|
|
||||||
join('artifact-name/', '/file-to-upload.txt')
|
|
||||||
join('artifact-name', 'file-to-upload.txt')
|
|
||||||
join('artifact-name', '/file-to-upload.txt')
|
|
||||||
*/
|
|
||||||
specifications.push({
|
|
||||||
absolutePath: file,
|
|
||||||
uploadPath: path.join(this.artifact, uploadPath)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Directories are rejected by the server during upload
|
|
||||||
core.debug(`Removing ${file} from rawSearchResults because it is a directory`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return specifications;
|
|
||||||
}
|
}
|
||||||
|
let root = path.normalize(this.rootDirectory)
|
||||||
|
root = path.resolve(root)
|
||||||
private async zipFiles(specs: FileSpec[]): Promise<string> {
|
for (let file of files) {
|
||||||
const tempArtifactDir = path.join(os.tmpdir(), randomUUID());
|
if (!fsSync.existsSync(file)) {
|
||||||
const artifactPath = path.join(tempArtifactDir, `artifact-${this.artifact}`);
|
throw new Error(`File ${file} does not exist`)
|
||||||
await fs.mkdir(artifactPath, { recursive: true });
|
}
|
||||||
for (let spec of specs) {
|
if (!fsSync.lstatSync(file).isDirectory()) {
|
||||||
await fs.copyFile(spec.absolutePath, path.join(artifactPath, spec.uploadPath));
|
file = path.normalize(file)
|
||||||
|
file = path.resolve(file)
|
||||||
|
if (!file.startsWith(root)) {
|
||||||
|
throw new Error(`The rootDirectory: ${root} is not a parent directory of the file: ${file}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const archivePath = path.join(artifactPath, `${this.artifact}.zip`);
|
const uploadPath = file.replace(root, '')
|
||||||
await this.zip(path.join(artifactPath, this.artifact), archivePath);
|
specifications.push({
|
||||||
|
absolutePath: file,
|
||||||
|
uploadPath: path.join(this.artifact, uploadPath)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
core.debug(`Removing ${file} from rawSearchResults because it is a directory`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return specifications
|
||||||
|
}
|
||||||
|
|
||||||
return archivePath;
|
private async zipFiles(specs: FileSpec[]): Promise<string> {
|
||||||
|
const tempArtifactDir = path.join(os.tmpdir(), this.guid)
|
||||||
|
const artifactPath = path.join(tempArtifactDir, `artifact-${this.artifact}`)
|
||||||
|
await fs.mkdir(path.join(artifactPath, this.artifact), { recursive: true })
|
||||||
|
const copies = []
|
||||||
|
for (const spec of specs) {
|
||||||
|
const dstpath = path.join(artifactPath, spec.uploadPath)
|
||||||
|
const dstDir = path.dirname(dstpath)
|
||||||
|
if (!fsSync.existsSync(dstDir)) {
|
||||||
|
await fs.mkdir(dstDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
copies.push(fs.copyFile(spec.absolutePath, dstpath))
|
||||||
}
|
}
|
||||||
|
|
||||||
private async zip(dirpath: string, destpath: string) {
|
await Promise.all(copies)
|
||||||
const archive = archiver.create('zip', { zlib: { level: 9 } });
|
core.info(`files: ${await fs.readdir(path.join(artifactPath, this.artifact))}`)
|
||||||
const stream = fsSync.createWriteStream(destpath);
|
|
||||||
archive.directory(dirpath, false)
|
|
||||||
.on('error', e => Promise.reject())
|
|
||||||
.on('close', () => Promise.resolve())
|
|
||||||
.pipe(stream);
|
|
||||||
|
|
||||||
return archive.finalize();
|
const archivePath = path.join(artifactPath, `${this.artifact}.zip`)
|
||||||
|
await this.zip(path.join(artifactPath, this.artifact), archivePath)
|
||||||
|
|
||||||
|
return archivePath
|
||||||
|
}
|
||||||
|
|
||||||
|
private async zip(dirpath: string, destpath: string) {
|
||||||
|
const archive = archiver.create('zip', { zlib: { level: 9 } })
|
||||||
|
const stream = archive.directory(dirpath, false).pipe(fsSync.createWriteStream(destpath))
|
||||||
|
|
||||||
|
await archive.finalize()
|
||||||
|
|
||||||
|
return await new Promise<void>((resolve, reject) => {
|
||||||
|
stream.on('error', e => reject(e)).on('close', () => resolve())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async upload(file: string): Promise<string> {
|
||||||
|
const remoteFileDir = `/artifacts/${this.guid}`
|
||||||
|
if (!(await this.davClient.exists(remoteFileDir))) {
|
||||||
|
await this.davClient.createDirectory(remoteFileDir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
private async upload(file: string) {
|
const remoteFilePath = `${remoteFileDir}/${this.artifact}.zip`
|
||||||
const url = new URL(this.endpoint, '/remote.php/dav/files/user/path/to/file');
|
core.debug(`Transferring file... (${file})`)
|
||||||
const stream = fsSync.createReadStream(file);
|
|
||||||
await fetch(url.href, {
|
const fileStat = await fs.stat(file)
|
||||||
method: 'PUT',
|
const fileStream = fsSync.createReadStream(file)
|
||||||
body: stream
|
const fileStreamPromise = new Promise<void>((resolve, reject) => {
|
||||||
});
|
fileStream.on('error', e => reject(e)).on('close', () => resolve())
|
||||||
|
})
|
||||||
|
const remoteStream = this.davClient.createWriteStream(remoteFilePath, {
|
||||||
|
headers: { 'Content-Length': fileStat.size.toString() }
|
||||||
|
})
|
||||||
|
const remoteStreamPromise = new Promise<void>((resolve, reject) => {
|
||||||
|
remoteStream.on('error', e => reject(e)).on('finish', () => resolve())
|
||||||
|
})
|
||||||
|
|
||||||
|
fileStream.pipe(remoteStream)
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {}, 20_000)
|
||||||
|
await Promise.all([fileStreamPromise, remoteStreamPromise])
|
||||||
|
|
||||||
|
// HACK: Nextcloud has not fully processed the file, despite returning 200.
|
||||||
|
// Waiting for 1s seems to do the trick.
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1_000))
|
||||||
|
|
||||||
|
clearTimeout(timer)
|
||||||
|
return remoteFilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
private async shareFile(remoteFilePath: string): Promise<string> {
|
||||||
|
const url = `${this.endpoint.href}ocs/v2.php/apps/files_sharing/api/v1/shares`
|
||||||
|
const body = {
|
||||||
|
path: remoteFilePath,
|
||||||
|
shareType: 3,
|
||||||
|
publicUpload: 'false',
|
||||||
|
permissions: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private shareFile() {
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: Object.assign(this.headers, {
|
||||||
|
'OCS-APIRequest': true,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}),
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await res.text()
|
||||||
|
core.debug(`Share response: ${result}`)
|
||||||
|
const re = /<url>(?<share_url>.*)<\/url>/
|
||||||
|
const match = re.exec(result)
|
||||||
|
core.debug(`Match groups:\n${JSON.stringify(match?.groups)}`)
|
||||||
|
const sharableUrl = (match?.groups || {})['share_url']
|
||||||
|
if (!sharableUrl) {
|
||||||
|
throw new Error(`Failed to parse or find sharable URL:\n${result}`)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return sharableUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
"noImplicitAny": true,
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "dist",
|
"outDir": "lib",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"lib": ["es6"]
|
"lib": ["ES2019"]
|
||||||
}
|
},
|
||||||
|
"exclude": ["node_modules", "__tests__/**/*.ts"]
|
||||||
}
|
}
|
||||||
|
9
workspace.code-workspace
Normal file
9
workspace.code-workspace
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remoteAuthority": "wsl+Ubuntu-20.04",
|
||||||
|
"settings": {}
|
||||||
|
}
|
Reference in New Issue
Block a user