├── .commitlintrc.js ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── codeql.yaml │ ├── commitlint.yaml │ ├── deployment.yaml │ └── integration.yaml ├── .gitignore ├── .mocharc.js ├── .npmignore ├── .remarkignore ├── .remarkrc.js ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── makefile ├── package-lock.json ├── package.json ├── release.config.js ├── src ├── ErrnoException.ts ├── cjsresolve.ts ├── index.ts ├── pkgparts.test.ts ├── pkgparts.ts └── resolve │ ├── .fixtures │ └── packages │ │ ├── main-pkg │ │ ├── file.js │ │ ├── main.js │ │ └── package.json │ │ ├── pkg │ │ ├── file.js │ │ ├── index.js │ │ └── package.json │ │ └── scope-pkg │ │ ├── file.js │ │ ├── index.js │ │ └── package.json │ ├── Config.ts │ ├── Outcome.ts │ ├── index.ts │ ├── resolve.test.ts │ └── resolve.ts ├── test ├── bootstrap.ts ├── globalhooks.test.ts └── package.test.ts ├── tsconfig.json └── utils ├── ci └── sync.sh ├── githooks └── commit-msg └── make ├── projectfiles.sh └── release.sh /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | extends: [ 5 | '@strv/commitlint-config', 6 | ], 7 | 8 | rules: { 9 | 'body-max-line-length': [0], 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [{*, .*}] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | max_line_length = 100 13 | 14 | [{makefile,Makefile,**.mk}] 15 | indent_style = tab 16 | 17 | [package-lock.json] 18 | indent_style = tab 19 | 20 | [{**.md,**.json}] 21 | max_line_length = off 22 | 23 | [COMMIT_EDITMSG] 24 | max_line_length = 70 25 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.d.ts 3 | !.*.js 4 | !*.config.js 5 | node_modules 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const os = require('os') 4 | 5 | /** Line break style to be used, based on current OS */ 6 | const lbstyle = os.platform() === 'win32' ? 'windows' : 'unix' 7 | 8 | module.exports = { 9 | reportUnusedDisableDirectives: true, 10 | 11 | extends: [ 12 | '@strv/node/v14', 13 | '@strv/node/optional', 14 | '@strv/node/style', 15 | ], 16 | 17 | rules: { 18 | // If your editor cannot show these to you, occasionally turn this off and run the linter 19 | 'no-warning-comments': 0, 20 | 21 | // This repository is configured so that upon checkout, git should convert line endings to 22 | // platform-specific defaults and convert them back to LF when checking in. As such, we must 23 | // enforce CRLF endings on Windows, otherwise the lint task would fail on Windows systems. 24 | 'linebreak-style': ['error', lbstyle], 25 | }, 26 | 27 | overrides: [{ 28 | files: [ 29 | 'src/**/*.ts', 30 | 'test/**/*.ts', 31 | 'test/**/*.test.ts', 32 | ], 33 | 34 | extends: [ 35 | '@strv/node/v14', 36 | '@strv/node/optional', 37 | '@strv/typescript', 38 | '@strv/typescript/style', 39 | '@strv/mocha', 40 | ], 41 | 42 | parserOptions: { 43 | project: 'tsconfig.json', 44 | }, 45 | 46 | settings: { 47 | 'import/resolver': { 48 | typescript: {}, 49 | }, 50 | }, 51 | 52 | env: { 53 | // Disable Mocha globals which are enabled in @strv/mocha. We will import the necessary 54 | // functions directly from 'mocha' package in this project. 55 | // This is done so that we avoid having all of the Mocha globals being declared even in source 56 | // files. It is currently not possible to disable these globals for source files but have them 57 | // available in test files - they are either fully available or not available at all. 58 | mocha: false, 59 | }, 60 | 61 | rules: { 62 | // If your editor cannot show these to you, occasionally turn this off and run the linter 63 | 'no-warning-comments': 0, 64 | 'linebreak-style': ['error', lbstyle], 65 | }, 66 | }], 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'CodeQL' 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | # The branches below must be a subset of the branches above 10 | branches: 11 | - master 12 | schedule: 13 | - cron: '30 3 * * 2' 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: ['javascript'] 24 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 25 | # Learn more... 26 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v2 31 | 32 | # Initializes the CodeQL tools for scanning. 33 | - name: Initialize CodeQL 34 | uses: github/codeql-action/init@v1 35 | with: 36 | languages: ${{ matrix.language }} 37 | # If you wish to specify custom queries, you can do so here or in a config file. 38 | # By default, queries listed here will override any specified in a config file. 39 | # Prefix the list here with "+" to use these queries and those in the config file. 40 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 41 | 42 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 43 | # If this step fails, then you should remove it and run the build manually (see below) 44 | - name: Autobuild 45 | uses: github/codeql-action/autobuild@v1 46 | 47 | # ℹ️ Command-line programs to run using the OS shell. 48 | # 📚 https://git.io/JvXDl 49 | 50 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 51 | # and modify them (or add more) to build your code if your project 52 | # uses a compiled language 53 | 54 | #- run: | 55 | # make bootstrap 56 | # make release 57 | 58 | - name: Perform CodeQL Analysis 59 | uses: github/codeql-action/analyze@v1 60 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Commitlint 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'master' 7 | 8 | jobs: 9 | commitlint: 10 | runs-on: ubuntu-20.04 11 | timeout-minutes: 5 12 | env: 13 | CI: 'true' 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 50 19 | - uses: actions/setup-node@v1 20 | with: 21 | node-version: 14 22 | 23 | - name: Fetch master 24 | run: git fetch --no-tags --depth=50 origin master 25 | 26 | - run: make install 27 | - name: Commitlint 28 | run: npx commitlint --color --verbose --from $(git merge-base origin/master HEAD) 29 | -------------------------------------------------------------------------------- /.github/workflows/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Continuous Deployment 3 | 4 | on: 5 | push: 6 | branches: 7 | - release/* 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-20.04 12 | timeout-minutes: 10 13 | env: 14 | CI: 'true' 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - uses: actions/setup-node@v1 21 | with: 22 | node-version: 14 23 | 24 | - run: make install 25 | - run: make compile 26 | - name: Semantic Release 27 | run: npx semantic-release 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | 32 | - name: Post-release sync 33 | run: ./utils/ci/sync.sh 34 | if: success() 35 | -------------------------------------------------------------------------------- /.github/workflows/integration.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Continuous Integration 3 | 4 | on: 5 | push: 6 | 7 | jobs: 8 | verify: 9 | strategy: 10 | matrix: 11 | os: 12 | - ubuntu-20.04 13 | - macos-10.15 14 | - windows-2019 15 | node: 16 | - 12 17 | - 14 18 | 19 | runs-on: ${{ matrix.os }} 20 | timeout-minutes: 10 21 | env: 22 | CI: 'true' 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | with: 27 | fetch-depth: 0 28 | - uses: actions/setup-node@v1 29 | with: 30 | node-version: ${{ matrix.node }} 31 | 32 | - run: make install 33 | - run: make compile 34 | 35 | - name: ESLint 36 | run: make lint 37 | - name: Tests 38 | run: make test 39 | # TODO: Perhaps enable later 40 | # coverage: 41 | # runs-on: ubuntu-20.04 42 | # timeout-minutes: 10 43 | # env: 44 | # CI: 'true' 45 | # steps: 46 | # - uses: actions/checkout@v2 47 | # with: 48 | # fetch-depth: 0 49 | # - uses: actions/setup-node@v1 50 | # with: 51 | # node-version: 14 52 | # - run: make install 53 | # - run: make compile 54 | # - run: make coverage 55 | # - uses: devmasx/coverage-check-action@v1.1.0 56 | # with: 57 | # type: lcov 58 | # result_path: coverage/lcov.info 59 | # min_coverage: 90 60 | # token: ${{ secrets.GITHUB_TOKEN }} 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Local configuration 5 | .env 6 | .bashrc 7 | .npmrc 8 | local.mk 9 | 10 | # Compiled files 11 | **/*.js 12 | **/*.js.map 13 | **/*.d.ts 14 | **/*.d.ts.map 15 | !*.config.js 16 | !**/.*.js 17 | !src/resolve/.fixtures/**/*.js 18 | 19 | # Generated files 20 | .nvmrc 21 | docs 22 | coverage 23 | gh-pages 24 | 25 | # Garbage 26 | *.lerna_backup 27 | *.log 28 | *~ 29 | *# 30 | .DS_Store 31 | .nyc_output 32 | .buildstate 33 | 34 | # IDE stuff 35 | .netbeans 36 | nbproject 37 | .idea 38 | .node_history 39 | *.sublime-* 40 | *.atom-* 41 | .tern-* 42 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | // Options for mocha-reporter-remote 5 | // Other reporters will ignore these options. 6 | reporterOption: [ 7 | 'nostats=1', 8 | ], 9 | colors: true, 10 | checkLeaks: true, 11 | require: [ 12 | 'source-map-support/register', 13 | 'test/bootstrap', 14 | ], 15 | exclude: [ 16 | 'node_modules/**', 17 | ], 18 | } 19 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /coverage 3 | /utils 4 | /test 5 | 6 | .* 7 | makefile 8 | *.mk 9 | *.config.js 10 | -------------------------------------------------------------------------------- /.remarkignore: -------------------------------------------------------------------------------- 1 | # Changelog is autogenerated 2 | CHANGELOG.md 3 | -------------------------------------------------------------------------------- /.remarkrc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('@strv/remark-config') 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Mocha: All", 9 | "type": "node", 10 | "request": "launch", 11 | "skipFiles": [ 12 | "/**" 33 | ], 34 | "presentation": { 35 | "group": "Node.js" 36 | } 37 | }, 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "editor.tabSize": 2, 6 | "eslint.lintTask.enable": true, 7 | "eslint.lintTask.options": "--cache --cache-location .buildstate/.eslintcache --ext js,ts .", 8 | "files.exclude": { 9 | "**/*.d.ts": { 10 | "when": "$(basename).ts" 11 | }, 12 | "**/*.d.ts.map": true, 13 | "**/*.js": { 14 | "when": "$(basename).ts" 15 | }, 16 | "**/.DS_Store": true, 17 | "**/.git": true, 18 | ".buildstate": true 19 | }, 20 | "files.insertFinalNewline": true, 21 | "files.trimFinalNewlines": true, 22 | "files.trimTrailingWhitespace": true, 23 | "mochaExplorer.files": "{src,test}/**/*.test.js", 24 | "typescript.enablePromptUseWorkspaceTsdk": true, 25 | "typescript.tsdk": "node_modules/typescript/lib" 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "make:test", 8 | "detail": "Run the test suite", 9 | "type": "process", 10 | "command": "make", 11 | "args": [ 12 | "test" 13 | ], 14 | "group": { 15 | "kind": "test", 16 | "isDefault": true 17 | }, 18 | "problemMatcher": [ 19 | "$tsc" 20 | ], 21 | "presentation": { 22 | "clear": true 23 | } 24 | }, 25 | { 26 | "label": "make:compile", 27 | "detail": "Compile the project", 28 | "type": "process", 29 | "command": "make", 30 | "args": [ 31 | "compile" 32 | ], 33 | "group": { 34 | "kind": "build", 35 | "isDefault": true 36 | }, 37 | "presentation": { 38 | "reveal": "silent", 39 | "clear": true 40 | }, 41 | "problemMatcher": [ 42 | "$tsc" 43 | ] 44 | }, 45 | { 46 | "label": "make:watch/compile", 47 | "detail": "Start TypeScript compiler in watch mode", 48 | "type": "process", 49 | "command": "make", 50 | "args": [ 51 | "watch/compile" 52 | ], 53 | "problemMatcher": [ 54 | "$tsc-watch", 55 | ], 56 | "group": "build", 57 | "isBackground": true, 58 | "runOptions": { 59 | "instanceLimit": 1, 60 | "runOn": "folderOpen" 61 | }, 62 | "presentation": { 63 | "echo": false, 64 | "panel": "dedicated", 65 | "showReuseMessage": false, 66 | "clear": true 67 | } 68 | }, 69 | { 70 | "label": "make:watch/test", 71 | "detail": "Start Test suite in watch mode", 72 | "type": "process", 73 | "command": "make", 74 | "args": [ 75 | "watch/test" 76 | ], 77 | "problemMatcher": [], 78 | "group": "test", 79 | "isBackground": true, 80 | "runOptions": { 81 | "instanceLimit": 1 82 | }, 83 | "presentation": { 84 | "echo": false, 85 | "panel": "dedicated", 86 | "showReuseMessage": false, 87 | "clear": true 88 | } 89 | } 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.0.0](https://github.com/Dreamscapes/eslint-import-resolver-lerna/compare/1.1.1...2.0.0) (2020-11-02) 2 | 3 | 4 | ### Features 5 | 6 | * rewrite to TypeScript ([d6f15f7](https://github.com/Dreamscapes/eslint-import-resolver-lerna/commit/d6f15f7eff8eab4d0b5fb9739d46bbf021ea75ee)) 7 | 8 | 9 | ### BREAKING CHANGES 10 | 11 | * This package underwent a major rewrite into TypeScript. All of the reported bugs have been fixed. 🎉 Aaand we now have a unit test suite, yay! 🚀 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Robert Rossmann 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-import-resolver-lerna 2 | 3 | [![eslint-import-resolver-lerna][npm-version]][npm-home] 4 | [![Build Status][travis-badge]][travis-home] 5 | ![Built with GNU Make][make-badge] 6 | 7 | This resolver can be used together with [`eslint-plugin-import`][eslint-plugin-import-home] package to help it find modules in your Lerna-based monorepo. 8 | 9 | ## When you might need this 10 | 11 | In general you might not even need this, since Lerna symlinks your monorepo packages into your _node_modules_ directory where the resolver can pick it up using the standard Node.js resolution mechanism. You probably will need this if: 12 | 13 | - You compile your project with Babel, Flow, TypeScript or any other compilation pipeline 14 | - You compile your _packages_ directory into a completely different directory, ie. from _src/packages_ into _dist/packages_ 15 | - You configure Lerna to do link your packages together in the _dist/packages_ directory 16 | 17 | This will cause your _src/packages_ directory to **not have** _node_modules_ folder, thus causing the plugin to be unable to find your other packages using the standard built-in Node.js module resolution mechanism. That's where this resolver will help you. 18 | 19 | ## What this does 20 | 21 | This plugin will look in your _packages_ directory and generate a list of all the package names (as defined in their _package.json_ files) that the monorepo contains. Then, when the import plugin tries to resolve any of those names it will be able to help the import plugin to locate the package. 22 | 23 | ## Usage 24 | 25 | This resolver accepts only one configuration option: `packages` (string or array of strings, required) which must be an absolute path to Lerna's _packages_ directory or an array of such absolute paths. 26 | 27 | ```js 28 | // .eslintrc.js 29 | const path = require('path') 30 | 31 | module.exports = { 32 | settings: { 33 | 'import/resolver': { 34 | 'eslint-import-resolver-lerna': { 35 | packages: path.resolve(__dirname, 'src/packages') 36 | } 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | ## LICENSE 43 | 44 | See the [LICENSE](LICENSE) file for information. 45 | 46 | [eslint-plugin-import-home]: https://github.com/benmosher/eslint-plugin-import 47 | [npm-home]: https://www.npmjs.com/package/eslint-import-resolver-lerna 48 | [npm-version]: https://img.shields.io/npm/v/eslint-import-resolver-lerna.svg?style=flat-square 49 | [travis-badge]: https://img.shields.io/travis/Dreamscapes/eslint-import-resolver-lerna.svg?style=flat-square 50 | [travis-home]: https://travis-ci.org/Dreamscapes/eslint-import-resolver-lerna 51 | [make-badge]: https://img.shields.io/badge/Built%20with-GNU%20Make-brightgreen.svg?style=flat-square 52 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Defining shell is necessary in order to modify PATH 2 | SHELL := sh 3 | export PATH := node_modules/.bin/:$(PATH) 4 | export NODE_OPTIONS := --trace-deprecation 5 | 6 | # On CI servers, use the `npm ci` installer to avoid introducing changes to the package-lock.json 7 | # On developer machines, prefer the generally more flexible `npm install`. 💪 8 | NPM_I := $(if $(CI), ci, install) 9 | 10 | # Modify these variables in local.mk to add flags to the commands, ie. 11 | # FLAGS_MOCHA += --reporter nyan 12 | # Now mocha will be invoked with the extra flag and will show a nice nyan cat as progress bar 🎉 13 | FLAGS_MOCHA := 14 | FLAGS_NPM := 15 | FLAGS_TSC := 16 | FLAGS_ESLINT := --ext js,ts --cache --cache-location .buildstate/.eslintcache 17 | 18 | SRCFILES = $(shell utils/make/projectfiles.sh ts) 19 | DSTFILES = $(patsubst %.ts, %.js, $(SRCFILES)) 20 | GITFILES = $(patsubst utils/githooks/%, .git/hooks/%, $(wildcard utils/githooks/*)) 21 | TSTFILES = "{src,test}/**/*.test.js" 22 | 23 | # Do this when make is invoked without targets 24 | all: compile $(GITFILES) 25 | 26 | 27 | # GENERIC TARGETS 28 | 29 | .buildstate: 30 | mkdir .buildstate 31 | 32 | .buildstate/compile.make: node_modules tsconfig.json $(SRCFILES) .buildstate 33 | tsc $(FLAGS_TSC) && touch $@ 34 | 35 | node_modules: package.json 36 | npm $(NPM_I) $(FLAGS_NPM) && touch $@ 37 | 38 | # Default target for all possible git hooks 39 | .git/hooks/%: utils/githooks/% 40 | cp $< $@ 41 | 42 | coverage/lcov.info: compile 43 | nyc mocha $(FLAGS_MOCHA) $(TSTFILES) 44 | 45 | 46 | # TASK DEFINITIONS 47 | 48 | compile: .buildstate/compile.make 49 | 50 | coverage: coverage/lcov.info 51 | 52 | install: node_modules $(GITFILES) 53 | 54 | lint: force install 55 | eslint $(FLAGS_ESLINT) . 56 | remark --quiet . 57 | 58 | test: force compile 59 | mocha $(FLAGS_MOCHA) $(TSTFILES) 60 | 61 | inspect: force compile 62 | mocha --inspect --inspect-brk $(FLAGS_MOCHA) $(TSTFILES) 63 | 64 | watch/compile: force install 65 | tsc $(FLAGS_TSC) --watch 66 | 67 | watch/test: force install 68 | mocha $(FLAGS_MOCHA) --watch "{src,test}/**/*.js" $(TSTFILES) 69 | 70 | clean: 71 | rm -rf .nyc_output coverage docs .eslintcache 72 | find . -not -path '*/node_modules/*' -name '*.log' -print -delete 73 | 74 | clean/compile: clean 75 | rm -rf .buildstate 76 | git clean -Xf src test 77 | 78 | clean/all: clean/compile 79 | rm -rf node_modules 80 | 81 | release/latest: 82 | utils/make/release.sh release/latest 83 | 84 | release/next: 85 | utils/make/release.sh release/next 86 | 87 | .PHONY: force 88 | 89 | -include local.mk 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-import-resolver-lerna", 3 | "description": "Resolver for Lerna-based projects for eslint-plugin-import", 4 | "version": "2.0.0", 5 | "author": "Robert Rossmann ", 6 | "bugs": "https://github.com/Dreamscapes/eslint-import-resolver-lerna/issues", 7 | "contributors": [], 8 | "dependencies": {}, 9 | "devDependencies": { 10 | "@commitlint/cli": "^11.0.0", 11 | "@semantic-release/changelog": "^5.0.1", 12 | "@semantic-release/git": "^9.0.0", 13 | "@strv/commitlint-config": "^2.0.0-alpha.0", 14 | "@strv/eslint-config-mocha": "^3.0.0-alpha.2", 15 | "@strv/eslint-config-node": "^3.0.0-alpha.3", 16 | "@strv/eslint-config-typescript": "^3.0.0-alpha.4", 17 | "@strv/remark-config": "^1.0.2", 18 | "@types/chai": "^4.2.14", 19 | "@types/mocha": "^8.0.3", 20 | "@types/node": "^14.14.6", 21 | "chai": "^4.2.0", 22 | "eslint": "^7.12.1", 23 | "mocha": "^8.2.0", 24 | "remark-cli": "^9.0.0", 25 | "semantic-release": "^17.2.2", 26 | "source-map-support": "^0.5.19", 27 | "typescript": "^4.0.5" 28 | }, 29 | "engines": { 30 | "node": ">=12" 31 | }, 32 | "homepage": "https://www.npmjs.com/package/eslint-import-resolver-lerna", 33 | "keywords": [ 34 | "eslint-plugin-import", 35 | "lerna", 36 | "resolver" 37 | ], 38 | "license": "BSD-3-Clause", 39 | "main": "src", 40 | "publishConfig": { 41 | "access": "public" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git://github.com/Dreamscapes/eslint-import-resolver-lerna.git" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | // eslint-disable-next-line no-template-curly-in-string 5 | tagFormat: '${version}', 6 | branches: [ 7 | { name: 'release/latest', channel: 'latest' }, 8 | { name: 'release/next', channel: 'next', prerelease: 'alpha' }, 9 | ], 10 | 11 | plugins: [ 12 | '@semantic-release/commit-analyzer', 13 | '@semantic-release/release-notes-generator', 14 | '@semantic-release/changelog', 15 | ['@semantic-release/npm', { 16 | npmPublish: true, 17 | tarballDir: '.', 18 | }], 19 | '@semantic-release/git', 20 | ['@semantic-release/github', { 21 | assets: [{ path: '*.tgz', label: 'eslint-import-resolver-lerna.tgz' }], 22 | }], 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /src/ErrnoException.ts: -------------------------------------------------------------------------------- 1 | /** Copied over from @types/node because it's not possible to import this type directly 🤦‍♂️ */ 2 | interface ErrnoException extends Error { 3 | errno?: number 4 | code?: string 5 | path?: string 6 | syscall?: string 7 | stack?: string 8 | } 9 | 10 | export { 11 | ErrnoException, 12 | } 13 | -------------------------------------------------------------------------------- /src/cjsresolve.ts: -------------------------------------------------------------------------------- 1 | import type { ErrnoException } from './ErrnoException' 2 | 3 | function isErrnoException(err: unknown): err is ErrnoException { 4 | return Object.keys(err as Record).includes('code') 5 | } 6 | 7 | function cjsresolve(filepath: string): string | null { 8 | try { 9 | return require.resolve(filepath) 10 | } catch (err: unknown) { 11 | if (isErrnoException(err) && err.code === 'MODULE_NOT_FOUND') { 12 | return null 13 | } 14 | 15 | throw err 16 | } 17 | } 18 | 19 | export { 20 | cjsresolve, 21 | } 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from './resolve' 2 | 3 | /** 4 | * Resolver interface version 5 | * 6 | * @see https://github.com/benmosher/eslint-plugin-import/tree/master/resolvers 7 | */ 8 | const interfaceVersion = 2 9 | 10 | export { 11 | interfaceVersion, 12 | resolve, 13 | } 14 | -------------------------------------------------------------------------------- /src/pkgparts.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import { pkgparts } from './pkgparts' 4 | 5 | type Expectation = [basename: string, relpath: string | null] 6 | const expectations: Array<[input: string, expectation: Expectation]> = [ 7 | ['pkg', ['pkg', null]], 8 | ['pkg/file', ['pkg', 'file']], 9 | ['pkg/file.js', ['pkg', 'file.js']], 10 | ['pkg/lib/file', ['pkg', 'lib/file']], 11 | ['pkg/lib/file.js', ['pkg', 'lib/file.js']], 12 | ['@scope/pkg', ['@scope/pkg', null]], 13 | ['@scope/pkg/file', ['@scope/pkg', 'file']], 14 | ['@scope/pkg/file.js', ['@scope/pkg', 'file.js']], 15 | ['@scope/pkg/lib/file', ['@scope/pkg', 'lib/file']], 16 | ['@scope/pkg/lib/file.js', ['@scope/pkg', 'lib/file.js']], 17 | ] 18 | 19 | describe('pgkparts()', () => { 20 | it('exists', () => { 21 | expect(pkgparts).to.be.a('function') 22 | }) 23 | 24 | it('returns an object with `basename` and `relpath`', () => { 25 | const parts = pkgparts('@scope/pkg/lib/file') 26 | 27 | expect(parts).to.be.an('object') 28 | expect(parts).to.have.all.keys(['basename', 'relpath']) 29 | }) 30 | 31 | 32 | for (const [input, [basename, relpath]] of expectations) { 33 | it(`splits: ${input}`, () => { 34 | const parts = pkgparts(input) 35 | 36 | expect(parts).to.be.an('object') 37 | expect(parts.basename).to.equal(basename) 38 | expect(parts.relpath).to.equal(relpath) 39 | }) 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /src/pkgparts.ts: -------------------------------------------------------------------------------- 1 | interface PkgParts { 2 | /** Package name only, excluding any nested paths in the import path */ 3 | basename: string 4 | /** Relative path within the package, if provided in the original import path */ 5 | relpath: string | null 6 | } 7 | 8 | /** 9 | * Get the base name of a package 10 | * 11 | * Given an import path of `@scope/pkgname/nested-module`, this function will return the base name 12 | * of the package, `@scope/pkgname`. For unscoped packages, this would return just the name. 13 | * 14 | * @private 15 | * @param name - The name of the package 16 | */ 17 | function pkgparts(name: string): PkgParts { 18 | const parts = name.split('/') 19 | const isScoped = name.startsWith('@') 20 | const basename = isScoped 21 | // Scoped package name - pick first two parts (the scope and the package name) 22 | ? parts.slice(0, 2).join('/') 23 | // Regular package name - pick just the first part (the package name) 24 | : parts[0] 25 | const relpath = isScoped 26 | // Scoped package name - pick everything after the package scope and name identifier 27 | ? parts.slice(2).join('/') || null 28 | // Regular package name - pick everything after the package name identifier 29 | : parts.slice(1).join('/') || null 30 | 31 | return { 32 | basename, 33 | relpath, 34 | } 35 | } 36 | 37 | export { 38 | pkgparts, 39 | PkgParts, 40 | } 41 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/main-pkg/file.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/main-pkg/main.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/main-pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main-pkg", 3 | "main": "main.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/pkg/file.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/pkg/index.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg" 3 | } 4 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/scope-pkg/file.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/scope-pkg/index.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /src/resolve/.fixtures/packages/scope-pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scope/pkg" 3 | } 4 | -------------------------------------------------------------------------------- /src/resolve/Config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Resolver configuration 3 | */ 4 | interface Config { 5 | /** 6 | * Either a single absolute path or an array thereof pointing to where Lerna packages might be 7 | * located in your source code. This plugin will look for package references there. 8 | */ 9 | packages?: string | string[] 10 | } 11 | 12 | export { 13 | Config, 14 | } 15 | -------------------------------------------------------------------------------- /src/resolve/Outcome.ts: -------------------------------------------------------------------------------- 1 | /** Outcome of the package resolution attempt */ 2 | interface Outcome { 3 | /** Did we find the package we are looking for? */ 4 | found: boolean 5 | /** If we found it, this will be an absolute path to where that package is located */ 6 | path: string | null 7 | } 8 | 9 | export { 10 | Outcome, 11 | } 12 | -------------------------------------------------------------------------------- /src/resolve/index.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from './resolve' 2 | import { Outcome } from './Outcome' 3 | import { Config } from './Config' 4 | 5 | export { 6 | resolve, 7 | Outcome, 8 | Config, 9 | } 10 | -------------------------------------------------------------------------------- /src/resolve/resolve.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import { describe, it } from 'mocha' 3 | import { expect } from 'chai' 4 | import { resolve } from '.' 5 | 6 | const fakeroot = path.resolve(__dirname, '.fixtures/packages') 7 | const expectations: Array<[importpath: string, result: string]> = [ 8 | ['pkg', path.resolve(fakeroot, 'pkg/index.js')], 9 | ['pkg/file', path.resolve(fakeroot, 'pkg/file.js')], 10 | ['pkg/file.js', path.resolve(fakeroot, 'pkg/file.js')], 11 | ['@scope/pkg', path.resolve(fakeroot, 'scope-pkg/index.js')], 12 | ['@scope/pkg/file', path.resolve(fakeroot, 'scope-pkg/file.js')], 13 | ['@scope/pkg/file.js', path.resolve(fakeroot, 'scope-pkg/file.js')], 14 | ['main-pkg', path.resolve(fakeroot, 'main-pkg/main.js')], 15 | ['main-pkg/file', path.resolve(fakeroot, 'main-pkg/file.js')], 16 | ] 17 | 18 | describe('resolve()', () => { 19 | it('exists', () => { 20 | expect(resolve).to.be.a('function') 21 | }) 22 | 23 | for (const [importpath, expectation] of expectations) { 24 | it(`resolves: ${importpath}`, () => { 25 | const outcome = resolve(importpath, __filename, { packages: fakeroot }) 26 | 27 | expect(outcome.found).to.equal(Boolean(expectation)) 28 | expect(outcome.path).to.equal(path.resolve(fakeroot, expectation)) 29 | }) 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /src/resolve/resolve.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable node/no-sync */ 2 | import * as fs from 'fs' 3 | import * as path from 'path' 4 | import { pkgparts } from '../pkgparts' 5 | import { cjsresolve } from '../cjsresolve' 6 | import type { Config } from './Config' 7 | import type { Outcome } from './Outcome' 8 | 9 | const outcome = { 10 | notFound: (): Outcome => ({ found: false, path: null }), 11 | found: (location: string): Outcome => ({ found: true, path: location }), 12 | } 13 | 14 | function jsonfile(source: string): Record { 15 | const contents = fs.readFileSync(source, 'utf-8').trim() 16 | const json = JSON.parse(contents) as Record 17 | 18 | return json 19 | } 20 | 21 | /** 22 | * Resolve a given import path into a Lerna package 23 | * 24 | * @param identifier - A potential package name or path to be resolved into absolute path 25 | * @param importer - The absolute path to the module making the import 26 | * @param config - Configuration options for the resolver, as provided via ESLint config file 27 | * @returns The outcome of the module resolution attempt, including the module's path 28 | */ 29 | function resolve(identifier: string, importer: string, config?: Config): Outcome { 30 | const { basename, relpath } = pkgparts(identifier) 31 | const packages = config?.packages ?? [] 32 | const roots = Array.isArray(packages) 33 | ? packages 34 | : [packages] 35 | 36 | for (const root of roots) { 37 | const filenames = fs 38 | .readdirSync(root) 39 | .map(filename => path.resolve(root, filename)) 40 | .filter(filename => fs.statSync(filename).isDirectory()) 41 | .filter(filename => fs.existsSync(path.resolve(filename, 'package.json'))) 42 | 43 | for (const filename of filenames) { 44 | const pkg = jsonfile(path.resolve(filename, 'package.json')) 45 | 46 | if (pkg.name === basename) { 47 | const resolved = cjsresolve(path.resolve(filename, relpath ?? '')) 48 | 49 | return resolved 50 | ? outcome.found(resolved) 51 | : outcome.notFound() 52 | } 53 | } 54 | } 55 | 56 | return outcome.notFound() 57 | } 58 | 59 | export { 60 | resolve, 61 | } 62 | -------------------------------------------------------------------------------- /test/bootstrap.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line node/no-process-env 2 | process.env.NODE_ENV = 'test' 3 | -------------------------------------------------------------------------------- /test/globalhooks.test.ts: -------------------------------------------------------------------------------- 1 | import { before } from 'mocha' 2 | 3 | before(() => { 4 | // If we are running Mocha in watch mode, clear the Console before each test run so we have only 5 | // one report on screen at any time. This is how the `min` reporter works by default, but we 6 | // actually do want to see the full test suite log. 7 | if (process.argv.includes('--watch')) { 8 | process.stdout.write('\u001Bc') 9 | // eslint-disable-next-line no-console 10 | console.log('Terminal screen cleared in global mocha:before()\n') 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /test/package.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import * as pkg from '..' 4 | 5 | describe('package', () => { 6 | it('exists', () => { 7 | expect(pkg).to.be.an('object') 8 | }) 9 | 10 | it('exports expected members', () => { 11 | expect(pkg).to.have.all.keys([ 12 | 'interfaceVersion', 13 | 'resolve', 14 | ]) 15 | }) 16 | 17 | it('sets interfaceVersion', () => { 18 | expect(pkg.interfaceVersion).to.equal(2) 19 | }) 20 | 21 | it('exports .resolve() function', () => { 22 | expect(pkg.resolve).to.be.a('function') 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "incremental": true, 9 | "inlineSourceMap": true, 10 | "inlineSources": true, 11 | "module": "commonjs", 12 | "noEmitOnError": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noImplicitAny": true, 15 | "noImplicitReturns": true, 16 | "noImplicitThis": true, 17 | "noUnusedLocals": false, 18 | "noUnusedParameters": false, 19 | "strict": true, 20 | "target": "ES2019", 21 | "tsBuildInfoFile": ".buildstate/tsconfig.tsbuildinfo" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /utils/ci/sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o errexit 4 | 5 | head=$(git rev-parse --abbrev-ref HEAD) 6 | 7 | printf "\n=====>\tFetching new commits...\n" 8 | 9 | # Ensure we are merging the release into current branch tips to avoid pushes being rejected 10 | git fetch origin 11 | 12 | printf "\n=====>\tMerging to master: %s...\n" "${head}" 13 | 14 | git checkout master 15 | git merge "${head}" 16 | 17 | # Print the repo status after merging, for troubleshooting purposes 18 | git status 19 | git log --oneline HEAD~10..HEAD 20 | 21 | printf "\n=====>\tPushing...\n" 22 | 23 | git push origin master 24 | -------------------------------------------------------------------------------- /utils/githooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Validate commit message to adhere to conventional-commits standard 4 | # This is necessary to facilitate automated package publishing. 5 | 6 | # This utility's configuration resides in .commitlintrc.js file. 7 | ./node_modules/.bin/commitlint < "$1" 8 | -------------------------------------------------------------------------------- /utils/make/projectfiles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | EXT=$1 4 | 5 | find src test \ 6 | -name "*.${EXT}" \ 7 | -not -path '*/.*' \ 8 | -not -name '.*.js' \ 9 | -not -name '*.config.js' \ 10 | -not -name "*.d.ts" 11 | -------------------------------------------------------------------------------- /utils/make/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | branch="$1" 4 | 5 | set -o errexit 6 | 7 | head=$(git rev-parse --abbrev-ref HEAD) 8 | 9 | printf "\n=====>\tChecking out release branch: %s...\n" "${branch}" 10 | 11 | git checkout -B "${branch}" 12 | 13 | printf "\n=====>\tMerging %s...\n" "${head}" 14 | 15 | git merge --message "chore: release" "${head}" 16 | 17 | printf "\n=====>\tPushing...\n" 18 | 19 | git push --set-upstream origin "${branch}" 20 | 21 | printf "\n=====>\tSwitching back to previous branch: %s...\n" "${head}" 22 | 23 | git checkout "${head}" 24 | 25 | printf "\n=====>\tRelease in progress: https://github.com/strvcom/heimdall/actions\n" 26 | --------------------------------------------------------------------------------