Files
strapi/tests/migration/framework/shared.js
Ben Irvin a437878277 fix(migration): pass compose project env to mysql/mariadb containers
Use strapi_migration_v5 compose project consistently when starting
mysql/mariadb containers in prepareDockerDatabase, matching postgres wipe.
2026-05-28 19:09:51 +02:00

274 lines
8.4 KiB
JavaScript

'use strict';
const fs = require('fs');
const path = require('path');
const execa = require('execa');
function composeEnv(ctx, extra = {}) {
const composeProject = process.env.STRAPI_MIGRATION_COMPOSE_PROJECT || 'strapi_migration_v5';
return {
...process.env,
COMPOSE_PROJECT_NAME: composeProject,
...extra,
};
}
/**
* Env for yarn install in ephemeral apps outside the monorepo workspace.
* CI PR "hardened mode" forbids lockfile updates unless immutable installs are disabled.
*/
function nestedYarnInstallEnv(extra = {}) {
return {
...process.env,
YARN_ENABLE_IMMUTABLE_INSTALLS: 'false',
...extra,
};
}
async function dockerComposeDownVolumes(ctx) {
const { runCompose } = require(path.join(ctx.COMPLEX_DIR, 'scripts', 'compose'));
try {
runCompose(['-f', ctx.DOCKER_COMPOSE_FILE, 'down', '-v'], {
cwd: ctx.COMPLEX_DIR,
stdio: 'pipe',
env: composeEnv(ctx),
});
} catch {
// No project / already clean
}
}
async function resolveDockerHostPorts(client) {
const getPort = require('get-port');
if (client === 'postgres') {
if (!process.env.POSTGRES_PORT && !process.env.DATABASE_PORT) {
const p = await getPort({ port: 5432 });
process.env.POSTGRES_PORT = String(p);
process.env.DATABASE_PORT = String(p);
console.log(
`\n🔌 Using host port ${p} for Postgres → container 5432 (set POSTGRES_PORT or DATABASE_PORT to pin).`
);
} else if (!process.env.POSTGRES_PORT && process.env.DATABASE_PORT) {
process.env.POSTGRES_PORT = process.env.DATABASE_PORT;
} else if (process.env.POSTGRES_PORT && !process.env.DATABASE_PORT) {
process.env.DATABASE_PORT = process.env.POSTGRES_PORT;
}
return;
}
if (client === 'mysql') {
if (!process.env.MYSQL_PORT && !process.env.DATABASE_PORT) {
const p = await getPort({ port: 3306 });
process.env.MYSQL_PORT = String(p);
process.env.DATABASE_PORT = String(p);
console.log(
`\n🔌 Using host port ${p} for MySQL → container 3306 (set MYSQL_PORT or DATABASE_PORT to pin).`
);
} else if (!process.env.MYSQL_PORT && process.env.DATABASE_PORT) {
process.env.MYSQL_PORT = process.env.DATABASE_PORT;
} else if (process.env.MYSQL_PORT && !process.env.DATABASE_PORT) {
process.env.DATABASE_PORT = process.env.MYSQL_PORT;
}
}
if (client === 'mariadb') {
if (!process.env.MARIADB_PORT && !process.env.DATABASE_PORT) {
const p = await getPort({ port: 3307 });
process.env.MARIADB_PORT = String(p);
process.env.DATABASE_PORT = String(p);
console.log(
`\n🔌 Using host port ${p} for MariaDB → container 3306 (set MARIADB_PORT or DATABASE_PORT to pin).`
);
} else if (!process.env.MARIADB_PORT && process.env.DATABASE_PORT) {
process.env.MARIADB_PORT = process.env.DATABASE_PORT;
} else if (process.env.MARIADB_PORT && !process.env.DATABASE_PORT) {
process.env.DATABASE_PORT = process.env.MARIADB_PORT;
}
}
}
function buildDatabaseEnvForClient(ctx, client) {
const postgresPort = process.env.POSTGRES_PORT || '5432';
const mysqlPort = process.env.MYSQL_PORT || '3306';
const mariadbPort = process.env.MARIADB_PORT || '3307';
// Strapi exposes postgres | mysql | sqlite only (Knex mysql2 against MariaDB uses client "mysql").
const base = {
DATABASE_CLIENT: client === 'mariadb' ? 'mysql' : client,
DATABASE_HOST: process.env.DATABASE_HOST || 'localhost',
DATABASE_NAME: process.env.DATABASE_NAME || 'strapi',
DATABASE_USERNAME: process.env.DATABASE_USERNAME || 'strapi',
DATABASE_PASSWORD: process.env.DATABASE_PASSWORD || 'strapi',
DATABASE_SSL: process.env.DATABASE_SSL || 'false',
};
if (client === 'postgres') {
return {
...base,
DATABASE_PORT: process.env.DATABASE_PORT || postgresPort,
};
}
if (client === 'mysql') {
return {
...base,
DATABASE_PORT: process.env.DATABASE_PORT || mysqlPort,
};
}
if (client === 'mariadb') {
return {
...base,
DATABASE_CLIENT: 'mysql',
DATABASE_PORT: process.env.DATABASE_PORT || mariadbPort,
};
}
return {
...base,
DATABASE_FILENAME: ctx.SQLITE_PATH,
};
}
function writeAppDotenv(ctx, dbEnv) {
const keys = ['toBeModified1', 'toBeModified2', 'toBeModified3', 'toBeModified4'].join(',');
let body = `HOST=0.0.0.0
PORT=1337
APP_KEYS=${keys}
API_TOKEN_SALT=toBeModified
ADMIN_JWT_SECRET=toBeModified
TRANSFER_TOKEN_SALT=toBeModified
JWT_SECRET=toBeModified
`;
if (dbEnv.DATABASE_CLIENT === 'sqlite') {
body += `DATABASE_CLIENT=sqlite
DATABASE_FILENAME=${dbEnv.DATABASE_FILENAME}
`;
} else {
body += `DATABASE_CLIENT=${dbEnv.DATABASE_CLIENT}
DATABASE_HOST=${dbEnv.DATABASE_HOST}
DATABASE_PORT=${dbEnv.DATABASE_PORT}
DATABASE_NAME=${dbEnv.DATABASE_NAME}
DATABASE_USERNAME=${dbEnv.DATABASE_USERNAME}
DATABASE_PASSWORD=${dbEnv.DATABASE_PASSWORD}
`;
}
fs.writeFileSync(require('path').join(ctx.V4_APP_DIR, '.env'), body);
}
async function prepareDockerDatabase(ctx, client) {
const dbUtils = require(require('path').join(ctx.COMPLEX_DIR, 'scripts', 'db-utils.js'));
const { startContainer, getContainerId, waitForPostgresReady, waitForMysqlReady } = dbUtils;
const env = composeEnv(ctx);
if (client === 'postgres') {
await execa(
'node',
[require('path').join(ctx.COMPLEX_DIR, 'scripts', 'db-postgres.js'), 'wipe'],
{
cwd: ctx.COMPLEX_DIR,
stdio: 'inherit',
env,
}
);
const cid = getContainerId(ctx.DOCKER_COMPOSE_FILE, ctx.COMPLEX_DIR, 'postgres', env);
if (cid) {
waitForPostgresReady(cid);
}
return;
}
if (client === 'mysql') {
startContainer(ctx.DOCKER_COMPOSE_FILE, ctx.COMPLEX_DIR, 'mysql', env);
const cid = getContainerId(ctx.DOCKER_COMPOSE_FILE, ctx.COMPLEX_DIR, 'mysql', env);
if (cid) {
waitForMysqlReady(cid);
}
await execa('node', [require('path').join(ctx.COMPLEX_DIR, 'scripts', 'db-mysql.js'), 'wipe'], {
cwd: ctx.COMPLEX_DIR,
stdio: 'inherit',
env,
});
}
if (client === 'mariadb') {
startContainer(ctx.DOCKER_COMPOSE_FILE, ctx.COMPLEX_DIR, 'mariadb', env);
const cid = getContainerId(ctx.DOCKER_COMPOSE_FILE, ctx.COMPLEX_DIR, 'mariadb', env);
if (cid) {
waitForMysqlReady(cid);
}
await execa(
'node',
[require('path').join(ctx.COMPLEX_DIR, 'scripts', 'db-mariadb.js'), 'wipe'],
{
cwd: ctx.COMPLEX_DIR,
stdio: 'inherit',
env,
}
);
}
}
function assertNodeForV4() {
const major = Number(process.versions.node.split('.')[0]);
if (major > 20) {
console.warn(
`\n⚠️ Strapi v4 in this scaffold expects Node <= 20 (engines). Current Node is ${process.version}. If install or seed fails, use Node 20.\n`
);
}
}
async function runWorkspaceBuilds(repoRoot, { fullBuild, skipBuild }) {
const fs = require('fs');
if (fullBuild) {
console.log('\n📦 Running yarn build at repo root (first step)...');
await execa('yarn', ['build'], { cwd: repoRoot, stdio: 'inherit' });
return;
}
if (!skipBuild) {
const strapiPkg = require('path').join(repoRoot, 'packages', 'core', 'strapi', 'package.json');
const databasePkg = require('path').join(
repoRoot,
'packages',
'core',
'database',
'package.json'
);
if (!fs.existsSync(strapiPkg)) {
console.error('Missing @strapi/strapi package; run from a full checkout.');
process.exit(1);
}
if (!fs.existsSync(databasePkg)) {
console.error('Missing @strapi/database package; run from a full checkout.');
process.exit(1);
}
console.log(
'\n📦 Building @strapi/core and @strapi/database (migration dist). Use --skip-build to skip.'
);
await execa('yarn', ['workspace', '@strapi/core', 'build'], {
cwd: repoRoot,
stdio: 'inherit',
});
await execa('yarn', ['workspace', '@strapi/database', 'build'], {
cwd: repoRoot,
stdio: 'inherit',
});
} else {
console.log(
'\n⚠️ --skip-build: tests load compiled code from `packages/core/core/dist` and `packages/core/database/dist`; rebuild those workspaces after editing migration TS if results look stale.'
);
}
}
module.exports = {
composeEnv,
nestedYarnInstallEnv,
dockerComposeDownVolumes,
resolveDockerHostPorts,
buildDatabaseEnvForClient,
writeAppDotenv,
prepareDockerDatabase,
assertNodeForV4,
runWorkspaceBuilds,
};