mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(cli): add rollback-to db action (#4329)
* feat(cli): add rollback-to db action add rollback-to db action * chore: bump alteration timestamp bump alteration timestamp * chore: rename the alteration action name rename the alteration action name
This commit is contained in:
parent
44d023ab2e
commit
6771f9ed6f
3 changed files with 80 additions and 42 deletions
|
@ -12,7 +12,11 @@ import {
|
||||||
import { consoleLog } from '../../../utils.js';
|
import { consoleLog } from '../../../utils.js';
|
||||||
|
|
||||||
import type { AlterationFile } from './type.js';
|
import type { AlterationFile } from './type.js';
|
||||||
import { getAlterationFiles, getTimestampFromFilename } from './utils.js';
|
import {
|
||||||
|
getAlterationFiles,
|
||||||
|
getTimestampFromFilename,
|
||||||
|
chooseRevertAlterationsByTimestamp,
|
||||||
|
} from './utils.js';
|
||||||
import { chooseAlterationsByVersion, chooseRevertAlterationsByVersion } from './version.js';
|
import { chooseAlterationsByVersion, chooseRevertAlterationsByVersion } from './version.js';
|
||||||
|
|
||||||
const importAlterationScript = async (filePath: string): Promise<AlterationScript> => {
|
const importAlterationScript = async (filePath: string): Promise<AlterationScript> => {
|
||||||
|
@ -87,6 +91,21 @@ const deployAlteration = async (
|
||||||
consoleLog.info(`Run alteration ${filename} \`${action}()\` function succeeded`);
|
consoleLog.info(`Run alteration ${filename} \`${action}()\` function succeeded`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const revertAlterations = async (alterations: AlterationFile[], pool: DatabasePool) => {
|
||||||
|
consoleLog.info(
|
||||||
|
`Found ${alterations.length} alteration${conditionalString(
|
||||||
|
alterations.length > 1 && 's'
|
||||||
|
)} to revert`
|
||||||
|
);
|
||||||
|
|
||||||
|
// The await inside the loop is intended, alterations should run in order
|
||||||
|
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
||||||
|
for (const alteration of alterations.slice().reverse()) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await deployAlteration(pool, alteration, 'down');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const alteration: CommandModule<unknown, { action: string; target?: string }> = {
|
const alteration: CommandModule<unknown, { action: string; target?: string }> = {
|
||||||
command: ['alteration <action> [target]', 'alt', 'alter'],
|
command: ['alteration <action> [target]', 'alt', 'alter'],
|
||||||
describe: 'Perform database alteration',
|
describe: 'Perform database alteration',
|
||||||
|
@ -101,56 +120,64 @@ const alteration: CommandModule<unknown, { action: string; target?: string }> =
|
||||||
describe: 'The target Logto version for alteration',
|
describe: 'The target Logto version for alteration',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
handler: async ({ action, target }) => {
|
handler: async ({ action, target }) => {
|
||||||
if (action === 'list') {
|
switch (action) {
|
||||||
const files = await getAlterationFiles();
|
case 'list': {
|
||||||
|
const files = await getAlterationFiles();
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
consoleLog.plain(file.filename);
|
consoleLog.plain(file.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (action === 'deploy') {
|
case 'deploy': {
|
||||||
const pool = await createPoolFromConfig();
|
const pool = await createPoolFromConfig();
|
||||||
const alterations = await chooseAlterationsByVersion(
|
const alterations = await chooseAlterationsByVersion(
|
||||||
await getAvailableAlterations(pool),
|
await getAvailableAlterations(pool),
|
||||||
target
|
target
|
||||||
);
|
);
|
||||||
|
|
||||||
consoleLog.info(
|
consoleLog.info(
|
||||||
`Found ${alterations.length} alteration${conditionalString(
|
`Found ${alterations.length} alteration${conditionalString(
|
||||||
alterations.length > 1 && 's'
|
alterations.length > 1 && 's'
|
||||||
)} to deploy`
|
)} to deploy`
|
||||||
);
|
);
|
||||||
|
|
||||||
// The await inside the loop is intended, alterations should run in order
|
// The await inside the loop is intended, alterations should run in order
|
||||||
for (const alteration of alterations) {
|
for (const alteration of alterations) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await deployAlteration(pool, alteration);
|
await deployAlteration(pool, alteration);
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.end();
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case 'rollback':
|
||||||
|
case 'r': {
|
||||||
|
const pool = await createPoolFromConfig();
|
||||||
|
const alterations = await chooseRevertAlterationsByVersion(
|
||||||
|
await getAvailableAlterations(pool, 'lte'),
|
||||||
|
target ?? ''
|
||||||
|
);
|
||||||
|
|
||||||
await pool.end();
|
await revertAlterations(alterations, pool);
|
||||||
} else if (['rollback', 'r'].includes(action)) {
|
await pool.end();
|
||||||
const pool = await createPoolFromConfig();
|
break;
|
||||||
const alterations = await chooseRevertAlterationsByVersion(
|
|
||||||
await getAvailableAlterations(pool, 'lte'),
|
|
||||||
target ?? ''
|
|
||||||
);
|
|
||||||
|
|
||||||
consoleLog.info(
|
|
||||||
`Found ${alterations.length} alteration${conditionalString(
|
|
||||||
alterations.length > 1 && 's'
|
|
||||||
)} to revert`
|
|
||||||
);
|
|
||||||
|
|
||||||
// The await inside the loop is intended, alterations should run in order
|
|
||||||
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
|
||||||
for (const alteration of alterations.slice().reverse()) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await deployAlteration(pool, alteration, 'down');
|
|
||||||
}
|
}
|
||||||
|
case 'rollback-to-timestamp': {
|
||||||
|
const pool = await createPoolFromConfig();
|
||||||
|
const alterations = await chooseRevertAlterationsByTimestamp(target ?? '');
|
||||||
|
|
||||||
await pool.end();
|
await revertAlterations(alterations, pool);
|
||||||
} else {
|
await pool.end();
|
||||||
consoleLog.fatal('Unsupported action');
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
consoleLog.fatal('Unsupported action');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -55,3 +55,14 @@ export const getAlterationFiles = async (): Promise<AlterationFile[]> => {
|
||||||
.sort((file1, file2) => getTimestampFromFilename(file1) - getTimestampFromFilename(file2))
|
.sort((file1, file2) => getTimestampFromFilename(file1) - getTimestampFromFilename(file2))
|
||||||
.map((filename) => ({ path: path.join(localAlterationDirectory, filename), filename }));
|
.map((filename) => ({ path: path.join(localAlterationDirectory, filename), filename }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const chooseRevertAlterationsByTimestamp = async (target: string) => {
|
||||||
|
const files = await getAlterationFiles();
|
||||||
|
const targetTimestamp = Number(target);
|
||||||
|
|
||||||
|
if (Number.isNaN(targetTimestamp)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.filter(({ filename }) => getTimestampFromFilename(filename) > targetTimestamp);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue