diff --git a/packages/netlify-cms-backend-bitbucket/package.json b/packages/netlify-cms-backend-bitbucket/package.json index 41c9a286..4f2cb64e 100644 --- a/packages/netlify-cms-backend-bitbucket/package.json +++ b/packages/netlify-cms-backend-bitbucket/package.json @@ -20,11 +20,7 @@ "semaphore": "^1.1.0" }, "devDependencies": { - "@babel/cli": "^7.0.0-beta.54", - "@babel/core": "^7.0.0-beta.54", "cross-env": "^5.2.0", - "rollup": "^0.63.2", - "rollup-plugin-babel": "^4.0.0-beta.7", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-backend-git-gateway/package.json b/packages/netlify-cms-backend-git-gateway/package.json index 9ba4170d..75aceda2 100644 --- a/packages/netlify-cms-backend-git-gateway/package.json +++ b/packages/netlify-cms-backend-git-gateway/package.json @@ -14,13 +14,14 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "gotrue-js": "^0.9.22", "jwt-decode": "^2.2.0" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-backend-github/package.json b/packages/netlify-cms-backend-github/package.json index b5a3f8ae..fd63aa26 100644 --- a/packages/netlify-cms-backend-github/package.json +++ b/packages/netlify-cms-backend-github/package.json @@ -13,13 +13,14 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "js-base64": "^2.4.8", "semaphore": "^1.1.0" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-backend-gitlab/package.json b/packages/netlify-cms-backend-gitlab/package.json index 98fef69d..dcdf0da7 100644 --- a/packages/netlify-cms-backend-gitlab/package.json +++ b/packages/netlify-cms-backend-gitlab/package.json @@ -13,13 +13,14 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "js-base64": "^2.4.8", "semaphore": "^1.1.0" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-backend-test/package.json b/packages/netlify-cms-backend-test/package.json index b29eca86..b4e3aa3e 100644 --- a/packages/netlify-cms-backend-test/package.json +++ b/packages/netlify-cms-backend-test/package.json @@ -12,12 +12,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "uuid": "^3.3.2" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-core/package.json b/packages/netlify-cms-core/package.json index 85ef2910..86e3d73a 100644 --- a/packages/netlify-cms-core/package.json +++ b/packages/netlify-cms-core/package.json @@ -9,7 +9,7 @@ ], "scripts": { "watch": "webpack-dev-server --hot --open", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "keywords": [ "netlify", @@ -66,6 +66,7 @@ "what-input": "^5.0.3" }, "devDependencies": { + "cross-env": "^5.2.0", "css-loader": "^1.0.0", "friendly-errors-webpack-plugin": "^1.7.0", "to-string-loader": "^1.1.5", diff --git a/packages/netlify-cms-editor-component-image/package.json b/packages/netlify-cms-editor-component-image/package.json index c2fcc173..4d3d2ec9 100644 --- a/packages/netlify-cms-editor-component-image/package.json +++ b/packages/netlify-cms-editor-component-image/package.json @@ -13,9 +13,10 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-lib-auth/package.json b/packages/netlify-cms-lib-auth/package.json index 019e6702..1886b862 100644 --- a/packages/netlify-cms-lib-auth/package.json +++ b/packages/netlify-cms-lib-auth/package.json @@ -14,12 +14,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "uuid": "^3.1.0" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-lib-util/package.json b/packages/netlify-cms-lib-util/package.json index 222d2376..b4820493 100644 --- a/packages/netlify-cms-lib-util/package.json +++ b/packages/netlify-cms-lib-util/package.json @@ -10,12 +10,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "localforage": "^1.4.2" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-ui-default/package.json b/packages/netlify-cms-ui-default/package.json index a56c4e8c..372e3b9b 100644 --- a/packages/netlify-cms-ui-default/package.json +++ b/packages/netlify-cms-ui-default/package.json @@ -10,7 +10,7 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "react-aria-menubutton": "^5.1.0", @@ -18,6 +18,7 @@ "react-transition-group": "^2.2.1" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-boolean/package.json b/packages/netlify-cms-widget-boolean/package.json index a88bbcbd..e1b599e1 100644 --- a/packages/netlify-cms-widget-boolean/package.json +++ b/packages/netlify-cms-widget-boolean/package.json @@ -13,9 +13,10 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-date/package.json b/packages/netlify-cms-widget-date/package.json index adb1a09d..0046ee01 100644 --- a/packages/netlify-cms-widget-date/package.json +++ b/packages/netlify-cms-widget-date/package.json @@ -14,12 +14,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "react-datetime": "^2.11.0" }, "devDependencies": { + "cross-env": "^5.2.0", "css-loader": "^1.0.0", "to-string-loader": "^1.1.5", "webpack": "^4.16.1", diff --git a/packages/netlify-cms-widget-datetime/package.json b/packages/netlify-cms-widget-datetime/package.json index e603653f..a1bfcf56 100644 --- a/packages/netlify-cms-widget-datetime/package.json +++ b/packages/netlify-cms-widget-datetime/package.json @@ -15,12 +15,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "netlify-cms-widget-date": "^2.0.0-alpha.0" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-file/package.json b/packages/netlify-cms-widget-file/package.json index e3bdd466..20aaf0cc 100644 --- a/packages/netlify-cms-widget-file/package.json +++ b/packages/netlify-cms-widget-file/package.json @@ -15,12 +15,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "uuid": "^3.3.2" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-image/package.json b/packages/netlify-cms-widget-image/package.json index 63442799..47524793 100644 --- a/packages/netlify-cms-widget-image/package.json +++ b/packages/netlify-cms-widget-image/package.json @@ -15,12 +15,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "netlify-cms-widget-file": "^2.0.0-alpha.0" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-list/package.json b/packages/netlify-cms-widget-list/package.json index 39fe7574..7904e4a4 100644 --- a/packages/netlify-cms-widget-list/package.json +++ b/packages/netlify-cms-widget-list/package.json @@ -14,13 +14,14 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "netlify-cms-widget-object": "^2.0.0-alpha.0", "react-sortable-hoc": "^0.6.8" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-markdown/package.json b/packages/netlify-cms-widget-markdown/package.json index 0d9b3f75..4febf2e8 100644 --- a/packages/netlify-cms-widget-markdown/package.json +++ b/packages/netlify-cms-widget-markdown/package.json @@ -14,7 +14,7 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "is-hotkey": "^0.1.1", @@ -37,6 +37,7 @@ "unist-util-visit-parents": "^1.1.1" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-number/package.json b/packages/netlify-cms-widget-number/package.json index a1e075bd..b81957d2 100644 --- a/packages/netlify-cms-widget-number/package.json +++ b/packages/netlify-cms-widget-number/package.json @@ -13,9 +13,10 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-object/package.json b/packages/netlify-cms-widget-object/package.json index 50b5b203..becd5d64 100644 --- a/packages/netlify-cms-widget-object/package.json +++ b/packages/netlify-cms-widget-object/package.json @@ -15,9 +15,10 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-relation/package.json b/packages/netlify-cms-widget-relation/package.json index 4ca9813d..f94b0f4f 100644 --- a/packages/netlify-cms-widget-relation/package.json +++ b/packages/netlify-cms-widget-relation/package.json @@ -14,12 +14,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "react-autosuggest": "^9.3.2" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-select/package.json b/packages/netlify-cms-widget-select/package.json index 7b8debc1..4017a855 100644 --- a/packages/netlify-cms-widget-select/package.json +++ b/packages/netlify-cms-widget-select/package.json @@ -15,9 +15,10 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-string/package.json b/packages/netlify-cms-widget-string/package.json index c155d093..1394d3a3 100644 --- a/packages/netlify-cms-widget-string/package.json +++ b/packages/netlify-cms-widget-string/package.json @@ -13,9 +13,10 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms-widget-text/package.json b/packages/netlify-cms-widget-text/package.json index ad69352c..9ec8dcff 100644 --- a/packages/netlify-cms-widget-text/package.json +++ b/packages/netlify-cms-widget-text/package.json @@ -16,12 +16,13 @@ "sideEffects": false, "scripts": { "watch": "webpack -w", - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "react-textarea-autosize": "^5.2.0" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" }, diff --git a/packages/netlify-cms/package.json b/packages/netlify-cms/package.json index 67cee4f8..6f19756b 100644 --- a/packages/netlify-cms/package.json +++ b/packages/netlify-cms/package.json @@ -4,7 +4,7 @@ "version": "2.0.0-alpha.0", "main": "dist/netlify-cms.js", "scripts": { - "build": "webpack" + "build": "cross-env NODE_ENV=production webpack" }, "keywords": [ "netlify", @@ -38,6 +38,7 @@ "netlify-cms-widget-text": "2.0.0-alpha.0" }, "devDependencies": { + "cross-env": "^5.2.0", "webpack": "^4.16.1", "webpack-cli": "^3.1.0" } diff --git a/packages/netlify-cms/src/backends.js b/packages/netlify-cms/src/backends.js index 687bd53e..22dce21d 100644 --- a/packages/netlify-cms/src/backends.js +++ b/packages/netlify-cms/src/backends.js @@ -4,7 +4,6 @@ import { GitLabBackend } from 'netlify-cms-backend-gitlab/src'; import { GitGatewayBackend } from 'netlify-cms-backend-git-gateway/src'; import { TestBackend } from 'netlify-cms-backend-test/src'; -console.log(cms); const { registerBackend } = cms; registerBackend('git-gateway', GitGatewayBackend); diff --git a/scripts/webpack.js b/scripts/webpack.js index 234f147e..4f9858da 100644 --- a/scripts/webpack.js +++ b/scripts/webpack.js @@ -25,7 +25,12 @@ const rules = () => ({ const plugins = () => { return { - friendlyErrors: () => new FriendlyErrorsWebpackPlugin() + define: () => new webpack.DefinePlugin({ + NETLIFY_CMS_VERSION: `${pkg.version}${isProduction ? '' : '-dev'}`, + }), + ignoreEsprima: () => new webpack.IgnorePlugin(/^esprima$/, /js-yaml/), + ignoreMomentOptionalDeps: () => new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + friendlyErrors: () => new FriendlyErrorsWebpackPlugin(), }; }; diff --git a/src/backends/bitbucket/API.js b/src/backends/bitbucket/API.js deleted file mode 100644 index b1c3e9ef..00000000 --- a/src/backends/bitbucket/API.js +++ /dev/null @@ -1,150 +0,0 @@ -import { flow } from "lodash"; -import LocalForage from "Lib/LocalForage"; -import unsentRequest from "Lib/unsentRequest"; -import { responseParser } from "Lib/backendHelper"; -import { then } from "Lib/promiseHelper"; -import { basename } from "Lib/pathHelper"; -import AssetProxy from "ValueObjects/AssetProxy"; -import Cursor from "ValueObjects/Cursor"; -import { APIError } from "ValueObjects/errors"; - -export default class API { - constructor(config) { - this.api_root = config.api_root || "https://api.bitbucket.org/2.0"; - this.branch = config.branch || "master"; - this.repo = config.repo || ""; - this.requestFunction = config.requestFunction || unsentRequest.performRequest; - // Allow overriding this.hasWriteAccess - this.hasWriteAccess = config.hasWriteAccess || this.hasWriteAccess; - this.repoURL = this.repo ? `/repositories/${ this.repo }` : ""; - } - - buildRequest = req => flow([ - unsentRequest.withRoot(this.api_root), - unsentRequest.withTimestamp, - ])(req); - - request = req => flow([ - this.buildRequest, - this.requestFunction, - p => p.catch(err => Promise.reject(new APIError(err.message, null, "BitBucket"))), - ])(req); - - requestJSON = req => flow([ - unsentRequest.withDefaultHeaders({ "Content-Type": "application/json" }), - this.request, - then(responseParser({ format: "json" })), - p => p.catch(err => Promise.reject(new APIError(err.message, null, "BitBucket"))), - ])(req); - requestText = req => flow([ - unsentRequest.withDefaultHeaders({ "Content-Type": "text/plain" }), - this.request, - then(responseParser({ format: "text" })), - p => p.catch(err => Promise.reject(new APIError(err.message, null, "BitBucket"))), - ])(req); - - user = () => this.request("/user"); - hasWriteAccess = user => this.request(this.repoURL).then(res => res.ok); - - isFile = ({ type }) => type === "commit_file"; - processFile = file => ({ - ...file, - name: basename(file.path), - download_url: file.links.self.href, - - // BitBucket does not return file SHAs, but it does give us the - // commit SHA. Since the commit SHA will change if any files do, - // we can construct an ID using the commit SHA and the file path - // that will help with caching (though not as well as a normal - // SHA, since it will change even if the individual file itself - // doesn't.) - ...(file.commit && file.commit.hash - ? { id: `${ file.commit.hash }/${ file.path }` } - : {}), - }); - processFiles = files => files.filter(this.isFile).map(this.processFile); - - readFile = async (path, sha, { ref = this.branch, parseText = true } = {}) => { - const cacheKey = parseText ? `bb.${ sha }` : `bb.${ sha }.blob`; - const cachedFile = sha ? await LocalForage.getItem(cacheKey) : null; - if (cachedFile) { return cachedFile; } - const result = await this.request({ - url: `${ this.repoURL }/src/${ ref }/${ path }`, - cache: "no-store", - }).then(parseText ? responseParser({ format: "text" }) : responseParser({ format: "blob" })); - if (sha) { LocalForage.setItem(cacheKey, result); } - return result; - } - - getEntriesAndCursor = jsonResponse => { - const { size: count, page: index, pagelen: pageSize, next, previous: prev, values: entries } = jsonResponse; - const pageCount = (pageSize && count) ? Math.ceil(count / pageSize) : undefined; - return { - entries, - cursor: Cursor.create({ - actions: [...(next ? ["next"] : []), ...(prev ? ["prev"] : [])], - meta: { index, count, pageSize, pageCount }, - data: { links: { next, prev } }, - }), - }; - } - - listFiles = async path => { - const { entries, cursor } = await flow([ - // sort files by filename ascending - unsentRequest.withParams({ sort: "-path" }), - this.requestJSON, - then(this.getEntriesAndCursor), - ])(`${ this.repoURL }/src/${ this.branch }/${ path }`); - return { entries: this.processFiles(entries), cursor }; - } - - traverseCursor = async (cursor, action) => flow([ - this.requestJSON, - then(this.getEntriesAndCursor), - then(({ cursor: newCursor, entries }) => ({ cursor: newCursor, entries: this.processFiles(entries) })), - ])(cursor.data.getIn(["links", action])); - - listAllFiles = async path => { - const { cursor: initialCursor, entries: initialEntries } = await this.listFiles(path); - const entries = [...initialEntries]; - let currentCursor = initialCursor; - while (currentCursor && currentCursor.actions.has("next")) { - const { cursor: newCursor, entries: newEntries } = await this.traverseCursor(currentCursor, "next"); - entries.push(...newEntries); - currentCursor = newCursor; - } - return this.processFiles(entries); - }; - - uploadBlob = async item => { - const contentBase64 = await (item instanceof AssetProxy ? item.toBase64() : Promise.resolve(item.raw)); - const formData = new FormData(); - formData.append(item.path, contentBase64); - - return flow([ - unsentRequest.withMethod("POST"), - unsentRequest.withBody(formData), - this.request, - then(() => ({ ...item, uploaded: true })), - ])(`${ this.repoURL }/src`); - }; - - persistFiles = (files, { commitMessage, newEntry }) => Promise.all( - files.filter(({ uploaded }) => !uploaded).map(this.uploadBlob) - ); - - deleteFile = (path, message, options={}) => { - const branch = options.branch || this.branch; - const body = new FormData(); - body.append('files', path); - if (message && message !== "") { - body.append("message", message); - } - return flow([ - unsentRequest.withMethod("POST"), - unsentRequest.withBody(body), - this.request, - ])(`${ this.repoURL }/src`); - }; -} diff --git a/src/backends/bitbucket/AuthenticationPage.js b/src/backends/bitbucket/AuthenticationPage.js deleted file mode 100644 index bce564a4..00000000 --- a/src/backends/bitbucket/AuthenticationPage.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Authenticator from 'Lib/netlify-auth'; -import { Icon } from 'UI'; - -export default class AuthenticationPage extends React.Component { - static propTypes = { - onLogin: PropTypes.func.isRequired, - inProgress: PropTypes.bool, - }; - - state = {}; - - handleLogin = (e) => { - e.preventDefault(); - const cfg = { - base_url: this.props.base_url, - site_id: (document.location.host.split(':')[0] === 'localhost') ? 'cms.netlify.com' : this.props.site_id, - auth_endpoint: this.props.authEndpoint, - }; - const auth = new Authenticator(cfg); - - auth.authenticate({ provider: 'bitbucket', scope: 'repo' }, (err, data) => { - if (err) { - this.setState({ loginError: err.toString() }); - return; - } - this.props.onLogin(data); - }); - }; - - render() { - const { loginError } = this.state; - const { inProgress } = this.props; - - return ( -
- - {loginError &&

{loginError}

} - -
- ); - } -} diff --git a/src/backends/bitbucket/implementation.js b/src/backends/bitbucket/implementation.js deleted file mode 100644 index 6933dbab..00000000 --- a/src/backends/bitbucket/implementation.js +++ /dev/null @@ -1,222 +0,0 @@ -import semaphore from "semaphore"; -import { flow, trimStart } from "lodash"; -import { EDITORIAL_WORKFLOW } from "Constants/publishModes"; -import { CURSOR_COMPATIBILITY_SYMBOL } from "ValueObjects/Cursor"; -import { filterByPropExtension } from "Lib/backendHelper"; -import { resolvePromiseProperties, then } from "Lib/promiseHelper"; -import unsentRequest from "Lib/unsentRequest"; -import AuthenticationPage from "./AuthenticationPage"; -import Authenticator from 'Lib/netlify-auth'; -import API from "./API"; - -const MAX_CONCURRENT_DOWNLOADS = 10; - -// Implementation wrapper class -export default class Bitbucket { - constructor(config, options={}) { - this.config = config; - this.options = { - proxied: false, - API: null, - updateUserCredentials: async () => null, - ...options, - }; - - if (config.getIn(["publish_mode"]) === EDITORIAL_WORKFLOW) { - throw new Error("The BitBucket backend does not support the Editorial Workflow."); - } - - if (!this.options.proxied && !config.getIn(["backend", "repo"], false)) { - throw new Error("The BitBucket backend needs a \"repo\ in the backend configuration."); - } - - this.api = this.options.API || null; - - this.updateUserCredentials = this.options.updateUserCredentials; - - this.repo = config.getIn(["backend", "repo"], ""); - this.branch = config.getIn(["backend", "branch"], "master"); - this.api_root = config.getIn(["backend", "api_root"], "https://api.bitbucket.org/2.0"); - this.base_url = config.get("base_url"); - this.site_id = config.get("site_id"); - this.token = ""; - } - - authComponent() { - return AuthenticationPage; - } - - setUser(user) { - this.token = user.token; - this.api = new API({ requestFunction: this.apiRequestFunction, branch: this.branch, repo: this.repo }); - } - - restoreUser(user) { - return this.authenticate(user); - } - - authenticate(state) { - this.token = state.token; - this.refreshToken = state.refresh_token; - this.api = new API({ requestFunction: this.apiRequestFunction, branch: this.branch, repo: this.repo, api_root: this.api_root }); - - return this.api.user().then(user => - this.api.hasWriteAccess(user).then(isCollab => { - if (!isCollab) throw new Error("Your BitBucker user account does not have access to this repo."); - return Object.assign({}, user, { token: state.token, refresh_token: state.refresh_token }); - }) - ); - } - - getRefreshedAccessToken() { - if (this.refreshedTokenPromise) { - return this.refreshedTokenPromise; - } - - // instantiating a new Authenticator on each refresh isn't ideal, - if (!this.auth) { - const cfg = { - base_url: this.base_url, - site_id: this.site_id, - }; - this.authenticator = new Authenticator(cfg); - } - - this.refreshedTokenPromise = this.authenticator.refresh({ provider: "bitbucket", refresh_token: this.refreshToken }) - .then(({ token, refresh_token }) => { - this.token = token; - this.refreshToken = refresh_token; - this.refreshedTokenPromise = undefined; - this.updateUserCredentials({ token, refresh_token }); - return token; - }); - - return this.refreshedTokenPromise; - } - - logout() { - this.token = null; - return; - } - - getToken() { - if (this.refreshedTokenPromise) { - return this.refreshedTokenPromise; - } - - return Promise.resolve(this.token); - } - - apiRequestFunction = async req => { - const token = this.refreshedTokenPromise ? await this.refreshedTokenPromise : this.token; - return flow([ - unsentRequest.withHeaders({ Authorization: `Bearer ${ token }` }), - unsentRequest.performRequest, - then(async res => { - if (res.status === 401) { - const json = (await res.json().catch(() => null)); - if (json && json.type === "error" && /^access token expired/i.test(json.error.message)) { - const newToken = await this.getRefreshedAccessToken(); - const reqWithNewToken = unsentRequest.withHeaders({ Authorization: `Bearer ${ newToken }` }, req); - return unsentRequest.performRequest(reqWithNewToken); - } - } - return res; - }), - ])(req); - }; - - entriesByFolder(collection, extension) { - const listPromise = this.api.listFiles(collection.get("folder")); - return resolvePromiseProperties({ - files: listPromise - .then(({ entries }) => entries) - .then(filterByPropExtension(extension, "path")) - .then(this.fetchFiles), - cursor: listPromise.then(({ cursor }) => cursor), - }).then(({ files, cursor }) => { - files[CURSOR_COMPATIBILITY_SYMBOL] = cursor; - return files; - }); - } - - allEntriesByFolder(collection, extension) { - return this.api.listAllFiles(collection.get("folder")) - .then(filterByPropExtension(extension, "path")) - .then(this.fetchFiles); - } - - entriesByFiles(collection) { - const files = collection.get("files").map(collectionFile => ({ - path: collectionFile.get("file"), - label: collectionFile.get("label"), - })); - return this.fetchFiles(files); - } - - fetchFiles = (files) => { - const sem = semaphore(MAX_CONCURRENT_DOWNLOADS); - const promises = []; - files.forEach((file) => { - promises.push(new Promise((resolve, reject) => ( - sem.take(() => this.api.readFile(file.path, file.id).then((data) => { - resolve({ file, data }); - sem.leave(); - }).catch((error = true) => { - sem.leave(); - console.error(`failed to load file from BitBucket: ${ file.path }`); - resolve({ error }); - })) - ))); - }); - return Promise.all(promises) - .then(loadedEntries => loadedEntries.filter(loadedEntry => !loadedEntry.error)); - } - - getEntry(collection, slug, path) { - return this.api.readFile(path).then(data => ({ - file: { path }, - data, - })); - } - - getMedia() { - const sem = semaphore(MAX_CONCURRENT_DOWNLOADS); - - return this.api.listAllFiles(this.config.get("media_folder")) - .then(files => files.map(({ id, name, download_url, path }) => { - const getBlobPromise = () => new Promise((resolve, reject) => - sem.take(() => - this.api.readFile(path, id, { parseText: false }) - .then(resolve, reject) - .finally(() => sem.leave()) - ) - ); - - return { id, name, getBlobPromise, url: download_url, path }; - })); - } - - persistEntry(entry, mediaFiles, options = {}) { - return this.api.persistFiles([entry], options); - } - - async persistMedia(mediaFile, options = {}) { - await this.api.persistFiles([mediaFile], options); - const { value, path, fileObj } = mediaFile; - const getBlobPromise = () => Promise.resolve(fileObj); - return { name: value, size: fileObj.size, getBlobPromise, path: trimStart(path, '/k') }; - } - - deleteFile(path, commitMessage, options) { - return this.api.deleteFile(path, commitMessage, options); - } - - traverseCursor(cursor, action) { - return this.api.traverseCursor(cursor, action) - .then(async ({ entries, cursor: newCursor }) => ({ - entries: await Promise.all(entries.map(file => this.api.readFile(file.path, file.id).then(data => ({ file, data })))), - cursor: newCursor, - })); - } -}