src/core/dataApiBuilder/dab.ts (115 lines of code) (raw):

import { DATA_API_BUILDER_BINARY_NAME, DATA_API_BUILDER_COMMAND, DATA_API_BUILDER_FOLDER, DATA_API_BUILDER_LATEST_TAG, DATA_API_BUILDER_RELEASE_METADATA_URL, DEFAULT_DATA_API_BUILDER_BINARY, } from "../constants.js"; import { fetchWithProxy as fetch } from "../../core/utils/fetch-proxy.js"; import { promisify } from "node:util"; import { exec } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import AdmZip from "adm-zip"; import path from "node:path"; import { logger } from "../utils/logger.js"; import { getPlatform } from "../utils/platform.js"; import { downloadAndValidateBinary } from "../download-binary-helper.js"; /** * Gets the filepath where the Microsoft.DataApiBuilder.exe is located * - Gets the latest version from the manifest file * - Checks if it is installed and is latest or not already * - Gets the installed path if it is already present * - Downloads, unzips and Installs if not already present * @params null * * @returns binaryPath */ export async function installAndGetDataApiBuilder(): Promise<{ binaryPath: string }> { const platform = getPlatform(); if (!platform) { throw new Error(`Unsupported platform: ${os.platform()}`); } const releaseMetadata = (await getReleaseDataApiBuilderMetadata()).releaseMetadata; if (releaseMetadata === undefined) { throw new Error(`Could not load ${DATA_API_BUILDER_BINARY_NAME} metadata from remote. Please check your internet connection.`); // should we throw error and stop or can we allow users to use local version } const isLatestVersionInstalled = await isLocalVersionInstalledAndLatest(releaseMetadata.versionId); if (!isLatestVersionInstalled) { const binaryPath = await downloadAndUnzipBinary(releaseMetadata, platform); if (binaryPath != undefined) { return { binaryPath: binaryPath, }; } } return { binaryPath: DATA_API_BUILDER_COMMAND, }; } /** * * @param releaseMetadata Release metadata obtained from DATA_API_BUILDER_RELEASE_METADATA_URL * @param platform Current OS * @returns Binary Path after downloading and extracting */ async function downloadAndUnzipBinary(releaseMetadata: DataApiBuilderReleaseMetadata, platform: "win-x64" | "osx-x64" | "linux-x64") { try { const destDirectory = path.join(DATA_API_BUILDER_FOLDER, releaseMetadata.versionId); const binaryPath = path.join(destDirectory, getDefaultDataApiBuilderBinaryForOS(platform)); if (!fs.existsSync(binaryPath)) { logger.silly(`Downloading the version ${releaseMetadata.versionId}`); const zipFilePath = await downloadAndValidateBinary( releaseMetadata, DATA_API_BUILDER_BINARY_NAME, DATA_API_BUILDER_FOLDER, releaseMetadata?.versionId, platform, ); extractBinary(zipFilePath, destDirectory); } if (platform == "linux-x64" || platform == "osx-x64") { logger.silly(`Setting executable permissions for data-api-builder binary`); fs.chmodSync(binaryPath, 0o755); } return binaryPath; } catch (ex) { logger.error(`Unable to download/extract ${DATA_API_BUILDER_BINARY_NAME} binary. Exception ${ex}`); return undefined; } } /** * Fetches the latest version, metadata of Microsoft.DataApiBuilder.exe from DATA_API_BUILDER_RELEASE_METADATA_URL * @returns DataApiBuilderReleaseMetadata */ async function getReleaseDataApiBuilderMetadata(): Promise<{ releaseMetadata: DataApiBuilderReleaseMetadata | undefined }> { const response = await fetch(DATA_API_BUILDER_RELEASE_METADATA_URL); const responseMetadata = (await response.json()) as DataApiBuilderReleaseMetadata; if (Array.isArray(responseMetadata)) { const releaseMetadata = responseMetadata.find((c) => c.version === DATA_API_BUILDER_LATEST_TAG); // If we want to proceed with downloading the latest tag // const releaseMetadata = responseMetadata.find((c) => c.versionId === DATA_API_BUILDER_VERSION_ID); // If we want to pin a specific version return { releaseMetadata: releaseMetadata, }; } else { return { releaseMetadata: undefined, }; } } /** * Returns if the version installed locally is latest or not * @param releaseVersion current released Version of the Microsoft.DataApiBuilder.exe * @param platform current OS * @returns true if latest Version of data-api-builder is installed else false */ async function isLocalVersionInstalledAndLatest(releaseVersion: string): Promise<boolean | undefined> { const versionInstalled = await getInstalledVersion(DATA_API_BUILDER_COMMAND); if (versionInstalled) { logger.silly(`Installed version: ${versionInstalled}`); return versionInstalled == releaseVersion; } logger.silly(`${DATA_API_BUILDER_COMMAND} is not installed.`); return undefined; } /** * Unzips the given file to destDirectory * @param zipFilePath file to unzip * @param destDirectory directory to extract */ function extractBinary(zipFilePath: string, destDirectory: string) { var zip = new AdmZip(zipFilePath); zip.extractAllTo(destDirectory, true); fs.unlinkSync(zipFilePath); // delete zip file after extraction } /** * the Data-api-builder binary for given OS * @param platform current OS * @returns the Data-api-builder binary for given OS */ // exported for testing export function getDefaultDataApiBuilderBinaryForOS(platform: string): string { switch (platform) { case "win-x64": return DEFAULT_DATA_API_BUILDER_BINARY.Windows; case "osx-x64": return DEFAULT_DATA_API_BUILDER_BINARY.MacOs; case "linux-x64": return DEFAULT_DATA_API_BUILDER_BINARY.Linux; default: return DEFAULT_DATA_API_BUILDER_BINARY.Windows; } } /** * Returns installed version if installed else undefined * @param command package to know the version * @returns installed version */ async function getInstalledVersion(command: "dab"): Promise<string | undefined> { logger.silly(`Running ${DATA_API_BUILDER_COMMAND} --version`); try { const { stdout } = await promisify(exec)(`${command} --version`); const version = stdout.split(" ")[1].split("\r")[0]; // parsing output which looks like this "Microsoft.DataApiBuilder 0.5.0" (specific to dab) return version; } catch { return undefined; } }