Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monorepo with project-level lint-staged config not running commands with correct cwd #1194

Closed
bbugh opened this issue Aug 6, 2022 · 8 comments

Comments

@bbugh
Copy link

bbugh commented Aug 6, 2022

Description

For us monorepo-ers, #1106 was good progress and allows for the .lintstagedrc to be in the project folders of a monorepo instead of the root. #1091 also added running commands in the cwd of the configuration, but that doesn't seem to be working.

Commands still seem to run in the root folder instead of using the .lintstagedrc folder as the working directory. For monorepos that aren't all javascript, there is also no top-level workspace command and they aren't going to have package.json.

Steps to reproduce

Example repo: https://github.com/bbugh/lint-staged-example

  1. Download the repo
  2. yarn install
  3. cd api
  4. bundle install
  5. Modify Gemfile with some comments or something
  6. git add Gemfile
  7. cd ..
  8. yarn lint-staged
  9. Observe the issue shown in the logs

Debug Logs

expand to view

For example api is a Rails-only project with no package.json:

~/projects/app » yarn lint-staged

Error: ENOENT: no such file or directory, open '/Users/bbugh/projects/app/api/package.json'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/Users/bbugh/projects/app/api/package.json'
}
❯ Running tasks for staged files...
  ❯ api/.lintstagedrc — 2 files # ✅ using correct config file
    ❯ ** — 2 files
      ✖ bundle exec rubocop --format quiet [FAILED]
✔ Reverting to original state because of errors...
✔ Cleaning up temporary files...

✖ bundle exec rubocop --format quiet:
Could not locate Gemfile or .bundle/ directory

This error happens because the command is not running in the working directory in the config folder location, as it seems like it should from reading #1091.

  lint-staged:makeCmdTasks Creating listr tasks for commands 'bundle exec rubocop --format quiet' +0ms
  lint-staged:resolveTaskFn cmd: bundle +0ms
  lint-staged:resolveTaskFn args: [ 'exec', 'rubocop', '--format', 'quiet' ] +0ms
  lint-staged:resolveTaskFn execaOptions: {
  cwd: '/Users/bbugh/projects/app', # ❌ cwd is wrong, should be "/Users/bbugh/projects/app/api"
  preferLocal: true,
  reject: false,
  shell: false
}

Environment

  • OS: macOS 12.4
  • Node.js: v18.2.0
  • lint-staged: 13.0.3
@iiroj
Copy link
Member

iiroj commented Aug 7, 2022

Hello,

if you don't mind doing some manual debugging, it's probably around here:

https://github.com/okonet/lint-staged/blob/3f3e1523b284056c70b90d3f0ec350b5dce60a50/lib/runAll.js#L166-L169

The configPath should be app/api, and cwd should be app, I assume. Maybe it's because it's a "monorepo" but with only a single config file? Adding an empty config in the root would fix it, even if it might not be the cleanest way.

@bbugh
Copy link
Author

bbugh commented Aug 8, 2022

Oof, yep, that was the problem, there was only one. Thanks for the reply. I had created a client .lintstagedrc in a different clone of that repo, and I didn't notice in VSCode. Sorry! Hopefully this will help someone else. 😅

@bbugh bbugh closed this as completed Aug 8, 2022
@iiroj
Copy link
Member

iiroj commented Aug 8, 2022

The behavior is not perfectly intuitive, but "fixing" it would be a breaking change for single-config scenarios where the CWD is not in the config dir.

@strazto
Copy link

strazto commented Aug 19, 2022

I'm stumbling on a similar use-case -
I have a monorepo where, in general, I'd like to use whatever linter a given subpackage has, defaulting to eslint.

Unfortunately, eslint really doesn't like it when you have a monorepo with eslint at the top level, and subpackages with their own eslint.

There's not an "easy" configuration option to tell lint staged to try to run whatever it runs from the context of whatever subpackage the matched file is under.

This being said, I am quite sure that I can write a function-based config that does the following:

// lint-staged.config.mjs
import path from 'path';
// npm install --save-dev pkg-up
import { pkgUpSync } from 'pkg-up';

export default {
 '*.md' : 'prettier',
 '*.{js,ts}' : (filenames /*: string[]*/) => {
      const root = process.cwd();

      var srcByPkg = {};
      var pkgPrefixes = [];

      filenames.forEach(file => {
         const pkgPath = pkgUpSync({ cwd: path.dirname(file) });
         if (! pkgPath ) return;

         const pkgPrefix = path.dirname(pkgPath);

         if (! srcByPkg[pkgPrefix] ) {
            srcByPkg[pkgPrefix] = [];

            pkgPrefixes = pkgPrefixes.concat([pkgPrefix]);
         }

         srcByPkg[pkgPrefix] = srcByPkg[pkgPrefix].concat([file]);
      });

      return pkgPrefixes.map(pkgPrefix => {
         const toLint = srcByPkg[pkgPrefix];

         return `npm --prefix ${pkgPrefix} exec -- eslint --max-warnings 0 ${toLint.join(' ')}`;
      });
   },
}

I'm hoping this is good enough to run whatever eslint should run within whatever package

@iiroj
Copy link
Member

iiroj commented Aug 19, 2022

@matthewstrasiotto Might the root option of ESLint config help? If you used that setting in all your sub-package ESLint files, they should be treated as separate projects and ESLint try to use the parent config:

ESLint will automatically look for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem (/), the home directory of the current user (~/), or when root: true is specified

https://eslint.org/docs/latest/user-guide/configuring/configuration-files#using-configuration-files

@strazto
Copy link

strazto commented Aug 19, 2022

@matthewstrasiotto Might the root option of ESLint config help? If you used that setting in all your sub-package ESLint files, they should be treated as separate projects and ESLint try to use the parent config:

I think you're right, but in adding some devX tools, I'd rather not force that change for every project in my monorepo.

@Tri125
Copy link

Tri125 commented Jul 15, 2023

Hello,

if you don't mind doing some manual debugging, it's probably around here:

https://github.com/okonet/lint-staged/blob/3f3e1523b284056c70b90d3f0ec350b5dce60a50/lib/runAll.js#L166-L169

The configPath should be app/api, and cwd should be app, I assume. Maybe it's because it's a "monorepo" but with only a single config file? Adding an empty config in the root would fix it, even if it might not be the cleanest way.

What exactly is an empty config file and can this be documented?

If I create an empty .lintstagedrc.json on the root of my monorepo, the file isn't loaded and when my /app/.lintstagedrc.json is executed the CWD is still on the root.

If I modify the .lintstagedrc.json to an empty object

{}

Then lint-staged fail to start and it doesn't print an error.

If I actually put a pattern

{
  "!**": ""
}

Then /app/.lintstagedrc.json will be executed with the correct CWD.

@atsixian
Copy link

I have the same problem as @Tri125, and adding this to root .lintstagedrc works.

{
  "!**": ""
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants