begin scaffolding for lerna

This commit is contained in:
Shawn Erquhart
2018-07-03 15:47:15 -04:00
parent 26f7c38a9f
commit 768fcbaa1d
320 changed files with 50292 additions and 464 deletions

View File

@ -0,0 +1,101 @@
import { fileExtensionWithSeparator, fileExtension } from '../pathHelper';
describe('fileExtensionWithSeparator', () => {
it('should return the extension of a file', () => {
expect(
fileExtensionWithSeparator('index.html')
).toEqual(
'.html'
);
});
it('should return the extension of a file path', () => {
expect(
fileExtensionWithSeparator('/src/main/index.html')
).toEqual(
'.html'
);
});
it('should return the extension of a file path with trailing slash', () => {
expect(
fileExtensionWithSeparator('/src/main/index.html/')
).toEqual(
'.html'
);
});
it('should return the extension for an extension with two ..', () => {
expect(
fileExtensionWithSeparator('/src/main/index..html')
).toEqual(
'.html'
);
});
it('should return an empty string for the parent path ..', () => {
expect(
fileExtensionWithSeparator('..')
).toEqual(
''
);
});
it('should return an empty string if the file has no extension', () => {
expect(
fileExtensionWithSeparator('/src/main/index')
).toEqual(
''
);
});
});
describe('fileExtension', () => {
it('should return the extension of a file', () => {
expect(
fileExtension('index.html')
).toEqual(
'html'
);
});
it('should return the extension of a file path', () => {
expect(
fileExtension('/src/main/index.html')
).toEqual(
'html'
);
});
it('should return the extension of a file path with trailing slash', () => {
expect(
fileExtension('/src/main/index.html/')
).toEqual(
'html'
);
});
it('should return the extension for an extension with two ..', () => {
expect(
fileExtension('/src/main/index..html')
).toEqual(
'html'
);
});
it('should return an empty string for the parent path ..', () => {
expect(
fileExtension('..')
).toEqual(
''
);
});
it('should return an empty string if the file has no extension', () => {
expect(
fileExtension('/src/main/index')
).toEqual(
''
);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
{
"name": "netlify-cms-lib-util",
"description": "Shared utilities for Netlify CMS.",
"version": "2.0.0-alpha.0",
"license": "MIT",
"main": "index.js",
"keywords": [
"netlify-cms"
],
"scripts": {
"build": "parcel build src --out-dir ."
},
"dependencies": {
"immutable": "^3.7.6",
"localforage": "^1.4.2",
"lodash": "^4.13.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"parcel-bundler": "^1.9.4"
}
}

View File

@ -0,0 +1,16 @@
import localForage from './localForage';
import { resolvePath, basename, fileExtensionWithSeparator, fileExtension } from './path';
import { filterPromises, resolvePromiseProperties, then } from './promise';
import unsentRequest from './unsentRequest';
export {
localForage,
resolvePath,
basename,
fileExtensionWithSeparator,
fileExtension,
filterPromises,
resolvePromiseProperties,
then,
unsentRequest,
};

View File

@ -0,0 +1,18 @@
import localForage from "localforage";
function localForageTest() {
const testKey = 'localForageTest';
localForage.setItem(testKey, {expires: Date.now() + 300000}).then(() => {
localForage.removeItem(testKey);
}).catch((err) => {
if (err.code === 22) {
const message = `Unable to set localStorage key. Quota exceeded! Full disk?`;
return alert(`${message}\n\n${err}`);
}
console.log(err);
})
}
localForageTest();
export default localForage;

View File

@ -0,0 +1,95 @@
const absolutePath = new RegExp('^(?:[a-z]+:)?//', 'i');
const normalizePath = path => path.replace(/[\\\/]+/g, '/');
export function resolvePath(path, basePath) { // eslint-disable-line
// No path provided, skip
if (!path) return null;
// It's an absolute path.
if (absolutePath.test(path)) return path;
if (path.indexOf('/') === -1) {
// It's a single file name, no directories. Prepend public folder
return normalizePath(`/${ basePath }/${ path }`);
}
// It's a relative path. Prepend a forward slash.
return normalizePath(`/${ path }`);
}
/**
* Return the last portion of a path. Similar to the Unix basename command.
* @example Usage example
* path.basename('/foo/bar/baz/asdf/quux.html')
* // returns
* 'quux.html'
*
* path.basename('/foo/bar/baz/asdf/quux.html', '.html')
* // returns
* 'quux'
*/
export function basename(p, ext = "") {
// Special case: Normalize will modify this to '.'
if (p === '') {
return p;
}
// Normalize the string first to remove any weirdness.
p = normalizePath(p);
// Get the last part of the string.
const sections = p.split('/');
const lastPart = sections[sections.length - 1];
// Special case: If it's empty, then we have a string like so: foo/
// Meaning, 'foo' is guaranteed to be a directory.
if (lastPart === '' && sections.length > 1) {
return sections[sections.length - 2];
}
// Remove the extension, if need be.
if (ext.length > 0) {
const lastPartExt = lastPart.substr(lastPart.length - ext.length);
if (lastPartExt === ext) {
return lastPart.substr(0, lastPart.length - ext.length);
}
}
return lastPart;
}
/**
* Return the extension of the path, from the last '.' to end of string in the
* last portion of the path. If there is no '.' in the last portion of the path
* or the first character of it is '.', then it returns an empty string.
* @example Usage example
* path.fileExtensionWithSeparator('index.html')
* // returns
* '.html'
*/
export function fileExtensionWithSeparator(p) {
p = normalizePath(p);
const sections = p.split('/');
p = sections.pop();
// Special case: foo/file.ext/ should return '.ext'
if (p === '' && sections.length > 0) {
p = sections.pop();
}
if (p === '..') {
return '';
}
const i = p.lastIndexOf('.');
if (i === -1 || i === 0) {
return '';
}
return p.substr(i);
}
/**
* Return the extension of the path, from after the last '.' to end of string in the
* last portion of the path. If there is no '.' in the last portion of the path
* or the first character of it is '.', then it returns an empty string.
* @example Usage example
* path.fileExtension('index.html')
* // returns
* 'html'
*/
export function fileExtension(p) {
const ext = fileExtensionWithSeparator(p);
return ext === '' ? ext : ext.substr(1);
}

View File

@ -0,0 +1,22 @@
import zipObject from 'lodash/zipObject';
export const filterPromises = (arr, filter) =>
Promise.all(arr.map(entry => filter(entry)))
.then(bits => arr.filter(entry => bits.shift()));
export const resolvePromiseProperties = (obj) => {
// Get the keys which represent promises
const promiseKeys = Object.keys(obj).filter(
key => typeof obj[key].then === "function");
const promises = promiseKeys.map(key => obj[key]);
// Resolve all promises
return Promise.all(promises)
.then(resolvedPromises =>
// Return a copy of obj with promises overwritten by their
// resolved values
Object.assign({}, obj, zipObject(promiseKeys, resolvedPromises)));
};
export const then = fn => p => Promise.resolve(p).then(fn);

View File

@ -0,0 +1,81 @@
import { fromJS, List, Map } from 'immutable';
import curry from 'lodash/curry';
import flow from 'lodash/flow';
import isString from 'lodash/isString';
const decodeParams = paramsString => List(paramsString.split("&"))
.map(s => List(s.split("=")).map(decodeURIComponent))
.update(Map);
const fromURL = wholeURL => {
const [url, allParamsString] = wholeURL.split("?");
return Map({ url, ...(allParamsString ? { params: decodeParams(allParamsString) } : {}) });
};
const encodeParams = params => params.entrySeq()
.map(([k, v]) => `${ encodeURIComponent(k) }=${ encodeURIComponent(v) }`)
.join("&");
const toURL = req => `${ req.get("url") }${ req.get("params") ? `?${ encodeParams(req.get("params")) }` : "" }`;
const toFetchArguments = req => [toURL(req), req.delete("url").delete("params").toJS()];
const maybeRequestArg = req => {
if (isString(req)) { return fromURL(req); }
if (req) { return fromJS(req); }
return Map();
};
const ensureRequestArg = func => req => func(maybeRequestArg(req));
const ensureRequestArg2 = func => (arg, req) => func(arg, maybeRequestArg(req));
// This actually performs the built request object
const performRequest = ensureRequestArg(req => fetch(...toFetchArguments(req)));
// Each of the following functions takes options and returns another
// function that performs the requested action on a request. They each
// default to containing an empty object, so you can simply call them
// without arguments to generate a request with only those properties.
const getCurriedRequestProcessor = flow([ensureRequestArg2, curry]);
const getPropSetFunctions = path => [
getCurriedRequestProcessor((val, req) => req.setIn(path, val)),
getCurriedRequestProcessor((val, req) => (req.getIn(path) ? req : req.setIn(path, val))),
];
const getPropMergeFunctions = path => [
getCurriedRequestProcessor((obj, req) => req.updateIn(path, (p=Map()) => p.merge(obj))),
getCurriedRequestProcessor((obj, req) => req.updateIn(path, (p=Map()) => Map(obj).merge(p))),
];
const [withMethod, withDefaultMethod] = getPropSetFunctions(["method"]);
const [withBody, withDefaultBody] = getPropSetFunctions(["body"]);
const [withParams, withDefaultParams] = getPropMergeFunctions(["params"]);
const [withHeaders, withDefaultHeaders] = getPropMergeFunctions(["headers"]);
// withRoot sets a root URL, unless the URL is already absolute
const absolutePath = new RegExp('^(?:[a-z]+:)?//', 'i');
const withRoot = getCurriedRequestProcessor((root, req) => req.update("url", p => {
if (absolutePath.test(p)) { return p; }
return (root && p && p[0] !== "/" && root[root.length - 1] !== "/")
? `${ root }/${ p }`
: `${ root }${ p }`;
}));
// withTimestamp needs no argument and has to run as late as possible,
// so it calls `withParams` only when it's actually called with a
// request.
const withTimestamp = ensureRequestArg(req => withParams({ ts: new Date().getTime() }, req));
export default {
toURL,
fromURL,
performRequest,
withMethod,
withDefaultMethod,
withBody,
withDefaultBody,
withHeaders,
withDefaultHeaders,
withParams,
withDefaultParams,
withRoot,
withTimestamp,
};