Skip to content

Instantly share code, notes, and snippets.

@farkmarnum
Last active July 13, 2022 15:35
Show Gist options
  • Save farkmarnum/6b612b9a011fb5913eeb984c4059a7d0 to your computer and use it in GitHub Desktop.
Save farkmarnum/6b612b9a011fb5913eeb984c4059a7d0 to your computer and use it in GitHub Desktop.
Check Expo project for changes to packages that would necessitate a new binary

About

This script compares two branches, SOURCE_REF and TARGET_REF. It finds all packages in node_modules that contain ios or android directories, and then compares the versions of those packages between the two branches. If there are any differences, it returns with a non-zero code.

This is intended to be used within a CI process that needs to determine whether or not a new binary is needed for an Expo project.

Usage

Locally

SOURCE_REF=source-branch TARGET_REF=target-branch ./checkForNativePackageChanges.js

In GitHub Actions

run: |
  SOURCE_REF=$GITHUB_HEAD_REF TARGET_REF=$GITHUB_BASE_REF ./scripts/checkForNativePackageChanges.js || \
  echo "::set-output name=binary_changes::true"

Notes

You also need to check if app.config.js has changed, but that can be done like this (in GitHub Actions):

- name: Detect if binary files changed
  id: changed-files-specific
  uses: tj-actions/changed-files@v23.1
  with:
    files: |
      app.config.js
#!/usr/bin/env node
const { execSync } = require("child_process");
const { readFileSync } = require("fs");
const { resolve } = require("path");
/* CONSTANTS */
const ROOT_DIR = resolve(__dirname, "../../..");
/* HELPERS */
const exec = (command, options = {}) =>
execSync(
command,
{
encoding: "utf-8",
cwd: ROOT_DIR,
...options
}
);
const getPaths = (name) =>
exec(`find node_modules -type d -name "${name}"`)
.split("\n")
.filter((s) => s.length > 0);
const getPackageNameFromPath = (path) => {
const endOfPath = path.split('node_modules/').slice(-1)[0];
const segments = endOfPath.split("/");
if (segments[0].startsWith("@")) {
return segments.slice(0, 2).join("/");
}
return segments[0];
};
const getPotentiallyNestedPackagePath = (path) => {
const potentiallyNestedPath = path.split(/(node_modules\/)/).slice(0, -1);
const packageName = getPackageNameFromPath(path);
return [...potentiallyNestedPath, packageName].join("");
};
const getNativePackagePaths = () => {
const paths = getPaths("android").concat(getPaths("ios"));
const packagePaths = paths.map((path) =>
getPotentiallyNestedPackagePath(path)
);
return Array.from(new Set(packagePaths)); // Return a unique array
};
const getVersions = (packagePaths) =>
Object.fromEntries(
packagePaths.reduce((acc, path) => {
const packageJsonFilename = resolve(path, "package.json");
const packageJsonContents = readFileSync(packageJsonFilename, { encoding: "utf8" });
const packageInfo = JSON.parse(packageJsonContents);
const { version } = packageInfo;
const packageName = getPackageNameFromPath(path);;
acc.push([packageName, version]);
return acc;
}, []).sort((a, b) => a[0] > b[0] ? 1 : -1) // Sort by package name
);
const checkout = (ref) => {
console.info(`➡️ Switching to ${ref}`);
exec(`git checkout ${ref}`);
};
const cleanup = () => {
console.info("➡️ Removing node_modules/");
exec('rm -rf node_modules');
};
const installProdDeps = () => {
console.info("➡️ Installing production dependencies");
exec("yarn install --production");
};
const getNativePackageVersions = () => {
console.info("➡️ Finding native dependencies");
const nativePackagePaths = getNativePackagePaths();
const packagesWithVersions = getVersions(nativePackagePaths);
return packagesWithVersions;
};
const setup = (ref) => {
checkout(ref);
cleanup();
installProdDeps();
};
/* MAIN */
const main = () => {
const { SOURCE_REF, TARGET_REF } = process.env;
if (!SOURCE_REF || !TARGET_REF) {
console.error('ERROR: Must provide SOURCE_REF and TARGET_REF in env.')
process.exit(1);
}
setup(SOURCE_REF);
const sourcePackageVersions = getNativePackageVersions();
setup(TARGET_REF);
const targetPackageVersions = getNativePackageVersions();
console.info("➡️ Comparing");
let noChanges = true;
Object.entries(sourcePackageVersions).forEach(([package, version]) => {
if (!targetPackageVersions[package]) {
console.info(`❌ Package removed: ${package}`);
noChanges = false;
} else if (targetPackageVersions[package] !== version) {
console.info(`🚀 Package version changed: ${package} (${version} -> ${targetPackageVersions[package]})`);
noChanges = false;
}
});
Object.entries(targetPackageVersions).forEach(([package]) => {
if (!sourcePackageVersions[package]) {
console.info(`✅ Package added: ${package}`);
noChanges = false;
}
});
return noChanges;
};
if (require.main === module) {
const noChanges = main();
process.exit(noChanges ? 0 : 1); // Exit w/ non-zero code if there are changes to native packages
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment