Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,22 @@ jobs:
echo "EOF" >> "$GITHUB_OUTPUT"

cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: Patch npm binary.js for libc detection
shell: bash
run: |
# Fix null libcVersion crash on Android/Termux (bionic libc)
# See: https://github.com/googleworkspace/cli/issues/271
NPM_TARBALL=$(find target/distrib -name '*-npm-package.tar.gz' | head -1)
if [ -n "$NPM_TARBALL" ]; then
WORK_DIR=$(mktemp -d)
tar xzf "$NPM_TARBALL" -C "$WORK_DIR"
cp npm/binary.js "$WORK_DIR/package/binary.js"
tar czf "$NPM_TARBALL" -C "$WORK_DIR" package
rm -rf "$WORK_DIR"
echo "Patched npm binary.js in $NPM_TARBALL"
else
echo "No npm tarball found, skipping patch"
fi
- name: "Upload artifacts"
uses: actions/upload-artifact@v6
with:
Expand Down
137 changes: 137 additions & 0 deletions npm/binary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const { Package } = require("./binary-install");
const os = require("os");
const cTable = require("console.table");
const libc = require("detect-libc");
const { configureProxy } = require("axios-proxy-builder");

const error = (msg) => {
console.error(msg);
process.exit(1);
};

const {
name,
artifactDownloadUrls,
supportedPlatforms,
glibcMinimum,
} = require("./package.json");

// FIXME: implement NPM installer handling of fallback download URLs
const artifactDownloadUrl = artifactDownloadUrls[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The installer only uses the first URL from artifactDownloadUrls, which makes it vulnerable to failures if that specific URL is down. The FIXME comment acknowledges this. To improve reliability, the installer should be updated to try all available fallback URLs in artifactDownloadUrls until a binary is successfully downloaded.

const builderGlibcMajorVersion = glibcMinimum.major;
const builderGlibcMinorVersion = glibcMinimum.series;

const getPlatform = () => {
const rawOsType = os.type();
const rawArchitecture = os.arch();

// We want to use rust-style target triples as the canonical key
// for a platform, so translate the "os" library's concepts into rust ones
let osType = "";
switch (rawOsType) {
case "Windows_NT":
osType = "pc-windows-msvc";
break;
case "Darwin":
osType = "apple-darwin";
break;
case "Linux":
osType = "unknown-linux-gnu";
break;
}

let arch = "";
switch (rawArchitecture) {
case "x64":
arch = "x86_64";
break;
case "arm64":
arch = "aarch64";
break;
}

if (rawOsType === "Linux") {
if (libc.familySync() == "musl") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

It's best practice to use the strict equality operator (===) for comparisons to avoid unexpected type coercion. This prevents potential bugs and makes the code's intent clearer.

Suggested change
if (libc.familySync() == "musl") {
if (libc.familySync() === "musl") {

osType = "unknown-linux-musl-dynamic";
} else if (libc.isNonGlibcLinuxSync()) {
console.warn(
"Your libc is neither glibc nor musl; trying static musl binary instead",
);
osType = "unknown-linux-musl-static";
} else {
let libcVersion = libc.versionSync();
if (libcVersion == null) {
// detect-libc could not determine the libc version (e.g. Android/Termux
// with bionic libc). Fall back to static musl binary.
console.warn(
"Could not detect libc version; trying static musl binary instead",
);
osType = "unknown-linux-musl-static";
} else {
let splitLibcVersion = libcVersion.split(".");
let libcMajorVersion = splitLibcVersion[0];
let libcMinorVersion = splitLibcVersion[1];
if (
libcMajorVersion != builderGlibcMajorVersion ||
libcMinorVersion < builderGlibcMinorVersion
Comment on lines +71 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The version components obtained from split('.') are strings. Comparing them directly with numbers relies on implicit type coercion, which can be fragile and lead to bugs (e.g., lexicographical string comparison like '10' < '2' is true). It's safer and clearer to explicitly parse the version components into integers before performing numerical comparisons. This makes the logic more robust.

        const [majorStr, minorStr] = libcVersion.split(".");
        const libcMajorVersion = parseInt(majorStr, 10);
        const libcMinorVersion = parseInt(minorStr, 10);
        if (
          libcMajorVersion !== builderGlibcMajorVersion ||
          libcMinorVersion < builderGlibcMinorVersion
        )

) {
// We can't run the glibc binaries, but we can run the static musl ones
// if they exist
console.warn(
"Your glibc isn't compatible; trying static musl binary instead",
);
osType = "unknown-linux-musl-static";
}
}
}
}

// Assume the above succeeded and build a target triple to look things up with.
// If any of it failed, this lookup will fail and we'll handle it like normal.
let targetTriple = `${arch}-${osType}`;
let platform = supportedPlatforms[targetTriple];

if (!platform) {
error(
`Platform with type "${rawOsType}" and architecture "${rawArchitecture}" is not supported by ${name}.\nYour system must be one of the following:\n\n${Object.keys(
supportedPlatforms,
).join(",")}`,
);
}

return platform;
};

const getPackage = () => {
const platform = getPlatform();
const url = `${artifactDownloadUrl}/${platform.artifactName}`;
let filename = platform.artifactName;
let ext = platform.zipExt;
let binary = new Package(platform, name, url, filename, ext, platform.bins);

return binary;
};

const install = (suppressLogs) => {
if (!artifactDownloadUrl || artifactDownloadUrl.length === 0) {
console.warn("in demo mode, not installing binaries");
return;
}
const package = getPackage();
const proxy = configureProxy(package.url);

return package.install(proxy, suppressLogs);
};

const run = (binaryName) => {
const package = getPackage();
const proxy = configureProxy(package.url);

package.run(binaryName, proxy);
};

module.exports = {
install,
run,
getPackage,
};