<commit_before>import ms from 'ms';
import chalk from 'chalk';
import { SetDifference } from 'utility-types';

import { AliasRecord } from '../../util/alias/create-alias';
import { NowContext, Domain } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors-ts';
import assignAlias from '../../util/alias/assign-alias';
import Client from '../../util/client';
import cmd from '../../util/output/cmd';
import dnsTable from '../../util/format-dns-table';
import formatDnsTable from '../../util/format-dns-table';
import formatNSTable from '../../util/format-ns-table';
import getDeploymentForAlias from '../../util/alias/get-deployment-for-alias';
import getRulesFromFile from '../../util/alias/get-rules-from-file';
import getScope from '../../util/get-scope';
import getTargetsForAlias from '../../util/alias/get-targets-for-alias';
import humanizePath from '../../util/humanize-path';
import setupDomain from '../../util/domains/setup-domain';
import stamp from '../../util/output/stamp';
import upsertPathAlias from '../../util/alias/upsert-path-alias';

type Options = {
  '--debug': boolean,
  '--local-config': string,
  '--no-verify': boolean,
  '--rules': string
}

export default async function set(ctx: NowContext, opts: Options, args: string[], output: Output) {
  const { authConfig: { token }, config } = ctx;
  const { currentTeam } = config;
  const { apiUrl } = ctx;
  const setStamp = stamp();

  const {
    '--debug': debugEnabled,
    '--no-verify': noVerify,
    '--rules': rulesPath
  } = opts;

  const client = new Client({ apiUrl, token, currentTeam, debug: debugEnabled });
  let contextName = null;
  let user = null;

  try {
    ({ contextName, user } = await getScope(client));
  } catch (err) {
    if (err.code === 'not_authorized') {
      output.error(err.message);
      return 1;
    }

    throw err;
  }

  // If there are more than two args we have to error
  if (args.length > 2) {
    output.error(`${cmd('now alias <deployment> <target>')} accepts at most two arguments`);
    return 1;
  }

  // Read the path alias rules in case there is is given
  const rules = await getRulesFromFile(rulesPath);
  if (rules instanceof ERRORS.FileNotFound) {
    output.error(`Can't find the provided rules file at location:`);
    output.print(`  ${chalk.gray('-')} ${rules.meta.file}\n`);
    return 1;
  }

  if (rules instanceof ERRORS.CantParseJSONFile) {
    output.error(`Error parsing provided rules.json file at location:`);
    output.print(`  ${chalk.gray('-')} ${rules.meta.file}\n`);
    return 1;
  }

  if (rules instanceof ERRORS.RulesFileValidationError) {
    output.error(`Path Alias validation error: ${rules.meta.message}`);
    output.print(`  ${chalk.gray('-')} ${rules.meta.location}\n`);
    return 1;
  }

  // If the user provided rules and also a deployment target, we should fail
  if (args.length === 2 && rules) {
    output.error(`You can't supply a deployment target and target rules simultaneously.`);
    return 1;
  }

  // Find the targets to perform the alias
  const targets = await getTargetsForAlias(output, args, opts['--local-config']);
  if (targets instanceof ERRORS.CantFindConfig) {
    output.error(`Couldn't find a project configuration file at \n    ${targets.meta.paths.join(' or\n    ')}`);
    return 1;
  }

  if (targets instanceof ERRORS.NoAliasInConfig) {
    output.error(`Couldn't find a an alias in config`);
    return 1;
  }

  if (targets instanceof ERRORS.InvalidAliasInConfig) {
    output.error(`Wrong value for alias found in config. It must be a string or array of string.`);
    return 1;
  }

  if (targets instanceof ERRORS.CantParseJSONFile) {
    output.error(`Couldn't parse JSON file ${targets.meta.file}.`);
    return 1;
  }

  if (rules) {
    // If we have rules for path alias we assign them to the domain
    for (const target of targets) {
      output.log(`Assigning path alias rules from ${humanizePath(rulesPath)} to ${target}`);
      const pathAlias = await upsertPathAlias(output, client, rules, target, contextName);
      const remaining = handleCreateAliasError(output, pathAlias);
      if (handleSetupDomainError(output, remaining) !== 1) {
        console.log(`${chalk.cyan('> Success!')} ${rules.length} rules configured for ${chalk.underline(target)} ${setStamp()}`);
      }
    }

    return 0;
  }
  // If there are no rules for path alias we should find out a deployment and perform the alias
  const deployment = await getDeploymentForAlias(client, output, args, opts['--local-config'], user, contextName);
  if (deployment instanceof ERRORS.DeploymentNotFound) {
    output.error(`Failed to find deployment "${deployment.meta.id}" under ${chalk.bold(contextName)}`);
    return 1;
  }

  if (deployment instanceof ERRORS.DeploymentPermissionDenied) {
    output.error(`No permission to access deployment "${deployment.meta.id}" under ${chalk.bold(deployment.meta.context)}`);
    return 1;
  }

  if (deployment === null) {
    output.error(`Couldn't find a deployment to alias. Please provide one as an argument.`);
    return 1;
  }

  // Assign the alias for each of the targets in the array
  for (const target of targets) {
    output.log(`Assigning alias ${target} to deployment ${deployment.url}`);
    const record = await assignAlias(output, client, deployment, target, contextName, noVerify);
    const restOfErrors = handleCreateAliasError(output, record);
    const handleResult = handleSetupDomainError(output, restOfErrors);
    if (handleResult !== 1) {
      console.log(`${chalk.cyan('> Success!')} ${handleResult.alias} now points to ${chalk.bold(deployment.url)} ${setStamp()}`);
      return 0;
    }

    return 1;
  }

  return 0;
}

type ThenArg<T> = T extends Promise<infer U> ? U : T
type SetupDomainResolve = ThenArg<ReturnType<typeof setupDomain>>;
type SetupDomainError = Exclude<SetupDomainResolve, Domain>;

function handleSetupDomainError<T>(output: Output, error: SetupDomainError | T): T | 1 {
  if (error instanceof ERRORS.DomainVerificationFailed) {
    const { nsVerification, txtVerification, domain } = error.meta;
    output.error(`We could not alias since the domain ${domain} could not be verified due to the following reasons:\n`);
    output.print(`  ${chalk.gray('a)')} Nameservers verification failed since we see a different set than the intended set:`);
    output.print(`\n${formatNSTable(nsVerification.intendedNameservers, nsVerification.nameservers, { extraSpace: '     ' })}\n\n`);
    output.print(`  ${chalk.gray('b)')} DNS TXT verification failed since found no matching records.`);
    output.print(`\n${formatDnsTable([['_now', 'TXT', txtVerification.verificationRecord]], { extraSpace: '     ' })}\n\n`);
    output.print(`  Once your domain uses either the nameservers or the TXT DNS record from above, run again ${cmd('now domains verify <domain>')}.\n`);
    output.print(`  We will also periodically run a verification check for you and you will receive an email once your domain is verified.\n`);
    output.print('  Read more: https://err.sh/now-cli/domain-verification\n');
    return 1;
  }

  if (error instanceof ERRORS.DomainPermissionDenied) {
    output.error(`You don't have permissions over domain ${chalk.underline(error.meta.domain)} under ${chalk.bold(error.meta.context)}.`);
    return 1;<commit_msg>Fix issue when we define aliases in now.json array (#1757)<commit_after>import ms from 'ms';
import chalk from 'chalk';
import { SetDifference } from 'utility-types';

import { AliasRecord } from '../../util/alias/create-alias';
import { NowContext, Domain } from '../../types';
import { Output } from '../../util/output';
import * as ERRORS from '../../util/errors-ts';
import assignAlias from '../../util/alias/assign-alias';
import Client from '../../util/client';
import cmd from '../../util/output/cmd';
import dnsTable from '../../util/format-dns-table';
import formatDnsTable from '../../util/format-dns-table';
import formatNSTable from '../../util/format-ns-table';
import getDeploymentForAlias from '../../util/alias/get-deployment-for-alias';
import getRulesFromFile from '../../util/alias/get-rules-from-file';
import getScope from '../../util/get-scope';
import getTargetsForAlias from '../../util/alias/get-targets-for-alias';
import humanizePath from '../../util/humanize-path';
import setupDomain from '../../util/domains/setup-domain';
import stamp from '../../util/output/stamp';
import upsertPathAlias from '../../util/alias/upsert-path-alias';

type Options = {
  '--debug': boolean,
  '--local-config': string,
  '--no-verify': boolean,
  '--rules': string
}

export default async function set(ctx: NowContext, opts: Options, args: string[], output: Output) {
  const { authConfig: { token }, config } = ctx;
  const { currentTeam } = config;
  const { apiUrl } = ctx;
  const setStamp = stamp();

  const {
    '--debug': debugEnabled,
    '--no-verify': noVerify,
    '--rules': rulesPath
  } = opts;

  const client = new Client({ apiUrl, token, currentTeam, debug: debugEnabled });
  let contextName = null;
  let user = null;

  try {
    ({ contextName, user } = await getScope(client));
  } catch (err) {
    if (err.code === 'not_authorized') {
      output.error(err.message);
      return 1;
    }

    throw err;
  }

  // If there are more than two args we have to error
  if (args.length > 2) {
    output.error(`${cmd('now alias <deployment> <target>')} accepts at most two arguments`);
    return 1;
  }

  // Read the path alias rules in case there is is given
  const rules = await getRulesFromFile(rulesPath);
  if (rules instanceof ERRORS.FileNotFound) {
    output.error(`Can't find the provided rules file at location:`);
    output.print(`  ${chalk.gray('-')} ${rules.meta.file}\n`);
    return 1;
  }

  if (rules instanceof ERRORS.CantParseJSONFile) {
    output.error(`Error parsing provided rules.json file at location:`);
    output.print(`  ${chalk.gray('-')} ${rules.meta.file}\n`);
    return 1;
  }

  if (rules instanceof ERRORS.RulesFileValidationError) {
    output.error(`Path Alias validation error: ${rules.meta.message}`);
    output.print(`  ${chalk.gray('-')} ${rules.meta.location}\n`);
    return 1;
  }

  // If the user provided rules and also a deployment target, we should fail
  if (args.length === 2 && rules) {
    output.error(`You can't supply a deployment target and target rules simultaneously.`);
    return 1;
  }

  // Find the targets to perform the alias
  const targets = await getTargetsForAlias(output, args, opts['--local-config']);
  if (targets instanceof ERRORS.CantFindConfig) {
    output.error(`Couldn't find a project configuration file at \n    ${targets.meta.paths.join(' or\n    ')}`);
    return 1;
  }

  if (targets instanceof ERRORS.NoAliasInConfig) {
    output.error(`Couldn't find a an alias in config`);
    return 1;
  }

  if (targets instanceof ERRORS.InvalidAliasInConfig) {
    output.error(`Wrong value for alias found in config. It must be a string or array of string.`);
    return 1;
  }

  if (targets instanceof ERRORS.CantParseJSONFile) {
    output.error(`Couldn't parse JSON file ${targets.meta.file}.`);
    return 1;
  }

  if (rules) {
    // If we have rules for path alias we assign them to the domain
    for (const target of targets) {
      output.log(`Assigning path alias rules from ${humanizePath(rulesPath)} to ${target}`);
      const pathAlias = await upsertPathAlias(output, client, rules, target, contextName);
      const remaining = handleCreateAliasError(output, pathAlias);
      if (handleSetupDomainError(output, remaining) !== 1) {
        console.log(`${chalk.cyan('> Success!')} ${rules.length} rules configured for ${chalk.underline(target)} ${setStamp()}`);
      }
    }

    return 0;
  }

  // If there are no rules for path alias we should find out a deployment and perform the alias
  const deployment = await getDeploymentForAlias(client, output, args, opts['--local-config'], user, contextName);
  if (deployment instanceof ERRORS.DeploymentNotFound) {
    output.error(`Failed to find deployment "${deployment.meta.id}" under ${chalk.bold(contextName)}`);
    return 1;
  }

  if (deployment instanceof ERRORS.DeploymentPermissionDenied) {
    output.error(`No permission to access deployment "${deployment.meta.id}" under ${chalk.bold(deployment.meta.context)}`);
    return 1;
  }

  if (deployment === null) {
    output.error(`Couldn't find a deployment to alias. Please provide one as an argument.`);
    return 1;
  }

  // Assign the alias for each of the targets in the array
  for (const target of targets) {
    output.log(`Assigning alias ${target} to deployment ${deployment.url}`);
    const record = await assignAlias(output, client, deployment, target, contextName, noVerify);
    const handleResult = handleSetupDomainError(output, handleCreateAliasError(output, record));
    if (handleResult === 1) {
      return 1;
    } else {
      console.log(`${chalk.cyan('> Success!')} ${handleResult.alias} now points to ${chalk.bold(deployment.url)} ${setStamp()}`);
    }
  }

  return 0;
}

type ThenArg<T> = T extends Promise<infer U> ? U : T
type SetupDomainResolve = ThenArg<ReturnType<typeof setupDomain>>;
type SetupDomainError = Exclude<SetupDomainResolve, Domain>;

function handleSetupDomainError<T>(output: Output, error: SetupDomainError | T): T | 1 {
  if (error instanceof ERRORS.DomainVerificationFailed) {
    const { nsVerification, txtVerification, domain } = error.meta;
    output.error(`We could not alias since the domain ${domain} could not be verified due to the following reasons:\n`);
    output.print(`  ${chalk.gray('a)')} Nameservers verification failed since we see a different set than the intended set:`);
    output.print(`\n${formatNSTable(nsVerification.intendedNameservers, nsVerification.nameservers, { extraSpace: '     ' })}\n\n`);
    output.print(`  ${chalk.gray('b)')} DNS TXT verification failed since found no matching records.`);
    output.print(`\n${formatDnsTable([['_now', 'TXT', txtVerification.verificationRecord]], { extraSpace: '     ' })}\n\n`);
    output.print(`  Once your domain uses either the nameservers or the TXT DNS record from above, run again ${cmd('now domains verify <domain>')}.\n`);
    output.print(`  We will also periodically run a verification check for you and you will receive an email once your domain is verified.\n`);
    output.print('  Read more: https://err.sh/now-cli/domain-verification\n');
    return 1;
  }

  if (error instanceof ERRORS.DomainPermissionDenied) {
    output.error(`You don't have permissions over domain ${chalk.underline(error.meta.domain)} under ${chalk.bold(error.meta.context)}.`);
    return 1;
{'fraction_non_alphanumeric': 0.1452828907152075, 'fraction_numerical': 0.003463967941316308, 'mean_word_length': 3.683206106870229, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 27, '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': '3679790', 'n_tokens_mistral': 4411, 'n_tokens_neox': 4346, 'n_words': 1366}