Skip to content

Commit 1300e6e

Browse files
committed
chore(db-migrations): vendor mysql-patcher to eliminate supply chain risk
Remove the mysql-patcher npm dependency and vendor the library as a modernized async/await ESM module into lib/mysql-patcher.js. Replaces async, bluebird, clone, glob, and xtend with native equivalents. Adds project.json with nx test-unit, test-integration, and lint targets. Adds eslint config extending root, prettier formatting, and jest config. Includes 15 unit tests and 7 integration tests. Rewrites check-db-patcher.sh to poll PM2 logs directly instead of using pgrep, fixing the race condition where the patcher finishes before the script can find the process. Strips ANSI escape codes from log output for clean patch summary display.
1 parent fcffd9f commit 1300e6e

25 files changed

Lines changed: 1021 additions & 98 deletions

_scripts/check-db-patcher.sh

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,24 @@
11
#!/bin/bash
2-
NAME="patcher.mjs" # nodejs script's name here
32
RETRY=60
43

54
echo -e "\nChecking for DB patches..."
65

7-
# Wait for patcher process to appear
8-
echo "⏳ Waiting for patcher process to start..."
9-
for i in $(seq 1 $RETRY); do
10-
PATCHER_PID=$(pgrep -f "$NAME")
11-
if [[ -n "$PATCHER_PID" ]]; then
12-
echo "🔄 Patcher process found (PID: $PATCHER_PID)"
13-
break
14-
fi
15-
if [[ $i -eq $RETRY ]]; then
16-
echo "❌ Timeout: Patcher process did not start in time"
17-
exit 1
18-
fi
19-
sleep 1
20-
done
21-
22-
# Confirm the process is running
23-
if ! ps -p "$PATCHER_PID" > /dev/null; then
24-
echo "⚠️ DB patcher process ($NAME, PID $PATCHER_PID) is not running. Skipping wait."
25-
exit 0
26-
fi
6+
# Strategy: poll PM2 logs for the patcher outcome. This avoids the race
7+
# condition where the patcher starts and finishes before we can pgrep it
8+
# (common when all DBs are already at target level).
279

10+
echo "⏳ Waiting for DB patches to complete..."
2811
for i in $(seq 1 $RETRY); do
29-
if ps -p "$PATCHER_PID" > /dev/null; then
30-
sleep 0.5
31-
else
32-
# Show patch summary from PM2 logs (deduplicated)
33-
echo "📋 Patch Summary:"
34-
if command -v pm2 >/dev/null 2>&1; then
35-
pm2 logs mysql --lines 50 --nostream 2>/dev/null | \
12+
if command -v pm2 >/dev/null 2>&1; then
13+
LOG_OUTPUT=$(pm2 logs mysql --lines 100 --nostream 2>/dev/null)
14+
15+
# Check for failure first
16+
if echo "$LOG_OUTPUT" | grep -qE "Failed to patch|Error:.*patch"; then
17+
echo "📋 Patch Summary:"
18+
echo "$LOG_OUTPUT" | \
3619
grep -E "(Successfully patched|Error:|Failed to patch)" | \
37-
sed 's/.*|mysql[[:space:]]*|//' | \
38-
sort -u | \
39-
tail -20 | \
20+
sed 's/.*|mysql[[:space:]]*|[[:space:]]*//' | sed 's/\x1b\[[0-9;]*m//g' | \
21+
sort -u | tail -20 | \
4022
while read line; do
4123
if [[ $line =~ ^Successfully ]]; then
4224
echo "$line"
@@ -46,24 +28,28 @@ for i in $(seq 1 $RETRY); do
4628
echo " 💥 $line"
4729
fi
4830
done
31+
echo "❌ DB patches failed"
32+
exit 1
4933
fi
5034

51-
# Check if there were any errors in the logs
52-
has_errors=$(pm2 logs mysql --lines 50 --nostream 2>/dev/null | grep -c "Error:\|Failed to patch" 2>/dev/null || echo "0")
53-
54-
if [[ ! "$has_errors" =~ ^[0-9]+$ ]]; then
55-
has_errors=0
56-
fi
57-
58-
if [[ $has_errors -eq 0 ]]; then
35+
# Check for success — need all 4 databases
36+
SUCCESS_COUNT=$(echo "$LOG_OUTPUT" | grep -c "Successfully patched" 2>/dev/null || echo "0")
37+
if [[ "$SUCCESS_COUNT" -ge 4 ]]; then
38+
echo "📋 Patch Summary:"
39+
echo "$LOG_OUTPUT" | \
40+
grep -E "Successfully patched" | \
41+
sed 's/.*|mysql[[:space:]]*|[[:space:]]*//' | sed 's/\x1b\[[0-9;]*m//g' | \
42+
sort -u | tail -20 | \
43+
while read line; do
44+
echo "$line"
45+
done
5946
echo "✅ DB patches applied successfully"
6047
exit 0
61-
else
62-
echo "❌ DB patches failed"
63-
exit 1
6448
fi
6549
fi
50+
51+
sleep 1
6652
done
6753

68-
echo "❌ Timeout: DB patches did not finish in time."
54+
echo "❌ Timeout: DB patches did not complete in 60 seconds."
6955
exit 1
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": ["../../.eslintrc.json"],
3+
"root": true,
4+
"ignorePatterns": ["dist", "databases", "contentful"],
5+
"overrides": [
6+
{
7+
"files": ["**/*.spec.js"],
8+
"env": {
9+
"jest": true
10+
}
11+
}
12+
]
13+
}

packages/db-migrations/bin/patcher.mjs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
import { promisify } from 'util';
65
import fs from 'fs/promises';
76
import path from 'path';
87
import mysql from 'mysql';
9-
import patcher from 'mysql-patcher';
8+
import Patcher from '../lib/mysql-patcher.js';
109
import convict from 'convict';
1110
import { makeMySQLConfig } from 'fxa-shared/db/config';
12-
const patch = promisify(patcher.patch);
1311

1412
const conf = convict({
1513
fxa: makeMySQLConfig('AUTH', 'fxa'),
@@ -43,7 +41,7 @@ for (const db of databases) {
4341
try {
4442
const cfg = conf.get(db);
4543
console.log(`Patching ${db} to ${level}`);
46-
let results = await patch({
44+
await Patcher.patch({
4745
user: cfg.user,
4846
password: cfg.password,
4947
host: cfg.host,
@@ -57,7 +55,6 @@ for (const db of databases) {
5755
reversePatchAllowed: false,
5856
database: cfg.database,
5957
});
60-
console.log(`Results: ${results}`);
6158
console.log(`Successfully patched ${db} to ${level}`);
6259
} catch (error) {
6360
// fyi these logs show up in `pm2 logs mysql`
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
/** @type {import('jest').Config} */
6+
export default {
7+
displayName: 'db-migrations',
8+
testEnvironment: 'node',
9+
moduleFileExtensions: ['js', 'json'],
10+
coverageDirectory: '../../coverage/packages/db-migrations',
11+
reporters: [
12+
'default',
13+
[
14+
'jest-junit',
15+
{
16+
outputDirectory: 'artifacts/tests/db-migrations',
17+
outputName: 'db-migrations-jest-results.xml',
18+
},
19+
],
20+
],
21+
}

0 commit comments

Comments
 (0)