use webpack for all builds
This commit is contained in:
3
packages/netlify-cms-backend-github/babel.config.js
Normal file
3
packages/netlify-cms-backend-github/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
const config = require('../../babel.config.js');
|
||||
|
||||
module.exports = config;
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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/';
|
||||
|
||||
|
@ -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) {
|
||||
|
197
packages/netlify-cms-backend-github/src/implementation.js
Normal file
197
packages/netlify-cms-backend-github/src/implementation.js
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
1
packages/netlify-cms-backend-github/webpack.config.js
Normal file
1
packages/netlify-cms-backend-github/webpack.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('../../webpack.config.js');
|
Reference in New Issue
Block a user