From 04e7a6adbcb688f42c85687be06e4e6ff81b596c Mon Sep 17 00:00:00 2001 From: Kunam Balaram Reddy Date: Wed, 25 Sep 2024 17:21:42 +0530 Subject: [PATCH] fix(2694): tailcall server run via npx command (#2733) Co-authored-by: Tushar Mathur --- npm/gen-root.ts | 38 ++++++++--------- npm/package-lock.json | 94 +------------------------------------------ npm/package.json | 3 +- npm/post-install.js | 70 ++++++++++++++++++++++++++------ npm/pre-install.js | 19 +++++++-- npm/utils.js | 24 +++++++++++ 6 files changed, 118 insertions(+), 130 deletions(-) create mode 100644 npm/utils.js diff --git a/npm/gen-root.ts b/npm/gen-root.ts index c5cfbd5ae2..380e60353b 100644 --- a/npm/gen-root.ts +++ b/npm/gen-root.ts @@ -1,9 +1,9 @@ import * as fs from "fs/promises" import {resolve, dirname} from "path" -import * as yml from "yaml" import {fileURLToPath} from "url" import {parse} from "ts-command-line-args" import {PackageJson as IPackageJSON} from "type-fest" +import YML from "yaml" const __dirname = dirname(fileURLToPath(import.meta.url)) @@ -17,9 +17,9 @@ const options = parse({ name: {alias: "n", type: String}, }) -async function getBuildDefinitions(): Promise { +async function get_build_matrix() { const ciYMLPath = resolve(__dirname, "../.github/workflows/build_matrix.yml") - const ciYML = await fs.readFile(ciYMLPath, "utf8").then(yml.parse) + const ciYML = await fs.readFile(ciYMLPath, "utf8").then(YML.parse) const steps = ciYML.jobs["setup-matrix"].steps for (const step of steps) { @@ -27,26 +27,19 @@ async function getBuildDefinitions(): Promise { if (matrix) { // Parse yaml again since matrix is defined as string inside setup-matrix - return yml.parse(matrix).build + return YML.parse(matrix) } } throw new Error("Cannot find matrix definition in workflow file") } -async function genServerPackage(buildDefinitions: string[]) { +async function genServerPackage() { const packageVersion = options.version || "0.1.0" const name = options.name || "@tailcallhq/tailcall" console.log(`Generating package.json with version ${packageVersion}`) - // Construct the optionalDependencies object with the provided version - const optionalDependencies: Record = {} - - for (const buildDef of buildDefinitions) { - optionalDependencies[`@tailcallhq/core-${buildDef}`] = packageVersion - } - const packageJson = await fs.readFile(resolve(__dirname, "./package.json"), "utf8") const basePackage = JSON.parse(packageJson) as IPackageJSON const {description, license, repository, homepage, keywords} = basePackage @@ -60,7 +53,6 @@ async function genServerPackage(buildDefinitions: string[]) { name: name, type: "module", version: packageVersion, - optionalDependencies, scarfSettings: { defaultOptIn: true, allowTopLevel: true, @@ -68,34 +60,42 @@ async function genServerPackage(buildDefinitions: string[]) { dependencies: { "detect-libc": "^2.0.2", "@scarf/scarf": "^1.3.0", + yaml: "^2.3.3", + axios: "^1.7.4", }, scripts: { postinstall: "node ./scripts/post-install.js", preinstall: "node ./scripts/pre-install.js", }, + bin: { + tailcall: "bin/tailcall", // will replace with respective platform binary later. + }, } // Define the directory path where the package.json should be created const directoryPath = resolve(__dirname, "@tailcallhq/tailcall") const scriptsPath = resolve(directoryPath, "./scripts") + const binPath = resolve(directoryPath, "./bin") await fs.mkdir(scriptsPath, {recursive: true}) + await fs.mkdir(binPath, {recursive: true}) await fs.mkdir(directoryPath, {recursive: true}) const postInstallScript = await fs.readFile(resolve(__dirname, "./post-install.js"), "utf8") const preInstallScript = await fs.readFile(resolve(__dirname, "./pre-install.js"), "utf8") + const utilsScript = await fs.readFile(resolve(__dirname, "./utils.js"), "utf8") + const stringified_yaml = YML.stringify(await get_build_matrix()) const postInstallScriptContent = `const version = "${packageVersion}";\n${postInstallScript}` - const preInstallScriptContent = `const optionalDependencies = ${JSON.stringify( - optionalDependencies, - )};\n${preInstallScript}` await fs.writeFile(resolve(scriptsPath, "post-install.js"), postInstallScriptContent, "utf8") - await fs.writeFile(resolve(scriptsPath, "pre-install.js"), preInstallScriptContent, "utf8") + await fs.writeFile(resolve(scriptsPath, "pre-install.js"), preInstallScript, "utf8") + await fs.writeFile(resolve(scriptsPath, "utils.js"), utilsScript, "utf8") + await fs.writeFile(resolve(directoryPath, "./build-matrix.yaml"), stringified_yaml, "utf8") + await fs.writeFile(resolve(directoryPath, "./package.json"), JSON.stringify(tailcallPackage, null, 2), "utf8") await fs.copyFile(resolve(__dirname, "../README.md"), resolve(directoryPath, "./README.md")) } -const buildDefinitions = await getBuildDefinitions() -await genServerPackage(buildDefinitions) +await genServerPackage() diff --git a/npm/package-lock.json b/npm/package-lock.json index 6dcf132c46..0314b82f07 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -9,8 +9,7 @@ "dependencies": { "ts-command-line-args": "^2.5.1", "type-fest": "^4.7.1", - "yaml": "^2.3.3", - "yml": "^1.0.0" + "yaml": "^2.3.3" }, "devDependencies": { "tsx": "^4.1.0" @@ -438,14 +437,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/array-back": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", @@ -641,18 +632,6 @@ "node": ">=0.8.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/find-replace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", @@ -678,14 +657,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-tsconfig": { "version": "4.7.5", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", @@ -707,59 +678,11 @@ "node": ">=8" } }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", - "engines": { - "node": "*" - } - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, - "node_modules/node.extend": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.3.tgz", - "integrity": "sha512-xwADg/okH48PvBmRZyoX8i8GJaKuJ1CqlqotlZOhUio8egD1P5trJupHKBzcPjSF9ifK2gPcEICRBnkfPqQXZw==", - "dependencies": { - "hasown": "^2.0.0", - "is": "^3.3.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/reduce-flatten": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", @@ -777,11 +700,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, "node_modules/string-format": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", @@ -913,16 +831,6 @@ "engines": { "node": ">= 14" } - }, - "node_modules/yml": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yml/-/yml-1.0.0.tgz", - "integrity": "sha512-x9KKVpZtKunwoi6J7fCtiAJkV+M4F5woMD8RQJ7wv3DkIBrIx70XOryhnSFbwXxpLsH/s1reCxSZ1CPHUr0PWg==", - "dependencies": { - "js-yaml": "^3.9.0", - "lodash": "^4.17.4", - "node.extend": "^2.0.0" - } } } } diff --git a/npm/package.json b/npm/package.json index 82cebc7e53..964ef3e926 100644 --- a/npm/package.json +++ b/npm/package.json @@ -29,8 +29,7 @@ "dependencies": { "ts-command-line-args": "^2.5.1", "type-fest": "^4.7.1", - "yaml": "^2.3.3", - "yml": "^1.0.0" + "yaml": "^2.3.3" }, "devDependencies": { "tsx": "^4.1.0" diff --git a/npm/post-install.js b/npm/post-install.js index 365f6dfad2..d3f4d2edd4 100644 --- a/npm/post-install.js +++ b/npm/post-install.js @@ -2,25 +2,69 @@ import {familySync, GLIBC, MUSL} from "detect-libc" import {exec} from "child_process" import util from "util" +import get_matched_platform from "./utils.js" +import fs from "fs" +import axios from "axios" +import {resolve, dirname} from "path" +import {fileURLToPath} from "url" const execa = util.promisify(exec) -const platform = process.platform +const os = process.platform const arch = process.arch - const libcFamily = familySync() -let libc -if (platform === "win32") { - libc = "-msvc" + +let libc = "" +if (os === "win32") { + libc = "msvc" } else { - libc = libcFamily === GLIBC ? "-gnu" : libcFamily === MUSL ? "-musl" : "" + libc = libcFamily === GLIBC ? "gnu" : libcFamily === MUSL ? "musl" : "" } -const pkg = `@tailcallhq/core-${platform}-${arch}${libc}` +const matched_platform = get_matched_platform(os, arch, libc) +if (matched_platform != null) { + const targetPlatform = matched_platform + + let targetPlatformExt = "" + if (targetPlatform.get("ext") != undefined) { + targetPlatformExt = targetPlatform.get("ext") + } + + const pkg_download_base_url = "https://github.com/tailcallhq/tailcall/releases/download/" + const specific_url = `v${version}/tailcall-${targetPlatform.get("target")}${targetPlatformExt}` + const full_url = pkg_download_base_url + specific_url + + console.log(`Downloading Tailcall for ${targetPlatform.get("target")}${targetPlatformExt} ,\nUrl - ${full_url} ...`) + + const output_path = `bin/tailcall-${targetPlatform.get("target")}${targetPlatformExt}` + await download_binary(full_url, output_path) +} + +async function download_binary(full_url, output_path) { + try { + const file = fs.createWriteStream(output_path) + console.log("bin path -", output_path) + const response = await axios({ + url: full_url, + method: "GET", + responseType: "stream", + }) + + response.data.pipe(file) + response.data.on("error", (error) => { + console.error("Error with resp data - ", error) + }) -try { - // @ts-ignore - const {stdout, stderr} = await execa(`npm install ${pkg}@${version} --no-save`) - stderr ? console.log(stderr) : console.log(`Successfully installed optional dependency: ${pkg}`, stdout) -} catch (error) { - console.error(`Failed to install optional dependency: ${pkg}`, error.stderr) + file.on("close", async () => { + const packageJsonString = await fs.promises.readFile("package.json", "utf8") + const packageJson = JSON.parse(packageJsonString) + packageJson.bin = {tailcall: output_path} + await fs.promises.writeFile("package.json", JSON.stringify(packageJson, null, 2), "utf8") + console.log("Tailcall binary downloaded successfully") + }) + file.on("error", (error) => { + console.error("Error while writing to a file - ", error) + }) + } catch (error) { + console.error("Error downloading", error.message) + } } diff --git a/npm/pre-install.js b/npm/pre-install.js index 6d008b256b..005a8bab63 100644 --- a/npm/pre-install.js +++ b/npm/pre-install.js @@ -1,10 +1,23 @@ +// @ts-check +import {familySync, GLIBC, MUSL} from "detect-libc" +import get_matched_platform from "./utils.js" + const os = process.platform const arch = process.arch +const libcFamily = familySync() + +let libc = "" +if (os === "win32") { + libc = "msvc" +} else { + libc = libcFamily === GLIBC ? "gnu" : libcFamily === MUSL ? "musl" : "" +} + +const matched_platform = get_matched_platform(os, arch, libc) -const dependency = Object.keys(optionalDependencies).find((name) => name.includes(`${os}-${arch}`)) -if (!dependency) { +if (matched_platform == null) { const redColor = "\x1b[31m" const resetColor = "\x1b[0m" - console.error(`${redColor} Tailcall does not support platform ${os} arch ${arch} ${resetColor}`) + console.error(`${redColor} Tailcall does not support platform - ${os}, arch - ${arch}, libc - ${libc} ${resetColor}`) process.exit(1) } diff --git a/npm/utils.js b/npm/utils.js new file mode 100644 index 0000000000..20cf97c048 --- /dev/null +++ b/npm/utils.js @@ -0,0 +1,24 @@ +import fs from "fs" +import {dirname, resolve} from "path" +import {fileURLToPath} from "url" +import YML from "yaml" + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +export default function get_matched_platform(os, arch, libc) { + const directoryPath = resolve(__dirname, "../") + const file = fs.readFileSync(resolve(directoryPath, "./build-matrix.yaml"), "utf8") + const build_matrix = YML.parse(file, {mapAsMap: true}) + + let found = null + build_matrix.get("include").forEach((platform) => { + const split = platform.get("build").split("-") + const platform_arch = split.at(1) + const platform_os = split.at(0) + const platform_libc = split.at(-1) + if (platform_arch == arch && platform_os == os && platform_libc == libc) { + found = platform + } + }) + return found +}