mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
637 lines
20 KiB
TypeScript
637 lines
20 KiB
TypeScript
import os from 'os'
|
|
import path from 'path'
|
|
import execa from 'execa'
|
|
import fs from 'fs-extra'
|
|
import { NextInstance } from './base'
|
|
import * as projectEnv from '../../../scripts/reset-project.mjs'
|
|
import { Span } from 'next/dist/trace'
|
|
import { setTimeout } from 'timers/promises'
|
|
|
|
export class NextDeployInstance extends NextInstance {
|
|
private _cliOutput: string
|
|
private _buildId: string
|
|
private _deploymentId: string | undefined
|
|
private _supportsImmutableAssets: boolean = false
|
|
private _writtenHostsLine: string | null = null
|
|
|
|
protected throwIfUnavailable(): void | never {
|
|
if (this.isStopping !== null) {
|
|
throw new Error('Next.js is no longer available.', {
|
|
cause: this.isStopping,
|
|
})
|
|
}
|
|
if (this.isDestroyed !== null) {
|
|
throw new Error('Next.js is no longer available.', {
|
|
cause: this.isDestroyed,
|
|
})
|
|
}
|
|
if (this.childProcess === undefined) {
|
|
// deploy tests don't have access to the process
|
|
}
|
|
}
|
|
|
|
public get buildId() {
|
|
// get deployment ID via fetch since we can't access
|
|
// build artifacts directly
|
|
return this._buildId
|
|
}
|
|
|
|
public get deploymentId() {
|
|
return this._deploymentId
|
|
}
|
|
|
|
public get supportsImmutableAssets() {
|
|
return process.env.IS_TURBOPACK_TEST ? this._supportsImmutableAssets : false
|
|
}
|
|
|
|
private async deployUsingCustomScript(): Promise<{ url: string }> {
|
|
const deployScriptPath = process.env.NEXT_TEST_DEPLOY_SCRIPT_PATH!
|
|
|
|
require('console').log(
|
|
`Deploying project using custom script: ${deployScriptPath}`
|
|
)
|
|
|
|
// Prepare environment variables to pass to the deploy script
|
|
const scriptEnv = {
|
|
...process.env,
|
|
// Pass the test directory to the script
|
|
NEXT_TEST_DIR: this.testDir,
|
|
// Pass test-specific env vars
|
|
...this.env,
|
|
}
|
|
|
|
const deployRes = await execa(deployScriptPath, [], {
|
|
cwd: this.testDir,
|
|
env: scriptEnv,
|
|
reject: false,
|
|
stderr: 'inherit',
|
|
})
|
|
|
|
if (deployRes.exitCode !== 0) {
|
|
throw new Error(
|
|
`Custom deploy script failed: ${deployRes.stdout} ${deployRes.stderr} (${deployRes.exitCode})`
|
|
)
|
|
}
|
|
|
|
// The script should output the deployment URL to stdout
|
|
const url = deployRes.stdout.trim()
|
|
if (!url) {
|
|
throw new Error(
|
|
'Custom deploy script did not return a deployment URL on stdout'
|
|
)
|
|
}
|
|
|
|
// Validate it's a proper URL
|
|
try {
|
|
new URL(url)
|
|
} catch (err) {
|
|
throw new Error(`Custom deploy script returned invalid URL: ${url}`, {
|
|
cause: err,
|
|
})
|
|
}
|
|
|
|
return { url }
|
|
}
|
|
|
|
private async fetchBuildLogsUsingCustomScript(): Promise<string> {
|
|
const logsScriptPath = process.env.NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH!
|
|
|
|
require('console').log(
|
|
`Fetching build logs using custom script: ${logsScriptPath}`
|
|
)
|
|
|
|
const scriptEnv = {
|
|
...process.env,
|
|
NEXT_TEST_DIR: this.testDir,
|
|
// Pass the deployment URL to the logs script
|
|
NEXT_TEST_DEPLOY_URL: this._url,
|
|
...this.env,
|
|
}
|
|
|
|
const logsRes = await execa(logsScriptPath, [], {
|
|
cwd: this.testDir,
|
|
env: scriptEnv,
|
|
reject: false,
|
|
})
|
|
|
|
if (logsRes.exitCode !== 0) {
|
|
throw new Error(
|
|
`Custom deploy logs script failed: ${logsRes.stdout} ${logsRes.stderr} (${logsRes.exitCode})`
|
|
)
|
|
}
|
|
|
|
// The script should output the build logs to stdout
|
|
return logsRes.stdout + logsRes.stderr
|
|
}
|
|
|
|
private async cleanupUsingCustomScript(): Promise<void> {
|
|
const cleanupScriptPath = process.env.NEXT_TEST_CLEANUP_SCRIPT_PATH!
|
|
|
|
require('console').log(
|
|
`Running cleanup using custom script: ${cleanupScriptPath}`
|
|
)
|
|
|
|
const scriptEnv = {
|
|
...process.env,
|
|
NEXT_TEST_DIR: this.testDir,
|
|
NEXT_TEST_DEPLOY_URL: this._url,
|
|
...this.env,
|
|
}
|
|
|
|
const cleanupChild = execa(cleanupScriptPath, [], {
|
|
cwd: this.testDir,
|
|
env: scriptEnv,
|
|
reject: false,
|
|
stderr: 'inherit',
|
|
})
|
|
|
|
cleanupChild.stdout?.pipe(process.stdout)
|
|
cleanupChild.stderr?.pipe(process.stderr)
|
|
|
|
const { exitCode } = await cleanupChild
|
|
|
|
if (exitCode !== 0) {
|
|
throw new Error(
|
|
`Custom cleanup script failed with exit code: ${exitCode}`
|
|
)
|
|
}
|
|
}
|
|
|
|
private parseIdsFromCliOuput(): void {
|
|
const buildId = this._cliOutput.match(/BUILD_ID: (.+)/)?.[1]?.trim()
|
|
if (!buildId) {
|
|
throw new Error(`Failed to get buildId from logs ${this._cliOutput}`)
|
|
}
|
|
this._buildId = buildId
|
|
const deploymentId = this._cliOutput
|
|
.match(/DEPLOYMENT_ID: (.+)/)?.[1]
|
|
?.trim()
|
|
if (!deploymentId) {
|
|
throw new Error(`Failed to get deploymentId from logs ${this._cliOutput}`)
|
|
}
|
|
this._deploymentId = deploymentId
|
|
const supportsImmutableAssets = this._cliOutput
|
|
.match(/NEXT_SUPPORTS_IMMUTABLE_ASSETS: (.+)/)?.[1]
|
|
?.trim()
|
|
if (!supportsImmutableAssets) {
|
|
throw new Error(
|
|
`Failed to get supportsImmutableAssets from logs ${this._cliOutput}`
|
|
)
|
|
}
|
|
this._supportsImmutableAssets =
|
|
supportsImmutableAssets === '1' ? true : false
|
|
|
|
require('console').log(
|
|
`Got buildId: ${this._buildId}, deploymentId: ${this._deploymentId}, supportsImmutableAssets: ${this._supportsImmutableAssets}`
|
|
)
|
|
}
|
|
|
|
private async fetchBuildLogsUntilComplete(
|
|
url: string,
|
|
vercelEnv: NodeJS.ProcessEnv,
|
|
vercelFlags: string[]
|
|
): Promise<string> {
|
|
// The fixture's `post-build` script prints the BUILD_ID, DEPLOYMENT_ID and
|
|
// NEXT_SUPPORTS_IMMUTABLE_ASSETS markers (in that order) as the final lines
|
|
// of the build (see `base.ts`). A deployment can report `Ready` before that
|
|
// tail has propagated to the log query API, so `vercel inspect --logs` can
|
|
// return a truncated prefix that stops before the markers. Gate on the
|
|
// last-printed marker so a partial read can't slip into the parser, and
|
|
// re-query until it appears.
|
|
const maxAttempts = 20
|
|
const retryDelayMs = 3000
|
|
let output = ''
|
|
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
const buildLogs = await execa(
|
|
'vercel',
|
|
['inspect', '--logs', url, ...vercelFlags],
|
|
{
|
|
env: vercelEnv,
|
|
reject: false,
|
|
}
|
|
)
|
|
if (buildLogs.exitCode !== 0) {
|
|
throw new Error(`Failed to get build output logs: ${buildLogs.stderr}`)
|
|
}
|
|
// Build logs are piped to stderr, so combine both streams.
|
|
output = buildLogs.stdout + buildLogs.stderr
|
|
|
|
if (/NEXT_SUPPORTS_IMMUTABLE_ASSETS: (.+)/.test(output)) {
|
|
return output
|
|
}
|
|
|
|
if (attempt < maxAttempts) {
|
|
require('console').log(
|
|
`Build log markers not yet propagated for ${url} (attempt ${attempt}/${maxAttempts}); the build log tail likely hasn't propagated yet. Retrying in ${retryDelayMs}ms...`
|
|
)
|
|
await setTimeout(retryDelayMs)
|
|
}
|
|
}
|
|
|
|
// The markers never appeared within the retry window; return the last
|
|
// output so `parseIdsFromCliOuput` throws a descriptive error including it.
|
|
return output
|
|
}
|
|
|
|
public async setup(parentSpan: Span) {
|
|
super.setup(parentSpan)
|
|
await super.createTestDir({ parentSpan, skipInstall: true })
|
|
|
|
await this.writeMirrorNpmrcIfNecessary()
|
|
|
|
const existingDeployUrl = process.env.NEXT_TEST_DEPLOY_URL?.trim()
|
|
const customDeployScriptPath =
|
|
process.env.NEXT_TEST_DEPLOY_SCRIPT_PATH?.trim()
|
|
const customLogsScriptPath =
|
|
process.env.NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH?.trim()
|
|
|
|
// Check if using an existing deployment URL (takes priority)
|
|
if (existingDeployUrl) {
|
|
try {
|
|
this._url = new URL(existingDeployUrl).toString()
|
|
} catch (err) {
|
|
throw new Error(
|
|
`Invalid NEXT_TEST_DEPLOY_URL value: ${existingDeployUrl}`,
|
|
{ cause: err }
|
|
)
|
|
}
|
|
require('console').log(`Using existing deployment URL: ${this._url}`)
|
|
|
|
this._parsedUrl = new URL(this._url)
|
|
|
|
// Configure proxy address if needed
|
|
await this.configureProxyAddress()
|
|
|
|
// Use custom logs script if provided, otherwise use Vercel CLI
|
|
if (customLogsScriptPath) {
|
|
this._cliOutput = await this.fetchBuildLogsUsingCustomScript()
|
|
} else {
|
|
// Use vercel inspect to get logs for existing deployment
|
|
const buildLogs = await execa(
|
|
'vercel',
|
|
['inspect', '--logs', this._url],
|
|
{
|
|
env: process.env,
|
|
reject: false,
|
|
}
|
|
)
|
|
if (buildLogs.exitCode !== 0) {
|
|
throw new Error(
|
|
`Failed to get build output logs: ${buildLogs.stderr}`
|
|
)
|
|
}
|
|
this._cliOutput = buildLogs.stdout + buildLogs.stderr
|
|
}
|
|
|
|
this.parseIdsFromCliOuput()
|
|
return
|
|
}
|
|
|
|
// Check if using custom deploy script
|
|
if (customDeployScriptPath) {
|
|
if (!customLogsScriptPath) {
|
|
throw new Error(
|
|
'NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH is required when using NEXT_TEST_DEPLOY_SCRIPT_PATH'
|
|
)
|
|
}
|
|
|
|
const { url } = await this.deployUsingCustomScript()
|
|
this._url = url
|
|
|
|
this._parsedUrl = new URL(this._url)
|
|
|
|
// Configure proxy address if needed
|
|
await this.configureProxyAddress()
|
|
|
|
require('console').log(`Deployment URL: ${this._url}`)
|
|
|
|
// Use the custom logs script to get build logs and extract buildId
|
|
this._cliOutput = await this.fetchBuildLogsUsingCustomScript()
|
|
this.parseIdsFromCliOuput()
|
|
return
|
|
}
|
|
|
|
// Original Vercel CLI deployment logic
|
|
// ensure Vercel CLI is installed
|
|
try {
|
|
const res = await execa('vercel', ['--version'])
|
|
require('console').log(`Using Vercel CLI version:`, res.stdout)
|
|
} catch (_) {
|
|
require('console').log(`Installing Vercel CLI`)
|
|
await execa('npm', ['i', '-g', 'vercel@latest'], {
|
|
stdio: 'inherit',
|
|
})
|
|
}
|
|
|
|
const vercelFlags: string[] = []
|
|
const NEXT_ENABLE_ADAPTER = process.env.NEXT_ENABLE_ADAPTER === '1'
|
|
const IS_TURBOPACK_TEST = process.env.IS_TURBOPACK_TEST
|
|
|
|
const TEST_TEAM_NAME = NEXT_ENABLE_ADAPTER
|
|
? projectEnv.ADAPTER_TEST_TEAM_NAME
|
|
: IS_TURBOPACK_TEST
|
|
? projectEnv.TURBOPACK_TEST_TEAM_NAME
|
|
: projectEnv.TEST_TEAM_NAME
|
|
|
|
const TEST_TOKEN = NEXT_ENABLE_ADAPTER
|
|
? projectEnv.ADAPTER_TEST_TOKEN
|
|
: IS_TURBOPACK_TEST
|
|
? projectEnv.TURBOPACK_TEST_TOKEN
|
|
: projectEnv.TEST_TOKEN
|
|
|
|
// If the team name is available in the environment, use it as the scope.
|
|
if (TEST_TEAM_NAME) {
|
|
vercelFlags.push('--scope', TEST_TEAM_NAME)
|
|
}
|
|
const vercelEnv = { ...process.env }
|
|
|
|
// The Vercel CLI uses @vercel/detect-agent to detect when it's running
|
|
// under an AI coding agent (Claude Code, Cursor, Codex, …) and, when it
|
|
// does, switches `vercel deploy` stdout from a plain URL to a JSON
|
|
// manifest intended for AI consumption — which breaks
|
|
// `new URL(deployRes.stdout)` below. The CLI honors an explicit
|
|
// `--non-interactive=false` as an override of the agent default, and the
|
|
// JSON-vs-plain decision keys off `client.nonInteractive`, so passing
|
|
// the flag is enough to force plain-URL output for both link and deploy.
|
|
vercelFlags.push('--non-interactive=false')
|
|
|
|
// If the token is available in the environment, use it as the token in the
|
|
// environment.
|
|
if (TEST_TOKEN) {
|
|
vercelEnv.TOKEN = TEST_TOKEN
|
|
}
|
|
|
|
// create auth file in CI
|
|
if (process.env.NEXT_TEST_JOB) {
|
|
if (!TEST_TOKEN && !TEST_TEAM_NAME) {
|
|
throw new Error(
|
|
'Missing TEST_TOKEN and TEST_TEAM_NAME environment variables for CI'
|
|
)
|
|
}
|
|
|
|
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}`)
|
|
|
|
// link the project
|
|
const linkRes = await execa(
|
|
'vercel',
|
|
['link', '-p', projectEnv.TEST_PROJECT_NAME, '--yes', ...vercelFlags],
|
|
{
|
|
cwd: this.testDir,
|
|
env: vercelEnv,
|
|
reject: false,
|
|
}
|
|
)
|
|
|
|
if (linkRes.exitCode !== 0) {
|
|
throw new Error(
|
|
`Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
|
|
)
|
|
}
|
|
require('console').log(`Deploying project at ${this.testDir}`)
|
|
|
|
const additionalEnv: string[] = []
|
|
|
|
for (const key of Object.keys(this.env || {})) {
|
|
additionalEnv.push(`${key}=${this.env[key]}`)
|
|
}
|
|
|
|
additionalEnv.push(
|
|
`VERCEL_CLI_VERSION=${process.env.VERCEL_CLI_VERSION || 'vercel@latest'}`
|
|
)
|
|
|
|
// Add experimental feature flags
|
|
|
|
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
|
additionalEnv.push(
|
|
`NEXT_PRIVATE_EXPERIMENTAL_CACHE_COMPONENTS=${process.env.__NEXT_CACHE_COMPONENTS}`
|
|
)
|
|
}
|
|
if (process.env.__NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS) {
|
|
additionalEnv.push(
|
|
`NEXT_PRIVATE_EXPERIMENTAL_CACHED_NAVIGATIONS=${process.env.__NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS}`
|
|
)
|
|
}
|
|
if (process.env.__NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER) {
|
|
additionalEnv.push(
|
|
`NEXT_PRIVATE_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=${process.env.__NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER}`
|
|
)
|
|
}
|
|
if (process.env.IS_TURBOPACK_TEST) {
|
|
additionalEnv.push(`IS_TURBOPACK_TEST=1`)
|
|
}
|
|
if (process.env.IS_WEBPACK_TEST) {
|
|
additionalEnv.push(`IS_WEBPACK_TEST=1`)
|
|
}
|
|
if (NEXT_ENABLE_ADAPTER) {
|
|
additionalEnv.push(`NEXT_ENABLE_ADAPTER=1`)
|
|
} else {
|
|
additionalEnv.push(`NEXT_ENABLE_ADAPTER=0`)
|
|
}
|
|
|
|
const deployRes = await execa(
|
|
'vercel',
|
|
[
|
|
'deploy',
|
|
'--build-env',
|
|
'NEXT_PRIVATE_TEST_MODE=e2e',
|
|
'--build-env',
|
|
'NEXT_TELEMETRY_DISABLED=1',
|
|
'--build-env',
|
|
'VERCEL_NEXT_BUNDLED_SERVER=1',
|
|
...additionalEnv.flatMap((pair) => [
|
|
'--env',
|
|
pair,
|
|
'--build-env',
|
|
pair,
|
|
]),
|
|
'--force',
|
|
...vercelFlags,
|
|
],
|
|
{
|
|
cwd: this.testDir,
|
|
env: vercelEnv,
|
|
reject: false,
|
|
// This will print deployment information earlier to the console so we
|
|
// don't have to wait until the deployment is complete to get the
|
|
// inspect URL.
|
|
stderr: 'inherit',
|
|
}
|
|
)
|
|
|
|
if (deployRes.exitCode !== 0) {
|
|
throw new Error(
|
|
`Failed to deploy project ${deployRes.stdout} ${deployRes.stderr} (${deployRes.exitCode})`
|
|
)
|
|
}
|
|
|
|
// the CLI gives just the deployment URL back when not a TTY
|
|
this._url = deployRes.stdout
|
|
this._parsedUrl = new URL(this._url)
|
|
|
|
// Configure proxy address if needed
|
|
await this.configureProxyAddress()
|
|
|
|
require('console').log(`Deployment URL: ${this._url}`)
|
|
|
|
// Fetch the build logs to extract the build/deployment id markers that the
|
|
// fixture's `post-build` script prints. The deployment can report `Ready`
|
|
// (and `vercel deploy` can return) before its full build-log tail has
|
|
// propagated to the log query API, so re-query until the markers appear
|
|
// rather than failing on the first incomplete read. TODO: Combine with
|
|
// runtime logs (via `vercel logs`)
|
|
this._cliOutput = await this.fetchBuildLogsUntilComplete(
|
|
this._url,
|
|
vercelEnv,
|
|
vercelFlags
|
|
)
|
|
|
|
this.parseIdsFromCliOuput()
|
|
}
|
|
|
|
// When the preview-builds npm mirror is auth-protected, the deploy build
|
|
// installs Next.js artifacts from it and needs credentials. We write an
|
|
// `.npmrc` with a read token (provided as a CI secret) so the remote install
|
|
// can authenticate. Only written when the token is set, so unprotected and
|
|
// local deploy runs are unaffected.
|
|
private async writeMirrorNpmrcIfNecessary(): Promise<void> {
|
|
const token = process.env.PREVIEW_BUILDS_READ_TOKEN
|
|
const baseUrlRaw = process.env.NEXT_TEST_PREVIEW_BUILDS_BASE_URL
|
|
|
|
if (!token || !baseUrlRaw) {
|
|
require('console').log(
|
|
`Skipping .npmrc write for preview-builds mirror: missing token or base URL`
|
|
)
|
|
return
|
|
}
|
|
|
|
const baseUrl = new URL(baseUrlRaw)
|
|
// Derive the npmrc auth key from the mirror base URL: strip the scheme and
|
|
// ensure a trailing slash so it matches requests to that registry path.
|
|
const registryKey = `//${baseUrl.host}${baseUrl.pathname.replace(/\/?$/, '/')}`
|
|
|
|
require('console').log(
|
|
`Writing .npmrc for preview-builds mirror: ${registryKey}`
|
|
)
|
|
await fs.writeFile(
|
|
path.join(this.testDir, '.npmrc'),
|
|
`${registryKey}:_authToken=${token}\n`
|
|
)
|
|
}
|
|
|
|
private async configureProxyAddress(): Promise<void> {
|
|
// If configured, we should configure the `/etc/hosts` file to point the
|
|
// deployment domain to the specified proxy address.
|
|
if (
|
|
process.env.NEXT_TEST_PROXY_ADDRESS &&
|
|
// Validate that the proxy address is a valid IP address.
|
|
/^\d+\.\d+\.\d+\.\d+$/.test(process.env.NEXT_TEST_PROXY_ADDRESS)
|
|
) {
|
|
this._writtenHostsLine = `${process.env.NEXT_TEST_PROXY_ADDRESS}\t${this._parsedUrl.hostname}\n`
|
|
|
|
require('console').log(
|
|
`Writing proxy address to hosts file: ${this._writtenHostsLine.trim()}`
|
|
)
|
|
|
|
// Using a child process, we'll use sudo to tee the hosts file to add the
|
|
// proxy address to the target domain.
|
|
await execa('sudo', ['tee', '-a', '/etc/hosts'], {
|
|
input: this._writtenHostsLine,
|
|
stdout: 'inherit',
|
|
shell: true,
|
|
})
|
|
|
|
// Verify that the proxy address was written to the hosts file.
|
|
const hostsFile = await fs.readFile('/etc/hosts', 'utf8')
|
|
if (!hostsFile.includes(this._writtenHostsLine)) {
|
|
throw new Error('Proxy address not found in hosts file after writing')
|
|
}
|
|
|
|
require('console').log(`Proxy address written to hosts file`)
|
|
}
|
|
}
|
|
|
|
public async destroy() {
|
|
// Run custom cleanup script if provided
|
|
const customCleanupScriptPath =
|
|
process.env.NEXT_TEST_CLEANUP_SCRIPT_PATH?.trim()
|
|
if (customCleanupScriptPath) {
|
|
await this.cleanupUsingCustomScript().catch((err) => {
|
|
require('console').error(
|
|
'Error running custom cleanup script, continuing with destroy:',
|
|
err
|
|
)
|
|
})
|
|
}
|
|
|
|
// If configured, we should remove the proxy address from the hosts file.
|
|
if (this._writtenHostsLine) {
|
|
const trimmed = this._writtenHostsLine.trim()
|
|
|
|
require('console').log(
|
|
`Removing proxy address from hosts file: ${this._writtenHostsLine.trim()}`
|
|
)
|
|
|
|
const hostsFile = await fs.readFile('/etc/hosts', 'utf8')
|
|
|
|
const cleanedHostsFile = hostsFile
|
|
.split('\n')
|
|
.filter((line) => line.trim() !== trimmed)
|
|
.join('\n')
|
|
|
|
await execa('sudo', ['tee', '/etc/hosts'], {
|
|
input: cleanedHostsFile,
|
|
stdout: 'inherit',
|
|
shell: true,
|
|
})
|
|
|
|
require('console').log(`Removed proxy address from hosts file`)
|
|
}
|
|
|
|
// Run the super destroy to clean up the test directory.
|
|
return super.destroy()
|
|
}
|
|
|
|
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<{ newFile: boolean }> {
|
|
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')
|
|
}
|
|
public async readFileBuffer(filename: string): Promise<Buffer> {
|
|
throw new Error('readFileBuffer is not available in deploy test mode')
|
|
}
|
|
public async writeFileBuffer(filename: string, data: Buffer): Promise<void> {
|
|
throw new Error('writeFileBuffer is not available in deploy test mode')
|
|
}
|
|
}
|