PHP WebShell

Текущая директория: /opt/BitGoJS/scripts/tests/prepareRelease

Просмотр файла: util.ts

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { execFileSync } from 'child_process';
import * as tmp from 'tmp';

/**
 * Helper function to execute git commands and return output as string
 * @param args Git command arguments
 * @param cwd Working directory (optional)
 * @returns Command output as string
 */
export function execGitCapture(args: string[], cwd?: string): string {
  return execFileSync('git', args, {
    encoding: 'utf8',
    maxBuffer: 100 * 1024 * 1024, // Increase buffer size to 100MB
    ...(cwd ? { cwd } : {}),
  });
}

/**
 * Helper function to execute git commands with stdio inherited (for visible output)
 * @param args Git command arguments
 * @param cwd Working directory (optional)
 */
export function execGit(args: string[], cwd?: string): void {
  execFileSync('git', args, {
    stdio: 'inherit',
    ...(cwd ? { cwd } : {}),
  });
}

/**
 * Creates a temporary directory for testing
 * @returns Path to the temporary directory
 */
export function createTempDir(): string {
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bitgo-release-test-'));
  return tempDir;
}

/**
 * Creates a symlink for node_modules in the target directory
 * @param targetDir The directory to create the symlink in
 */
export function symlinkNodeModules(targetDir: string): void {
  // Get the current git repository directory
  const repoDir = execGitCapture(['rev-parse', '--show-toplevel']).trim();

  // Path to the source node_modules directory
  const sourceNodeModules = path.join(repoDir, 'node_modules');

  // Path where the symlink will be created
  const targetNodeModules = path.join(targetDir, 'node_modules');

  // Check if source node_modules exists
  if (fs.existsSync(sourceNodeModules)) {
    // Create symlink
    fs.symlinkSync(sourceNodeModules, targetNodeModules, 'junction');
    console.log(`Symlinked node_modules from ${sourceNodeModules} to ${targetNodeModules}`);
  } else {
    console.warn('Source node_modules directory not found, skipping symlink creation');
  }
}

/**
 * Creates a shallow clone of the git repository at a specific commit
 * or ensures an existing directory is at the right commit
 * @param commitHash The git commit hash to clone or checkout
 * @param tempDir The directory to clone into or update
 */
export function createShallowClone(commitHash: string, tempDir: string): void {
  // Get the current git repository directory
  const repoDir = execGitCapture(['rev-parse', '--show-toplevel']).trim();

  if (fs.existsSync(tempDir)) {
    console.log(`Directory ${tempDir} already exists, ensuring it's at commit ${commitHash}`);

    try {
      // Check if it's a git repository
      execGitCapture(['rev-parse', '--git-dir'], tempDir);

      // Fetch from the original repository to ensure we have the commit
      execGit(['fetch', repoDir], tempDir);

      // Force checkout to the specific commit
      execGit(['checkout', '-f', commitHash], tempDir);

      // Clean the working directory to remove any untracked files
      execGit(['clean', '-fdx'], tempDir);

      console.log(`Successfully updated existing directory to commit ${commitHash}`);
    } catch (error) {
      console.error(`Error updating existing directory: ${error}`);
      console.log('Removing directory and cloning fresh...');

      // If there was an error, remove the directory and clone fresh
      fs.rmSync(tempDir, { recursive: true, force: true });

      // Clone the repository
      execGit(['clone', '--local', repoDir, tempDir]);
      execGit(['checkout', commitHash], tempDir);
    }
  } else {
    // Directory doesn't exist, clone the repository
    console.log(`Cloning repository to ${tempDir} at commit ${commitHash}`);
    execGit(['clone', '--local', repoDir, tempDir]);
    execGit(['checkout', commitHash], tempDir);
  }

  // Create symlink for node_modules
  symlinkNodeModules(tempDir);
}

/**
 * Applies the prepare-release script on the cloned repository
 * @param clonedRepoDir The directory of the cloned repository
 * @param preid Optional prerelease identifier
 * @param scope Optional scope to use
 * @param distTagsCachePath Optional path to cache dist tags
 * @returns The stdout from the prepare-release script
 */
export function applyPrepareReleaseScript(
  clonedRepoDir: string,
  preid: string,
  scope = '@bitgo-beta',
  distTagsCachePath?: string
): string {
  const args: string[] = [];
  args.push(preid);
  args.push(`scope=${scope}`);

  // Get the current git repository directory
  const currentRepoDir = execGitCapture(['rev-parse', '--show-toplevel']).trim();

  // Use the script from the current repo, not the cloned one
  const scriptPath = path.join(currentRepoDir, 'scripts', 'prepare-release.ts');

  // Create environment with the cache path if provided
  const env: Record<string, string> = {
    ...process.env,
    // Set the root directory to the cloned repo directory
    BITGO_PREPARE_RELEASE_ROOT_DIR: clonedRepoDir,
  };

  if (distTagsCachePath) {
    env.BITGO_PREPARE_RELEASE_CACHE_DIST_TAGS = distTagsCachePath;
  }

  // Execute the prepare-release script with the repoDir as cwd
  return execFileSync('npx', ['ts-node', '--transpile-only', scriptPath, ...args], {
    encoding: 'utf8',
    stdio: 'inherit',
    cwd: clonedRepoDir,
    env, // Pass the environment variables
  });
}

/**
 * Generates a git diff of the changes made by the prepare-release script
 * @param repoDir The directory of the cloned repository
 * @param pathFilter Optional path to filter the diff
 * @returns The git diff as a string
 */
export function generateGitDiff(repoDir: string, pathFilter?: string): string {
  // Add all changes to git staging
  execGit(['add', '.'], repoDir);

  // Generate the diff, optionally filtering by path
  const diffArgs = ['diff', '--cached'];
  if (pathFilter) {
    diffArgs.push('--', pathFilter);
  }
  return execGitCapture(diffArgs, repoDir);
}

/**
 * Asserts that the generated diff matches the reference diff
 * @param generatedDiff The generated git diff
 * @param referenceDiffPath The path to the reference diff file
 * @throws AssertionError if the diffs don't match
 */
export function assertEqualDiffs(generatedDiff: string, referenceDiffPath: string): void {
  if (!fs.existsSync(referenceDiffPath)) {
    throw new Error(`Reference diff file does not exist: ${referenceDiffPath}`);
  }

  // Write the generated diff to a temporary file
  const tempFile = tmp.fileSync({ prefix: 'generated-diff-', postfix: '.diff' });
  fs.writeFileSync(tempFile.name, generatedDiff);

  try {
    // Run the diff command to compare the two files
    // The diff command returns exit code 0 if files are identical, 1 if different, >1 if error
    execFileSync('diff', ['-u', tempFile.name, referenceDiffPath], {
      stdio: 'inherit', // Show the diff output to the console
    });

    // If we get here, the diff command returned 0, meaning files are identical
    console.log('Generated diff matches reference diff');
  } catch (error) {
    // If diff command returns exit code 1, files are different
    if (error.status === 1) {
      throw new Error('Generated diff does not match reference diff');
    } else {
      // For other exit codes, there was an error running diff
      throw new Error(`Error comparing diffs: ${error.message}`);
    }
  } finally {
    // Clean up the temporary file
    try {
      tempFile.removeCallback();
    } catch (e) {
      console.warn(`Failed to remove temporary diff file: ${tempFile.name}`);
    }
  }
}

/**
 * Creates a reference diff file
 * @param diff The diff to save as reference
 * @param referenceDiffPath The path to save the reference diff to
 */
export function createReferenceDiff(diff: string, referenceDiffPath: string): void {
  const dirPath = path.dirname(referenceDiffPath);
  if (!fs.existsSync(dirPath)) {
    fs.mkdirSync(dirPath, { recursive: true });
  }

  fs.writeFileSync(referenceDiffPath, diff, { encoding: 'utf8' });
}

/**
 * Sets up a test environment, applies the prepare-release script, and compares or creates a reference diff
 * @param commitHash The git commit hash to test
 * @param referenceDiffPath The path to the reference diff file
 * @param options Optional configuration options
 * @returns Object with test results and cleanup function
 */
export function runTestAndCompare(
  commitHash: string,
  referenceDiffPath: string,
  options: {
    preid: string;
    scope?: string;
    tempDir?: string;
    pathFilter?: string;
    distTagsCachePath?: string;
  }
): void {
  // Use provided tempDir or create a new one
  const tempDir = options?.tempDir || createTempDir();
  // Get preid and scope from options with defaults
  const preid = options?.preid;
  const scope = options?.scope || '@bitgo-beta';
  // Get path filter
  const pathFilter = options?.pathFilter;
  // Get dist tags cache path
  const distTagsCachePath = options?.distTagsCachePath;

  // Clone the repository at the specific commit
  createShallowClone(commitHash, tempDir);

  // Apply the prepare-release script
  applyPrepareReleaseScript(tempDir, preid, scope, distTagsCachePath);

  // Generate a git diff, optionally filtered by path
  const generatedDiff = generateGitDiff(tempDir, pathFilter);

  // Check if reference diff exists
  const referenceExists = fs.existsSync(referenceDiffPath);

  // If reference doesn't exist, create it
  if (!referenceExists) {
    createReferenceDiff(generatedDiff, referenceDiffPath);
  }

  // Assert that the diffs match
  assertEqualDiffs(generatedDiff, referenceDiffPath);
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!