Skip to content

Instantly share code, notes, and snippets.

@jzaefferer
Created February 18, 2021 15:20
Show Gist options
  • Save jzaefferer/39bd074b5a448cace1e3fe9f7c57e2b4 to your computer and use it in GitHub Desktop.
Save jzaefferer/39bd074b5a448cace1e3fe9f7c57e2b4 to your computer and use it in GitHub Desktop.
Automatically check if installed packages are up-to-date, before start and lint scripts, and on `git pull`; MIT License, Copyright Jörn Zaefferer
#!/usr/bin/env node
//@ts-check
const { execSync } = require('child_process')
const mainPackage = require('../package.json')
const packageLock = require('../package-lock.json')
const outdated = []
Object.keys(mainPackage.dependencies)
.concat(Object.keys(mainPackage.devDependencies))
.forEach(packageName => {
try {
const installedPackage = require(`${packageName}/package.json`)
const installedVersion = installedPackage.version
const desiredVersion = packageLock.dependencies[packageName].version
if (installedVersion !== desiredVersion) {
outdated.push(
`${packageName}:, installed ${installedVersion}, desired ${desiredVersion}`
)
}
} catch (error) {
if (error.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
console.error(error)
process.exit(1)
}
}
})
if (outdated.length > 0) {
console.log(`some package(s) are outdated, updating`)
console.log(outdated.join('\n'))
execSync(`npm i --registry=https://registry.npmjs.org/`)
}
{
"other": "stuff,
"scripts": {
"prelint": "node scripts/check-packages.js",
"prestart": "node scripts/check-packages.js"
},
"husky": {
"hooks": {
"post-merge": "node scripts/check-packages.js"
}
},
}
@manuschillerdev
Copy link

interesting! 💯
I experimented with an alternative approach for yarn.

downside: it needs @yarnpkg/lockfile as its own dependency to parse yarn.lock
upside: it only needs to read two files, in comparison of 2 + n in your approach, if I understood it correctly (package.json, package-lock.json, package.json of every dependency.)

It is pretty fast in my case.

Benchmark: run the snipped on a default create-next-app setup (yarn create next-app yarn-check)

$ time node yarn-check.js
node yarn-check.js  0.08s user 0.02s system 130% cpu 0.078 total
const { parse } = require("@yarnpkg/lockfile");
const { readFileSync } = require("fs");

const yarnLock = readFileSync("./yarn.lock", "utf8");
const { object: installedDeps } = parse(yarnLock);

const pkg = require("./package.json");
const desiredDeps = {
  ...(pkg.dependencies || {}),
  ...(pkg.devDependencies || {}),
};

for (const [name, version] of Object.entries(desiredDeps)) {
  if (!installedDeps[`${name}@${version}`]) {
    console.log("found uninstalled dependencies, starting yarn install...");
    require("child_process").execSync("yarn");
    break;
  }
}

@jzaefferer
Copy link
Author

This sounds promising, but I'm not sure if it's really equivalent. Does yarn.lock really know what's currently installed? Or only what's supposed to be installed? The latter applies to package-lock.json, and that's the reason for checking every individual dependency.

@manuschillerdev
Copy link

manuschillerdev commented Feb 20, 2021

ah, I see - thanks! I misunderstood the use case.
so basically for yarn it's the same as you and your team did:

const { parse } = require("@yarnpkg/lockfile");
const { readFileSync } = require("fs");
const { execSync } = require("child_process");

const yarnLock = readFileSync("./yarn.lock", "utf8");
const { object: lockedDependencies } = parse(yarnLock);

const pkg = require("./package.json");
const dependencies = {
  ...(pkg.dependencies || {}),
  ...(pkg.devDependencies || {}),
};

const install = () => execSync("yarn");

for (const [name, version] of Object.entries(dependencies)) {
  try {
    const installed = require(`${name}/package.json`);
    const locked = lockedDependencies[`${name}@${version}`];
    if (!locked || installed.version !== locked.version) {
      install();
      break;
    }
  } catch (e) {
    if (e.code === "MODULE_NOT_FOUND") install();
    else throw e;
  }
}

Would be cool if we turned this into a module that supports npm, yarn and pnpm probably!

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