migrate GitLab backend

This commit is contained in:
Shawn Erquhart 2018-07-23 12:14:53 -04:00
parent 33f490055d
commit faab1e38ba
18 changed files with 149 additions and 73 deletions

View File

@ -15,7 +15,7 @@ export default class API {
this.repoURL = `/repos/${ this.repo }`; this.repoURL = `/repos/${ this.repo }`;
this.merge_method = config.squash_merges ? "squash" : "merge"; this.merge_method = config.squash_merges ? "squash" : "merge";
this.initialStatus = config.initialStatus; this.initialStatus = config.initialStatus;
this.useWorkflow = config.useWorkflow;
} }
user() { user() {

View File

@ -2,39 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from 'react-emotion';
import { NetlifyAuthenticator } from 'netlify-cms-lib-auth'; import { NetlifyAuthenticator } from 'netlify-cms-lib-auth';
import { Icon, buttons, shadows } from 'netlify-cms-ui-default'; import { AuthenticationPage, Icon, buttons, shadows } from 'netlify-cms-ui-default';
const StyledAuthenticationPage = styled.section` const LoginButtonIcon = styled(Icon)`
display: flex; margin-right: 18px;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
height: 100vh;
` `
const PageLogoIcon = styled(Icon)` export default class GitHubAuthenticationPage extends React.Component {
color: #c4c6d2;
margin-top: -300px;
`
const LoginButton = styled.button`
${buttons.button};
${shadows.dropDeep};
${buttons.default};
${buttons.gray};
padding: 0 12px;
margin-top: -40px;
display: flex;
align-items: center;
position: relative;
${Icon} {
margin-right: 18px;
}
`
export default class AuthenticationPage extends React.Component {
static propTypes = { static propTypes = {
onLogin: PropTypes.func.isRequired, onLogin: PropTypes.func.isRequired,
inProgress: PropTypes.bool, inProgress: PropTypes.bool,
@ -64,17 +38,18 @@ export default class AuthenticationPage extends React.Component {
}; };
render() { render() {
const { loginError } = this.state;
const { inProgress } = this.props; const { inProgress } = this.props;
return ( return (
<StyledAuthenticationPage> <AuthenticationPage
<PageLogoIcon size="300px" type="netlify-cms"/> onLogin={this.handleLogin}
{loginError ? <p>{loginError}</p> : null} loginDisabled={inProgress}
<LoginButton disabled={inProgress} onClick={this.handleLogin}> loginErrorMessage={this.state.loginError}
<Icon type="github" /> {inProgress ? "Logging in..." : "Login with GitHub"} renderButtonContent={() => (
</LoginButton> <React.Fragment>
</StyledAuthenticationPage> <LoginButtonIcon type="github"/> {inProgress ? "Logging in..." : "Login with GitHub"}
</React.Fragment>
)}
/>
); );
} }
} }

View File

@ -37,7 +37,14 @@ export default class GitHub {
authenticate(state) { authenticate(state) {
this.token = state.token; this.token = state.token;
this.api = new API({ token: this.token, branch: this.branch, repo: this.repo, api_root: this.api_root, squash_merges: this.squash_merges }); this.api = new API({
token: this.token,
branch: this.branch,
repo: this.repo,
api_root: this.api_root,
squash_merges: this.squash_merges,
useWorkflow: this.options.useWorkflow,
});
return this.api.user().then(user => return this.api.user().then(user =>
this.api.hasWriteAccess().then((isCollab) => { this.api.hasWriteAccess().then((isCollab) => {
// Unauthorized user // Unauthorized user

View File

@ -0,0 +1,3 @@
const config = require('../../babel.config.js');
module.exports = config;

View File

@ -0,0 +1,41 @@
{
"name": "netlify-cms-backend-gitlab",
"description": "GitLab backend for Netlify CMS",
"version": "2.0.0-alpha.0",
"license": "MIT",
"main": "dist/netlify-cms-backend-gitlab.js",
"keywords": [
"netlify",
"netlify-cms",
"backend",
"gitlab"
],
"sideEffects": false,
"scripts": {
"watch": "webpack -w",
"build": "cross-env NODE_ENV=production webpack"
},
"dependencies": {
"js-base64": "^2.4.8",
"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"
},
"peerDependencies": {
"immutable": "^3.7.6",
"lodash": "^4.17.10",
"netlify-cms-lib-auth": "2.0.0-alpha.0",
"netlify-cms-lib-util": "2.0.0-alpha.0",
"netlify-cms-ui-default": "2.0.0-alpha.0",
"prop-types": "^15.6.2",
"react": "^16.4.1",
"react-emotion": "^9.2.6"
}
}

View File

@ -2,7 +2,6 @@ import { localForage, unsentRequest, then, APIError, Cursor } from "netlify-cms-
import { Base64 } from "js-base64"; import { Base64 } from "js-base64";
import { fromJS, List, Map } from "immutable"; import { fromJS, List, Map } from "immutable";
import { cond, flow, isString, partial, partialRight, pick, omit, set, update, get } from "lodash"; import { cond, flow, isString, partial, partialRight, pick, omit, set, update, get } from "lodash";
import AssetProxy from "ValueObjects/AssetProxy";
export default class API { export default class API {
constructor(config) { constructor(config) {

View File

@ -1,9 +1,14 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion';
import { NetlifyAuthenticator, ImplicitAuthenticator } from 'netlify-cms-lib-auth'; import { NetlifyAuthenticator, ImplicitAuthenticator } from 'netlify-cms-lib-auth';
import { Icon } from 'netlify-cms-ui-default'; import { AuthenticationPage, Icon, buttons, shadows } from 'netlify-cms-ui-default';
export default class AuthenticationPage extends React.Component { const LoginButtonIcon = styled(Icon)`
margin-right: 18px;
`
export default class GitLabAuthenticationPage extends React.Component {
static propTypes = { static propTypes = {
onLogin: PropTypes.func.isRequired, onLogin: PropTypes.func.isRequired,
inProgress: PropTypes.bool, inProgress: PropTypes.bool,
@ -49,21 +54,18 @@ export default class AuthenticationPage extends React.Component {
}; };
render() { render() {
const { loginError } = this.state;
const { inProgress } = this.props; const { inProgress } = this.props;
return ( return (
<section className="nc-githubAuthenticationPage-root"> <AuthenticationPage
<Icon className="nc-githubAuthenticationPage-logo" size="500px" type="netlify-cms"/> onLogin={this.handleLogin}
{loginError && <p>{loginError}</p>} loginDisabled={inProgress}
<button loginErrorMessage={this.state.loginError}
className="nc-githubAuthenticationPage-button" renderButtonContent={() => (
disabled={inProgress} <React.Fragment>
onClick={this.handleLogin} <LoginButtonIcon type="gitlab"/> {inProgress ? "Logging in..." : "Login with GitLab"}
> </React.Fragment>
<Icon type="gitlab" /> {inProgress ? "Logging in..." : "Login with GitLab"} )}
</button> />
</section>
); );
} }
} }

View File

@ -3,12 +3,11 @@ import semaphore from "semaphore";
import { fileExtension, Cursor, CURSOR_COMPATIBILITY_SYMBOL } from 'netlify-cms-lib-util'; import { fileExtension, Cursor, CURSOR_COMPATIBILITY_SYMBOL } from 'netlify-cms-lib-util';
import AuthenticationPage from "./AuthenticationPage"; import AuthenticationPage from "./AuthenticationPage";
import API from "./API"; import API from "./API";
import { EDITORIAL_WORKFLOW } from "Constants/publishModes";
const MAX_CONCURRENT_DOWNLOADS = 10; const MAX_CONCURRENT_DOWNLOADS = 10;
export default class GitLab { export default class GitLab {
constructor(config, options={}) { constructor(config, options = {}) {
this.config = config; this.config = config;
this.options = { this.options = {
proxied: false, proxied: false,
@ -16,7 +15,7 @@ export default class GitLab {
...options, ...options,
}; };
if (config.getIn(["publish_mode"]) === EDITORIAL_WORKFLOW) { if (this.options.useWorkflow) {
throw new Error("The GitLab backend does not support the Editorial Workflow.") throw new Error("The GitLab backend does not support the Editorial Workflow.")
} }

View File

@ -0,0 +1,4 @@
export GitLabBackend from './implementation';
export API from './API';
export AuthenticationPage from './AuthenticationPage';

View File

@ -0,0 +1 @@
module.exports = require('../../webpack.config.js');

View File

@ -43,10 +43,11 @@ const getFolderEntries = (folder, extension) => {
}; };
export default class TestRepo { export default class TestRepo {
constructor(config) { constructor(config, options = {}) {
this.config = config; this.config = config;
this.assets = []; this.assets = [];
this.initialStatus = config.initialStatus; this.initialStatus = config.initialStatus;
this.options = options;
} }
authComponent() { authComponent() {
@ -135,7 +136,7 @@ export default class TestRepo {
} }
persistEntry({ path, raw, slug }, mediaFiles = [], options = {}) { persistEntry({ path, raw, slug }, mediaFiles = [], options = {}) {
if (options.useWorkflow) { if (this.options.useWorkflow) {
const unpubStore = window.repoFilesUnpublished; const unpubStore = window.repoFilesUnpublished;
const existingEntryIndex = unpubStore.findIndex(e => e.file.path === path); const existingEntryIndex = unpubStore.findIndex(e => e.file.path === path);
if (existingEntryIndex >= 0) { if (existingEntryIndex >= 0) {

View File

@ -1,10 +1,10 @@
backend: backend:
name: test-repo name: gitlab
repo: erquhart/blank
display_url: https://example.com display_url: https://example.com
media_folder: "assets/uploads" media_folder: "assets/uploads"
publish_mode: editorial_workflow # optional, enables publishing workflow
collections: # A list of collections the CMS should be able to edit collections: # A list of collections the CMS should be able to edit
- name: "posts" # Used in routes, ie.: /admin/collections/:slug/edit - name: "posts" # Used in routes, ie.: /admin/collections/:slug/edit

View File

@ -2,6 +2,7 @@ import { attempt, flatten, isError } from 'lodash';
import { fromJS, Map } from 'immutable'; import { fromJS, Map } from 'immutable';
import fuzzy from 'fuzzy'; import fuzzy from 'fuzzy';
import { GitHubBackend } from "netlify-cms-backend-github"; import { GitHubBackend } from "netlify-cms-backend-github";
import { GitLabBackend } from "netlify-cms-backend-gitlab";
import { TestBackend } from "netlify-cms-backend-test"; import { TestBackend } from "netlify-cms-backend-test";
import { resolveFormat } from "Formats/formats"; import { resolveFormat } from "Formats/formats";
import { selectIntegration } from 'Reducers/integrations'; import { selectIntegration } from 'Reducers/integrations';
@ -17,7 +18,6 @@ import {
} from "Reducers/collections"; } from "Reducers/collections";
import { createEntry } from "ValueObjects/Entry"; import { createEntry } from "ValueObjects/Entry";
import { sanitizeSlug } from "Lib/urlHelper"; import { sanitizeSlug } from "Lib/urlHelper";
import GitLabBackend from "./gitlab/implementation";
import BitBucketBackend from "./bitbucket/implementation"; import BitBucketBackend from "./bitbucket/implementation";
import GitGatewayBackend from "./git-gateway/implementation"; import GitGatewayBackend from "./git-gateway/implementation";
import { registerBackend, getBackend } from 'Lib/registry'; import { registerBackend, getBackend } from 'Lib/registry';
@ -127,8 +127,11 @@ const sortByScore = (a, b) => {
}; };
class Backend { class Backend {
constructor(implementation, { authStore = null, backendName, config } = {}) { constructor(implementation, { backendName, authStore = null, config } = {}) {
this.implementation = implementation.init(config, { updateUserCredentials: this.updateUserCredentials }); this.implementation = implementation.init(config, {
useWorkflow: config.getIn(["publish_mode"]) === EDITORIAL_WORKFLOW,
updateUserCredentials: this.updateUserCredentials,
});
this.backendName = backendName; this.backendName = backendName;
this.authStore = authStore; this.authStore = authStore;
if (this.implementation === null) { if (this.implementation === null) {
@ -396,8 +399,6 @@ class Backend {
const commitMessage = commitMessageFormatter(newEntry ? 'create' : 'update', config, { collection, slug: entryObj.slug, path: entryObj.path }); const commitMessage = commitMessageFormatter(newEntry ? 'create' : 'update', config, { collection, slug: entryObj.slug, path: entryObj.path });
const useWorkflow = config.get("publish_mode") === EDITORIAL_WORKFLOW;
const collectionName = collection.get("name"); const collectionName = collection.get("name");
/** /**
@ -410,7 +411,6 @@ class Backend {
parsedData, parsedData,
commitMessage, commitMessage,
collectionName, collectionName,
useWorkflow,
initialStatus: status.first(), initialStatus: status.first(),
...updatedOptions ...updatedOptions
}; };

View File

@ -1,6 +1,6 @@
import { flow } from "lodash"; import { flow } from "lodash";
import { API as GitlabAPI } from "netlify-cms-backend-gitlab";
import { unsentRequest, then } from "netlify-cms-lib-util"; import { unsentRequest, then } from "netlify-cms-lib-util";
import GitlabAPI from "Backends/gitlab/API";
export default class API extends GitlabAPI { export default class API extends GitlabAPI {
constructor(config) { constructor(config) {

View File

@ -4,7 +4,7 @@ import {List} from 'immutable';
import { get, pick, intersection } from "lodash"; import { get, pick, intersection } from "lodash";
import { unsentRequest } from "netlify-cms-lib-util"; import { unsentRequest } from "netlify-cms-lib-util";
import { GitHubBackend } from "netlify-cms-backend-github"; import { GitHubBackend } from "netlify-cms-backend-github";
import GitLabBackend from "Backends/gitlab/implementation"; import { GitLabBackend } from "netlify-cms-backend-gitlab";
import BitBucketBackend from "Backends/bitbucket/implementation"; import BitBucketBackend from "Backends/bitbucket/implementation";
import GitHubAPI from "./GitHubAPI"; import GitHubAPI from "./GitHubAPI";
import GitLabAPI from "./GitLabAPI"; import GitLabAPI from "./GitLabAPI";

View File

@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'react-emotion';
import Icon from './Icon';
import { buttons, shadows } from './styles';
const StyledAuthenticationPage = styled.section`
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
height: 100vh;
`
const PageLogoIcon = styled(Icon)`
color: #c4c6d2;
margin-top: -300px;
`
const LoginButton = styled.button`
${buttons.button};
${shadows.dropDeep};
${buttons.default};
${buttons.gray};
padding: 0 12px;
margin-top: -40px;
display: flex;
align-items: center;
position: relative;
`
const AuthenticationPage = ({ onLogin, loginDisabled, loginErrorMessage, renderButtonContent }) => (
<StyledAuthenticationPage>
<PageLogoIcon size="300px" type="netlify-cms"/>
{loginErrorMessage ? <p>{loginErrorMessage}</p> : null}
<LoginButton disabled={loginDisabled} onClick={onLogin}>
{renderButtonContent()}
</LoginButton>
</StyledAuthenticationPage>
);
export default AuthenticationPage;

View File

@ -3,6 +3,7 @@ export Icon from './Icon';
export ListItemTopBar from './ListItemTopBar'; export ListItemTopBar from './ListItemTopBar';
export Loader from './Loader'; export Loader from './Loader';
export Toggle from './Toggle'; export Toggle from './Toggle';
export AuthenticationPage from './AuthenticationPage';
export { export {
fonts, fonts,
colorsRaw, colorsRaw,

View File

@ -29,7 +29,7 @@ module.exports = {
target: 'web', target: 'web',
externals: (context, request, cb) => { externals: (context, request, cb) => {
const peerDeps = Object.keys(pkg.peerDependencies || {}); const peerDeps = Object.keys(pkg.peerDependencies || {});
const isPeerDep = dep => new RegExp(`^${dep}/?`).test(request); const isPeerDep = dep => new RegExp(`^${dep}($|/)`).test(request);
return peerDeps.some(isPeerDep) ? cb(null, request) : cb(); return peerDeps.some(isPeerDep) ? cb(null, request) : cb();
}, },
stats: { stats: {