mirror of
https://github.com/trympet/nextcloud-artifacts-action.git
synced 2025-04-24 20:16:08 +02:00
Compare commits
85 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7c9fa20219 | ||
![]() |
3d18ab00c5 | ||
![]() |
20dadea389 | ||
![]() |
a496fd037d | ||
![]() |
61ff55deae | ||
![]() |
b357f186aa | ||
![]() |
dec03f56e0 | ||
![]() |
62e99ad6b2 | ||
![]() |
5825f9614a | ||
![]() |
e1264cbb56 | ||
![]() |
7d73f7e889 | ||
![]() |
81238124ba | ||
![]() |
935313b418 | ||
![]() |
995999b60c | ||
![]() |
cd4f651c13 | ||
![]() |
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 |
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
|
||||
}
|
||||
}
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
lib
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
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 output 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' # Nextcloud URL
|
||||
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()
|
13
action.yml
13
action.yml
@ -25,11 +25,10 @@ inputs:
|
||||
error: Fail the action with an error message
|
||||
ignore: Do not output any warnings or errors, the action does not fail
|
||||
default: 'warn'
|
||||
retention-days:
|
||||
description: >
|
||||
Duration after which artifact will expire in days. 0 means using default retention.
|
||||
Minimum 1 day.
|
||||
Maximum 90 days unless changed from the repository settings page.
|
||||
token:
|
||||
description: GitHub Access Token
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
|
151
dist/FileFinder.js
vendored
151
dist/FileFinder.js
vendored
@ -1,151 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
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());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FileFinder = void 0;
|
||||
const glob = __importStar(require("@actions/glob"));
|
||||
const fs_1 = require("fs");
|
||||
const core_1 = require("@actions/core");
|
||||
const path = __importStar(require("path"));
|
||||
const util_1 = require("util");
|
||||
const stats = util_1.promisify(fs_1.stat);
|
||||
class FileFinder {
|
||||
constructor(searchPath, globOptions) {
|
||||
this.searchPath = searchPath;
|
||||
this.globOptions = globOptions || FileFinder.DefaultGlobOptions;
|
||||
}
|
||||
findFiles() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const searchResults = [];
|
||||
const globber = yield glob.create(this.searchPath, this.globOptions);
|
||||
const rawSearchResults = yield globber.glob();
|
||||
/*
|
||||
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
|
||||
*/
|
||||
const set = new Set();
|
||||
/*
|
||||
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) {
|
||||
const fileStats = yield stats(searchResult);
|
||||
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
||||
if (!fileStats.isDirectory()) {
|
||||
core_1.debug(`File:${searchResult} was found using the provided searchPath`);
|
||||
searchResults.push(searchResult);
|
||||
// detect any files that would be overwritten because of case insensitivity
|
||||
if (set.has(searchResult.toLowerCase())) {
|
||||
core_1.info(`Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path`);
|
||||
}
|
||||
else {
|
||||
set.add(searchResult.toLowerCase());
|
||||
}
|
||||
}
|
||||
else {
|
||||
core_1.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`);
|
||||
}
|
||||
}
|
||||
// Calculate the root directory for the artifact using the search paths that were utilized
|
||||
const searchPaths = globber.getSearchPaths();
|
||||
if (searchPaths.length > 1) {
|
||||
core_1.info(`Multiple search paths detected. Calculating the least common ancestor of all paths`);
|
||||
const lcaSearchPath = this.getMultiPathLCA(searchPaths);
|
||||
core_1.info(`The least common ancestor is ${lcaSearchPath}. This will be the root directory of the artifact`);
|
||||
return {
|
||||
filesToUpload: searchResults,
|
||||
rootDirectory: lcaSearchPath
|
||||
};
|
||||
}
|
||||
/*
|
||||
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: path.dirname(searchResults[0])
|
||||
};
|
||||
}
|
||||
return {
|
||||
filesToUpload: searchResults,
|
||||
rootDirectory: searchPaths[0]
|
||||
};
|
||||
});
|
||||
}
|
||||
getMultiPathLCA(searchPaths) {
|
||||
if (searchPaths.length < 2) {
|
||||
throw new Error('At least two search paths must be provided');
|
||||
}
|
||||
const commonPaths = new Array();
|
||||
const splitPaths = new Array();
|
||||
let smallestPathLength = Number.MAX_SAFE_INTEGER;
|
||||
// split each of the search paths using the platform specific separator
|
||||
for (const searchPath of searchPaths) {
|
||||
core_1.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() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
exports.FileFinder = FileFinder;
|
||||
FileFinder.DefaultGlobOptions = {
|
||||
followSymbolicLinks: true,
|
||||
implicitDescendants: true,
|
||||
omitBrokenSymbolicLinks: true
|
||||
};
|
||||
//# sourceMappingURL=FileFinder.js.map
|
1
dist/FileFinder.js.map
vendored
1
dist/FileFinder.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"FileFinder.js","sourceRoot":"","sources":["../src/FileFinder.ts"],"names":[],"mappings":"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"}
|
38
dist/Inputs.js
vendored
38
dist/Inputs.js
vendored
@ -1,38 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Inputs = void 0;
|
||||
const core_1 = __importDefault(require("@actions/core"));
|
||||
const NoFileOption_1 = require("./NoFileOption");
|
||||
class Inputs {
|
||||
static get ArtifactName() {
|
||||
return core_1.default.getInput("name");
|
||||
}
|
||||
static get ArtifactPath() {
|
||||
return core_1.default.getInput("path");
|
||||
}
|
||||
static get Retention() {
|
||||
return core_1.default.getInput("retention-days");
|
||||
}
|
||||
static get Endpoint() {
|
||||
return core_1.default.getInput("nextcloud-url");
|
||||
}
|
||||
static get Username() {
|
||||
return core_1.default.getInput("nextcloud-username");
|
||||
}
|
||||
static get Password() {
|
||||
return core_1.default.getInput("nextcloud-password");
|
||||
}
|
||||
static get NoFileBehvaior() {
|
||||
const notFoundAction = core_1.default.getInput("if-no-files-found");
|
||||
const noFileBehavior = NoFileOption_1.NoFileOption[notFoundAction];
|
||||
if (!noFileBehavior) {
|
||||
core_1.default.setFailed(`Unrecognized ${"ifNoFilesFound"} input. Provided: ${notFoundAction}. Available options: ${Object.keys(NoFileOption_1.NoFileOption)}`);
|
||||
}
|
||||
return noFileBehavior;
|
||||
}
|
||||
}
|
||||
exports.Inputs = Inputs;
|
||||
//# sourceMappingURL=Inputs.js.map
|
1
dist/Inputs.js.map
vendored
1
dist/Inputs.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"Inputs.js","sourceRoot":"","sources":["../src/Inputs.ts"],"names":[],"mappings":";;;;;;AAAA,yDAAiC;AACjC,iDAA8C;AAE9C,MAAa,MAAM;IACf,MAAM,KAAK,YAAY;QACnB,OAAO,cAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,KAAK,YAAY;QACnB,OAAO,cAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,KAAK,SAAS;QAChB,OAAO,cAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,KAAK,QAAQ;QACf,OAAO,cAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,KAAK,QAAQ;QACf,OAAO,cAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,KAAK,QAAQ;QACf,OAAO,cAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,KAAK,cAAc;QACrB,MAAM,cAAc,GAAG,cAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QAC1D,MAAM,cAAc,GAAiB,2BAAY,CAAC,cAA2C,CAAC,CAAC;QAE/F,IAAI,CAAC,cAAc,EAAE;YACjB,cAAI,CAAC,SAAS,CACV,gBAAgB,gBAAgB,qBAAqB,cAAc,wBAAwB,MAAM,CAAC,IAAI,CAClG,2BAAY,CACf,EAAE,CACN,CAAC;SACL;QAED,OAAO,cAAc,CAAC;IAC1B,CAAC;CACJ;AAvCD,wBAuCC"}
|
73
dist/NextcloudArtifact.js
vendored
73
dist/NextcloudArtifact.js
vendored
@ -1,73 +0,0 @@
|
||||
"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 __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NextcloudArtifact = void 0;
|
||||
const core_1 = __importDefault(require("@actions/core"));
|
||||
const FileFinder_1 = require("./FileFinder");
|
||||
const Inputs_1 = require("./Inputs");
|
||||
const NextcloudClient_1 = require("./nextcloud/NextcloudClient");
|
||||
const NoFileOption_1 = require("./NoFileOption");
|
||||
class NextcloudArtifact {
|
||||
constructor(name, path, errorBehavior) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.errorBehavior = errorBehavior;
|
||||
}
|
||||
run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const fileFinder = new FileFinder_1.FileFinder(this.path);
|
||||
const files = yield fileFinder.findFiles();
|
||||
if (files.filesToUpload.length > 0) {
|
||||
yield this.uploadFiles(files);
|
||||
}
|
||||
else {
|
||||
this.logNoFilesFound();
|
||||
}
|
||||
});
|
||||
}
|
||||
uploadFiles(files) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.logUpload(files.filesToUpload.length, files.rootDirectory);
|
||||
const client = new NextcloudClient_1.NextcloudClient(Inputs_1.Inputs.endpoint, this.name, files.rootDirectory);
|
||||
yield client.uploadFiles(files.filesToUpload);
|
||||
});
|
||||
}
|
||||
logUpload(fileCount, rootDirectory) {
|
||||
const s = fileCount === 1 ? '' : 's';
|
||||
core_1.default.info(`With the provided path, there will be ${fileCount} file${s} uploaded`);
|
||||
core_1.default.debug(`Root artifact directory is ${rootDirectory}`);
|
||||
if (fileCount > 10000) {
|
||||
core_1.default.warning(`There are over 10,000 files in this artifact, consider create an archive before upload to improve the upload performance.`);
|
||||
}
|
||||
}
|
||||
logNoFilesFound() {
|
||||
const errorMessage = `No files were found with the provided path: ${this.path}. No artifacts will be uploaded.`;
|
||||
switch (this.errorBehavior) {
|
||||
case NoFileOption_1.NoFileOption.warn: {
|
||||
core_1.default.warning(errorMessage);
|
||||
break;
|
||||
}
|
||||
case NoFileOption_1.NoFileOption.error: {
|
||||
core_1.default.setFailed(errorMessage);
|
||||
break;
|
||||
}
|
||||
case NoFileOption_1.NoFileOption.ignore: {
|
||||
core_1.default.info(errorMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.NextcloudArtifact = NextcloudArtifact;
|
||||
//# sourceMappingURL=NextcloudArtifact.js.map
|
1
dist/NextcloudArtifact.js.map
vendored
1
dist/NextcloudArtifact.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"NextcloudArtifact.js","sourceRoot":"","sources":["../src/NextcloudArtifact.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,yDAAiC;AACjC,6CAA0C;AAC1C,qCAAkC;AAClC,iEAA8D;AAC9D,iDAA8C;AAE9C,MAAa,iBAAiB;IAC1B,YACY,IAAY,EACZ,IAAY,EACZ,aAA2B;QAF3B,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAQ;QACZ,kBAAa,GAAb,aAAa,CAAc;IAAI,CAAC;IAE/B,GAAG;;YACZ,MAAM,UAAU,GAAG,IAAI,uBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,CAAC;YAE3C,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE;gBAChC,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;aACjC;iBACI;gBACD,IAAI,CAAC,eAAe,EAAE,CAAC;aAC1B;QACL,CAAC;KAAA;IAEa,WAAW,CAAC,KAA0D;;YAChF,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;YAEhE,MAAM,MAAM,GAAG,IAAI,iCAAe,CAAC,eAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;YAEpF,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAClD,CAAC;KAAA;IAEO,SAAS,CAAC,SAAiB,EAAE,aAAqB;QACtD,MAAM,CAAC,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACrC,cAAI,CAAC,IAAI,CACL,yCAAyC,SAAS,QAAQ,CAAC,WAAW,CACzE,CAAC;QACF,cAAI,CAAC,KAAK,CAAC,8BAA8B,aAAa,EAAE,CAAC,CAAC;QAE1D,IAAI,SAAS,GAAG,KAAK,EAAE;YACnB,cAAI,CAAC,OAAO,CACR,2HAA2H,CAC9H,CAAC;SACL;IACL,CAAC;IAEO,eAAe;QACnB,MAAM,YAAY,GAAG,+CAA+C,IAAI,CAAC,IAAI,kCAAkC,CAAC;QAChH,QAAQ,IAAI,CAAC,aAAa,EAAE;YACxB,KAAK,2BAAY,CAAC,IAAI,CAAC,CAAC;gBACpB,cAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;gBAC3B,MAAM;aACT;YACD,KAAK,2BAAY,CAAC,KAAK,CAAC,CAAC;gBACrB,cAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;gBAC7B,MAAM;aACT;YACD,KAAK,2BAAY,CAAC,MAAM,CAAC,CAAC;gBACtB,cAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBACxB,MAAM;aACT;SACJ;IACL,CAAC;CACJ;AAzDD,8CAyDC"}
|
19
dist/NoFileOption.js
vendored
19
dist/NoFileOption.js
vendored
@ -1,19 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NoFileOption = void 0;
|
||||
var NoFileOption;
|
||||
(function (NoFileOption) {
|
||||
/**
|
||||
* Default. Output a warning but do not fail the action
|
||||
*/
|
||||
NoFileOption["warn"] = "warn";
|
||||
/**
|
||||
* Fail the action with an error message
|
||||
*/
|
||||
NoFileOption["error"] = "error";
|
||||
/**
|
||||
* Do not output any warnings or errors, the action does not fail
|
||||
*/
|
||||
NoFileOption["ignore"] = "ignore";
|
||||
})(NoFileOption = exports.NoFileOption || (exports.NoFileOption = {}));
|
||||
//# sourceMappingURL=NoFileOption.js.map
|
1
dist/NoFileOption.js.map
vendored
1
dist/NoFileOption.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"NoFileOption.js","sourceRoot":"","sources":["../src/NoFileOption.ts"],"names":[],"mappings":";;;AAAA,IAAY,YAeT;AAfH,WAAY,YAAY;IACpB;;OAEG;IACH,6BAAa,CAAA;IAEb;;OAEG;IACH,+BAAe,CAAA;IAEf;;OAEG;IACH,iCAAiB,CAAA;AACnB,CAAC,EAfS,YAAY,GAAZ,oBAAY,KAAZ,oBAAY,QAerB"}
|
45375
dist/index.js
vendored
45375
dist/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
51
dist/input-helper.js
vendored
51
dist/input-helper.js
vendored
@ -1,51 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getInputs = void 0;
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const constants_1 = require("./constants");
|
||||
/**
|
||||
* Helper to get all the inputs for the action
|
||||
*/
|
||||
function getInputs() {
|
||||
const name = core.getInput(constants_1.Inputs.Name);
|
||||
const path = core.getInput(constants_1.Inputs.Path, { required: true });
|
||||
const ifNoFilesFound = core.getInput(constants_1.Inputs.IfNoFilesFound);
|
||||
const noFileBehavior = constants_1.NoFileOptions[ifNoFilesFound];
|
||||
if (!noFileBehavior) {
|
||||
core.setFailed(`Unrecognized ${constants_1.Inputs.IfNoFilesFound} input. Provided: ${ifNoFilesFound}. Available options: ${Object.keys(constants_1.NoFileOptions)}`);
|
||||
}
|
||||
const inputs = {
|
||||
artifactName: name,
|
||||
searchPath: path,
|
||||
ifNoFilesFound: noFileBehavior
|
||||
};
|
||||
const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays);
|
||||
if (retentionDaysStr) {
|
||||
inputs.retentionDays = parseInt(retentionDaysStr);
|
||||
if (isNaN(inputs.retentionDays)) {
|
||||
core.setFailed('Invalid retention-days');
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
exports.getInputs = getInputs;
|
||||
//# sourceMappingURL=input-helper.js.map
|
1
dist/input-helper.js.map
vendored
1
dist/input-helper.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"input-helper.js","sourceRoot":"","sources":["../src/input-helper.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAqC;AACrC,2CAAiD;AAGjD;;GAEG;AACH,SAAgB,SAAS;IACvB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,kBAAM,CAAC,IAAI,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,kBAAM,CAAC,IAAI,EAAE,EAAC,QAAQ,EAAE,IAAI,EAAC,CAAC,CAAA;IAEzD,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,kBAAM,CAAC,cAAc,CAAC,CAAA;IAC3D,MAAM,cAAc,GAAkB,yBAAa,CAAC,cAAc,CAAC,CAAA;IAEnE,IAAI,CAAC,cAAc,EAAE;QACnB,IAAI,CAAC,SAAS,CACZ,gBACE,kBAAM,CAAC,cACT,qBAAqB,cAAc,wBAAwB,MAAM,CAAC,IAAI,CACpE,yBAAa,CACd,EAAE,CACJ,CAAA;KACF;IAED,MAAM,MAAM,GAAG;QACb,YAAY,EAAE,IAAI;QAClB,UAAU,EAAE,IAAI;QAChB,cAAc,EAAE,cAAc;KACf,CAAA;IAEjB,MAAM,gBAAgB,GAAG,IAAI,CAAC,QAAQ,CAAC,kBAAM,CAAC,aAAa,CAAC,CAAA;IAC5D,IAAI,gBAAgB,EAAE;QACpB,MAAM,CAAC,aAAa,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAA;QACjD,IAAI,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE;YAC/B,IAAI,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAA;SACzC;KACF;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAhCD,8BAgCC"}
|
2386
dist/licenses.txt
vendored
Normal file
2386
dist/licenses.txt
vendored
Normal file
File diff suppressed because it is too large
Load Diff
73
dist/nextcloud/NextcloudArtifact.js
vendored
73
dist/nextcloud/NextcloudArtifact.js
vendored
@ -1,73 +0,0 @@
|
||||
"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 __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NextcloudArtifact = void 0;
|
||||
const core_1 = __importDefault(require("@actions/core"));
|
||||
const FileFinder_1 = require("../FileFinder");
|
||||
const Inputs_1 = require("../Inputs");
|
||||
const NextcloudClient_1 = require("./NextcloudClient");
|
||||
const NoFileOption_1 = require("../NoFileOption");
|
||||
class NextcloudArtifact {
|
||||
constructor(name, path, errorBehavior) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.errorBehavior = errorBehavior;
|
||||
}
|
||||
run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const fileFinder = new FileFinder_1.FileFinder(this.path);
|
||||
const files = yield fileFinder.findFiles();
|
||||
if (files.filesToUpload.length > 0) {
|
||||
yield this.uploadFiles(files);
|
||||
}
|
||||
else {
|
||||
this.logNoFilesFound();
|
||||
}
|
||||
});
|
||||
}
|
||||
uploadFiles(files) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.logUpload(files.filesToUpload.length, files.rootDirectory);
|
||||
const client = new NextcloudClient_1.NextcloudClient(Inputs_1.Inputs.Endpoint, this.name, files.rootDirectory);
|
||||
yield client.uploadFiles(files.filesToUpload);
|
||||
});
|
||||
}
|
||||
logUpload(fileCount, rootDirectory) {
|
||||
const s = fileCount === 1 ? '' : 's';
|
||||
core_1.default.info(`With the provided path, there will be ${fileCount} file${s} uploaded`);
|
||||
core_1.default.debug(`Root artifact directory is ${rootDirectory}`);
|
||||
if (fileCount > 10000) {
|
||||
core_1.default.warning(`There are over 10,000 files in this artifact, consider create an archive before upload to improve the upload performance.`);
|
||||
}
|
||||
}
|
||||
logNoFilesFound() {
|
||||
const errorMessage = `No files were found with the provided path: ${this.path}. No artifacts will be uploaded.`;
|
||||
switch (this.errorBehavior) {
|
||||
case NoFileOption_1.NoFileOption.warn: {
|
||||
core_1.default.warning(errorMessage);
|
||||
break;
|
||||
}
|
||||
case NoFileOption_1.NoFileOption.error: {
|
||||
core_1.default.setFailed(errorMessage);
|
||||
break;
|
||||
}
|
||||
case NoFileOption_1.NoFileOption.ignore: {
|
||||
core_1.default.info(errorMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.NextcloudArtifact = NextcloudArtifact;
|
||||
//# sourceMappingURL=NextcloudArtifact.js.map
|
1
dist/nextcloud/NextcloudArtifact.js.map
vendored
1
dist/nextcloud/NextcloudArtifact.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"NextcloudArtifact.js","sourceRoot":"","sources":["../../src/nextcloud/NextcloudArtifact.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,yDAAiC;AACjC,8CAA2C;AAC3C,sCAAmC;AACnC,uDAAoD;AACpD,kDAA+C;AAE/C,MAAa,iBAAiB;IAC1B,YACY,IAAY,EACZ,IAAY,EACZ,aAA2B;QAF3B,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAQ;QACZ,kBAAa,GAAb,aAAa,CAAc;IAAI,CAAC;IAE/B,GAAG;;YACZ,MAAM,UAAU,GAAG,IAAI,uBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,CAAC;YAE3C,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE;gBAChC,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;aACjC;iBACI;gBACD,IAAI,CAAC,eAAe,EAAE,CAAC;aAC1B;QACL,CAAC;KAAA;IAEa,WAAW,CAAC,KAA0D;;YAChF,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;YAEhE,MAAM,MAAM,GAAG,IAAI,iCAAe,CAAC,eAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;YAEpF,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAClD,CAAC;KAAA;IAEO,SAAS,CAAC,SAAiB,EAAE,aAAqB;QACtD,MAAM,CAAC,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACrC,cAAI,CAAC,IAAI,CACL,yCAAyC,SAAS,QAAQ,CAAC,WAAW,CACzE,CAAC;QACF,cAAI,CAAC,KAAK,CAAC,8BAA8B,aAAa,EAAE,CAAC,CAAC;QAE1D,IAAI,SAAS,GAAG,KAAK,EAAE;YACnB,cAAI,CAAC,OAAO,CACR,2HAA2H,CAC9H,CAAC;SACL;IACL,CAAC;IAEO,eAAe;QACnB,MAAM,YAAY,GAAG,+CAA+C,IAAI,CAAC,IAAI,kCAAkC,CAAC;QAChH,QAAQ,IAAI,CAAC,aAAa,EAAE;YACxB,KAAK,2BAAY,CAAC,IAAI,CAAC,CAAC;gBACpB,cAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;gBAC3B,MAAM;aACT;YACD,KAAK,2BAAY,CAAC,KAAK,CAAC,CAAC;gBACrB,cAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;gBAC7B,MAAM;aACT;YACD,KAAK,2BAAY,CAAC,MAAM,CAAC,CAAC;gBACtB,cAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBACxB,MAAM;aACT;SACJ;IACL,CAAC;CACJ;AAzDD,8CAyDC"}
|
183
dist/nextcloud/NextcloudClient.js
vendored
183
dist/nextcloud/NextcloudClient.js
vendored
@ -1,183 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
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 __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NextcloudClient = void 0;
|
||||
const fsSync = __importStar(require("fs"));
|
||||
const fs = __importStar(require("fs/promises"));
|
||||
const path = __importStar(require("path"));
|
||||
const core_1 = __importDefault(require("@actions/core"));
|
||||
const os = __importStar(require("os"));
|
||||
const crypto_1 = require("crypto");
|
||||
const archiver = __importStar(require("archiver"));
|
||||
const node_fetch_1 = __importDefault(require("node-fetch"));
|
||||
const Inputs_1 = require("../Inputs");
|
||||
const btoa_1 = __importDefault(require("btoa"));
|
||||
class NextcloudClient {
|
||||
constructor(endpoint, artifact, rootDirectory) {
|
||||
this.endpoint = endpoint;
|
||||
this.artifact = artifact;
|
||||
this.rootDirectory = rootDirectory;
|
||||
this.guid = crypto_1.randomUUID();
|
||||
this.headers = { 'Authorization': 'Basic ' + btoa_1.default(`${Inputs_1.Inputs.Username}:${Inputs_1.Inputs.Password}`) };
|
||||
}
|
||||
uploadFiles(files) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const spec = this.uploadSpec(files);
|
||||
var zip = yield this.zipFiles(spec);
|
||||
const path = yield this.upload(zip);
|
||||
yield this.shareFile(path);
|
||||
});
|
||||
}
|
||||
uploadSpec(files) {
|
||||
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_1.default.debug(`Removing ${file} from rawSearchResults because it is a directory`);
|
||||
}
|
||||
}
|
||||
return specifications;
|
||||
}
|
||||
zipFiles(specs) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const tempArtifactDir = path.join(os.tmpdir(), this.guid);
|
||||
const artifactPath = path.join(tempArtifactDir, `artifact-${this.artifact}`);
|
||||
yield fs.mkdir(artifactPath, { recursive: true });
|
||||
for (let spec of specs) {
|
||||
yield fs.copyFile(spec.absolutePath, path.join(artifactPath, spec.uploadPath));
|
||||
}
|
||||
const archivePath = path.join(artifactPath, `${this.artifact}.zip`);
|
||||
yield this.zip(path.join(artifactPath, this.artifact), archivePath);
|
||||
return archivePath;
|
||||
});
|
||||
}
|
||||
zip(dirpath, destpath) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const archive = archiver.create('zip', { zlib: { level: 9 } });
|
||||
const stream = fsSync.createWriteStream(destpath);
|
||||
archive.directory(dirpath, false)
|
||||
.on('error', e => Promise.reject())
|
||||
.on('close', () => Promise.resolve())
|
||||
.pipe(stream);
|
||||
return archive.finalize();
|
||||
});
|
||||
}
|
||||
upload(file) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const filePath = `/artifacts/${this.guid}/${this.artifact}`;
|
||||
const url = this.endpoint + `/remote.php/dav/files/${Inputs_1.Inputs.Username}` + filePath;
|
||||
const stream = fsSync.createReadStream(file);
|
||||
const res = yield node_fetch_1.default(url, {
|
||||
method: 'PUT',
|
||||
body: stream,
|
||||
headers: this.headers
|
||||
});
|
||||
core_1.default.debug(yield res.json());
|
||||
return filePath;
|
||||
});
|
||||
}
|
||||
shareFile(nextcloudPath) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const url = this.endpoint + `/ocs/v2.php/apps/files_sharing/api/v1/shares`;
|
||||
const body = {
|
||||
path: nextcloudPath,
|
||||
shareType: 3,
|
||||
publicUpload: "false",
|
||||
permissions: 1,
|
||||
};
|
||||
const res = yield node_fetch_1.default(url, {
|
||||
method: 'PUT',
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
core_1.default.debug(yield res.json());
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.NextcloudClient = NextcloudClient;
|
||||
//# sourceMappingURL=NextcloudClient.js.map
|
1
dist/nextcloud/NextcloudClient.js.map
vendored
1
dist/nextcloud/NextcloudClient.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"NextcloudClient.js","sourceRoot":"","sources":["../../src/nextcloud/NextcloudClient.ts"],"names":[],"mappings":"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"}
|
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
3
dist/upload-inputs.js
vendored
3
dist/upload-inputs.js
vendored
@ -1,3 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=upload-inputs.js.map
|
1
dist/upload-inputs.js.map
vendored
1
dist/upload-inputs.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"upload-inputs.js","sourceRoot":"","sources":["../src/upload-inputs.ts"],"names":[],"mappings":""}
|
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"]
|
||||
}
|
6519
package-lock.json
generated
6519
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@ -1,34 +1,70 @@
|
||||
{
|
||||
"name": "nextcloud-artifacts-action",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "lib/nextcloud-artifacts.js",
|
||||
"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": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/trympet/nextcloud-artifacts-action.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"author": "Trym Lund Flogard <trym@flogard.no>",
|
||||
"license": "GPL-2.0-only",
|
||||
"bugs": {
|
||||
"url": "https://github.com/trympet/nextcloud-artifacts-action/issues"
|
||||
},
|
||||
"homepage": "https://github.com/trympet/nextcloud-artifacts-action#readme",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.3.0",
|
||||
"@actions/exec": "^1.0.4",
|
||||
"@actions/github": "^5.0.0",
|
||||
"@actions/glob": "^0.1.2",
|
||||
"archiver": "^5.3.0",
|
||||
"btoa": "^1.2.1",
|
||||
"node-fetch": "^2.6.1"
|
||||
"node-fetch": "^2.6.1",
|
||||
"uuid": "^8.3.2",
|
||||
"webdav": "^4.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/types": "^6.16.2",
|
||||
"@octokit/webhooks": "^9.6.3",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/btoa": "^1.2.3",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.6.2",
|
||||
"@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"
|
||||
},
|
||||
"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 {stat} from 'fs'
|
||||
import {debug, info} from '@actions/core'
|
||||
import { stat } from 'fs'
|
||||
import { debug, info } from '@actions/core'
|
||||
import * as path from 'path'
|
||||
import {promisify} from 'util'
|
||||
import { promisify } from 'util'
|
||||
const stats = promisify(stat)
|
||||
|
||||
export class FileFinder {
|
||||
private static DefaultGlobOptions: glob.GlobOptions = {
|
||||
followSymbolicLinks: true,
|
||||
implicitDescendants: true,
|
||||
omitBrokenSymbolicLinks: true
|
||||
};
|
||||
private static DefaultGlobOptions: glob.GlobOptions = {
|
||||
followSymbolicLinks: true,
|
||||
implicitDescendants: true,
|
||||
omitBrokenSymbolicLinks: true
|
||||
}
|
||||
|
||||
private globOptions: glob.GlobOptions
|
||||
private globOptions: glob.GlobOptions
|
||||
|
||||
public constructor(private searchPath: string, globOptions?: glob.GlobOptions) {
|
||||
this.globOptions = globOptions || FileFinder.DefaultGlobOptions;
|
||||
}
|
||||
constructor(private searchPath: string, globOptions?: glob.GlobOptions) {
|
||||
this.globOptions = globOptions || FileFinder.DefaultGlobOptions
|
||||
}
|
||||
|
||||
public async findFiles() {
|
||||
const searchResults: string[] = []
|
||||
const globber = await glob.create(
|
||||
this.searchPath,
|
||||
this.globOptions
|
||||
);
|
||||
async findFiles() {
|
||||
const searchResults: string[] = []
|
||||
const globber = await glob.create(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
|
||||
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 so filter any directories out from the raw search results
|
||||
*/
|
||||
for (const searchResult of rawSearchResults) {
|
||||
const fileStats = await stats(searchResult)
|
||||
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
||||
if (!fileStats.isDirectory()) {
|
||||
debug(`File:${searchResult} was found using the provided searchPath`)
|
||||
searchResults.push(searchResult)
|
||||
for (const searchResult of rawSearchResults) {
|
||||
const fileStats = await stats(searchResult)
|
||||
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
|
||||
if (!fileStats.isDirectory()) {
|
||||
debug(`File:${searchResult} was found using the provided searchPath`)
|
||||
searchResults.push(searchResult)
|
||||
|
||||
// detect any files that would be overwritten because of case insensitivity
|
||||
if (set.has(searchResult.toLowerCase())) {
|
||||
info(
|
||||
`Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path`
|
||||
)
|
||||
} else {
|
||||
set.add(searchResult.toLowerCase())
|
||||
}
|
||||
} else {
|
||||
debug(
|
||||
`Removing ${searchResult} from rawSearchResults because it is a directory`
|
||||
)
|
||||
}
|
||||
// detect any files that would be overwritten because of case insensitivity
|
||||
if (set.has(searchResult.toLowerCase())) {
|
||||
info(
|
||||
`Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path`
|
||||
)
|
||||
} else {
|
||||
set.add(searchResult.toLowerCase())
|
||||
}
|
||||
} 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
|
||||
const searchPaths: string[] = globber.getSearchPaths()
|
||||
// Calculate the root directory for the artifact using the search paths that were utilized
|
||||
const searchPaths: string[] = globber.getSearchPaths()
|
||||
|
||||
if (searchPaths.length > 1) {
|
||||
info(
|
||||
`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`
|
||||
)
|
||||
if (searchPaths.length > 1) {
|
||||
info(`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`)
|
||||
|
||||
return {
|
||||
filesToUpload: searchResults,
|
||||
rootDirectory: lcaSearchPath
|
||||
}
|
||||
}
|
||||
return {
|
||||
filesToUpload: searchResults,
|
||||
rootDirectory: lcaSearchPath
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
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: path.dirname(searchResults[0])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
filesToUpload: searchResults,
|
||||
rootDirectory: searchPaths[0]
|
||||
}
|
||||
if (searchResults.length === 1 && searchPaths[0] === searchResults[0]) {
|
||||
return {
|
||||
filesToUpload: searchResults,
|
||||
rootDirectory: path.dirname(searchResults[0])
|
||||
}
|
||||
}
|
||||
|
||||
private getMultiPathLCA(searchPaths: string[]): string {
|
||||
if (searchPaths.length < 2) {
|
||||
throw new Error('At least two search paths must be provided')
|
||||
return {
|
||||
filesToUpload: searchResults,
|
||||
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,43 +1,18 @@
|
||||
import core from '@actions/core';
|
||||
import { NoFileOption } from './NoFileOption';
|
||||
import { URL } from 'url'
|
||||
import { NoFileOption } from './NoFileOption'
|
||||
|
||||
export class Inputs {
|
||||
static get ArtifactName(): string {
|
||||
return core.getInput("name");
|
||||
}
|
||||
export interface Inputs {
|
||||
readonly ArtifactName: string
|
||||
|
||||
static get ArtifactPath(): string {
|
||||
return core.getInput("path");
|
||||
}
|
||||
readonly ArtifactPath: string
|
||||
|
||||
static get Retention(): string {
|
||||
return core.getInput("retention-days");
|
||||
}
|
||||
readonly Endpoint: URL
|
||||
|
||||
static get Endpoint(): string {
|
||||
return core.getInput("nextcloud-url");
|
||||
}
|
||||
readonly Username: string
|
||||
|
||||
static get Username(): string {
|
||||
return core.getInput("nextcloud-username");
|
||||
}
|
||||
readonly Password: string
|
||||
|
||||
static get Password(): string {
|
||||
return core.getInput("nextcloud-password");
|
||||
}
|
||||
readonly Token: string
|
||||
|
||||
static get NoFileBehvaior(): NoFileOption {
|
||||
const notFoundAction = core.getInput("if-no-files-found");
|
||||
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;
|
||||
}
|
||||
readonly NoFileBehvaior: NoFileOption
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
export enum NoFileOption {
|
||||
/**
|
||||
* Default. Output a warning but do not fail the action
|
||||
*/
|
||||
warn = 'warn',
|
||||
|
||||
/**
|
||||
* Fail the action with an error message
|
||||
*/
|
||||
error = 'error',
|
||||
|
||||
/**
|
||||
* Do not output any warnings or errors, the action does not fail
|
||||
*/
|
||||
ignore = 'ignore',
|
||||
}
|
||||
/**
|
||||
* Default. Output a warning but do not fail the action
|
||||
*/
|
||||
warn = 'warn',
|
||||
|
||||
/**
|
||||
* Fail the action with an error message
|
||||
*/
|
||||
error = 'error',
|
||||
|
||||
/**
|
||||
* Do not output any warnings or errors, the action does not fail
|
||||
*/
|
||||
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,155 @@
|
||||
import core from '@actions/core';
|
||||
import { FileFinder } from '../FileFinder';
|
||||
import { Inputs } from '../Inputs';
|
||||
import { NextcloudClient } from './NextcloudClient';
|
||||
import { NoFileOption } from '../NoFileOption';
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import { GitHub } from '@actions/github/lib/utils'
|
||||
|
||||
import { FileFinder } from '../FileFinder'
|
||||
import { Inputs } from '../Inputs'
|
||||
import { NextcloudClient } from './NextcloudClient'
|
||||
import { NoFileOption } from '../NoFileOption'
|
||||
|
||||
export class NextcloudArtifact {
|
||||
public constructor(
|
||||
private name: string,
|
||||
private path: string,
|
||||
private errorBehavior: NoFileOption) { }
|
||||
readonly octokit: InstanceType<typeof GitHub>
|
||||
readonly context = NextcloudArtifact.getCheckRunContext()
|
||||
readonly token: string
|
||||
readonly name: string
|
||||
readonly artifactTitle: string
|
||||
readonly path: string
|
||||
readonly errorBehavior: NoFileOption
|
||||
|
||||
public async run() {
|
||||
const fileFinder = new FileFinder(this.path);
|
||||
const files = await fileFinder.findFiles();
|
||||
constructor(private inputs: Inputs) {
|
||||
this.token = inputs.Token
|
||||
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) {
|
||||
await this.uploadFiles(files);
|
||||
}
|
||||
else {
|
||||
this.logNoFilesFound();
|
||||
}
|
||||
async run() {
|
||||
const fileFinder = new FileFinder(this.path)
|
||||
const files = await fileFinder.findFiles()
|
||||
|
||||
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; }) {
|
||||
this.logUpload(files.filesToUpload.length, files.rootDirectory);
|
||||
|
||||
const client = new NextcloudClient(Inputs.Endpoint, this.name, files.rootDirectory);
|
||||
|
||||
await client.uploadFiles(files.filesToUpload);
|
||||
const runId = github.context.runId
|
||||
if (github.context.payload.pull_request) {
|
||||
core.info(`Action was triggered by ${github.context.eventName}: using SHA from head of source branch`)
|
||||
const pr = github.context.payload.pull_request
|
||||
return { sha: pr.head.sha, runId }
|
||||
}
|
||||
|
||||
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}`);
|
||||
return { sha: github.context.sha, runId }
|
||||
}
|
||||
|
||||
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 async uploadFiles(files: { filesToUpload: string[]; rootDirectory: string }) {
|
||||
this.logUpload(files.filesToUpload.length, files.rootDirectory)
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
const client = new NextcloudClient(
|
||||
this.inputs.Endpoint,
|
||||
this.name,
|
||||
files.rootDirectory,
|
||||
this.inputs.Username,
|
||||
this.inputs.Password
|
||||
)
|
||||
|
||||
try {
|
||||
const shareableUrl = await client.uploadFiles(files.filesToUpload)
|
||||
core.setOutput('SHAREABLE_URL', shareableUrl)
|
||||
core.info(`Nextcloud shareable URL: ${shareableUrl}`)
|
||||
const resp = await this.octokit.rest.checks.update({
|
||||
check_run_id: createResp.data.id,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,160 +1,167 @@
|
||||
import * as fsSync from 'fs'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as path from 'path'
|
||||
import core from '@actions/core';
|
||||
import * as os from 'os';
|
||||
import { randomUUID } from 'crypto';
|
||||
import * as archiver from 'archiver';
|
||||
import { URL } from 'url';
|
||||
import fetch, { HeadersInit } from 'node-fetch';
|
||||
import { Inputs } from '../Inputs';
|
||||
import btoa from 'btoa';
|
||||
import * as core from '@actions/core'
|
||||
import * as os from 'os'
|
||||
import * as archiver from 'archiver'
|
||||
import fetch, { HeadersInit } from 'node-fetch'
|
||||
import btoa from 'btoa'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as webdav from 'webdav'
|
||||
import { URL } from 'url'
|
||||
|
||||
const fs = fsSync.promises
|
||||
|
||||
interface FileSpec {
|
||||
absolutePath: string,
|
||||
uploadPath: string
|
||||
absolutePath: string
|
||||
uploadPath: string
|
||||
}
|
||||
|
||||
export class NextcloudClient {
|
||||
private guid: string;
|
||||
private headers: HeadersInit;
|
||||
private guid: string
|
||||
private headers: HeadersInit
|
||||
private davClient
|
||||
|
||||
public constructor(
|
||||
private endpoint: string,
|
||||
private artifact: string,
|
||||
private rootDirectory: string) {
|
||||
this.guid = randomUUID();
|
||||
this.headers = {'Authorization': 'Basic ' + btoa(`${Inputs.Username}:${Inputs.Password}`)};
|
||||
constructor(
|
||||
private endpoint: URL,
|
||||
private artifact: string,
|
||||
private rootDirectory: string,
|
||||
private username: string,
|
||||
private password: string
|
||||
) {
|
||||
this.guid = uuidv4()
|
||||
this.headers = { Authorization: 'Basic ' + Buffer.from(`${this.username}:${this.password}`).toString('base64') }
|
||||
this.davClient = webdav.createClient(`${this.endpoint.href}remote.php/dav/files/${this.username}`, {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
maxBodyLength: 1024 ** 3
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
try {
|
||||
core.info('Uploading to Nextcloud...')
|
||||
const filePath = await this.upload(zip)
|
||||
core.info(`Remote file path: ${filePath}`)
|
||||
return await this.shareFile(filePath)
|
||||
} finally {
|
||||
await fs.unlink(zip)
|
||||
}
|
||||
}
|
||||
|
||||
public async uploadFiles(files: string[]) {
|
||||
const spec = this.uploadSpec(files);
|
||||
var zip = await this.zipFiles(spec);
|
||||
const path = await this.upload(zip);
|
||||
await this.shareFile(path);
|
||||
private uploadSpec(files: string[]): FileSpec[] {
|
||||
const specifications = []
|
||||
if (!fsSync.existsSync(this.rootDirectory)) {
|
||||
throw new Error(`this.rootDirectory ${this.rootDirectory} does not exist`)
|
||||
}
|
||||
|
||||
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()) {
|
||||
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;
|
||||
if (!fsSync.lstatSync(this.rootDirectory).isDirectory()) {
|
||||
throw new Error(`this.rootDirectory ${this.rootDirectory} is not a valid directory`)
|
||||
}
|
||||
|
||||
|
||||
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(artifactPath, { recursive: true });
|
||||
for (let spec of specs) {
|
||||
await fs.copyFile(spec.absolutePath, path.join(artifactPath, spec.uploadPath));
|
||||
let root = path.normalize(this.rootDirectory)
|
||||
root = path.resolve(root)
|
||||
for (let file of files) {
|
||||
if (!fsSync.existsSync(file)) {
|
||||
throw new Error(`File ${file} does not exist`)
|
||||
}
|
||||
if (!fsSync.lstatSync(file).isDirectory()) {
|
||||
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`);
|
||||
await this.zip(path.join(artifactPath, this.artifact), archivePath);
|
||||
const uploadPath = file.replace(root, '')
|
||||
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) {
|
||||
const archive = archiver.create('zip', { zlib: { level: 9 } });
|
||||
const stream = fsSync.createWriteStream(destpath);
|
||||
archive.directory(dirpath, false)
|
||||
.on('error', e => Promise.reject())
|
||||
.on('close', () => Promise.resolve())
|
||||
.pipe(stream);
|
||||
await Promise.all(copies)
|
||||
core.info(`files: ${await fs.readdir(path.join(artifactPath, this.artifact))}`)
|
||||
|
||||
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 filePath = `/artifacts/${this.guid}/${this.artifact}`;
|
||||
const url = this.endpoint + `/remote.php/dav/files/${Inputs.Username}` + filePath;
|
||||
const stream = fsSync.createReadStream(file);
|
||||
const res = await fetch(url, {
|
||||
method: 'PUT',
|
||||
body: stream,
|
||||
headers: this.headers
|
||||
});
|
||||
core.debug(await res.json())
|
||||
const remoteFilePath = `${remoteFileDir}/${this.artifact}.zip`
|
||||
core.debug(`Transferring file... (${file})`)
|
||||
|
||||
return filePath;
|
||||
await this.davClient.putFileContents(remoteFilePath, await fs.readFile(file))
|
||||
|
||||
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 async shareFile(nextcloudPath: string) {
|
||||
const url = this.endpoint + `/ocs/v2.php/apps/files_sharing/api/v1/shares`;
|
||||
const body = {
|
||||
path: nextcloudPath,
|
||||
shareType: 3,
|
||||
publicUpload: "false",
|
||||
permissions: 1,
|
||||
};
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: Object.assign(this.headers, {
|
||||
'OCS-APIRequest': true,
|
||||
'Content-Type': 'application/json'
|
||||
}),
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
core.debug(await res.json())
|
||||
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": {
|
||||
"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'. */
|
||||
"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'. */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||
"noImplicitAny": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"outDir": "lib",
|
||||
"sourceMap": true,
|
||||
"lib": ["es6"]
|
||||
}
|
||||
"lib": ["ES2019"]
|
||||
},
|
||||
"exclude": ["node_modules", "__tests__/**/*.ts"]
|
||||
}
|
||||
|
8
workspace.code-workspace
Normal file
8
workspace.code-workspace
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user