Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 227 additions & 98 deletions pgpm/cli/src/commands/export.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { exportMigrations,PgpmPackage } from '@pgpmjs/core';
import { exportMigrations, exportGraphQL, GraphQLClient, PgpmPackage } from '@pgpmjs/core';
import { graphqlRowToPostgresRow } from '@pgpmjs/core';
import { getEnvOptions } from '@pgpmjs/env';
import { getGitConfigInfo } from '@pgpmjs/types';
import { CLIOptions, Inquirerer } from 'inquirerer';
Expand All @@ -14,13 +15,17 @@ Export Command:

Options:
--help, -h Show this help message
--graphql-endpoint <url> GraphQL endpoint for meta/services data (enables GraphQL mode)
--migrate-endpoint <url> GraphQL endpoint for db_migrate data (optional, for sql_actions)
--token <token> Bearer token for GraphQL authentication
--author <name> Project author (default: from git config)
--extensionName <name> Extension name
--metaExtensionName <name> Meta extension name (default: svc)
--cwd <directory> Working directory (default: current directory)

Examples:
pgpm export Export migrations from selected database
pgpm export Export migrations from selected database (SQL mode)
pgpm export --graphql-endpoint http://private.localhost:3002/graphql --migrate-endpoint http://db_migrate.localhost:3000/graphql
`;

export default async (
Expand All @@ -40,106 +45,230 @@ export default async (
project.ensureWorkspace();
project.resetCwd(project.workspacePath);

const options = getEnvOptions();

const db = await getPgPool({
database: 'postgres'
});

const databasesResult = await db.query(`
SELECT datname FROM pg_catalog.pg_database
WHERE datistemplate = FALSE AND datname NOT IN ('postgres')
AND datname !~ '^pg_';
`);

const { databases: dbname } = await prompter.prompt(argv, [
{
type: 'list',
name: 'databases',
message: 'Select a database',
options: databasesResult.rows.map(row => row.datname),
required: true
const graphqlEndpoint = argv['graphql-endpoint'] || argv.graphqlEndpoint;
const migrateEndpoint = argv['migrate-endpoint'] || argv.migrateEndpoint;
const token = argv.token;

if (graphqlEndpoint) {
// =========================================================================
// GraphQL export mode
// =========================================================================
console.log(`GraphQL export mode: ${graphqlEndpoint}`);

const metaClient = new GraphQLClient({ endpoint: graphqlEndpoint, token });

// Fetch databases via GraphQL
const dbRows = await metaClient.fetchAllNodes<{ id: string; name: string }>(
'databases',
'id\nname'
);

if (!dbRows.length) {
console.log('No databases found via GraphQL.');
prompter.close();
return;
}
]);
const selectedDb = await getPgPool({
database: dbname
});

const dbsResult = await selectedDb.query(`
SELECT id, name FROM metaschema_public.database;
`);

const { database_ids: selectedDatabaseName } = await prompter.prompt({} as any, [
{
type: 'list',
name: 'database_ids',
message: 'Select database_id',
options: dbsResult.rows.map(db => db.name),
required: true

const { database_ids: selectedDatabaseName } = await prompter.prompt(argv, [
{
type: 'list',
name: 'database_ids',
message: 'Select database',
options: dbRows.map(db => db.name),
required: true
}
]);

const selectedDatabase = dbRows.find(db => db.name === selectedDatabaseName);
if (!selectedDatabase) {
console.log('Database not found.');
prompter.close();
return;
}
]);

const selectedDatabase = dbsResult.rows.find(db => db.name === selectedDatabaseName);

const dbInfo = {
dbname,
databaseName: selectedDatabaseName,
database_ids: [selectedDatabase!.id]
};

const { author, extensionName, metaExtensionName } = await prompter.prompt(argv, [
{
type: 'text',
name: 'author',
message: 'Project author',
default: `${username} <${email}>`,
required: true
},
{
type: 'text',
name: 'extensionName',
message: 'Extension name',
default: selectedDatabaseName || dbname,
required: true
},
{
type: 'text',
name: 'metaExtensionName',
message: 'Meta extension name',
default: `${selectedDatabaseName || dbname}-service`,
required: true

const databaseId = selectedDatabase.id;

const { author, extensionName, metaExtensionName } = await prompter.prompt(argv, [
{
type: 'text',
name: 'author',
message: 'Project author',
default: `${username} <${email}>`,
required: true
},
{
type: 'text',
name: 'extensionName',
message: 'Extension name',
default: selectedDatabaseName,
required: true
},
{
type: 'text',
name: 'metaExtensionName',
message: 'Meta extension name',
default: `${selectedDatabaseName}-service`,
required: true
}
]);

// Fetch schemas via GraphQL
const schemaRows = await metaClient.fetchAllNodes<{ id: string; schemaName: string; name: string }>(
'schemas',
'id\nschemaName\nname',
{ databaseId }
);

// Convert camelCase to snake_case for schema rows
const pgSchemaRows = schemaRows.map(s => graphqlRowToPostgresRow(s)) as Array<{ id: string; schema_name: string; name: string }>;

// Normalize comma-separated schema_names string into an array for checkbox override
if (typeof argv.schema_names === 'string') {
argv.schema_names = argv.schema_names.split(',').map((s: string) => s.trim()).filter(Boolean);
}
]);

const schemasResult = await selectedDb.query(
`SELECT * FROM metaschema_public.schema WHERE database_id = $1`,
[dbInfo.database_ids[0]]
);

const { schema_names } = await prompter.prompt({} as any, [
{
type: 'checkbox',
name: 'schema_names',
message: 'Select schema_name(s)',
options: schemasResult.rows.map(s => s.schema_name),
default: schemasResult.rows.map(s => s.schema_name),
required: true

const { schema_names } = await prompter.prompt(argv, [
{
type: 'checkbox',
name: 'schema_names',
message: 'Select schema_name(s)',
options: pgSchemaRows.map(s => s.schema_name),
default: pgSchemaRows.map(s => s.schema_name),
required: true
}
]);

const outdir = resolve(project.workspacePath, 'packages/');

await exportGraphQL({
project,
metaEndpoint: graphqlEndpoint,
migrateEndpoint,
token,
databaseId,
databaseName: selectedDatabaseName,
schema_names,
schemas: pgSchemaRows,
author,
outdir,
extensionName,
metaExtensionName,
prompter,
argv,
username
});
} else {
// =========================================================================
// SQL export mode (original behavior)
// =========================================================================
const options = getEnvOptions();

const db = await getPgPool({
database: 'postgres'
});

const databasesResult = await db.query(`
SELECT datname FROM pg_catalog.pg_database
WHERE datistemplate = FALSE AND datname NOT IN ('postgres')
AND datname !~ '^pg_';
`);

const { databases: dbname } = await prompter.prompt(argv, [
{
type: 'list',
name: 'databases',
message: 'Select a database',
options: databasesResult.rows.map(row => row.datname),
required: true
}
]);
const selectedDb = await getPgPool({
database: dbname
});

const dbsResult = await selectedDb.query(`
SELECT id, name FROM metaschema_public.database;
`);

const { database_ids: selectedDatabaseName } = await prompter.prompt(argv, [
{
type: 'list',
name: 'database_ids',
message: 'Select database_id',
options: dbsResult.rows.map(db => db.name),
required: true
}
]);

const selectedDatabase = dbsResult.rows.find(db => db.name === selectedDatabaseName);

const dbInfo = {
dbname,
databaseName: selectedDatabaseName,
database_ids: [selectedDatabase!.id]
};

const { author, extensionName, metaExtensionName } = await prompter.prompt(argv, [
{
type: 'text',
name: 'author',
message: 'Project author',
default: `${username} <${email}>`,
required: true
},
{
type: 'text',
name: 'extensionName',
message: 'Extension name',
default: selectedDatabaseName || dbname,
required: true
},
{
type: 'text',
name: 'metaExtensionName',
message: 'Meta extension name',
default: `${selectedDatabaseName || dbname}-service`,
required: true
}
]);

const schemasResult = await selectedDb.query(
`SELECT * FROM metaschema_public.schema WHERE database_id = $1`,
[dbInfo.database_ids[0]]
);

// Normalize comma-separated schema_names string into an array for checkbox override
if (typeof argv.schema_names === 'string') {
argv.schema_names = argv.schema_names.split(',').map((s: string) => s.trim()).filter(Boolean);
}
]);

const outdir = resolve(project.workspacePath, 'packages/');

await exportMigrations({
project,
options,
dbInfo,
author,
schema_names,
outdir,
extensionName,
metaExtensionName,
prompter
});

const { schema_names } = await prompter.prompt(argv, [
{
type: 'checkbox',
name: 'schema_names',
message: 'Select schema_name(s)',
options: schemasResult.rows.map(s => s.schema_name),
default: schemasResult.rows.map(s => s.schema_name),
required: true
}
]);

const outdir = resolve(project.workspacePath, 'packages/');

await exportMigrations({
project,
options,
dbInfo,
author,
schema_names,
outdir,
extensionName,
metaExtensionName,
prompter,
argv,
username
});
}

prompter.close();

Expand Down
1 change: 1 addition & 0 deletions pgpm/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"csv-to-pg": "workspace:^",
"genomic": "^5.3.5",
"glob": "^13.0.0",
"inflekt": "^0.3.3",
"komoji": "^0.8.1",
"minimatch": "^10.2.4",
"parse-package-name": "^1.0.0",
Expand Down
Loading