-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathrun-pre-push-checks.ts
More file actions
119 lines (94 loc) · 3.24 KB
/
run-pre-push-checks.ts
File metadata and controls
119 lines (94 loc) · 3.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import { execFileSync } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import dotenv from 'dotenv';
import { Pool } from 'pg';
const pnpmCommand = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
dotenv.config();
function resolveDirectDatabaseUrl() {
const candidate = process.env.DIRECT_DATABASE_URL ?? process.env.DIRECT_URL;
if (!candidate) {
throw new Error('Set DIRECT_DATABASE_URL or legacy DIRECT_URL in your shell or .env before pushing.');
}
if (candidate.startsWith('prisma://') || candidate.startsWith('prisma+postgres://')) {
throw new Error('Pre-push checks require DIRECT_DATABASE_URL or DIRECT_URL to point at direct PostgreSQL, not Prisma Accelerate.');
}
return new URL(candidate);
}
function isLocalDatabaseHost(hostname: string) {
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
}
function tempDatabaseUrl(baseUrl: URL, databaseName: string) {
const next = new URL(baseUrl.toString());
next.pathname = `/${databaseName}`;
return next.toString();
}
function adminDatabaseUrl(baseUrl: URL) {
const next = new URL(baseUrl.toString());
next.pathname = '/postgres';
return next.toString();
}
function quoteIdentifier(value: string) {
return `"${value.replaceAll('"', '""')}"`;
}
function toError(error: unknown) {
if (error instanceof Error) {
return error;
}
return new Error(String(error));
}
function runPnpm(args: string[], env: NodeJS.ProcessEnv) {
execFileSync(pnpmCommand, args, {
env,
stdio: 'inherit',
});
}
async function dropTemporaryDatabase(pool: Pool, databaseName: string) {
await pool.query(
`SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = $1
AND pid <> pg_backend_pid()`,
[databaseName],
);
await pool.query(`DROP DATABASE IF EXISTS ${quoteIdentifier(databaseName)}`);
}
async function main() {
const directUrl = resolveDirectDatabaseUrl();
if (!isLocalDatabaseHost(directUrl.hostname) && process.env.ALLOW_REMOTE_PREPUSH_DB !== '1') {
throw new Error('Pre-push checks refuse to use non-local databases by default. Set ALLOW_REMOTE_PREPUSH_DB=1 if you really want that.');
}
const originalDatabase = directUrl.pathname.replace(/^\//, '') || 'locations_api';
const tempDatabaseName = `${originalDatabase}_prepush_${randomUUID().replace(/-/g, '').slice(0, 8)}`;
const isolatedDatabaseUrl = tempDatabaseUrl(directUrl, tempDatabaseName);
const adminPool = new Pool({
connectionString: adminDatabaseUrl(directUrl),
});
let primaryError: unknown;
try {
await adminPool.query(`CREATE DATABASE ${quoteIdentifier(tempDatabaseName)}`);
runPnpm(['test:ci'], {
...process.env,
DATABASE_URL: isolatedDatabaseUrl,
DIRECT_DATABASE_URL: isolatedDatabaseUrl,
NODE_ENV: 'test',
});
} catch (error) {
primaryError = error;
}
try {
await dropTemporaryDatabase(adminPool, tempDatabaseName);
} catch (cleanupError) {
if (primaryError) {
console.error('Failed to drop temporary pre-push database after the primary failure.');
console.error(cleanupError);
} else {
throw cleanupError;
}
} finally {
await adminPool.end();
}
if (primaryError) {
throw toError(primaryError);
}
}
await main();