From ad551348137f57e5d9613c9133e5bbf294386916 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Fri, 22 Mar 2024 13:31:35 -0400 Subject: [PATCH 01/19] Repurpose the `cli.ts` module for EdgeDB CLI --- packages/driver/package.json | 2 +- packages/driver/src/cli.ts | 45 +++++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/driver/package.json b/packages/driver/package.json index f97f7a2fe..8000ff3d6 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -22,7 +22,7 @@ "./dist/index.node.js": "./dist/index.browser.js" }, "bin": { - "edgeql-js": "./dist/cli.js" + "edgedb": "./dist/cli.js" }, "devDependencies": { "@js-temporal/polyfill": "0.4.3", diff --git a/packages/driver/src/cli.ts b/packages/driver/src/cli.ts index aa6a232a9..1c0ead725 100644 --- a/packages/driver/src/cli.ts +++ b/packages/driver/src/cli.ts @@ -1,20 +1,39 @@ #!/usr/bin/env node +import { execSync } from "node:child_process"; -// tslint:disable:no-console -console.log( - `Failure: The \`npx edgeql-js\` command is no longer supported. +function isEdgeDBCLIInstalled() { + try { + execSync("edgedb --version", { stdio: "ignore" }); + return true; + } catch (error) { + return false; + } +} -To generate the EdgeDB query builder, install \`@edgedb/generate\` -package as a dev dependency in your local project. This package implements -a set of code generation tools for EdgeDB. +function installEdgeDBCLI() { + console.log("Installing EdgeDB CLI..."); + if (process.platform === "win32") { + execSync("iwr https://ps1.edgedb.com -useb | iex", { + stdio: "inherit", + shell: "powershell", + }); + } else { + execSync("curl https://sh.edgedb.com --proto '=https' -sSf1 | sh", { + stdio: "inherit", + }); + } +} - $ npm install -D @edgedb/generate (npm) - $ yarn add -D @edgedb/generate (yarn) +function runEdgeDBCLI(args: string[]) { + execSync(`edgedb ${args.join(" ")}`, { stdio: "inherit" }); +} -Then run the following command to generate the query builder. +function main(args: string[]) { + if (!isEdgeDBCLIInstalled()) { + installEdgeDBCLI(); + } - $ npx @edgedb/generate edgeql-js -` -); + runEdgeDBCLI(args); +} -export {}; +main(process.argv.slice(2)); From 81945ab5dc34f6c399f8831755bde4b826a4c57c Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Fri, 22 Mar 2024 14:13:42 -0400 Subject: [PATCH 02/19] Prefer `https` over curl/iwr --- packages/driver/src/cli.ts | 85 ++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/packages/driver/src/cli.ts b/packages/driver/src/cli.ts index 1c0ead725..54a174702 100644 --- a/packages/driver/src/cli.ts +++ b/packages/driver/src/cli.ts @@ -1,7 +1,70 @@ #!/usr/bin/env node import { execSync } from "node:child_process"; +import https from "node:https"; +import { createWriteStream, unlinkSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; -function isEdgeDBCLIInstalled() { +function downloadAndWriteScript( + url: string, + filePath: string, + callback: (err?: Error | null) => void +) { + const fileStream = createWriteStream(filePath); + const request = https.get(url, (response) => { + response.pipe(fileStream); + fileStream.on("finish", () => { + fileStream.close(callback); // Call the callback function once the stream is closed + }); + }); + + request.on("error", (err) => { + console.error("Error downloading the script:", err); + unlinkSync(filePath); // Delete the file in case of any error during the download + process.exit(1); + }); + + fileStream.on("error", (err) => { + console.error("Error writing the script to disk:", err); + unlinkSync(filePath); + process.exit(1); + }); +} + +function installEdgeDBCLI() { + console.log("Installing EdgeDB CLI..."); + + const url = + process.platform === "win32" + ? "https://ps1.edgedb.com" + : "https://sh.edgedb.com"; + const scriptPath = path.join(tmpdir(), "temp_install_script.sh"); + + downloadAndWriteScript(url, scriptPath, (closeError) => { + if (closeError) { + console.error("Error downloading the script:", closeError); + process.exit(1); + } + + if (process.platform === "win32") { + execSync( + `powershell -Command "iex (Get-Content ${scriptPath} | Out-String)"`, + { + stdio: "inherit", + } + ); + } else { + execSync(`sh ${scriptPath}`, { + stdio: "inherit", + }); + } + + // Clean up the temporary script file + unlinkSync(scriptPath); + }); +} + +function isEdgeDbCliInstalled() { try { execSync("edgedb --version", { stdio: "ignore" }); return true; @@ -10,30 +73,16 @@ function isEdgeDBCLIInstalled() { } } -function installEdgeDBCLI() { - console.log("Installing EdgeDB CLI..."); - if (process.platform === "win32") { - execSync("iwr https://ps1.edgedb.com -useb | iex", { - stdio: "inherit", - shell: "powershell", - }); - } else { - execSync("curl https://sh.edgedb.com --proto '=https' -sSf1 | sh", { - stdio: "inherit", - }); - } -} - -function runEdgeDBCLI(args: string[]) { +function runEdgeDbCli(args: string[]) { execSync(`edgedb ${args.join(" ")}`, { stdio: "inherit" }); } function main(args: string[]) { - if (!isEdgeDBCLIInstalled()) { + if (!isEdgeDbCliInstalled()) { installEdgeDBCLI(); } - runEdgeDBCLI(args); + runEdgeDbCli(args); } main(process.argv.slice(2)); From a3e00efee550cd4f2cb18b1996fb599d70786705 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Thu, 28 Mar 2024 13:20:52 -0400 Subject: [PATCH 03/19] Get binary directory from temporary CLI --- packages/driver/buildDeno.ts | 2 +- packages/driver/package.json | 11 +- packages/driver/src/cli.mts | 254 ++++++++++++++++++++++++++++++ packages/driver/src/cli.ts | 88 ----------- packages/driver/tsconfig.cli.json | 11 ++ packages/driver/tsconfig.esm.json | 21 +-- packages/driver/tsconfig.json | 26 +-- yarn.lock | 22 +++ 8 files changed, 302 insertions(+), 133 deletions(-) create mode 100644 packages/driver/src/cli.mts delete mode 100644 packages/driver/src/cli.ts create mode 100644 packages/driver/tsconfig.cli.json diff --git a/packages/driver/buildDeno.ts b/packages/driver/buildDeno.ts index d51586b3e..ec03aff89 100644 --- a/packages/driver/buildDeno.ts +++ b/packages/driver/buildDeno.ts @@ -12,7 +12,7 @@ await run({ destDir: "../deno", destEntriesToClean: ["_src", "mod.ts"], sourceFilter: (path) => { - return !/\/syntax\//.test(path); + return !(/\/syntax\//.test(path) || /cli\.mts$/.test(path)); }, pathRewriteRules: [ { match: /^src\/index.node.ts$/, replace: "mod.ts" }, diff --git a/packages/driver/package.json b/packages/driver/package.json index 8000ff3d6..84dfb3187 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -22,11 +22,12 @@ "./dist/index.node.js": "./dist/index.browser.js" }, "bin": { - "edgedb": "./dist/cli.js" + "edgedb": "./dist/cli.mjs" }, "devDependencies": { "@js-temporal/polyfill": "0.4.3", "@types/jest": "^29.5.2", + "@types/which": "^3.0.3", "fast-check": "^3.10.0", "get-stdin": "^9.0.0", "globby": "^13.2.0", @@ -38,7 +39,8 @@ }, "scripts": { "typecheck": "tsc --project tsconfig.json --noEmit", - "build": "echo 'Building edgedb-js...' && rm -rf dist && yarn build:cjs && yarn build:deno", + "build": "echo 'Building edgedb-js...' && rm -rf dist && yarn build:cjs && yarn build:cli && yarn build:deno", + "build:cli": "tsc --project tsconfig.cli.json", "build:cjs": "tsc --project tsconfig.json", "build:deno": "deno run --unstable --allow-all ./buildDeno.ts", "test": "npx --node-options='--experimental-fetch' jest --detectOpenHandles", @@ -47,5 +49,10 @@ "gen-errors": "edb gen-errors-json --client | node genErrors.mjs", "watch": "nodemon -e js,ts,tsx --ignore dist -x ", "dev": "yarn tsc --project tsconfig.json --incremental && yarn build:deno" + }, + "dependencies": { + "debug": "^4.3.4", + "env-paths": "^3.0.0", + "which": "^4.0.0" } } diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts new file mode 100644 index 000000000..e7b327572 --- /dev/null +++ b/packages/driver/src/cli.mts @@ -0,0 +1,254 @@ +#!/usr/bin/env node +import { execSync, type ExecSyncOptions } from "node:child_process"; +import * as os from "node:os"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as process from "node:process"; +import * as semver from "semver"; +import envPaths from "env-paths"; +import Debug from "debug"; +import which from "which"; + +const debug = Debug("edgedb:cli"); + +const EDGEDB_PKG_ROOT = "https://packages.edgedb.com"; +const EDGEDB_PKG_IDX = `${EDGEDB_PKG_ROOT}/archive/.jsonindexes`; +const CACHE_DIR = envPaths("edgedb").cache; +const TEMPORARY_CLI_PATH = path.join(CACHE_DIR, "/edgedb-cli"); +const CLI_CACHE_PATH = path.join(CACHE_DIR, "/cli-location"); + +interface Package { + name: string; + version: string; + revision: string; + installref: string; +} + +try { + await main(process.argv.slice(2)); + process.exit(0); +} catch (err) { + console.error(err); + process.exit(1); +} + +async function main(args: string[]) { + debug("Starting main function with args:", args); + const cliLocation = + whichEdgeDbCli() ?? + (await getCliLocationFromCache()) ?? + (await installEdgeDbCli()) ?? + null; + + return runEdgeDbCli(args, cliLocation); +} + +async function installEdgeDbCli(): Promise { + debug("Installing EdgeDB CLI..."); + await downloadCliPackage(); + + const binDir = getBinDir(TEMPORARY_CLI_PATH); + fs.writeFileSync(CLI_CACHE_PATH, binDir, { encoding: "utf8" }); + debug("CLI installed at:", binDir); + + if (!fs.existsSync(path.join(binDir, "edgedb"))) { + debug("Self-installing EdgeDB CLI..."); + selfInstallEdgeDbCli(TEMPORARY_CLI_PATH); + } + return binDir; +} + +async function downloadCliPackage() { + debug("Downloading CLI package..."); + const cliPkg = await findPackage(); + const downloadDir = path.dirname(TEMPORARY_CLI_PATH); + if (!fs.existsSync(downloadDir)) { + fs.mkdirSync(downloadDir, { recursive: true }); + } + const downloadUrl = new URL(cliPkg.installref, EDGEDB_PKG_ROOT); + await downloadFile(downloadUrl, TEMPORARY_CLI_PATH); + debug("CLI package downloaded to:", TEMPORARY_CLI_PATH); + + fs.chmodSync(TEMPORARY_CLI_PATH, 0o755); +} + +async function getCliLocationFromCache(): Promise { + debug("Checking CLI cache..."); + if (fs.existsSync(CLI_CACHE_PATH)) { + const cachedBinDir = fs + .readFileSync(CLI_CACHE_PATH, { encoding: "utf8" }) + .trim(); + if (cachedBinDir && fs.existsSync(path.join(cachedBinDir, "edgedb"))) { + debug("CLI found in cache at:", cachedBinDir); + return cachedBinDir; + } + } + debug("No CLI found in cache."); + return null; +} + +function whichEdgeDbCli() { + debug("Checking if CLI is in PATH..."); + const location = which.sync("edgedb", { nothrow: true }); + debug("CLI location:", location); + if (location) { + return path.dirname(location); + } + return null; +} + +function runEdgeDbCli( + args: string[], + pathToCli: string | null, + execOptions: ExecSyncOptions = { stdio: "inherit" } +) { + const cliCommand = path.join(pathToCli ?? "", "edgedb"); + debug("Running EdgeDB CLI command:", cliCommand, "with args:", args); + return execSync(`${cliCommand} ${args.join(" ")}`, execOptions); +} + +function selfInstallEdgeDbCli(pathToCli: string) { + debug("Self-installing EdgeDB CLI..."); + return runEdgeDbCli(["_self_install"], pathToCli); +} + +async function findPackage(): Promise { + debug("Finding compatible package..."); + const arch = os.arch(); + const platform = os.platform(); + const includeCliPrereleases = true; + const cliVersionRange = "*"; + const libc = platform === "linux" ? "musl" : ""; + const dist = getBaseDist(arch, platform, libc); + + const versionMap = await getVersionMap(dist); + const pkg = await getMatchingPkg( + versionMap, + cliVersionRange, + includeCliPrereleases + ); + if (!pkg) { + throw Error( + "No compatible EdgeDB CLI package found for the current platform" + ); + } + debug("Package found:", pkg); + return pkg; +} + +async function getVersionMap(dist: string): Promise> { + debug("Getting version map for distribution:", dist); + const indexRequest = await fetch(`${EDGEDB_PKG_IDX}/${dist}.json`); + const index = (await indexRequest.json()) as { packages: Package[] }; + const versionMap = new Map(); + + for (const pkg of index.packages) { + if (pkg.name !== "edgedb-cli") { + continue; + } + + if ( + !versionMap.has(pkg.version) || + versionMap.get(pkg.version).revision < pkg.revision + ) { + versionMap.set(pkg.version, pkg); + } + } + + return versionMap; +} + +async function getMatchingPkg( + versionMap: Map, + cliVersionRange: string, + includeCliPrereleases: boolean +): Promise { + debug("Getting matching version for range:", cliVersionRange); + let matchingPkg: Package | null = null; + for (const [version, pkg] of versionMap.entries()) { + if ( + semver.satisfies(version, cliVersionRange, { + includePrerelease: includeCliPrereleases, + }) + ) { + if ( + !matchingPkg || + semver.compareBuild(version, matchingPkg.version) > 0 + ) { + matchingPkg = pkg; + } + } + } + + if (matchingPkg) { + debug("Matching version found:", matchingPkg.version); + return matchingPkg; + } else { + throw Error( + "no published EdgeDB CLI version matches requested version " + + `'${cliVersionRange}'` + ); + } +} + +async function downloadFile(url: string | URL, path: string) { + debug("Downloading file from URL:", url); + const response = await fetch(url); + const fileStream = fs.createWriteStream(path); + if (response.body) { + for await (const chunk of streamReader(response.body)) { + fileStream.write(chunk); + } + fileStream.end(); + debug("File downloaded successfully."); + } else { + throw new Error("Download failed: no response body"); + } +} + +function getBaseDist(arch: string, platform: string, libc = ""): string { + debug("Getting base distribution for:", arch, platform, libc); + let distArch = ""; + let distPlatform = ""; + + if (platform === "linux") { + if (libc === "") { + libc = "gnu"; + } + distPlatform = `unknown-linux-${libc}`; + } else if (platform === "darwin") { + distPlatform = "apple-darwin"; + } else { + throw Error(`This action cannot be run on ${platform}`); + } + + if (arch === "x64") { + distArch = "x86_64"; + } else if (arch === "arm64") { + distArch = "aarch64"; + } else { + throw Error(`This action does not support the ${arch} architecture`); + } + + const dist = `${distArch}-${distPlatform}`; + debug("Base distribution:", dist); + return dist; +} + +function getBinDir(cliPath: string): string { + debug("Getting binary directory for CLI path:", cliPath); + const binDir = runEdgeDbCli(["info", "--get", "'bin-dir'"], cliPath); + debug("Binary directory:", binDir); + return binDir.toString().trim(); +} + +async function* streamReader(readableStream: ReadableStream) { + debug("Reading stream..."); + const reader = readableStream.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + yield value; + } + debug("Stream reading completed."); +} diff --git a/packages/driver/src/cli.ts b/packages/driver/src/cli.ts deleted file mode 100644 index 54a174702..000000000 --- a/packages/driver/src/cli.ts +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import https from "node:https"; -import { createWriteStream, unlinkSync } from "node:fs"; -import { tmpdir } from "node:os"; -import path from "node:path"; - -function downloadAndWriteScript( - url: string, - filePath: string, - callback: (err?: Error | null) => void -) { - const fileStream = createWriteStream(filePath); - const request = https.get(url, (response) => { - response.pipe(fileStream); - fileStream.on("finish", () => { - fileStream.close(callback); // Call the callback function once the stream is closed - }); - }); - - request.on("error", (err) => { - console.error("Error downloading the script:", err); - unlinkSync(filePath); // Delete the file in case of any error during the download - process.exit(1); - }); - - fileStream.on("error", (err) => { - console.error("Error writing the script to disk:", err); - unlinkSync(filePath); - process.exit(1); - }); -} - -function installEdgeDBCLI() { - console.log("Installing EdgeDB CLI..."); - - const url = - process.platform === "win32" - ? "https://ps1.edgedb.com" - : "https://sh.edgedb.com"; - const scriptPath = path.join(tmpdir(), "temp_install_script.sh"); - - downloadAndWriteScript(url, scriptPath, (closeError) => { - if (closeError) { - console.error("Error downloading the script:", closeError); - process.exit(1); - } - - if (process.platform === "win32") { - execSync( - `powershell -Command "iex (Get-Content ${scriptPath} | Out-String)"`, - { - stdio: "inherit", - } - ); - } else { - execSync(`sh ${scriptPath}`, { - stdio: "inherit", - }); - } - - // Clean up the temporary script file - unlinkSync(scriptPath); - }); -} - -function isEdgeDbCliInstalled() { - try { - execSync("edgedb --version", { stdio: "ignore" }); - return true; - } catch (error) { - return false; - } -} - -function runEdgeDbCli(args: string[]) { - execSync(`edgedb ${args.join(" ")}`, { stdio: "inherit" }); -} - -function main(args: string[]) { - if (!isEdgeDbCliInstalled()) { - installEdgeDBCLI(); - } - - runEdgeDbCli(args); -} - -main(process.argv.slice(2)); diff --git a/packages/driver/tsconfig.cli.json b/packages/driver/tsconfig.cli.json new file mode 100644 index 000000000..17acad0ad --- /dev/null +++ b/packages/driver/tsconfig.cli.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "node16", + "moduleResolution": "node16", + "outDir": "./dist", + "declaration": false + }, + "include": ["src/cli.mts"] +} + diff --git a/packages/driver/tsconfig.esm.json b/packages/driver/tsconfig.esm.json index 811c47a98..74ea3ea9e 100644 --- a/packages/driver/tsconfig.esm.json +++ b/packages/driver/tsconfig.esm.json @@ -4,23 +4,8 @@ "module": "es2015", "outDir": "./dist/__esm", "declaration": false, - "declarationDir": null, - // "paths": { - // "edgedb": [ - // "./src/index.node" - // ], - // "@generated/*": [ - // "./src/syntax/genMock/*" - // ], - // } + "declarationDir": null }, - "include": [ - "src/syntax", - ], - "exclude": [ - "**/*.deno.ts", - "test/deno/*", - "dist", - "qb", - ] + "include": ["src/syntax"], + "exclude": ["**/*.deno.ts", "test/deno/*", "dist", "qb"] } diff --git a/packages/driver/tsconfig.json b/packages/driver/tsconfig.json index 73bb1f242..adcac3d7f 100644 --- a/packages/driver/tsconfig.json +++ b/packages/driver/tsconfig.json @@ -4,29 +4,7 @@ "declarationDir": "./dist", "outDir": "./dist", "downlevelIteration": true - // "baseUrl": ".", - // "typeRoots": [ - // "./node_modules/@types" - // ], - // "paths": { - // "edgedb": [ - // "./src/index.node" - // ], - // "@generated/*": [ - // "./src/syntax/genMock/*" - // ], - // } }, - "include": [ - "src", - // "../generate/src/generate.ts", - // "../generate/src/util/functionUtils.ts", - // "../generate/src/util/genutil.ts", - ], - "exclude": [ - "**/*.deno.ts", - // "test/deno/*", - "dist", - // "qb", - ] + "include": ["src"], + "exclude": ["src/cli.mts", "**/*.deno.ts", "dist"] } diff --git a/yarn.lock b/yarn.lock index df2640e3a..79e4a0394 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1605,6 +1605,11 @@ resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== +"@types/which@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/which/-/which-3.0.3.tgz#41142ed5a4743128f1bc0b69c46890f0453ddb89" + integrity sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" @@ -2403,6 +2408,11 @@ entities@^4.4.0: resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +env-paths@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-3.0.0.tgz#2f1e89c2f6dbd3408e1b1711dd82d62e317f58da" + integrity sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" @@ -3313,6 +3323,11 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" @@ -5260,6 +5275,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +which@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" + integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== + dependencies: + isexe "^3.1.1" + word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" From c769bc29cff0e5f37a58dfa6e03911b7e7806836 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Thu, 28 Mar 2024 16:19:12 -0400 Subject: [PATCH 04/19] Update for `install-dir` Changed the naming of this during the implementation in the CLI --- packages/driver/src/cli.mts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index e7b327572..882cb7a23 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -15,7 +15,7 @@ const EDGEDB_PKG_ROOT = "https://packages.edgedb.com"; const EDGEDB_PKG_IDX = `${EDGEDB_PKG_ROOT}/archive/.jsonindexes`; const CACHE_DIR = envPaths("edgedb").cache; const TEMPORARY_CLI_PATH = path.join(CACHE_DIR, "/edgedb-cli"); -const CLI_CACHE_PATH = path.join(CACHE_DIR, "/cli-location"); +const CLI_LOCATION_CACHE_FILE_PATH = path.join(CACHE_DIR, "/cli-location"); interface Package { name: string; @@ -47,15 +47,15 @@ async function installEdgeDbCli(): Promise { debug("Installing EdgeDB CLI..."); await downloadCliPackage(); - const binDir = getBinDir(TEMPORARY_CLI_PATH); - fs.writeFileSync(CLI_CACHE_PATH, binDir, { encoding: "utf8" }); - debug("CLI installed at:", binDir); + const installDir = getInstallDir(TEMPORARY_CLI_PATH); + fs.writeFileSync(CLI_LOCATION_CACHE_FILE_PATH, installDir, { encoding: "utf8" }); + debug("CLI installed at:", installDir); - if (!fs.existsSync(path.join(binDir, "edgedb"))) { + if (!fs.existsSync(path.join(installDir, "edgedb"))) { debug("Self-installing EdgeDB CLI..."); selfInstallEdgeDbCli(TEMPORARY_CLI_PATH); } - return binDir; + return installDir; } async function downloadCliPackage() { @@ -74,9 +74,9 @@ async function downloadCliPackage() { async function getCliLocationFromCache(): Promise { debug("Checking CLI cache..."); - if (fs.existsSync(CLI_CACHE_PATH)) { + if (fs.existsSync(CLI_LOCATION_CACHE_FILE_PATH)) { const cachedBinDir = fs - .readFileSync(CLI_CACHE_PATH, { encoding: "utf8" }) + .readFileSync(CLI_LOCATION_CACHE_FILE_PATH, { encoding: "utf8" }) .trim(); if (cachedBinDir && fs.existsSync(path.join(cachedBinDir, "edgedb"))) { debug("CLI found in cache at:", cachedBinDir); @@ -235,9 +235,9 @@ function getBaseDist(arch: string, platform: string, libc = ""): string { return dist; } -function getBinDir(cliPath: string): string { +function getInstallDir(cliPath: string): string { debug("Getting binary directory for CLI path:", cliPath); - const binDir = runEdgeDbCli(["info", "--get", "'bin-dir'"], cliPath); + const binDir = runEdgeDbCli(["info", "--get", "'install-dir'"], cliPath); debug("Binary directory:", binDir); return binDir.toString().trim(); } From a87d8216a3321ad7be0558a7e42ff1c38bfc4a95 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Thu, 28 Mar 2024 16:19:40 -0400 Subject: [PATCH 05/19] Use a URL instead of strings --- packages/driver/src/cli.mts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index 882cb7a23..cd668c208 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -12,7 +12,7 @@ import which from "which"; const debug = Debug("edgedb:cli"); const EDGEDB_PKG_ROOT = "https://packages.edgedb.com"; -const EDGEDB_PKG_IDX = `${EDGEDB_PKG_ROOT}/archive/.jsonindexes`; +const EDGEDB_PKG_IDX = new URL("archive/.jsonindexes", EDGEDB_PKG_ROOT); const CACHE_DIR = envPaths("edgedb").cache; const TEMPORARY_CLI_PATH = path.join(CACHE_DIR, "/edgedb-cli"); const CLI_LOCATION_CACHE_FILE_PATH = path.join(CACHE_DIR, "/cli-location"); @@ -138,7 +138,7 @@ async function findPackage(): Promise { async function getVersionMap(dist: string): Promise> { debug("Getting version map for distribution:", dist); - const indexRequest = await fetch(`${EDGEDB_PKG_IDX}/${dist}.json`); + const indexRequest = await fetch(new URL(`${dist}.json`, EDGEDB_PKG_IDX)); const index = (await indexRequest.json()) as { packages: Package[] }; const versionMap = new Map(); From bc89528ba805db4e62dcc80a38b50f6a59faabcb Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Thu, 28 Mar 2024 16:22:43 -0400 Subject: [PATCH 06/19] Avoid building an intermediate URL object --- packages/driver/src/cli.mts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index cd668c208..09686f362 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -12,7 +12,6 @@ import which from "which"; const debug = Debug("edgedb:cli"); const EDGEDB_PKG_ROOT = "https://packages.edgedb.com"; -const EDGEDB_PKG_IDX = new URL("archive/.jsonindexes", EDGEDB_PKG_ROOT); const CACHE_DIR = envPaths("edgedb").cache; const TEMPORARY_CLI_PATH = path.join(CACHE_DIR, "/edgedb-cli"); const CLI_LOCATION_CACHE_FILE_PATH = path.join(CACHE_DIR, "/cli-location"); @@ -48,7 +47,9 @@ async function installEdgeDbCli(): Promise { await downloadCliPackage(); const installDir = getInstallDir(TEMPORARY_CLI_PATH); - fs.writeFileSync(CLI_LOCATION_CACHE_FILE_PATH, installDir, { encoding: "utf8" }); + fs.writeFileSync(CLI_LOCATION_CACHE_FILE_PATH, installDir, { + encoding: "utf8", + }); debug("CLI installed at:", installDir); if (!fs.existsSync(path.join(installDir, "edgedb"))) { @@ -138,7 +139,9 @@ async function findPackage(): Promise { async function getVersionMap(dist: string): Promise> { debug("Getting version map for distribution:", dist); - const indexRequest = await fetch(new URL(`${dist}.json`, EDGEDB_PKG_IDX)); + const indexRequest = await fetch( + new URL(`archive/.jsonindexes/${dist}.json`, EDGEDB_PKG_ROOT) + ); const index = (await indexRequest.json()) as { packages: Package[] }; const versionMap = new Map(); From 7679bccf4a4cd7dedd21321daeca9cfcaf89fb33 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Fri, 29 Mar 2024 09:44:51 -0400 Subject: [PATCH 07/19] Simplify getting the cliLocation update debug --- packages/driver/src/cli.mts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index 09686f362..cbfa7e313 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -34,11 +34,15 @@ try { async function main(args: string[]) { debug("Starting main function with args:", args); const cliLocation = - whichEdgeDbCli() ?? + (await whichEdgeDbCli()) ?? (await getCliLocationFromCache()) ?? (await installEdgeDbCli()) ?? null; + if (cliLocation === null) { + throw Error("Failed to find or install EdgeDB CLI."); + } + return runEdgeDbCli(args, cliLocation); } @@ -88,10 +92,10 @@ async function getCliLocationFromCache(): Promise { return null; } -function whichEdgeDbCli() { +async function whichEdgeDbCli() { debug("Checking if CLI is in PATH..."); - const location = which.sync("edgedb", { nothrow: true }); - debug("CLI location:", location); + const location = await which("edgedb", { nothrow: true }); + debug(`CLI found in PATH at: ${location}`); if (location) { return path.dirname(location); } @@ -104,8 +108,9 @@ function runEdgeDbCli( execOptions: ExecSyncOptions = { stdio: "inherit" } ) { const cliCommand = path.join(pathToCli ?? "", "edgedb"); - debug("Running EdgeDB CLI command:", cliCommand, "with args:", args); - return execSync(`${cliCommand} ${args.join(" ")}`, execOptions); + const command = `${cliCommand} ${args.join(" ")}`; + debug(`Running EdgeDB CLI: ${command}`); + return execSync(command, execOptions); } function selfInstallEdgeDbCli(pathToCli: string) { @@ -114,7 +119,6 @@ function selfInstallEdgeDbCli(pathToCli: string) { } async function findPackage(): Promise { - debug("Finding compatible package..."); const arch = os.arch(); const platform = os.platform(); const includeCliPrereleases = true; @@ -122,6 +126,7 @@ async function findPackage(): Promise { const libc = platform === "linux" ? "musl" : ""; const dist = getBaseDist(arch, platform, libc); + debug(`Finding compatible package for ${dist}...`); const versionMap = await getVersionMap(dist); const pkg = await getMatchingPkg( versionMap, From 08c45f4f2b28bdf4837f13bf94049f88c517193d Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Tue, 2 Apr 2024 09:24:25 -0400 Subject: [PATCH 08/19] Fix logic based on actual usage Mostly involved confusion between paths to directories and paths to the binary files we are interested in. Also fixes a small issue with a race condition on writing the downloaded temporary CLI, changing the mode of the file, and executing the binary. --- packages/driver/src/cli.mts | 80 +++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index cbfa7e313..be4a428b3 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -1,7 +1,8 @@ #!/usr/bin/env node import { execSync, type ExecSyncOptions } from "node:child_process"; +import { createWriteStream } from "node:fs"; import * as os from "node:os"; -import * as fs from "node:fs"; +import * as fs from "node:fs/promises"; import * as path from "node:path"; import * as process from "node:process"; import * as semver from "semver"; @@ -28,6 +29,15 @@ try { process.exit(0); } catch (err) { console.error(err); + if ( + typeof err === "object" && + err !== null && + "code" in err && + typeof err.code === "number" + ) { + process.exit(err.code); + } + process.exit(1); } @@ -51,45 +61,59 @@ async function installEdgeDbCli(): Promise { await downloadCliPackage(); const installDir = getInstallDir(TEMPORARY_CLI_PATH); - fs.writeFileSync(CLI_LOCATION_CACHE_FILE_PATH, installDir, { + const binaryPath = path.join(installDir, "edgedb"); + await fs.writeFile(CLI_LOCATION_CACHE_FILE_PATH, binaryPath, { encoding: "utf8", }); - debug("CLI installed at:", installDir); + debug("CLI installed at:", binaryPath); - if (!fs.existsSync(path.join(installDir, "edgedb"))) { + try { + await fs.access(binaryPath, fs.constants.F_OK); + } catch { debug("Self-installing EdgeDB CLI..."); selfInstallEdgeDbCli(TEMPORARY_CLI_PATH); } - return installDir; + return binaryPath; } async function downloadCliPackage() { + console.log("No EdgeDB CLI found, downloading CLI package..."); debug("Downloading CLI package..."); const cliPkg = await findPackage(); const downloadDir = path.dirname(TEMPORARY_CLI_PATH); - if (!fs.existsSync(downloadDir)) { - fs.mkdirSync(downloadDir, { recursive: true }); - } + await fs.mkdir(downloadDir, { recursive: true }).catch((error) => { + if (error.code !== "EEXIST") throw error; + }); const downloadUrl = new URL(cliPkg.installref, EDGEDB_PKG_ROOT); await downloadFile(downloadUrl, TEMPORARY_CLI_PATH); debug("CLI package downloaded to:", TEMPORARY_CLI_PATH); - fs.chmodSync(TEMPORARY_CLI_PATH, 0o755); + await fs.chmod(TEMPORARY_CLI_PATH, 0o755); + const stat = await fs.stat(TEMPORARY_CLI_PATH); + debug("CLI package stats:", stat); } async function getCliLocationFromCache(): Promise { debug("Checking CLI cache..."); - if (fs.existsSync(CLI_LOCATION_CACHE_FILE_PATH)) { - const cachedBinDir = fs - .readFileSync(CLI_LOCATION_CACHE_FILE_PATH, { encoding: "utf8" }) - .trim(); - if (cachedBinDir && fs.existsSync(path.join(cachedBinDir, "edgedb"))) { - debug("CLI found in cache at:", cachedBinDir); - return cachedBinDir; + try { + const cachedBinaryPath = ( + await fs.readFile(CLI_LOCATION_CACHE_FILE_PATH, { encoding: "utf8" }) + ).trim(); + debug("CLI path in cache at:", cachedBinaryPath); + try { + await fs.access(cachedBinaryPath, fs.constants.F_OK); + debug("CLI binary found in path:", cachedBinaryPath); + return cachedBinaryPath; + } catch (err) { + // EdgeDB binary not found in the cached directory + debug("No CLI found in cache.", err); + return null; } + } catch (err) { + // Cache file does not exist or other error + debug("Cache directory does not exist.", err); + return null; } - debug("No CLI found in cache."); - return null; } async function whichEdgeDbCli() { @@ -97,7 +121,7 @@ async function whichEdgeDbCli() { const location = await which("edgedb", { nothrow: true }); debug(`CLI found in PATH at: ${location}`); if (location) { - return path.dirname(location); + return location; } return null; } @@ -107,7 +131,7 @@ function runEdgeDbCli( pathToCli: string | null, execOptions: ExecSyncOptions = { stdio: "inherit" } ) { - const cliCommand = path.join(pathToCli ?? "", "edgedb"); + const cliCommand = pathToCli ?? "edgedb"; const command = `${cliCommand} ${args.join(" ")}`; debug(`Running EdgeDB CLI: ${command}`); return execSync(command, execOptions); @@ -124,7 +148,7 @@ async function findPackage(): Promise { const includeCliPrereleases = true; const cliVersionRange = "*"; const libc = platform === "linux" ? "musl" : ""; - const dist = getBaseDist(arch, platform, libc); + const dist = getBaseDist(arch, platform, libc) + ".nightly"; debug(`Finding compatible package for ${dist}...`); const versionMap = await getVersionMap(dist); @@ -202,7 +226,7 @@ async function getMatchingPkg( async function downloadFile(url: string | URL, path: string) { debug("Downloading file from URL:", url); const response = await fetch(url); - const fileStream = fs.createWriteStream(path); + const fileStream = createWriteStream(path); if (response.body) { for await (const chunk of streamReader(response.body)) { fileStream.write(chunk); @@ -244,10 +268,14 @@ function getBaseDist(arch: string, platform: string, libc = ""): string { } function getInstallDir(cliPath: string): string { - debug("Getting binary directory for CLI path:", cliPath); - const binDir = runEdgeDbCli(["info", "--get", "'install-dir'"], cliPath); - debug("Binary directory:", binDir); - return binDir.toString().trim(); + debug("Getting install directory for CLI path:", cliPath); + const installDir = runEdgeDbCli(["info", "--get", "'install-dir'"], cliPath, { + stdio: "pipe", + }) + .toString() + .trim(); + debug("Install directory:", installDir); + return installDir; } async function* streamReader(readableStream: ReadableStream) { From 0a868797da3b4b85f1ce73813bedf03e013d281e Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Tue, 2 Apr 2024 11:23:16 -0400 Subject: [PATCH 09/19] Format and better error message --- packages/driver/src/cli.mts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index be4a428b3..b37eb98d9 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -99,19 +99,18 @@ async function getCliLocationFromCache(): Promise { const cachedBinaryPath = ( await fs.readFile(CLI_LOCATION_CACHE_FILE_PATH, { encoding: "utf8" }) ).trim(); - debug("CLI path in cache at:", cachedBinaryPath); + debug("CLI path in cache at:", cachedBinaryPath); + try { await fs.access(cachedBinaryPath, fs.constants.F_OK); debug("CLI binary found in path:", cachedBinaryPath); return cachedBinaryPath; } catch (err) { - // EdgeDB binary not found in the cached directory debug("No CLI found in cache.", err); return null; } } catch (err) { - // Cache file does not exist or other error - debug("Cache directory does not exist.", err); + debug("Cache file cannot be read.", err); return null; } } From 83cc3777e1c30885f5aff8e44c3638f8ce085a11 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Tue, 2 Apr 2024 11:31:48 -0400 Subject: [PATCH 10/19] Add `mts` files to prettier check --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d676762e5..ef8f14ba4 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "scripts": { "lint": "tslint 'packages/*/src/**/*.ts'", "eslint": "eslint 'packages/*/src/**/*.ts'", - "format:check": "prettier --check 'packages/*/src/**/*.ts' 'packages/*/test/**/*.ts'", - "format": "prettier --write 'packages/*/src/**/*.ts' 'packages/*/test/**/*.ts'", + "format:check": "prettier --check 'packages/*/src/**/*.(mts|ts)' 'packages/*/test/**/*.ts'", + "format": "prettier --write 'packages/*/src/**/*.(mts|ts)' 'packages/*/test/**/*.ts'", "generate": "yarn workspace @edgedb/generate generate" } } From e1d6bf03dd9b110da147d15128a6f6067cfc5585 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Tue, 2 Apr 2024 13:40:13 -0400 Subject: [PATCH 11/19] Add explicit dependency on semver --- packages/driver/package.json | 1 + yarn.lock | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/packages/driver/package.json b/packages/driver/package.json index 84dfb3187..df298afc2 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -53,6 +53,7 @@ "dependencies": { "debug": "^4.3.4", "env-paths": "^3.0.0", + "semver": "^7.6.0", "which": "^4.0.0" } } diff --git a/yarn.lock b/yarn.lock index 79e4a0394..01f504120 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4598,6 +4598,13 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + send@0.18.0: version "0.18.0" resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" From 6f2839bcf0ec2bafd3e2ad1171a91ad9383331f4 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Tue, 2 Apr 2024 16:18:10 -0400 Subject: [PATCH 12/19] Use compatible stable version --- packages/driver/src/cli.mts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index b37eb98d9..d2fc01cc3 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -145,9 +145,9 @@ async function findPackage(): Promise { const arch = os.arch(); const platform = os.platform(); const includeCliPrereleases = true; - const cliVersionRange = "*"; + const cliVersionRange = ">=4.1.1"; const libc = platform === "linux" ? "musl" : ""; - const dist = getBaseDist(arch, platform, libc) + ".nightly"; + const dist = getBaseDist(arch, platform, libc); debug(`Finding compatible package for ${dist}...`); const versionMap = await getVersionMap(dist); From 3cf551144d133349e099680a526743dd3b1688ed Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Tue, 2 Apr 2024 16:18:28 -0400 Subject: [PATCH 13/19] Check preconditions before opening fd --- packages/driver/src/cli.mts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index d2fc01cc3..d144f803b 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -225,7 +225,12 @@ async function getMatchingPkg( async function downloadFile(url: string | URL, path: string) { debug("Downloading file from URL:", url); const response = await fetch(url); - const fileStream = createWriteStream(path); + if (!response.ok || !response.body) { + throw new Error(`Download failed: ${response.statusText}`); + } + + const fileStream = createWriteStream(path, { flush: true }); + if (response.body) { for await (const chunk of streamReader(response.body)) { fileStream.write(chunk); From f93e76fd23bf6fc29b715c05550b368110f2978b Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Tue, 2 Apr 2024 16:19:13 -0400 Subject: [PATCH 14/19] Ensure data is flushed after downloading --- packages/driver/src/cli.mts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index d144f803b..8d5e59e2b 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -88,9 +88,10 @@ async function downloadCliPackage() { await downloadFile(downloadUrl, TEMPORARY_CLI_PATH); debug("CLI package downloaded to:", TEMPORARY_CLI_PATH); - await fs.chmod(TEMPORARY_CLI_PATH, 0o755); - const stat = await fs.stat(TEMPORARY_CLI_PATH); - debug("CLI package stats:", stat); + const fd = await fs.open(TEMPORARY_CLI_PATH, "r+"); + await fd.chmod(0o755); + await fd.datasync(); + await fd.close(); } async function getCliLocationFromCache(): Promise { From 7819a584bfc7ec0325f62b9a76fb15ad80ebe105 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Wed, 3 Apr 2024 12:48:35 -0400 Subject: [PATCH 15/19] Break up temporary CLI and self-install steps Make it more explicit that the temporary CLI might point to an existing binary if it happens to exist at the install directory already. In that case, we don't run the self-install. --- packages/driver/src/cli.mts | 113 +++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index 8d5e59e2b..5bbb04d16 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -46,7 +46,8 @@ async function main(args: string[]) { const cliLocation = (await whichEdgeDbCli()) ?? (await getCliLocationFromCache()) ?? - (await installEdgeDbCli()) ?? + (await getCliLocationFromTempCli()) ?? + (await selfInstallFromTempCli()) ?? null; if (cliLocation === null) { @@ -56,8 +57,41 @@ async function main(args: string[]) { return runEdgeDbCli(args, cliLocation); } -async function installEdgeDbCli(): Promise { - debug("Installing EdgeDB CLI..."); +async function whichEdgeDbCli() { + debug("Checking if CLI is in PATH..."); + const location = await which("edgedb", { nothrow: true }); + if (location) { + debug(` - CLI found in PATH at: ${location}`); + return location; + } + debug(" - No CLI found in PATH.") + return null; +} + +async function getCliLocationFromCache(): Promise { + debug("Checking CLI cache..."); + try { + const cachedBinaryPath = ( + await fs.readFile(CLI_LOCATION_CACHE_FILE_PATH, { encoding: "utf8" }) + ).trim(); + debug(" - CLI path in cache at:", cachedBinaryPath); + + try { + await fs.access(cachedBinaryPath, fs.constants.F_OK); + debug(" - CLI binary found in path:", cachedBinaryPath); + return cachedBinaryPath; + } catch (err) { + debug(" - No CLI found in cache.", err); + return null; + } + } catch (err) { + debug(" - Cache file cannot be read.", err); + return null; + } +} + +async function getCliLocationFromTempCli(): Promise { + debug("Installing temporary CLI to get install directory..."); await downloadCliPackage(); const installDir = getInstallDir(TEMPORARY_CLI_PATH); @@ -65,15 +99,23 @@ async function installEdgeDbCli(): Promise { await fs.writeFile(CLI_LOCATION_CACHE_FILE_PATH, binaryPath, { encoding: "utf8", }); - debug("CLI installed at:", binaryPath); + debug(" - CLI installed at:", binaryPath); try { + debug(" - CLI binary found in path:", binaryPath); await fs.access(binaryPath, fs.constants.F_OK); + return binaryPath; } catch { - debug("Self-installing EdgeDB CLI..."); - selfInstallEdgeDbCli(TEMPORARY_CLI_PATH); + debug(" - CLI binary not found in path:", binaryPath); + return null; } - return binaryPath; +} + +async function selfInstallFromTempCli(): Promise { + debug("Self-installing EdgeDB CLI..."); + runEdgeDbCli(["_self_install"], TEMPORARY_CLI_PATH); + debug(" - CLI self-installed successfully."); + return getCliLocationFromCache(); } async function downloadCliPackage() { @@ -86,7 +128,7 @@ async function downloadCliPackage() { }); const downloadUrl = new URL(cliPkg.installref, EDGEDB_PKG_ROOT); await downloadFile(downloadUrl, TEMPORARY_CLI_PATH); - debug("CLI package downloaded to:", TEMPORARY_CLI_PATH); + debug(" - CLI package downloaded to:", TEMPORARY_CLI_PATH); const fd = await fs.open(TEMPORARY_CLI_PATH, "r+"); await fd.chmod(0o755); @@ -94,38 +136,6 @@ async function downloadCliPackage() { await fd.close(); } -async function getCliLocationFromCache(): Promise { - debug("Checking CLI cache..."); - try { - const cachedBinaryPath = ( - await fs.readFile(CLI_LOCATION_CACHE_FILE_PATH, { encoding: "utf8" }) - ).trim(); - debug("CLI path in cache at:", cachedBinaryPath); - - try { - await fs.access(cachedBinaryPath, fs.constants.F_OK); - debug("CLI binary found in path:", cachedBinaryPath); - return cachedBinaryPath; - } catch (err) { - debug("No CLI found in cache.", err); - return null; - } - } catch (err) { - debug("Cache file cannot be read.", err); - return null; - } -} - -async function whichEdgeDbCli() { - debug("Checking if CLI is in PATH..."); - const location = await which("edgedb", { nothrow: true }); - debug(`CLI found in PATH at: ${location}`); - if (location) { - return location; - } - return null; -} - function runEdgeDbCli( args: string[], pathToCli: string | null, @@ -137,11 +147,6 @@ function runEdgeDbCli( return execSync(command, execOptions); } -function selfInstallEdgeDbCli(pathToCli: string) { - debug("Self-installing EdgeDB CLI..."); - return runEdgeDbCli(["_self_install"], pathToCli); -} - async function findPackage(): Promise { const arch = os.arch(); const platform = os.platform(); @@ -159,10 +164,10 @@ async function findPackage(): Promise { ); if (!pkg) { throw Error( - "No compatible EdgeDB CLI package found for the current platform" + " - No compatible EdgeDB CLI package found for the current platform" ); } - debug("Package found:", pkg); + debug(" - Package found:", pkg); return pkg; } @@ -213,7 +218,7 @@ async function getMatchingPkg( } if (matchingPkg) { - debug("Matching version found:", matchingPkg.version); + debug(" - Matching version found:", matchingPkg.version); return matchingPkg; } else { throw Error( @@ -227,7 +232,7 @@ async function downloadFile(url: string | URL, path: string) { debug("Downloading file from URL:", url); const response = await fetch(url); if (!response.ok || !response.body) { - throw new Error(`Download failed: ${response.statusText}`); + throw new Error(` - Download failed: ${response.statusText}`); } const fileStream = createWriteStream(path, { flush: true }); @@ -237,9 +242,9 @@ async function downloadFile(url: string | URL, path: string) { fileStream.write(chunk); } fileStream.end(); - debug("File downloaded successfully."); + debug(" - File downloaded successfully."); } else { - throw new Error("Download failed: no response body"); + throw new Error(" - Download failed: no response body"); } } @@ -268,7 +273,7 @@ function getBaseDist(arch: string, platform: string, libc = ""): string { } const dist = `${distArch}-${distPlatform}`; - debug("Base distribution:", dist); + debug(" - Base distribution:", dist); return dist; } @@ -279,7 +284,7 @@ function getInstallDir(cliPath: string): string { }) .toString() .trim(); - debug("Install directory:", installDir); + debug(" - Install directory:", installDir); return installDir; } @@ -291,5 +296,5 @@ async function* streamReader(readableStream: ReadableStream) { if (done) break; yield value; } - debug("Stream reading completed."); + debug(" - Stream reading completed."); } From 5fec348cebdafcc5fadfde248017d5fb51024d9b Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Wed, 3 Apr 2024 13:20:02 -0400 Subject: [PATCH 16/19] Slightly better error message --- packages/driver/src/cli.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index 5bbb04d16..480907c31 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -164,7 +164,7 @@ async function findPackage(): Promise { ); if (!pkg) { throw Error( - " - No compatible EdgeDB CLI package found for the current platform" + `No compatible EdgeDB CLI package found for the current platform ${dist}` ); } debug(" - Package found:", pkg); From 3a6fb277074f748eaf7d630513b83d69ff2c5d90 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Wed, 3 Apr 2024 13:20:51 -0400 Subject: [PATCH 17/19] prettier --- packages/driver/src/cli.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index 480907c31..5d3688abd 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -64,7 +64,7 @@ async function whichEdgeDbCli() { debug(` - CLI found in PATH at: ${location}`); return location; } - debug(" - No CLI found in PATH.") + debug(" - No CLI found in PATH."); return null; } From e873d0f7de828206a727dd27a377406dff386496 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Fri, 5 Apr 2024 16:10:32 -0400 Subject: [PATCH 18/19] Only write messages to stdout/err if TTY --- packages/driver/src/cli.mts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index 5d3688abd..e6e343f96 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -12,6 +12,7 @@ import which from "which"; const debug = Debug("edgedb:cli"); +const IS_TTY = process.stdout.isTTY; const EDGEDB_PKG_ROOT = "https://packages.edgedb.com"; const CACHE_DIR = envPaths("edgedb").cache; const TEMPORARY_CLI_PATH = path.join(CACHE_DIR, "/edgedb-cli"); @@ -28,7 +29,9 @@ try { await main(process.argv.slice(2)); process.exit(0); } catch (err) { - console.error(err); + if (IS_TTY) { + console.error(err); + } if ( typeof err === "object" && err !== null && @@ -119,7 +122,9 @@ async function selfInstallFromTempCli(): Promise { } async function downloadCliPackage() { - console.log("No EdgeDB CLI found, downloading CLI package..."); + if (IS_TTY) { + console.log("No EdgeDB CLI found, downloading CLI package..."); + } debug("Downloading CLI package..."); const cliPkg = await findPackage(); const downloadDir = path.dirname(TEMPORARY_CLI_PATH); From df325c37ba287b0f2c0b8c3386542b9e3182a67a Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Fri, 5 Apr 2024 16:48:23 -0400 Subject: [PATCH 19/19] Always output errors to stderr even if not TTY Might revisit this if it's deemed too noisy or unergonomic for script usage. --- packages/driver/src/cli.mts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/driver/src/cli.mts b/packages/driver/src/cli.mts index e6e343f96..85393cecb 100644 --- a/packages/driver/src/cli.mts +++ b/packages/driver/src/cli.mts @@ -29,9 +29,7 @@ try { await main(process.argv.slice(2)); process.exit(0); } catch (err) { - if (IS_TTY) { - console.error(err); - } + console.error(err); if ( typeof err === "object" && err !== null &&