diff --git a/src/bin/create-index.js b/src/bin/create-index.js new file mode 100644 index 0000000..9f2adc3 --- /dev/null +++ b/src/bin/create-index.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +import yargs from 'yargs'; +import writeIndex from './../writeIndex'; + +let argv; + +argv = yargs + .demand(1) + .argv; + +writeIndex(argv._); + +/* eslint-disable no-console */ +console.log('OK!'); +/* eslint-enable no-console */ diff --git a/src/createIndexCode.js b/src/createIndexCode.js new file mode 100644 index 0000000..73812f1 --- /dev/null +++ b/src/createIndexCode.js @@ -0,0 +1,56 @@ +import _ from 'lodash'; + +let buildExportBlock, + buildImportBlock, + safeVariableName; + +safeVariableName = (fileName) => { + let indexOfDot; + + indexOfDot = fileName.indexOf('.'); + + if (indexOfDot === -1) { + return fileName; + } else { + return fileName.slice(0, indexOfDot); + } +}; + +buildImportBlock = (files) => { + let importBlock; + + importBlock = _.map(files, (fileName) => { + return 'import ' + safeVariableName(fileName) + ' from \'./' + fileName + '\';'; + }); + + importBlock = importBlock.join('\n'); + + return importBlock; +}; + +buildExportBlock = (files) => { + let exportBlock; + + exportBlock = _.map(files, (fileName) => { + return ' ' + safeVariableName(fileName); + }); + + exportBlock = 'export {\n' + exportBlock.join(',\n') + '\n};'; + + return exportBlock; +}; + +export default (filePaths) => { + let code, + sortedFilePaths; + + code = '\'create index\';\n\n'; + + if (filePaths.length) { + sortedFilePaths = filePaths.sort(); + + code += buildImportBlock(sortedFilePaths) + '\n\n' + buildExportBlock(sortedFilePaths) + '\n\n'; + } + + return code; +}; diff --git a/src/readDirectory.js b/src/readDirectory.js new file mode 100644 index 0000000..4d020ab --- /dev/null +++ b/src/readDirectory.js @@ -0,0 +1,97 @@ +import fs from 'fs'; +import _ from 'lodash'; +import path from 'path'; +import validateTargetDirectory from './validateTargetDirectory'; + +let hasIndex, + hasMultipleExtensions, + hasNoExtension, + isSafeName, + removeDuplicates; + +hasIndex = (directoryPath) => { + let indexPath; + + indexPath = path.resolve(directoryPath, 'index.js'); + + try { + fs.statSync(indexPath); + + return true; + } catch (error) { + return false; + } +}; + +hasNoExtension = (fileName) => { + let matches; + + matches = fileName.match(/\./g); + + return !matches; +}; + +hasMultipleExtensions = (fileName) => { + let matches; + + matches = fileName.match(/\./g); + + return matches && matches.length > 1; +}; + +isSafeName = (fileName) => { + return /^[a-z][a-z0-9\.]+$/i.test(fileName); +}; + +removeDuplicates = (files) => { + return _.filter(files, (fileName) => { + return !_.includes(files, fileName + '.js'); + }); +}; + +export default (directoryPath) => { + let children; + + validateTargetDirectory(directoryPath); + + children = fs.readdirSync(directoryPath); + + children = _.filter(children, (fileName) => { + let absolutePath, + isDirectory; + + absolutePath = path.resolve(directoryPath, fileName); + + isDirectory = fs.statSync(absolutePath).isDirectory(); + + if (!isSafeName(fileName)) { + return false; + } + + if (hasNoExtension(fileName) && !isDirectory) { + return false; + } + + if (hasMultipleExtensions(fileName)) { + return false; + } + + if (_.startsWith(fileName, 'index.js')) { + return false; + } + + if (!isDirectory && !_.endsWith(fileName, '.js')) { + return false; + } + + if (isDirectory && !hasIndex(absolutePath)) { + return false; + } + + return true; + }); + + children = removeDuplicates(children); + + return children.sort(); +}; diff --git a/src/sortByDepth.js b/src/sortByDepth.js new file mode 100644 index 0000000..dc7b36e --- /dev/null +++ b/src/sortByDepth.js @@ -0,0 +1,7 @@ +import _ from 'lodash'; + +export default (paths) => { + return _.sortBy(paths, (path) => { + return -path.split('/').length; + }); +}; diff --git a/src/validateTargetDirectory.js b/src/validateTargetDirectory.js new file mode 100644 index 0000000..abf0638 --- /dev/null +++ b/src/validateTargetDirectory.js @@ -0,0 +1,35 @@ +import _ from 'lodash'; +import fs from 'fs'; +import path from 'path'; + +export default (targetDirectory) => { + let indexFile, + indexFilePath, + stats; + + try { + stats = fs.statSync(targetDirectory); + } catch (error) { + throw new Error('Directory "' + targetDirectory + '" does not exist.'); + } + + if (!stats.isDirectory()) { + throw new Error('"' + targetDirectory + '" is not a directory.'); + } + + indexFilePath = path.resolve(targetDirectory, './index.js'); + + try { + fs.statSync(indexFilePath); + } catch (error) { + return true; + } + + indexFile = fs.readFileSync(indexFilePath, 'utf8'); + + if (!_.startsWith(indexFile, '\'create index\';\n')) { + throw new Error('"' + indexFilePath + '" unsafe index.'); + } + + return true; +}; diff --git a/src/writeIndex.js b/src/writeIndex.js new file mode 100644 index 0000000..bbb521c --- /dev/null +++ b/src/writeIndex.js @@ -0,0 +1,31 @@ +import _ from 'lodash'; +import createIndexCode from './createIndexCode'; +import fs from 'fs'; +import path from 'path'; +import validateTargetDirectory from './validateTargetDirectory'; +import readDirectory from './readDirectory'; +import sortByDepth from './sortByDepth'; + +export default (directoryPaths) => { + let sortedDirectoryPaths; + + sortedDirectoryPaths = sortByDepth(directoryPaths); + + _.forEach(sortedDirectoryPaths, (directoryPath) => { + validateTargetDirectory(directoryPath); + }); + + _.forEach(sortedDirectoryPaths, (directoryPath) => { + let indexCode, + indexFilePath, + siblings; + + siblings = readDirectory(directoryPath); + + indexCode = createIndexCode(siblings); + + indexFilePath = path.resolve(directoryPath, 'index.js'); + + fs.writeFileSync(indexFilePath, indexCode); + }); +}; diff --git a/tests/codeExample.js b/tests/codeExample.js new file mode 100644 index 0000000..c11c5aa --- /dev/null +++ b/tests/codeExample.js @@ -0,0 +1,5 @@ +import _ from 'lodash'; + +export default (code) => { + return _.trim(code) + '\n\n'; +}; diff --git a/tests/createIndexCode.js b/tests/createIndexCode.js new file mode 100644 index 0000000..2e67e4b --- /dev/null +++ b/tests/createIndexCode.js @@ -0,0 +1,89 @@ +/* eslint-disable no-restricted-syntax */ + +import { + expect +} from 'chai'; + +import createIndexCode from './../src/createIndexCode'; + +import codeExample from './codeExample'; + +describe('createIndexCode()', () => { + it('describes no children', () => { + let indexCode; + + indexCode = createIndexCode([]); + + expect(indexCode).to.equal(codeExample(` +'create index'; + `)); + }); + it('describes a single child', () => { + let indexCode; + + indexCode = createIndexCode(['foo']); + + expect(indexCode).to.equal(codeExample(` +'create index'; + +import foo from './foo'; + +export { + foo +}; + `)); + }); + it('describes multiple children', () => { + let indexCode; + + indexCode = createIndexCode(['bar', 'foo']); + + expect(indexCode).to.equal(codeExample(` +'create index'; + +import bar from './bar'; +import foo from './foo'; + +export { + bar, + foo +}; + `)); + }); + context('file with extension', () => { + it('removes the extension from the export statement', () => { + let indexCode; + + indexCode = createIndexCode(['foo.js']); + + expect(indexCode).to.equal(codeExample(` +'create index'; + +import foo from './foo.js'; + +export { + foo +}; + `)); + }); + }); + context('multiple, unsorted', () => { + it('sorts the files', () => { + let indexCode; + + indexCode = createIndexCode(['foo', 'bar']); + + expect(indexCode).to.equal(codeExample(` +'create index'; + +import bar from './bar'; +import foo from './foo'; + +export { + bar, + foo +}; + `)); + }); + }); +}); diff --git a/tests/fixtures/read-directory/children-directories-and-files/foo.js b/tests/fixtures/read-directory/children-directories-and-files/foo.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-directories-and-files/foo/index.js b/tests/fixtures/read-directory/children-directories-and-files/foo/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories-and-files/foo/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-directories-and-files/present.js b/tests/fixtures/read-directory/children-directories-and-files/present.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-directories-unsafe-name/bar-bar/index.js b/tests/fixtures/read-directory/children-directories-unsafe-name/bar-bar/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories-unsafe-name/bar-bar/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-directories-unsafe-name/foo-foo/index.js b/tests/fixtures/read-directory/children-directories-unsafe-name/foo-foo/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories-unsafe-name/foo-foo/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-directories-unsafe-name/present/index.js b/tests/fixtures/read-directory/children-directories-unsafe-name/present/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories-unsafe-name/present/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-directories-without-index/bar/bar.js b/tests/fixtures/read-directory/children-directories-without-index/bar/bar.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories-without-index/bar/bar.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-directories-without-index/foo/foo.js b/tests/fixtures/read-directory/children-directories-without-index/foo/foo.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories-without-index/foo/foo.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-directories-without-index/present.js b/tests/fixtures/read-directory/children-directories-without-index/present.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories-without-index/present.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-directories/bar/index.js b/tests/fixtures/read-directory/children-directories/bar/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories/bar/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-directories/foo/index.js b/tests/fixtures/read-directory/children-directories/foo/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-directories/foo/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-dot-files/present.js b/tests/fixtures/read-directory/children-dot-files/present.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-files-multiple-extensions/bar.bar.js b/tests/fixtures/read-directory/children-files-multiple-extensions/bar.bar.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-files-multiple-extensions/foo.foo.js b/tests/fixtures/read-directory/children-files-multiple-extensions/foo.foo.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-files-multiple-extensions/present.js b/tests/fixtures/read-directory/children-files-multiple-extensions/present.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-files-no-extension/bar b/tests/fixtures/read-directory/children-files-no-extension/bar new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-files-no-extension/foo b/tests/fixtures/read-directory/children-files-no-extension/foo new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-files-no-extension/present.js b/tests/fixtures/read-directory/children-files-no-extension/present.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-files/bar.js b/tests/fixtures/read-directory/children-files/bar.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-files/foo.js b/tests/fixtures/read-directory/children-files/foo.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/read-directory/children-index/bar/index.js b/tests/fixtures/read-directory/children-index/bar/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-index/bar/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-index/foo/index.js b/tests/fixtures/read-directory/children-index/foo/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-index/foo/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/read-directory/children-index/index.js b/tests/fixtures/read-directory/children-index/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/read-directory/children-index/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/validate-target-directory/no-index/present.js b/tests/fixtures/validate-target-directory/no-index/present.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/validate-target-directory/not-a-directory.js b/tests/fixtures/validate-target-directory/not-a-directory.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/validate-target-directory/safe-index/index.js b/tests/fixtures/validate-target-directory/safe-index/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/validate-target-directory/safe-index/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/validate-target-directory/unsafe-index/index.js b/tests/fixtures/validate-target-directory/unsafe-index/index.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/write-index/mixed/bar/index.js b/tests/fixtures/write-index/mixed/bar/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/write-index/mixed/bar/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/fixtures/write-index/mixed/foo.js b/tests/fixtures/write-index/mixed/foo.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/write-index/mixed/foo/index.js b/tests/fixtures/write-index/mixed/foo/index.js new file mode 100644 index 0000000..a45ca72 --- /dev/null +++ b/tests/fixtures/write-index/mixed/foo/index.js @@ -0,0 +1 @@ +'create index'; diff --git a/tests/readDirectory.js b/tests/readDirectory.js new file mode 100644 index 0000000..0a87dba --- /dev/null +++ b/tests/readDirectory.js @@ -0,0 +1,92 @@ +import { + expect +} from 'chai'; + +import path from 'path'; +import readDirectory from './../src/readDirectory'; + +const fixturesPath = path.resolve(__dirname, './fixtures/read-directory'); + +describe('readDirectory()', () => { + context('target directory contains child directories', () => { + it('gets names of the children directories', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-directories')); + + expect(names).to.deep.equal(['bar', 'foo']); + }); + }); + context('target directory contains child directories that do not contain index', () => { + it('gets names of the children directories', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-directories-without-index')); + + expect(names).to.deep.equal(['present.js']); + }); + }); + context('target directory contains child directories (unsafe name)', () => { + it('gets names of the children directories', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-directories-unsafe-name')); + + expect(names).to.deep.equal(['present']); + }); + }); + context.skip('target directory contains ./index.js', () => { + it('does not include ./index.js', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-index')); + + expect(names).to.deep.equal(['bar', 'foo']); + }); + }); + context('target directory contains files', () => { + it('refers to the files (with extension)', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-files')); + + expect(names).to.deep.equal(['bar.js', 'foo.js']); + }); + }); + context('target directory contains dot files', () => { + it('ignores files', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-dot-files')); + + expect(names).to.deep.equal(['present.js']); + }); + }); + context('target directory contains files with no extension', () => { + it('ignores files', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-files-no-extension')); + + expect(names).to.deep.equal(['present.js']); + }); + }); + context('target directory contains files with multiple extensions', () => { + it('ignores files', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-files-multiple-extensions')); + + expect(names).to.deep.equal(['present.js']); + }); + }); + context('target directory contains directories and files with the same name', () => { + it('prefers file', () => { + let names; + + names = readDirectory(path.resolve(fixturesPath, 'children-directories-and-files')); + + expect(names).to.deep.equal(['foo.js', 'present.js']); + }); + }); +}); diff --git a/tests/sortByDepth.js b/tests/sortByDepth.js new file mode 100644 index 0000000..60dfefc --- /dev/null +++ b/tests/sortByDepth.js @@ -0,0 +1,22 @@ +import { + expect +} from 'chai'; +import sortByDepth from './../src/sortByDepth'; + +describe('sortByDepth()', () => { + it('sorts from deepest to the most shallow', () => { + let paths, + sortedPaths; + + paths = [ + '/b', + '/a', + '/a/b/c', + '/a/b' + ]; + + sortedPaths = sortByDepth(paths); + + expect(sortedPaths).to.deep.equal(['/a/b/c', '/a/b', '/b', '/a']); + }); +}); diff --git a/tests/validateTargetDirectory.js b/tests/validateTargetDirectory.js new file mode 100644 index 0000000..c8e7699 --- /dev/null +++ b/tests/validateTargetDirectory.js @@ -0,0 +1,47 @@ +/* eslint-disable max-nested-callbacks */ + +import { + expect +} from 'chai'; +import path from 'path'; +import validateTargetDirectory from './../src/validateTargetDirectory'; + +const fixturesPath = path.resolve(__dirname, './fixtures/validate-target-directory'); + +describe('validateTargetDirectory()', () => { + describe('directory path', () => { + context('refers to a directory that does not exist', () => { + it('throws an error', () => { + expect(() => { + validateTargetDirectory(path.resolve(fixturesPath, 'does-not-exist')); + }).to.throw(Error, 'Directory "' + path.resolve(fixturesPath, 'does-not-exist') + '" does not exist.'); + }); + }); + context('refers to a file', () => { + it('throws an error', () => { + expect(() => { + validateTargetDirectory(path.resolve(fixturesPath, 'not-a-directory.js')); + }).to.throw(Error, '"' + path.resolve(fixturesPath, 'not-a-directory.js') + '" is not a directory.'); + }); + }); + }); + describe('target index', () => { + context('no index', () => { + it('returns true', () => { + expect(validateTargetDirectory(path.resolve(fixturesPath, 'no-index'))).to.equal(true); + }); + }); + context.skip('safe', () => { + it('returns true', () => { + expect(validateTargetDirectory(path.resolve(fixturesPath, 'safe-index'))).to.equal(true); + }); + }); + context('unsafe', () => { + it('throws an error', () => { + expect(() => { + validateTargetDirectory(path.resolve(fixturesPath, 'unsafe-index')); + }).to.throw(Error, '"' + path.resolve(fixturesPath, 'unsafe-index/index.js') + '" unsafe index.'); + }); + }); + }); +}); diff --git a/tests/writeIndex.js b/tests/writeIndex.js new file mode 100644 index 0000000..2cfcfe6 --- /dev/null +++ b/tests/writeIndex.js @@ -0,0 +1,42 @@ +/* eslint-disable no-restricted-syntax */ + +import { + expect +} from 'chai'; +import fs from 'fs'; +import path from 'path'; +import codeExample from './codeExample'; +import writeIndex from './../src/writeIndex'; + +const fixturesPath = path.resolve(__dirname, './fixtures/write-index'); + +describe('writeIndex()', () => { + it('creates index in target directory', () => { + let indexCode, + indexFilePath; + + indexFilePath = path.resolve(fixturesPath, './mixed/index.js'); + + try { + fs.unlinkSync(indexFilePath); + } catch (error) { + + } + + writeIndex([path.resolve(fixturesPath, './mixed')]); + + indexCode = fs.readFileSync(indexFilePath, 'utf8'); + + expect(indexCode).to.equal(codeExample(` +'create index'; + +import bar from './bar'; +import foo from './foo.js'; + +export { + bar, + foo +}; + `)); + }); +});