use webpack for all builds

This commit is contained in:
Shawn Erquhart
2018-07-17 19:13:52 -04:00
parent 040dd6859c
commit 2f95d8c4fc
96 changed files with 2886 additions and 2068 deletions

View File

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

View File

@ -3,28 +3,38 @@
"description": "GitHub backend for Netlify CMS",
"version": "2.0.0-alpha.0",
"license": "MIT",
"main": "dist/netlify-cms-backend-github.js",
"keywords": [
"netlify",
"netlify-cms",
"backend",
"github"
],
"sideEffects": false,
"scripts": {
"watch": "parcel watch src/*.js --out-dir . --no-cache",
"build": "parcel build src/*.js --out-dir . --no-cache"
"watch": "webpack -w",
"build": "cross-env NODE_ENV=production webpack"
},
"dependencies": {
"js-base64": "^2.4.8",
"lodash": "^4.17.10",
"netlify-cms-lib-auth": "file:../netlify-cms-lib-auth",
"netlify-cms-lib-util": "file:../netlify-cms-lib-util",
"netlify-cms-ui-default": "file:../netlify-cms-ui-default",
"prop-types": "^15.6.2",
"react": "^16.4.1",
"react-emotion": "^9.2.6",
"semaphore": "^1.1.0"
},
"devDependencies": {
"parcel-bundler": "^1.9.4"
"@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": {
"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",
"lodash": "^4.17.10",
"prop-types": "^15.6.2",
"react": "^16.4.1",
"react-emotion": "^9.2.6"
}
}

View File

@ -1,9 +1,8 @@
import localForage from "netlify-cms-lib-util/localForage";
import { localForage } from "netlify-cms-lib-util";
import { Base64 } from "js-base64";
import { uniq, initial, last, get, find, hasIn, partial } from "lodash";
import { filterPromises, resolvePromiseProperties } from "netlify-cms-lib-util/promise";
import APIError from "netlify-cms-lib-util/APIError";
import EditorialWorkflowError from "netlify-cms-lib-util/EditorialWorkflowError";
import { filterPromises, resolvePromiseProperties } from "netlify-cms-lib-util";
import { APIError, EditorialWorkflowError } from "netlify-cms-lib-util";
const CMS_BRANCH_PREFIX = 'cms/';

View File

@ -1,9 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'react-emotion';
import Authenticator from 'netlify-cms-lib-auth/netlify-auth';
import Icon from 'netlify-cms-ui-default/Icon';
import { buttons, shadows } from 'netlify-cms-ui-default/styles';
import { NetlifyAuthenticator } from 'netlify-cms-lib-auth';
import { Icon, buttons, shadows } from 'netlify-cms-ui-default';
const StyledAuthenticationPage = styled.section`
display: flex;
@ -53,7 +52,7 @@ export default class AuthenticationPage extends React.Component {
site_id: (document.location.host.split(':')[0] === 'localhost') ? 'cms.netlify.com' : this.props.siteId,
auth_endpoint: this.props.authEndpoint,
};
const auth = new Authenticator(cfg);
const auth = new NetlifyAuthenticator(cfg);
auth.authenticate({ provider: 'github', scope: 'repo' }, (err, data) => {
if (err) {

View File

@ -0,0 +1,197 @@
import trimStart from 'lodash/trimStart';
import semaphore from "semaphore";
import AuthenticationPage from "./AuthenticationPage";
import API from "./API";
const MAX_CONCURRENT_DOWNLOADS = 10;
export default class GitHub {
constructor(config, options={}) {
this.config = config;
this.options = {
proxied: false,
API: null,
...options,
};
if (!this.options.proxied && config.getIn(["backend", "repo"]) == null) {
throw new Error("The GitHub backend needs a \"repo\" in the backend configuration.");
}
this.api = this.options.API || null;
this.repo = config.getIn(["backend", "repo"], "");
this.branch = config.getIn(["backend", "branch"], "master").trim();
this.api_root = config.getIn(["backend", "api_root"], "https://api.github.com");
this.token = '';
this.squash_merges = config.getIn(["backend", "squash_merges"]);
}
authComponent() {
return AuthenticationPage;
}
restoreUser(user) {
return this.authenticate(user);
}
authenticate(state) {
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 });
return this.api.user().then(user =>
this.api.hasWriteAccess().then((isCollab) => {
// Unauthorized user
if (!isCollab) throw new Error("Your GitHub user account does not have access to this repo.");
// Authorized user
user.token = state.token;
return user;
})
);
}
logout() {
this.token = null;
return;
}
getToken() {
return Promise.resolve(this.token);
}
entriesByFolder(collection, extension) {
return this.api.listFiles(collection.get("folder"))
.then(files => files.filter(file => file.name.endsWith('.' + extension)))
.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.sha).then((data) => {
resolve({ file, data });
sem.leave();
}).catch((err = true) => {
sem.leave();
console.error(`failed to load file from GitHub: ${file.path}`);
resolve({ error: err });
}))
)));
});
return Promise.all(promises)
.then(loadedEntries => loadedEntries.filter(loadedEntry => !loadedEntry.error));
};
// Fetches a single entry.
getEntry(collection, slug, path) {
return this.api.readFile(path).then(data => ({
file: { path },
data,
}));
}
getMedia() {
return this.api.listFiles(this.config.get('media_folder'))
.then(files => files.map(({ sha, name, size, download_url, path }) => {
const url = new URL(download_url);
if (url.pathname.match(/.svg$/)) {
url.search += (url.search.slice(1) === '' ? '?' : '&') + 'sanitize=true';
}
return { id: sha, name, size, url: url.href, path };
}));
}
persistEntry(entry, mediaFiles = [], options = {}) {
return this.api.persistFiles(entry, mediaFiles, options);
}
async persistMedia(mediaFile, options = {}) {
try {
const response = await this.api.persistFiles(null, [mediaFile], options);
const { sha, value, size, path, fileObj } = mediaFile;
const url = URL.createObjectURL(fileObj);
return { id: sha, name: value, size: fileObj.size, url, path: trimStart(path, '/') };
}
catch(error) {
console.error(error);
throw error;
}
}
deleteFile(path, commitMessage, options) {
return this.api.deleteFile(path, commitMessage, options);
}
unpublishedEntries() {
return this.api.listUnpublishedBranches().then((branches) => {
const sem = semaphore(MAX_CONCURRENT_DOWNLOADS);
const promises = [];
branches.map((branch) => {
promises.push(new Promise((resolve, reject) => {
const slug = branch.ref.split("refs/heads/cms/").pop();
return sem.take(() => this.api.readUnpublishedBranchFile(slug).then((data) => {
if (data === null || data === undefined) {
resolve(null);
sem.leave();
} else {
const path = data.metaData.objects.entry.path;
resolve({
slug,
file: { path },
data: data.fileData,
metaData: data.metaData,
isModification: data.isModification,
});
sem.leave();
}
}).catch((err) => {
sem.leave();
resolve(null);
}));
}));
});
return Promise.all(promises);
})
.catch((error) => {
if (error.message === "Not Found") {
return Promise.resolve([]);
}
return error;
});
}
unpublishedEntry(collection, slug) {
return this.api.readUnpublishedBranchFile(slug)
.then((data) => {
if (!data) return null;
return {
slug,
file: { path: data.metaData.objects.entry.path },
data: data.fileData,
metaData: data.metaData,
isModification: data.isModification,
};
});
}
updateUnpublishedEntryStatus(collection, slug, newStatus) {
return this.api.updateUnpublishedEntryStatus(collection, slug, newStatus);
}
deleteUnpublishedEntry(collection, slug) {
return this.api.deleteUnpublishedEntry(collection, slug);
}
publishUnpublishedEntry(collection, slug) {
return this.api.publishUnpublishedEntry(collection, slug);
}
}

View File

@ -1,197 +1,4 @@
import trimStart from 'lodash/trimStart';
import semaphore from "semaphore";
import AuthenticationPage from "./AuthenticationPage";
import API from "./API";
export GitHubBackend from './implementation';
export API from './API';
export AuthenticationPage from './AuthenticationPage';
const MAX_CONCURRENT_DOWNLOADS = 10;
export default class GitHub {
constructor(config, options={}) {
this.config = config;
this.options = {
proxied: false,
API: null,
...options,
};
if (!this.options.proxied && config.getIn(["backend", "repo"]) == null) {
throw new Error("The GitHub backend needs a \"repo\" in the backend configuration.");
}
this.api = this.options.API || null;
this.repo = config.getIn(["backend", "repo"], "");
this.branch = config.getIn(["backend", "branch"], "master").trim();
this.api_root = config.getIn(["backend", "api_root"], "https://api.github.com");
this.token = '';
this.squash_merges = config.getIn(["backend", "squash_merges"]);
}
authComponent() {
return AuthenticationPage;
}
restoreUser(user) {
return this.authenticate(user);
}
authenticate(state) {
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 });
return this.api.user().then(user =>
this.api.hasWriteAccess().then((isCollab) => {
// Unauthorized user
if (!isCollab) throw new Error("Your GitHub user account does not have access to this repo.");
// Authorized user
user.token = state.token;
return user;
})
);
}
logout() {
this.token = null;
return;
}
getToken() {
return Promise.resolve(this.token);
}
entriesByFolder(collection, extension) {
return this.api.listFiles(collection.get("folder"))
.then(files => files.filter(file => file.name.endsWith('.' + extension)))
.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.sha).then((data) => {
resolve({ file, data });
sem.leave();
}).catch((err = true) => {
sem.leave();
console.error(`failed to load file from GitHub: ${file.path}`);
resolve({ error: err });
}))
)));
});
return Promise.all(promises)
.then(loadedEntries => loadedEntries.filter(loadedEntry => !loadedEntry.error));
};
// Fetches a single entry.
getEntry(collection, slug, path) {
return this.api.readFile(path).then(data => ({
file: { path },
data,
}));
}
getMedia() {
return this.api.listFiles(this.config.get('media_folder'))
.then(files => files.map(({ sha, name, size, download_url, path }) => {
const url = new URL(download_url);
if (url.pathname.match(/.svg$/)) {
url.search += (url.search.slice(1) === '' ? '?' : '&') + 'sanitize=true';
}
return { id: sha, name, size, url: url.href, path };
}));
}
persistEntry(entry, mediaFiles = [], options = {}) {
return this.api.persistFiles(entry, mediaFiles, options);
}
async persistMedia(mediaFile, options = {}) {
try {
const response = await this.api.persistFiles(null, [mediaFile], options);
const { sha, value, size, path, fileObj } = mediaFile;
const url = URL.createObjectURL(fileObj);
return { id: sha, name: value, size: fileObj.size, url, path: trimStart(path, '/') };
}
catch(error) {
console.error(error);
throw error;
}
}
deleteFile(path, commitMessage, options) {
return this.api.deleteFile(path, commitMessage, options);
}
unpublishedEntries() {
return this.api.listUnpublishedBranches().then((branches) => {
const sem = semaphore(MAX_CONCURRENT_DOWNLOADS);
const promises = [];
branches.map((branch) => {
promises.push(new Promise((resolve, reject) => {
const slug = branch.ref.split("refs/heads/cms/").pop();
return sem.take(() => this.api.readUnpublishedBranchFile(slug).then((data) => {
if (data === null || data === undefined) {
resolve(null);
sem.leave();
} else {
const path = data.metaData.objects.entry.path;
resolve({
slug,
file: { path },
data: data.fileData,
metaData: data.metaData,
isModification: data.isModification,
});
sem.leave();
}
}).catch((err) => {
sem.leave();
resolve(null);
}));
}));
});
return Promise.all(promises);
})
.catch((error) => {
if (error.message === "Not Found") {
return Promise.resolve([]);
}
return error;
});
}
unpublishedEntry(collection, slug) {
return this.api.readUnpublishedBranchFile(slug)
.then((data) => {
if (!data) return null;
return {
slug,
file: { path: data.metaData.objects.entry.path },
data: data.fileData,
metaData: data.metaData,
isModification: data.isModification,
};
});
}
updateUnpublishedEntryStatus(collection, slug, newStatus) {
return this.api.updateUnpublishedEntryStatus(collection, slug, newStatus);
}
deleteUnpublishedEntry(collection, slug) {
return this.api.deleteUnpublishedEntry(collection, slug);
}
publishUnpublishedEntry(collection, slug) {
return this.api.publishUnpublishedEntry(collection, slug);
}
}

View File

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