Use a direct download from Node distribution instead of relying on pkg-fetch

Summary:
It seems the available binary for macOS arm64 is not signed:

```
codesign -dv --verbose=4 ./node-v16.16.0-macos-arm64
./node-v16.16.0-macos-arm64: code object is not signed
```

This is an issue as it crashes for our flipper server releases.

This can be compared to a binary downloaded from the Node distribution page:

```
codesign -dv --verbose=4 ./node
    Executable=/Users/realpassy/Downloads/node-v16.15.0-darwin-arm64/bin/node
    Identifier=node
    Format=Mach-O thin (arm64)
    CodeDirectory v=20500 size=597360 flags=0x10000(runtime) hashes=18657+7 location=embedded
    VersionPlatform=1
    VersionMin=720896
    VersionSDK=721152
    Hash type=sha256 size=32
    CandidateCDHash sha256=31cdf84cac42a622c1a68558376700a2dd12d40d
    CandidateCDHashFull sha256=31cdf84cac42a622c1a68558376700a2dd12d40d81c5118f3b0e0370c414eb69
    Hash choices=sha256
 CMSDigest=31cdf84cac42a622c1a68558376700a2dd12d40d81c5118f3b0e0370c414eb69
    CMSDigestType=2
    Executable Segment base=0
    Executable Segment limit=56082432
    Executable Segment flags=0x1
    Page size=4096
    Launch Constraints:
        None
    CDHash=31cdf84cac42a622c1a68558376700a2dd12d40d
    Signature size=8986
    Authority=Developer ID Application: Node.js Foundation (HX7739G8FX)
    Authority=Developer ID Certification Authority
    Authority=Apple Root CA
    Timestamp=26 Apr 2022 at 23:00:57
    Info.plist=not bound
    TeamIdentifier=HX7739G8FX
    Runtime Version=11.1.0
    Sealed Resources=none
    Internal requirements count=1 size=164
```

For additional context:

Node binary by using pkg-fetch. We just get the binary from this release page: https://github.com/vercel/pkg-fetch/releases/tag/v3.4

The exact binary we're downloading is https://github.com/vercel/pkg-fetch/releases/download/v3.4/node-v16.15.0-macos-arm64

If you just download that via Chrome, it will trigger Gatekeeper and you need to manually click "Allow" in the system privacy settings. You can also do the same by just running:

    xattr -c ./node-v16*

Afterwards you can:
   chmod +x ./node*

It will crash in the same way that it does after our distribution.

Reviewed By: passy

Differential Revision: D46225503

fbshipit-source-id: f0ae2d5101b99c9db7fe80333573caef52c787a2
This commit is contained in:
Lorenzo Blasa
2023-05-31 01:06:17 -07:00
committed by Facebook GitHub Bot
parent 1d595862b5
commit 49c356059a
3 changed files with 140 additions and 8 deletions

View File

@@ -9,6 +9,9 @@
const dotenv = require('dotenv').config(); const dotenv = require('dotenv').config();
import path from 'path'; import path from 'path';
import https from 'https';
import os from 'os';
import tar from 'tar';
import { import {
buildBrowserBundle, buildBrowserBundle,
buildFolder, buildFolder,
@@ -35,6 +38,9 @@ import {need as pkgFetch} from 'pkg-fetch';
// This needs to be tested individually. As of 2022Q2, node17 is not supported. // This needs to be tested individually. As of 2022Q2, node17 is not supported.
const SUPPORTED_NODE_PLATFORM = 'node16'; const SUPPORTED_NODE_PLATFORM = 'node16';
// Node version below is only used for macOS AARCH64 builds as we download
// the binary directly from Node distribution site instead of relying on pkg-fetch.
const NODE_VERSION = 'v16.15.0';
enum BuildPlatform { enum BuildPlatform {
LINUX = 'linux', LINUX = 'linux',
@@ -461,6 +467,69 @@ function nodeArchFromBuildPlatform(platform: BuildPlatform): string {
return 'x64'; return 'x64';
} }
/**
* Downloads a file located at the given URL and saves it to the destination path..
* @param url - URL of the file to download.
* @param dest - Destination path for the downloaded file.
* @returns - A promise that resolves when the file is downloaded.
* If the file can't be downloaded, it rejects with an error.
*/
async function download(url: string, dest: string): Promise<void> {
// First, check if the file already exists and remove it.
try {
await fs.access(dest, fs.constants.F_OK);
await fs.unlink(dest);
} catch (err) {}
return new Promise<void>((resolve, reject) => {
// Then, download the file and save it to the destination path.
const file: fs.WriteStream = fs.createWriteStream(dest);
https
.get(url, (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
console.log(`✅ Download successful ${url}.`);
resolve();
});
})
.on('error', (error: Error) => {
fs.unlink(dest);
reject(error);
});
});
}
/**
* Unpacks a tarball and extracts the contents to a directory.
* @param source - Source tarball.
* @param dest - Destination directory for the extracted contents.
*/
async function unpack(source: string, destination: string) {
console.log(`⚙️ Extracting ${source}.`);
try {
await fs.access(destination, fs.constants.F_OK);
await fs.rm(destination, {recursive: true, force: true});
} catch (err) {}
await fs.mkdir(destination);
try {
await tar.x({
file: source,
strip: 1,
cwd: destination,
});
console.log(`✅ Extraction completed.`);
} catch (error) {
console.error(
`⚙️ Error found whilst trying to extract '${source}'. Found: ${error}`,
);
}
}
function nodePlatformFromBuildPlatform(platform: BuildPlatform): string { function nodePlatformFromBuildPlatform(platform: BuildPlatform): string {
switch (platform) { switch (platform) {
case BuildPlatform.LINUX: case BuildPlatform.LINUX:
@@ -476,14 +545,59 @@ function nodePlatformFromBuildPlatform(platform: BuildPlatform): string {
} }
async function installNodeBinary(outputPath: string, platform: BuildPlatform) { async function installNodeBinary(outputPath: string, platform: BuildPlatform) {
console.log(`⚙️ Downloading node version for ${platform} using pkg-fetch`); /**
const path = await pkgFetch({ * Below is a temporary patch that doesn't use pkg-fetch to
arch: nodeArchFromBuildPlatform(platform), * download a node binary for macOS arm64.
platform: nodePlatformFromBuildPlatform(platform), * This will be removed once there's a properly
nodeRange: SUPPORTED_NODE_PLATFORM, * signed binary for macOS arm64 architecture.
}); */
console.log(`⚙️ Copying node binary from ${path} to ${outputPath}`); if (platform === BuildPlatform.MAC_AARCH64) {
await fs.copyFile(path, outputPath); const temporaryDirectory = os.tmpdir();
const name = `node-${NODE_VERSION}-darwin-arm64`;
const downloadOutputPath = path.resolve(
temporaryDirectory,
`${name}.tar.gz`,
);
const unpackedOutputPath = path.resolve(temporaryDirectory, name);
let nodePath = path.resolve(unpackedOutputPath, 'bin', 'node');
console.log(
`⚙️ Downloading node version for ${platform} using temporary patch.`,
);
// Check local cache.
let cached = false;
try {
const cachePath = path.join(homedir(), '.node', name);
await fs.access(cachePath, fs.constants.F_OK);
console.log(`⚙️ Cached artifact found, skip download.`);
nodePath = path.resolve(cachePath, 'bin', 'node');
cached = true;
} catch (err) {}
if (!cached) {
// Download node tarball from the distribution site.
await download(
`https://nodejs.org/dist/${NODE_VERSION}/${name}.tar.gz`,
downloadOutputPath,
);
// Finally, unpack the tarball to a local folder i.e. outputPath.
await unpack(downloadOutputPath, unpackedOutputPath);
console.log(`✅ Node successfully downloaded and unpacked.`);
}
console.log(`⚙️ Copying node binary from ${nodePath} to ${outputPath}`);
await fs.copyFile(nodePath, outputPath);
} else {
console.log(`⚙️ Downloading node version for ${platform} using pkg-fetch`);
const nodePath = await pkgFetch({
arch: nodeArchFromBuildPlatform(platform),
platform: nodePlatformFromBuildPlatform(platform),
nodeRange: SUPPORTED_NODE_PLATFORM,
});
console.log(`⚙️ Copying node binary from ${nodePath} to ${outputPath}`);
await fs.copyFile(nodePath, outputPath);
}
// Set +x on the binary as copyFile doesn't maintain the bit. // Set +x on the binary as copyFile doesn't maintain the bit.
await fs.chmod(outputPath, 0o755); await fs.chmod(outputPath, 0o755);
} }

View File

@@ -34,6 +34,7 @@
"pkg-fetch": "3.4.1", "pkg-fetch": "3.4.1",
"promisify-child-process": "^4.1.0", "promisify-child-process": "^4.1.0",
"socket.io": "^4.5.0", "socket.io": "^4.5.0",
"tar": "6.1.15",
"tmp": "^0.2.1", "tmp": "^0.2.1",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"yargs": "^17.6.0" "yargs": "^17.6.0"

View File

@@ -11453,6 +11453,11 @@ minipass@^3.0.0:
dependencies: dependencies:
yallist "^4.0.0" yallist "^4.0.0"
minipass@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
minizlib@^2.1.1: minizlib@^2.1.1:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
@@ -14526,6 +14531,18 @@ tar-stream@^2.0.0, tar-stream@^2.1.4, tar-stream@^2.2.0:
inherits "^2.0.3" inherits "^2.0.3"
readable-stream "^3.1.1" readable-stream "^3.1.1"
tar@6.1.15:
version "6.1.15"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69"
integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^5.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"
tar@^6.1.10, tar@^6.1.11: tar@^6.1.10, tar@^6.1.11:
version "6.1.11" version "6.1.11"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"