yarnpkg / berry

πŸ“¦πŸˆ Active development trunk for Yarn βš’
https://yarnpkg.com
BSD 2-Clause "Simplified" License
7.23k stars 1.07k forks source link

[Bug?]: package.json is needlessly formatted during install when no changes are required #6298

Closed Tatsh closed 1 month ago

Tatsh commented 1 month ago

Self-service

Describe the bug

package.json is formatted unexpectedly when there are no changes to be applied to the file during yarn install. Yes --immutable does stop the formatting but sometimes I want to get the changes and then re-format package.json after. install should not format anything unless the file content is actually changed.

To reproduce

mkdir new
cd new
yarn init
yarn add -D cspell eslint prettier ts-node typescript

Add the following to package.json:

  "eslint": {
    "rules": {
      "@typescript-eslint/no-magic-numbers": [
        "warn",
        {
          "enforceConst": true,
          "ignore": [
            -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 25, 60, 100, 128, 200, 201, 204, 256, 301,
            302, 400, 404, 500, 1000, 3000
          ],
          "ignoreEnums": true,
          "ignoreNumericLiteralTypes": true,
          "ignoreReadonlyClassProperties": true
        }
      ]}
yarn install

package.json at path .eslint.rules["@typescript-eslint/no-magic-numbers"][1].ignore is now formatted with one number per line in the list.

Environment

System:
    OS: Linux 6.8 Gentoo Linux
    CPU: (12) x64 12th Gen Intel(R) Core(TM) i7-1265U
  Binaries:
    Node: 22.1.0 - /tmp/xfs-63083062/node
    Yarn: 4.2.2 - /tmp/xfs-63083062/yarn
    npm: 10.2.5 - ~/.local/share/npm-global/bin/npm

Additional context

No response

arcanis commented 1 month ago

That's not a bug, and that's not something I think we should add complexity to support.

Tatsh commented 1 month ago

As a workaround, I made a simple plugin to run prettier after all installed:

import { cwd as cwdFunc, stdout, stdin, stderr } from 'process';
import { Configuration, Hooks, Project, scriptUtils } from '@yarnpkg/core';
import { getPluginConfiguration } from '@yarnpkg/cli';
import { PortablePath } from '@yarnpkg/fslib';

// The documentation says to use `const plugin: Plugin = {...}; export.default = plugin` but this
// does not work. Explicit `module.exports` does.
module.exports = {
  factory: (): { hooks: Hooks } => ({
    hooks: {
      afterAllInstalled(project: Project): void {
        void (async () => {
          const cwd = cwdFunc() as PortablePath;
          const configuration = await Configuration.find(cwd, getPluginConfiguration());
          const { locator } = await Project.find(configuration, cwd);
          const packageAccessibleBinaries = await scriptUtils.getPackageAccessibleBinaries(
            locator,
            {
              project,
            },
          );
          if (!packageAccessibleBinaries.get('prettier')) {
            throw new Error('Prettier not found.');
          }
          const ret = await scriptUtils.executePackageAccessibleBinary(
            locator,
            'prettier',
            ['--log-level', 'error', '-w', 'package.json', '.yarnrc.yml'],
            { cwd, packageAccessibleBinaries, project, stderr, stdin, stdout },
          );
          if (ret !== 0) {
            throw new Error(`Prettier returned non-zero: {ret}.`);
          }
        })();
      },
    },
  }),
  name: 'plugin-prettier-after-all-installed',
};

The reason for this is because I have a hook in pre-commit to check that yarn.lock is valid against package.json. The formatting done by Yarn leads to a false positive.

repos:
  - hooks:
      - entry: yarn install --check-cache --immutable
        files: ^package\.json$
        id: yarn-check-lock
        language: system
        name: check yarn.lock is up-to-date
        pass_filenames: false