├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src ├── bin │ └── create-index.js ├── index.js └── utilities │ ├── constants.js │ ├── createIndexCode.js │ ├── findIndexFiles.js │ ├── hasIndex.js │ ├── index.js │ ├── log.js │ ├── readDirectory.js │ ├── readIndexConfig.js │ ├── sortByDepth.js │ ├── validateTargetDirectory.js │ ├── writeIndex.js │ └── writeIndexCli.js └── test ├── .eslintrc ├── codeExample.js ├── createIndexCode.js ├── findIndexFiles.js ├── fixtures ├── find-index-files │ ├── do-not-find-no-index │ │ └── not-index.js │ ├── do-not-find │ │ └── index.js │ ├── find-1 │ │ └── index.js │ └── nested │ │ ├── do-not-find │ │ └── index.js │ │ ├── find-2 │ │ └── index.js │ │ └── find-3 │ │ └── index.js ├── read-directory │ ├── children-directories-and-files │ │ ├── foo.js │ │ ├── foo │ │ │ └── index.js │ │ └── present.js │ ├── children-directories-short-name │ │ ├── F │ │ │ └── index.js │ │ ├── T │ │ │ └── index.js │ │ ├── __ │ │ │ └── index.js │ │ └── o │ │ │ └── index.js │ ├── children-directories-unsafe-name │ │ ├── bar-bar │ │ │ └── index.js │ │ ├── foo-foo │ │ │ └── index.js │ │ └── present │ │ │ └── index.js │ ├── children-directories-without-index │ │ ├── bar │ │ │ └── bar.js │ │ ├── foo │ │ │ └── foo.js │ │ └── present.js │ ├── children-directories │ │ ├── bar │ │ │ └── index.js │ │ └── foo │ │ │ └── index.js │ ├── children-dot-files │ │ └── present.js │ ├── children-files-alt-extension-with-homonyms │ │ ├── bar.js │ │ ├── bar.jsx │ │ └── present.js │ ├── children-files-alt-extension │ │ ├── bar.jsx │ │ ├── foo.css │ │ └── present.js │ ├── children-files-multiple-extensions │ │ ├── bar.bar.js │ │ ├── foo.foo.js │ │ └── present.js │ ├── children-files-no-extension │ │ ├── bar │ │ ├── foo │ │ └── present.js │ ├── children-files │ │ ├── bar.js │ │ └── foo.js │ └── children-index │ │ ├── bar │ │ └── index.js │ │ ├── foo │ │ └── index.js │ │ └── index.js ├── read-index-config │ ├── no-index │ │ └── foo.js │ ├── with-config │ │ └── index.js │ ├── with-invalid-config │ │ └── index.js │ └── without-config │ │ └── index.js ├── validate-target-directory │ ├── no-index │ │ └── present.js │ ├── not-a-directory.js │ ├── safe-index-with-banner │ │ └── index.js │ ├── safe-index │ │ └── index.js │ └── unsafe-index │ │ └── index.js └── write-index │ ├── mixed │ ├── bar │ │ └── index.js │ ├── foo.js │ ├── foo │ │ └── index.js │ └── index.js │ └── with-config │ ├── bar.js │ ├── foo.js │ └── index.js ├── readDirectory.js ├── readIndexConfig.js ├── sortByDepth.js ├── validateTargetDirectory.js └── writeIndex.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-transform-modules-commonjs", 4 | "@babel/plugin-proposal-export-default-from", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-transform-destructuring", 7 | "@babel/plugin-transform-parameters" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/fixtures 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "canonical" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules 4 | *.log 5 | .* 6 | !.babelrc 7 | !.editorconfig 8 | !.eslintignore 9 | !.eslintrc 10 | !.gitignore 11 | !.npmignore 12 | !.travis.yml 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage 2 | fixtures 3 | src 4 | test 5 | .* 6 | *.log 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - 7 5 | - 6 6 | - 5 7 | script: 8 | - npm run lint 9 | - npm run test 10 | - npm run build 11 | after_success: 12 | - semantic-release 13 | notifications: 14 | email: false 15 | sudo: false 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Gajus Kuizinas (http://gajus.com/) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-index 2 | 3 | [![NPM version](http://img.shields.io/npm/v/create-index.svg?style=flat-square)](https://www.npmjs.org/package/create-index) 4 | [![Travis build status](http://img.shields.io/travis/gajus/create-index/master.svg?style=flat-square)](https://travis-ci.org/gajus/create-index) 5 | [![js-canonical-style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical) 6 | 7 | `create-index` program creates (and maintains) ES6 `./index.js` file in target directories that imports and exports sibling files and directories. 8 | 9 | ## Example 10 | 11 | ```sh 12 | > tree ./ 13 | ./ 14 | ├── bar.js 15 | └── foo.js 16 | 17 | 0 directories, 2 files 18 | 19 | > create-index ./ 20 | [13:17:34] Target directories [ './' ] 21 | [13:17:34] Update index: false 22 | [13:17:34] ./index.js [created index] 23 | [13:17:34] Done 24 | 25 | > tree 26 | . 27 | ├── bar.js 28 | ├── foo.js 29 | └── index.js 30 | 31 | 0 directories, 3 files 32 | ``` 33 | 34 | This created `index.js` with: 35 | 36 | ```js 37 | // @create-index 38 | 39 | export { default as bar } from './bar.js'; 40 | export { default as foo } from './foo.js'; 41 | 42 | ``` 43 | 44 | Lets create a new file and re-run `create-index`: 45 | 46 | ```js 47 | > touch baz.js 48 | > tree ./ 49 | ./ 50 | ├── bar.js 51 | ├── baz.js 52 | ├── foo.js 53 | └── index.js 54 | 55 | 0 directories, 4 files 56 | 57 | > create-index ./ 58 | [13:21:55] Target directories [ './' ] 59 | [13:21:55] Update index: false 60 | [13:21:55] ./index.js [updated index] 61 | [13:21:55] Done 62 | ``` 63 | 64 | This have updated `index.js` file: 65 | 66 | ```js 67 | // @create-index 68 | 69 | export { default as bar } from './bar.js'; 70 | export { default as baz } from './baz.js'; 71 | export { default as foo } from './foo.js'; 72 | 73 | ``` 74 | 75 | ## Usage 76 | 77 | ### Using CLI Program 78 | 79 | ```sh 80 | npm install create-index 81 | 82 | create-index --help 83 | 84 | Options: 85 | --recursive, -r Create/update index files recursively. Halts on any 86 | unsafe "index.js" files. [boolean] [default: false] 87 | --ignoreUnsafe, -i Ignores unsafe "index.js" files instead of halting. 88 | [boolean] [default: false] 89 | --ignoreDirectories, -d Ignores importing directories into the index file, 90 | even if they have a safe "index.js". 91 | [boolean] [default: false] 92 | --update, -u Updates only previously created index files 93 | (recursively). [boolean] [default: false] 94 | --banner Add a custom banner at the top of the index file 95 | [string] 96 | --extensions, -x Allows some extensions to be parsed as valid source. 97 | First extension will always be preferred to homonyms 98 | with another allowed extension. 99 | [array] [default: ["js"]] 100 | --outputFile, -o Output file [string] [default: "index.js"] [array] [default: ["js"]] 101 | 102 | Examples: 103 | create-index ./src ./src/utilities Creates or updates an existing 104 | create-index index file in the target 105 | (./src, ./src/utilities) directories. 106 | create-index --update ./src ./tests Finds all create-index index files in 107 | the target directories and descending 108 | directories. Updates found index 109 | files. 110 | create-index ./src --extensions js jsx Creates or updates an existing 111 | create-index index file in the target 112 | (./src) directory for both .js and 113 | .jsx extensions. 114 | ``` 115 | 116 | ### Using `create-index` Programmatically 117 | 118 | ```js 119 | import { 120 | writeIndex 121 | } from 'create-index'; 122 | 123 | /** 124 | * @type {Function} 125 | * @param {Array} directoryPaths 126 | * @throws {Error} Directory "..." does not exist. 127 | * @throws {Error} "..." is not a directory. 128 | * @throws {Error} "..." unsafe index. 129 | * @returns {boolean} 130 | */ 131 | writeIndex; 132 | ``` 133 | 134 | Note that the `writeIndex` function is synchronous. 135 | 136 | ```js 137 | import { 138 | findIndexFiles 139 | } from 'create-index'; 140 | 141 | /** 142 | * @type {Function} 143 | * @param {string} directoryPath 144 | * @returns {Array} List of directory paths that have create-index index file. 145 | */ 146 | findIndexFiles; 147 | ``` 148 | 149 | ### Gulp 150 | 151 | Since [Gulp](http://gulpjs.com/) can ran arbitrary JavaScript code, there is no need for a separate plugin. See [Using `create-index` Programmatically](#using-create-index-programmatically). 152 | 153 | ```js 154 | import { 155 | writeIndex 156 | } from 'create-index'; 157 | 158 | gulp.task('create-index', () => { 159 | writeIndex(['./target_directory']); 160 | }); 161 | ``` 162 | 163 | Note that the `writeIndex` function is synchronous. 164 | 165 | ## Implementation 166 | 167 | `create-index` program will look into the target directory. 168 | 169 | If there is no `./index.js`, it will create a new file, e.g. 170 | 171 | ```js 172 | // @create-index 173 | ``` 174 | 175 | Created index file must start with `// @create-index\n\n`. This is used to make sure that `create-index` does not accidentally overwrite your local files. 176 | 177 | If there are sibling files, index file will `import` them and `export`, e.g. 178 | 179 | ```sh 180 | children-directories-and-files git:(master) ✗ ls -lah 181 | total 0 182 | drwxr-xr-x 5 gajus staff 170B 6 Jan 15:39 . 183 | drwxr-xr-x 10 gajus staff 340B 6 Jan 15:53 .. 184 | drwxr-xr-x 2 gajus staff 68B 6 Jan 15:29 bar 185 | drwxr-xr-x 2 gajus staff 68B 6 Jan 15:29 foo 186 | -rw-r--r-- 1 gajus staff 0B 6 Jan 15:29 foo.js 187 | ``` 188 | 189 | Given the above directory contents, `./index.js` will be: 190 | 191 | ```js 192 | // @create-index 193 | 194 | import { default as bar } from './bar'; 195 | import { default as foo } from './foo.js'; 196 | 197 | export { 198 | bar, 199 | foo 200 | }; 201 | ``` 202 | 203 | When file has the same name as a sibling directory, file `import` takes precedence. 204 | 205 | Directories that do not have `./index.js` in themselves will be excluded. 206 | 207 | When run again, `create-index` will update existing `./index.js` if it starts with `// @create-index\n\n`. 208 | 209 | If `create-index` is executed against a directory that contains `./index.js`, which does not start with `// @create-index\n\n`, an error will be thrown. 210 | 211 | ## Ignore files on `--update` 212 | 213 | `create-index` can ignore files in a directory if `./index.js` contains special object with defined `ignore` property which takes `an array` of `regular expressions` defined as `strings`, e.g. 214 | 215 | ```js 216 | > cat index.js 217 | // @create-index {"ignore": ["/baz.js$/"]} 218 | ``` 219 | 220 | ```js 221 | > tree ./ 222 | ./ 223 | ├── bar.js 224 | ├── baz.js 225 | ├── foo.js 226 | └── index.js 227 | 228 | 0 directories, 4 files 229 | ``` 230 | 231 | Given the above directory contents, after running `create-index` with `--update` flag, `./index.js` will be: 232 | 233 | ```js 234 | // @create-index {"ignore": ["/baz.js$/"]} 235 | 236 | import { default as bar } from './bar.js'; 237 | import { default as foo } from './foo.js'; 238 | 239 | export { 240 | bar, 241 | foo 242 | }; 243 | ``` 244 | 245 | ## Generate `index.tsx` for TypeScript 246 | 247 | ```bash 248 | create-index src/components/Icons --extensions tsx --outputFile index.tsx 249 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "gajus@gajus.com", 4 | "name": "Gajus Kuizinas", 5 | "url": "http://gajus.com" 6 | }, 7 | "bin": "./dist/bin/create-index.js", 8 | "dependencies": { 9 | "chalk": "^4.0.0", 10 | "glob": "^7.1.6", 11 | "lodash": "^4.17.15", 12 | "moment": "^2.25.3", 13 | "yargs": "^15.3.1" 14 | }, 15 | "description": "Creates ES6 ./index.js file in target directories that imports and exports all sibling files and directories.", 16 | "devDependencies": { 17 | "@babel/cli": "^7.0.0", 18 | "@babel/core": "^7.0.0", 19 | "@babel/plugin-proposal-export-default-from": "^7.0.0", 20 | "@babel/plugin-proposal-export-namespace-from": "^7.0.0", 21 | "@babel/plugin-transform-destructuring": "^7.0.0", 22 | "@babel/plugin-transform-modules-commonjs": "^7.0.0", 23 | "@babel/plugin-transform-parameters": "^7.0.0", 24 | "@babel/register": "^7.0.0", 25 | "chai": "^4.2.0", 26 | "eslint": "^7.0.0", 27 | "eslint-config-canonical": "^20.0.4", 28 | "husky": "^4.2.5", 29 | "mocha": "^7.1.2", 30 | "semantic-release": "^17.0.7" 31 | }, 32 | "engines": { 33 | "node": ">=5" 34 | }, 35 | "keywords": [ 36 | "index", 37 | "import", 38 | "export" 39 | ], 40 | "license": "BSD-3-Clause", 41 | "main": "./dist/index.js", 42 | "name": "create-index", 43 | "repository": { 44 | "type": "git", 45 | "url": "https://github.com/gajus/create-index" 46 | }, 47 | "husky": { 48 | "hooks": { 49 | "pre-commit": "npm run test" 50 | } 51 | }, 52 | "scripts": { 53 | "build": "rm -fr ./dist && NODE_ENV=production babel --source-maps --copy-files ./src --out-dir ./dist", 54 | "create-index": "node ./dist/bin/create-index ./src/utilities", 55 | "lint": "NODE_ENV=development eslint ./src ./test", 56 | "test": "NODE_ENV=development mocha --require @babel/register" 57 | }, 58 | "version": "0.0.0-development" 59 | } 60 | -------------------------------------------------------------------------------- /src/bin/create-index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint-disable filenames/match-regex */ 4 | 5 | import yargs from 'yargs'; 6 | import { 7 | writeIndexCli, 8 | } from '../utilities'; 9 | 10 | const argv = yargs 11 | .demand(1) 12 | .options({ 13 | recursive: { 14 | alias: 'r', 15 | default: false, 16 | description: 'Create/update index files recursively. Halts on any unsafe "index.js" files.', 17 | type: 'boolean', 18 | }, 19 | }) 20 | .options({ 21 | ignoreUnsafe: { 22 | alias: 'i', 23 | default: false, 24 | description: 'Ignores unsafe "index.js" files instead of halting.', 25 | type: 'boolean', 26 | }, 27 | }) 28 | .options({ 29 | ignoreDirectories: { 30 | alias: 'd', 31 | default: false, 32 | description: 'Ignores importing directories into the index file, even if they have a safe "index.js".', 33 | type: 'boolean', 34 | }, 35 | }) 36 | .options({ 37 | update: { 38 | alias: 'u', 39 | default: false, 40 | description: 'Updates only previously created index files (recursively).', 41 | type: 'boolean', 42 | }, 43 | }) 44 | .options({ 45 | banner: { 46 | description: 'Add a custom banner at the top of the index file', 47 | type: 'string', 48 | }, 49 | }) 50 | .options({ 51 | extensions: { 52 | alias: 'x', 53 | default: ['js'], 54 | description: 'Allows some extensions to be parsed as valid source. First extension will always be preferred to homonyms with another allowed extension.', 55 | type: 'array', 56 | }, 57 | }) 58 | .options({ 59 | outputFile: { 60 | alias: 'o', 61 | default: 'index.js', 62 | description: 'Output file', 63 | type: 'string', 64 | }, 65 | }) 66 | .example( 67 | 'create-index ./src ./src/utilities', 68 | 'Creates or updates an existing create-index index file in the target (./src, ./src/utilities) directories.', 69 | ) 70 | .example( 71 | 'create-index --update ./src ./tests', 72 | 'Finds all create-index index files in the target directories and descending directories. Updates found index files.', 73 | ) 74 | .example( 75 | 'create-index ./src --extensions js jsx', 76 | 'Creates or updates an existing create-index index file in the target (./src) directory for both .js and .jsx extensions.', 77 | ) 78 | .argv; 79 | 80 | writeIndexCli(argv._, { 81 | banner: argv.banner, 82 | extensions: argv.extensions, 83 | ignoreDirectories: argv.ignoreDirectories, 84 | ignoreUnsafe: argv.ignoreUnsafe, 85 | outputFile: argv.outputFile, 86 | recursive: argv.recursive, 87 | updateIndex: argv.update, 88 | }); 89 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | findIndexFiles, 3 | writeIndex, 4 | } from './utilities'; 5 | -------------------------------------------------------------------------------- /src/utilities/constants.js: -------------------------------------------------------------------------------- 1 | export const CREATE_INDEX_PATTERN = /(?:^|[\n\r]+)\/\/ @create-index\s?({.*})?[\n\r]+/; 2 | -------------------------------------------------------------------------------- /src/utilities/createIndexCode.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | const safeVariableName = (fileName) => { 4 | const indexOfDot = fileName.indexOf('.'); 5 | 6 | if (indexOfDot === -1) { 7 | return fileName; 8 | } else { 9 | return fileName.slice(0, indexOfDot); 10 | } 11 | }; 12 | 13 | const buildExportBlock = (files) => { 14 | let importBlock; 15 | 16 | importBlock = _.map(files, (fileName) => { 17 | return 'export { default as ' + safeVariableName(fileName) + ' } from \'./' + fileName + '\';'; 18 | }); 19 | 20 | importBlock = importBlock.join('\n'); 21 | 22 | return importBlock; 23 | }; 24 | 25 | export default (filePaths, options = {}) => { 26 | let code; 27 | let configCode; 28 | 29 | code = ''; 30 | configCode = ''; 31 | 32 | if (options.banner) { 33 | const banners = _.isArray(options.banner) ? options.banner : [options.banner]; 34 | 35 | banners.forEach((banner) => { 36 | code += banner + '\n'; 37 | }); 38 | 39 | code += '\n'; 40 | } 41 | 42 | if (options.config && _.size(options.config) > 0) { 43 | configCode += ' ' + JSON.stringify(options.config); 44 | } 45 | 46 | code += '// @create-index' + configCode + '\n\n'; 47 | 48 | if (filePaths.length) { 49 | const sortedFilePaths = filePaths.sort(); 50 | 51 | code += buildExportBlock(sortedFilePaths) + '\n\n'; 52 | } 53 | 54 | return code; 55 | }; 56 | -------------------------------------------------------------------------------- /src/utilities/findIndexFiles.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import _ from 'lodash'; 3 | import glob from 'glob'; 4 | import validateTargetDirectory from './validateTargetDirectory'; 5 | 6 | export default (directoryPath, options = {}) => { 7 | let fileName; 8 | let targetDirectories; 9 | 10 | fileName = options.fileName || 'index.js'; 11 | fileName = './**/' + fileName; 12 | 13 | targetDirectories = glob.sync(path.join(directoryPath, fileName)); 14 | 15 | targetDirectories = _.filter(targetDirectories, (targetDirectoryPath) => { 16 | return validateTargetDirectory(path.dirname(targetDirectoryPath), { 17 | outputFile: options.fileName, 18 | silent: options.silent, 19 | }); 20 | }); 21 | 22 | targetDirectories = _.map(targetDirectories, path.dirname); 23 | 24 | return targetDirectories; 25 | }; 26 | -------------------------------------------------------------------------------- /src/utilities/hasIndex.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | export default (directoryPath, options = {}) => { 5 | const indexPath = path.resolve(directoryPath, options.outputFile || 'index.js'); 6 | 7 | try { 8 | fs.statSync(indexPath); 9 | 10 | return true; 11 | } catch { 12 | return false; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/utilities/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | 3 | export createIndexCode from './createIndexCode'; 4 | export findIndexFiles from './findIndexFiles'; 5 | export log from './log'; 6 | export readDirectory from './readDirectory'; 7 | export sortByDepth from './sortByDepth'; 8 | export validateTargetDirectory from './validateTargetDirectory'; 9 | export writeIndex from './writeIndex'; 10 | export writeIndexCli from './writeIndexCli'; 11 | -------------------------------------------------------------------------------- /src/utilities/log.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import moment from 'moment'; 3 | 4 | export default (...append) => { 5 | // eslint-disable-next-line no-console 6 | console.log(chalk.dim('[' + moment().format('HH:mm:ss') + ']'), ...append); 7 | }; 8 | -------------------------------------------------------------------------------- /src/utilities/readDirectory.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import _ from 'lodash'; 4 | import hasIndex from './hasIndex'; 5 | import validateTargetDirectory from './validateTargetDirectory'; 6 | 7 | const hasNoExtension = (fileName) => { 8 | const matches = fileName.match(/\./g); 9 | 10 | return !matches; 11 | }; 12 | 13 | const hasMultipleExtensions = (fileName) => { 14 | const matches = fileName.match(/\./g); 15 | 16 | return matches && matches.length > 1; 17 | }; 18 | 19 | const isSafeName = (fileName) => { 20 | return /^[_a-z][\w.]*$/i.test(fileName); 21 | }; 22 | 23 | const stripExtension = (fileName) => { 24 | const pos = fileName.lastIndexOf('.'); 25 | 26 | if (pos === -1) { 27 | return fileName; 28 | } 29 | 30 | return fileName.slice(0, Math.max(0, pos)); 31 | }; 32 | 33 | const removeDuplicates = (files, preferredExtension) => { 34 | return _.filter(files, (fileName) => { 35 | const withoutExtension = stripExtension(fileName); 36 | const mainAlternative = withoutExtension + '.' + preferredExtension; 37 | 38 | if (mainAlternative === fileName) { 39 | return true; 40 | } 41 | 42 | return !_.includes(files, mainAlternative); 43 | }); 44 | }; 45 | 46 | const removeIgnoredFiles = (files, ignorePatterns = []) => { 47 | if (ignorePatterns.length === 0) { 48 | return files; 49 | } 50 | 51 | const patterns = ignorePatterns.map((pattern) => { 52 | if (_.startsWith(pattern, '/') && _.endsWith(pattern, '/')) { 53 | const patternWithoutSlashes = pattern.slice(1, -1); 54 | 55 | return new RegExp(patternWithoutSlashes); 56 | } 57 | 58 | return new RegExp(pattern); 59 | }); 60 | 61 | return _.filter(files, (fileName) => { 62 | let pattern; 63 | 64 | for (pattern of patterns) { 65 | if (fileName.match(pattern) !== null) { 66 | return false; 67 | } 68 | } 69 | 70 | return true; 71 | }); 72 | }; 73 | 74 | export default (directoryPath, options = {}) => { 75 | if (!validateTargetDirectory(directoryPath, options)) { 76 | return false; 77 | } 78 | 79 | const { 80 | extensions = ['js'], 81 | config = {}, 82 | ignoreDirectories = false, 83 | } = options; 84 | 85 | let children; 86 | 87 | children = fs.readdirSync(directoryPath); 88 | 89 | children = _.filter(children, (fileName) => { 90 | const absolutePath = path.resolve(directoryPath, fileName); 91 | const isDirectory = fs.statSync(absolutePath).isDirectory(); 92 | 93 | if (!isSafeName(fileName)) { 94 | return false; 95 | } 96 | 97 | if (hasNoExtension(fileName) && !isDirectory) { 98 | return false; 99 | } 100 | 101 | if (hasMultipleExtensions(fileName)) { 102 | return false; 103 | } 104 | 105 | if (_.startsWith(fileName, options.outputFile || 'index.js')) { 106 | return false; 107 | } 108 | 109 | if (!isDirectory && !extensions.some((extension) => { 110 | return _.endsWith(fileName, '.' + extension); 111 | })) { 112 | return false; 113 | } 114 | 115 | if (isDirectory && (!hasIndex(absolutePath, options) || ignoreDirectories)) { 116 | return false; 117 | } 118 | 119 | return true; 120 | }); 121 | 122 | children = removeDuplicates(children, extensions[0]); 123 | children = removeIgnoredFiles(children, config.ignore); 124 | 125 | return children.sort(); 126 | }; 127 | -------------------------------------------------------------------------------- /src/utilities/readIndexConfig.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import hasIndex from './hasIndex'; 4 | import {CREATE_INDEX_PATTERN} from './constants'; 5 | 6 | export default (directoryPath, options = {}) => { 7 | if (!hasIndex(directoryPath, options)) { 8 | return {}; 9 | } 10 | 11 | const indexPath = path.resolve(directoryPath, options.outputFile || 'index.js'); 12 | const indexContents = fs.readFileSync(indexPath, 'utf-8'); 13 | const found = indexContents.match(CREATE_INDEX_PATTERN); 14 | const configLine = typeof found[1] === 'string' ? found[1].trim() : ''; 15 | 16 | if (configLine.length === 0) { 17 | return {}; 18 | } 19 | 20 | let config; 21 | 22 | try { 23 | config = JSON.parse(configLine); 24 | } catch { 25 | throw new Error( 26 | '"' + indexPath + '" contains invalid configuration object.\n' + 27 | 'Configuration object must be a valid JSON.', 28 | ); 29 | } 30 | 31 | return config; 32 | }; 33 | -------------------------------------------------------------------------------- /src/utilities/sortByDepth.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default (paths) => { 4 | return _.sortBy(paths, (path) => { 5 | return -path.split('/').length; 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/utilities/validateTargetDirectory.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import {CREATE_INDEX_PATTERN} from './constants'; 4 | 5 | export default (targetDirectory, options = {}) => { 6 | const silent = options.silent; 7 | let stats; 8 | 9 | try { 10 | stats = fs.statSync(targetDirectory); 11 | } catch { 12 | if (silent) { 13 | return false; 14 | } else { 15 | throw new Error('Directory "' + targetDirectory + '" does not exist.'); 16 | } 17 | } 18 | 19 | if (!stats.isDirectory()) { 20 | if (silent) { 21 | return false; 22 | } else { 23 | throw new Error('"' + targetDirectory + '" is not a directory.'); 24 | } 25 | } 26 | 27 | const indexFilePath = path.resolve(targetDirectory, './' + (options.outputFile || 'index.js')); 28 | 29 | try { 30 | fs.statSync(indexFilePath); 31 | } catch { 32 | return true; 33 | } 34 | 35 | const indexFile = fs.readFileSync(indexFilePath, 'utf8'); 36 | 37 | if (!indexFile.match(CREATE_INDEX_PATTERN)) { 38 | if (silent) { 39 | return false; 40 | } else { 41 | throw new Error('"' + indexFilePath + '" unsafe index.'); 42 | } 43 | } 44 | 45 | return true; 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /src/utilities/writeIndex.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import _ from 'lodash'; 4 | import createIndexCode from './createIndexCode'; 5 | import validateTargetDirectory from './validateTargetDirectory'; 6 | import readDirectory from './readDirectory'; 7 | import readIndexConfig from './readIndexConfig'; 8 | import sortByDepth from './sortByDepth'; 9 | 10 | export default (directoryPaths, options = {}) => { 11 | const sortedDirectoryPaths = sortByDepth(directoryPaths) 12 | .filter((directoryPath) => { 13 | return validateTargetDirectory(directoryPath, {outputFile: options.outputFile, 14 | silent: options.ignoreUnsafe}); 15 | }); 16 | 17 | _.forEach(sortedDirectoryPaths, (directoryPath) => { 18 | const config = readIndexConfig(directoryPath, options); 19 | const optionsWithConfig = Object.assign({}, options, {config}); 20 | const siblings = readDirectory(directoryPath, optionsWithConfig); 21 | const indexCode = createIndexCode(siblings, optionsWithConfig); 22 | const indexFilePath = path.resolve(directoryPath, options.outputFile || 'index.js'); 23 | 24 | fs.writeFileSync(indexFilePath, indexCode); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/utilities/writeIndexCli.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import _ from 'lodash'; 4 | import chalk from 'chalk'; 5 | import createIndexCode from './createIndexCode'; 6 | import validateTargetDirectory from './validateTargetDirectory'; 7 | import readDirectory from './readDirectory'; 8 | import readIndexConfig from './readIndexConfig'; 9 | import sortByDepth from './sortByDepth'; 10 | import log from './log'; 11 | import findIndexFiles from './findIndexFiles'; 12 | 13 | export default (directoryPaths, options = {}) => { 14 | let sortedDirectoryPaths; 15 | 16 | sortedDirectoryPaths = sortByDepth(directoryPaths); 17 | 18 | log('Target directories', sortedDirectoryPaths); 19 | log('Output file', options.outputFile); 20 | if (options.updateIndex) { 21 | log('Update index:', options.updateIndex ? chalk.green('true') : chalk.red('false')); 22 | } else { 23 | log('Recursive:', options.recursive ? chalk.green('true') : chalk.red('false')); 24 | log('Ignore unsafe:', options.ignoreUnsafe ? chalk.green('true') : chalk.red('false')); 25 | log('Extensions:', chalk.green(options.extensions)); 26 | } 27 | 28 | if (options.updateIndex || options.recursive) { 29 | sortedDirectoryPaths = _.map(sortedDirectoryPaths, (directory) => { 30 | return findIndexFiles(directory, { 31 | fileName: options.updateIndex ? options.outputFile || 'index.js' : '*', 32 | silent: options.updateIndex || options.ignoreUnsafe, 33 | }); 34 | }); 35 | sortedDirectoryPaths = _.flatten(sortedDirectoryPaths); 36 | sortedDirectoryPaths = _.uniq(sortedDirectoryPaths); 37 | sortedDirectoryPaths = sortByDepth(sortedDirectoryPaths); 38 | 39 | log('Updating index files in:', sortedDirectoryPaths.reverse().join(', ')); 40 | } 41 | 42 | sortedDirectoryPaths = sortedDirectoryPaths.filter((directoryPath) => { 43 | return validateTargetDirectory(directoryPath, {outputFile: options.outputFile, 44 | silent: options.ignoreUnsafe}); 45 | }); 46 | 47 | _.forEach(sortedDirectoryPaths, (directoryPath) => { 48 | let existingIndexCode; 49 | 50 | const config = readIndexConfig(directoryPath, options); 51 | 52 | const siblings = readDirectory(directoryPath, { 53 | config, 54 | extensions: options.extensions, 55 | ignoreDirectories: options.ignoreDirectories, 56 | silent: options.ignoreUnsafe, 57 | }); 58 | 59 | const indexCode = createIndexCode(siblings, { 60 | banner: options.banner, 61 | config, 62 | }); 63 | 64 | const indexFilePath = path.resolve(directoryPath, options.outputFile || 'index.js'); 65 | 66 | try { 67 | existingIndexCode = fs.readFileSync(indexFilePath, 'utf8'); 68 | 69 | /* eslint-disable no-empty */ 70 | } catch { 71 | 72 | } 73 | 74 | /* eslint-enable no-empty */ 75 | 76 | fs.writeFileSync(indexFilePath, indexCode); 77 | 78 | if (existingIndexCode && existingIndexCode === indexCode) { 79 | log(indexFilePath, chalk.yellow('[index has not changed]')); 80 | } else if (existingIndexCode && existingIndexCode !== indexCode) { 81 | log(indexFilePath, chalk.green('[updated index]')); 82 | } else { 83 | log(indexFilePath, chalk.green('[created index]')); 84 | } 85 | }); 86 | 87 | log('Done'); 88 | }; 89 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "canonical/mocha" 3 | } 4 | -------------------------------------------------------------------------------- /test/codeExample.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default (code) => { 4 | return _.trim(code) + '\n\n'; 5 | }; 6 | -------------------------------------------------------------------------------- /test/createIndexCode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-syntax */ 2 | 3 | import { 4 | expect, 5 | } from 'chai'; 6 | import createIndexCode from '../src/utilities/createIndexCode'; 7 | import codeExample from './codeExample'; 8 | 9 | describe('createIndexCode()', () => { 10 | it('describes no children', () => { 11 | const indexCode = createIndexCode([]); 12 | 13 | expect(indexCode).to.equal(codeExample(` 14 | // @create-index 15 | `)); 16 | }); 17 | it('describes a single child', () => { 18 | const indexCode = createIndexCode(['foo']); 19 | 20 | expect(indexCode).to.equal(codeExample(` 21 | // @create-index 22 | 23 | export { default as foo } from './foo'; 24 | `)); 25 | }); 26 | it('describes multiple children', () => { 27 | const indexCode = createIndexCode(['bar', 'foo']); 28 | 29 | expect(indexCode).to.equal(codeExample(` 30 | // @create-index 31 | 32 | export { default as bar } from './bar'; 33 | export { default as foo } from './foo'; 34 | `)); 35 | }); 36 | context('file with extension', () => { 37 | it('removes the extension from the export statement', () => { 38 | const indexCode = createIndexCode(['foo.js']); 39 | 40 | expect(indexCode).to.equal(codeExample(` 41 | // @create-index 42 | 43 | export { default as foo } from './foo.js'; 44 | `)); 45 | }); 46 | }); 47 | context('multiple, unsorted', () => { 48 | it('sorts the files', () => { 49 | const indexCode = createIndexCode(['foo', 'bar']); 50 | 51 | expect(indexCode).to.equal(codeExample(` 52 | // @create-index 53 | 54 | export { default as bar } from './bar'; 55 | export { default as foo } from './foo'; 56 | `)); 57 | }); 58 | }); 59 | 60 | context('with config', () => { 61 | it('should append config', () => { 62 | const config = { 63 | ignore: ['/^zoo/'], 64 | }; 65 | const indexCode = createIndexCode(['foo', 'bar'], {config}); 66 | 67 | expect(indexCode).to.equal(codeExample(` 68 | // @create-index {"ignore":["/^zoo/"]} 69 | 70 | export { default as bar } from './bar'; 71 | export { default as foo } from './foo'; 72 | `)); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/findIndexFiles.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { 3 | expect, 4 | } from 'chai'; 5 | import glob from 'glob'; 6 | import findIndexFiles from '../src/utilities/findIndexFiles'; 7 | 8 | const fixturesPath = path.resolve(__dirname, '../../fixtures/find-index-files'); 9 | 10 | describe('findIndexFiles()', () => { 11 | it('finds only the directories that have an existing valid index file', () => { 12 | let names; 13 | 14 | names = findIndexFiles(path.resolve(fixturesPath)); 15 | names = names.sort(); 16 | 17 | expect(names).to.deep.equal(glob.sync(path.resolve(fixturesPath, '**/find-*'))); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/fixtures/find-index-files/do-not-find-no-index/not-index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/find-index-files/do-not-find/index.js: -------------------------------------------------------------------------------- 1 | // create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/find-index-files/find-1/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/find-index-files/nested/do-not-find/index.js: -------------------------------------------------------------------------------- 1 | // create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/find-index-files/nested/find-2/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/find-index-files/nested/find-3/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-and-files/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-directories-and-files/foo.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-and-files/foo/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-and-files/present.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-directories-and-files/present.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-short-name/F/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-short-name/T/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-short-name/__/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-short-name/o/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-unsafe-name/bar-bar/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-unsafe-name/foo-foo/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-unsafe-name/present/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-without-index/bar/bar.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-without-index/foo/foo.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories-without-index/present.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories/bar/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-directories/foo/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-dot-files/present.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-dot-files/present.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-alt-extension-with-homonyms/bar.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-alt-extension-with-homonyms/bar.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-alt-extension-with-homonyms/bar.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-alt-extension-with-homonyms/bar.jsx -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-alt-extension-with-homonyms/present.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-alt-extension-with-homonyms/present.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-alt-extension/bar.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-alt-extension/bar.jsx -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-alt-extension/foo.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-alt-extension/foo.css -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-alt-extension/present.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-alt-extension/present.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-multiple-extensions/bar.bar.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-multiple-extensions/bar.bar.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-multiple-extensions/foo.foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-multiple-extensions/foo.foo.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-multiple-extensions/present.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-multiple-extensions/present.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-no-extension/bar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-no-extension/bar -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-no-extension/foo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-no-extension/foo -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files-no-extension/present.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files-no-extension/present.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files/bar.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files/bar.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-files/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-directory/children-files/foo.js -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-index/bar/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-index/foo/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-directory/children-index/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/read-index-config/no-index/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/read-index-config/no-index/foo.js -------------------------------------------------------------------------------- /test/fixtures/read-index-config/with-config/index.js: -------------------------------------------------------------------------------- 1 | // @create-index {"ignore": ["/foo.js$/"]} 2 | -------------------------------------------------------------------------------- /test/fixtures/read-index-config/with-invalid-config/index.js: -------------------------------------------------------------------------------- 1 | // @create-index {ignore: 'foo'} 2 | -------------------------------------------------------------------------------- /test/fixtures/read-index-config/without-config/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/validate-target-directory/no-index/present.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/validate-target-directory/no-index/present.js -------------------------------------------------------------------------------- /test/fixtures/validate-target-directory/not-a-directory.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/validate-target-directory/not-a-directory.js -------------------------------------------------------------------------------- /test/fixtures/validate-target-directory/safe-index-with-banner/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // @create-index 4 | -------------------------------------------------------------------------------- /test/fixtures/validate-target-directory/safe-index/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/validate-target-directory/unsafe-index/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/validate-target-directory/unsafe-index/index.js -------------------------------------------------------------------------------- /test/fixtures/write-index/mixed/bar/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/write-index/mixed/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/write-index/mixed/foo.js -------------------------------------------------------------------------------- /test/fixtures/write-index/mixed/foo/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | -------------------------------------------------------------------------------- /test/fixtures/write-index/mixed/index.js: -------------------------------------------------------------------------------- 1 | // @create-index 2 | 3 | export { default as bar } from './bar'; 4 | export { default as foo } from './foo.js'; 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/write-index/with-config/bar.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/write-index/with-config/bar.js -------------------------------------------------------------------------------- /test/fixtures/write-index/with-config/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/create-index/929754cc91a207cf3557a1cd9762be565d0b5755/test/fixtures/write-index/with-config/foo.js -------------------------------------------------------------------------------- /test/fixtures/write-index/with-config/index.js: -------------------------------------------------------------------------------- 1 | // @create-index {"ignore":["/bar.js$/"]} 2 | 3 | export { default as foo } from './foo.js'; 4 | 5 | -------------------------------------------------------------------------------- /test/readDirectory.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { 3 | expect, 4 | } from 'chai'; 5 | import readDirectory from '../src/utilities/readDirectory'; 6 | 7 | const fixturesPath = path.resolve(__dirname, 'fixtures/read-directory'); 8 | 9 | describe('readDirectory()', () => { 10 | context('target directory contains child directories', () => { 11 | it('gets names of the children directories', () => { 12 | const names = readDirectory(path.resolve(fixturesPath, 'children-directories')); 13 | 14 | expect(names).to.deep.equal(['bar', 'foo']); 15 | }); 16 | }); 17 | context('target directory contains child directories that do not contain index', () => { 18 | it('gets names of the children directories', () => { 19 | const names = readDirectory(path.resolve(fixturesPath, 'children-directories-without-index')); 20 | 21 | expect(names).to.deep.equal(['present.js']); 22 | }); 23 | }); 24 | context('target directory contains child directories (short safe name)', () => { 25 | it('gets names of the children directories', () => { 26 | const names = readDirectory(path.resolve(fixturesPath, 'children-directories-short-name')); 27 | 28 | expect(names).to.deep.equal(['F', 'T', '__', 'o']); 29 | }); 30 | }); 31 | context('target directory contains child directories (unsafe name)', () => { 32 | it('gets names of the children directories', () => { 33 | const names = readDirectory(path.resolve(fixturesPath, 'children-directories-unsafe-name')); 34 | 35 | expect(names).to.deep.equal(['present']); 36 | }); 37 | }); 38 | context('target directory contains ./index.js', () => { 39 | it('does not include ./index.js', () => { 40 | const names = readDirectory(path.resolve(fixturesPath, 'children-index')); 41 | 42 | expect(names).to.deep.equal(['bar', 'foo']); 43 | }); 44 | 45 | it('excludes directories if ignoreDirectories = true', () => { 46 | const names = readDirectory(path.resolve(fixturesPath, 'children-index'), {ignoreDirectories: true}); 47 | 48 | expect(names).to.deep.equal([]); 49 | }); 50 | }); 51 | context('target directory contains files', () => { 52 | it('refers to the files (with extension)', () => { 53 | const names = readDirectory(path.resolve(fixturesPath, 'children-files')); 54 | 55 | expect(names).to.deep.equal(['bar.js', 'foo.js']); 56 | }); 57 | }); 58 | context('target directory contains dot files', () => { 59 | it('ignores files', () => { 60 | const names = readDirectory(path.resolve(fixturesPath, 'children-dot-files')); 61 | 62 | expect(names).to.deep.equal(['present.js']); 63 | }); 64 | }); 65 | context('target directory contains non js files, and not configured to allow that', () => { 66 | it('prefers file', () => { 67 | const names = readDirectory(path.resolve(fixturesPath, 'children-files-alt-extension')); 68 | 69 | expect(names).to.deep.equal(['present.js']); 70 | }); 71 | }); 72 | context('target directory contains non js files, and allowing only jsx', () => { 73 | it('prefers file', () => { 74 | const options = {extensions: ['jsx']}; 75 | const names = readDirectory(path.resolve(fixturesPath, 'children-files-alt-extension'), options); 76 | 77 | expect(names).to.deep.equal(['bar.jsx']); 78 | }); 79 | }); 80 | context('target directory contains non js files, and allowing both js and jsx', () => { 81 | it('prefers file', () => { 82 | const options = {extensions: ['js', 'jsx']}; 83 | const names = readDirectory(path.resolve(fixturesPath, 'children-files-alt-extension'), options); 84 | 85 | expect(names).to.deep.equal(['bar.jsx', 'present.js']); 86 | }); 87 | }); 88 | context('target directory contains homonyms files, and allowing both js and jsx, will prefer JS as it is first extension listed', () => { 89 | it('prefers file', () => { 90 | const options = {extensions: ['js', 'jsx']}; 91 | const names = readDirectory(path.resolve(fixturesPath, 'children-files-alt-extension-with-homonyms'), options); 92 | 93 | expect(names).to.deep.equal(['bar.js', 'present.js']); 94 | }); 95 | }); 96 | context('target directory contains homonyms files, and allowing both js and jsx, will prefer JSX as it is first extension listed', () => { 97 | it('prefers file', () => { 98 | const options = {extensions: ['jsx', 'js']}; 99 | const names = readDirectory(path.resolve(fixturesPath, 'children-files-alt-extension-with-homonyms'), options); 100 | 101 | expect(names).to.deep.equal(['bar.jsx', 'present.js']); 102 | }); 103 | }); 104 | context('target directory contains files with no extension', () => { 105 | it('ignores files', () => { 106 | const names = readDirectory(path.resolve(fixturesPath, 'children-files-no-extension')); 107 | 108 | expect(names).to.deep.equal(['present.js']); 109 | }); 110 | }); 111 | context('target directory contains files with multiple extensions', () => { 112 | it('ignores files', () => { 113 | const names = readDirectory(path.resolve(fixturesPath, 'children-files-multiple-extensions')); 114 | 115 | expect(names).to.deep.equal(['present.js']); 116 | }); 117 | }); 118 | context('target directory contains directories and files with the same name', () => { 119 | it('prefers file', () => { 120 | const names = readDirectory(path.resolve(fixturesPath, 'children-directories-and-files')); 121 | 122 | expect(names).to.deep.equal(['foo.js', 'present.js']); 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/readIndexConfig.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { 3 | expect, 4 | } from 'chai'; 5 | import readIndexConfig from '../src/utilities/readIndexConfig'; 6 | 7 | const fixtures = { 8 | noIndex: path.resolve(__dirname, 'fixtures/read-index-config/no-index'), 9 | withConfig: path.resolve(__dirname, 'fixtures/read-index-config/with-config'), 10 | withInvalidConfig: path.resolve(__dirname, 'fixtures/read-index-config/with-invalid-config'), 11 | withoutConfig: path.resolve(__dirname, 'fixtures/read-index-config/without-config'), 12 | }; 13 | 14 | const expectedValues = { 15 | noIndex: {}, 16 | withConfig: { 17 | ignore: ['/foo.js$/'], 18 | }, 19 | withoutConfig: {}, 20 | }; 21 | 22 | describe('readIndexConfig()', () => { 23 | context('When valid config is defined', () => { 24 | it('reads config object', () => { 25 | const config = readIndexConfig(fixtures.withConfig); 26 | 27 | expect(config).to.deep.equal(expectedValues.withConfig); 28 | }); 29 | }); 30 | 31 | context('When invalid config is defined', () => { 32 | it('should throw an error', () => { 33 | const wrappedReadIndexConfig = () => { 34 | readIndexConfig(fixtures.withInvalidConfig); 35 | }; 36 | 37 | expect(wrappedReadIndexConfig).to.throw(/Configuration object must be a valid JSON./); 38 | }); 39 | }); 40 | 41 | context('When config is NOT defined', () => { 42 | it('returns an empty object', () => { 43 | const config = readIndexConfig(fixtures.withoutConfig); 44 | 45 | expect(config).to.deep.equal(expectedValues.withoutConfig); 46 | }); 47 | }); 48 | 49 | context('When index file doesn\'t exist', () => { 50 | it('returns an empty object', () => { 51 | const config = readIndexConfig(fixtures.withoutConfig); 52 | 53 | expect(config).to.deep.equal(expectedValues.noIndex); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/sortByDepth.js: -------------------------------------------------------------------------------- 1 | import { 2 | expect, 3 | } from 'chai'; 4 | import sortByDepth from '../src/utilities/sortByDepth'; 5 | 6 | describe('sortByDepth()', () => { 7 | it('sorts from deepest to the most shallow', () => { 8 | const paths = [ 9 | '/b', 10 | '/a', 11 | '/a/b/c', 12 | '/a/b', 13 | ]; 14 | 15 | const sortedPaths = sortByDepth(paths); 16 | 17 | expect(sortedPaths).to.deep.equal(['/a/b/c', '/a/b', '/b', '/a']); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/validateTargetDirectory.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | 3 | import path from 'path'; 4 | import { 5 | expect, 6 | } from 'chai'; 7 | import validateTargetDirectory from '../src/utilities/validateTargetDirectory'; 8 | 9 | const fixturesPath = path.resolve(__dirname, 'fixtures/validate-target-directory'); 10 | 11 | describe('validateTargetDirectory()', () => { 12 | describe('directory path', () => { 13 | context('refers to a directory that does not exist', () => { 14 | it('throws an error', () => { 15 | expect(() => { 16 | validateTargetDirectory(path.resolve(fixturesPath, 'does-not-exist')); 17 | }).to.throw(Error, 'Directory "' + path.resolve(fixturesPath, 'does-not-exist') + '" does not exist.'); 18 | }); 19 | }); 20 | context('refers to a file', () => { 21 | it('throws an error', () => { 22 | expect(() => { 23 | validateTargetDirectory(path.resolve(fixturesPath, 'not-a-directory.js')); 24 | }).to.throw(Error, '"' + path.resolve(fixturesPath, 'not-a-directory.js') + '" is not a directory.'); 25 | }); 26 | }); 27 | }); 28 | describe('target index', () => { 29 | context('no index', () => { 30 | it('returns true', () => { 31 | expect(validateTargetDirectory(path.resolve(fixturesPath, 'no-index'))).to.equal(true); 32 | }); 33 | }); 34 | context('safe', () => { 35 | it('returns true', () => { 36 | expect(validateTargetDirectory(path.resolve(fixturesPath, 'safe-index'))).to.equal(true); 37 | }); 38 | }); 39 | context('safe with banner', () => { 40 | it('returns true', () => { 41 | expect(validateTargetDirectory(path.resolve(fixturesPath, 'safe-index-with-banner'))).to.equal(true); 42 | }); 43 | }); 44 | context('unsafe', () => { 45 | it('throws an error', () => { 46 | expect(() => { 47 | validateTargetDirectory(path.resolve(fixturesPath, 'unsafe-index')); 48 | }).to.throw(Error, '"' + path.resolve(fixturesPath, 'unsafe-index/index.js') + '" unsafe index.'); 49 | }); 50 | }); 51 | context('unsafe ignored', () => { 52 | it('returns false', () => { 53 | expect(validateTargetDirectory(path.resolve(fixturesPath, 'unsafe-index'), {silent: true})).to.equal(false); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/writeIndex.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-syntax */ 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import { 6 | expect, 7 | } from 'chai'; 8 | import writeIndex from '../src/utilities/writeIndex'; 9 | import codeExample from './codeExample'; 10 | 11 | const readFile = (filePath) => { 12 | return fs.readFileSync(filePath, 'utf8'); 13 | }; 14 | 15 | const removeFile = (filePath) => { 16 | fs.unlinkSync(filePath); 17 | }; 18 | 19 | const appendToFile = (filePath, content) => { 20 | fs.appendFileSync(filePath, content, 'utf-8'); 21 | }; 22 | 23 | const fixturesPath = path.resolve(__dirname, 'fixtures/write-index'); 24 | 25 | describe('writeIndex()', () => { 26 | it('creates index in target directory', () => { 27 | const indexFilePath = path.resolve(fixturesPath, 'mixed/index.js'); 28 | 29 | removeFile(indexFilePath); 30 | writeIndex([path.resolve(fixturesPath, 'mixed')]); 31 | const indexCode = readFile(indexFilePath); 32 | 33 | expect(indexCode).to.equal(codeExample(` 34 | // @create-index 35 | 36 | export { default as bar } from './bar'; 37 | export { default as foo } from './foo.js'; 38 | `)); 39 | }); 40 | 41 | it('creates index with config in target directory', () => { 42 | const indexFilePath = path.resolve(fixturesPath, 'with-config/index.js'); 43 | // eslint-disable-next-line quotes 44 | const ignoredExportLine = `export { default as bar } from './bar.js';`; 45 | 46 | appendToFile(indexFilePath, ignoredExportLine); 47 | expect(readFile(indexFilePath).includes(ignoredExportLine)).to.equal(true); 48 | 49 | writeIndex([path.resolve(fixturesPath, 'with-config')]); 50 | const indexCode = readFile(indexFilePath); 51 | 52 | expect(indexCode).to.equal(codeExample(` 53 | // @create-index {"ignore":["/bar.js$/"]} 54 | 55 | export { default as foo } from './foo.js'; 56 | `)); 57 | }); 58 | }); 59 | --------------------------------------------------------------------------------