162 lines
5.5 KiB
JavaScript
162 lines
5.5 KiB
JavaScript
import fs from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
import { spawn, spawnSync } from 'node:child_process'
|
|
import http from 'node:http'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const currentFile = fileURLToPath(import.meta.url)
|
|
const websiteRoot = path.resolve(path.dirname(currentFile), '..')
|
|
const frontendProductionEnv = path.join(websiteRoot, '.env.production.example')
|
|
const serverProductionEnv = path.join(websiteRoot, 'server', '.env.production.example')
|
|
const runtimeReadinessScript = path.join(websiteRoot, 'scripts', 'check-runtime-readiness.mjs')
|
|
|
|
function runCli(args, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(process.execPath, args, {
|
|
cwd: websiteRoot,
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
...options,
|
|
})
|
|
|
|
let stdout = ''
|
|
let stderr = ''
|
|
child.stdout.on('data', (chunk) => {
|
|
stdout += chunk
|
|
})
|
|
child.stderr.on('data', (chunk) => {
|
|
stderr += chunk
|
|
})
|
|
child.on('error', reject)
|
|
child.on('close', (status) => {
|
|
resolve({
|
|
status,
|
|
stdout,
|
|
stderr,
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
describe('check-runtime-readiness CLI', () => {
|
|
it('fails the real production example env files until placeholder launch values are replaced', () => {
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
runtimeReadinessScript,
|
|
'--frontend-env',
|
|
frontendProductionEnv,
|
|
'--server-env',
|
|
serverProductionEnv,
|
|
'--skip-live-health',
|
|
'--json',
|
|
],
|
|
{
|
|
cwd: websiteRoot,
|
|
encoding: 'utf8',
|
|
},
|
|
)
|
|
|
|
expect(result.status).toBe(1)
|
|
expect(result.stderr).toBe('')
|
|
|
|
const report = JSON.parse(result.stdout)
|
|
expect(report.ok).toBe(false)
|
|
expect(report.failures).toContain('VITE_WINDOWS_DOWNLOAD_URL still contains a placeholder value.')
|
|
expect(report.failures).toContain('VITE_PADDLE_CHECKOUT_URL_OPERATOR still contains a placeholder value.')
|
|
expect(report.failures).toContain('VITE_MPL_SOURCE_URL still contains a placeholder value.')
|
|
expect(report.failures).toContain('PADDLE_WEBHOOK_SECRET still contains a placeholder value.')
|
|
expect(report.failures).toContain('PADDLE_PRODUCT_PLAN_MAP still contains a placeholder value.')
|
|
expect(report.failures).toContain('PADDLE_PRICE_PLAN_MAP still contains a placeholder value.')
|
|
})
|
|
|
|
it('fails a live deployment probe when the public origin still serves the placeholder page instead of JSON routes', async () => {
|
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hypertwist-runtime-readiness-'))
|
|
const frontendEnv = path.join(tempDir, 'frontend.env')
|
|
const serverEnv = path.join(tempDir, 'server.env')
|
|
|
|
fs.writeFileSync(frontendEnv, `
|
|
VITE_SUPERTOKENS_API_DOMAIN=https://hypertwist.app
|
|
VITE_SUPERTOKENS_WEBSITE_DOMAIN=https://hypertwist.app
|
|
VITE_AUTH_API_BASE_URL=https://hypertwist.app
|
|
VITE_WINDOWS_DOWNLOAD_URL=https://downloads.hypertwist.app/windows.exe
|
|
VITE_PADDLE_CHECKOUT_URL_OPERATOR=https://buy.paddle.com/operator
|
|
VITE_PADDLE_CHECKOUT_URL_STUDIO=https://buy.paddle.com/studio
|
|
VITE_MPL_SOURCE_URL=https://hypertwist.app/open-source/source
|
|
VITE_OPEN_SOURCE_REPO_URL=https://git.scriptoriumai.io/scriptoriumadmin/hypertwist
|
|
`)
|
|
fs.writeFileSync(serverEnv, `
|
|
API_DOMAIN=https://hypertwist.app
|
|
WEBSITE_DOMAIN=https://hypertwist.app
|
|
SUPERTOKENS_CORE_URI=https://auth-core.internal
|
|
COOKIE_SECURE=true
|
|
SERVE_STATIC_WEBSITE=true
|
|
PADDLE_WEBHOOK_SECRET=secret
|
|
PADDLE_PRICE_PLAN_MAP={"pri_operator":"operator"}
|
|
`)
|
|
|
|
const placeholderHtml = `<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>HyperTwist</title>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<h1>HyperTwist</h1>
|
|
<p>Deployment target is live on the new VPS. Application rollout is pending.</p>
|
|
</main>
|
|
</body>
|
|
</html>`
|
|
|
|
const server = http.createServer((req, res) => {
|
|
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
|
|
res.end(placeholderHtml)
|
|
})
|
|
|
|
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
|
|
const address = server.address()
|
|
if (!address || typeof address === 'string') {
|
|
throw new Error('Expected an IPv4 test listener address.')
|
|
}
|
|
const healthUrl = `http://127.0.0.1:${address.port}`
|
|
|
|
try {
|
|
const result = await runCli(
|
|
[
|
|
runtimeReadinessScript,
|
|
'--frontend-env',
|
|
frontendEnv,
|
|
'--server-env',
|
|
serverEnv,
|
|
'--health-url',
|
|
healthUrl,
|
|
'--json',
|
|
],
|
|
)
|
|
|
|
expect(result.status).toBe(1)
|
|
expect(result.stderr).toBe('')
|
|
|
|
const report = JSON.parse(result.stdout)
|
|
expect(report.ok).toBe(false)
|
|
expect(report.failures.some((item) => item.includes('Live auth health check failed: Live auth health endpoint returned HTML instead of JSON.'))).toBe(true)
|
|
expect(report.failures.some((item) => item.includes('placeholder rollout page'))).toBe(true)
|
|
expect(report.failures.some((item) => item.includes('Live release manifest check failed: Live release manifest endpoint returned HTML instead of JSON.'))).toBe(true)
|
|
expect(report.failures.some((item) => item.includes('Live website root still serves the placeholder rollout page'))).toBe(true)
|
|
} finally {
|
|
await new Promise((resolve, reject) => {
|
|
server.close((error) => {
|
|
if (error) {
|
|
reject(error)
|
|
return
|
|
}
|
|
resolve()
|
|
})
|
|
})
|
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
})
|