├── .bitmap ├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel-register.js ├── babel.config.js ├── bin └── bitjs.js ├── bit.json ├── fixtures ├── build-tree │ ├── a.js │ ├── b.js │ ├── not-link-file │ │ ├── file-a.js │ │ ├── file-b.js │ │ └── file-c.js │ ├── tree-shaking-cycle │ │ ├── foo.js │ │ ├── index.js │ │ ├── is-string.js │ │ └── self-cycle.js │ ├── unparsed.css │ └── unparsed.js ├── dependency-tree │ ├── amd │ │ ├── a.js │ │ ├── b.js │ │ └── c.js │ ├── commonjs │ │ ├── a.js │ │ ├── b.js │ │ └── c.js │ ├── onlyRealDeps │ │ └── a.js │ └── webpack │ │ ├── aliased.js │ │ └── unaliased.js ├── filing-cabinet │ ├── ast.js │ ├── foo.jsx │ ├── foo.scss │ ├── foo2.scss │ ├── mockedJSFiles.js │ ├── node_modules │ │ └── bootstrap │ │ │ └── index.scss │ ├── root1 │ │ └── mod1.js │ ├── root2 │ │ └── mod2.js │ └── webpack.config.js ├── missing-deps.js ├── path-map.json ├── precinct │ ├── Gruntfile.js │ ├── amd.js │ ├── cjsExportLazy.js │ ├── cjsMixedImport.js │ ├── commonjs.js │ ├── coreModules.js │ ├── es6.js │ ├── es6MixedExportLazy.js │ ├── es6MixedImport.js │ ├── es6NoImport.js │ ├── es6WithError.js │ ├── es7.js │ ├── exampleAST.js │ ├── jsx.js │ ├── none.js │ ├── styles.css │ ├── styles.less │ ├── styles.sass │ ├── styles.scss │ ├── styles.styl │ ├── typescript.ts │ ├── typescriptWithError.ts │ └── unparseable.js └── unsupported-file.pdf ├── package-lock.json ├── package.json ├── register-component.js ├── scripts └── compare-versions.sh ├── src ├── actions │ ├── get-dependencies.ts │ └── index.ts ├── cli │ ├── app.ts │ ├── command-registrar.ts │ ├── commands │ │ ├── command-list.ts │ │ ├── get-dependencies.ts │ │ └── types.ts │ └── loader │ │ ├── index.ts │ │ └── loader.ts ├── constants.ts ├── dependency-builder │ ├── build-tree.spec.ts │ ├── build-tree.ts │ ├── dependency-tree │ │ ├── Config.ts │ │ ├── index.spec.ts │ │ └── index.ts │ ├── detectives │ │ ├── detective-css-and-preprocessors │ │ │ └── index.ts │ │ ├── detective-css │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ ├── detective-es6 │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ ├── detective-less │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ ├── detective-sass │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ ├── detective-scss │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ ├── detective-stylable │ │ │ └── index.ts │ │ ├── detective-typescript │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ ├── detective-vue │ │ │ └── index.ts │ │ └── parser-helper.ts │ ├── filing-cabinet │ │ ├── index.spec.ts │ │ └── index.ts │ ├── generate-tree-madge.ts │ ├── index.ts │ ├── lookups │ │ └── vue-lookup │ │ │ └── index.ts │ ├── path-map.spec.ts │ ├── path-map.ts │ ├── precinct │ │ ├── index.spec.ts │ │ └── index.ts │ └── types │ │ ├── dependency-tree-type.ts │ │ └── index.ts ├── index.ts ├── package-json │ └── package-json.ts ├── registry │ ├── exceptions │ │ ├── index.ts │ │ ├── path-to-npmrc-not-exist.ts │ │ └── write-to-npmrc-error.ts │ ├── index.ts │ └── registry.ts └── utils │ ├── index.ts │ └── is-relative-import.ts └── tsconfig.json /.bitmap: -------------------------------------------------------------------------------- 1 | /* THIS IS A BIT-AUTO-GENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. */ 2 | 3 | { 4 | "david.bit-javascript/dependency-tree/types@0.0.2": { 5 | "files": [ 6 | { 7 | "name": "dependency-tree-type.js", 8 | "relativePath": "dependency-tree-type.js", 9 | "test": false 10 | } 11 | ], 12 | "mainFile": "dependency-tree-type.js", 13 | "rootDir": "src/dependency-builder/types", 14 | "origin": "IMPORTED", 15 | "originallySharedDir": "src/consumer/component/dependencies/dependency-resolver/types" 16 | }, 17 | "version": "13.0.1" 18 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | default_image: &default_image 2 | docker: 3 | - image: circleci/node:8.12.0 4 | 5 | default_resource_class: &default_resource_class 6 | resource_class: small 7 | 8 | default_working_dir: &default_working_dir 9 | working_directory: ~/bit-javascript 10 | 11 | defaults: &defaults 12 | <<: *default_image 13 | <<: *default_resource_class 14 | <<: *default_working_dir 15 | 16 | semver_tags_only_filters: &semver_tags_only_filters 17 | filters: 18 | # ignore any commit on any branch by default 19 | branches: 20 | ignore: /.*/ 21 | # only act on version tags 22 | tags: 23 | only: /^v[0-9]+(\.[0-9]+)*$/ 24 | 25 | version: 2 26 | jobs: 27 | checkout_code: 28 | <<: *defaults 29 | steps: 30 | - checkout 31 | - 32 | persist_to_workspace: 33 | root: /home/circleci 34 | paths: 35 | - bit-javascript 36 | install_npm_deps: 37 | <<: *defaults 38 | steps: 39 | - 40 | attach_workspace: 41 | at: ./ 42 | - 43 | run: 44 | name: 'save SHA to a file' 45 | command: 'echo $CIRCLE_SHA1 > .circle-sha' 46 | - 47 | run: 48 | name: 'Install npm dependencies' 49 | command: 'cd bit-javascript && npm install' 50 | - 51 | persist_to_workspace: 52 | root: . 53 | paths: 54 | - bit-javascript/node_modules 55 | validate-git-tag-and-version: 56 | <<: *defaults 57 | steps: 58 | - 59 | attach_workspace: 60 | at: ./ 61 | - run: 62 | name: Setup bit version environment variables 63 | command: cd bit-javascript && echo "export BIT_JS_VERSION=$(cat ./package.json | jq .version -r)" >> $BASH_ENV && source $BASH_ENV 64 | - 65 | run: 66 | name: 'installing semver tool' 67 | command: 'sudo npm i -g semver' 68 | - 69 | run: 70 | name: 'validate version in package.json does not contains pre release tags' 71 | # This will return code 1 when the version contains pre release tags 72 | command: 'semver $BIT_JS_VERSION -r x.x.x' 73 | - 74 | run: 75 | name: 'validate tag match version in package.json' 76 | command: 'cd bit-javascript && ./scripts/compare-versions.sh $CIRCLE_TAG v$BIT_JS_VERSION' 77 | npm-publish: 78 | <<: *defaults 79 | steps: 80 | - 81 | attach_workspace: 82 | at: ./ 83 | - run: 84 | name: Authenticate with registry 85 | command: echo "//registry.npmjs.org/:_authToken=$npmToken" > ~/.npmrc 86 | - 87 | run: 88 | name: Publish bit to the npm registry 89 | command: 'cd bit-javascript && npm publish' 90 | github-release: 91 | <<: *defaults 92 | steps: 93 | - 94 | attach_workspace: 95 | at: ./ 96 | # - run: 97 | # name: set GitHub token 98 | # command: export GH_RELEASE_GITHUB_API_TOKEN=$ghToken 99 | - 100 | run: 'cd bit-javascript && npm run release:circle' 101 | build: 102 | <<: *defaults 103 | steps: 104 | - 105 | run: 106 | name: 'save SHA to a file' 107 | command: 'echo $CIRCLE_SHA1 > .circle-sha' 108 | - 109 | attach_workspace: 110 | at: ./ 111 | - 112 | run: 113 | name: 'Build bit javascript source code' 114 | command: 'cd bit-javascript && npm run build' 115 | - 116 | persist_to_workspace: 117 | root: . 118 | paths: 119 | - bit-javascript/dist 120 | unit_test: 121 | <<: *defaults 122 | steps: 123 | - 124 | run: 125 | name: 'save SHA to a file' 126 | command: 'echo $CIRCLE_SHA1 > .circle-sha' 127 | - 128 | attach_workspace: 129 | at: ./ 130 | - 131 | run: 'cd bit-javascript && mkdir junit' 132 | - 133 | run: 134 | name: 'Run unit tests' 135 | command: 'cd bit-javascript && npm run test-circle' 136 | environment: 137 | MOCHA_FILE: junit/unit-test-results.xml 138 | when: always 139 | - 140 | store_test_results: 141 | path: bit-javascript/junit 142 | - 143 | store_artifacts: 144 | path: bit-javascript/junit 145 | lint: 146 | <<: *defaults 147 | resource_class: medium 148 | steps: 149 | - 150 | run: 151 | name: 'save SHA to a file' 152 | command: 'echo $CIRCLE_SHA1 > .circle-sha' 153 | - 154 | restore_cache: 155 | keys: 156 | - 'repo-{{ checksum ".circle-sha" }}' 157 | - 158 | attach_workspace: 159 | at: ./ 160 | - 161 | run: 162 | name: 'run ESLint' 163 | command: 'cd bit-javascript && npm run lint-circle' 164 | - 165 | store_test_results: 166 | path: bit-javascript/junit 167 | - 168 | store_artifacts: 169 | path: bit-javascript/junit 170 | check_types: 171 | <<: *defaults 172 | resource_class: medium 173 | steps: 174 | - 175 | run: 176 | name: 'save SHA to a file' 177 | command: 'echo $CIRCLE_SHA1 > .circle-sha' 178 | - 179 | restore_cache: 180 | keys: 181 | - 'repo-{{ checksum ".circle-sha" }}' 182 | - 183 | attach_workspace: 184 | at: ./ 185 | - 186 | run: 187 | name: 'run TSC' 188 | command: 'cd bit-javascript && npm run check-types' 189 | - 190 | store_test_results: 191 | path: bit-javascript/junit 192 | - 193 | store_artifacts: 194 | path: bit-javascript/junit 195 | workflows: 196 | version: 2 197 | build_and_test: 198 | jobs: 199 | - checkout_code 200 | - 201 | install_npm_deps: 202 | requires: 203 | - checkout_code 204 | - 205 | build: 206 | requires: 207 | - install_npm_deps 208 | - 209 | unit_test: 210 | requires: 211 | - build 212 | - 213 | lint: 214 | requires: 215 | - install_npm_deps 216 | - 217 | check_types: 218 | requires: 219 | - install_npm_deps 220 | deploy: 221 | jobs: 222 | - checkout_code: 223 | <<: *semver_tags_only_filters 224 | - 225 | validate-git-tag-and-version: 226 | <<: *semver_tags_only_filters 227 | requires: 228 | - checkout_code 229 | - 230 | install_npm_deps: 231 | <<: *semver_tags_only_filters 232 | requires: 233 | - validate-git-tag-and-version 234 | - checkout_code 235 | - 236 | build: 237 | <<: *semver_tags_only_filters 238 | requires: 239 | - install_npm_deps 240 | - 241 | npm-publish: 242 | <<: *semver_tags_only_filters 243 | requires: 244 | - build 245 | - 246 | github-release: 247 | <<: *semver_tags_only_filters 248 | requires: 249 | - build 250 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | fixtures -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: './tsconfig.json' 5 | }, 6 | extends: [ 7 | 'airbnb-typescript/base', 8 | // 'plugin:@typescript-eslint/recommended-requiring-type-checking', 9 | 'plugin:@typescript-eslint/recommended', 10 | // 'plugin:eslint-comments/recommended', 11 | 'plugin:promise/recommended', 12 | // 'plugin:unicorn/recommended', 13 | // 'plugin:mocha/recommended', 14 | 'prettier', 15 | 'prettier/@typescript-eslint' 16 | ], 17 | plugins: [ 18 | '@typescript-eslint', 19 | // 'eslint-comments', 20 | 'promise' 21 | // 'mocha', 22 | // 'unicorn' 23 | ], 24 | rules: { 25 | '@typescript-eslint/no-use-before-define': [ 26 | 'error', 27 | { functions: false, classes: true, variables: true, typedefs: true } 28 | ], 29 | 30 | // ERRORS OF plugin:@typescript-eslint/recommended 31 | '@typescript-eslint/no-var-requires': 'off', 32 | '@typescript-eslint/ban-ts-ignore': 'off', 33 | '@typescript-eslint/no-explicit-any': 'off', 34 | // END ERRORS OF plugin:@typescript-eslint/recommended 35 | 36 | // ERRORS OF 'plugin:promise/recommended' 37 | 'promise/always-return': 'off', 38 | 'promise/no-nesting': 'off', 39 | // END ERRORS OF 'plugin:promise/recommended' 40 | 41 | 'import/export': 'off', // typescript does allow multiple export default when overloading. not sure why it's enabled here. rule source: https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/export.md 42 | 'prefer-object-spread': 'off', 43 | 'import/no-duplicates': 'off', 44 | '@typescript-eslint/explicit-function-return-type': 'off', 45 | 'import/no-cycle': 'off', 46 | 'import/no-useless-path-segments': 'off', 47 | 'lines-between-class-members': 'off', 48 | radix: 'off', 49 | 'no-underscore-dangle': 'off', 50 | 'no-param-reassign': 'off', 51 | 'no-return-assign': [0, 'except-parens'], 52 | 'class-methods-use-this': 'off', 53 | 'prefer-destructuring': 'off', 54 | 'import/no-extraneous-dependencies': 'off', 55 | 'no-restricted-syntax': [2, 'ForInStatement', 'LabeledStatement', 'WithStatement'], 56 | 'no-unused-expressions': 'off', 57 | 'max-len': [ 58 | 2, 59 | 120, 60 | 2, 61 | { 62 | ignoreUrls: true, 63 | ignoreComments: true, 64 | ignoreRegExpLiterals: true, 65 | ignoreStrings: true, 66 | ignoreTemplateLiterals: true 67 | } 68 | ], 69 | 'max-lines': [2, 1700], 70 | 'func-names': [0] 71 | }, 72 | env: { 73 | node: true, 74 | mocha: true 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*node_modules/ 3 | 4 | [libs] 5 | 6 | [options] 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Dependency directories 13 | node_modules 14 | !fixtures/filing-cabinet/node_modules 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # vscode 20 | .vscode/ 21 | 22 | # vscode history 23 | .history 24 | 25 | # idea 26 | .idea/ 27 | 28 | # dist 29 | dist 30 | 31 | # bit 32 | .bit 33 | inline_components 34 | components 35 | 36 | # Flow 37 | flow-typed -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Dependency directories 13 | node_modules 14 | 15 | # Optional npm cache directory 16 | .npm 17 | 18 | # vscode 19 | .vscode/ 20 | 21 | # idea 22 | .idea/ 23 | 24 | .history 25 | 26 | # bit 27 | inline_components 28 | .bit -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "parser": "typescript", 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "useTabs": false, 7 | "bracketSpacing": true 8 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/). 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [unreleased] 9 | 10 | ## [2.1.6-dev.3] - 2020-04-17 11 | 12 | - update @typescript-eslint/typescript-estree package to avoid warning 13 | 14 | ## [2.1.6-dev.2] - 2020-04-16 15 | 16 | - upgrade typescript to 3.8.3 17 | 18 | ## [2.1.6-dev.1] - 2020-01-25 19 | 20 | - fix components dependencies detection to resolve from package.json if not exist on the fs 21 | 22 | ## [2.1.5] - 2020-01-12 23 | 24 | ### Bug fixes 25 | 26 | - [#2231](https://github.com/teambit/bit/issues/2231) update typescript to support optional chaining 27 | 28 | ## [2.1.4] - 2019-11-24 29 | 30 | - [#2140](https://github.com/teambit/bit/issues/2140) support `import { x as y }` syntax 31 | 32 | ## [2.1.3] - 2019-10-23 33 | 34 | ### Bug fixes 35 | 36 | - [#2079](https://github.com/teambit/bit/issues/2079) fix error when Yarn workspaces uses nohoist 37 | - [#2072](https://github.com/teambit/bit/issues/2072) upgrade module-definition package to support React fragments 38 | 39 | ### Internal 40 | 41 | - move from flow to typescript 42 | 43 | ## [2.1.2] - 2019-10-06 44 | 45 | - avoid recognizing any require/import starts with `.` as a custom-resolve-module on Windows 46 | 47 | ## [2.1.1] - 2019-08-14 48 | 49 | - remove angular dependencies. use typescript compiler to parse Angular Decorators 50 | - [#1925](https://github.com/teambit/bit/issues/1925) fix Angular non-relative paths from decorators 51 | 52 | ## [2.1.0] - 2019-07-18 53 | 54 | - propagate from component root dir backwards to find tsconfig.json for Angular projects 55 | - [#1779](https://github.com/teambit/bit/issues/1779) resolve first according to the custom-resolve settings and fallback to the standard resolver 56 | - fix dependency resolution of `.` and `..` to not be identified as custom-resolved used. 57 | - add rxjs package 58 | - support angular components (experimental) 59 | 60 | ## [2.0.11] - 2019-06-05 61 | 62 | - [#1708](https://github.com/teambit/bit/issues/1708) support `require` with apostrophes 63 | 64 | ## [2.0.10] - 2019-05-31 65 | 66 | - [#1690](https://github.com/teambit/bit/issues/1690) fix error "Cannot read property 'find' of undefined" with typescript files 67 | 68 | ## [2.0.9] - 2019-05-31 69 | 70 | - [#1665](https://github.com/teambit/bit/issues/1665) fix resolve-modules prefix with tilda 71 | 72 | ## [2.0.8] - 2019-05-27 73 | 74 | - add support with `optionalChaining` and `nullishCoalescingOperator` plugins (by updating node-source-walk) 75 | 76 | ## [2.0.7] - 2019-05-17 77 | 78 | - ignore import/require from CDN (http/https) 79 | 80 | ## [2.0.6] - 2019-05-16 81 | 82 | - fix identification of link files to take into account not only the `import` statements but also `export` 83 | 84 | ## [2.0.5] - 2019-05-01 85 | 86 | - fix es6 with dynamic import to not show as missing dependencies 87 | - improve performance by lazy load all external packages and internal files 88 | 89 | ## [2.0.4] - 2019-03-10 90 | 91 | - support scoped packages when resolving package.json directory of a package 92 | 93 | ## [2.0.3] - 2019-02-24 94 | 95 | - upgrade to babel 7 96 | - resolve symlink packages as packages when custom-resolve-modules is used 97 | - fix resolve dependencies cache to include parsing errors 98 | 99 | ## [2.0.2] - 2019-02-08 100 | 101 | - fix "Maximum call stack" error when resolving js files after css files 102 | 103 | ## [2.0.1] - 2019-02-04 104 | 105 | - fix parsing `.tsx` files 106 | 107 | ## [[2.0.0] - 2019-02-04](https://github.com/teambit/bit-javascript/releases/tag/v2.0.0) 108 | 109 | - update node-source-walk package to support `<>` jsx syntax by Babel 110 | - support mix syntax of typescript and javascript inside .ts file 111 | - fix ampersand and minus sings causing parse error in css files 112 | - fix dependency resolver cache to not be accidentally overridden 113 | - fix a warning about mismatch typescript versions 114 | - replace the deprecated typescript-eslint-parser with @typescript-eslint/typescript-estree 115 | - replace css parser (gonzales-pe -> css-tree) for better import syntax support 116 | - expose precinct lib as getDependenciesFromSource 117 | - replace Caporal package with Commander to parse cli input 118 | - update mocha-appveyor-reporter for security reasons 119 | - support other script languages (such as typescript) inside Vue files 120 | - utilize the cache mechanism of dependency-tree to cache resolved dependencies 121 | - fix resolving npm paths to be linked to the correct file 122 | - fix symlinks to node_modules to be recognized as modules instead of files 123 | - fix get-dependency command to cache processed files 124 | - improve the scalability of the dependency resolution 125 | - add detective-css (before it was using detective-sass for css files) 126 | - avoid suppressing parsing errors for css, sass, scss and less 127 | 128 | ## [1.0.5] - 2018-07-24 129 | 130 | - update typescript package 131 | - fix error "Cannot read property 'missing' of undefined" when a dependency of dependency has parsing errors 132 | - fix vulnerabilities reported by npm audit 133 | 134 | ## [1.0.4] - 2018-07-12 135 | 136 | - fix error "Cannot read property push of undefined" when a dependent has parsing error 137 | - avoid parsing unsupported dependencies files 138 | 139 | ## [1.0.3] - 2018-07-10 140 | 141 | - fix error "Cannot read property push of undefined" when a resolver throws an exception 142 | - fix the resolver for an unknown extension 143 | - bug fix - on Linux module path (require('a.js')) is resolved as relative path (require('./a.js')) 144 | - improve the tree shaking mechanism to work with unlimited number of intermediate (link) files 145 | - fix parsing error when a Vue file has a dependency prefix with a Tilde inside a style section 146 | - fix resolution of style files (.scss, .css, .sass, .less) when required with no extension 147 | - remove .json extension from the supported-files list as it doesn't have dependencies 148 | - avoid passing unsupported files to the parser 149 | - add parsing and resolving errors to the dependency tree 150 | - add missing dependencies to the dependency tree 151 | - fix detection of "export * from" syntax of ES6 152 | - fix "Cannot read property 'lang' of null" error when resolving Vue dependencies 153 | 154 | ## [1.0.2] - 2018-06-26 155 | 156 | - fix .tsx parsing issue when the tsx dependency is required from a non .tsx file 157 | - fix support of .json dependencies 158 | - fix "SyntaxError: Unexpected token" when parsing .ts files with .js dependencies 159 | 160 | ## [1.0.0] - 2018-06-18 161 | 162 | - support custom module resolution 163 | - support mixed mode of common-js and ES6 ("require" and "import" together) 164 | - support "export X from Y" syntax of ES6 without importing X first 165 | - fix handle tsx files when detectiveOption is empty 166 | - bug fix - packages on d.ts files were not recognized 167 | - lock stylable version since the new version no longer support node 6 168 | - fix issue with load package dependencies when main file not in the root of the package 169 | 170 | ## [0.10.16] - 2018-05-09 171 | 172 | - support for bit login (set registry with token in npmrc file) 173 | - adding scss to support ~ 174 | 175 | ## [0.10.15] - 2018-04-19 176 | 177 | - fix resolve-node-package process for Windows 178 | 179 | ## [0.10.14] - 2018-04-10 180 | 181 | - support link-files with "export { default as ... }"; syntax 182 | - fix merge of madge dependencies with package.json dependencies with dot in them 183 | 184 | ## [0.10.13] - 2018-03-21 185 | 186 | - fix issue with stylus files inside vue-lookup 187 | 188 | ## [0.10.12] - 2018-03-12 189 | 190 | - insert dependency-resolutions packages code into this repo. 191 | 192 | ## [0.10.11] - 2018-02-27 193 | 194 | - support dependency detection for Vue files 195 | 196 | ## [0.10.10] - 2018-01-30 197 | 198 | - restore old behavior of requiring package installation 199 | 200 | ## [0.10.9] - 2018-01-24 201 | 202 | - support case when there is no package.json 203 | - support removing components from workspaces and dependencies in package.json 204 | 205 | ## [0.10.8] - 2018-01-18 206 | 207 | - remove pack command 208 | - support yarn workspaces in package.json 209 | - remove auto generated post install scripts 210 | - fix bug with package.json without dependencies 211 | - fix bug with resolve dependencies from package.json 212 | - dont try to resolve dependencies from package.json if dosent exist 213 | - dont show missing packages if they appear in package.json 214 | - add a new method to PackageJson class: addComponentsIntoExistingPackageJson 215 | 216 | ## [0.10.7] - 2017-11-29 217 | 218 | - Stylable support 219 | - improve stability and performance of resolving dependencies 220 | - change post install hook to work with symlink 221 | - bug fix - components that require dependencies with custom binding prefix were not recognized 222 | 223 | ## [0.10.6] - 2017-11-12 224 | 225 | - add pack command to create tgz for components 226 | 227 | ## [0.10.5] - 2017-10-19 228 | 229 | - Identify dependencies that are link files (files that only require other files) 230 | - Add a CLI command to easily get dependencies for a file 231 | - Support semver in packages dependencies 232 | - Add support for bindingPrefix 233 | 234 | ## [0.10.4] - 2017-10-01 235 | - Add support for writing package.json files 236 | - Support .tsx files 237 | 238 | ## [0.10.3] - 2017-08-23 239 | - Improve windows support 240 | - change back Madge version to the NPM package as it now supports TypeScript 241 | 242 | ## [0.10.2] - 2017-08-15 243 | 244 | - Use a forked version of madge for better ts support 245 | - Improve resolving packages dependencies (remove duplicates) 246 | 247 | ## [0.10.1] - 2017-08-07 248 | - Improve resolving packages dependencies for ts files 249 | 250 | ## [0.10.0] - 2017-08-07 251 | ### BREAKING CHANGES 252 | 253 | - Upgrade: Bit now works with a new set of APIs and data models for the code component and scope consumer. 254 | - Important: Bit is not backward compatible with remote scopes running older versions of Bit. 255 | 256 | ## [0.6.4] - 2017-06-25 257 | 258 | ## [0.6.4-rc.1] - 2017-06-07 259 | 260 | - create inner dependency links for all components in components directory. 261 | - support latest tag on bind process. 262 | - persist only in the end of the bind process. 263 | - fix the file-extension of the dist file to be based on the language defined in bit.json 264 | - fix bind of dependencies of dependencies 265 | - remove watch command 266 | - [bind] also create links for inline_components dependencies in the components directory 267 | 268 | ## [0.6.3] - 2017-05-21 269 | 270 | - fix to generation of links for inline-components 271 | 272 | ## [0.6.1] - 2017-05-18 273 | 274 | - fixed watcher and import command 275 | - generate dependencies links for inline-components 276 | 277 | ## [0.6.0] - 2017-05-15 278 | 279 | - exclude dynamic compile behavior 280 | 281 | ## [0.5.12] - 2017-05-14 rc 282 | 283 | - dynamically compile an impl file for inline-components on development environment 284 | 285 | ## [0.5.11] - 2017-05-11 rc 286 | 287 | - add onModify hook 288 | - create public-api for the dependencies root. 289 | 290 | ## [0.5.8] - 2017-05-11 rc 291 | 292 | - fix binding dependencies when using a non-hub scope. 293 | - create public-api for the dependencies namespaces. 294 | 295 | ## [0.5.7] - 2017-05-10 rc 296 | 297 | - fix public api namespace destructuring for bind specific components 298 | 299 | ## [0.5.4] - 2017-05-09 rc 300 | 301 | - support binding specific components 302 | - support passing a directory as a parameter to the `bind` function. 303 | - change the dist/dist.js constant to be dist/\ when performing the bind process 304 | - add public-api for pending export components (staged components, that were commited and didn't exported yet). 305 | - major refactor + remove old deprecated behavior (the load function) 306 | - change name to bit-javascript 307 | 308 | ## [0.5.3] - rc (2017-04-23) 309 | 310 | - support require('bit/namespace').component and import { component } from 'bit/namespace' syntax. 311 | - write a default bit.json if not exists 312 | - keep the bit-module in sync with the components map (e.g. remove the node_modules/bit module on every bind action) 313 | 314 | ## [0.5.1] - rc (2017-04-16) 315 | 316 | - implemented watch for inline_components and bit.json, call bind on every change. 317 | 318 | ## [0.5.0] - rc (2017-04-16) 319 | 320 | - add pretty error handling to the command registrar 321 | - Move the writing-to-fs functionality to [bit-scope-client](https://github.com/teambit/bit-scope-client) project 322 | - Add opts to the load function 323 | - Add resolve function 324 | - Fix dist bug 325 | - add sourcemaps 326 | - update bit-scope-client to version 0.5.2 327 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at team@bit.dev. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | Contributions are always welcome, no matter how large or small. Before contributing, 4 | please read the [code of conduct](CODE_OF_CONDUCT.md). 5 | 6 | ## Pull Requests 7 | 8 | We actively welcome your pull requests. 9 | 10 | 1. Fork the repo and create your branch from `master`. 11 | 2. If you've added code that should be tested, add tests. 12 | 3. Ensure the test suite passes. 13 | 4. Make sure your code lints. 14 | 15 | ## License 16 | 17 | By contributing to Bit, you agree that your contributions will be licensed 18 | under its [Apache2 license](LICENSE). 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Bit - Distributed code component manager. 2 | 3 | Copyright (C) 2014-2017 Cocycles LTD. 4 | 5 | Can be contacted at: team@bit.dev 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bit driver for javascript 2 | 3 | For more information on Bit [see Bit's docs](https://docs.bit.dev/). 4 | 5 | No need to install this package individually anymore. By installing Bit, this driver is already installed. 6 | 7 | ## Installing bit-javascript 8 | 9 | ```sh 10 | npm install bit-javascript -g 11 | ``` 12 | 13 | ## Contributing to bit-javascript 14 | 15 | Contributions are always welcome, no matter how large or small. Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md). 16 | 17 | See [Contributing](CONTRIBUTING.md). 18 | 19 | ![Analytics](https://ga-beacon.appspot.com/UA-96032224-1/bit-js/readme) 20 | -------------------------------------------------------------------------------- /babel-register.js: -------------------------------------------------------------------------------- 1 | require('@babel/register')({ extensions: ['.js', '.jsx', '.ts', '.tsx'] }); 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | const presets = [ 5 | '@babel/typescript', 6 | [ 7 | '@babel/preset-env', 8 | { 9 | targets: { 10 | node: 8 11 | } 12 | } 13 | ] 14 | ]; 15 | const plugins = [ 16 | [ 17 | '@babel/plugin-transform-modules-commonjs', 18 | { 19 | lazy: () => true 20 | } 21 | ], 22 | ['@babel/plugin-transform-runtime'], 23 | ['@babel/plugin-proposal-object-rest-spread'], 24 | ['@babel/plugin-proposal-class-properties'], 25 | [ 26 | '@babel/plugin-transform-async-to-generator', 27 | { 28 | module: 'bluebird', 29 | method: 'coroutine' 30 | } 31 | ] 32 | ]; 33 | 34 | return { 35 | presets, 36 | plugins, 37 | only: ['**/*.ts'], 38 | ignore: ['components/*'] 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /bin/bitjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; // eslint-disable-line 3 | require('regenerator-runtime/runtime'); 4 | require('../dist/cli/app.js'); 5 | -------------------------------------------------------------------------------- /bit.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": {}, 3 | "dependencies": { 4 | "david.bit-javascript/dependency-tree/types": "0.0.2" 5 | }, 6 | "componentsDefaultDirectory": "components/{namespace}/{name}", 7 | "packageManager": "npm" 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/build-tree/a.js: -------------------------------------------------------------------------------- 1 | const b = require('./b'); -------------------------------------------------------------------------------- /fixtures/build-tree/b.js: -------------------------------------------------------------------------------- 1 | const unparsed = require('./unparsed'); -------------------------------------------------------------------------------- /fixtures/build-tree/not-link-file/file-a.js: -------------------------------------------------------------------------------- 1 | import { varX } from './file-b'; -------------------------------------------------------------------------------- /fixtures/build-tree/not-link-file/file-b.js: -------------------------------------------------------------------------------- 1 | import { varX } from './file-c'; 2 | 3 | // export { varX }; // uncomment to make the following test fail "fileA imports varX from fileB, fileB imports varX from fileC but not export it" 4 | -------------------------------------------------------------------------------- /fixtures/build-tree/not-link-file/file-c.js: -------------------------------------------------------------------------------- 1 | const varX = 4; 2 | export { varX }; -------------------------------------------------------------------------------- /fixtures/build-tree/tree-shaking-cycle/foo.js: -------------------------------------------------------------------------------- 1 | import { isString } from '.'; -------------------------------------------------------------------------------- /fixtures/build-tree/tree-shaking-cycle/index.js: -------------------------------------------------------------------------------- 1 | export { default as isString } from './is-string'; -------------------------------------------------------------------------------- /fixtures/build-tree/tree-shaking-cycle/is-string.js: -------------------------------------------------------------------------------- 1 | import { isString } from '.'; // cycle with ./index.js 2 | export default isString; -------------------------------------------------------------------------------- /fixtures/build-tree/tree-shaking-cycle/self-cycle.js: -------------------------------------------------------------------------------- 1 | import { selfCycle } from './self-cycle'; 2 | export { selfCycle }; 3 | -------------------------------------------------------------------------------- /fixtures/build-tree/unparsed.css: -------------------------------------------------------------------------------- 1 | this should trigger parsing error -------------------------------------------------------------------------------- /fixtures/build-tree/unparsed.js: -------------------------------------------------------------------------------- 1 | this should trigger parsing error -------------------------------------------------------------------------------- /fixtures/dependency-tree/amd/a.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './b', 3 | './c' 4 | ], function(b, c) { 5 | 'use strict'; 6 | 7 | return 'cool'; 8 | }); 9 | -------------------------------------------------------------------------------- /fixtures/dependency-tree/amd/b.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './c' 3 | ], function(c) { 4 | 'use strict'; 5 | 6 | return c; 7 | }); 8 | -------------------------------------------------------------------------------- /fixtures/dependency-tree/amd/c.js: -------------------------------------------------------------------------------- 1 | define({}); 2 | -------------------------------------------------------------------------------- /fixtures/dependency-tree/commonjs/a.js: -------------------------------------------------------------------------------- 1 | var b = require('./b'); 2 | var c = require('./c'); 3 | -------------------------------------------------------------------------------- /fixtures/dependency-tree/commonjs/b.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /fixtures/dependency-tree/commonjs/c.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /fixtures/dependency-tree/onlyRealDeps/a.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var debug = require('debug'); 3 | var notReal = require('not-real'); -------------------------------------------------------------------------------- /fixtures/dependency-tree/webpack/aliased.js: -------------------------------------------------------------------------------- 1 | var foo = require('F'); -------------------------------------------------------------------------------- /fixtures/dependency-tree/webpack/unaliased.js: -------------------------------------------------------------------------------- 1 | var cabinet = require('filing-cabinet'); -------------------------------------------------------------------------------- /fixtures/filing-cabinet/ast.js: -------------------------------------------------------------------------------- 1 | // AST of 'import bar from "./bar";' 2 | module.exports = { 3 | "type": "File", 4 | "start": 0, 5 | "end": 24, 6 | "loc": { 7 | "start": { 8 | "line": 1, 9 | "column": 0 10 | }, 11 | "end": { 12 | "line": 1, 13 | "column": 24 14 | } 15 | }, 16 | "program": { 17 | "type": "Program", 18 | "start": 0, 19 | "end": 24, 20 | "loc": { 21 | "start": { 22 | "line": 1, 23 | "column": 0 24 | }, 25 | "end": { 26 | "line": 1, 27 | "column": 24 28 | } 29 | }, 30 | "sourceType": "module", 31 | "body": [ 32 | { 33 | "type": "ImportDeclaration", 34 | "start": 0, 35 | "end": 24, 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 0 40 | }, 41 | "end": { 42 | "line": 1, 43 | "column": 24 44 | } 45 | }, 46 | "specifiers": [ 47 | { 48 | "type": "ImportDefaultSpecifier", 49 | "start": 7, 50 | "end": 10, 51 | "loc": { 52 | "start": { 53 | "line": 1, 54 | "column": 7 55 | }, 56 | "end": { 57 | "line": 1, 58 | "column": 10 59 | } 60 | }, 61 | "local": { 62 | "type": "Identifier", 63 | "start": 7, 64 | "end": 10, 65 | "loc": { 66 | "start": { 67 | "line": 1, 68 | "column": 7 69 | }, 70 | "end": { 71 | "line": 1, 72 | "column": 10 73 | } 74 | }, 75 | "name": "bar" 76 | } 77 | } 78 | ], 79 | "importKind": "value", 80 | "source": { 81 | "type": "StringLiteral", 82 | "start": 16, 83 | "end": 23, 84 | "loc": { 85 | "start": { 86 | "line": 1, 87 | "column": 16 88 | }, 89 | "end": { 90 | "line": 1, 91 | "column": 23 92 | } 93 | }, 94 | "extra": { 95 | "rawValue": "./bar", 96 | "raw": "\"./bar\"" 97 | }, 98 | "value": "./bar" 99 | } 100 | } 101 | ], 102 | "directives": [] 103 | }, 104 | "comments": [], 105 | "tokens": [ 106 | { 107 | "type": { 108 | "label": "import", 109 | "keyword": "import", 110 | "beforeExpr": false, 111 | "startsExpr": false, 112 | "rightAssociative": false, 113 | "isLoop": false, 114 | "isAssign": false, 115 | "prefix": false, 116 | "postfix": false, 117 | "binop": null, 118 | "updateContext": null 119 | }, 120 | "value": "import", 121 | "start": 0, 122 | "end": 6, 123 | "loc": { 124 | "start": { 125 | "line": 1, 126 | "column": 0 127 | }, 128 | "end": { 129 | "line": 1, 130 | "column": 6 131 | } 132 | } 133 | }, 134 | { 135 | "type": { 136 | "label": "name", 137 | "beforeExpr": false, 138 | "startsExpr": true, 139 | "rightAssociative": false, 140 | "isLoop": false, 141 | "isAssign": false, 142 | "prefix": false, 143 | "postfix": false, 144 | "binop": null 145 | }, 146 | "value": "bar", 147 | "start": 7, 148 | "end": 10, 149 | "loc": { 150 | "start": { 151 | "line": 1, 152 | "column": 7 153 | }, 154 | "end": { 155 | "line": 1, 156 | "column": 10 157 | } 158 | } 159 | }, 160 | { 161 | "type": { 162 | "label": "name", 163 | "beforeExpr": false, 164 | "startsExpr": true, 165 | "rightAssociative": false, 166 | "isLoop": false, 167 | "isAssign": false, 168 | "prefix": false, 169 | "postfix": false, 170 | "binop": null 171 | }, 172 | "value": "from", 173 | "start": 11, 174 | "end": 15, 175 | "loc": { 176 | "start": { 177 | "line": 1, 178 | "column": 11 179 | }, 180 | "end": { 181 | "line": 1, 182 | "column": 15 183 | } 184 | } 185 | }, 186 | { 187 | "type": { 188 | "label": "string", 189 | "beforeExpr": false, 190 | "startsExpr": true, 191 | "rightAssociative": false, 192 | "isLoop": false, 193 | "isAssign": false, 194 | "prefix": false, 195 | "postfix": false, 196 | "binop": null, 197 | "updateContext": null 198 | }, 199 | "value": "./bar", 200 | "start": 16, 201 | "end": 23, 202 | "loc": { 203 | "start": { 204 | "line": 1, 205 | "column": 16 206 | }, 207 | "end": { 208 | "line": 1, 209 | "column": 23 210 | } 211 | } 212 | }, 213 | { 214 | "type": { 215 | "label": ";", 216 | "beforeExpr": true, 217 | "startsExpr": false, 218 | "rightAssociative": false, 219 | "isLoop": false, 220 | "isAssign": false, 221 | "prefix": false, 222 | "postfix": false, 223 | "binop": null, 224 | "updateContext": null 225 | }, 226 | "start": 23, 227 | "end": 24, 228 | "loc": { 229 | "start": { 230 | "line": 1, 231 | "column": 23 232 | }, 233 | "end": { 234 | "line": 1, 235 | "column": 24 236 | } 237 | } 238 | }, 239 | { 240 | "type": { 241 | "label": "eof", 242 | "beforeExpr": false, 243 | "startsExpr": false, 244 | "rightAssociative": false, 245 | "isLoop": false, 246 | "isAssign": false, 247 | "prefix": false, 248 | "postfix": false, 249 | "binop": null, 250 | "updateContext": null 251 | }, 252 | "start": 24, 253 | "end": 24, 254 | "loc": { 255 | "start": { 256 | "line": 1, 257 | "column": 24 258 | }, 259 | "end": { 260 | "line": 1, 261 | "column": 24 262 | } 263 | } 264 | } 265 | ] 266 | }; 267 | -------------------------------------------------------------------------------- /fixtures/filing-cabinet/foo.jsx: -------------------------------------------------------------------------------- 1 | export default () => { 2 | return ; 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/filing-cabinet/foo.scss: -------------------------------------------------------------------------------- 1 | @import "~bootstrap/index"; -------------------------------------------------------------------------------- /fixtures/filing-cabinet/foo2.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teambit/bit-javascript/2e08819e93321ae9654788be3aaabae5ebe87a73/fixtures/filing-cabinet/foo2.scss -------------------------------------------------------------------------------- /fixtures/filing-cabinet/mockedJSFiles.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'js': { 3 | 'es6': { 4 | 'foo.js': 'import bar from "./bar";', 5 | 'foo.jsx': 'import React from "react"; export default () => { return (
); }', 6 | 'bar.js': 'export default function() {};' 7 | }, 8 | 'cjs': { 9 | 'foo.js': 'module.exports = 1;', 10 | 'bar.jsx': 'var React = require("react"); module.exports = function() { return (
); };', 11 | 'baz.scss': '.main: {}', 12 | 'pkg.json': '' 13 | }, 14 | 'ts': { 15 | 'index.ts': 'import foo from "./foo";', 16 | 'foo.ts': 'export default 1;' 17 | }, 18 | 'amd': { 19 | 'foo.js': 'define(["./bar"], function(bar){ return bar; });', 20 | 'bar.js': 'define({});' 21 | }, 22 | 'commonjs': { 23 | 'foo.js': 'var bar = require("./bar");', 24 | 'bar.js': 'module.exports = function() {};', 25 | 'foo.baz': 'module.exports = "yo";', 26 | 'index.js': '', 27 | 'module.entry.js': 'import * as module from "module.entry"', 28 | 'subdir': { 29 | 'module.js': 'var entry = require("../");', 30 | 'index.js': '' 31 | }, 32 | 'test': { 33 | 'index.spec.js': 'var subdir = require("subdir");' 34 | } 35 | }, 36 | 'node_modules': { 37 | 'lodash.assign': { 38 | 'index.js': 'module.exports = function() {};' 39 | }, 40 | 'module.entry': { 41 | 'index.main.js': 'module.exports = function() {};', 42 | 'index.module.js': 'module.exports = function() {};', 43 | 'package.json': '{ "main": "index.main.js", "module": "index.module.js" }' 44 | }, 45 | 'nested': { 46 | 'index.js': 'require("lodash.assign")', 47 | 'node_modules': { 48 | 'lodash.assign': { 49 | 'index.js': 'module.exports = function() {};' 50 | } 51 | } 52 | } 53 | }, 54 | 'withIndex': { 55 | 'subdir': { 56 | 'index.js': '' 57 | }, 58 | 'index.js': 'var sub = require("./subdir");' 59 | } 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /fixtures/filing-cabinet/node_modules/bootstrap/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teambit/bit-javascript/2e08819e93321ae9654788be3aaabae5ebe87a73/fixtures/filing-cabinet/node_modules/bootstrap/index.scss -------------------------------------------------------------------------------- /fixtures/filing-cabinet/root1/mod1.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /fixtures/filing-cabinet/root2/mod2.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /fixtures/filing-cabinet/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: "./index.js", 3 | resolve: { 4 | alias: { 5 | R: './node_modules/resolve' 6 | } 7 | } 8 | }; -------------------------------------------------------------------------------- /fixtures/missing-deps.js: -------------------------------------------------------------------------------- 1 | const a = require('non-exist-package'); // missing package 2 | const b = require('../non-exist-dep'); // missing dependency -------------------------------------------------------------------------------- /fixtures/path-map.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "file": "bar/foo.js", 4 | "dependencies": [ 5 | { 6 | "importSource": "../utils", 7 | "resolvedDep": "utils/index.js", 8 | "importSpecifiers": [ 9 | { 10 | "isDefault": false, 11 | "name": "isString" 12 | }, 13 | { 14 | "isDefault": false, 15 | "name": "isArray" 16 | } 17 | ] 18 | } 19 | ] 20 | }, 21 | { 22 | "file": "utils/index.js", 23 | "dependencies": [ 24 | { 25 | "importSource": "./is-string", 26 | "resolvedDep": "utils/is-string/index.js", 27 | "importSpecifiers": [ 28 | { 29 | "isDefault": true, 30 | "name": "isString", 31 | "exported": true 32 | } 33 | ] 34 | }, 35 | { 36 | "importSource": "./is-array", 37 | "resolvedDep": "utils/is-array/index.js", 38 | "importSpecifiers": [ 39 | { 40 | "isDefault": true, 41 | "name": "isArray", 42 | "exported": true 43 | } 44 | ] 45 | } 46 | ] 47 | }, 48 | { 49 | "file": "utils/is-string/index.js", 50 | "dependencies": [ 51 | { 52 | "importSource": "./is-string", 53 | "resolvedDep": "utils/is-string/is-string.js", 54 | "importSpecifiers": [ 55 | { 56 | "isDefault": true, 57 | "name": "isString", 58 | "exported": true 59 | } 60 | ] 61 | } 62 | ] 63 | }, 64 | { 65 | "file": "utils/is-string/is-string.js", 66 | "dependencies": [] 67 | }, 68 | { 69 | "file": "utils/is-array/index.js", 70 | "dependencies": [ 71 | { 72 | "importSource": "./is-array", 73 | "resolvedDep": "utils/is-array/is-array.js", 74 | "importSpecifiers": [ 75 | { 76 | "isDefault": true, 77 | "name": "isArray", 78 | "exported": true 79 | } 80 | ] 81 | } 82 | ] 83 | }, 84 | { 85 | "file": "utils/is-array/is-array.js", 86 | "dependencies": [] 87 | } 88 | ] -------------------------------------------------------------------------------- /fixtures/precinct/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | jshint: { 5 | files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'], 6 | options: { 7 | globals: { 8 | jQuery: true 9 | } 10 | } 11 | }, 12 | watch: { 13 | files: ['<%= jshint.files %>'], 14 | tasks: ['jshint'] 15 | } 16 | }); 17 | 18 | grunt.loadNpmTasks('grunt-contrib-jshint'); 19 | grunt.loadNpmTasks('grunt-contrib-watch'); 20 | 21 | grunt.registerTask('default', ['jshint']); 22 | 23 | }; -------------------------------------------------------------------------------- /fixtures/precinct/amd.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './a', 3 | './b' 4 | ], function(a, b) { 5 | 6 | }); 7 | -------------------------------------------------------------------------------- /fixtures/precinct/cjsExportLazy.js: -------------------------------------------------------------------------------- 1 | module.exports = function({ 2 | // Just requiring any files that exist 3 | amd = require('./amd'), 4 | es6 = require('./es6').foo, 5 | es7 = require('./es7'), 6 | }) { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/precinct/cjsMixedImport.js: -------------------------------------------------------------------------------- 1 | var bar = require('./bar'); 2 | import foo from './foo'; 3 | -------------------------------------------------------------------------------- /fixtures/precinct/commonjs.js: -------------------------------------------------------------------------------- 1 | var a = require('./a'), 2 | b = require('./b'); 3 | -------------------------------------------------------------------------------- /fixtures/precinct/coreModules.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var path = require('path'); 3 | var fs = require('fs'); -------------------------------------------------------------------------------- /fixtures/precinct/es6.js: -------------------------------------------------------------------------------- 1 | import { square, diag } from 'lib'; 2 | console.log(square(11)); // 121 3 | console.log(diag(4, 3)); // 5 -------------------------------------------------------------------------------- /fixtures/precinct/es6MixedExportLazy.js: -------------------------------------------------------------------------------- 1 | export default function({ 2 | // Just requiring any files that exist 3 | amd = require('./amd'), 4 | es6 = require('./es6'), 5 | es7 = require('./es7') 6 | }) { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/precinct/es6MixedImport.js: -------------------------------------------------------------------------------- 1 | import foo from './foo'; 2 | var bar = require('./bar'); 3 | -------------------------------------------------------------------------------- /fixtures/precinct/es6NoImport.js: -------------------------------------------------------------------------------- 1 | export const sqrt = Math.sqrt; 2 | export function square(x) { 3 | return x * x; 4 | } 5 | export function diag(x, y) { 6 | return sqrt(square(x) + square(y)); 7 | } -------------------------------------------------------------------------------- /fixtures/precinct/es6WithError.js: -------------------------------------------------------------------------------- 1 | import { square, diag } from 'lib' // error, semicolon 2 | console.log(square(11)); // 121 3 | console.log(diag(4, 3); // 5, error, missing paren -------------------------------------------------------------------------------- /fixtures/precinct/es7.js: -------------------------------------------------------------------------------- 1 | import { square, diag } from 'lib'; 2 | async function foo() {}; 3 | -------------------------------------------------------------------------------- /fixtures/precinct/exampleAST.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'Program', 3 | body: [{ 4 | type: 'VariableDeclaration', 5 | declarations: [{ 6 | type: 'VariableDeclarator', 7 | id: { 8 | type: 'Identifier', 9 | name: 'a' 10 | }, 11 | init: { 12 | type: 'CallExpression', 13 | callee: { 14 | type: 'Identifier', 15 | name: 'require' 16 | }, 17 | arguments: [{ 18 | type: 'Literal', 19 | value: './a', 20 | raw: './a' 21 | }] 22 | } 23 | }], 24 | kind: 'var' 25 | }] 26 | }; 27 | -------------------------------------------------------------------------------- /fixtures/precinct/jsx.js: -------------------------------------------------------------------------------- 1 | import { square, diag } from 'lib'; 2 | const tmpl = ; 3 | -------------------------------------------------------------------------------- /fixtures/precinct/none.js: -------------------------------------------------------------------------------- 1 | var a = new window.Foo(); 2 | -------------------------------------------------------------------------------- /fixtures/precinct/styles.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import url("baz.css"); 3 | @value a from 'bla.css'; 4 | @value a, b as x from url(another.css); 5 | -------------------------------------------------------------------------------- /fixtures/precinct/styles.less: -------------------------------------------------------------------------------- 1 | @import "_foo"; 2 | @import "_bar.css"; 3 | @import "baz.less"; 4 | -------------------------------------------------------------------------------- /fixtures/precinct/styles.sass: -------------------------------------------------------------------------------- 1 | @import _foo 2 | -------------------------------------------------------------------------------- /fixtures/precinct/styles.scss: -------------------------------------------------------------------------------- 1 | @import "_foo"; 2 | @import "baz.scss"; 3 | -------------------------------------------------------------------------------- /fixtures/precinct/styles.styl: -------------------------------------------------------------------------------- 1 | @import "mystyles" 2 | @import "styles2.styl" 3 | @require "styles3.styl"; 4 | @require "styles4"; -------------------------------------------------------------------------------- /fixtures/precinct/typescript.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { square, diag } from 'lib'; 3 | import foo from './bar'; 4 | import "./my-module.js"; // Import a module for side-effects only 5 | import zip = require("./ZipCodeValidator"); // needed when importing a module using `export =` syntax 6 | 7 | console.log(square(11)); // 121 8 | console.log(diag(4, 3)); // 5 9 | -------------------------------------------------------------------------------- /fixtures/precinct/typescriptWithError.ts: -------------------------------------------------------------------------------- 1 | import { square, diag } from 'lib'; 2 | 3 | console.log(diag(4, 3); // error, missing bracket 4 | -------------------------------------------------------------------------------- /fixtures/precinct/unparseable.js: -------------------------------------------------------------------------------- 1 | { 2 | "very invalid": "javascript", 3 | "this", "is actually json", 4 | "But" not even valid json. 5 | } -------------------------------------------------------------------------------- /fixtures/unsupported-file.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teambit/bit-javascript/2e08819e93321ae9654788be3aaabae5ebe87a73/fixtures/unsupported-file.pdf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bit-javascript", 3 | "version": "2.1.6-dev.3", 4 | "scripts": { 5 | "lint": "tsc && eslint \"src/**/*.ts\"", 6 | "lint-circle": "eslint \"src/**/*.ts\" --format junit -o junit/eslint-results.xml", 7 | "check-types": "tsc", 8 | "precommit": "lint-staged", 9 | "format": "prettier \"src/**/*.ts\" --write", 10 | "test": "mocha --require ./babel-register './src/**/*.spec.ts'", 11 | "test-circle": "mocha --require ./babel-register --reporter mocha-circleci-reporter './src/**/*.spec.ts'", 12 | "test-appveyor": "mocha --require ./babel-register --reporter mocha-appveyor-reporter \"./src/**/*.spec.ts\"", 13 | "clean": "rm -rf dist", 14 | "build": "babel src -d dist --verbose --extensions \".ts\"", 15 | "watch": "babel --watch src -d dist --source-maps --verbose --extensions \".ts\"", 16 | "prepublishOnly": "npm run clean && npm run build", 17 | "build-debug": "babel src -d dist --source-maps", 18 | "pre-release": "gh-release --prerelease", 19 | "release": "gh-release", 20 | "release:circle": "gh-release -y" 21 | }, 22 | "bin": { 23 | "bitjs": "bin/bitjs.js" 24 | }, 25 | "homepage": "https://github.com/teambit/bit-javascript", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/teambit/bit-javascript" 29 | }, 30 | "keywords": [ 31 | "bit", 32 | "node", 33 | "components", 34 | "bit-node", 35 | "bit-js", 36 | "bit-component", 37 | "bit-components" 38 | ], 39 | "main": "dist/index.js", 40 | "dependencies": { 41 | "@babel/runtime": "^7.6.3", 42 | "@typescript-eslint/typescript-estree": "^2.28.0", 43 | "app-module-path": "^2.2.0", 44 | "bluebird": "^3.7.1", 45 | "camelcase": "^5.3.1", 46 | "chalk": "^2.4.2", 47 | "commander": "^2.14.1", 48 | "css-tree": "^1.0.0-alpha.29", 49 | "debug": "^4.1.1", 50 | "detective-amd": "^3.0.0", 51 | "detective-stylus": "^1.0.0", 52 | "enhanced-resolve": "^4.1.1", 53 | "fs-extra": "^8.1.0", 54 | "glob": "^7.1.1", 55 | "ini-builder": "^1.1.1", 56 | "is-relative-path": "^2.0.0", 57 | "is-url": "^1.2.4", 58 | "lodash.partition": "^4.6.0", 59 | "lodash.set": "^4.3.2", 60 | "module-definition": "^3.2.0", 61 | "module-lookup-amd": "^6.2.0", 62 | "node-source-walk": "^4.2.0", 63 | "object-assign": "^4.1.1", 64 | "ora": "^1.2.0", 65 | "parents": "^1.0.1", 66 | "ramda": "^0.26.1", 67 | "regenerator-runtime": "^0.10.5", 68 | "resolve": "^1.5.0", 69 | "resolve-dependency-path": "^2.0.0", 70 | "sass-lookup": "^3.0.0", 71 | "stylable": "5.2.2", 72 | "stylus-lookup": "^3.0.2", 73 | "typescript": "3.8.3", 74 | "user-home": "^2.0.0", 75 | "vue-template-compiler": "^2.5.13" 76 | }, 77 | "devDependencies": { 78 | "@babel/cli": "^7.6.4", 79 | "@babel/core": "^7.6.4", 80 | "@babel/plugin-proposal-class-properties": "^7.5.5", 81 | "@babel/plugin-proposal-object-rest-spread": "^7.6.2", 82 | "@babel/plugin-transform-async-to-generator": "^7.4.4", 83 | "@babel/plugin-transform-flow-strip-types": "^7.0.0", 84 | "@babel/plugin-transform-modules-commonjs": "^7.2.0", 85 | "@babel/plugin-transform-runtime": "^7.0.0", 86 | "@babel/preset-env": "^7.0.0", 87 | "@babel/preset-flow": "^7.0.0", 88 | "@babel/preset-typescript": "^7.6.0", 89 | "@babel/register": "^7.4.4", 90 | "@types/mocha": "^5.2.7", 91 | "@types/node": "^12.7.9", 92 | "@typescript-eslint/eslint-plugin": "^2.28.0", 93 | "@typescript-eslint/parser": "^2.28.0", 94 | "babel-eslint": "^10.0.3", 95 | "babel-plugin-syntax-async-functions": "^6.13.0", 96 | "babel-plugin-transform-builtin-extend": "^1.1.2", 97 | "chai": "^4.1.2", 98 | "eslint": "^6.5.1", 99 | "eslint-config-airbnb-typescript": "^5.0.0", 100 | "eslint-config-prettier": "^6.3.0", 101 | "eslint-plugin-comments": "^1.0.0", 102 | "eslint-plugin-eslint-comments": "^3.1.2", 103 | "eslint-plugin-import": "^2.18.2", 104 | "eslint-plugin-mocha": "^6.1.1", 105 | "eslint-plugin-promise": "^4.2.1", 106 | "eslint-plugin-unicorn": "^12.0.2", 107 | "gh-release": "^3.5.0", 108 | "husky": "^0.14.3", 109 | "lint-staged": "^7.1.0", 110 | "mocha": "^5.1.1", 111 | "mocha-appveyor-reporter": "^0.4.2", 112 | "mocha-circleci-reporter": "0.0.3", 113 | "mocha-junit-reporter": "^1.13.0", 114 | "mock-fs": "^4.8.0", 115 | "prettier": "^1.18.2", 116 | "rewire": "^4.0.1", 117 | "sinon": "^6.0.1" 118 | }, 119 | "lint-staged": { 120 | "*.{ts,js,css}": [ 121 | "prettier --write", 122 | "git add" 123 | ] 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /register-component.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/register-component'); 2 | -------------------------------------------------------------------------------- /scripts/compare-versions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "compare $1 to $2" 4 | if [ $1 == $2 ]; then 5 | echo "Versions match" 6 | exit 0; 7 | else 8 | echo "Versions not match" 9 | exit 1; 10 | fi -------------------------------------------------------------------------------- /src/actions/get-dependencies.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { getDependencyTree } from '../dependency-builder'; 3 | 4 | export default (async function getDependenciesAction(baseDir, file, bindingPrefix, resolveConfig): Promise { 5 | const consumerPath = process.cwd(); 6 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 7 | return getDependencyTree({ 8 | baseDir, 9 | consumerPath, 10 | filePaths: [file], 11 | bindingPrefix, 12 | resolveModulesConfig: resolveConfig 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/actions/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: 0 */ 2 | 3 | // import bindAction, { bindSpecificComponentsAction } from './bind'; 4 | import getDependenciesAction from './get-dependencies'; 5 | 6 | export { 7 | // bindAction, 8 | // bindSpecificComponentsAction, 9 | getDependenciesAction 10 | }; 11 | -------------------------------------------------------------------------------- /src/cli/app.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | import commandRegistrar from './command-registrar'; 3 | 4 | commandRegistrar(); 5 | -------------------------------------------------------------------------------- /src/cli/command-registrar.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | import chalk from 'chalk'; 3 | import program from 'commander'; 4 | import pkg from '../../package.json'; 5 | import loader from './loader'; 6 | import commands from './commands/command-list'; 7 | 8 | function bitError(message) { 9 | return chalk.red( 10 | `${message 11 | .split('\n') // eslint-disable-line 12 | .map(m => `bitjs ERR! ${m}`) 13 | .join('\n')}\n` 14 | ); 15 | } 16 | 17 | function errorHandler(e) { 18 | loader.off(); 19 | process.stderr.write(bitError(e.message)); 20 | if (e.code) { 21 | process.stderr.write(bitError(`\ncode: ${e.code}\n`)); 22 | } 23 | process.stderr.write(bitError(e.stack)); 24 | process.exit(1); 25 | } 26 | 27 | function logAndExit(str) { 28 | loader.off(); 29 | if (str) { 30 | console.log(str); // eslint-disable-line 31 | } 32 | process.exit(0); 33 | } 34 | 35 | function start() { 36 | program.version(pkg.version).description('bit driver for javascript'); 37 | commands.forEach(c => { 38 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 39 | const currentCommand = program.command(c.name).description(c.description); 40 | 41 | if (c.options && Array.isArray(c.options)) { 42 | c.options.forEach(option => currentCommand.option(`${option.alias} ${option.name}`, option.description)); 43 | } 44 | 45 | currentCommand.action((args, options) => { 46 | if (c.loader === true) loader.on(); 47 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 48 | loader.start(c.loaderText || `performing ${c.name} command`); 49 | c.action(args, options) 50 | .then(c.report) 51 | .then(logAndExit) 52 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 53 | .catch(c.handleError || errorHandler); 54 | }); 55 | }); 56 | 57 | program.parse(process.argv); 58 | } 59 | 60 | export default start; 61 | -------------------------------------------------------------------------------- /src/cli/commands/command-list.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | import getDependencies from './get-dependencies'; 3 | 4 | const commands = [getDependencies]; 5 | 6 | export default commands; 7 | -------------------------------------------------------------------------------- /src/cli/commands/get-dependencies.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Command } from './types'; 3 | import { getDependenciesAction } from '../../actions'; 4 | import { DEFAULT_BINDINGS_PREFIX } from '../../constants'; 5 | 6 | const report = data => { 7 | if (!data) return 'No dependencies found!'; 8 | return JSON.stringify(data, null, ' '); 9 | }; 10 | 11 | const resolveConfig = undefined; // @todo: figure out how to get the data from the command line, maybe as a file 12 | const getDependenciesCommand: Command = { 13 | name: 'get-dependencies ', 14 | description: 'get dependencies list of a file', 15 | action: (baseDir, file) => getDependenciesAction(baseDir, file, DEFAULT_BINDINGS_PREFIX, resolveConfig), 16 | report, 17 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 18 | loaderText: 'Finding dependencies', 19 | loader: true 20 | }; 21 | 22 | export default getDependenciesCommand; 23 | -------------------------------------------------------------------------------- /src/cli/commands/types.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type Command = { 3 | name: string; 4 | description?: string | null | undefined; 5 | options?: Array<{ alias: string; name: string; description: string }> | null | undefined; 6 | action: (args: Record, options: Record) => Promise; 7 | report: (...args: any[]) => string; 8 | handleError?: (e: Error) => string; 9 | loader?: boolean; 10 | }; 11 | -------------------------------------------------------------------------------- /src/cli/loader/index.ts: -------------------------------------------------------------------------------- 1 | import loader from './loader'; 2 | 3 | export default loader; 4 | -------------------------------------------------------------------------------- /src/cli/loader/loader.ts: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | import ora from 'ora'; 3 | 4 | let _loader; 5 | 6 | type Loader = { 7 | on: () => Loader; 8 | off: () => Loader | null; 9 | start: (text: string | null | undefined) => Loader | null; 10 | stop: () => Loader | null; 11 | setText: (text: string | null | undefined) => Loader | null | undefined; 12 | get: () => Loader | null; 13 | }; 14 | 15 | const start = (text: string | null | undefined): Loader | null | undefined => { 16 | if (_loader) { 17 | if (text) _loader.text = text; 18 | _loader.start(); 19 | } 20 | 21 | return _loader; 22 | }; 23 | 24 | const setText = (text: string): Loader | null | undefined => { 25 | if (_loader) _loader.text = text; 26 | return _loader; 27 | }; 28 | 29 | const get = (): Loader | null | undefined => _loader; 30 | 31 | const stop = (): Loader | null | undefined => { 32 | if (_loader) _loader.stop(); 33 | return _loader; 34 | }; 35 | 36 | const on = (): Loader => { 37 | if (!_loader) _loader = ora({ text: '' }); 38 | return _loader; 39 | }; 40 | 41 | const off = (): Loader | null | undefined => { 42 | stop(); 43 | _loader = null; 44 | return _loader; 45 | }; 46 | 47 | const loader: Loader = { 48 | on, 49 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 50 | off, 51 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 52 | stop, 53 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 54 | start, 55 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 56 | setText, 57 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 58 | get 59 | }; 60 | 61 | export default loader; 62 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * cache root directory 3 | */ 4 | export const LATEST_VERSION = 'latest'; 5 | 6 | export const PACKAGE_JSON = 'package.json'; 7 | 8 | export const COMPONENTS_DIRNAME = 'components'; 9 | 10 | export const VERSION_DELIMITER = '@'; 11 | 12 | export const NO_PLUGIN_TYPE = 'none'; 13 | 14 | export const MODULE_NAME = 'bit'; 15 | 16 | export const DEFAULT_BINDINGS_PREFIX = '@bit'; 17 | 18 | export const MODULES_DIR = 'node_modules'; 19 | 20 | export const INDEX_JS = 'index.js'; 21 | 22 | export const REMOTE_ALIAS_SIGN = '@'; 23 | 24 | export const LOCAL_SCOPE_NOTATION = '@this'; 25 | 26 | export const BIT_MAP = '.bit.map.json'; 27 | 28 | export const SUPPORTED_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.css', '.scss', '.less', '.sass', '.vue', '.styl']; 29 | 30 | export const COMPONENT_ORIGINS = { 31 | IMPORTED: 'IMPORTED', 32 | AUTHORED: 'AUTHORED', 33 | NESTED: 'NESTED' 34 | }; 35 | -------------------------------------------------------------------------------- /src/dependency-builder/build-tree.spec.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { expect } from 'chai'; 3 | import * as buildTree from './build-tree'; 4 | 5 | const fixtures = `${__dirname}/../../fixtures`; 6 | const precinctFixtures = path.join(fixtures, 'precinct'); 7 | const buildTreeFixtures = path.join(fixtures, 'build-tree'); 8 | 9 | describe('buildTree', () => { 10 | describe('getDependencyTree', () => { 11 | const dependencyTreeParams = { 12 | baseDir: '.', 13 | consumerPath: __dirname, 14 | filePaths: [], 15 | bindingPrefix: '@bit', 16 | resolveModulesConfig: undefined 17 | }; 18 | it('when no files are passed should return an empty tree', async () => { 19 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 20 | const results = await buildTree.getDependencyTree(dependencyTreeParams); 21 | expect(results).to.deep.equal({ tree: {} }); 22 | }); 23 | it('when unsupported files are passed should return them with no dependencies', async () => { 24 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 25 | dependencyTreeParams.filePaths = [`${fixtures}/unsupported-file.pdf`]; 26 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 27 | const results = await buildTree.getDependencyTree(dependencyTreeParams); 28 | expect(results.tree).to.deep.equal({ 'fixtures/unsupported-file.pdf': {} }); 29 | }); 30 | it('when supported and unsupported files are passed should return them all', async () => { 31 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 32 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 33 | dependencyTreeParams.filePaths = [`${fixtures}/unsupported-file.pdf`, `${precinctFixtures}/es6.js`]; 34 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 35 | const results = await buildTree.getDependencyTree(dependencyTreeParams); 36 | expect(results.tree).to.have.property('fixtures/unsupported-file.pdf'); 37 | expect(results.tree).to.have.property('fixtures/precinct/es6.js'); 38 | }); 39 | it('when a js file has parsing error it should add the file to the tree with the error instance', async () => { 40 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 41 | dependencyTreeParams.filePaths = [`${precinctFixtures}/unparseable.js`]; 42 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 43 | const results = await buildTree.getDependencyTree(dependencyTreeParams); 44 | const unParsedFile = 'fixtures/precinct/unparseable.js'; 45 | expect(results.tree).to.have.property(unParsedFile); 46 | expect(results.tree[unParsedFile]).to.have.property('error'); 47 | expect(results.tree[unParsedFile].error).to.be.instanceof(Error); 48 | }); 49 | it('when a js file has parsing error and it retrieved from the cache it should add the file to the tree with the error instance', async () => { 50 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 51 | dependencyTreeParams.filePaths = [`${precinctFixtures}/unparseable.js`]; 52 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 53 | dependencyTreeParams.visited = {}; 54 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 55 | const results = await buildTree.getDependencyTree(dependencyTreeParams); 56 | const unParsedFile = 'fixtures/precinct/unparseable.js'; 57 | expect(results.tree).to.have.property(unParsedFile); 58 | expect(results.tree[unParsedFile]).to.have.property('error'); 59 | expect(results.tree[unParsedFile].error).to.be.instanceof(Error); 60 | 61 | // second time, this time it fetches from the cache (visited object) 62 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 63 | const resultsCached = await buildTree.getDependencyTree(dependencyTreeParams); 64 | expect(resultsCached.tree).to.have.property(unParsedFile); 65 | expect(resultsCached.tree[unParsedFile]).to.have.property('error'); 66 | expect(resultsCached.tree[unParsedFile].error).to.be.instanceof(Error); 67 | }); 68 | it.skip('when a css file has parsing error it should add the file to the tree with the error instance', async () => { 69 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 70 | dependencyTreeParams.filePaths = [`${buildTreeFixtures}/unparsed.css`]; 71 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 72 | const results = await buildTree.getDependencyTree(dependencyTreeParams); 73 | const unParsedFile = 'fixtures/build-tree/unparsed.css'; 74 | expect(results.tree).to.have.property(unParsedFile); 75 | expect(results.tree[unParsedFile]).to.have.property('error'); 76 | expect(results.tree[unParsedFile].error).to.be.instanceof(Error); 77 | }); 78 | describe('when a dependency of dependency has parsing error', () => { 79 | let results; 80 | before(async () => { 81 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 82 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 83 | dependencyTreeParams.filePaths = [`${buildTreeFixtures}/a.js`, `${buildTreeFixtures}/b.js`]; 84 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 85 | results = await buildTree.getDependencyTree(dependencyTreeParams); 86 | }); 87 | it('should add all the files to the tree', async () => { 88 | expect(results.tree).to.have.property('fixtures/build-tree/a.js'); 89 | expect(results.tree).to.have.property('fixtures/build-tree/b.js'); 90 | expect(results.tree).to.have.property('fixtures/build-tree/unparsed.js'); 91 | }); 92 | it('should not add the error to the files without parsing error', () => { 93 | expect(results.tree['fixtures/build-tree/a.js']).to.not.have.property('error'); 94 | expect(results.tree['fixtures/build-tree/b.js']).to.not.have.property('error'); 95 | }); 96 | it('should add the parsing error to the un-parsed file', () => { 97 | expect(results.tree['fixtures/build-tree/unparsed.js']).to.have.property('error'); 98 | expect(results.tree['fixtures/build-tree/unparsed.js'].error).to.be.instanceof(Error); 99 | }); 100 | }); 101 | describe('missing dependencies', () => { 102 | let results; 103 | const missingDepsFile = 'fixtures/missing-deps.js'; 104 | before(async () => { 105 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 106 | dependencyTreeParams.filePaths = [`${fixtures}/missing-deps.js`]; 107 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 108 | results = await buildTree.getDependencyTree(dependencyTreeParams); 109 | expect(results.tree).to.have.property(missingDepsFile); 110 | expect(results.tree[missingDepsFile]).to.have.property('missing'); 111 | }); 112 | it('it should add the missing dependency to the missing section in the tree', async () => { 113 | expect(results.tree[missingDepsFile].missing).to.have.property('files'); 114 | expect(results.tree[missingDepsFile].missing.files[0]).to.equal('../non-exist-dep'); 115 | }); 116 | it('it should add the missing package to the missing section in the tree', async () => { 117 | expect(results.tree[missingDepsFile].missing).to.have.property('packages'); 118 | expect(results.tree[missingDepsFile].missing.packages[0]).to.equal('non-exist-package'); 119 | }); 120 | }); 121 | describe('tree shaking with cycle', () => { 122 | describe('when a file imports from itself', () => { 123 | let results; 124 | before(async () => { 125 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 126 | dependencyTreeParams.filePaths = [`${buildTreeFixtures}/tree-shaking-cycle/self-cycle.js`]; 127 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 128 | results = await buildTree.getDependencyTree(dependencyTreeParams); 129 | }); 130 | it('should not throw an error and should remove itself from the dependencies files', () => { 131 | const file = 'fixtures/build-tree/tree-shaking-cycle/self-cycle.js'; 132 | expect(results.tree[file].files).to.be.an('array').and.empty; 133 | }); 134 | }); 135 | describe('cycle with multiple files', () => { 136 | let results; 137 | before(async () => { 138 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 139 | dependencyTreeParams.filePaths = [`${buildTreeFixtures}/tree-shaking-cycle/foo.js`]; 140 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 141 | results = await buildTree.getDependencyTree(dependencyTreeParams); 142 | }); 143 | it('should not recognize the cycle dependencies as link files', () => { 144 | const file = 'fixtures/build-tree/tree-shaking-cycle/foo.js'; 145 | expect(results.tree[file].files) 146 | .to.be.an('array') 147 | .and.have.lengthOf(1); 148 | const indexDep = results.tree[file].files[0]; 149 | expect(indexDep.file).to.equal('fixtures/build-tree/tree-shaking-cycle/index.js'); 150 | expect(indexDep).to.not.have.property('isLink'); 151 | expect(indexDep).to.not.have.property('linkDependencies'); 152 | }); 153 | }); 154 | }); 155 | describe('fileA imports varX from fileB, fileB imports varX from fileC but not export it', () => { 156 | let results; 157 | before(async () => { 158 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 159 | dependencyTreeParams.filePaths = [`${buildTreeFixtures}/not-link-file/file-a.js`]; 160 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 161 | results = await buildTree.getDependencyTree(dependencyTreeParams); 162 | }); 163 | it('should not mark fileB as a link file', () => { 164 | const fileA = 'fixtures/build-tree/not-link-file/file-a.js'; 165 | expect(results.tree[fileA].files) 166 | .to.be.an('array') 167 | .with.lengthOf(1); 168 | const fileBDep = results.tree[fileA].files[0]; 169 | expect(fileBDep).to.not.have.property('isLink'); 170 | }); 171 | }); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /src/dependency-builder/dependency-tree/Config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * this file had been forked from https://github.com/dependents/node-dependency-tree 3 | */ 4 | 5 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 6 | const path = require('path'); 7 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 8 | const debug = require('debug')('tree'); 9 | 10 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 11 | function Config(options) { 12 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 13 | this.filename = options.filename; 14 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 15 | this.directory = options.directory || options.root; 16 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 17 | this.visited = options.visited || {}; 18 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 19 | this.errors = options.errors || {}; 20 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 21 | this.nonExistent = options.nonExistent || []; 22 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 23 | this.isListForm = options.isListForm; 24 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 25 | this.requireConfig = options.config || options.requireConfig; 26 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 27 | this.webpackConfig = options.webpackConfig; 28 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 29 | this.detectiveConfig = options.detective || options.detectiveConfig || {}; 30 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 31 | this.pathMap = options.pathMap || []; 32 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 33 | this.resolveConfig = options.resolveConfig; 34 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 35 | this.cacheProjectAst = options.cacheProjectAst; 36 | 37 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 38 | this.filter = options.filter; 39 | 40 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 41 | if (!this.filename) { 42 | throw new Error('filename not given'); 43 | } 44 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 45 | if (!this.directory) { 46 | throw new Error('directory not given'); 47 | } 48 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 49 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 50 | if (this.filter && typeof this.filter !== 'function') { 51 | throw new Error('filter must be a function'); 52 | } 53 | 54 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 55 | debug(`given filename: ${this.filename}`); 56 | 57 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 58 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 59 | this.filename = path.resolve(process.cwd(), this.filename); 60 | 61 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 62 | debug(`resolved filename: ${this.filename}`); 63 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 64 | debug('visited: ', this.visited); 65 | } 66 | 67 | Config.prototype.clone = function() { 68 | return new Config(this); 69 | }; 70 | 71 | module.exports = Config; 72 | -------------------------------------------------------------------------------- /src/dependency-builder/dependency-tree/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * this file had been forked from https://github.com/dependents/node-dependency-tree 3 | */ 4 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 5 | const debug = require('debug')('tree'); 6 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 7 | const fs = require('fs'); 8 | const precinct = require('../precinct'); 9 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 10 | const cabinet = require('../filing-cabinet'); 11 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 12 | const Config = require('./Config'); 13 | 14 | /** 15 | * Recursively find all dependencies (avoiding circular) traversing the entire dependency tree 16 | * and returns a flat list of all unique, visited nodes 17 | * 18 | * @param {Object} options 19 | * @param {String} options.filename - The path of the module whose tree to traverse 20 | * @param {String} options.directory - The directory containing all JS files 21 | * @param {String} [options.requireConfig] - The path to a requirejs config 22 | * @param {String} [options.webpackConfig] - The path to a webpack config 23 | * @param {Object} [options.visited] - Cache of visited, absolutely pathed files that should not be reprocessed. 24 | * Format is a filename -> tree as list lookup table 25 | * @param {Array} [options.nonExistent] - List of partials that do not exist 26 | * @return {Object} 27 | */ 28 | module.exports = function(options) { 29 | const config = new Config(options); 30 | 31 | if (!fs.existsSync(config.filename)) { 32 | debug(`file ${config.filename} does not exist`); 33 | return {}; 34 | } 35 | 36 | return traverse(config); 37 | }; 38 | 39 | /** 40 | * Executes a post-order depth first search on the dependency tree and returns a 41 | * list of absolute file paths. The order of files in the list will be the 42 | * proper concatenation order for bundling. 43 | * 44 | * In other words, for any file in the list, all of that file's dependencies (direct or indirect) will appear at 45 | * lower indices in the list. The root (entry point) file will therefore appear last. 46 | * 47 | * The list will not contain duplicates. 48 | * 49 | * Params are those of module.exports 50 | */ 51 | module.exports.toList = function(options) { 52 | options.isListForm = true; 53 | 54 | return module.exports(options); 55 | }; 56 | 57 | /** 58 | * Returns the list of dependencies for the given filename 59 | * 60 | * Protected for testing 61 | * 62 | * @param {Config} config 63 | * @return {Array} 64 | */ 65 | module.exports._getDependencies = function(config) { 66 | let dependenciesRaw; // from some detectives it comes as an array, from some it is an object 67 | const precinctOptions = config.detectiveConfig; 68 | precinctOptions.includeCore = false; 69 | delete precinct.ast; 70 | 71 | try { 72 | dependenciesRaw = precinct.paperwork(config.filename, precinctOptions); 73 | } catch (e) { 74 | debug(`error getting dependencies: ${e.message}`); 75 | debug(e.stack); 76 | e.code = 'PARSING_ERROR'; 77 | config.errors[config.filename] = e; 78 | dependenciesRaw = []; 79 | } 80 | const dependencies = 81 | typeof dependenciesRaw === 'object' && !Array.isArray(dependenciesRaw) 82 | ? Object.keys(dependenciesRaw) 83 | : dependenciesRaw; 84 | const isDependenciesArray = Array.isArray(dependenciesRaw); 85 | debug(`extracted ${dependencies.length} dependencies: `, dependencies); 86 | 87 | const resolvedDependencies = []; 88 | const pathMapDependencies = []; 89 | const pathMapFile = { file: config.filename }; 90 | 91 | dependencies.forEach(dependency => processDependency(dependency)); 92 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 93 | pathMapFile.dependencies = pathMapDependencies; 94 | config.pathMap.push(pathMapFile); 95 | return resolvedDependencies; 96 | 97 | function processDependency(dependency) { 98 | if (isHttp(dependency)) { 99 | debug(`skipping an http dependency: ${dependency}`); 100 | return; 101 | } 102 | const cabinetParams = { 103 | dependency, 104 | filename: config.filename, 105 | directory: config.directory, 106 | ast: precinct.ast, 107 | config: config.requireConfig, 108 | webpackConfig: config.webpackConfig, 109 | resolveConfig: config.resolveConfig 110 | }; 111 | if (!isDependenciesArray && dependenciesRaw[dependency].isScript !== undefined) { 112 | // used for vue 113 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 114 | cabinetParams.isScript = dependenciesRaw[dependency].isScript; 115 | } 116 | let result; 117 | try { 118 | result = cabinet(cabinetParams); 119 | } catch (err) { 120 | debug(`error resolving dependencies: ${err.message}`); 121 | debug(err.stack); 122 | err.code = 'RESOLVE_ERROR'; 123 | throw err; 124 | } 125 | 126 | if (!result) { 127 | debug(`skipping an empty filepath resolution for dependency: ${dependency}`); 128 | addToNonExistent(dependency); 129 | return; 130 | } 131 | 132 | const exists = fs.existsSync(result); 133 | 134 | if (!exists) { 135 | addToNonExistent(dependency); 136 | debug(`skipping non-empty but non-existent resolution: ${result} for dependency: ${dependency}`); 137 | return; 138 | } 139 | const pathMap = { importSource: dependency, resolvedDep: result }; 140 | if (!isDependenciesArray && dependenciesRaw[dependency].importSpecifiers) { 141 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 142 | pathMap.importSpecifiers = dependenciesRaw[dependency].importSpecifiers; 143 | } 144 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 145 | if (cabinetParams.wasCustomResolveUsed) { 146 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 147 | pathMap.isCustomResolveUsed = true; 148 | } 149 | 150 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 151 | pathMapDependencies.push(pathMap); 152 | 153 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 154 | resolvedDependencies.push(result); 155 | } 156 | function addToNonExistent(dependency) { 157 | if (config.nonExistent[config.filename]) { 158 | config.nonExistent[config.filename].push(dependency); 159 | } else { 160 | config.nonExistent[config.filename] = [dependency]; 161 | } 162 | } 163 | }; 164 | 165 | /** 166 | * the traverse is not a recursive function anymore, it has been changed to be iterative to fix 167 | * some performance issues. 168 | * @todo: we have some redundancy here with the `tree` and pathMap`. the `tree` has only the dependencies, 169 | * `pathMap` has the dependencies and some more info, such as importSpecifiers. we should use only 170 | * pathMap and get rid of tree. 171 | */ 172 | function traverse(config) { 173 | const tree = []; 174 | const stack = [config.filename]; 175 | while (stack.length) { 176 | const dependency = stack.pop(); 177 | debug(`traversing ${dependency}`); 178 | if (config.visited[dependency]) { 179 | populateFromCache(dependency); 180 | } else { 181 | traverseDependency(dependency); 182 | } 183 | } 184 | 185 | return tree; 186 | 187 | function traverseDependency(dependency) { 188 | const localConfig = config.clone(); 189 | localConfig.filename = dependency; 190 | let dependencies = module.exports._getDependencies(localConfig); 191 | if (config.filter) { 192 | debug('using filter function to filter out dependencies'); 193 | debug(`number of dependencies before filtering: ${dependencies.length}`); 194 | dependencies = dependencies.filter(function(filePath) { 195 | return localConfig.filter(filePath, localConfig.filename); 196 | }); 197 | debug(`number of dependencies after filtering: ${dependencies.length}`); 198 | } 199 | debug('cabinet-resolved all dependencies: ', dependencies); 200 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 201 | tree[dependency] = dependencies; 202 | const filePathMap = config.pathMap.find(pathMapEntry => pathMapEntry.file === dependency); 203 | if (!filePathMap) throw new Error(`file ${dependency} is missing from PathMap`); 204 | config.visited[dependency] = { 205 | pathMap: filePathMap, 206 | missing: config.nonExistent[dependency], 207 | error: config.errors[dependency] 208 | }; 209 | stack.push(...dependencies); 210 | } 211 | 212 | function populateFromCache(dependency) { 213 | debug(`already visited ${dependency}. Will try to find it and its dependencies in the cache`); 214 | const dependenciesStack = [dependency]; 215 | while (dependenciesStack.length) { 216 | findAllDependenciesInCache(dependenciesStack); 217 | } 218 | } 219 | 220 | function findAllDependenciesInCache(dependenciesStack) { 221 | const dependency = dependenciesStack.pop(); 222 | if (!config.visited[dependency]) { 223 | debug(`unable to find ${dependency} in the cache, it was probably filtered before`); 224 | return; 225 | } 226 | debug(`found ${dependency} in the cache`); 227 | const dependencies = config.visited[dependency].pathMap.dependencies.map(d => d.resolvedDep); 228 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 229 | tree[dependency] = dependencies; 230 | config.pathMap.push(config.visited[dependency].pathMap); 231 | if (config.visited[dependency].missing) { 232 | config.nonExistent[dependency] = config.visited[dependency].missing; 233 | } 234 | if (config.visited[dependency].error) { 235 | config.errors[dependency] = config.visited[dependency].error; 236 | } 237 | dependencies.forEach(d => { 238 | if (!tree[d]) dependenciesStack.push(d); 239 | }); 240 | } 241 | } 242 | 243 | /** 244 | * whether the dependency is from CDN. (http/https) 245 | */ 246 | function isHttp(dependency) { 247 | return Boolean(dependency.startsWith('http://') || dependency.startsWith('https://')); 248 | } 249 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-css-and-preprocessors/index.ts: -------------------------------------------------------------------------------- 1 | // forked and changed from https://github.com/dependents/node-detective-sass 2 | const csstree = require('css-tree'); 3 | const isUrl = require('is-url'); 4 | 5 | /** 6 | * Extract the @import statements from a given file's content 7 | * 8 | * @param {String} fileContent 9 | * @param {String} syntax, can be one of the following: css, less, sass, scss. 10 | * @return {String[]} 11 | */ 12 | module.exports = function detective(fileContent, syntax) { 13 | // eslint-disable-next-line import/no-dynamic-require, global-require 14 | const debug = require('debug')(`detective-${syntax}`); 15 | debug(`parsing ${syntax} syntax`); 16 | if (typeof fileContent === 'undefined') { 17 | throw new Error('content not given'); 18 | } 19 | if (typeof fileContent !== 'string') { 20 | throw new Error('content is not a string'); 21 | } 22 | 23 | let dependencies = []; 24 | 25 | const ast = csstree.parse(fileContent, { 26 | onParseError(error) { 27 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 28 | handleError(error); 29 | } 30 | }); 31 | 32 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 33 | detective.ast = ast; 34 | 35 | csstree.walk(ast, function(node) { 36 | if (!isImportStatement(node)) { 37 | return; 38 | } 39 | 40 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 41 | dependencies = dependencies.concat(extractDependencies(node, syntax)); 42 | dependencies = clearUrlImports(dependencies); 43 | }); 44 | return dependencies; 45 | }; 46 | 47 | function isImportStatement(node) { 48 | if (node.type === 'Atrule' && node.name === 'import') { 49 | return true; 50 | } 51 | return false; 52 | } 53 | 54 | function extractDependencies(importStatementNode) { 55 | // handle URL import @import url("baz.css"); 56 | if ( 57 | importStatementNode.prelude.type === 'AtrulePrelude' && 58 | importStatementNode.prelude.children.tail.data.type === 'Url' 59 | ) { 60 | return importStatementNode.prelude.children.tail.data.value.value.replace(/["']/g, ''); 61 | } 62 | 63 | // simple @import 64 | if ( 65 | importStatementNode.prelude.type === 'AtrulePrelude' && 66 | importStatementNode.prelude.children && 67 | importStatementNode.prelude.children.tail.data.type !== 'Url' 68 | ) { 69 | return importStatementNode.prelude.children.tail.data.value.replace(/["']/g, ''); 70 | } 71 | 72 | // allows imports with no semicolon 73 | if (importStatementNode.prelude.type === 'Raw' && importStatementNode.prelude.value.includes('@import')) { 74 | let imports = importStatementNode.prelude.value.split('@import'); 75 | imports = imports.map(imp => { 76 | return imp 77 | .replace(/["']/g, '') 78 | .replace(/\n/g, '') 79 | .replace(/\s/g, ''); 80 | }); 81 | 82 | return imports; 83 | } 84 | 85 | // handles comma-separated imports 86 | if (importStatementNode.prelude.type === 'Raw' && importStatementNode.prelude.value.includes(',')) { 87 | importStatementNode.prelude.value = clearLessImportsRules(importStatementNode.prelude.value); 88 | let imports = importStatementNode.prelude.value.split(','); 89 | imports = imports.map(imp => { 90 | return imp 91 | .replace(/["']/g, '') 92 | .replace(/\n/g, '') 93 | .replace(/\s/g, ''); 94 | }); 95 | 96 | return imports; 97 | } 98 | 99 | // returns the dependencies of the given .sass file content 100 | if (importStatementNode.prelude.type === 'Raw') { 101 | importStatementNode.prelude.value = clearLessImportsRules(importStatementNode.prelude.value); 102 | return importStatementNode.prelude.value; 103 | } 104 | return []; 105 | } 106 | 107 | function clearLessImportsRules(importString) { 108 | // list from http://lesscss.org/features/#import-atrules-feature-import-options 109 | const lessImportOptions = ['reference', 'inline', 'less', 'css', 'once', 'multiple', 'optional']; 110 | const toClearSepicalImports = lessImportOptions.some(imp => { 111 | if (importString.includes(imp)) { 112 | return true; 113 | } 114 | return false; 115 | }); 116 | 117 | if (toClearSepicalImports) { 118 | importString = importString.replace(/ *\([^)]*\) */g, ''); 119 | } 120 | 121 | return importString 122 | .replace(/["']/g, '') 123 | .replace(/\n/g, '') 124 | .replace(/\s/g, ''); 125 | } 126 | 127 | function clearUrlImports(dependencies) { 128 | dependencies = dependencies.map(imp => { 129 | if (isUrl(imp)) { 130 | return null; 131 | } 132 | return imp; 133 | }); 134 | 135 | return dependencies.filter(Boolean); 136 | } 137 | 138 | function handleError() { 139 | // handle parse error 140 | return false; 141 | } 142 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-css/index.spec.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 2 | const assert = require('assert'); 3 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 4 | const detective = require('./'); 5 | 6 | describe('detective-css', function() { 7 | function test(src, deps, opts) { 8 | assert.deepEqual(detective(src, opts), deps); 9 | } 10 | 11 | describe('throws', function() { 12 | it('does not throw for empty files', function() { 13 | assert.doesNotThrow(function() { 14 | detective(''); 15 | }); 16 | }); 17 | 18 | it('throws if the given content is not a string', function() { 19 | assert.throws(function() { 20 | detective(function() {}); 21 | }); 22 | }); 23 | 24 | it('throws if called with no arguments', function() { 25 | assert.throws(function() { 26 | detective(); 27 | }); 28 | }); 29 | 30 | it.skip('throws on broken syntax', function() { 31 | assert.throws(function() { 32 | detective('@'); 33 | }); 34 | }); 35 | }); 36 | 37 | it('dangles the parsed AST', function() { 38 | detective('@import "_foo.css";'); 39 | 40 | assert.ok(detective.ast); 41 | }); 42 | 43 | describe('css', function() { 44 | it('returns the dependencies of the given .css file content', function() { 45 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 46 | test('@import "_foo.css";', ['_foo.css']); 47 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 48 | test('@import "_foo.css";', ['_foo.css']); 49 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 50 | test('@import "_foo";', ['_foo']); 51 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 52 | test('body { color: blue; } @import "_foo";', ['_foo']); 53 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 54 | test('@import "bar";', ['bar']); 55 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 56 | test('@import "bar"; @import "foo";', ['bar', 'foo']); 57 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 58 | test("@import 'bar';", ['bar']); 59 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 60 | test("@import 'bar.css';", ['bar.css']); 61 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 62 | test('@import "_foo.css";\n@import "_bar.css";', ['_foo.css', '_bar.css']); 63 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 64 | test('@import "_foo.css";\n@import "_bar.css";\n@import "_baz";\n@import "_buttons";', [ 65 | '_foo.css', 66 | '_bar.css', 67 | '_baz', 68 | '_buttons' 69 | ]); 70 | }); 71 | 72 | it('handles simple import', function() { 73 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 74 | test('@import "_foo.css"', ['_foo.css']); 75 | }); 76 | 77 | it('handles comma-separated imports (#2)', function() { 78 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 79 | test('@import "_foo.css", "bar";', ['_foo.css', 'bar']); 80 | }); 81 | 82 | it('allows imports with no semicolon', function() { 83 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 84 | test('@import "_foo.css"\n@import "_bar.css"', ['_foo.css', '_bar.css']); 85 | }); 86 | 87 | it('not allow https and http', function() { 88 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 89 | test('@import url("https://fonts.googleapis.com/css?family=Lato:100,300,400,700,900");"', []); 90 | }); 91 | 92 | it('not allow ftp', function() { 93 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 94 | test('@import url("ftp://fonts.googleapis.com/css?family=Lato:100,300,400,700,900");"', []); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-css/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 2 | import detectiveCssAndPreprocessors from '../detective-css-and-preprocessors'; 3 | 4 | /** 5 | * Extract the @import statements from a given css file's content 6 | * 7 | * @param {String} fileContent 8 | * @return {String[]} 9 | */ 10 | module.exports = function detective(fileContent) { 11 | const detectiveResult = detectiveCssAndPreprocessors(fileContent, 'css'); 12 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 13 | detective.ast = detectiveCssAndPreprocessors.ast; 14 | return detectiveResult; 15 | }; 16 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-es6/index.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * this file had been forked (and changed since then) from https://github.com/dependents/node-detective-es6 3 | */ 4 | 5 | import { expect } from 'chai'; 6 | 7 | const assert = require('assert'); 8 | const detective = require('./'); 9 | 10 | describe('detective-es6', () => { 11 | const ast = { 12 | type: 'Program', 13 | body: [ 14 | { 15 | type: 'VariableDeclaration', 16 | declarations: [ 17 | { 18 | type: 'VariableDeclarator', 19 | id: { 20 | type: 'Identifier', 21 | name: 'x' 22 | }, 23 | init: { 24 | type: 'Literal', 25 | value: 4, 26 | raw: '4' 27 | } 28 | } 29 | ], 30 | kind: 'let' 31 | } 32 | ] 33 | }; 34 | 35 | it('accepts an ast', () => { 36 | const deps = detective(ast); 37 | const depsKeys = Object.keys(deps); 38 | assert(!depsKeys.length); 39 | }); 40 | 41 | it('retrieves the dependencies of es6 modules', () => { 42 | const deps = detective('import {foo, bar} from "mylib";'); 43 | const depsKeys = Object.keys(deps); 44 | assert(depsKeys.length === 1); 45 | assert(depsKeys[0] === 'mylib'); 46 | }); 47 | 48 | it('retrieves the re-export dependencies of es6 modules', () => { 49 | const deps = detective('export {foo, bar} from "mylib";'); 50 | const depsKeys = Object.keys(deps); 51 | assert(depsKeys.length === 1); 52 | assert(depsKeys[0] === 'mylib'); 53 | }); 54 | 55 | it('retrieves the re-export * dependencies of es6 modules', () => { 56 | const deps = detective('export * from "mylib";'); 57 | const depsKeys = Object.keys(deps); 58 | assert(depsKeys.length === 1); 59 | assert(depsKeys[0] === 'mylib'); 60 | }); 61 | 62 | it('handles multiple imports', () => { 63 | const deps = detective('import {foo, bar} from "mylib";\nimport "mylib2"'); 64 | const depsKeys = Object.keys(deps); 65 | assert(depsKeys.length === 2); 66 | assert(depsKeys[0] === 'mylib'); 67 | assert(depsKeys[1] === 'mylib2'); 68 | }); 69 | 70 | it('handles default imports', () => { 71 | const deps = detective('import foo from "foo";'); 72 | const depsKeys = Object.keys(deps); 73 | assert(depsKeys.length === 1); 74 | assert(depsKeys[0] === 'foo'); 75 | }); 76 | 77 | it('handles dynamic imports', function() { 78 | const deps = detective('import("foo").then(foo => foo());'); 79 | const depsKeys = Object.keys(deps); 80 | assert(depsKeys.length === 1); 81 | assert(depsKeys[0] === 'foo'); 82 | }); 83 | 84 | it('should support commonJS syntax', function() { 85 | const deps = detective('var foo = require("foo");'); 86 | const depsKeys = Object.keys(deps); 87 | assert(depsKeys.length === 1); 88 | assert(depsKeys[0] === 'foo'); 89 | }); 90 | 91 | it('returns an empty list for empty files', function() { 92 | const deps = detective(''); 93 | const depsKeys = Object.keys(deps); 94 | assert.equal(depsKeys.length, 0); 95 | }); 96 | 97 | it('throws when content is not provided', function() { 98 | assert.throws( 99 | function() { 100 | detective(); 101 | }, 102 | Error, 103 | 'src not given' 104 | ); 105 | }); 106 | 107 | it('does not throw with jsx in a module', function() { 108 | assert.doesNotThrow(function() { 109 | detective("import foo from 'foo'; var templ = ;"); 110 | }); 111 | }); 112 | 113 | it('does not throw on an async ES7 function', function() { 114 | assert.doesNotThrow(function() { 115 | detective("import foo from 'foo'; export default async function bar() {}"); 116 | }); 117 | }); 118 | 119 | describe('string in apostrophes', () => { 120 | it('should recognize when using require statement', () => { 121 | const deps = detective('const foo = require(`foo`);'); // eslint-disable-line 122 | const depsKeys = Object.keys(deps); 123 | assert.equal(depsKeys.length, 1); 124 | assert.equal(depsKeys[0], 'foo'); 125 | }); 126 | it('should throw when using import syntax', () => { 127 | expect(() => detective('import foo from `foo`;')).to.throw(); // eslint-disable-line 128 | }); 129 | }); 130 | 131 | describe('import-specifiers detection (for tree shaking)', () => { 132 | it('should recognize default imports as default', () => { 133 | const deps = detective('import foo from "foo";'); 134 | expect(deps).to.have.property('foo'); 135 | expect(deps.foo).to.have.property('importSpecifiers'); 136 | const importSpecifier = deps.foo.importSpecifiers[0]; 137 | expect(importSpecifier.name).to.equal('foo'); 138 | expect(importSpecifier.isDefault).to.be.true; 139 | }); 140 | it('should recognize non-default imports as non-default', () => { 141 | const deps = detective('import { foo } from "foo";'); 142 | expect(deps).to.have.property('foo'); 143 | expect(deps.foo).to.have.property('importSpecifiers'); 144 | const importSpecifier = deps.foo.importSpecifiers[0]; 145 | expect(importSpecifier.name).to.equal('foo'); 146 | expect(importSpecifier.isDefault).to.be.false; 147 | }); 148 | it('should support export-default-as syntax', () => { 149 | const deps = detective('export { default as foo } from "foo";'); 150 | expect(deps).to.have.property('foo'); 151 | expect(deps.foo).to.have.property('importSpecifiers'); 152 | const importSpecifier = deps.foo.importSpecifiers[0]; 153 | expect(importSpecifier.name).to.equal('foo'); 154 | expect(importSpecifier.isDefault).to.be.true; 155 | }); 156 | it('should not be supported for CommonJS', () => { 157 | const deps = detective('const foo = require("foo");'); 158 | expect(deps).to.have.property('foo'); 159 | expect(deps.foo).to.not.have.property('importSpecifiers'); 160 | }); 161 | it('should add "exported": true if the same variable has been imported and exported', () => { 162 | const deps = detective('import { foo } from "foo"; export default foo;'); 163 | expect(deps).to.have.property('foo'); 164 | expect(deps.foo).to.have.property('importSpecifiers'); 165 | const importSpecifier = deps.foo.importSpecifiers[0]; 166 | expect(importSpecifier.name).to.equal('foo'); 167 | expect(importSpecifier.exported).to.be.true; 168 | }); 169 | it('should not add "exported" property if the variable has been imported but not exported', () => { 170 | const deps = detective('import { foo } from "foo";'); 171 | expect(deps).to.have.property('foo'); 172 | expect(deps.foo).to.have.property('importSpecifiers'); 173 | const importSpecifier = deps.foo.importSpecifiers[0]; 174 | expect(importSpecifier.name).to.equal('foo'); 175 | expect(importSpecifier).to.not.have.property('exported'); 176 | }); 177 | }); 178 | }); 179 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-es6/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getDependenciesFromMemberExpression, 3 | getDependenciesFromCallExpression, 4 | getSpecifierValueForImportDeclaration 5 | } from '../parser-helper'; 6 | /** 7 | * this file had been forked (and changed since then) from https://github.com/dependents/node-detective-es6 8 | */ 9 | 10 | const Walker = require('node-source-walk'); 11 | 12 | /** 13 | * Extracts the dependencies of the supplied es6 module 14 | * 15 | * @param {String|Object} src - File's content or AST 16 | * @return {String[]} 17 | */ 18 | module.exports = function(src) { 19 | const walker = new Walker(); 20 | 21 | const dependencies = {}; 22 | const addDependency = dependency => { 23 | if (!dependencies[dependency]) { 24 | dependencies[dependency] = {}; 25 | } 26 | }; 27 | const addImportSpecifier = (dependency, importSpecifier) => { 28 | if (dependencies[dependency].importSpecifiers) { 29 | dependencies[dependency].importSpecifiers.push(importSpecifier); 30 | } else { 31 | dependencies[dependency].importSpecifiers = [importSpecifier]; 32 | } 33 | }; 34 | const addExportedToImportSpecifier = name => { 35 | Object.keys(dependencies).forEach(dependency => { 36 | if (!dependencies[dependency].importSpecifiers) return; 37 | const specifier = dependencies[dependency].importSpecifiers.find(i => i.name === name); 38 | if (specifier) specifier.exported = true; 39 | }); 40 | }; 41 | 42 | if (typeof src === 'undefined') { 43 | throw new Error('src not given'); 44 | } 45 | 46 | if (src === '') { 47 | return dependencies; 48 | } 49 | 50 | walker.walk(src, function(node) { 51 | switch (node.type) { 52 | case 'ImportDeclaration': 53 | if (node.source && node.source.value) { 54 | const dependency = node.source.value; 55 | addDependency(dependency); 56 | node.specifiers.forEach(specifier => { 57 | const specifierValue = getSpecifierValueForImportDeclaration(specifier); 58 | addImportSpecifier(dependency, specifierValue); 59 | }); 60 | } 61 | break; 62 | case 'ExportNamedDeclaration': 63 | case 'ExportAllDeclaration': 64 | if (node.source && node.source.value) { 65 | const dependency = node.source.value; 66 | addDependency(dependency); 67 | if (node.specifiers) { 68 | // in case of "export * from" there are no node.specifiers 69 | node.specifiers.forEach(specifier => { 70 | const specifierValue = { 71 | isDefault: !specifier.local || specifier.local.name === 'default', // e.g. export { default as isArray } from './is-array'; 72 | name: specifier.exported.name, 73 | exported: true 74 | }; 75 | addImportSpecifier(dependency, specifierValue); 76 | }); 77 | } 78 | } else if (node.specifiers && node.specifiers.length) { 79 | node.specifiers.forEach(exportSpecifier => { 80 | addExportedToImportSpecifier(exportSpecifier.exported.name); 81 | }); 82 | } 83 | break; 84 | case 'ExportDefaultDeclaration': 85 | addExportedToImportSpecifier(node.declaration.name); 86 | break; 87 | case 'CallExpression': 88 | { 89 | const value = getDependenciesFromCallExpression(node); 90 | if (value) addDependency(value); 91 | } 92 | break; 93 | case 'MemberExpression': 94 | { 95 | const value = getDependenciesFromMemberExpression(node); 96 | if (value) addDependency(value); 97 | } 98 | break; 99 | default: 100 | break; 101 | } 102 | }); 103 | 104 | return dependencies; 105 | }; 106 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-less/index.spec.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 2 | const assert = require('assert'); 3 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 4 | const detective = require('./'); 5 | 6 | describe('detective-less', function() { 7 | function test(src, deps, opts) { 8 | assert.deepEqual(detective(src, opts), deps); 9 | } 10 | 11 | describe('throws', function() { 12 | it('does not throw for empty files', function() { 13 | assert.doesNotThrow(function() { 14 | detective(''); 15 | }); 16 | }); 17 | 18 | it('throws if the given content is not a string', function() { 19 | assert.throws(function() { 20 | detective(function() {}); 21 | }); 22 | }); 23 | 24 | it('throws if called with no arguments', function() { 25 | assert.throws(function() { 26 | detective(); 27 | }); 28 | }); 29 | 30 | it.skip('does throw on broken syntax', function() { 31 | assert.throws(function() { 32 | detective('@'); 33 | }); 34 | }); 35 | }); 36 | 37 | it('dangles the parsed AST', function() { 38 | detective('@import "_foo.less";'); 39 | assert.ok(detective.ast); 40 | }); 41 | 42 | describe('less', function() { 43 | it('returns the dependencies of the given .less file content', function() { 44 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 45 | test('@import "_foo.less";', ['_foo.less']); 46 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 47 | test('@import "_foo.less";', ['_foo.less']); 48 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 49 | test('@import "_foo";', ['_foo']); 50 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 51 | test('body { color: blue; } @import "_foo.css";', ['_foo.css']); 52 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 53 | test('@import "bar";', ['bar']); 54 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 55 | test('@import "bar"; @import "foo";', ['bar', 'foo']); 56 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 57 | test("@import 'bar';", ['bar']); 58 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 59 | test("@import 'bar.less';", ['bar.less']); 60 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 61 | test('@import "_foo.less";\n@import "_bar.less";', ['_foo.less', '_bar.less']); 62 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 63 | test('@import "_foo.less";\n@import "_bar.less";\n@import "_baz";\n@import "_buttons";', [ 64 | '_foo.less', 65 | '_bar.less', 66 | '_baz', 67 | '_buttons' 68 | ]); 69 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 70 | test('@import "_nested.less"; body { color: blue; a { text-decoration: underline; }}', ['_nested.less']); 71 | }); 72 | 73 | it('handles comma-separated imports (#2)', function() { 74 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 75 | test('@import "_foo.less", "bar";', ['_foo.less', 'bar']); 76 | }); 77 | 78 | it('allows imports with no semicolon', function() { 79 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 80 | test('@import "_foo.less"\n@import "_bar.less"', ['_foo.less', '_bar.less']); 81 | }); 82 | 83 | it('allow less spical imports', function() { 84 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 85 | test('@import (reference) "_foo.less";', ['_foo.less']); 86 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 87 | test('@import (reference ) "_foo.less"; ', ['_foo.less']); 88 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 89 | test('@import ( reference ) "_foo.less";', ['_foo.less']); 90 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 91 | test('@import (less) "_foo.less";', ['_foo.less']); 92 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 93 | test('@import (css) "_foo.less";', ['_foo.less']); 94 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 95 | test('@import (once) "_foo.less";', ['_foo.less']); 96 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 97 | test('@import (multiple) "_foo.less";', ['_foo.less']); 98 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 99 | test('@import (optional) "_foo.less";', ['_foo.less']); 100 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 101 | test('@import (inline , optional) "_foo.less";', ['_foo.less']); 102 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 103 | test('@import (inline , optional, multiple) "_foo.less";', ['_foo.less']); 104 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 105 | test('@import (inline , optional, multiple) "_foo.less";', ['_foo.less']); 106 | }); 107 | it('allow style decleretion with number, without block inside class', function() { 108 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 109 | test( 110 | "@import '../../style/themes/default';@import '../../style/mixins/index'; @keyframes card-loading{0%,100%{background-position:0 50%}50%{background-position:100% 50%}}", 111 | ['../../style/themes/default', '../../style/mixins/index'] 112 | ); 113 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 114 | test( 115 | '@import url(../colors.module.css);@import url(../variables.module.css);@import url(./config.module.css);.navigationContainer{display:flex}', 116 | ['../colors.module.css', '../variables.module.css', './config.module.css'] 117 | ); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-less/index.ts: -------------------------------------------------------------------------------- 1 | // forked and changed from https://github.com/dependents/node-detective-less 2 | 3 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 4 | import detectiveCssAndPreprocessors from '../detective-css-and-preprocessors'; 5 | 6 | /** 7 | * Extract the @import statements from a given less file's content 8 | * 9 | * @param {String} fileContent 10 | * @return {String[]} 11 | */ 12 | module.exports = function detective(fileContent) { 13 | const detectiveResult = detectiveCssAndPreprocessors(fileContent, 'less'); 14 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 15 | detective.ast = detectiveCssAndPreprocessors.ast; 16 | return detectiveResult; 17 | }; 18 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-sass/index.spec.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 2 | const assert = require('assert'); 3 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 4 | const detective = require('./'); 5 | 6 | describe('detective-sass', function() { 7 | function test(src, deps, opts) { 8 | assert.deepEqual(detective(src, opts), deps); 9 | } 10 | 11 | describe('throws', function() { 12 | it('does not throw for empty files', function() { 13 | assert.doesNotThrow(function() { 14 | detective(''); 15 | }); 16 | }); 17 | 18 | it('throws if the given content is not a string', function() { 19 | assert.throws(function() { 20 | detective(function() {}); 21 | }); 22 | }); 23 | 24 | it('throws if called with no arguments', function() { 25 | assert.throws(function() { 26 | detective(); 27 | }); 28 | }); 29 | 30 | it.skip('throws on broken syntax', function() { 31 | assert.throws(function() { 32 | detective('@'); 33 | }); 34 | }); 35 | }); 36 | 37 | it('dangles the parsed AST', function() { 38 | detective('@import _foo'); 39 | assert.ok(detective.ast); 40 | }); 41 | 42 | describe('sass', function() { 43 | it('returns the dependencies of the given .sass file content', function() { 44 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 45 | test('@import _foo', ['_foo']); 46 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 47 | test('@import _foo', ['_foo']); 48 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 49 | test('@import reset', ['reset']); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-sass/index.ts: -------------------------------------------------------------------------------- 1 | // forked and changed from https://github.com/dependents/node-detective-sass 2 | 3 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 4 | import detectiveCssAndPreprocessors from '../detective-css-and-preprocessors'; 5 | 6 | /** 7 | * Extract the @import statements from a given sass file's content 8 | * 9 | * @param {String} fileContent 10 | * @return {String[]} 11 | */ 12 | module.exports = function detective(fileContent) { 13 | const detectiveResult = detectiveCssAndPreprocessors(fileContent, 'sass'); 14 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 15 | detective.ast = detectiveCssAndPreprocessors.ast; 16 | return detectiveResult; 17 | }; 18 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-scss/index.spec.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 2 | const assert = require('assert'); 3 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 4 | const detective = require('./'); 5 | 6 | describe('detective-scss', function() { 7 | function test(src, deps, opts) { 8 | assert.deepEqual(detective(src, opts), deps); 9 | } 10 | 11 | describe('throws', function() { 12 | it('does not throw for empty files', function() { 13 | assert.doesNotThrow(function() { 14 | detective(''); 15 | }); 16 | }); 17 | 18 | it('throws if the given content is not a string', function() { 19 | assert.throws(function() { 20 | detective(function() {}); 21 | }); 22 | }); 23 | 24 | it('throws if called with no arguments', function() { 25 | assert.throws(function() { 26 | detective(); 27 | }); 28 | }); 29 | 30 | it.skip('throws on broken syntax', function() { 31 | assert.throws(function() { 32 | detective('@'); 33 | }); 34 | }); 35 | }); 36 | 37 | it('dangles the parsed AST', function() { 38 | detective('@import "_foo.scss";'); 39 | 40 | assert.ok(detective.ast); 41 | }); 42 | 43 | describe('scss', function() { 44 | it('returns the dependencies of the given .scss file content', function() { 45 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 46 | test('@import "_foo.scss";', ['_foo.scss']); 47 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 48 | test('@import "_foo.scss";', ['_foo.scss']); 49 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 50 | test('@import "_foo";', ['_foo']); 51 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 52 | test('body { color: blue; } @import "_foo";', ['_foo']); 53 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 54 | test('@import "bar";', ['bar']); 55 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 56 | test('@import "bar"; @import "foo";', ['bar', 'foo']); 57 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 58 | test("@import 'bar';", ['bar']); 59 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 60 | test("@import 'bar.scss';", ['bar.scss']); 61 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 62 | test('@import "_foo.scss";\n@import "_bar.scss";', ['_foo.scss', '_bar.scss']); 63 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 64 | test('@import "_foo.scss";\n@import "_bar.scss";\n@import "_baz";\n@import "_buttons";', [ 65 | '_foo.scss', 66 | '_bar.scss', 67 | '_baz', 68 | '_buttons' 69 | ]); 70 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 71 | test('@import "_nested.scss"; body { color: blue; a { text-decoration: underline; }}', ['_nested.scss']); 72 | }); 73 | 74 | it('handles comma-separated imports (#2)', function() { 75 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 76 | test('@import "_foo.scss", "bar";', ['_foo.scss', 'bar']); 77 | }); 78 | 79 | it('allows imports with no semicolon', function() { 80 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 81 | test('@import "_foo.scss"\n@import "_bar.scss"', ['_foo.scss', '_bar.scss']); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-scss/index.ts: -------------------------------------------------------------------------------- 1 | // forked and changed from https://github.com/dependents/node-detective-scss 2 | 3 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 4 | import detectiveCssAndPreprocessors from '../detective-css-and-preprocessors'; 5 | 6 | /** 7 | * Extract the @import statements from a given scss file's content 8 | * 9 | * @param {String} fileContent 10 | * @return {String[]} 11 | */ 12 | module.exports = function detective(fileContent) { 13 | const detectiveResult = detectiveCssAndPreprocessors(fileContent, 'scss'); 14 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 15 | detective.ast = detectiveCssAndPreprocessors.ast; 16 | return detectiveResult; 17 | }; 18 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-stylable/index.ts: -------------------------------------------------------------------------------- 1 | const stylable = require('stylable'); 2 | 3 | module.exports = function(src) { 4 | const css = stylable.safeParse(src); 5 | const dependencies = {}; 6 | const addDependency = dependency => { 7 | if (!dependencies[dependency]) { 8 | dependencies[dependency] = {}; 9 | } 10 | }; 11 | const addImportSpecifier = (dependency, importSpecifier) => { 12 | if (dependencies[dependency].importSpecifiers) { 13 | dependencies[dependency].importSpecifiers.push(importSpecifier); 14 | } else { 15 | dependencies[dependency].importSpecifiers = [importSpecifier]; 16 | } 17 | }; 18 | 19 | css.walkRules(rule => { 20 | const stFrom = rule.nodes.find(node => node.prop === '-st-from'); 21 | if (!stFrom) return; 22 | const stFromValue = stFrom.value.replace(/["']/g, ''); 23 | addDependency(stFromValue); 24 | const stNamed = rule.nodes.find(node => node.prop === '-st-named'); 25 | if (!stNamed) return; 26 | const specifierValue = { 27 | isDefault: false, // @todo, 28 | name: stNamed.value, 29 | exported: true 30 | }; 31 | addImportSpecifier(stFromValue, specifierValue); 32 | }); 33 | 34 | return dependencies; 35 | }; 36 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-typescript/index.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import { expect } from 'chai'; 3 | import assert from 'assert'; 4 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 5 | import detective from './'; 6 | 7 | describe('detective-typescript', () => { 8 | const ast = { 9 | type: 'Program', 10 | body: [ 11 | { 12 | type: 'VariableDeclaration', 13 | declarations: [ 14 | { 15 | type: 'VariableDeclarator', 16 | id: { 17 | type: 'Identifier', 18 | name: 'x' 19 | }, 20 | init: { 21 | type: 'Literal', 22 | value: 4, 23 | raw: '4' 24 | } 25 | } 26 | ], 27 | kind: 'let' 28 | } 29 | ] 30 | }; 31 | 32 | it('accepts an ast', () => { 33 | const deps = detective(ast); 34 | const depsKeys = Object.keys(deps); 35 | assert(!depsKeys.length); 36 | }); 37 | 38 | it('retrieves the dependencies of modules', () => { 39 | const deps = detective('import {foo, bar} from "mylib";'); 40 | const depsKeys = Object.keys(deps); 41 | assert(depsKeys.length === 1); 42 | expect(deps).to.have.property('mylib'); 43 | }); 44 | 45 | it('retrieves the re-export dependencies of modules', () => { 46 | const deps = detective('export {foo, bar} from "mylib";'); 47 | const depsKeys = Object.keys(deps); 48 | assert(depsKeys.length === 1); 49 | expect(deps).to.have.property('mylib'); 50 | }); 51 | 52 | it('retrieves the re-export * dependencies of modules', () => { 53 | const deps = detective('export * from "mylib";'); 54 | const depsKeys = Object.keys(deps); 55 | assert(depsKeys.length === 1); 56 | expect(deps).to.have.property('mylib'); 57 | }); 58 | 59 | it('handles multiple imports', () => { 60 | const deps = detective('import {foo, bar} from "mylib";\nimport "mylib2"'); 61 | const depsKeys = Object.keys(deps); 62 | assert(depsKeys.length === 2); 63 | expect(deps).to.have.property('mylib'); 64 | expect(deps).to.have.property('mylib2'); 65 | }); 66 | 67 | it('handles mixed imports of typescript and javascript', () => { 68 | const deps = detective('import {foo, bar} from "mylib";\nconst mylib2 = require("mylib2");'); 69 | const depsKeys = Object.keys(deps); 70 | assert(depsKeys.length === 2); 71 | expect(deps).to.have.property('mylib'); 72 | expect(deps).to.have.property('mylib2'); 73 | }); 74 | 75 | it('handles default imports', () => { 76 | const deps = detective('import foo from "foo";'); 77 | const depsKeys = Object.keys(deps); 78 | assert(depsKeys.length === 1); 79 | expect(deps).to.have.property('foo'); 80 | }); 81 | 82 | it('retrieves dependencies from modules using "export ="', () => { 83 | const deps = detective('import foo = require("mylib");'); 84 | const depsKeys = Object.keys(deps); 85 | assert(depsKeys.length === 1); 86 | expect(deps).to.have.property('mylib'); 87 | }); 88 | 89 | it('retrieves dependencies when using javascript syntax', () => { 90 | const deps = detective('var foo = require("foo");'); 91 | const depsKeys = Object.keys(deps); 92 | assert(depsKeys.length === 1); 93 | expect(deps).to.have.property('foo'); 94 | }); 95 | 96 | it('returns an empty list for empty files', () => { 97 | const deps = detective(''); 98 | const depsKeys = Object.keys(deps); 99 | assert.equal(depsKeys.length, 0); 100 | }); 101 | 102 | it('throws when content is not provided', () => { 103 | assert.throws( 104 | () => { 105 | detective(); 106 | }, 107 | Error, 108 | 'src not given' 109 | ); 110 | }); 111 | 112 | it('does not throw with jsx in a module', () => { 113 | assert.doesNotThrow(() => { 114 | detective("import foo from 'foo'; var baz = bar;"); 115 | }); 116 | }); 117 | 118 | it('does not throw with empty import and export', () => { 119 | assert.doesNotThrow(() => { 120 | detective("import './layout.scss'; export default something;"); 121 | }); 122 | }); 123 | 124 | describe('string in apostrophes', () => { 125 | it('should recognize when using require statement', () => { 126 | const deps = detective('const foo = require(`foo`);'); // eslint-disable-line 127 | const depsKeys = Object.keys(deps); 128 | assert.equal(depsKeys.length, 1); 129 | assert.equal(depsKeys[0], 'foo'); 130 | }); 131 | }); 132 | 133 | describe('Angular Decorators', () => { 134 | let deps; 135 | before(() => { 136 | const componentDecorator = `const styleUrl = './my-style2.css'; 137 | @Component({ 138 | selector: 'main-component', 139 | templateUrl: './my-template.html', 140 | styleUrls: ['./my-style1.css', styleUrl, './my-style3.css', 'my-style4.css'] 141 | }) 142 | export class MainComponent {}`; 143 | const results = detective(componentDecorator); // eslint-disable-line 144 | deps = Object.keys(results); 145 | }); 146 | it('should recognize the templateUrl as a dependency', () => { 147 | expect(deps).to.include('./my-template.html'); 148 | }); 149 | it('should recognize the styleUrls as dependencies', () => { 150 | expect(deps).to.include('./my-style1.css'); 151 | expect(deps).to.include('./my-style3.css'); 152 | }); 153 | it('should not recognize dynamic style (style path entered as a variable)', () => { 154 | expect(deps).to.not.include('./my-style2.css'); 155 | }); 156 | it('should change non-relative paths to be relative', () => { 157 | expect(deps).to.include('./my-style4.css'); 158 | }); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-typescript/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * this file had been forked from https://github.com/pahen/detective-typescript 3 | */ 4 | import { 5 | getDependenciesFromMemberExpression, 6 | getDependenciesFromCallExpression, 7 | getSpecifierValueForImportDeclaration 8 | } from '../parser-helper'; 9 | import { isRelativeImport } from '../../../utils'; 10 | 11 | const Parser = require('@typescript-eslint/typescript-estree'); 12 | const Walker = require('node-source-walk'); 13 | 14 | /** 15 | * Extracts the dependencies of the supplied TypeScript module 16 | * 17 | * @param {String|Object} src - File's content or AST 18 | * @param {Object} options - options to pass to the parser 19 | * @return {String[]} 20 | */ 21 | module.exports = function(src, options: Record = {}) { 22 | options.parser = Parser; 23 | 24 | const walker = new Walker(options); 25 | 26 | const dependencies = {}; 27 | const addDependency = dependency => { 28 | if (!dependencies[dependency]) { 29 | dependencies[dependency] = {}; 30 | } 31 | }; 32 | const addAngularLocalDependency = dependency => { 33 | const angularDep = isRelativeImport(dependency) ? dependency : `./${dependency}`; 34 | addDependency(angularDep); 35 | }; 36 | const addImportSpecifier = (dependency, importSpecifier) => { 37 | if (dependencies[dependency].importSpecifiers) { 38 | dependencies[dependency].importSpecifiers.push(importSpecifier); 39 | } else { 40 | dependencies[dependency].importSpecifiers = [importSpecifier]; 41 | } 42 | }; 43 | const addExportedToImportSpecifier = name => { 44 | Object.keys(dependencies).forEach(dependency => { 45 | if (!dependencies[dependency].importSpecifiers) return; 46 | const specifier = dependencies[dependency].importSpecifiers.find(i => i.name === name); 47 | if (specifier) specifier.exported = true; 48 | }); 49 | }; 50 | 51 | if (typeof src === 'undefined') { 52 | throw new Error('src not given'); 53 | } 54 | 55 | if (src === '') { 56 | return dependencies; 57 | } 58 | 59 | walker.walk(src, function(node) { 60 | switch (node.type) { 61 | case 'ImportDeclaration': 62 | if (node.source && node.source.value) { 63 | const dependency = node.source.value; 64 | addDependency(dependency); 65 | 66 | node.specifiers.forEach(specifier => { 67 | const specifierValue = getSpecifierValueForImportDeclaration(specifier); 68 | addImportSpecifier(dependency, specifierValue); 69 | }); 70 | } 71 | break; 72 | case 'ExportNamedDeclaration': 73 | case 'ExportAllDeclaration': 74 | if (node.source && node.source.value) { 75 | addDependency(node.source.value); 76 | } else if (node.specifiers && node.specifiers.length) { 77 | node.specifiers.forEach(exportSpecifier => { 78 | addExportedToImportSpecifier(exportSpecifier.exported.name); 79 | }); 80 | } 81 | break; 82 | case 'ExportDefaultDeclaration': 83 | addExportedToImportSpecifier(node.declaration.name); 84 | break; 85 | case 'TSExternalModuleReference': 86 | if (node.expression && node.expression.value) { 87 | addDependency(node.expression.value); 88 | } 89 | break; 90 | case 'CallExpression': 91 | { 92 | const value = getDependenciesFromCallExpression(node); 93 | if (value) addDependency(value); 94 | } 95 | break; 96 | case 'MemberExpression': 97 | { 98 | const value = getDependenciesFromMemberExpression(node); 99 | if (value) addDependency(value); 100 | } 101 | break; 102 | case 'Decorator': // parse Angular Decorators to find style/template dependencies 103 | if ( 104 | node.expression && 105 | node.expression.callee && 106 | node.expression.callee.name === 'Component' && 107 | node.expression.arguments && 108 | node.expression.arguments.length && 109 | node.expression.arguments[0].type === 'ObjectExpression' 110 | ) { 111 | const angularComponent = node.expression.arguments[0].properties; 112 | angularComponent.forEach(prop => { 113 | if (!prop.key || !prop.value) return; 114 | if (prop.key.name === 'templateUrl' && prop.value.type === 'Literal') { 115 | addAngularLocalDependency(prop.value.value); 116 | } 117 | if (prop.key.name === 'styleUrl' && prop.value.type === 'Literal') { 118 | addAngularLocalDependency(prop.value.value); 119 | } 120 | if (prop.key.name === 'styleUrls' && prop.value.type === 'ArrayExpression') { 121 | const literalsElements = prop.value.elements.filter(e => e.type === 'Literal'); 122 | literalsElements.forEach(element => addAngularLocalDependency(element.value)); 123 | } 124 | }); 125 | } 126 | break; 127 | default: 128 | break; 129 | } 130 | }); 131 | 132 | return dependencies; 133 | }; 134 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/detective-vue/index.ts: -------------------------------------------------------------------------------- 1 | module.exports = function(src, options: Record = {}) { 2 | // eslint-disable-next-line import/no-dynamic-require, global-require 3 | const compiler = require('vue-template-compiler'); 4 | const finalDependencies = {}; 5 | const addDependencies = (dependencies, isScript) => { 6 | let objDependencies = {}; 7 | if (Array.isArray(dependencies)) { 8 | dependencies.forEach(dependency => { 9 | objDependencies[dependency] = {}; 10 | }); 11 | } else { 12 | objDependencies = dependencies; 13 | } 14 | Object.keys(objDependencies).forEach(dependency => { 15 | finalDependencies[dependency] = objDependencies[dependency]; 16 | finalDependencies[dependency].isScript = isScript; 17 | }); 18 | }; 19 | 20 | const { script, styles } = compiler.parseComponent(src, { pad: 'line' }); 21 | // it must be required here, otherwise, it'll be a cyclic dependency 22 | // eslint-disable-next-line import/no-dynamic-require, global-require 23 | const precinct = require('../../precinct'); 24 | if (script) { 25 | if (script.lang) { 26 | options.type = script.lang; 27 | } else { 28 | options.useContent = true; 29 | } 30 | const dependencies = precinct(script.content, options); 31 | addDependencies(dependencies, true); 32 | } 33 | if (styles) { 34 | styles.forEach(style => { 35 | const dependencies = precinct(style.content, { type: style.lang || 'scss' }); 36 | addDependencies(dependencies, false); 37 | }); 38 | } 39 | 40 | return finalDependencies; 41 | }; 42 | -------------------------------------------------------------------------------- /src/dependency-builder/detectives/parser-helper.ts: -------------------------------------------------------------------------------- 1 | export function getDependenciesFromMemberExpression(node) { 2 | if ( 3 | node.object.type === 'CallExpression' && 4 | node.object.callee.type === 'Identifier' && 5 | node.object.callee.name === 'require' && 6 | node.object.arguments && 7 | node.object.arguments.length 8 | ) { 9 | return getStringValue(node.object.arguments[0]); 10 | } 11 | return null; 12 | } 13 | 14 | export function getDependenciesFromCallExpression(node) { 15 | if (node.callee.type === 'Import' && node.arguments.length && node.arguments[0].value) { 16 | return node.arguments[0].value; 17 | } 18 | if ( 19 | node.callee.type === 'Identifier' && // taken from detective-cjs 20 | node.callee.name === 'require' && 21 | node.arguments && 22 | node.arguments.length 23 | ) { 24 | return getStringValue(node.arguments[0]); 25 | } 26 | return null; 27 | } 28 | 29 | export function getSpecifierValueForImportDeclaration(specifier) { 30 | return { 31 | isDefault: specifier.type === 'ImportDefaultSpecifier', 32 | // syntax of `import x from 'file'` doesn't have specifier.imported, only specifier.local 33 | // syntax of `import { x as y } from 'file'`, has `x` as specifier.imported and `y` as 34 | // specifier.local. we interested in `x` in this case. 35 | name: specifier.imported ? specifier.imported.name : specifier.local.name 36 | }; 37 | } 38 | 39 | function getStringValue(node) { 40 | // using single or double quotes (', ") 41 | if (node.type === 'Literal' || node.type === 'StringLiteral') { 42 | return node.value; 43 | } 44 | // using apostrophe (`) 45 | if ( 46 | node.type === 'TemplateLiteral' && 47 | node.quasis && 48 | node.quasis.length && 49 | node.quasis[0].type === 'TemplateElement' 50 | ) { 51 | return node.quasis[0].value.raw; 52 | } 53 | return null; 54 | } 55 | -------------------------------------------------------------------------------- /src/dependency-builder/filing-cabinet/index.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** 4 | * this file had been forked from https://github.com/dependents/node-filing-cabinet 5 | */ 6 | import ts from 'typescript'; 7 | import path from 'path'; 8 | import getModuleType from 'module-definition'; 9 | import resolve from 'resolve'; 10 | import amdLookup from 'module-lookup-amd'; 11 | import stylusLookup from 'stylus-lookup'; 12 | import sassLookup from 'sass-lookup'; 13 | import resolveDependencyPath from 'resolve-dependency-path'; 14 | import appModulePath from 'app-module-path'; 15 | import webpackResolve from 'enhanced-resolve'; 16 | import isRelative from 'is-relative-path'; 17 | import objectAssign from 'object-assign'; 18 | import { isRelativeImport } from '../../utils'; 19 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 20 | import vueLookUp from '../lookups/vue-lookup'; 21 | 22 | const debug = require('debug')('cabinet'); 23 | 24 | const defaultLookups = { 25 | '.js': jsLookup, 26 | '.jsx': jsLookup, 27 | '.ts': tsLookup, 28 | '.tsx': tsLookup, 29 | '.scss': cssPreprocessorLookup, 30 | '.sass': cssPreprocessorLookup, 31 | '.styl': stylusLookup, 32 | '.less': cssPreprocessorLookup, 33 | '.vue': vueLookUp 34 | }; 35 | 36 | // for some reason, .ts is not sufficient, .d.ts is needed as well 37 | // these extensions are used with commonJs and nonRelative lookups. When a dependency doesn't have an 38 | // extension it will look for files with these extensions in order. 39 | // for example, `const a = require('.a')`, it'll look for a.js, a.jsx, a.ts and so on. 40 | const resolveExtensions = Object.keys(defaultLookups).concat(['.d.ts', '.json', '.css']); 41 | 42 | // when webpack resolves dependencies from a style file, such as .scss file, look for style extensions only 43 | const styleExtensions = ['.scss', '.sass', '.less', '.css']; 44 | 45 | type Options = { 46 | dependency: string; // previous name was "partial" 47 | filename: string; 48 | directory: string; 49 | config: Record; 50 | webpackConfig?: Record; 51 | configPath?: string; 52 | resolveConfig?: Record; 53 | isScript?: boolean; // relevant for Vue files 54 | ast?: string; 55 | ext?: string; 56 | content?: string; 57 | wasCustomResolveUsed?: boolean; 58 | }; 59 | 60 | module.exports = function cabinet(options: Options) { 61 | const { dependency, filename } = options; 62 | const ext = options.ext || path.extname(filename); 63 | debug(`working on a dependency "${dependency}" of a file "${filename}"`); 64 | 65 | let resolver = defaultLookups[ext]; 66 | 67 | if (!resolver) { 68 | debug('using generic resolver'); 69 | resolver = resolveDependencyPath; 70 | } 71 | if (ext === '.css' && dependency.startsWith('~')) resolver = cssPreprocessorLookup; 72 | 73 | debug(`found a resolver ${resolver.name} for ${ext}`); 74 | 75 | let result = resolver(options); 76 | const dependencyExt = path.extname(dependency); 77 | if (!result && dependencyExt && dependencyExt !== ext) { 78 | resolver = defaultLookups[dependencyExt]; 79 | if (resolver) { 80 | // todo: this strategy probably fails often. if, for example, a dependency A.js inside B.ts 81 | // was not found using the ts resolve, it tries the js resolver, however, parsing the ts file 82 | // with js parser, won't work most of the time. A better approach would be to fix the 83 | // original resolver. 84 | debug( 85 | `dependency has a different extension (${dependencyExt}) than the original file (${ext}), trying to use its resolver instead` 86 | ); 87 | try { 88 | result = resolver(options); 89 | } catch (err) { 90 | debug(`unable to use the resolver of ${dependencyExt} for ${filename}. got an error ${err.message}`); 91 | } 92 | } 93 | } 94 | debug(`resolved path for ${dependency}: ${result}`); 95 | return result; 96 | }; 97 | 98 | module.exports.supportedFileExtensions = Object.keys(defaultLookups); 99 | 100 | /** 101 | * Register a custom lookup resolver for a file extension 102 | * 103 | * @param {String} extension - The file extension that should use the resolver 104 | * @param {Function} lookupStrategy - A resolver of dependency paths 105 | */ 106 | module.exports.register = function(extension, lookupStrategy) { 107 | defaultLookups[extension] = lookupStrategy; 108 | 109 | if (this.supportedFileExtensions.indexOf(extension) === -1) { 110 | this.supportedFileExtensions.push(extension); 111 | } 112 | }; 113 | 114 | /** 115 | * Exposed for testing 116 | * 117 | * @param {Object} options 118 | * @param {String} options.config 119 | * @param {String} options.webpackConfig 120 | * @param {String} options.filename 121 | * @param {Object} options.ast 122 | * @return {String} 123 | */ 124 | module.exports._getJSType = function(options) { 125 | options = options || {}; 126 | 127 | if (options.config) { 128 | return 'amd'; 129 | } 130 | 131 | if (options.webpackConfig) { 132 | return 'webpack'; 133 | } 134 | 135 | const ast = options.ast || options.content; 136 | if (ast) { 137 | debug('reusing the given ast or content'); 138 | return getModuleType.fromSource(ast); 139 | } 140 | 141 | debug('using the filename to find the module type'); 142 | return getModuleType.sync(options.filename); 143 | }; 144 | 145 | /** 146 | * @private 147 | * @param {String} dependency 148 | * @param {String} filename 149 | * @param {String} directory 150 | * @param {String} [config] 151 | * @param {String} [webpackConfig] 152 | * @param {String} [configPath] 153 | * @param {Object} [ast] 154 | * @return {String} 155 | */ 156 | function jsLookup(options: Options) { 157 | const { configPath, dependency, directory, config, webpackConfig, filename, ast, isScript, content } = options; 158 | const type = module.exports._getJSType({ 159 | config, 160 | webpackConfig, 161 | filename, 162 | ast, 163 | isScript, 164 | content 165 | }); 166 | 167 | switch (type) { 168 | case 'amd': 169 | debug('using amd resolver'); 170 | return amdLookup({ 171 | config, 172 | // Optional in case a pre-parsed config is being passed in 173 | configPath, 174 | partial: dependency, 175 | directory, 176 | filename 177 | }); 178 | 179 | case 'webpack': 180 | debug('using webpack resolver for es6'); 181 | return resolveWebpackPath(dependency, filename, directory, webpackConfig); 182 | 183 | case 'commonjs': 184 | case 'es6': 185 | default: 186 | debug(`using commonjs resolver ${type}`); 187 | return commonJSLookup(options); 188 | } 189 | } 190 | 191 | /** 192 | * from https://github.com/webpack-contrib/sass-loader 193 | webpack provides an [advanced mechanism to resolve files](https://webpack.js.org/concepts/module-resolution/). 194 | The sass-loader uses node-sass' custom importer feature to pass all queries to the webpack resolving engine. 195 | Thus you can import your Sass modules from `node_modules`. Just prepend them with a `~` to tell webpack that 196 | this is not a relative import: 197 | ```css 198 | @import "~bootstrap/dist/css/bootstrap"; 199 | ``` 200 | It's important to only prepend it with `~`, because `~/` resolves to the home directory. 201 | webpack needs to distinguish between `bootstrap` and `~bootstrap` because CSS and Sass files have no special 202 | syntax for importing relative files. Writing `@import "file"` is the same as `@import "./file"; 203 | */ 204 | function cssPreprocessorLookup(options: Options) { 205 | const { filename, dependency, directory, resolveConfig } = options; 206 | if (resolveConfig && !isRelativeImport(dependency)) { 207 | const result = resolveNonRelativePath(dependency, filename, directory, resolveConfig); 208 | if (result) { 209 | options.wasCustomResolveUsed = true; 210 | return result; 211 | } 212 | } 213 | if (dependency.startsWith('~') && !dependency.startsWith('~/')) { 214 | // webpack syntax for resolving a module from node_modules 215 | debug('changing the resolver of css preprocessor to resolveWebpackPath as it has a ~ prefix'); 216 | const dependencyWithNoTilda = dependency.replace('~', ''); 217 | return resolveWebpack(dependencyWithNoTilda, filename, directory, { extensions: styleExtensions, symlinks: false }); 218 | } 219 | 220 | // Less and Sass imports are very similar 221 | return sassLookup({ dependency, filename, directory }); 222 | } 223 | 224 | function tsLookup(options: Options) { 225 | const { dependency, filename } = options; 226 | if (dependency[0] !== '.') { 227 | // when a path is not relative, use the standard commonJS lookup 228 | return commonJSLookup(options); 229 | } 230 | debug('performing a typescript lookup'); 231 | 232 | const tsOptions = { 233 | module: ts.ModuleKind.CommonJS 234 | }; 235 | 236 | const host = ts.createCompilerHost({}); 237 | debug('with options: ', tsOptions); 238 | let resolvedModule = ts.resolveModuleName(dependency, filename, tsOptions, host).resolvedModule; 239 | if (!resolvedModule) { 240 | // for some reason, on Windows, ts.resolveModuleName method tries to find the module in the 241 | // root directory. For example, it searches for module './bar', in 'c:\bar.ts'. 242 | const fallbackModule = path.resolve(path.dirname(filename), dependency); 243 | resolvedModule = ts.resolveModuleName(fallbackModule, filename, tsOptions, host).resolvedModule; 244 | } 245 | if (!resolvedModule) { 246 | // ts.resolveModuleName doesn't always work, fallback to commonJSLookup 247 | debug('failed resolving with tsLookup, trying commonJSLookup'); 248 | return commonJSLookup(options); 249 | } 250 | debug('ts resolved module: ', resolvedModule); 251 | const result = resolvedModule ? resolvedModule.resolvedFileName : ''; 252 | 253 | debug(`result: ${result}`); 254 | return result ? path.resolve(result) : ''; 255 | } 256 | 257 | function resolveNonRelativePath(dependency, filename, directory, resolveConfig) { 258 | const webpackResolveConfig = {}; 259 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 260 | if (resolveConfig.modulesDirectories) webpackResolveConfig.modules = resolveConfig.modulesDirectories; 261 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 262 | if (resolveConfig.aliases) webpackResolveConfig.alias = resolveConfig.aliases; 263 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 264 | webpackResolveConfig.extensions = resolveExtensions; 265 | // a resolve module might point to an imported component via the package name, in which case 266 | // the package name is a symlink to the imported component. we want it to be resolved as a pkg 267 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 268 | webpackResolveConfig.symlinks = false; 269 | return resolveWebpack(dependency, filename, directory, webpackResolveConfig); 270 | } 271 | 272 | function commonJSLookup(options: Options) { 273 | const { filename, resolveConfig } = options; 274 | const directory = path.dirname(filename); // node_modules should be propagated from the file location backwards 275 | // Need to resolve dependencies within the directory of the module, not filing-cabinet 276 | const moduleLookupDir = path.join(directory, 'node_modules'); 277 | 278 | debug(`adding ${moduleLookupDir} to the require resolution paths`); 279 | 280 | appModulePath.addPath(moduleLookupDir); 281 | 282 | let dependency = options.dependency; 283 | 284 | let result = ''; 285 | 286 | if (!isRelativeImport(dependency) && resolveConfig) { 287 | debug(`trying to resolve using resolveConfig ${JSON.stringify(resolveConfig)}`); 288 | result = resolveNonRelativePath(dependency, filename, directory, resolveConfig); 289 | if (result) { 290 | debug('successfully resolved using resolveConfig'); 291 | options.wasCustomResolveUsed = true; 292 | return result; 293 | } 294 | debug('failed resolved using resolveConfig, fall back to the standard resolver'); 295 | } 296 | 297 | // Make sure the dependency is being resolved to the filename's context 298 | // 3rd party modules will not be relative 299 | if (dependency[0] === '.') { 300 | dependency = path.resolve(path.dirname(filename), dependency); 301 | } 302 | 303 | try { 304 | result = resolve.sync(dependency, { 305 | extensions: resolveExtensions, 306 | basedir: directory, 307 | moduleDirectory: ['node_modules'] 308 | }); 309 | debug(`resolved path: ${result}`); 310 | } catch (e) { 311 | debug(`could not resolve ${dependency}`); 312 | } 313 | 314 | return result; 315 | } 316 | 317 | function resolveWebpackPath(dependency, filename, directory, webpackConfig) { 318 | webpackConfig = path.resolve(webpackConfig); 319 | let loadedConfig; 320 | try { 321 | // eslint-disable-next-line import/no-dynamic-require, global-require 322 | loadedConfig = require(webpackConfig); 323 | 324 | if (typeof loadedConfig === 'function') { 325 | loadedConfig = loadedConfig(); 326 | } 327 | } catch (e) { 328 | debug(`error loading the webpack config at ${webpackConfig}`); 329 | debug(e.message); 330 | debug(e.stack); 331 | return ''; 332 | } 333 | 334 | const resolveConfig = objectAssign({}, loadedConfig.resolve); 335 | 336 | if (!resolveConfig.modules && (resolveConfig.root || resolveConfig.modulesDirectories)) { 337 | resolveConfig.modules = []; 338 | 339 | if (resolveConfig.root) { 340 | resolveConfig.modules = resolveConfig.modules.concat(resolveConfig.root); 341 | } 342 | 343 | if (resolveConfig.modulesDirectories) { 344 | resolveConfig.modules = resolveConfig.modules.concat(resolveConfig.modulesDirectories); 345 | } 346 | } 347 | 348 | return resolveWebpack(dependency, filename, directory, resolveConfig); 349 | } 350 | 351 | function resolveWebpack(dependency, filename, directory, resolveConfig) { 352 | try { 353 | const resolver = webpackResolve.create.sync(resolveConfig); 354 | 355 | // We don't care about what the loader resolves the dependency to 356 | // we only want the path of the resolved file 357 | dependency = stripLoader(dependency); 358 | 359 | const lookupPath = isRelative(dependency) ? path.dirname(filename) : directory; 360 | 361 | return resolver(lookupPath, dependency); 362 | } catch (e) { 363 | debug(`error when resolving ${dependency}`); 364 | debug(e.message); 365 | debug(e.stack); 366 | return ''; 367 | } 368 | } 369 | 370 | function stripLoader(dependency) { 371 | const exclamationLocation = dependency.indexOf('!'); 372 | 373 | if (exclamationLocation === -1) { 374 | return dependency; 375 | } 376 | 377 | return dependency.slice(exclamationLocation + 1); 378 | } 379 | -------------------------------------------------------------------------------- /src/dependency-builder/generate-tree-madge.ts: -------------------------------------------------------------------------------- 1 | // most of the functions in this file were taken from the Madge project: https://github.com/pahen/madge 2 | // reasons for not using Madge directly: 1) it has issues with TypeScript on Windows. 2) it has issues with tsx files 3 | 4 | import os from 'os'; 5 | import path from 'path'; 6 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 7 | import dependencyTree from './dependency-tree'; 8 | 9 | /** 10 | * Check if running on Windows. 11 | * @type {Boolean} 12 | */ 13 | const isWin = os.platform() === 'win32'; 14 | 15 | /** 16 | * Check if path is from NPM folder 17 | * @param {String} path 18 | * @return {Boolean} 19 | */ 20 | function isNpmPathFunc(pathStr) { 21 | return pathStr.indexOf('node_modules') >= 0; 22 | } 23 | 24 | /** 25 | * Sort tree. 26 | * @param {Object} tree 27 | * @return {Object} 28 | */ 29 | function sort(tree) { 30 | return Object.keys(tree) 31 | .sort() 32 | .reduce((acc, id) => { 33 | acc[id] = tree[id].sort(); 34 | return acc; 35 | }, {}); 36 | } 37 | 38 | /** 39 | * Exclude modules from tree using RegExp. 40 | * @param {Object} tree 41 | * @param {Array} excludeRegExp 42 | * @return {Object} 43 | */ 44 | function exclude(tree, excludeRegExp) { 45 | const regExpList = excludeRegExp.map(re => new RegExp(re)); 46 | 47 | function regExpFilter(id) { 48 | return regExpList.findIndex(regexp => regexp.test(id)) < 0; 49 | } 50 | 51 | return Object.keys(tree) 52 | .filter(regExpFilter) 53 | .reduce((acc, id) => { 54 | acc[id] = tree[id].filter(regExpFilter); 55 | return acc; 56 | }, {}); 57 | } 58 | 59 | /** 60 | * Process absolute path and return a shorter one. 61 | * @param {String} absPath 62 | * @param {Object} cache 63 | * @param {String} baseDir 64 | * @return {String} 65 | */ 66 | export function processPath(absPath, cache, baseDir) { 67 | if (cache[absPath]) { 68 | return cache[absPath]; 69 | } 70 | 71 | let relPath = path.relative(baseDir, absPath); 72 | 73 | if (isWin) { 74 | relPath = relPath.replace(/\\/g, '/'); 75 | } 76 | 77 | cache[absPath] = relPath; 78 | 79 | return relPath; 80 | } 81 | 82 | /** 83 | * Convert deep tree produced by dependency-tree to a 84 | * shallow (one level deep) tree used by madge. 85 | * @param {Object} depTree 86 | * @param {Object} tree 87 | * @param {Object} pathCache 88 | * @param {String} baseDir 89 | * @return {Object} 90 | */ 91 | function convertTreePaths(depTree, pathCache, baseDir) { 92 | const tree = {}; 93 | Object.keys(depTree).forEach(file => { 94 | tree[processPath(file, pathCache, baseDir)] = depTree[file].map(d => processPath(d, pathCache, baseDir)); 95 | }); 96 | 97 | return tree; 98 | } 99 | 100 | /** 101 | * Generate the tree from the given files 102 | * @param {Array} files 103 | * @param config 104 | * @return {Object} 105 | */ 106 | export default function generateTree(files = [], config) { 107 | const depTree = {}; 108 | const nonExistent = {}; 109 | const npmPaths = {}; 110 | const pathCache = {}; 111 | const pathMap = []; 112 | const errors = {}; 113 | 114 | files.forEach(file => { 115 | if (depTree[file]) { 116 | return; 117 | } 118 | 119 | const detective = config.detectiveOptions; 120 | try { 121 | const dependencyTreeResult = dependencyTree({ 122 | filename: file, 123 | directory: config.baseDir, 124 | requireConfig: config.requireConfig, 125 | webpackConfig: config.webpackConfig, 126 | resolveConfig: config.resolveConfig, 127 | visited: config.visited, 128 | errors, 129 | filter: (dependencyFilePath, traversedFilePath) => { 130 | let dependencyFilterRes = true; 131 | const isNpmPath = isNpmPathFunc(dependencyFilePath); 132 | 133 | if (config.dependencyFilter) { 134 | dependencyFilterRes = config.dependencyFilter(dependencyFilePath, traversedFilePath, config.baseDir); 135 | } 136 | 137 | if (config.includeNpm && isNpmPath) { 138 | (npmPaths[traversedFilePath] = npmPaths[traversedFilePath] || []).push(dependencyFilePath); 139 | } 140 | 141 | return !isNpmPath && (dependencyFilterRes || dependencyFilterRes === undefined); 142 | }, 143 | detective, 144 | nonExistent, 145 | pathMap, 146 | cacheProjectAst: config.cacheProjectAst 147 | }); 148 | Object.assign(depTree, dependencyTreeResult); 149 | } catch (err) { 150 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 151 | errors[file] = err; 152 | } 153 | }); 154 | 155 | let tree = convertTreePaths(depTree, pathCache, config.baseDir); 156 | 157 | // rename errors keys from absolute paths to relative paths 158 | Object.keys(errors).forEach(file => { 159 | const relativeFile = processPath(file, pathCache, config.baseDir); 160 | if (relativeFile !== file) { 161 | errors[relativeFile] = errors[file]; 162 | delete errors[file]; 163 | } 164 | }); 165 | 166 | Object.keys(npmPaths).forEach(npmKey => { 167 | const id = processPath(npmKey, pathCache, config.baseDir); 168 | // a file might not be in the tree if it has errors or errors found with its parents 169 | if (!tree[id]) return; 170 | npmPaths[npmKey].forEach(npmPath => { 171 | tree[id].push(processPath(npmPath, pathCache, config.baseDir)); 172 | }); 173 | }); 174 | 175 | if (config.excludeRegExp) { 176 | tree = exclude(tree, config.excludeRegExp); 177 | } 178 | 179 | return { 180 | madgeTree: sort(tree), 181 | skipped: nonExistent, 182 | pathMap, 183 | errors 184 | }; 185 | } 186 | -------------------------------------------------------------------------------- /src/dependency-builder/index.ts: -------------------------------------------------------------------------------- 1 | import { getDependencyTree, resolveNodePackage, resolveModulePath } from './build-tree'; 2 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 3 | import getDependenciesFromSource from './precinct'; 4 | 5 | export { resolveNodePackage, resolveModulePath, getDependencyTree, getDependenciesFromSource }; 6 | -------------------------------------------------------------------------------- /src/dependency-builder/lookups/vue-lookup/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 2 | const fs = require('fs'); 3 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 4 | const path = require('path'); 5 | const compiler = require('vue-template-compiler'); 6 | 7 | const DEFAULT_SCRIPT_LANG = 'js'; 8 | const DEFAULT_STYLE_LANG = 'scss'; 9 | 10 | const languageMap = { 11 | css: 'scss', 12 | stylus: 'styl' 13 | }; 14 | module.exports = function(options) { 15 | const { dependency, filename, isScript } = options; 16 | // eslint-disable-next-line import/no-dynamic-require, global-require 17 | const cabinet = require('../../filing-cabinet'); 18 | 19 | const fileContent = fs.readFileSync(filename); 20 | const { script, styles } = compiler.parseComponent(fileContent.toString(), { pad: 'line' }); 21 | if (isScript) { 22 | const scriptExt = script.lang ? languageMap[script.lang] || script.lang : DEFAULT_SCRIPT_LANG; 23 | return cabinet( 24 | Object.assign(options, { 25 | directory: path.dirname(filename), 26 | content: script.content, 27 | ast: null, 28 | ext: `.${scriptExt}` || path.extname(dependency) 29 | }) 30 | ); 31 | } 32 | const stylesResult = styles.map(style => { 33 | const styleExt = style.lang ? languageMap[style.lang] || style.lang : DEFAULT_STYLE_LANG; 34 | return cabinet( 35 | Object.assign(options, { 36 | filename: `${path.join(path.dirname(filename), path.parse(filename).name)}.${styleExt}`, 37 | directory: path.dirname(filename), 38 | content: style.content, 39 | ast: null, 40 | ext: `.${styleExt}` 41 | }) 42 | ); 43 | }); 44 | return stylesResult[0]; 45 | }; 46 | -------------------------------------------------------------------------------- /src/dependency-builder/path-map.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import pathMapFixture from '../../fixtures/path-map.json'; 3 | import { getPathMapWithLinkFilesData } from './path-map'; 4 | 5 | describe('path-map', () => { 6 | describe('updatePathMapWithLinkFilesData', () => { 7 | it('should return an empty array for an empty pathMap', () => { 8 | const results = getPathMapWithLinkFilesData([]); 9 | expect(results).to.have.lengthOf(0); 10 | }); 11 | it('should throw TypeError for a non array input', () => { 12 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 13 | expect(() => getPathMapWithLinkFilesData()).to.throw(TypeError); 14 | }); 15 | it('should return the same pathMap when there are no link-files involved', () => { 16 | const fixture = [{ file: 'bar/foo.js', dependencies: [] }]; 17 | const results = getPathMapWithLinkFilesData(fixture); 18 | expect(results).to.deep.equal(fixture); 19 | }); 20 | describe('a file bar/foo require dependencies isString and isArray through two levels of link files', () => { 21 | let results; 22 | let barFooDependency; 23 | before(() => { 24 | results = getPathMapWithLinkFilesData(pathMapFixture); 25 | const barFoo = results.find(f => f.file === 'bar/foo.js'); 26 | barFooDependency = barFoo.dependencies[0]; 27 | }); 28 | it('should mark the dependency as a linkFile', () => { 29 | expect(barFooDependency).to.have.property('linkFile'); 30 | expect(barFooDependency.linkFile).to.be.true; 31 | }); 32 | it('should add a new attribute realDependencies to the dependency', () => { 33 | expect(barFooDependency).to.have.property('realDependencies'); 34 | }); 35 | it('realDependencies should include the final dependencies skipping multiple links in between', () => { 36 | expect(barFooDependency.realDependencies).to.have.lengthOf(2); 37 | // ensures that it skips both: utils/index.js and utils/is-string/index.js 38 | expect(barFooDependency.realDependencies[0].file).to.equal('utils/is-string/is-string.js'); 39 | // ensures that it skips both: utils/index.js and utils/is-array/index.js 40 | expect(barFooDependency.realDependencies[1].file).to.equal('utils/is-array/is-array.js'); 41 | }); 42 | it('realDependencies should have the importSpecifiers.mainFile same as the original file', () => { 43 | const isStringSpecifier = barFooDependency.importSpecifiers.find(i => i.name === 'isString'); 44 | const realDepIsStringSpecifier = barFooDependency.realDependencies[0].importSpecifiers[0]; 45 | expect(realDepIsStringSpecifier.mainFile.name).to.equal(isStringSpecifier.name); 46 | expect(realDepIsStringSpecifier.mainFile.isDefault).to.equal(isStringSpecifier.isDefault); 47 | }); 48 | it('realDependencies should have the importSpecifiers.linkFile same as the last link file', () => { 49 | const lastLinkFile = 'utils/is-string/index.js'; 50 | const lastLink = results.find(f => f.file === lastLinkFile); 51 | const lastLinkSpecifier = lastLink.dependencies[0].importSpecifiers[0]; 52 | const realDepIsStringSpecifier = barFooDependency.realDependencies[0].importSpecifiers[0]; 53 | expect(realDepIsStringSpecifier.linkFile.name).to.equal(lastLinkSpecifier.name); 54 | expect(realDepIsStringSpecifier.linkFile.isDefault).to.equal(lastLinkSpecifier.isDefault); 55 | }); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/dependency-builder/path-map.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** 3 | * Path Map is the extra data about the files and the dependencies, such as ImportSpecifiers and 4 | * custom-resolve-modules used. 5 | * The data is retrieved by dependency-tree which collects them from the various detectives. 6 | * It is used to update the final tree. 7 | */ 8 | import R from 'ramda'; 9 | import { processPath } from './generate-tree-madge'; 10 | import { ImportSpecifier, Specifier, LinkFile } from './types/dependency-tree-type'; 11 | 12 | const debug = require('debug')('path-map'); 13 | 14 | export type PathMapDependency = { 15 | importSource: string; // dependency path as it has been received from dependency-tree lib 16 | isCustomResolveUsed?: boolean; // whether a custom resolver, such as an alias "@" for "src" dir, is used 17 | resolvedDep: string; // path relative to consumer root (after convertPathMapToRelativePaths() ) 18 | importSpecifiers?: Specifier[]; // relevant for ES6 and TS 19 | linkFile?: boolean; 20 | realDependencies?: LinkFile[]; // in case it's a link-file 21 | }; 22 | 23 | /** 24 | * PathMap is used to get the ImportSpecifiers from dependency-tree library 25 | */ 26 | export type PathMapItem = { 27 | file: string; // path relative to consumer root (after convertPathMapToRelativePaths() ) 28 | dependencies: PathMapDependency[]; 29 | }; 30 | 31 | export function convertPathMapToRelativePaths(pathMap: PathMapItem[], baseDir: string): PathMapItem[] { 32 | const pathCache = {}; 33 | return pathMap.map((file: PathMapItem) => { 34 | const newFile = R.clone(file); 35 | newFile.file = processPath(file.file, pathCache, baseDir); 36 | newFile.dependencies = file.dependencies.map(dependency => { 37 | const newDependency = R.clone(dependency); 38 | newDependency.resolvedDep = processPath(dependency.resolvedDep, pathCache, baseDir); 39 | return newDependency; 40 | }); 41 | return newFile; 42 | }); 43 | } 44 | 45 | /** 46 | * if a resolvedDep of a specifier has its own importSpecifiers and one of them is the same of the 47 | * given specifier, then, the realDep is not the resolvedDep and might be the resolvedDep of the 48 | * resolvedDep. It needs to be done in multiple rounds because it might have multiple link files. 49 | * 50 | * it is easier to understand with an example: 51 | * bar/foo.js requires an index file => utils/index.js, => `import { isString } from '../utils';` 52 | * which requires another index file: utils/is-string/index.js, => `export { default as isString } from './is-string';` 53 | * which requires a real file utils/is-string/is-string.js => `export default function isString() { };` 54 | * 55 | * according to the files, the dependency of bar/foo is utils/index.js. 56 | * however, we suspect that utils/index.js is only a link file so we try to find the realDep. 57 | * 58 | * round 1: 59 | * utils/is-string/index.js is a first candidate because it has isString in one of the dependencies 60 | * importSpecifiers. however, since it has dependencies itself, we might find more candidates. 61 | * continue to round 2 to find out. 62 | * 63 | * round 2: 64 | * isString specifier of utils/is-string/index.js is connected to utils/is-string/is-string.js 65 | * which introduce this file as the second candidate. this file doesn't have any dependency, 66 | * therefore it returns this file as the final realDep. 67 | */ 68 | function findTheRealDependency( 69 | allPathMapItems: PathMapItem[], 70 | firstPathMap: PathMapItem, 71 | specifier: Specifier 72 | ): PathMapDependency | null | undefined { 73 | let currentPathMap: PathMapItem = firstPathMap; 74 | let lastRealDep: PathMapDependency | null | undefined; 75 | const visitedFiles: string[] = []; 76 | 77 | while (!visitedFiles.includes(currentPathMap.file)) { 78 | visitedFiles.push(currentPathMap.file); 79 | const currentRealDep: PathMapDependency | null | undefined = currentPathMap.dependencies.find(dep => { 80 | if (!dep.importSpecifiers) return false; 81 | return dep.importSpecifiers.find(depSpecifier => depSpecifier.name === specifier.name && depSpecifier.exported); 82 | }); 83 | if (!currentRealDep) { 84 | // the currentRealDep is not the real dependency, return the last real we found 85 | return lastRealDep; 86 | } 87 | const realDepPathMap = allPathMapItems.find(file => file.file === currentRealDep.resolvedDep); 88 | if (!realDepPathMap || !realDepPathMap.dependencies.length) { 89 | // since the currentRealDep we just found doesn't have any more dependencies, we know that it 90 | // is the last one. no need to continue searching. 91 | return currentRealDep; 92 | } 93 | // the realDep we found might not be the last one, continue searching 94 | lastRealDep = currentRealDep; 95 | currentPathMap = realDepPathMap; 96 | } 97 | 98 | // visitedFiles includes currentPathMap.file, which means, it has a cycle dependency 99 | // when the cycle dependencies involve multiple files, we don't know which one is the real file 100 | // and which one is the link file. Here is an example: 101 | // bar/foo.js => `imports { isString } from 'utils'`; 102 | // utils/index.js => `export { isString } from './is-string'`; 103 | // utils/is-string.js => `import { isString } from '.'; export default function () {};` 104 | // the cycle is as follows: bar/foo.js => utils/index.js => utils/is-string.js => utils/index.js. 105 | // here, we don't know whether the utils/is-string.js is the link-file or maybe utils/index.js 106 | // we have no choice but ignoring the link-files. 107 | debug(`an invalid cycle has been found while looking for "${specifier.name}" specifier in "${firstPathMap.file}" file. 108 | visited files by this order: ${visitedFiles.join(', ')} 109 | the first file imports "${specifier.name}" from the second file, the second file imports it from the third and so on. 110 | eventually, the last file imports it from one of the files that were visited already which is an invalid state.`); 111 | return null; 112 | } 113 | 114 | /** 115 | * if a dependency file is in fact a link file, get its real dependencies. 116 | */ 117 | function getDependenciesFromLinkFileIfExists( 118 | dependency: PathMapDependency, 119 | pathMap: PathMapItem[] 120 | ): LinkFile[] | null | undefined { 121 | const dependencyPathMap: PathMapItem | null | undefined = pathMap.find(file => file.file === dependency.resolvedDep); 122 | if ( 123 | !dependencyPathMap || 124 | !dependencyPathMap.dependencies.length || 125 | !dependency.importSpecifiers || 126 | !dependency.importSpecifiers.length 127 | ) { 128 | return null; 129 | } 130 | 131 | const dependencies = dependency.importSpecifiers.map((specifier: Specifier) => { 132 | const realDep = findTheRealDependency(pathMap, dependencyPathMap, specifier); 133 | if (!realDep) return null; 134 | // @ts-ignore 135 | const depImportSpecifier = realDep.importSpecifiers.find(depSpecifier => depSpecifier.name === specifier.name); 136 | const importSpecifier: ImportSpecifier = { 137 | mainFile: specifier, 138 | linkFile: depImportSpecifier 139 | }; 140 | return { file: realDep.resolvedDep, importSpecifier }; 141 | }); 142 | 143 | if (dependencies.some(dep => !dep)) { 144 | // at least one specifier doesn't have "realDep", meaning it doesn't use link-file 145 | return null; 146 | } 147 | const linkFiles = []; 148 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 149 | dependencies.forEach((dep: { file: string; importSpecifier: ImportSpecifier }) => { 150 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 151 | const existingFile = linkFiles.find(linkFile => linkFile.file === dep.file); 152 | if (existingFile) { 153 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 154 | existingFile.importSpecifiers.push(dep.importSpecifier); 155 | } else { 156 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 157 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 158 | linkFiles.push({ file: dep.file, importSpecifiers: [dep.importSpecifier] }); 159 | } 160 | }); 161 | return linkFiles; 162 | } 163 | 164 | /** 165 | * mark dependencies that are link-files as such. Also, add the data of the real dependencies 166 | */ 167 | export function getPathMapWithLinkFilesData(pathMap: PathMapItem[]): PathMapItem[] { 168 | if (!Array.isArray(pathMap)) throw new TypeError('pathMap must be an array'); 169 | const updateDependencyWithLinkData = (dependency: PathMapDependency) => { 170 | const dependenciesFromLinkFiles = getDependenciesFromLinkFileIfExists(dependency, pathMap); 171 | if (dependenciesFromLinkFiles && dependenciesFromLinkFiles.length) { 172 | // it is a link file 173 | dependency.linkFile = true; 174 | dependency.realDependencies = dependenciesFromLinkFiles; 175 | } 176 | }; 177 | pathMap.forEach((file: PathMapItem) => { 178 | file.dependencies.forEach((dependency: PathMapDependency) => { 179 | updateDependencyWithLinkData(dependency); 180 | }); 181 | }); 182 | 183 | return pathMap; 184 | } 185 | -------------------------------------------------------------------------------- /src/dependency-builder/precinct/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | const assert = require('assert'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const rewire = require('rewire'); 7 | const sinon = require('sinon'); 8 | 9 | const fixtures = `${__dirname}/../../../fixtures/precinct`; 10 | // eslint-disable-next-line import/no-dynamic-require, global-require 11 | const ast = require(`${fixtures}/exampleAST`); 12 | const precinct = rewire('./'); 13 | 14 | function read(filename) { 15 | return fs.readFileSync(path.join(fixtures, filename), 'utf8'); 16 | } 17 | 18 | describe('node-precinct', () => { 19 | it('accepts an AST', () => { 20 | const deps = precinct(ast); 21 | const depsKeys = Object.keys(deps); 22 | assert(depsKeys.length === 1); 23 | }); 24 | 25 | it('dangles off a given ast', () => { 26 | precinct(ast); 27 | assert.deepEqual(precinct.ast, ast); 28 | }); 29 | 30 | it('dangles off the parsed ast from a .js file', () => { 31 | precinct(read('amd.js')); 32 | assert.ok(precinct.ast); 33 | assert.notDeepEqual(precinct.ast, ast); 34 | }); 35 | 36 | it('dangles off the parsed ast from a scss detective', () => { 37 | precinct(read('styles.scss'), 'scss'); 38 | assert.notDeepEqual(precinct.ast, {}); 39 | }); 40 | 41 | it('dangles off the parsed ast from a sass detective', () => { 42 | precinct(read('styles.sass'), 'sass'); 43 | assert.notDeepEqual(precinct.ast, {}); 44 | }); 45 | 46 | it('grabs dependencies of amd modules', () => { 47 | const amd = precinct(read('amd.js')); 48 | assert(amd.indexOf('./a') !== -1); 49 | assert(amd.indexOf('./b') !== -1); 50 | assert(amd.length === 2); 51 | }); 52 | 53 | it('grabs dependencies of commonjs modules', () => { 54 | const cjs = precinct(read('commonjs.js')); 55 | expect(cjs).to.have.property('./a'); 56 | expect(cjs).to.have.property('./b'); 57 | assert(Object.keys(cjs).length === 2); 58 | }); 59 | 60 | it('grabs dependencies of es6 modules', () => { 61 | const cjs = precinct(read('es6.js')); 62 | expect(cjs).to.have.property('lib'); 63 | assert(Object.keys(cjs).length === 1); 64 | }); 65 | 66 | it('grabs dependencies of es6 modules with embedded jsx', () => { 67 | const cjs = precinct(read('jsx.js')); 68 | expect(cjs).to.have.property('lib'); 69 | assert(Object.keys(cjs).length === 1); 70 | }); 71 | 72 | it('grabs dependencies of es6 modules with embedded es7', () => { 73 | const cjs = precinct(read('es7.js')); 74 | expect(cjs).to.have.property('lib'); 75 | assert(Object.keys(cjs).length === 1); 76 | }); 77 | 78 | it('throws errors of es6 modules with syntax errors', () => { 79 | const precinctFunc = () => precinct(read('es6WithError.js')); 80 | expect(precinctFunc).to.throw(); 81 | }); 82 | 83 | // this is for supporting PostCSS dialect. The implementation is not merged to this project. 84 | // see the following PR of node-precinct: https://github.com/dependents/node-precinct/pull/40 85 | it.skip('grabs dependencies of css files', () => { 86 | const css = precinct(read('styles.css'), 'css'); 87 | expect(css).to.have.property('foo.css'); 88 | expect(css).to.have.property('baz.css'); 89 | expect(css).to.have.property('bla.css'); 90 | expect(css).to.have.property('another.css'); 91 | }); 92 | 93 | it('grabs dependencies of scss files', function() { 94 | const scss = precinct(read('styles.scss'), 'scss'); 95 | assert.deepEqual(scss, ['_foo', 'baz.scss']); 96 | }); 97 | 98 | it('grabs dependencies of sass files', () => { 99 | const sass = precinct(read('styles.sass'), 'sass'); 100 | assert.deepEqual(sass, ['_foo']); 101 | }); 102 | 103 | it('grabs dependencies of stylus files', () => { 104 | const result = precinct(read('styles.styl'), 'stylus'); 105 | const expected = ['mystyles', 'styles2.styl', 'styles3.styl', 'styles4']; 106 | 107 | assert.deepEqual(result, expected); 108 | }); 109 | 110 | it('grabs dependencies of less files', () => { 111 | const result = precinct(read('styles.less'), 'less'); 112 | const expected = ['_foo', '_bar.css', 'baz.less']; 113 | 114 | assert.deepEqual(result, expected); 115 | }); 116 | 117 | it('grabs dependencies of typescript files', () => { 118 | const result = precinct(read('typescript.ts'), 'ts'); 119 | const expected = ['fs', 'lib', './bar', './my-module.js', './ZipCodeValidator']; 120 | 121 | assert.deepEqual(Object.keys(result), expected); 122 | }); 123 | 124 | it('throws errors of typescript modules with syntax errors', () => { 125 | const precinctFunc = () => precinct(read('typescriptWithError.ts')); 126 | expect(precinctFunc).to.throw(); 127 | }); 128 | 129 | it('supports the object form of type configuration', () => { 130 | const result = precinct(read('styles.styl'), { type: 'stylus' }); 131 | const expected = ['mystyles', 'styles2.styl', 'styles3.styl', 'styles4']; 132 | 133 | assert.deepEqual(result, expected); 134 | }); 135 | 136 | it('yields no dependencies for es6 modules with no imports', () => { 137 | const cjs = precinct(read('es6NoImport.js')); 138 | assert.equal(Object.keys(cjs).length, 0); 139 | }); 140 | 141 | it('yields no dependencies for non-modules', () => { 142 | const none = precinct(read('none.js')); 143 | assert.equal(Object.keys(none).length, 0); 144 | }); 145 | 146 | it('throw on unparsable .js files', () => { 147 | assert.throws(() => { 148 | precinct(read('unparseable.js')); 149 | }, SyntaxError); 150 | }); 151 | 152 | it('does not blow up when parsing a gruntfile #2', () => { 153 | assert.doesNotThrow(() => { 154 | precinct(read('Gruntfile.js')); 155 | }); 156 | }); 157 | 158 | describe('paperwork', () => { 159 | it('returns the dependencies for the given filepath', () => { 160 | assert.ok(Object.keys(precinct.paperwork(`${fixtures}/es6.js`)).length); 161 | assert.ok(Object.keys(precinct.paperwork(`${fixtures}/styles.scss`)).length); 162 | assert.ok(Object.keys(precinct.paperwork(`${fixtures}/typescript.ts`)).length); 163 | assert.ok(Object.keys(precinct.paperwork(`${fixtures}/styles.css`)).length); 164 | }); 165 | 166 | it('throws if the file cannot be found', () => { 167 | assert.throws(() => { 168 | precinct.paperwork('foo'); 169 | }); 170 | }); 171 | 172 | it('filters out core modules if options.includeCore is false', () => { 173 | const deps = precinct.paperwork(`${fixtures}/coreModules.js`, { 174 | includeCore: false 175 | }); 176 | 177 | assert(!deps.length); 178 | }); 179 | 180 | it('does not filter out core modules by default', () => { 181 | const deps = precinct.paperwork(`${fixtures}/coreModules.js`); 182 | assert(Object.keys(deps).length); 183 | }); 184 | 185 | it('supports passing detective configuration', () => { 186 | const config = { 187 | amd: { 188 | skipLazyLoaded: true 189 | } 190 | }; 191 | 192 | const deps = precinct.paperwork(`${fixtures}/amd.js`, { 193 | includeCore: false, 194 | amd: config.amd 195 | }); 196 | assert.deepEqual(deps, ['./a', './b']); 197 | }); 198 | 199 | describe('when given detective configuration', () => { 200 | it('still does not filter out core module by default', () => { 201 | const stub = sinon.stub().returns([]); 202 | const revert = precinct.__set__('precinct', stub); 203 | 204 | precinct.paperwork(`${fixtures}/amd.js`, { 205 | amd: { 206 | skipLazyLoaded: true 207 | } 208 | }); 209 | 210 | assert.equal(stub.args[0][1].includeCore, true); 211 | revert(); 212 | }); 213 | }); 214 | }); 215 | 216 | describe('when given a configuration object', () => { 217 | it('passes amd config to the amd detective', () => { 218 | const config = { 219 | amd: { 220 | skipLazyLoaded: true 221 | } 222 | }; 223 | 224 | const deps = precinct(read('amd.js'), config); 225 | assert.deepEqual(deps, ['./a', './b']); 226 | }); 227 | 228 | describe('that sets mixedImports for es6', () => { 229 | describe('for a file identified as es6', () => { 230 | it('returns both the commonjs and es6 dependencies', () => { 231 | const deps = precinct(read('es6MixedImport.js'), { 232 | es6: { 233 | mixedImports: true 234 | } 235 | }); 236 | 237 | assert.equal(Object.keys(deps).length, 2); 238 | }); 239 | }); 240 | 241 | describe('for a file identified as cjs', () => { 242 | it('returns both the commonjs and es6 dependencies', () => { 243 | const deps = precinct(read('cjsMixedImport.js'), { 244 | es6: { 245 | mixedImports: true 246 | } 247 | }); 248 | 249 | assert.equal(Object.keys(deps).length, 2); 250 | }); 251 | }); 252 | }); 253 | }); 254 | 255 | describe('when lazy exported dependencies in CJS', () => { 256 | it('grabs those lazy dependencies', () => { 257 | const cjs = precinct(read('cjsExportLazy.js')); 258 | expect(cjs).to.have.property('./amd'); 259 | expect(cjs).to.have.property('./es6'); 260 | expect(cjs).to.have.property('./es7'); 261 | assert.equal(Object.keys(cjs).length, 3); 262 | }); 263 | }); 264 | 265 | describe('when given an es6 file', () => { 266 | describe('that uses CJS imports for lazy dependencies', () => { 267 | it('grabs the lazy imports', () => { 268 | const es6 = precinct(read('es6MixedExportLazy.js'), { 269 | es6: { 270 | mixedImports: true 271 | } 272 | }); 273 | expect(es6).to.have.property('./amd'); 274 | expect(es6).to.have.property('./es6'); 275 | expect(es6).to.have.property('./es7'); 276 | assert.equal(Object.keys(es6).length, 3); 277 | }); 278 | }); 279 | }); 280 | }); 281 | -------------------------------------------------------------------------------- /src/dependency-builder/precinct/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * this file had been forked from https://github.com/dependents/node-precinct 3 | */ 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | import getModuleType from 'module-definition'; 7 | import Walker from 'node-source-walk'; 8 | import detectiveAmd from 'detective-amd'; 9 | import detectiveStylus from 'detective-stylus'; 10 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 11 | import detectiveEs6 from '../detectives/detective-es6'; 12 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 13 | import detectiveLess from '../detectives/detective-less'; 14 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 15 | import detectiveSass from '../detectives/detective-sass'; 16 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 17 | import detectiveScss from '../detectives/detective-scss'; 18 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 19 | import detectiveCss from '../detectives/detective-css'; 20 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 21 | import detectiveTypeScript from '../detectives/detective-typescript'; 22 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 23 | import detectiveStylable from '../detectives/detective-stylable'; 24 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 25 | import detectiveVue from '../detectives/detective-vue'; 26 | import { SUPPORTED_EXTENSIONS } from '../../constants'; 27 | 28 | const debug = require('debug')('precinct'); 29 | 30 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 31 | const natives = process.binding('natives'); 32 | 33 | /** 34 | * Finds the list of dependencies for the given file 35 | * 36 | * @param {String|Object} content - File's content or AST 37 | * @param {Object} [options] 38 | * @param {String} [options.type] - The type of content being passed in. Useful if you want to use a non-js detective 39 | * @return {String[]} 40 | */ 41 | function precinct(content, options) { 42 | options = options || {}; 43 | let dependencies = []; 44 | let ast; 45 | let type = options.type; 46 | 47 | // Legacy form backCompat where type was the second parameter 48 | if (typeof options === 'string') { 49 | type = options; 50 | options = {}; 51 | } 52 | 53 | debug('options given: ', options); 54 | 55 | // We assume we're dealing with a JS file 56 | if (!type && typeof content !== 'object') { 57 | const walker = new Walker(); 58 | 59 | try { 60 | // Parse once and distribute the AST to all detectives 61 | ast = walker.parse(content); 62 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 63 | precinct.ast = ast; 64 | } catch (e) { 65 | // In case a previous call had it populated 66 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 67 | precinct.ast = null; 68 | debug('could not parse content: %s', e.message); 69 | throw e; 70 | } 71 | // SASS files shouldn't be parsed by Acorn 72 | } else { 73 | ast = content; 74 | 75 | if (typeof content === 'object') { 76 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 77 | precinct.ast = content; 78 | } 79 | } 80 | 81 | type = options.useContent ? getModuleType.fromSource(content) : type || getModuleType.fromSource(ast); 82 | debug('module type: ', type); 83 | 84 | let theDetective; 85 | 86 | switch (type) { 87 | case 'commonjs': 88 | case 'es6': 89 | theDetective = detectiveEs6; 90 | break; 91 | case 'amd': 92 | theDetective = detectiveAmd; 93 | break; 94 | case 'sass': 95 | theDetective = detectiveSass; 96 | break; 97 | case 'less': 98 | theDetective = detectiveLess; 99 | break; 100 | case 'scss': 101 | theDetective = detectiveScss; 102 | break; 103 | case 'css': 104 | theDetective = detectiveCss; 105 | break; 106 | case 'stylus': 107 | theDetective = detectiveStylus; 108 | break; 109 | case 'ts': 110 | case 'tsx': 111 | theDetective = detectiveTypeScript; 112 | break; 113 | case 'stylable': 114 | theDetective = detectiveStylable; 115 | break; 116 | case 'vue': 117 | theDetective = detectiveVue; 118 | break; 119 | default: 120 | break; 121 | } 122 | 123 | if (theDetective) { 124 | dependencies = type === 'vue' ? theDetective(ast, options) : theDetective(ast, options[type] || {}); 125 | } 126 | 127 | // For non-JS files that we don't parse 128 | if (theDetective && theDetective.ast) { 129 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 130 | precinct.ast = theDetective.ast; 131 | } 132 | 133 | return dependencies; 134 | } 135 | 136 | function assign(o1, o2) { 137 | // eslint-disable-next-line 138 | for (const key in o2) { 139 | // eslint-disable-next-line 140 | if (o2.hasOwnProperty(key)) { 141 | o1[key] = o2[key]; 142 | } 143 | } 144 | 145 | return o1; 146 | } 147 | 148 | /** 149 | * Returns the dependencies for the given file path 150 | * 151 | * @param {String} filename 152 | * @param {Object} [options] 153 | * @param {Boolean} [options.includeCore=true] - Whether or not to include core modules in the dependency list 154 | * @return {String[]} 155 | */ 156 | precinct.paperwork = function(filename, options) { 157 | options = assign( 158 | { 159 | includeCore: true 160 | }, 161 | options || {} 162 | ); 163 | 164 | const content = fs.readFileSync(filename, 'utf8'); 165 | const ext = path.extname(filename); 166 | 167 | const getType = () => { 168 | if (filename.endsWith('.st.css')) { 169 | return 'stylable'; 170 | } 171 | switch (ext) { 172 | case '.css': 173 | case '.scss': 174 | case '.sass': 175 | case '.less': 176 | case '.ts': 177 | case '.vue': 178 | return ext.replace('.', ''); 179 | case '.styl': 180 | return 'stylus'; 181 | case '.tsx': 182 | if (!options.ts) options.ts = {}; 183 | options.ts.jsx = true; 184 | return 'ts'; 185 | case '.jsx': 186 | return 'es6'; 187 | default: 188 | return null; 189 | } 190 | }; 191 | 192 | const getDeps = () => { 193 | if (SUPPORTED_EXTENSIONS.includes(ext)) return precinct(content, options); 194 | debug(`skipping unsupported file ${filename}`); 195 | return []; 196 | }; 197 | 198 | const type = getType(); 199 | options.type = type; 200 | 201 | const deps = getDeps(); 202 | 203 | if (deps && !options.includeCore) { 204 | if (Array.isArray(deps)) { 205 | return deps.filter(function(d) { 206 | return !natives[d]; 207 | }); 208 | } 209 | return Object.keys(deps).reduce((acc, value) => { 210 | if (!natives[value]) acc[value] = deps[value]; 211 | return acc; 212 | }, {}); 213 | } 214 | 215 | return deps; 216 | }; 217 | 218 | module.exports = precinct; 219 | -------------------------------------------------------------------------------- /src/dependency-builder/types/dependency-tree-type.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** 4 | * Import Specifier data. 5 | * For example, `import foo from './bar' `, "foo" is the import-specifier and is default. 6 | * Conversely, `import { foo } from './bar' `, here, "foo" is non-default. 7 | */ 8 | export type Specifier = { 9 | isDefault: boolean; 10 | name: string; 11 | exported?: boolean; 12 | }; 13 | 14 | /** 15 | * ImportSpecifier are used to generate links from component to its dependencies. 16 | * For example, a component might have a dependency: "import { foo } from './bar' ", when a link is generated, we use 17 | * the import-specifier name, which is "foo" to generate the link correctly. 18 | */ 19 | export type ImportSpecifier = { 20 | mainFile: Specifier; 21 | linkFile?: Specifier; // relevant only when the dependency is a link file (e.g. index.js which import and export the variable from other file) 22 | }; 23 | 24 | export type FileObject = { 25 | file: string; 26 | importSpecifiers?: ImportSpecifier[]; 27 | importSource: string; 28 | isCustomResolveUsed?: boolean; 29 | isLink?: boolean; 30 | linkDependencies?: Record[]; 31 | }; 32 | 33 | export type LinkFile = { 34 | file: string; 35 | importSpecifiers: ImportSpecifier[]; 36 | }; 37 | 38 | type MissingType = 'files' | 'packages' | 'bits'; 39 | 40 | export type DependenciesResults = { 41 | files?: FileObject[]; 42 | packages?: { [packageName: string]: string }; // pkgName: pkgVersion 43 | unidentifiedPackages?: string[]; 44 | bits?: Record; 45 | error?: Error; // error.code is either PARSING_ERROR or RESOLVE_ERROR 46 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 47 | missing?: { [key: MissingType]: string[] }; 48 | }; 49 | 50 | export type Tree = { 51 | [filePath: string]: DependenciesResults; 52 | }; 53 | 54 | export type ResolveModulesConfig = { 55 | modulesDirectories: string[]; 56 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 57 | aliases: { [key: string]: string }; // e.g. { '@': 'src' } 58 | }; 59 | 60 | export type DependencyTreeParams = { 61 | baseDir: string; 62 | consumerPath: string; 63 | filePaths: string[]; 64 | bindingPrefix: string; 65 | resolveModulesConfig: ResolveModulesConfig | null | undefined; 66 | visited: Record | null | undefined; 67 | cacheProjectAst: Record | null | undefined; 68 | }; 69 | -------------------------------------------------------------------------------- /src/dependency-builder/types/index.ts: -------------------------------------------------------------------------------- 1 | /* THIS IS A BIT-AUTO-GENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. */ 2 | 3 | module.exports = require('./dependency-tree-type'); 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | import { 3 | getDependencyTree, 4 | resolveNodePackage, 5 | resolveModulePath, 6 | getDependenciesFromSource 7 | } from './dependency-builder'; 8 | import { npmLogin } from './registry'; 9 | import PackageJson from './package-json/package-json'; 10 | 11 | module.exports = { 12 | getDependencyTree, 13 | resolveNodePackage, 14 | resolveModulePath, 15 | PackageJson, 16 | npmLogin, 17 | getDependenciesFromSource 18 | }; 19 | -------------------------------------------------------------------------------- /src/package-json/package-json.ts: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | import fs from 'fs-extra'; 3 | import R from 'ramda'; 4 | import parents from 'parents'; 5 | import path from 'path'; 6 | import { PACKAGE_JSON } from '../constants'; 7 | 8 | function composePath(componentRootFolder: string) { 9 | return path.join(componentRootFolder, PACKAGE_JSON); 10 | } 11 | function convertComponentsIdToValidPackageName(registryPrefix: string, id: string): string { 12 | return `${registryPrefix}/${id.replace(/\//g, '.')}`; 13 | } 14 | 15 | export type PackageJsonProps = { 16 | name?: string; 17 | version?: string; 18 | homepage?: string; 19 | main?: string; 20 | dependencies?: Record; 21 | devDependencies?: Record; 22 | peerDependencies?: Record; 23 | license?: string; 24 | scripts?: Record; 25 | workspaces?: string[]; 26 | private?: boolean; 27 | }; 28 | 29 | export default class PackageJson { 30 | name?: string; 31 | version?: string; 32 | homepage?: string; 33 | main?: string; 34 | dependencies?: Record; 35 | devDependencies?: Record; 36 | peerDependencies?: Record; 37 | componentRootFolder?: string; // path where to write the package.json 38 | license?: string; 39 | scripts?: Record; 40 | workspaces?: string[]; 41 | 42 | constructor( 43 | componentRootFolder: string, 44 | { 45 | name, 46 | version, 47 | homepage, 48 | main, 49 | dependencies, 50 | devDependencies, 51 | peerDependencies, 52 | license, 53 | scripts, 54 | workspaces 55 | }: PackageJsonProps 56 | ) { 57 | this.name = name; 58 | this.version = version; 59 | this.homepage = homepage; 60 | this.main = main; 61 | this.dependencies = dependencies; 62 | this.devDependencies = devDependencies; 63 | this.peerDependencies = peerDependencies; 64 | this.componentRootFolder = componentRootFolder; 65 | this.license = license; 66 | this.scripts = scripts; 67 | this.workspaces = workspaces; 68 | } 69 | 70 | static loadSync(componentRootFolder: string): PackageJson | null { 71 | const composedPath = composePath(componentRootFolder); 72 | if (!PackageJson.hasExisting(componentRootFolder)) return null; 73 | const componentJsonObject = fs.readJsonSync(composedPath); 74 | return new PackageJson(componentRootFolder, componentJsonObject); 75 | } 76 | 77 | static hasExisting(componentRootFolder: string): boolean { 78 | const packageJsonPath = composePath(componentRootFolder); 79 | return fs.pathExistsSync(packageJsonPath); 80 | } 81 | 82 | /** 83 | * Taken from this package (with some minor changes): 84 | * https://www.npmjs.com/package/find-package 85 | * https://github.com/jalba/find-package 86 | */ 87 | static findPath(dir) { 88 | const parentsArr = parents(dir); 89 | let i; 90 | // eslint-disable-next-line 91 | for (i = 0; i < parentsArr.length; i++) { 92 | const config = `${parentsArr[i]}/package.json`; 93 | try { 94 | if (fs.lstatSync(config).isFile()) { 95 | return config; 96 | } 97 | } catch (e) {} // eslint-disable-line 98 | } 99 | return null; 100 | } 101 | 102 | /** 103 | * Taken from this package (with some minor changes): 104 | * https://www.npmjs.com/package/find-package 105 | * https://github.com/jalba/find-package 106 | * 107 | */ 108 | static findPackage(dir, addPaths) { 109 | const pathToConfig = this.findPath(dir); 110 | let configJSON: any = null; 111 | // eslint-disable-next-line import/no-dynamic-require, global-require 112 | if (pathToConfig !== null) configJSON = require(path.resolve(pathToConfig)); 113 | if (configJSON && addPaths) { 114 | configJSON.paths = { 115 | // @ts-ignore 116 | relative: path.relative(dir, pathToConfig), 117 | absolute: pathToConfig 118 | }; 119 | } else if (configJSON !== null) { 120 | delete configJSON.paths; 121 | } 122 | 123 | return configJSON; 124 | } 125 | 126 | /* 127 | * load package.json from path 128 | */ 129 | static async getPackageJson(pathStr: string) { 130 | const getRawObject = () => fs.readJson(composePath(pathStr)); 131 | const exist = PackageJson.hasExisting(pathStr); 132 | if (exist) return getRawObject(); 133 | return null; 134 | } 135 | 136 | /* 137 | * save package.json in path 138 | */ 139 | static saveRawObject(pathStr: string, obj: Record) { 140 | return fs.outputJSON(composePath(pathStr), obj, { spaces: 2 }); 141 | } 142 | 143 | /* 144 | * For an existing package.json file of the root project, we don't want to do any change, other than what needed. 145 | * That's why this method doesn't use the 'load' and 'write' methods of this class. Otherwise, it'd write only the 146 | * PackageJsonPropsNames attributes. 147 | * Also, in case there is no package.json file in this project, it generates a new one with only the 'dependencies' 148 | * adds workspaces with private flag if dosent exist. 149 | */ 150 | static async addWorkspacesToPackageJson( 151 | rootDir: string, 152 | componentsDefaultDirectory: string, 153 | dependenciesDirectory: string, 154 | customImportPath: string | null | undefined 155 | ) { 156 | const pkg = (await PackageJson.getPackageJson(rootDir)) || {}; 157 | const workSpaces = PackageJson.extractWorkspacesPackages(pkg) || []; 158 | workSpaces.push(dependenciesDirectory); 159 | workSpaces.push(componentsDefaultDirectory); 160 | if (customImportPath) workSpaces.push(customImportPath); 161 | if (!pkg.workspaces) pkg.workspaces = []; 162 | this.updateWorkspacesPackages(pkg, R.uniq(workSpaces)); 163 | pkg.private = !!pkg.workspaces; 164 | await PackageJson.saveRawObject(rootDir, pkg); 165 | } 166 | 167 | /* 168 | * remove workspaces dir from workspace in package.json with changing other fields in package.json 169 | */ 170 | static async removeComponentsFromWorkspaces(rootDir: string, pathsTOoRemove: string[]) { 171 | const pkg = (await PackageJson.getPackageJson(rootDir)) || {}; 172 | const workspaces = this.extractWorkspacesPackages(pkg); 173 | if (!workspaces) return; 174 | const updatedWorkspaces = workspaces.filter(folder => !pathsTOoRemove.includes(folder)); 175 | this.updateWorkspacesPackages(pkg, updatedWorkspaces); 176 | await PackageJson.saveRawObject(rootDir, pkg); 177 | } 178 | 179 | /* 180 | * remove components from package.json dependencies 181 | */ 182 | static async removeComponentsFromDependencies(rootDir: string, registryPrefix, componentIds: string[]) { 183 | const pkg = await PackageJson.getPackageJson(rootDir); 184 | if (pkg && pkg.dependencies) { 185 | componentIds.forEach(id => { 186 | delete pkg.dependencies[convertComponentsIdToValidPackageName(registryPrefix, id)]; 187 | }); 188 | await PackageJson.saveRawObject(rootDir, pkg); 189 | } 190 | } 191 | 192 | static extractWorkspacesPackages(packageJson: { [k: string]: any }): string[] | null { 193 | if (!packageJson.workspaces) return null; 194 | this.throwForInvalidWorkspacesConfig(packageJson); 195 | if (Array.isArray(packageJson.workspaces)) { 196 | return packageJson.workspaces; 197 | } 198 | if (Array.isArray(packageJson.workspaces.packages)) { 199 | return packageJson.workspaces.packages; 200 | } 201 | return null; 202 | } 203 | 204 | static updateWorkspacesPackages(packageJson, workspacesPackages): void { 205 | if (!packageJson.workspaces) return; 206 | this.throwForInvalidWorkspacesConfig(packageJson); 207 | if (Array.isArray(packageJson.workspaces)) { 208 | packageJson.workspaces = workspacesPackages; 209 | } 210 | if (Array.isArray(packageJson.workspaces.packages)) { 211 | packageJson.workspaces.packages = workspacesPackages; 212 | } 213 | } 214 | 215 | /** 216 | * according to Yarn Git repo, the workspaces type configured as the following 217 | * `workspaces?: Array | WorkspacesConfig` 218 | * and `WorkspacesConfig` is: 219 | * `export type WorkspacesConfig = { packages?: Array, nohoist?: Array };` 220 | * see https://github.com/yarnpkg/yarn/blob/master/src/types.js 221 | */ 222 | static throwForInvalidWorkspacesConfig(packageJson) { 223 | if (!packageJson.workspaces) return; 224 | if ( 225 | typeof packageJson.workspaces !== 'object' || 226 | (!Array.isArray(packageJson.workspaces) && !Array.isArray(packageJson.workspaces.packages)) 227 | ) { 228 | throw new Error('workspaces property does not have the correct format, please refer to Yarn documentation'); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/registry/exceptions/index.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | import PathToNpmrcNotExist from './path-to-npmrc-not-exist'; 3 | import WriteToNpmrcError from './write-to-npmrc-error'; 4 | 5 | export { PathToNpmrcNotExist, WriteToNpmrcError }; 6 | -------------------------------------------------------------------------------- /src/registry/exceptions/path-to-npmrc-not-exist.ts: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | export default class PathsNotExist extends Error { 3 | path: string; 4 | constructor(path: string) { 5 | super(); 6 | this.path = path; 7 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 8 | this.code = 'PathNotExist'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/registry/exceptions/write-to-npmrc-error.ts: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | export default class WriteToNpmrcError extends Error { 3 | path: string; 4 | constructor(path: string) { 5 | super(); 6 | this.path = path; 7 | // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! 8 | this.code = 'WriteError'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/registry/index.ts: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | import npmLogin from './registry'; 3 | // eslint-disable-next-line import/prefer-default-export 4 | export { npmLogin }; 5 | -------------------------------------------------------------------------------- /src/registry/registry.ts: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import iniBuilder from 'ini-builder'; 5 | import userHome from 'user-home'; 6 | import { DEFAULT_BINDINGS_PREFIX } from '../constants'; 7 | import { PathToNpmrcNotExist, WriteToNpmrcError } from './exceptions'; 8 | 9 | function findrc(pathToNpmrc: string) { 10 | let userNpmrc = path.join(userHome, '.npmrc'); 11 | if (pathToNpmrc) { 12 | if (!fs.existsSync(pathToNpmrc)) throw new PathToNpmrcNotExist(pathToNpmrc); 13 | const stats = fs.statSync(pathToNpmrc); 14 | if (stats.isFile()) userNpmrc = pathToNpmrc; 15 | else userNpmrc = path.join(pathToNpmrc, '.npmrc'); 16 | } 17 | return userNpmrc; 18 | } 19 | 20 | function mergeOrCreateConfig( 21 | token: string, 22 | url: string, 23 | config: Array> = [] 24 | ): Array> { 25 | const strippedUrl = url.replace(/(^\w+:|^)\/\//, ''); 26 | const iniReg = iniBuilder.find(config, `${DEFAULT_BINDINGS_PREFIX}:registry`); 27 | const iniToken = iniBuilder.find(config, `//${strippedUrl}/:_authToken`); 28 | if (!iniReg) { 29 | config.push({ 30 | path: [`${DEFAULT_BINDINGS_PREFIX}:registry`], 31 | value: url 32 | }); 33 | } else { 34 | iniReg.value = url; 35 | } 36 | if (!iniToken) { 37 | config.push({ 38 | path: [`//${strippedUrl}/:_authToken`], 39 | value: token 40 | }); 41 | } else { 42 | iniToken.value = token; 43 | } 44 | return config; 45 | } 46 | 47 | export default function npmLogin(token: string, pathToNpmrc: string, url: string): string { 48 | const npmrcPath = findrc(pathToNpmrc); 49 | const npmrcConfig = fs.existsSync(npmrcPath) 50 | ? mergeOrCreateConfig(token, url, iniBuilder.parse(fs.readFileSync(npmrcPath, 'utf-8'))) 51 | : mergeOrCreateConfig(token, url); 52 | try { 53 | fs.writeFileSync(npmrcPath, iniBuilder.serialize(npmrcConfig)); 54 | } catch (err) { 55 | throw new WriteToNpmrcError(npmrcPath); 56 | } 57 | return npmrcPath; 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import isRelativeImport from './is-relative-import'; 2 | // eslint-disable-next-line import/prefer-default-export 3 | export { isRelativeImport }; 4 | -------------------------------------------------------------------------------- /src/utils/is-relative-import.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** 4 | * returns whether a the require/import path is relative. 5 | * 6 | * the criteria is taken from http://www.typescriptlang.org/docs/handbook/module-resolution.html 7 | * Quote: A relative import is one that starts with /, ./ or ../. Some examples include: 8 | * import Entry from "./components/Entry"; 9 | * import { DefaultHeaders } from "../constants/http"; 10 | * import "/mod"; 11 | * End quote. 12 | */ 13 | export default function isRelativeImport(str: string): boolean { 14 | return str.startsWith('./') || str.startsWith('../') || str.startsWith('/') || str === '.' || str === '..'; 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "moduleResolution": "node", 5 | "noEmit": true, 6 | "module": "commonjs", 7 | "target": "es2015", 8 | "noImplicitAny": false, 9 | "removeComments": true, 10 | "preserveConstEnums": true, 11 | "esModuleInterop": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true 14 | }, 15 | "include": [ 16 | "src" 17 | ], 18 | "exclude": [ 19 | "node_modules", 20 | "fixtures" 21 | ] 22 | } --------------------------------------------------------------------------------