@augment-vir/node
Version:
A collection of augments, helpers types, functions, and classes only for Node.js (backend) JavaScript environments.
151 lines (133 loc) • 4.34 kB
text/typescript
import {check} from '@augment-vir/assert';
import {log, safeMatch, toEnsuredNumber} from '@augment-vir/common';
import terminate from 'terminate';
import {runShellCommand} from '../augments/terminal/shell.js';
import {PrismaMigrationNeededError, PrismaResetNeededError} from './prisma-errors.js';
import {runPrismaCommand, verifyOutput} from './run-prisma-command.js';
/**
* Output of `prisma.migration.status`.
*
* @category Prisma : Node : Util
* @category Package : @augment-vir/node
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
*/
export type PrismaMigrationStatus = {
totalMigrations: number;
unappliedMigrations: string[];
};
export async function applyPrismaMigrationsToProd(
schemaFilePath: string,
env: Record<string, string> = {},
) {
await runPrismaCommand({command: 'migrate deploy'}, schemaFilePath, env);
}
enum DbChangeRequired {
MigrationNeeded = 'migration-needed',
ResetNeeded = 'reset-needed',
}
export async function applyPrismaMigrationsToDev(
schemaFilePath: string,
env: Record<string, string> = {},
) {
const command = [
'prisma',
'migrate',
'dev',
`--schema='${schemaFilePath}'`,
].join(' ');
log.faint(`> ${command}`);
let dbRequirement = undefined as DbChangeRequired | undefined;
const result = await runShellCommand(command, {
env: {
...process.env,
...env,
},
stdoutCallback(stdout, childProcess) {
if (stdout.includes('Enter a name for the new migration')) {
if (childProcess.pid) {
terminate(childProcess.pid);
}
dbRequirement = DbChangeRequired.MigrationNeeded;
} else if (stdout.includes('We need to reset the SQLite database')) {
if (childProcess.pid) {
terminate(childProcess.pid);
}
dbRequirement = DbChangeRequired.ResetNeeded;
}
},
});
if (dbRequirement === DbChangeRequired.MigrationNeeded) {
throw new PrismaMigrationNeededError(schemaFilePath);
} else if (dbRequirement === DbChangeRequired.ResetNeeded) {
throw new PrismaResetNeededError(schemaFilePath);
}
verifyOutput(schemaFilePath, result, false);
}
export async function getMigrationStatus(
schemaFilePath: string,
env: Record<string, string> = {},
): Promise<PrismaMigrationStatus> {
const output = await runPrismaCommand(
{
command: 'migrate status',
ignoreExitCode: true,
},
schemaFilePath,
env,
);
const listedMigrations: PrismaMigrationStatus = {
totalMigrations: 0,
unappliedMigrations: [],
};
let foundNotAppliedMigrations = false;
output.stdout.split('\n').some((rawLine) => {
const line = rawLine.trim();
if (foundNotAppliedMigrations) {
if (line) {
listedMigrations.unappliedMigrations.push(line);
} else {
/** We're done parsing. */
return true;
}
} else if (line.endsWith('not yet been applied:')) {
foundNotAppliedMigrations = true;
} else {
const [
,
countMatch,
] = safeMatch(line, /^([\d,]+) migrations? found in/);
if (countMatch) {
listedMigrations.totalMigrations = toEnsuredNumber(countMatch);
}
}
/** Still need to keep parsing. */
return false;
});
return listedMigrations;
}
export async function createPrismaMigration(
{
migrationName,
createOnly = false,
}: {
migrationName: string;
/**
* Set this to `true` to create a new migration without applying it to the database.
*
* @default false
*/
createOnly?: boolean | undefined;
},
schemaFilePath: string,
env: Record<string, string> = {},
) {
const command = [
'migrate',
'dev',
`--name='${migrationName}'`,
createOnly ? '--create-only' : '',
]
.filter(check.isTruthy)
.join(' ');
await runPrismaCommand({command}, schemaFilePath, env);
}