mirror of
https://github.com/strapi/strapi.git
synced 2026-06-02 16:27:47 +00:00
* fix(core): preserve createdBy/updatedBy on drafts created by discard-drafts migration Stage 1 of the v4→v5 discard-drafts migration now copies join-column foreign keys when cloning published rows to drafts, so creator metadata is no longer NULL on the new draft rows. Adds a focused migration test and extends the complex example migration validator to catch this regression. Fixes #26460 * fix(core): skip virtual join columns in discard-drafts stage 1 i18n localizations reuses document_id as a virtual join column; copying it alongside the scalar duplicated the column in INSERT…SELECT and broke v4→v5 migration. Dedupe clone columns and apply the same filter in later stages.
159 lines
4.9 KiB
JavaScript
159 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const SCRIPT_DIR = __dirname;
|
|
const COMPLEX_DIR = path.resolve(SCRIPT_DIR, '..');
|
|
const MONOREPO_ROOT = path.resolve(COMPLEX_DIR, '../..');
|
|
const V4_PROJECT_DIR = process.env.V4_OUTSIDE_DIR
|
|
? path.resolve(process.cwd(), process.env.V4_OUTSIDE_DIR)
|
|
: path.resolve(MONOREPO_ROOT, '..', 'complex-v4');
|
|
|
|
/**
|
|
* SQLite lives entirely as files; no container and no compose runtime.
|
|
*
|
|
* The v4 project stores its database under V4_PROJECT_DIR/.tmp/data.db.
|
|
* v5 reads/writes the same file so snapshots are interchangeable.
|
|
*/
|
|
const DATABASE_FILENAME =
|
|
process.env.SQLITE_DATABASE_FILENAME ||
|
|
process.env.DATABASE_FILENAME ||
|
|
path.join(V4_PROJECT_DIR, '.tmp', 'data.db');
|
|
|
|
const SNAPSHOTS_DIR = path.join(COMPLEX_DIR, 'snapshots');
|
|
|
|
const command = process.argv[2];
|
|
const snapshotName = process.argv[3];
|
|
|
|
function ensureSnapshotsDir() {
|
|
if (!fs.existsSync(SNAPSHOTS_DIR)) {
|
|
fs.mkdirSync(SNAPSHOTS_DIR, { recursive: true });
|
|
}
|
|
}
|
|
|
|
function snapshotPath(name) {
|
|
return path.join(SNAPSHOTS_DIR, `sqlite-${name}.db`);
|
|
}
|
|
|
|
function requireBetterSqlite() {
|
|
try {
|
|
// eslint-disable-next-line global-require
|
|
return require('better-sqlite3');
|
|
} catch (error) {
|
|
throw new Error(
|
|
'better-sqlite3 is required for sqlite operations. It is a peer dep of @strapi/strapi and should be present via workspace hoisting; if not, install it in examples/complex.'
|
|
);
|
|
}
|
|
}
|
|
|
|
switch (command) {
|
|
case 'start':
|
|
// No-op: sqlite is file-based.
|
|
console.log('✅ SQLite is file-based; nothing to start.');
|
|
console.log(` Database file: ${DATABASE_FILENAME}`);
|
|
break;
|
|
|
|
case 'stop':
|
|
// No-op: sqlite is file-based.
|
|
console.log('✅ SQLite is file-based; nothing to stop.');
|
|
break;
|
|
|
|
case 'snapshot': {
|
|
if (!snapshotName) {
|
|
console.error('Error: Snapshot name is required');
|
|
console.error('Usage: node db-sqlite.js snapshot <name>');
|
|
process.exit(1);
|
|
}
|
|
if (!fs.existsSync(DATABASE_FILENAME)) {
|
|
console.error(`Error: Database file not found: ${DATABASE_FILENAME}`);
|
|
console.error('Run the v4 app with `yarn develop:sqlite` and seed first to create it.');
|
|
process.exit(1);
|
|
}
|
|
ensureSnapshotsDir();
|
|
const target = snapshotPath(snapshotName);
|
|
fs.copyFileSync(DATABASE_FILENAME, target);
|
|
console.log(`✅ Snapshot created: ${target}`);
|
|
break;
|
|
}
|
|
|
|
case 'restore': {
|
|
if (!snapshotName) {
|
|
console.error('Error: Snapshot name is required');
|
|
console.error('Usage: node db-sqlite.js restore <name>');
|
|
process.exit(1);
|
|
}
|
|
const source = snapshotPath(snapshotName);
|
|
if (!fs.existsSync(source)) {
|
|
console.error(`Error: Snapshot not found: ${source}`);
|
|
process.exit(1);
|
|
}
|
|
// Remove walk-ahead / shared-memory sidecars so they don't conflict with the
|
|
// restored file's transaction state.
|
|
for (const suffix of ['', '-wal', '-shm', '-journal']) {
|
|
const sidecar = `${DATABASE_FILENAME}${suffix}`;
|
|
if (fs.existsSync(sidecar)) {
|
|
try {
|
|
fs.unlinkSync(sidecar);
|
|
} catch {
|
|
/* best-effort */
|
|
}
|
|
}
|
|
}
|
|
fs.mkdirSync(path.dirname(DATABASE_FILENAME), { recursive: true });
|
|
fs.copyFileSync(source, DATABASE_FILENAME);
|
|
console.log(`✅ Snapshot restored: ${snapshotName} -> ${DATABASE_FILENAME}`);
|
|
break;
|
|
}
|
|
|
|
case 'wipe':
|
|
for (const suffix of ['', '-wal', '-shm', '-journal']) {
|
|
const f = `${DATABASE_FILENAME}${suffix}`;
|
|
if (fs.existsSync(f)) {
|
|
fs.unlinkSync(f);
|
|
}
|
|
}
|
|
console.log(`✅ SQLite database file removed: ${DATABASE_FILENAME}`);
|
|
break;
|
|
|
|
case 'check': {
|
|
if (!fs.existsSync(DATABASE_FILENAME)) {
|
|
console.log('📊 No database file found (empty or wiped)');
|
|
break;
|
|
}
|
|
const Database = requireBetterSqlite();
|
|
const db = new Database(DATABASE_FILENAME, { readonly: true, fileMustExist: true });
|
|
try {
|
|
const tables = db
|
|
.prepare(
|
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
|
)
|
|
.all();
|
|
|
|
if (tables.length === 0) {
|
|
console.log('📊 No tables found (database is empty or wiped)');
|
|
break;
|
|
}
|
|
|
|
console.log('📊 Database Tables (row counts):\n');
|
|
console.log('Table Name | Row Count');
|
|
console.log('------------------------------------|----------');
|
|
|
|
for (const { name } of tables) {
|
|
// SQLite doesn't keep approximate row stats; use exact COUNT(*) here.
|
|
const { c } = db.prepare(`SELECT COUNT(*) AS c FROM "${name}"`).get();
|
|
const padded = name.padEnd(35);
|
|
console.log(`${padded} | ${c}`);
|
|
}
|
|
} finally {
|
|
db.close();
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
console.error('Error: Unknown command');
|
|
console.error('Usage: node db-sqlite.js <start|stop|snapshot|restore|wipe|check> [name]');
|
|
process.exit(1);
|
|
}
|