@launchql/core
Version:
LaunchQL Package and Migration Tools
181 lines (172 loc) • 5.96 kB
JavaScript
import Case from 'case';
import { mkdirSync, rmSync } from 'fs';
import { sync as glob } from 'glob';
import path from 'path';
import { getPgPool } from 'pg-cache';
import { writeSqitchFiles, writeSqitchPlan } from '../files';
import { exportMeta } from './export-meta';
const exportMigrationsToDisk = async ({ project, options, database, databaseId, author, outdir, schema_names, extensionName, metaExtensionName }) => {
outdir = outdir + '/';
const pgPool = getPgPool({
...options.pg,
database
});
const db = await pgPool.query(`select * from collections_public.database where id=$1`, [databaseId]);
const schemas = await pgPool.query(`select * from collections_public.schema where database_id=$1`, [databaseId]);
if (!db?.rows?.length) {
console.log('NO DATABASES.');
return;
}
if (!schemas?.rows?.length) {
console.log('NO SCHEMAS.');
return;
}
const name = extensionName || db.rows[0].name;
const { replace, replacer } = makeReplacer({
schemas: schemas.rows.filter((schema) => schema_names.includes(schema.schema_name)),
name
});
const results = await pgPool.query(`select * from db_migrate.sql_actions order by id`);
const opts = {
name,
replacer,
outdir,
author
};
if (results?.rows?.length > 0) {
await preparePackage({
project,
author,
outdir,
name,
extensions: [
'plpgsql',
'uuid-ossp',
'citext',
'pgcrypto',
'btree_gist',
'postgis',
'hstore',
'db_meta',
'launchql-inflection',
'launchql-uuid',
'launchql-utils',
'launchql-ext-jobs',
'launchql-jwt-claims',
'launchql-stamps',
'launchql-base32',
'launchql-totp',
'launchql-ext-types',
'launchql-ext-default-roles'
]
});
writeSqitchPlan(results.rows, opts);
writeSqitchFiles(results.rows, opts);
let meta = await exportMeta({
opts: options,
dbname: database,
database_id: databaseId
});
meta = replacer(meta);
await preparePackage({
project,
author,
outdir,
extensions: ['plpgsql', 'db_meta', 'db_meta_modules'],
name: metaExtensionName
});
const metaReplacer = makeReplacer({
schemas: schemas.rows.filter((schema) => schema_names.includes(schema.schema_name)),
name: metaExtensionName
});
const metaPackage = [
{
deps: [],
deploy: 'migrate/meta',
content: `SET session_replication_role TO replica;
-- using replica in case we are deploying triggers to collections_public
-- unaccent, postgis affected and require grants
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public to public;
DO $LQLMIGRATION$
DECLARE
BEGIN
EXECUTE format('GRANT CONNECT ON DATABASE %I TO %I', current_database(), 'app_user');
EXECUTE format('GRANT CONNECT ON DATABASE %I TO %I', current_database(), 'app_admin');
END;
$LQLMIGRATION$;
${meta}
UPDATE meta_public.apis
SET dbname = current_database() WHERE TRUE;
UPDATE meta_public.sites
SET dbname = current_database() WHERE TRUE;
SET session_replication_role TO DEFAULT;
`
}
];
opts.replacer = metaReplacer.replacer;
opts.name = metaExtensionName;
writeSqitchPlan(metaPackage, opts);
writeSqitchFiles(metaPackage, opts);
}
pgPool.end();
};
export const exportMigrations = async ({ project, options, dbInfo, author, outdir, schema_names, extensionName, metaExtensionName }) => {
for (let v = 0; v < dbInfo.database_ids.length; v++) {
const databaseId = dbInfo.database_ids[v];
await exportMigrationsToDisk({
project,
options,
extensionName,
metaExtensionName,
database: dbInfo.dbname,
databaseId,
schema_names,
author,
outdir
});
}
};
/**
* Creates a Sqitch package directory or resets the deploy/revert/verify directories if it exists.
*/
const preparePackage = async ({ project, author, outdir, name, extensions }) => {
const curDir = process.cwd();
const sqitchDir = path.resolve(path.join(outdir, name));
mkdirSync(sqitchDir, { recursive: true });
process.chdir(sqitchDir);
const plan = glob(path.join(sqitchDir, 'launchql.plan'));
if (!plan.length) {
project.initModule({
name,
description: name,
author,
extensions,
});
}
else {
rmSync(path.resolve(sqitchDir, 'deploy'), { recursive: true, force: true });
rmSync(path.resolve(sqitchDir, 'revert'), { recursive: true, force: true });
rmSync(path.resolve(sqitchDir, 'verify'), { recursive: true, force: true });
}
process.chdir(curDir);
};
/**
* Generates a function for replacing schema names and extension names in strings.
*/
const makeReplacer = ({ schemas, name }) => {
const replacements = ['launchql-extension-name', name];
const schemaReplacers = schemas.map((schema) => [
schema.schema_name,
Case.snake(`${name}_${schema.name}`)
]);
const replace = [...schemaReplacers, replacements].map(([from, to]) => [new RegExp(from, 'g'), to]);
const replacer = (str, n = 0) => {
if (!str)
return '';
if (replace[n] && replace[n].length === 2) {
return replacer(str.replace(replace[n][0], replace[n][1]), n + 1);
}
return str;
};
return { replacer, replace };
};