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

Add outdated lockfile check #438

Open
more-no opened this issue Sep 13, 2023 · 4 comments
Open

Add outdated lockfile check #438

more-no opened this issue Sep 13, 2023 · 4 comments
Assignees

Comments

@more-no
Copy link

more-no commented Sep 13, 2023

When I tried to deploy on Netlify this is what happened:

7:52:49 PM: Installing npm packages using pnpm version 8.3.1
7:52:49 PM: Lockfile is up to date, resolution step is skipped
7:52:49 PM:  ERR_PNPM_OUTDATED_LOCKFILE  Cannot install with frozen-lockfile because pnpm-lock.yaml is not up to date with package.json
7:52:49 PM: Note that in CI environments this setting is true by default. If you still need to run install in such cases, use pnpm install --no-frozen-lockfile
7:52:49 PM: Error during pnpm install
7:52:49 PM: Build was terminated: dependency_installation script returned non-zero exit code: 1
7:52:50 PM: Failing build: Failed to install dependencies

It wasn' t expecting it because Preflight didn't gave any error and it is supposed to be checking also dependency problems.

So it would be nice if in the future Preflight were able to also find the error that Netlify chatched.

BTW my problem was easily solved with:
pnpm install --no-frozen-lockfile

After that I was able to deploy.

@karlhorky karlhorky changed the title When trying to deploy Netlify found problems also when Preflight did not. Add outdated lockfile check Sep 13, 2023
@karlhorky
Copy link
Member

@Josehower mentioned today that there were some checks that Preflight already does which sometimes catch outdated lockfiles

But there are some situations which we still do not catch

  • Let's list out both the things we do catch and the things we don't
  • And then find the least expensive way to check that there's an outdated lockfile (running pnpm install is the most expensive)

@ProchaLu
Copy link
Member

I've reviewed the Preflight code, and it looks like we're not checking anything with the pnpm-lock.yaml file. We do have checks for package.json:

  • No unused dependencies
  • No dependencies without types
  • Use of a single package manager
  • If Next.js projects have the sharp dependency installed

@ProchaLu
Copy link
Member

After some research, I found a package called package-lock-utd that checks outdated package-lock.json files. I was able to extract the code and refactor it to check if the package.json file and pnpm-lock.yaml are matching. I created a script and tested it in a Portfolio project.

The script works as expected:

  • If you add a package to the package.json file and don't run pnpm install, the script will throw the error:
package-lock.yaml is not up to date. Run "pnpm install".
  • If you remove a package from the package.json file and don't run pnpm install, the script will also throw the error:
package-lock.yaml is not up to date. Run "pnpm install".

The downside of this script is that it uses pnpm install, which is what we want to avoid.

import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';

function existsAndIsFileSync(filePath) {
  try {
    const stat = fs.statSync(filePath);
    return stat.isFile();
  } catch (error) {
    return false;
  }
}

function readFileContent(filePath) {
  try {
    const data = fs.readFileSync(filePath, 'utf8');
    return { success: true, data };
  } catch (error) {
    return { success: false };
  }
}

const packageJsonPath = path.join(process.cwd(), 'package.json');
const pnpmLockYamlPath = path.join(process.cwd(), 'pnpm-lock.yaml');
let shouldRestorePackageLock = false;

if (!existsAndIsFileSync(packageJsonPath)) {
  console.log("File 'package.json' is missing");
  process.exit(1);
}

if (!existsAndIsFileSync(pnpmLockYamlPath)) {
  console.log("File 'package-lock.yaml' is missing");
  process.exit(1);
}

const originalPackageLockReadResult = readFileContent(pnpmLockYamlPath);

if (!originalPackageLockReadResult.success) {
  console.log("Failed to read 'package-lock.yaml'");
  process.exit(1);
}

try {
  execSync('pnpm install');
  shouldRestorePackageLock = true;
} catch {
  console.log(
    'Something went wrong generating the updated package-lock.yaml needed for comparison. This could be a problem with pnpm',
  );
  process.exit(1);
} finally {
  if (shouldRestorePackageLock) {
    // Restore the original package-lock.json when the process exits.
    process.on('exit', () => {
      fs.writeFileSync(
        pnpmLockYamlPath,
        originalPackageLockReadResult.data || '',
      );
    });
  }
}

const updatedPackageLockReadResult = readFileContent(pnpmLockYamlPath);

if (!updatedPackageLockReadResult.success) {
  console.log("Failed to read the updated 'package-lock.yaml'");
  process.exit(1);
}

if (originalPackageLockReadResult.data !== updatedPackageLockReadResult.data) {
  console.log('package-lock.yaml is not up to date. Run "pnpm install"');
  process.exit(1);
}

console.log('package-lock.yaml is up to date');

Alternative Solutions

Instead of using pnpm install, maybe we can use a diff tool to compare the contents of the package.json and pnpm-lock.yaml files.

@karlhorky
Copy link
Member

not sure if these pnpm install flags help (also 250ms on my M1, maybe too slow / expensive)

# lockfile needs no updates (exit code 0)
$ pnpm install --lockfile-only --frozen-lockfile
Done in 229ms

# lockfile needs an update (exit code 1)
$ pnpm install --lockfile-only --frozen-lockfile
 ERR_PNPM_OUTDATED_LOCKFILE  Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with package.json

Note that in CI environments this setting is true by default. If you still need to run install in such cases, use "pnpm install --no-frozen-lockfile"

    Failure reason:
    specifiers in the lockfile ({"@types/prettier":"2.7.3","eslint-config-upleveled":"5.0.4","stylelint":"15.10.3","typescript":"5.2.2"}) don't match specs in package.json ({"@babel/eslint-parser":"^7.22.15","@next/eslint-plugin-next":"^13.5.2","@types/eslint":"^8.44.2","@types/node":">=20.6.3","@types/react":"^18.2.22","@types/react-dom":"^18.2.7","@typescript-eslint/eslint-plugin":"^6.7.2","@typescript-eslint/parser":"^6.7.2","eslint":"^8.49.0","eslint-import-resolver-typescript":"^3.6.0","eslint-plugin-import":"^2.28.1","eslint-plugin-jsx-a11y":"^6.7.1","eslint-plugin-jsx-expressions":"^1.3.1","eslint-plugin-react":"^7.33.2","eslint-plugin-react-hooks":"^4.6.0","eslint-plugin-security":"^1.7.1","eslint-plugin-sonarjs":"^0.21.0","eslint-plugin-testing-library":"^6.0.1","eslint-plugin-unicorn":"^48.0.1","eslint-plugin-upleveled":"^2.1.9","typescript":"5.2.2","@types/prettier":"2.7.3","eslint-config-upleveled":"5.0.4","stylelint":"15.10.3"})

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

No branches or pull requests

4 participants