2022-05-24 00:37:21 +02:00
|
|
|
import os from 'os'
|
|
|
|
import path from 'path'
|
2022-04-20 14:23:09 +02:00
|
|
|
import execa from 'execa'
|
2022-05-24 00:37:21 +02:00
|
|
|
import fs from 'fs-extra'
|
2022-04-20 14:23:09 +02:00
|
|
|
import { NextInstance } from './base'
|
|
|
|
import {
|
|
|
|
TEST_PROJECT_NAME,
|
|
|
|
TEST_TEAM_NAME,
|
|
|
|
TEST_TOKEN,
|
|
|
|
} from '../../../scripts/reset-vercel-project.mjs'
|
|
|
|
import fetch from 'node-fetch'
|
2023-01-03 10:05:50 +01:00
|
|
|
import { Span } from 'next/src/trace'
|
2022-04-20 14:23:09 +02:00
|
|
|
|
|
|
|
export class NextDeployInstance extends NextInstance {
|
|
|
|
private _cliOutput: string
|
|
|
|
private _buildId: string
|
|
|
|
|
|
|
|
public get buildId() {
|
|
|
|
// get deployment ID via fetch since we can't access
|
|
|
|
// build artifacts directly
|
|
|
|
return this._buildId
|
|
|
|
}
|
|
|
|
|
2022-12-16 09:58:04 +01:00
|
|
|
public async setup(parentSpan: Span) {
|
|
|
|
await super.createTestDir({ parentSpan, skipInstall: true })
|
2022-04-20 14:23:09 +02:00
|
|
|
|
|
|
|
// ensure Vercel CLI is installed
|
|
|
|
try {
|
|
|
|
const res = await execa('vercel', ['--version'])
|
2022-05-24 00:37:21 +02:00
|
|
|
require('console').log(`Using Vercel CLI version:`, res.stdout)
|
2022-04-20 14:23:09 +02:00
|
|
|
} catch (_) {
|
2022-05-24 00:37:21 +02:00
|
|
|
require('console').log(`Installing Vercel CLI`)
|
2022-04-20 14:23:09 +02:00
|
|
|
await execa('npm', ['i', '-g', 'vercel@latest'], {
|
|
|
|
stdio: 'inherit',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
const vercelFlags = ['--scope', TEST_TEAM_NAME]
|
|
|
|
const vercelEnv = { ...process.env, TOKEN: TEST_TOKEN }
|
2022-05-24 00:37:21 +02:00
|
|
|
|
|
|
|
// create auth file in CI
|
|
|
|
if (process.env.NEXT_TEST_JOB) {
|
|
|
|
const vcConfigDir = path.join(os.homedir(), '.vercel')
|
|
|
|
await fs.ensureDir(vcConfigDir)
|
|
|
|
await fs.writeFile(
|
|
|
|
path.join(vcConfigDir, 'auth.json'),
|
|
|
|
JSON.stringify({ token: TEST_TOKEN })
|
|
|
|
)
|
|
|
|
vercelFlags.push('--global-config', vcConfigDir)
|
|
|
|
}
|
|
|
|
require('console').log(`Linking project at ${this.testDir}`)
|
2022-04-20 14:23:09 +02:00
|
|
|
|
|
|
|
// link the project
|
|
|
|
const linkRes = await execa(
|
|
|
|
'vercel',
|
2023-04-06 21:18:42 +02:00
|
|
|
['link', '-p', TEST_PROJECT_NAME, '--yes', ...vercelFlags],
|
2022-04-20 14:23:09 +02:00
|
|
|
{
|
|
|
|
cwd: this.testDir,
|
|
|
|
env: vercelEnv,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if (linkRes.exitCode !== 0) {
|
|
|
|
throw new Error(
|
|
|
|
`Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
|
|
|
|
)
|
|
|
|
}
|
2022-05-24 00:37:21 +02:00
|
|
|
require('console').log(`Deploying project at ${this.testDir}`)
|
2022-04-20 14:23:09 +02:00
|
|
|
|
2022-06-10 19:35:12 +02:00
|
|
|
const additionalEnv = []
|
|
|
|
|
|
|
|
for (const key of Object.keys(this.env || {})) {
|
|
|
|
additionalEnv.push('--build-env')
|
|
|
|
additionalEnv.push(`${key}=${this.env[key]}`)
|
|
|
|
additionalEnv.push('--env')
|
|
|
|
additionalEnv.push(`${key}=${this.env[key]}`)
|
|
|
|
}
|
|
|
|
|
2023-02-15 03:43:29 +01:00
|
|
|
additionalEnv.push('--build-env')
|
|
|
|
additionalEnv.push(
|
|
|
|
`VERCEL_CLI_VERSION=${process.env.VERCEL_CLI_VERSION || 'vercel@latest'}`
|
|
|
|
)
|
2022-08-22 16:54:25 +02:00
|
|
|
|
2022-04-20 14:23:09 +02:00
|
|
|
const deployRes = await execa(
|
|
|
|
'vercel',
|
|
|
|
[
|
|
|
|
'deploy',
|
|
|
|
'--build-env',
|
|
|
|
'NEXT_PRIVATE_TEST_MODE=1',
|
|
|
|
'--build-env',
|
|
|
|
'NEXT_TELEMETRY_DISABLED=1',
|
2022-06-10 19:35:12 +02:00
|
|
|
...additionalEnv,
|
2022-04-20 14:23:09 +02:00
|
|
|
'--force',
|
|
|
|
...vercelFlags,
|
|
|
|
],
|
|
|
|
{
|
|
|
|
cwd: this.testDir,
|
|
|
|
env: vercelEnv,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if (deployRes.exitCode !== 0) {
|
|
|
|
throw new Error(
|
|
|
|
`Failed to deploy project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// the CLI gives just the deployment URL back when not a TTY
|
|
|
|
this._url = deployRes.stdout
|
|
|
|
this._parsedUrl = new URL(this._url)
|
|
|
|
|
2022-05-24 00:37:21 +02:00
|
|
|
require('console').log(`Deployment URL: ${this._url}`)
|
2022-04-20 14:23:09 +02:00
|
|
|
const buildIdUrl = `${this._url}${
|
|
|
|
this.basePath || ''
|
|
|
|
}/_next/static/__BUILD_ID`
|
|
|
|
|
|
|
|
const buildIdRes = await fetch(buildIdUrl)
|
|
|
|
|
|
|
|
if (!buildIdRes.ok) {
|
2022-05-24 00:37:21 +02:00
|
|
|
require('console').error(
|
2022-04-20 14:23:09 +02:00
|
|
|
`Failed to load buildId ${buildIdUrl} (${buildIdRes.status})`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
this._buildId = (await buildIdRes.text()).trim()
|
|
|
|
|
2022-05-24 00:37:21 +02:00
|
|
|
require('console').log(`Got buildId: ${this._buildId}`)
|
2022-04-20 14:23:09 +02:00
|
|
|
|
|
|
|
const cliOutputRes = await fetch(
|
|
|
|
`https://vercel.com/api/v1/deployments/${this._parsedUrl.hostname}/events?builds=1&direction=backward`,
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
Authorization: `Bearer ${TEST_TOKEN}`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!cliOutputRes.ok) {
|
|
|
|
throw new Error(
|
|
|
|
`Failed to get build output: ${await cliOutputRes.text()} (${
|
|
|
|
cliOutputRes.status
|
|
|
|
})`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
this._cliOutput = (await cliOutputRes.json())
|
|
|
|
.map((line) => line.text || '')
|
|
|
|
.join('\n')
|
|
|
|
}
|
|
|
|
|
|
|
|
public get cliOutput() {
|
|
|
|
return this._cliOutput || ''
|
|
|
|
}
|
|
|
|
|
|
|
|
public async start() {
|
|
|
|
// no-op as the deployment is created during setup()
|
|
|
|
}
|
|
|
|
|
|
|
|
public async patchFile(filename: string, content: string): Promise<void> {
|
|
|
|
throw new Error('patchFile is not available in deploy test mode')
|
|
|
|
}
|
|
|
|
public async readFile(filename: string): Promise<string> {
|
|
|
|
throw new Error('readFile is not available in deploy test mode')
|
|
|
|
}
|
|
|
|
public async deleteFile(filename: string): Promise<void> {
|
|
|
|
throw new Error('deleteFile is not available in deploy test mode')
|
|
|
|
}
|
|
|
|
public async renameFile(
|
|
|
|
filename: string,
|
|
|
|
newFilename: string
|
|
|
|
): Promise<void> {
|
|
|
|
throw new Error('renameFile is not available in deploy test mode')
|
|
|
|
}
|
|
|
|
}
|