Beispiel #1
0
const main = async ctx => {
	argv = mri(ctx.argv.slice(2), {
		boolean: ['help', 'debug', 'all'],
		alias: {
			help: 'h',
			debug: 'd'
		}
	})

	argv._ = argv._.slice(1)

	app = argv._[0]
	debug = argv.debug
	apiUrl = ctx.apiUrl

	if (argv.help || app === 'help') {
		help()
		await exit(0)
	}

	const {authConfig: { credentials }, config: { sh, includeScheme }} = ctx
	const {token} = credentials.find(item => item.provider === 'sh')

	try {
		await list({ token, sh, includeScheme })
	} catch (err) {
		console.error(error(`Unknown error: ${err}\n${err.stack}`))
		getProcess().exit(1)
	}
}
Beispiel #2
0
const main = async ctx => {
	argv = mri(ctx.argv.slice(2), {
		boolean: ['help', 'debug', 'hard', 'yes', 'safe'],
		alias: {
			help: 'h',
			debug: 'd',
			yes: 'y',
			safe: 's'
		}
	})

	argv._ = argv._.slice(1)

	debug = argv.debug
	apiUrl = ctx.apiUrl
	hard = argv.hard || false
	skipConfirmation = argv.yes || false
	ids = argv._

	if (argv.help || ids.length === 0 || ids[0] === 'help') {
		help()
		await exit(0)
	}

	const {authConfig: { credentials }, config: { sh }} = ctx
	const {token} = credentials.find(item => item.provider === 'sh')

	try {
		await remove({ token, sh })
	} catch (err) {
		console.error(error(`Unknown error: ${err}\n${err.stack}`))
		getProcess().exit(1)
	}
}
Beispiel #3
0
const main = async ctx => {
  argv = mri(ctx.argv.slice(2), {
    boolean: ['help', 'debug', 'base64'],
    alias: {
      help: 'h',
      debug: 'd',
      base64: 'b'
    }
  })

  argv._ = argv._.slice(1)

  debug = argv.debug
  apiUrl = ctx.apiUrl
  subcommand = argv._[0]

  if (argv.help || !subcommand) {
    help()
    await exit(0)
  }

  const {authConfig: { credentials }, config: { sh }} = ctx
  const {token} = credentials.find(item => item.provider === 'sh')

  try {
    await run({ token, sh })
  } catch (err) {
    handleError(err)
    exit(1)
  }
}
Beispiel #4
0
const main = async ctx => {
	argv = mri(ctx.argv.slice(2), {
		boolean: ['help', 'debug'],
		alias: {
			help: 'h',
			debug: 'd'
		}
	})

	argv._ = argv._.slice(1)

	debug = argv.debug
	apiUrl = ctx.apiUrl

	if (argv.help || argv._[0] === 'help') {
		help()
		exit(0)
	}

	const {authConfig: { credentials }, config: { sh }} = ctx
	const {token} = credentials.find(item => item.provider === 'sh')

	try {
		await run({ token, sh })
	} catch (err) {
		if (err.userError) {
			console.error(error(err.message))
		} else {
			console.error(error(`Unknown error: ${err.stack}`))
		}

		exit(1)
	}
}
Beispiel #5
0
async function main(ctx) {
	argv = mri(ctx.argv.slice(2), mriOpts)

	// very ugly hack – this (now-cli's code) expects that `argv._[0]` is the path
	// we should fix this ASAP
	if (argv._[0] === 'sh') {
		argv._.shift()
	}

	if (argv._[0] === 'deploy') {
		argv._.shift()
	}

	if (argv._[0]) {
		// If path is relative: resolve
		// if path is absolute: clear up strange `/` etc
		path = resolve(getProcess().cwd(), argv._[0])
	} else {
		path = getProcess().cwd()
	}

	// Options
	forceNew = argv.force
	deploymentName = argv.name
	sessionAffinity = argv['session-affinity']
	debug = argv.debug
	clipboard = !argv['no-clipboard']
	forwardNpm = argv['forward-npm']
	followSymlinks = !argv.links
	wantsPublic = argv.public
	apiUrl = ctx.apiUrl
	isTTY = getProcess().stdout.isTTY
	quiet = !isTTY

	if (argv.h || argv.help) {
		help()
		await exit(0)
	}

	const { authConfig: { credentials }, config: { sh } } = ctx
	const { token } = credentials.find(item => item.provider === 'sh')
	const config = sh

	alwaysForwardNpm = config.forwardNpm

	try {
		return sync({ token, config })
	} catch (err) {
		await stopDeployment(err)
	}
}
Beispiel #6
0
const main = async ctx => {
  argv = mri(ctx.argv.slice(2), {
    boolean: ['help', 'debug', 'all'],
    alias: {
      help: 'h',
      debug: 'd'
    }
  })

  argv._ = argv._.slice(1)

  if (argv.help || argv._[0] === 'help') {
    help()
    process.exit(0)
  }

  await whoami(ctx.config.sh)
}
Beispiel #7
0
const main = async ctx => {
  argv = mri(ctx.argv.slice(2), {
    string: ['coupon'],
    boolean: ['help', 'debug', 'external', 'force'],
    alias: {
      help: 'h',
      coupon: 'c',
      debug: 'd',
      external: 'e',
      force: 'f'
    }
  })

  argv._ = argv._.slice(1)

  debug = argv.debug
  apiUrl = ctx.apiUrl
  subcommand = argv._[0]

  if (argv.help || !subcommand) {
    help()
    await exit(0)
  }

  const {authConfig: { credentials }, config: { sh }} = ctx
  const {token} = credentials.find(item => item.provider === 'sh')

  try {
    await run({ token, sh })
  } catch (err) {
    if (err.userError) {
      console.error(error(err.message))
    } else {
      console.error(error(`Unknown error: ${err}\n${err.stack}`))
    }

    exit(1)
  }
}
Beispiel #8
0
const deploy = async ({
  config,
  authConfig,
  argv: argv_
}: {
  config: any,
  authConfig: any,
  argv: Array<string>
}) => {
  const argv = mri(argv_, {
    boolean: ['help'],
    alias: {
      help: 'h'
    }
  })

  // `now [provider] [deploy] [target]`
  const [cmdOrTarget = null, target_ = null] = argv._.slice(2).slice(-2)

  let target

  if (cmdOrTarget === 'aws' || cmdOrTarget === 'deploy') {
    target = target_ === null ? process.cwd() : target_
  } else {
    if (target_) {
      console.error(error('Unexpected number of arguments for deploy command'))
      return 1
    } else {
      target = cmdOrTarget === null ? process.cwd() : cmdOrTarget
    }
  }

  const start = Date.now()
  const resolved = await resolve(target)

  if (resolved === null) {
    console.error(error(`Could not resolve deployment target ${param(target)}`))
    return 1
  }

  let desc

  try {
    desc = await describeProject(resolved)
  } catch (err) {
    if (err.code === 'AMBIGOUS_CONFIG') {
      console.error(
        error(`There is more than one source of \`now\` config: ${err.files}`)
      )
      return 1
    } else {
      throw err
    }
  }

  // a set of files that we personalize for this build
  const overrides = {
    '__now_handler.js': getLambdaHandler(desc)
  }

  // initialize aws client
  const aws = getAWS(authConfig)
  const region = aws.config.region || 'us-west-1'

  console.log(
    info(
      `Deploying ${param(humanPath(resolved))} ${gray('(aws)')} ${gray(
        `(${region})`
      )}`
    )
  )
  const buildStart = Date.now()
  const stopBuildSpinner = wait('Building and bundling your app…')
  const zipFile = await build(resolved, desc, { overrides })
  stopBuildSpinner()

  // lambda limits to 50mb
  if (zipFile.length > 50 * 1024 * 1024) {
    console.error(error('The build exceeds the 50mb AWS Lambda limit'))
    return 1
  }

  console.log(
    ok(
      `Build generated a ${bold(bytes(zipFile.length))} zip ${gray(
        `[${ms(Date.now() - buildStart)}]`
      )}`
    )
  )

  const iam = new aws.IAM({ apiVersion: '2010-05-08' })

  const gateway = new aws.APIGateway({
    apiVersion: '2015-07-09',
    region
  })

  const lambda = new aws.Lambda({
    apiVersion: '2015-03-31',
    region
  })

  let role

  try {
    role = await getRole(iam, { RoleName: NOW_DEFAULT_IAM_ROLE })
  } catch (err) {
    if ('NoSuchEntity' === err.code) {
      const iamStart = Date.now()
      role = await createRole(iam, {
        AssumeRolePolicyDocument: JSON.stringify(IAM_POLICY_DOCUMENT),
        RoleName: NOW_DEFAULT_IAM_ROLE
      })
      console.log(
        ok(
          `Initialized IAM role ${param(NOW_DEFAULT_IAM_ROLE)} ${gray(
            `[${ms(iamStart - Date.now())}]`
          )}`
        )
      )
    } else {
      throw err
    }
  }

  const deploymentId = 'now-' + desc.name + '-' + (await uid(10))

  const resourcesStart = Date.now()
  const stopResourcesSpinner = wait('Creating API resources')

  debug('initializing lambda function')
  const λ = await retry(
    async bail => {
      try {
        return await createFunction(lambda, {
          Code: {
            ZipFile: zipFile
          },
          Runtime: 'nodejs6.10',
          Description: desc.description,
          FunctionName: deploymentId,
          Handler: '__now_handler.handler',
          Role: role.Role.Arn,
          Timeout: 15,
          MemorySize: 512
        })
      } catch (err) {
        if (
          err.retryable ||
          // created role is not ready
          err.code === 'InvalidParameterValueException'
        ) {
          debug('retrying creating function (%s)', err.message)
          throw err
        }

        bail(err)
      }
    },
    { minTimeout: 2500, maxTimeout: 5000 }
  )

  debug('initializing api gateway')
  const api = await createAPI(gateway, {
    name: deploymentId,
    description: desc.description
  })

  debug('retrieving root resource id')
  const resources = await getResources(gateway, {
    restApiId: api.id
  })
  const rootResourceId = resources.items[0].id

  debug('initializing gateway method for /')
  await putMethod(gateway, {
    restApiId: api.id,
    authorizationType: 'NONE',
    httpMethod: 'ANY',
    resourceId: rootResourceId
  })

  debug('initializing gateway integration for /')
  await putIntegration(gateway, {
    restApiId: api.id,
    resourceId: rootResourceId,
    httpMethod: 'ANY',
    type: 'AWS_PROXY',
    integrationHttpMethod: 'POST',
    uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${λ.FunctionArn}/invocations`
  })

  debug('initializing gateway resource')
  const resource = await createResource(gateway, {
    restApiId: api.id,
    parentId: rootResourceId,
    pathPart: '{proxy+}'
  })

  debug('initializing gateway method for {proxy+}')
  await putMethod(gateway, {
    restApiId: api.id,
    authorizationType: 'NONE',
    httpMethod: 'ANY',
    resourceId: resource.id
  })

  debug('initializing gateway integration for {proxy+}')
  await putIntegration(gateway, {
    restApiId: api.id,
    resourceId: resource.id,
    httpMethod: 'ANY',
    type: 'AWS_PROXY',
    integrationHttpMethod: 'POST',
    uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${λ.FunctionArn}/invocations`
  })

  debug('creating deployment')
  await createDeployment(gateway, {
    restApiId: api.id,
    stageName: 'now'
  })

  const [, accountId] = role.Role.Arn.match(/^arn:aws:iam::(\d+):/)

  await addPermission(lambda, {
    FunctionName: deploymentId,
    StatementId: deploymentId,
    Action: 'lambda:InvokeFunction',
    Principal: 'apigateway.amazonaws.com',
    SourceArn: `arn:aws:execute-api:${region}:${accountId}:${api.id}/now/ANY/*`
  })

  stopResourcesSpinner()
  console.log(
    ok(
      `API resources created (id: ${param(deploymentId)}) ${gray(
        `[${ms(Date.now() - resourcesStart)}]`
      )}`
    )
  )

  const url = `https://${api.id}.execute-api.${region}.amazonaws.com/now`
  const copied = copyToClipboard(url, config.copyToClipboard)

  console.log(
    success(
      `${link(url)} ${copied ? gray('(in clipboard)') : ''} ${gray(
        `[${ms(Date.now() - start)}]`
      )}`
    )
  )

  return 0
}
Beispiel #9
0
const login = async ctx => {
  const argv = mri(ctx.argv.slice(2), {
    boolean: ['help'],
    alias: {
      help: 'h'
    }
  })

  if (argv.help) {
    help()
    await exit(0)
  }

  argv._ = argv._.slice(1)

  const apiUrl = ctx.apiUrl
  let email
  let emailIsValid = false
  let stopSpinner

  const possibleAddress = argv._[0]

  // if the last arg is not the command itself, then maybe it's an email
  if (possibleAddress) {
    if (!validateEmail(possibleAddress)) {
      // if it's not a valid email, let's just error
      console.log(error(`Invalid email: ${param(possibleAddress)}.`))
      return 1
    }

    // valid email, no need to prompt the user
    email = possibleAddress
  } else {
    do {
      try {
        email = await readEmail()
      } catch (err) {
        let erase = ''
        if (err.message.includes('Aborted')) {
          // no need to keep the prompt if the user `ctrl+c`ed
          erase = eraseLines(2)
        }
        console.log(erase + err.message)
        return 1
      }
      emailIsValid = validateEmail(email)
      if (!emailIsValid) {
        // let's erase the `> Enter email [...]`
        // we can't use `console.log()` because it appends a `\n`
        // we need this check because `email-prompt` doesn't print
        // anything if there's no TTY
        process.stdout.write(eraseLines(2))
      }
    } while (!emailIsValid)
  }

  let verificationToken
  let securityCode

  stopSpinner = wait('Sending you an email')

  try {
    const data = await getVerificationData({ apiUrl, email })
    verificationToken = data.token
    securityCode = data.securityCode
  } catch (err) {
    stopSpinner()
    console.log(err.message)
    return 1
  }

  stopSpinner()

  // Clear up `Sending email` success message
  process.stdout.write(eraseLines(possibleAddress ? 1 : 2))

  console.log(info(
    `We sent an email to ${highlight(email)}. Please follow the steps provided`,
    `  inside it and make sure the security code matches ${highlight(securityCode)}.`
  ))

  stopSpinner = wait('Waiting for your confirmation')

  let token

  while (!token) {
    try {
      await sleep(ms('1s'))
      token = await verify({ apiUrl, email, verificationToken })
    } catch (err) {
      if (/invalid json response body/.test(err.message)) {
        // /now/registraton is currently returning plain text in that case
        // we just wait for the user to click on the link
      } else {
        stopSpinner()
        console.log(err.message)
        return 1
      }
    }
  }

  stopSpinner()
  console.log(ok('Email confirmed'))

  stopSpinner = wait('Feching your personal details')

  let user

  try {
    user = await getUser({ apiUrl, token })
  } catch (err) {
    stopSpinner()
    console.log(err)
    return 1
  }

  const index = ctx.authConfig.credentials.findIndex(c => c.provider === 'sh')
  const obj = { provider: 'sh', token }

  if (index === -1) {
    // wasn't logged in before
    ctx.authConfig.credentials.push(obj)
  } else {
    // let's just replace the existing object
    ctx.authConfig.credentials[index] = obj
  }

  // NOTE: this will override any existing config for `sh`
  ctx.config.sh = { user }

  writeToAuthConfigFile(ctx.authConfig)
  writeToConfigFile(ctx.config)

  stopSpinner()
  console.log(ok('Fetched your personal details'))

  console.log(
    ready(
      `Authentication token and personal details saved in ${param(
        hp(getNowDir())
      )}`
    )
  )

  return ctx
}
Beispiel #10
0
#!/usr/bin/env node
'use strict'

const mri = require('mri')
const pkg = require('./package.json')

const argv = mri(process.argv.slice(2), {
	boolean: ['help', 'h', 'version', 'v', 'quiet', 'q']
})

if (argv.help || argv.h) {
	process.stdout.write(`
Usage:
    record-vbb-delays [command] [options]
Options:
	--db            -d  Path to LevelDB. Default: vbb-delays.ldb
	--stations      -s  Stations to monitor. Default: all
	--stations-file     JSON file with stations to monitor.
	--interval      -i  In seconds. Default: 30
	--quiet         -q  Don't show progress reports. Default: false
Examples:
    record-vbb-delays --db my-custom.leveldb -s 900000100003,900000100001
    record-vbb-delays --stations-file stations-to-monitor.json -q
    record-vbb-delays export-sql --db my-custom.leveldb
    record-vbb-delays export-ndjson --db my-custom.leveldb
\n`)
	process.exit(0)
}

if (argv.version || argv.v) {
	process.stdout.write(`record-vbb-delays v${pkg.version}\n`)
Beispiel #11
0
const success = require('../../../util/output/success')
const uid = require('../../../util/output/uid')
const eraseLines = require('../../../util/output/erase-lines')
const stamp = require('../../../util/output/stamp')
const error = require('../../../util/output/error')
const treatBuyError = require('../util/domains/treat-buy-error')
const scaleInfo = require('./scale-info')
const { DOMAIN_VERIFICATION_ERROR } = require('./errors')
const isZeitWorld = require('./is-zeit-world')
const resolve4 = require('./dns')
const toHost = require('./to-host')
const exit = require('../../../util/exit')
const Now = require('./')

const argv = mri(process.argv.slice(2), {
  boolean: ['no-clipboard'],
  alias: { 'no-clipboard': 'C' }
})

const isTTY = process.stdout.isTTY
const clipboard = !argv['no-clipboard']
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/

module.exports = class Alias extends Now {
  async ls(deployment) {
    if (deployment) {
      const target = await this.findDeployment(deployment)

      if (!target) {
        const err = new Error(
          `Aliases not found by "${deployment}". Run ${chalk.dim(
            '`now alias ls`'
Beispiel #12
0
const main = async (argv_) => {
  await checkForUpdates()

  const argv = mri(argv_, {
    boolean: [
      'help',
      'version'
    ],
    string: [
      'token',
      'team',
      'api'
    ],
    alias: {
      help: 'h',
      version: 'v',
      token: 't',
      team: 'T'
    }
  })

  // the second argument to the command can be a path
  // (as in: `now path/`) or a subcommand / provider
  // (as in: `now ls` or `now aws help`)
  let targetOrSubcommand: ?string = argv._[2]

  // we want to handle version or help directly only
  if (!targetOrSubcommand) {
    if (argv.version) {
      console.log(require('../package').version)
      return 0
    }
  }

  let nowDirExists

  try {
    nowDirExists = existsSync(NOW_DIR)
  } catch (err) {
    console.error(
      error(
        'An unexpected error occurred while trying to find the ' +
          'now global directory: ' +
          err.message
      )
    )
    return
  }

  if (!nowDirExists) {
    try {
      await mkdirp(NOW_DIR)
    } catch (err) {
      console.error(error(
        'An unexpected error occurred while trying to create the ' +
          `now global directory "${hp(NOW_DIR)}" ` +
          err.message
      ))
    }
  }

  let migrated = false
  let configExists

  try {
    configExists = existsSync(NOW_CONFIG_PATH)
  } catch (err) {
    console.error(
      error(
        'An unexpected error occurred while trying to find the ' +
          `now config file "${hp(NOW_CONFIG_PATH)}" ` +
          err.message
      )
    )
    return
  }

  let config

  if (configExists) {
    try {
      config = configFiles.readConfigFile()
    } catch (err) {
      console.error(
        error(
          'An unexpected error occurred while trying to read the ' +
            `now config in "${hp(NOW_CONFIG_PATH)}" ` +
            err.message
        )
      )
      return
    }

    try {
      config = JSON.parse(config)
    } catch (err) {
      console.error(
        error(
          `An error occurred while trying to parse "${hp(NOW_CONFIG_PATH)}": ` +
            err.message
        )
      )
      return
    }
  } else {
    const results = await getDefaultCfg()

    config = results.config
    migrated = results.migrated

    try {
      configFiles.writeToConfigFile(config)
    } catch (err) {
      console.error(
        error(
          'An unexpected error occurred while trying to write the ' +
            `default now config to "${hp(NOW_CONFIG_PATH)}" ` +
            err.message
        )
      )
      return
    }
  }

  let authConfigExists

  try {
    authConfigExists = existsSync(NOW_AUTH_CONFIG_PATH)
  } catch (err) {
    console.error(
      error(
        'An unexpected error occurred while trying to find the ' +
          `now auth file "${hp(NOW_AUTH_CONFIG_PATH)}" ` +
          err.message
      )
    )
    return
  }

  let authConfig = null

  if (authConfigExists) {
    try {
      authConfig = configFiles.readAuthConfigFile()
    } catch (err) {
      console.error(
        error(
          'An unexpected error occurred while trying to read the ' +
            `now auth config in "${hp(NOW_AUTH_CONFIG_PATH)}" ` +
            err.message
        )
      )
      return
    }

    try {
      authConfig = JSON.parse(authConfig)

      if (!Array.isArray(authConfig.credentials)) {
        console.error(
          error(
            `The content of "${hp(NOW_AUTH_CONFIG_PATH)}" is invalid. ` +
              'No `credentials` list found inside'
          )
        )
        return
      }

      for (const [i, { provider }] of authConfig.credentials.entries()) {
        if (null == provider) {
          console.error(
            error(
              `Invalid credential found in "${hp(NOW_AUTH_CONFIG_PATH)}". ` +
                `Missing \`provider\` key in entry with index ${i}`
            )
          )
          return
        }

        if (!(provider in providers)) {
          console.error(
            error(
              `Invalid credential found in "${hp(NOW_AUTH_CONFIG_PATH)}". ` +
                `Unknown provider "${provider}"`
            )
          )
          return
        }
      }
    } catch (err) {
      console.error(
        error(
          `An error occurred while trying to parse "${hp(
            NOW_AUTH_CONFIG_PATH
          )}": ` + err.message
        )
      )
      return
    }
  } else {
    const results = await getDefaultAuthCfg()

    authConfig = results.config
    migrated = results.migrated

    try {
      configFiles.writeToAuthConfigFile(authConfig)
    } catch (err) {
      console.error(
        error(
          'An unexpected error occurred while trying to write the ' +
            `default now config to "${hp(NOW_CONFIG_PATH)}" ` +
            err.message
        )
      )
      return
    }
  }

  // Let the user know we migrated the config
  if (migrated) {
    const directory = param(hp(NOW_DIR))
    console.log(info(`Your credentials and configuration were migrated to ${directory}`))
  }

  // the context object to supply to the providers or the commands
  const ctx: Object = {
    config,
    authConfig,
    argv: argv_
  }

  if (targetOrSubcommand === 'config') {
    const _config = require('./config')
    const subcommand = _config.subcommands.has(argv._[3]) ? argv._[3] : 'help'

    debug(`executing config %s`, subcommand)

    try {
      return _config[subcommand](ctx)
    } catch (err) {
      console.error(
        error(
          `An unexpected error occurred in config ${subcommand}: ${err.stack}`
        )
      )
      return
    }
  }

  let suppliedProvider = null

  // if the target is something like `aws`
  if (targetOrSubcommand && targetOrSubcommand in providers) {
    debug('user supplied a known provider')
    const targetPath = join(process.cwd(), targetOrSubcommand)
    const targetPathExists = existsSync(targetPath)

    if (targetPathExists) {
      console.error(
        error(
          `The supplied argument ${param(targetOrSubcommand)} is ambiguous. ` +
            'Both a directory and a provider are known'
        )
      )
      return
    }

    suppliedProvider = targetOrSubcommand
    targetOrSubcommand = argv._[3]
  }

  // $FlowFixMe
  let { defaultProvider = null }: { defaultProvider: ?string } = config

  if (null === suppliedProvider) {
    if (null === defaultProvider) {
      debug(`falling back to default now provider 'sh'`)
      defaultProvider = 'sh'
    } else {
      debug('using provider supplied by user', defaultProvider)

      if (!(defaultProvider in providers)) {
        console.error(
          error(
            `The \`defaultProvider\` "${defaultProvider}" supplied in ` +
              `"${NOW_CONFIG_PATH}" is not a valid provider`
          )
        )
        return
      }
    }
  }

  const providerName = suppliedProvider || defaultProvider
  const provider: Object = providers[providerName]

  let subcommand

  // we check if we are deploying something
  if (targetOrSubcommand) {
    const targetPath = join(process.cwd(), targetOrSubcommand)
    const targetPathExists = existsSync(targetPath)

    const subcommandExists =
      GLOBAL_COMMANDS.has(targetOrSubcommand) ||
      provider.subcommands.has(targetOrSubcommand)

    if (targetPathExists && subcommandExists) {
      console.error(
        error(
          `The supplied argument ${param(targetOrSubcommand)} is ambiguous. ` +
            'Both a directory and a subcommand are known'
        )
      )
      return
    }

    if (subcommandExists) {
      debug('user supplied known subcommand', targetOrSubcommand)
      subcommand = targetOrSubcommand
    } else {
      debug('user supplied a possible target for deployment')
      // our default command is deployment
      // at this point we're
      subcommand = 'deploy'
    }
  } else {
    debug('user supplied no target, defaulting to deploy')
    subcommand = 'deploy'
  }

  if (subcommand === 'help') {
    subcommand = argv._[3] || 'deploy'
    ctx.argv.push('-h')
  }

  const { sh } = ctx.config
  ctx.apiUrl = 'https://api.zeit.co'

  if (argv.api && typeof argv.api === 'string') {
    ctx.apiUrl = argv.api
  } else if (sh && sh.api) {
    ctx.apiUrl = sh.api
  }

  // $FlowFixMe
  const { isTTY } = process.stdout

  // If no credentials are set at all, prompt for
  // login to the .sh provider
  if (
    !authConfig.credentials.length &&
    !ctx.argv.includes('-h') && !ctx.argv.includes('--help') &&
    !argv.token &&
    subcommand !== 'login'
  ) {
    if (isTTY) {
      console.log(info(`No existing credentials found. Please log in:`))

      subcommand = 'login'
      ctx.argv[2] = 'login'

      // Ensure that sub commands lead to login as well, if
      // no credentials are defined
      ctx.argv = ctx.argv.splice(0, 3)
    } else {
      console.error(error({
        message: 'No existing credentials found. Please ' +
        `${param('now login')} to log in or pass ${param('--token')}`,
        slug: 'no-credentials-found'
      }))
      await exit(1)
    }
  }

  if (typeof argv.token === 'string' && subcommand === 'switch') {
    console.error(error({
      message: `This command doesn't work with ${param('--token')}. Please use ${param('--team')}.`,
      slug: 'no-token-allowed'
    }))
    await exit(1)
  }

  if (typeof argv.token === 'string') {
    const {token} = argv

    if (token.length === 0) {
      console.error(error({
        message: `You defined ${param('--token')}, but it's missing a value`,
        slug: 'missing-token-value'
      }))
      await exit(1)
    }

    const obj = {
      provider: 'sh',
      token
    }

    const credentialsIndex = ctx.authConfig.credentials.findIndex(
      cred => cred.provider === 'sh'
    )

    if (credentialsIndex === -1) {
      ctx.authConfig.credentials.push(obj)
    } else {
      ctx.authConfig.credentials[credentialsIndex] = obj
    }

    if (isTTY) {
      console.log(info('Caching account information'))
    }

    const user = await getUser({
      apiUrl: ctx.apiUrl,
      token
    })

    ctx.config.sh = Object.assign(ctx.config.sh || {}, { user })
  }

  if (typeof argv.team === 'string' && subcommand !== 'login') {
    const { team } = argv
    const { sh } = ctx.config

    if (team.length === 0) {
      console.error(error({
        message: `You defined ${param('--team')}, but it's missing a value`,
        slug: 'missing-team-value'
      }))
      await exit(1)
    }

    const cachedUser = sh && sh.user && sh.user.username === team

    if (cachedUser) {
      delete ctx.config.sh.currentTeam
    }

    const cachedTeam = sh && sh.currentTeam && sh.currentTeam.slug === team

    // Only download team data if not cached
    if (!cachedTeam && !cachedUser) {
      if (isTTY) {
        console.log(info('Caching team information'))
      }

      const { token } = ctx.authConfig.credentials.find(item => item.provider === 'sh')

      const headers = {
        Authorization: `Bearer ${token}`
      }

      const url = `https://api.zeit.co/teams/?slug=${team}`
      let body

      try {
        const res = await fetch(url, { headers })

        if (res.status === 403) {
          console.error(error({
            message: `You don't have access to the specified team`,
            slug: 'team-not-accessible'
          }))
          await exit(1)
        }

        body = await res.json()
      } catch (err) {
        console.error(error('Not able to load teams'))
        await exit(1)
      }

      if (!body || body.error) {
        console.error(error({
          message: 'The specified team doesn\'t exist',
          slug: 'team-not-existent'
        }))
        await exit(1)
      }

      // $FlowFixMe
      delete body.creator_id

      // $FlowFixMe
      delete body.created

      ctx.config.sh.currentTeam = body
    }
  }

  try {
    await provider[subcommand](ctx)
  } catch (err) {
    console.error(
      error(
        `An unexpected error occurred in provider ${subcommand}: ${err.stack}`
      )
    )
  }

  if (providerName === 'gcp') {
    process.exit()
  }
}
Beispiel #13
0
const deploy = async (ctx: {
  config: any,
  authConfig: any,
  argv: Array<string>
}) => {
  const { argv: argv_ } = ctx
  const argv = mri(argv_, {
    boolean: ['help'],
    alias: {
      help: 'h'
    }
  })

  const token = await getToken(ctx)

  // `now [provider] [deploy] [target]`
  const [cmdOrTarget = null, target_ = null] = argv._.slice(2).slice(-2)

  let target

  if (cmdOrTarget === 'gcp' || cmdOrTarget === 'deploy') {
    target = target_ === null ? process.cwd() : target_
  } else {
    if (target_) {
      console.error(error('Unexpected number of arguments for deploy command'))
      return 1
    } else {
      target = cmdOrTarget === null ? process.cwd() : cmdOrTarget
    }
  }

  const start = Date.now()
  const resolved = await resolve(target)

  if (resolved === null) {
    console.error(error(`Could not resolve deployment target ${param(target)}`))
    return 1
  }

  let desc = null

  try {
    desc = await describeProject(resolved)
  } catch (err) {
    if (err.code === 'AMBIGOUS_CONFIG') {
      console.error(
        error(`There is more than one source of \`now\` config: ${err.files}`)
      )
      return 1
    } else {
      throw err
    }
  }

  // Example now.json for gcpConfig
  // {
  //   functionName: String,
  //   timeout: String,
  //   memory: Number,
  //   region: String
  // }
  const { nowJSON: { gcp: gcpConfig } } = desc

  const overrides = {
    'function.js': getFunctionHandler(desc)
  }

  const region = gcpConfig.region || 'us-central1'

  console.log(
    info(
      `Deploying ${param(humanPath(resolved))} ${gray('(gcp)')} ${gray(
        `(${region})`
      )}`
    )
  )

  const buildStart = Date.now()
  const stopBuildSpinner = wait('Building and bundling your app…')
  const zipFile = await build(resolved, desc, { overrides })
  stopBuildSpinner()

  if (zipFile.length > 100 * 1024 * 1024) {
    console.error(error('The build exceeds the 100mb GCP Functions limit'))
    return 1
  }

  console.log(
    ok(
      `Build generated a ${bold(bytes(zipFile.length))} zip ${gray(
        `[${ms(Date.now() - buildStart)}]`
      )}`
    )
  )

  const deploymentId = gcpConfig.functionName || 'now-' + desc.name + '-' + (await uid(10))
  const zipFileName = `${deploymentId}.zip`

  const { project } = ctx.authConfig.credentials.find(p => p.provider === 'gcp')

  const resourcesStart = Date.now()

  debug('checking gcp function check')
  const fnCheckExistsRes = () =>
    fetch(
      `https://cloudfunctions.googleapis.com/v1beta2/projects/${project.id}/locations/${region}/functions/${deploymentId}`,
      {
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${token}`,
        },
      }
    )
  const checkExistsRes = await fnCheckExistsRes()
  const fnExists = checkExistsRes.status !== 404

  const stopResourcesSpinner = wait(`${fnExists ? 'Updating' : 'Creating'} API resources`)

  if (!ctx.config.gcp) ctx.config.gcp = {}
  if (!ctx.config.gcp.bucketName) {
    ctx.config.gcp.bucketName = generateBucketName()
    writeToConfigFile(ctx.config)
  }

  const { bucketName } = ctx.config.gcp

  debug('creating gcp storage bucket')
  const bucketRes = await fetch(
    `https://www.googleapis.com/storage/v1/b?project=${project.id}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`
      },
      body: JSON.stringify({
        name: bucketName
      })
    }
  )

  if (
    bucketRes.status !== 200 &&
    bucketRes.status !== 409 /* already exists */
  ) {
    console.error(
      error(
        `Error while creating GCP Storage bucket: ${await bucketRes.text()}`
      )
    )
    return 1
  }

  debug('creating gcp storage file')
  const fileRes = await fetch(
    `https://www.googleapis.com/upload/storage/v1/b/${encodeURIComponent(
      bucketName
    )}/o?uploadType=media&name=${encodeURIComponent(
      zipFileName
    )}&project=${encodeURIComponent(project.id)}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/zip',
        'Content-Length': zipFile.length,
        Authorization: `Bearer ${token}`
      },
      body: zipFile
    }
  )

  try {
    await assertSuccessfulResponse(fileRes)
  } catch (err) {
    console.error(error(err.message))
    return 1
  }

  debug('creating gcp function create')
  const fnCreateRes = await fetch(
    `https://cloudfunctions.googleapis.com/v1beta2/projects/${project.id}/locations/${region}/functions${fnExists ? `/${deploymentId}` : ''}`,
    {
      method: fnExists ? 'PUT' : 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`
      },
      body: JSON.stringify({
        name: `projects/${project.id}/locations/${region}/functions/${deploymentId}`,
        timeout: gcpConfig.timeout || '15s',
        availableMemoryMb: gcpConfig.memory || 512,
        sourceArchiveUrl: `gs://${encodeURIComponent(
          bucketName
        )}/${zipFileName}`,
        entryPoint: 'handler',
        httpsTrigger: {
          url: null
        }
      })
    }
  )

  if (403 === fnCreateRes.status) {
    const url = `https://console.cloud.google.com/apis/api/cloudfunctions.googleapis.com/overview?project=${project.id}`
    console.error(
      error(
        'GCP Permission Denied error. Make sure the "Google Cloud Functions API" ' +
          `is enabled in the API Manager\n  ${bold('API Manager URL')}: ${link(
            url
          )}`
      )
    )
    return 1
  }

  try {
    await assertSuccessfulResponse(fnCreateRes)
  } catch (err) {
    console.error(error(err.message))
    return 1
  }

  let retriesLeft = 10
  let status
  let url = ''

  do {
    if (status === 'FAILED') {
      console.error(
        error('API resources failed to deploy.')
      )
      return 1
    } else if (!--retriesLeft) {
      console.error(
        error('Could not determine status of the deployment: ' + String(url))
      )
      return 1
    } else {
      await sleep(5000)
    }

    const checkExistsRes = await fnCheckExistsRes()
    try {
      await assertSuccessfulResponse(checkExistsRes)
    } catch (err) {
      console.error(error(err.message))
      return 1
    }

    ;({ status, httpsTrigger: { url } } = await checkExistsRes.json())
  } while (status !== 'READY')

  stopResourcesSpinner()
  console.log(
    ok(
      `API resources ${fnExists ? 'updated' : 'created'} (id: ${param(deploymentId)}) ${gray(
        `[${ms(Date.now() - resourcesStart)}]`
      )}`
    )
  )

  const copied = copyToClipboard(url, ctx.config.copyToClipboard)

  console.log(
    success(
      `${link(url)} ${copied ? gray('(in clipboard)') : ''} ${gray(
        `[${ms(Date.now() - start)}]`
      )}`
    )
  )

  return 0
}