From 5705c5cf8ad55cf9dc770c89479efcd9a492ffed Mon Sep 17 00:00:00 2001 From: Trym Lund Flogard Date: Wed, 2 Jun 2021 15:19:19 +0200 Subject: [PATCH] add nextcloud support --- package-lock.json | 449 +++++++++++++++++++++++++++++ package.json | 8 +- src/FileFinder.ts | 146 ++++++++++ src/Inputs.ts | 35 +++ src/NoFileOption.ts | 16 + src/index.ts | 6 +- src/nextcloud/NextcloudArtifact.ts | 64 ++++ src/nextcloud/NextcloudClient.ts | 132 +++++++++ tsconfig.json | 5 +- 9 files changed, 857 insertions(+), 4 deletions(-) create mode 100644 src/FileFinder.ts create mode 100644 src/Inputs.ts create mode 100644 src/NoFileOption.ts create mode 100644 src/nextcloud/NextcloudArtifact.ts create mode 100644 src/nextcloud/NextcloudClient.ts diff --git a/package-lock.json b/package-lock.json index 9273884..d4a22dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,460 @@ "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.3.0.tgz", "integrity": "sha512-xxtX0Cwdhb8LcgatfJkokqT8KzPvcIbwL9xpLU09nOwBzaStbfm0dNncsP0M4us+EpoPdWy7vbzU5vSOH7K6pg==" }, + "@actions/glob": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.1.2.tgz", + "integrity": "sha512-SclLR7Ia5sEqjkJTPs7Sd86maMDw43p769YxBOxvPvEWuPEhpAnBsQfENOpXjFYMmhCqd127bmf+YdvJqVqR4A==", + "requires": { + "@actions/core": "^1.2.6", + "minimatch": "^3.0.4" + } + }, + "@types/archiver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.0.tgz", + "integrity": "sha512-baFOhanb/hxmcOd1Uey2TfFg43kTSmM6py1Eo7Rjbv/ivcl7PXLhY0QgXGf50Hx/eskGCFqPfhs/7IZLb15C5g==", + "dev": true, + "requires": { + "@types/glob": "*" + } + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", + "dev": true + }, + "@types/node": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.2.tgz", + "integrity": "sha512-dxcOx8801kMo3KlU+C+/ctWrzREAH7YvoF3aoVpRdqgs+Kf7flp+PJDN/EX5bME3suDUZHsxes9hpvBmzYlWbA==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.10.tgz", + "integrity": "sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "requires": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + } + }, + "archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "requires": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "requires": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } + }, + "crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "dev": true, + "requires": { + "mime-db": "1.48.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, "typescript": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "requires": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + } } } } diff --git a/package.json b/package.json index c8acbf5..85d6f21 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,15 @@ }, "homepage": "https://github.com/trympet/nextcloud-artifacts-action#readme", "dependencies": { - "@actions/core": "^1.3.0" + "@actions/core": "^1.3.0", + "@actions/glob": "^0.1.2", + "archiver": "^5.3.0", + "node-fetch": "^2.6.1" }, "devDependencies": { + "@types/archiver": "^5.1.0", + "@types/node": "^15.6.2", + "@types/node-fetch": "^2.5.10", "typescript": "^4.3.2" } } diff --git a/src/FileFinder.ts b/src/FileFinder.ts new file mode 100644 index 0000000..b382d27 --- /dev/null +++ b/src/FileFinder.ts @@ -0,0 +1,146 @@ +import * as glob from '@actions/glob' +import {stat} from 'fs' +import {debug, info} from '@actions/core' +import * as path from 'path' +import {promisify} from 'util' +const stats = promisify(stat) + +export class FileFinder { + private static DefaultGlobOptions: glob.GlobOptions = { + followSymbolicLinks: true, + implicitDescendants: true, + omitBrokenSymbolicLinks: true + }; + + private globOptions: glob.GlobOptions + + public 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 + ); + + 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() + + /* + 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) + + // 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() + + 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 + } + } + + /* + 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] + } + } + + private getMultiPathLCA(searchPaths: string[]): string { + 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) { + 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) + } +} \ No newline at end of file diff --git a/src/Inputs.ts b/src/Inputs.ts new file mode 100644 index 0000000..e9fcd64 --- /dev/null +++ b/src/Inputs.ts @@ -0,0 +1,35 @@ +import core from '@actions/core'; +import { NoFileOption } from './NoFileOption'; + +export class Inputs { + static get ArtifactName(): string { + return core.getInput("name"); + } + + static get ArtifactPath(): string { + return core.getInput("path"); + } + + static get Retention(): string { + return core.getInput("retention-days"); + } + + static get Endpoint(): string { + return core.getInput("retention-days"); + } + + 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; + } +} diff --git a/src/NoFileOption.ts b/src/NoFileOption.ts new file mode 100644 index 0000000..52d9c1a --- /dev/null +++ b/src/NoFileOption.ts @@ -0,0 +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', + } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 0f50426..3a069c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,5 @@ -console.log("hello world"); \ No newline at end of file +import { Inputs } from './Inputs'; +import { NextcloudArtifact } from './nextcloud/NextcloudArtifact'; + +var artifact = new NextcloudArtifact(Inputs.ArtifactName, Inputs.ArtifactPath, Inputs.NoFileBehvaior); +artifact.run(); \ No newline at end of file diff --git a/src/nextcloud/NextcloudArtifact.ts b/src/nextcloud/NextcloudArtifact.ts new file mode 100644 index 0000000..54f6d80 --- /dev/null +++ b/src/nextcloud/NextcloudArtifact.ts @@ -0,0 +1,64 @@ +import core from '@actions/core'; +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) { } + + public 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 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); + } + + 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; + } + } + } +} diff --git a/src/nextcloud/NextcloudClient.ts b/src/nextcloud/NextcloudClient.ts new file mode 100644 index 0000000..4516789 --- /dev/null +++ b/src/nextcloud/NextcloudClient.ts @@ -0,0 +1,132 @@ +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 from 'node-fetch'; + +interface FileSpec { + absolutePath: string, + uploadPath: string +} + +export class NextcloudClient { + public constructor( + private endpoint: string, + private artifact: string, + private rootDirectory: string) { } + + public async uploadFiles(files: string[]) { + const spec = this.uploadSpec(files); + var zip = await this.zipFiles(spec); + await this.upload(zip); + } + + 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; + } + + + private async zipFiles(specs: FileSpec[]): Promise { + const tempArtifactDir = path.join(os.tmpdir(), randomUUID()); + 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)); + } + + 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 = fsSync.createWriteStream(destpath); + archive.directory(dirpath, false) + .on('error', e => Promise.reject()) + .on('close', () => Promise.resolve()) + .pipe(stream); + + return archive.finalize(); + } + + private async upload(file: string) { + const url = new URL(this.endpoint, '/remote.php/dav/files/user/path/to/file'); + const stream = fsSync.createReadStream(file); + await fetch(url.href, { + method: 'PUT', + body: stream + }); + } + + private shareFile() { + + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index a12138a..e7ea242 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "target": "es6", /* 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'. */ @@ -8,6 +8,7 @@ "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ "rootDir": "src", "outDir": "dist", - "sourceMap": true + "sourceMap": true, + "lib": ["es6"] } }