Source

bin/fabric.ts

/**
 * @module fabric-cli
 * @description Command-line interface for Fabric setup and update operations
 * @summary This module provides a CLI for managing Hyperledger Fabric installations.
 * It exposes commands for updating the Fabric install script and setting up Fabric
 * components. The module uses the Commander.js library to parse command-line
 * arguments and execute the appropriate actions.
 *
 * Key exports:
 * - {@link updateFabric}: Function to update the Fabric install script
 * - {@link setupFabric}: Function to set up Fabric components
 *
 * @example
 * // Update Fabric install script
 * node fabric.js update
 *
 * // Setup Fabric components
 * node fabric.js setup --fabric-version 2.5.12 --ca-version 1.5.15 --components binary docker
 *
 * @mermaid
 * sequenceDiagram
 *   participant User
 *   participant CLI
 *   participant UpdateFabric
 *   participant SetupFabric
 *   User->>CLI: Run command
 *   CLI->>CLI: Parse arguments
 *   alt update command
 *     CLI->>UpdateFabric: Call updateFabric()
 *     UpdateFabric->>UpdateFabric: Download install script
 *     UpdateFabric->>UpdateFabric: Make script executable
 *   else setup command
 *     CLI->>SetupFabric: Call setupFabric(config)
 *     SetupFabric->>SetupFabric: Install components
 *   end
 *   CLI->>User: Display result
 */

import fs from "fs";
import path from "path";
import axios from "axios";
import { execSync } from "child_process";
import { Command } from "commander";
import { safeParseCSV, VERSION } from "../index";

const INSTALL_SCRIPT = path.join(__dirname, "..", "bin", "install-fabric.sh");

// Default configuration
const defaultConfig = {
  fabricVersion: "2.5.12",
  caVersion: "1.5.15",
  components: ["binary"],
};

const program = new Command();

program.version(VERSION).description("Fabric setup and update utility");

program
  .command("update")
  .description("Update the Fabric install script")
  .action(async () => {
    await updateFabric();
  });

program
  .command("setup")
  .description("Set up Fabric components")
  .option(
    "-f, --fabric-version <version>",
    "Fabric version",
    defaultConfig.fabricVersion
  )
  .option(
    "-c, --ca-version <version>",
    "Fabric CA version",
    defaultConfig.caVersion
  )
  .option(
    "--components <components...>",
    "Components to install (binary, docker, podman, samples)",
    safeParseCSV,
    defaultConfig.components
  )
  .action(async (options) => {
    const config = {
      fabricVersion: options.fabricVersion || defaultConfig.fabricVersion,
      caVersion: options.caVersion || defaultConfig.caVersion,
      components: options.components || defaultConfig.components,
    };
    await setupFabric(config);
  });

program.parse(process.argv);

/**
 * @function updateFabric
 * @description Updates the Fabric install script by downloading the latest version
 * @summary This function removes the existing install script (if present), downloads
 * the latest version from the Hyperledger Fabric GitHub repository, and makes it executable.
 * @returns {Promise<void>}
 * @throws {Error} If the download fails or file operations encounter issues
 * @memberOf module:fabric-cli
 *
 * @example
 * await updateFabric();
 *
 * @mermaid
 * sequenceDiagram
 *   participant Function
 *   participant FileSystem
 *   participant GitHub
 *   Function->>FileSystem: Check if script exists
 *   alt Script exists
 *     Function->>FileSystem: Remove existing script
 *   end
 *   Function->>GitHub: Download latest script
 *   GitHub-->>Function: Return script content
 *   Function->>FileSystem: Write new script
 *   Function->>FileSystem: Make script executable
 */
async function updateFabric(): Promise<void> {
  console.log("Executing update...");
  const SCRIPT_URL =
    "https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh";

  // Remove the existing file if it exists
  if (fs.existsSync(INSTALL_SCRIPT)) {
    console.log("Removing existing install-fabric.sh...");
    fs.unlinkSync(INSTALL_SCRIPT);
  }

  // Download the new file
  console.log("Downloading new install-fabric.sh...");
  try {
    const response = await axios.get(SCRIPT_URL, {
      responseType: "arraybuffer",
    });

    fs.writeFileSync(INSTALL_SCRIPT, response.data);
    console.log("Download successful.");

    // Make the file executable
    fs.chmodSync(INSTALL_SCRIPT, "755");
    console.log("Made install-fabric.sh executable.");
  } catch (error: unknown) {
    console.error("Error: Failed to download the file.");
    console.error(error);
    process.exit(1);
  }
}

/**
 * @function setupFabric
 * @description Sets up Fabric components based on the provided configuration
 * @summary This function installs the specified Fabric components using the
 * install-fabric.sh script. It iterates through the components list and executes
 * the script for each component with the specified Fabric and CA versions.
 * After installation, it copies configuration files to the root config folder.
 * @param {Object} config - Configuration object for Fabric setup
 * @param {string} config.fabricVersion - Fabric version to install
 * @param {string} config.caVersion - Fabric CA version to install
 * @param {string[]} config.components - List of components to install
 * @returns {Promise<void>}
 * @throws {Error} If the install script is not found, component installation fails, or file copying fails
 * @memberOf module:fabric-cli
 *
 * @example
 * const config = {
 *   fabricVersion: "2.5.12",
 *   caVersion: "1.5.15",
 *   components: ["binary", "docker"]
 * };
 * await setupFabric(config);
 *
 * @mermaid
 * sequenceDiagram
 *   participant Function
 *   participant FileSystem
 *   participant InstallScript
 *   Function->>FileSystem: Check if install script exists
 *   alt Script not found
 *     Function->>Function: Log error and exit
 *   else Script found
 *     loop For each component
 *       Function->>InstallScript: Execute install script
 *       InstallScript-->>Function: Installation result
 *       alt Installation failed
 *         Function->>Function: Log error and exit
 *       end
 *     end
 *     Function->>FileSystem: Copy config files
 *     alt Copy failed
 *       Function->>Function: Log error
 *     end
 *   end
 *   Function->>Function: Log success message
 */
async function setupFabric(config: typeof defaultConfig): Promise<void> {
  console.log("Executing setup...");
  if (fs.existsSync(INSTALL_SCRIPT)) {
    console.log("Executing install-fabric.sh...");

    for (const component of config.components) {
      console.log(`Installing component: ${component}`);
      try {
        execSync(
          `bash "${INSTALL_SCRIPT}" "${component}" -f "${config.fabricVersion}" -c "${config.caVersion}"`,
          { stdio: "inherit" }
        );
      } catch (error) {
        console.error(`Error installing component: ${component}`);
        console.error(error);
        process.exit(1);
      }
    }

    try {
      const srcConfigDir = path.join(__dirname, "..", "src", "configs");
      const baseConfigDir = path.join(
        __dirname,
        "..",
        "fabric-samples",
        "config"
      );
      const destConfigDir = path.join(__dirname, "..", "config");

      // Create the destination directory if it doesn't exist
      if (!fs.existsSync(destConfigDir)) {
        fs.mkdirSync(destConfigDir, { recursive: true });
      }

      // Copy files from src/configs to rootDir/config
      fs.readdirSync(srcConfigDir).forEach((file) => {
        const srcPath = path.join(srcConfigDir, file);
        const destPath = path.join(destConfigDir, file);
        fs.copyFileSync(srcPath, destPath);
        console.log(`Copied ${file} to ${destConfigDir}`);
      });

      if (fs.existsSync(baseConfigDir))
        // In case install script installed config in fabric-samples
        fs.readdirSync(baseConfigDir).forEach((file) => {
          const srcPath = path.join(srcConfigDir, file);
          const destPath = path.join(destConfigDir, file);
          fs.copyFileSync(srcPath, destPath);
          console.log(`Copied ${file} to ${destConfigDir}`);
        });

      console.log("Configuration files copied successfully.");
    } catch (error) {
      console.error("Error copying configuration files:");
      console.error(error);
    }

    console.log("All components installed successfully.");
  } else {
    console.error(
      "Error: install-fabric.sh not found. Please run the update command first."
    );
    console.error(INSTALL_SCRIPT);
    process.exit(1);
  }
}