<commit_before>/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
import { normalize, virtualFs } from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
import { NodeWorkflow, validateOptionsWithSchema } from '@angular-devkit/schematics/tools';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
import { PackageManager } from '../lib/config/schema';
import { Command } from '../models/command';
import { Arguments } from '../models/interface';
import { runTempPackageBin } from '../tasks/install-package';
import { colors } from '../utilities/color';
import { getPackageManager } from '../utilities/package-manager';
import {
  PackageIdentifier,
  PackageManifest,
  PackageMetadata,
  fetchPackageManifest,
  fetchPackageMetadata,
} from '../utilities/package-metadata';
import { PackageTreeNode, findNodeDependencies, readPackageTree } from '../utilities/package-tree';
import { Schema as UpdateCommandSchema } from './update';

const npa = require('npm-package-arg') as (selector: string) => PackageIdentifier;
const pickManifest = require('npm-pick-manifest') as (
  metadata: PackageMetadata,
  selector: string,
) => PackageManifest;

const oldConfigFileNames = ['.angular-cli.json', 'angular-cli.json'];

export class UpdateCommand extends Command<UpdateCommandSchema> {
  public readonly allowMissingWorkspace = true;

  private workflow: NodeWorkflow;
  private packageManager: PackageManager;

  async initialize() {
    this.packageManager = await getPackageManager(this.workspace.root);
    this.workflow = new NodeWorkflow(
      new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(this.workspace.root)),
      {
        packageManager: this.packageManager,
        root: normalize(this.workspace.root),
        // __dirname -> favor @schematics/update from this package
        // Otherwise, use packages from the active workspace (migrations)
        resolvePaths: [__dirname, this.workspace.root],
      },
    );
    this.workflow.engineHost.registerOptionsTransform(
      validateOptionsWithSchema(this.workflow.registry),
    );
  }

  private async executeSchematic(
    collection: string,
    schematic: string,
    options = {},
  ): Promise<{ success: boolean; files: Set<string> }> {
    let error = false;
    let logs: string[] = [];
    const files = new Set<string>();

    const reporterSubscription = this.workflow.reporter.subscribe(event => {
      // Strip leading slash to prevent confusion.
      const eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path;

      switch (event.kind) {
        case 'error':
          error = true;
          const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist.';
          this.logger.error(`ERROR! ${eventPath} ${desc}.`);
          break;
        case 'update':
          logs.push(`${colors.whiteBright('UPDATE')} ${eventPath} (${event.content.length} bytes)`);
          files.add(eventPath);
          break;
        case 'create':
          logs.push(`${colors.green('CREATE')} ${eventPath} (${event.content.length} bytes)`);
          files.add(eventPath);
          break;
        case 'delete':
          logs.push(`${colors.yellow('DELETE')} ${eventPath}`);
          files.add(eventPath);
          break;
        case 'rename':
          logs.push(`${colors.blue('RENAME')} ${eventPath} => ${event.to}`);
          files.add(eventPath);
          break;
      }
    });

    const lifecycleSubscription = this.workflow.lifeCycle.subscribe(event => {
      if (event.kind == 'end' || event.kind == 'post-tasks-start') {
        if (!error) {
          // Output the logging queue, no error happened.
          logs.forEach(log => this.logger.info(log));
          logs = [];
        }
      }
    });

    // TODO: Allow passing a schematic instance directly
    try {
      await this.workflow
        .execute({
          collection,
          schematic,
          options,
          logger: this.logger,
        })
        .toPromise();

      reporterSubscription.unsubscribe();
      lifecycleSubscription.unsubscribe();

      return { success: !error, files };
    } catch (e) {
      if (e instanceof UnsuccessfulWorkflowExecution) {
        this.logger.error('The update failed. See above.');
      } else {
        this.logger.fatal(e.message);
      }

      return { success: false, files };
    }
  }

  private async executeMigrations(
    packageName: string,
    collectionPath: string,
    range: semver.Range,
    commit = false,
  ): Promise<boolean> {
    const collection = this.workflow.engine.createCollection(collectionPath);

    const migrations = [];
    for (const name of collection.listSchematicNames()) {
      const schematic = this.workflow.engine.createSchematic(name, collection);
      const description = schematic.description as typeof schematic.description & {
        version?: string;
      };
      description.version = coerceVersionNumber(description.version) || undefined;
      if (!description.version) {
        continue;
      }

      if (semver.satisfies(description.version, range, { includePrerelease: true })) {
        migrations.push(description as typeof schematic.description & { version: string });
      }
    }

    if (migrations.length === 0) {
      return true;
    }

    const startingGitSha = this.findCurrentGitSha();

    migrations.sort((a, b) => semver.compare(a.version, b.version) || a.name.localeCompare(b.name));

    this.logger.info(
      colors.cyan(`** Executing migrations of package '${packageName}' **\n`),
    );

    for (const migration of migrations) {
      this.logger.info(`${colors.symbols.pointer} ${migration.description.replace(/\. /g, '.\n  ')}`);

      const result = await this.executeSchematic(migration.collection.name, migration.name);
      if (!result.success) {
        if (startingGitSha !== null) {
          const currentGitSha = this.findCurrentGitSha();
          if (currentGitSha !== startingGitSha) {
            this.logger.warn(`git HEAD was at ${startingGitSha} before migrations.`);
          }
        }

        this.logger.error(`${colors.symbols.cross} Migration failed. See above for further details.\n`);

        return false;
      }

      // Commit migration
      if (commit) {
        let message = `${packageName} migration - ${migration.name}.`;
        if (migration.description) {
          message += '\n' + migration.description;
        }
        // TODO: Use result.files once package install tasks are accounted
        this.createCommit(message, []);
      }

      this.logger.info(colors.green(`${colors.symbols.check} Migration succeeded.\n`));
    }

    return true;
  }

  // tslint:disable-next-line:no-big-function
  async run(options: UpdateCommandSchema & Arguments) {
    // Check if the current installed CLI version is older than the latest version.
    if (await this.checkCLILatestVersion(options.verbose, options.next)) {
      this.logger.warn(
        'The installed Angular CLI version is older than the latest published version.\n' +
        'Installing a temporary version to perform the update.',
      );
<commit_msg>fix(@angular/cli): remove unneeded git HEAD update message<commit_after>/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
import { normalize, virtualFs } from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
import { NodeWorkflow, validateOptionsWithSchema } from '@angular-devkit/schematics/tools';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
import { PackageManager } from '../lib/config/schema';
import { Command } from '../models/command';
import { Arguments } from '../models/interface';
import { runTempPackageBin } from '../tasks/install-package';
import { colors } from '../utilities/color';
import { getPackageManager } from '../utilities/package-manager';
import {
  PackageIdentifier,
  PackageManifest,
  PackageMetadata,
  fetchPackageManifest,
  fetchPackageMetadata,
} from '../utilities/package-metadata';
import { PackageTreeNode, findNodeDependencies, readPackageTree } from '../utilities/package-tree';
import { Schema as UpdateCommandSchema } from './update';

const npa = require('npm-package-arg') as (selector: string) => PackageIdentifier;
const pickManifest = require('npm-pick-manifest') as (
  metadata: PackageMetadata,
  selector: string,
) => PackageManifest;

const oldConfigFileNames = ['.angular-cli.json', 'angular-cli.json'];

export class UpdateCommand extends Command<UpdateCommandSchema> {
  public readonly allowMissingWorkspace = true;

  private workflow: NodeWorkflow;
  private packageManager: PackageManager;

  async initialize() {
    this.packageManager = await getPackageManager(this.workspace.root);
    this.workflow = new NodeWorkflow(
      new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(this.workspace.root)),
      {
        packageManager: this.packageManager,
        root: normalize(this.workspace.root),
        // __dirname -> favor @schematics/update from this package
        // Otherwise, use packages from the active workspace (migrations)
        resolvePaths: [__dirname, this.workspace.root],
      },
    );
    this.workflow.engineHost.registerOptionsTransform(
      validateOptionsWithSchema(this.workflow.registry),
    );
  }

  private async executeSchematic(
    collection: string,
    schematic: string,
    options = {},
  ): Promise<{ success: boolean; files: Set<string> }> {
    let error = false;
    let logs: string[] = [];
    const files = new Set<string>();

    const reporterSubscription = this.workflow.reporter.subscribe(event => {
      // Strip leading slash to prevent confusion.
      const eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path;

      switch (event.kind) {
        case 'error':
          error = true;
          const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist.';
          this.logger.error(`ERROR! ${eventPath} ${desc}.`);
          break;
        case 'update':
          logs.push(`${colors.whiteBright('UPDATE')} ${eventPath} (${event.content.length} bytes)`);
          files.add(eventPath);
          break;
        case 'create':
          logs.push(`${colors.green('CREATE')} ${eventPath} (${event.content.length} bytes)`);
          files.add(eventPath);
          break;
        case 'delete':
          logs.push(`${colors.yellow('DELETE')} ${eventPath}`);
          files.add(eventPath);
          break;
        case 'rename':
          logs.push(`${colors.blue('RENAME')} ${eventPath} => ${event.to}`);
          files.add(eventPath);
          break;
      }
    });

    const lifecycleSubscription = this.workflow.lifeCycle.subscribe(event => {
      if (event.kind == 'end' || event.kind == 'post-tasks-start') {
        if (!error) {
          // Output the logging queue, no error happened.
          logs.forEach(log => this.logger.info(log));
          logs = [];
        }
      }
    });

    // TODO: Allow passing a schematic instance directly
    try {
      await this.workflow
        .execute({
          collection,
          schematic,
          options,
          logger: this.logger,
        })
        .toPromise();

      reporterSubscription.unsubscribe();
      lifecycleSubscription.unsubscribe();

      return { success: !error, files };
    } catch (e) {
      if (e instanceof UnsuccessfulWorkflowExecution) {
        this.logger.error('The update failed. See above.');
      } else {
        this.logger.fatal(e.message);
      }

      return { success: false, files };
    }
  }

  private async executeMigrations(
    packageName: string,
    collectionPath: string,
    range: semver.Range,
    commit = false,
  ): Promise<boolean> {
    const collection = this.workflow.engine.createCollection(collectionPath);

    const migrations = [];
    for (const name of collection.listSchematicNames()) {
      const schematic = this.workflow.engine.createSchematic(name, collection);
      const description = schematic.description as typeof schematic.description & {
        version?: string;
      };
      description.version = coerceVersionNumber(description.version) || undefined;
      if (!description.version) {
        continue;
      }

      if (semver.satisfies(description.version, range, { includePrerelease: true })) {
        migrations.push(description as typeof schematic.description & { version: string });
      }
    }

    if (migrations.length === 0) {
      return true;
    }

    migrations.sort((a, b) => semver.compare(a.version, b.version) || a.name.localeCompare(b.name));

    this.logger.info(
      colors.cyan(`** Executing migrations of package '${packageName}' **\n`),
    );

    for (const migration of migrations) {
      this.logger.info(`${colors.symbols.pointer} ${migration.description.replace(/\. /g, '.\n  ')}`);

      const result = await this.executeSchematic(migration.collection.name, migration.name);
      if (!result.success) {
        this.logger.error(`${colors.symbols.cross} Migration failed. See above for further details.\n`);

        return false;
      }

      // Commit migration
      if (commit) {
        let message = `${packageName} migration - ${migration.name}.`;
        if (migration.description) {
          message += '\n' + migration.description;
        }
        // TODO: Use result.files once package install tasks are accounted
        this.createCommit(message, []);
      }

      this.logger.info(colors.green(`${colors.symbols.check} Migration succeeded.\n`));
    }

    return true;
  }

  // tslint:disable-next-line:no-big-function
  async run(options: UpdateCommandSchema & Arguments) {
    // Check if the current installed CLI version is older than the latest version.
    if (await this.checkCLILatestVersion(options.verbose, options.next)) {
      this.logger.warn(
        'The installed Angular CLI version is older than the latest published version.\n' +
        'Installing a temporary version to perform the update.',
      );

{'fraction_non_alphanumeric': 0.13007021610198377, 'fraction_numerical': 0.0002726838911991274, 'mean_word_length': 2.9140875133404482, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 29, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7649816', 'n_tokens_mistral': 4079, 'n_tokens_neox': 4031, 'n_words': 1009}