pulse-zax/apps/server/build/helpers.ts

207 lines
5.3 KiB
TypeScript

import type { TArtifact } from '@pulse/shared';
import {
validateReleaseMetadata,
type TReleaseMetadata
} from 'bun-sfe-autoupdater';
import fs from 'fs/promises';
import path from 'path';
const buildScriptDir = import.meta.dir;
const serverCwd = path.resolve(buildScriptDir, '..');
const rootCwd = path.resolve(serverCwd, '..', '..');
const rootPckJson = path.join(rootCwd, 'package.json');
const serverPckJson = path.join(rootCwd, 'apps', 'server', 'package.json');
const clientPckJson = path.join(rootCwd, 'apps', 'client', 'package.json');
const sharedPckJson = path.join(rootCwd, 'packages', 'shared', 'package.json');
const unpack = async (tgzPath: string, outDir: string) => {
const tarProc = Bun.spawn(['tar', '-xzf', tgzPath, '-C', outDir], {
stdout: 'inherit',
stderr: 'inherit',
stdin: 'inherit'
});
await tarProc.exited;
if (tarProc.exitCode !== 0) {
throw new Error(`Failed to unpack ${tgzPath}`);
}
};
const downloadMediasoupBinary = async (
version: string,
target: Bun.Build.Target
) => {
let url = `https://github.com/versatica/mediasoup/releases/download/${version}/`;
let fileName = '';
switch (target) {
case 'bun-linux-x64':
url += `mediasoup-worker-${version}-linux-x64-kernel6.tgz`;
fileName = 'mediasoup-worker';
break;
case 'bun-linux-arm64':
url += `mediasoup-worker-${version}-linux-arm64-kernel6.tgz`;
fileName = 'mediasoup-worker';
break;
case 'bun-windows-x64':
url += `mediasoup-worker-${version}-win32-x64.tgz`;
fileName = 'mediasoup-worker.exe';
break;
case 'bun-darwin-arm64':
url += `mediasoup-worker-${version}-darwin-arm64.tgz`;
fileName = 'mediasoup-worker';
break;
default:
throw new Error(`Unsupported target for mediasoup binary: ${target}`);
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Failed to download mediasoup binary for target ${target}: ${response.statusText}`
);
}
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const targetPath = path.join(
serverCwd,
'build',
'temp',
`mediasoup-worker-${target}.tgz`
);
await fs.mkdir(path.dirname(targetPath), { recursive: true });
await fs.writeFile(targetPath, buffer);
await unpack(targetPath, path.join(serverCwd, 'build', 'temp'));
return fileName;
};
const getCurrentVersion = async () => {
const pkg = JSON.parse(await fs.readFile(rootPckJson, 'utf8'));
return pkg.version;
};
const getMediasoupVersion = async () => {
const serverPkg = JSON.parse(await fs.readFile(serverPckJson, 'utf8'));
return serverPkg.dependencies['mediasoup'].replace('^', '');
};
const patchPackageJsons = async (newVersion: string) => {
const packageJsonPaths = [
rootPckJson,
serverPckJson,
clientPckJson,
sharedPckJson
];
for (const pckPath of packageJsonPaths) {
const pkg = JSON.parse(await fs.readFile(pckPath, 'utf8'));
pkg.version = newVersion;
await fs.writeFile(pckPath, JSON.stringify(pkg, null, 2), 'utf8');
}
};
type TTarget = {
out: string;
target: Bun.Build.Target;
};
const compile = async ({ out, target }: TTarget) => {
const version = await getCurrentVersion();
const mediasoupVersion = await getMediasoupVersion();
const mediasoupBinary = await downloadMediasoupBinary(
mediasoupVersion,
target
);
const entryPoints = [
path.join(serverCwd, 'src', 'index.ts'),
path.join(serverCwd, 'build', 'temp', 'drizzle.zip'),
path.join(serverCwd, 'build', 'temp', 'interface.zip'),
path.join(serverCwd, 'build', 'temp', mediasoupBinary)
];
await Bun.build({
entrypoints: entryPoints,
compile: {
outfile: out,
target
},
minify: true,
define: {
'process.env.PULSE_ENV': '"production"',
'process.env.PULSE_BUILD_VERSION': `"${version}"`,
'process.env.PULSE_BUILD_DATE': `"${new Date().toISOString()}"`,
'process.env.PULSE_MEDIASOUP_BIN_NAME': `"${mediasoupBinary}"`,
'process.env.CURRENT_VERSION': `"${version}"`
}
});
};
const getFileChecksum = async (filePath: string) => {
const fileBuffer = await fs.readFile(filePath);
const hashBuffer = await crypto.subtle.digest('SHA-256', fileBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
return hashHex;
};
const getVersionInfo = async (
targets: TTarget[],
outPath: string
): Promise<TReleaseMetadata> => {
const version = await getCurrentVersion();
const artifacts: TArtifact[] = [];
for (const target of targets) {
const artifactPath = path.join(outPath, target.out);
artifacts.push({
name: path.basename(artifactPath),
target: target.target.replace('bun-', ''),
size: (await fs.stat(artifactPath)).size,
checksum: await getFileChecksum(artifactPath)
});
}
const versionInfo = validateReleaseMetadata({
version,
releaseDate: new Date().toISOString(),
artifacts
});
return versionInfo;
};
const rmIfExists = async (filePath: string) => {
try {
await fs.access(filePath);
await fs.rm(filePath);
} catch {
// ignore
}
};
export {
compile,
downloadMediasoupBinary,
getCurrentVersion,
getVersionInfo,
patchPackageJsons,
rmIfExists
};
export type { TTarget };