Skip to content

Commit

Permalink
feat(build): add a script for generating binary-dependencies.txt
Browse files Browse the repository at this point in the history
  • Loading branch information
justmoon committed May 17, 2024
1 parent c3dc35f commit f45aa7e
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 7 deletions.
141 changes: 141 additions & 0 deletions packages/app-build/bin/generate-binary-dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { $ } from "execa"
import { temporaryFile } from "tempy"

import { createHash } from "node:crypto"
import path from "node:path"
import { Readable } from "node:stream"
import { pipeline } from "node:stream/promises"
import type { ReadableStream } from "node:stream/web"

import { createFlow, header, tasklist } from "@dassie/lib-terminal-graphics"

import { SUPPORTED_ARCHITECTURES } from "../src/constants/architectures"
import {
NODEJS_SIGNERS,
NODEJS_SIGNERS_KEYSERVER,
} from "../src/constants/nodejs-signers"
import {
BETTER_SQLITE3_VERSION,
NODE_ABI_VERSION,
NODE_VERSION,
} from "../src/constants/version"
import {
SQLITE_ARCHITECTURE_MAP,
getBetterSqliteDownloadUrl,
} from "../src/steps/download-better-sqlite3"

const NODE_BASE_URL = `https://nodejs.org/dist/v${NODE_VERSION}/`

const flow = createFlow({ outputStream: process.stderr })

flow.show(header({ title: "Generate Binary Dependencies" }))

const shasumsMap = new Map<string, string>()

await flow.attach(tasklist({}), async (state) => {
state.addTask("fetch-nodejs-signer-keys", {
description: "Fetching Node.js signer keys",
progress: 0,
})
const temporaryKeyringPath = temporaryFile({ extension: ".gpg" })

let keysFetched = 0
for (const key of NODEJS_SIGNERS) {
await $`gpg --no-default-keyring --keyring ${temporaryKeyringPath} --keyserver ${NODEJS_SIGNERS_KEYSERVER} --recv-keys ${key}`

keysFetched++
state.updateTask("fetch-nodejs-signer-keys", {
progress: keysFetched / NODEJS_SIGNERS.length,
})
}

state.updateTask("fetch-nodejs-signer-keys", {
progress: "done",
})

state.addTask("fetch-nodejs-release-shasums", {
description: "Fetching Node.js release SHASUMS256.txt.asc",
progress: "indeterminate",
})

const result = await fetch(`${NODE_BASE_URL}/SHASUMS256.txt.asc`)
const signedShasums = await result.text()

state.updateTask("fetch-nodejs-release-shasums", {
progress: "done",
})

state.addTask("verify-nodejs-release-signature", {
description: "Verifying Node.js release signature",
progress: "indeterminate",
})

const { stdout: shasums } = await $({
input: signedShasums,
})`gpg --no-default-keyring --keyring ${temporaryKeyringPath}`

for (const line of shasums.split("\n")) {
const [hash, file] = line.split(" ")
if (!hash || !file?.startsWith("node-v")) continue

shasumsMap.set(file, hash)
}

state.updateTask("verify-nodejs-release-signature", {
progress: "done",
})

state.addTask("download-better-sqlite3", {
description: "Downloading better-sqlite3 binaries to calculate checksums",
progress: 0,
})

for (const architecture of SUPPORTED_ARCHITECTURES) {
const url = getBetterSqliteDownloadUrl(
BETTER_SQLITE3_VERSION,
NODE_ABI_VERSION,
SQLITE_ARCHITECTURE_MAP[architecture],
)
const filename = path.basename(new URL(url).pathname)

const result = await fetch(url)
if (!result.body) {
throw new Error("Sqlite download failed - no response body")
}

const digest = createHash("sha256")
// eslint-disable-next-line n/no-unsupported-features/node-builtins
await pipeline(Readable.fromWeb(result.body as ReadableStream), digest)

const hash = digest.digest("hex")

shasumsMap.set(filename, hash)
}

state.updateTask("download-better-sqlite3", {
progress: "done",
})
})

for (const architecture of SUPPORTED_ARCHITECTURES) {
const file = `node-v${NODE_VERSION}-linux-${architecture}.tar.xz`
if (!shasumsMap.has(file)) {
throw new Error(`No hash for ${file}`)
}

console.info(`${NODE_BASE_URL}${file} ${shasumsMap.get(file)}`)
}

for (const architecture of SUPPORTED_ARCHITECTURES) {
const url = getBetterSqliteDownloadUrl(
BETTER_SQLITE3_VERSION,
NODE_ABI_VERSION,
SQLITE_ARCHITECTURE_MAP[architecture],
)
const file = path.basename(new URL(url).pathname)
if (!shasumsMap.has(file)) {
throw new Error(`No hash for ${file}`)
}

console.info(`${url} ${shasumsMap.get(file)}`)
}
1 change: 1 addition & 0 deletions packages/app-build/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"esbuild": "^0.21.2",
"execa": "^9.1.0",
"http-server": "^14.1.1",
"tempy": "^3.1.0",
"vite": "^5.2.11",
"zod": "^3.23.8"
},
Expand Down
15 changes: 15 additions & 0 deletions packages/app-build/src/constants/nodejs-signers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const NODEJS_SIGNERS_KEYSERVER = "hkps://keys.openpgp.org"

export const NODEJS_SIGNERS = [
"4ED778F539E3634C779C87C6D7062848A1AB005C", // Beth Griggs
"141F07595B7B3FFE74309A937405533BE57C7D57", // Bryan English
"74F12602B6F1C4E913FAA37AD3A89613643B6201", // Danielle Adams
"DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7", // Juan José Arboleda
"CC68F5A3106FF448322E48ED27F5E38D5B0A215F", // Marco Ippolito
"8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600", // Michaël Zasso
"C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8", // Myles Borins
"890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4", // Rafael Gonzaga
"C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C", // Richard Lau
"108F52B48DB57BB0CC439B2997B01419BD92F80A", // Ruy Adorno
"A363A499291CBBC940DD62E41F10027AF002F8B0", // Ulises Gascón
]
10 changes: 6 additions & 4 deletions packages/app-build/src/steps/download-better-sqlite3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { BETTER_SQLITE3_VERSION, NODE_ABI_VERSION } from "../constants/version"
import { downloadFile } from "../utils/download-file"
import { getStagingPath } from "../utils/dynamic-paths"

const getDownloadUrl = (
export const getBetterSqliteDownloadUrl = (
version: string,
nodeAbiVersion: string,
architecture: SqliteArchitecture,
Expand All @@ -17,19 +17,21 @@ const getDownloadUrl = (

type SqliteArchitecture = "x64" | "arm" | "arm64"

const ARCHITECTURE_MAP: { [key in Architecture]: SqliteArchitecture } = {
export const SQLITE_ARCHITECTURE_MAP: {
[key in Architecture]: SqliteArchitecture
} = {
armv7l: "arm",
arm64: "arm64",
x64: "x64",
}

export const downloadBetterSqlite3 = async (architecture: Architecture) => {
const sqliteArchitecture = ARCHITECTURE_MAP[architecture]
const sqliteArchitecture = SQLITE_ARCHITECTURE_MAP[architecture]
const pathSqlite = path.resolve(
getStagingPath(architecture),
"better-sqlite3",
)
const downloadUrl = getDownloadUrl(
const downloadUrl = getBetterSqliteDownloadUrl(
BETTER_SQLITE3_VERSION,
NODE_ABI_VERSION,
sqliteArchitecture,
Expand Down
6 changes: 3 additions & 3 deletions packages/app-build/src/steps/download-node-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { NODE_VERSION } from "../constants/version"
import { downloadFile } from "../utils/download-file"
import { getStagingPath } from "../utils/dynamic-paths"

const getNodeUrl = (version: string, architecture: Architecture) =>
`https://nodejs.org/dist/v${version}/node-v${version}-linux-${architecture}.tar.xz`
const getNodeUrl = (architecture: Architecture) =>
`https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${architecture}.tar.xz`

export const downloadNodeJs = async (architecture: Architecture) => {
const pathNode = path.resolve(getStagingPath(architecture), "node")
Expand All @@ -20,7 +20,7 @@ export const downloadNodeJs = async (architecture: Architecture) => {

await mkdir(pathNode, { recursive: true })

await downloadFile(getNodeUrl(NODE_VERSION, architecture), nodeLocalFile)
await downloadFile(getNodeUrl(architecture), nodeLocalFile)

await $`tar -xJf ${nodeLocalFile} -C ${pathNode} --strip-components=1`
}
42 changes: 42 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f45aa7e

Please sign in to comment.