resolve.js: new file and implementation
ADD
src/index.js
+export { makeRootedResolver } from './resolve';

ADD
src/resolve.js
+export const makeRootedResolver = root => {
+ const rootSlash = root.endsWith('/') ? root : `${root}/`;
+ return (spec, referrer) => {
+ spec = `${spec}`;
+ referrer = `${referrer}`;
+
+ if (!referrer.startsWith(rootSlash)) {
+ throw TypeError(
+ `Referrer ${referrer} cannot be outside of root ${rootSlash}`,
+ );
+ }
+
+ // If spec is root-relative, take it from the root.
+ const rooted = spec[0] === '/';
+ if (!rooted && !spec.match(/^(\.|\.\.)($|\/)/)) {
+ // Not rooted or relative.
+ throw TypeError(`Only rooted or relative paths allowed, not ${spec}`);
+ }
+
+ const specEls = [];
+ if (!rooted) {
+ const relReferrer = referrer.slice(rootSlash.length);
+ const lastSlash = relReferrer.lastIndexOf('/');
+ if (lastSlash >= 0) {
+ // Not rooted, so prepend the referrer's dirname to the elements.
+ specEls.push(...relReferrer.slice(0, lastSlash).split('/'));
+ }
+ }
+ // Append the specifier elements.
+ specEls.push(...(rooted ? spec.slice(1) : spec).split('/'));
+
+ // Find the subpath elements.
+ const appendEls = [];
+ let i = 0;
+ while (i < specEls.length) {
+ if (specEls[i] === '.') {
+ // Do nothing.
+ } else if (specEls[i] === '..') {
+ // Navigate up in the appended elements, if possible.
+ if (appendEls.length === 0) {
+ throw TypeError(
+ `Specifier ${spec} cannot navigate above root ${rootSlash}`,
+ );
+ }
+ appendEls.pop();
+ } else {
+ // Navigate down according to the specifier.
+ appendEls.push(specEls[i]);
+ }
+ i += 1;
+ }
+
+ return rootSlash + appendEls.join('/');
+ };
+};

DELETE
test/sample-test.js
-import { test } from 'tape-promise/tape';
-
-// TODO: remove .only when you have this test working
-test.only('unique test description', async t => {
- try {
- t.true('various tests');
- t.equal(await Promise.resolve(123), 123);
- } catch (e) {
- t.isNot(e, e, 'unexpected exception');
- } finally {
- t.end();
- }
-});

ADD
test/test-resolve.js
+import { test } from 'tape-promise/tape';
+
+import { makeRootedResolver } from '../src';
+
+test('rooted resolve', async t => {
+ try {
+ const resolve = makeRootedResolver('file:///some/where/over');
+ const referrer = 'file:///some/where/over/rainbow/place.js';
+ t.equal(
+ resolve('./foo', referrer),
+ 'file:///some/where/over/rainbow/foo',
+ 'can use dot',
+ );
+ t.equal(
+ resolve('../root.js', referrer),
+ 'file:///some/where/over/root.js',
+ 'can use dotdot',
+ );
+ t.throws(
+ () => resolve('./foo/../../../superroot.js', referrer),
+ TypeError,
+ `cannot skip root`,
+ );
+ } catch (e) {
+ console.log('unexpected exception', e);
+ t.assert(false, e);
+ } finally {
+ t.end();
+ }
+});

resolve.js: new file and implementation