1- import { exportMigrations , PgpmPackage } from '@pgpmjs/core' ;
1+ import { PgpmPackage } from '@pgpmjs/core' ;
2+ import { exportMigrations , exportGraphQL , GraphQLClient , graphqlRowToPostgresRow } from '@pgpmjs/export' ;
23import { getEnvOptions } from '@pgpmjs/env' ;
34import { getGitConfigInfo } from '@pgpmjs/types' ;
45import { CLIOptions , Inquirerer } from 'inquirerer' ;
@@ -14,13 +15,19 @@ Export Command:
1415
1516Options:
1617 --help, -h Show this help message
18+ --graphql-endpoint <url> GraphQL endpoint for meta/services data (enables GraphQL mode)
19+ --migrate-endpoint <url> GraphQL endpoint for db_migrate data (optional, for sql_actions)
20+ --migrate-host <host> Host header for migrate endpoint (e.g. db_migrate.localhost:3000)
21+ --migrate-headers <json> Extra headers for migrate endpoint as JSON (e.g. '{"X-Schemata":"db_migrate"}')
22+ --token <token> Bearer token for GraphQL authentication
1723 --author <name> Project author (default: from git config)
1824 --extensionName <name> Extension name
1925 --metaExtensionName <name> Meta extension name (default: svc)
2026 --cwd <directory> Working directory (default: current directory)
2127
2228Examples:
23- pgpm export Export migrations from selected database
29+ pgpm export Export migrations from selected database (SQL mode)
30+ pgpm export --graphql-endpoint 'http://[::1]:3002/graphql' --migrate-endpoint 'http://[::1]:3000/graphql' --migrate-host db_migrate.localhost:3000
2431` ;
2532
2633export default async (
@@ -40,106 +47,267 @@ export default async (
4047 project . ensureWorkspace ( ) ;
4148 project . resetCwd ( project . workspacePath ) ;
4249
43- const options = getEnvOptions ( ) ;
44-
45- const db = await getPgPool ( {
46- database : 'postgres'
47- } ) ;
48-
49- const databasesResult = await db . query ( `
50- SELECT datname FROM pg_catalog.pg_database
51- WHERE datistemplate = FALSE AND datname NOT IN ('postgres')
52- AND datname !~ '^pg_';
53- ` ) ;
54-
55- const { databases : dbname } = await prompter . prompt ( argv , [
56- {
57- type : 'list' ,
58- name : 'databases' ,
59- message : 'Select a database' ,
60- options : databasesResult . rows . map ( row => row . datname ) ,
61- required : true
50+ const graphqlEndpoint = argv [ 'graphql-endpoint' ] || argv . graphqlEndpoint ;
51+ const migrateEndpoint = argv [ 'migrate-endpoint' ] || argv . migrateEndpoint ;
52+ const migrateHost = argv [ 'migrate-host' ] || argv . migrateHost ;
53+ const migrateHeadersRaw = argv [ 'migrate-headers' ] || argv . migrateHeaders ;
54+ const token = argv . token ;
55+
56+ if ( graphqlEndpoint ) {
57+ // =========================================================================
58+ // GraphQL export mode
59+ // =========================================================================
60+ console . log ( `GraphQL export mode: ${ graphqlEndpoint } ` ) ;
61+
62+ const metaClient = new GraphQLClient ( {
63+ endpoint : graphqlEndpoint ,
64+ token,
65+ headers : { 'X-Meta-Schema' : 'true' }
66+ } ) ;
67+
68+ // Fetch databases via GraphQL
69+ const dbRows = await metaClient . fetchAllNodes < { id : string ; name : string } > (
70+ 'databases' ,
71+ 'id\nname'
72+ ) ;
73+
74+ if ( ! dbRows . length ) {
75+ console . log ( 'No databases found via GraphQL.' ) ;
76+ prompter . close ( ) ;
77+ return ;
6278 }
63- ] ) ;
64- const selectedDb = await getPgPool ( {
65- database : dbname
66- } ) ;
67-
68- const dbsResult = await selectedDb . query ( `
69- SELECT id, name FROM metaschema_public.database;
70- ` ) ;
71-
72- const { database_ids : selectedDatabaseName } = await prompter . prompt ( { } as any , [
73- {
74- type : 'list' ,
75- name : 'database_ids' ,
76- message : 'Select database_id' ,
77- options : dbsResult . rows . map ( db => db . name ) ,
78- required : true
79+
80+ const { database_ids : selectedDatabaseName } = await prompter . prompt ( argv , [
81+ {
82+ type : 'list' ,
83+ name : 'database_ids' ,
84+ message : 'Select database' ,
85+ options : dbRows . map ( db => db . name ) ,
86+ required : true
87+ }
88+ ] ) ;
89+
90+ const selectedDatabase = dbRows . find ( db => db . name === selectedDatabaseName ) ;
91+ if ( ! selectedDatabase ) {
92+ console . log ( 'Database not found.' ) ;
93+ prompter . close ( ) ;
94+ return ;
7995 }
80- ] ) ;
81-
82- const selectedDatabase = dbsResult . rows . find ( db => db . name === selectedDatabaseName ) ;
83-
84- const dbInfo = {
85- dbname,
86- databaseName : selectedDatabaseName ,
87- database_ids : [ selectedDatabase ! . id ]
88- } ;
89-
90- const { author, extensionName, metaExtensionName } = await prompter . prompt ( argv , [
91- {
92- type : 'text' ,
93- name : 'author' ,
94- message : 'Project author' ,
95- default : `${ username } <${ email } >` ,
96- required : true
97- } ,
98- {
99- type : 'text' ,
100- name : 'extensionName' ,
101- message : 'Extension name' ,
102- default : selectedDatabaseName || dbname ,
103- required : true
104- } ,
105- {
106- type : 'text' ,
107- name : 'metaExtensionName' ,
108- message : 'Meta extension name' ,
109- default : `${ selectedDatabaseName || dbname } -service` ,
110- required : true
96+
97+ const databaseId = selectedDatabase . id ;
98+
99+ const { author, extensionName, metaExtensionName } = await prompter . prompt ( argv , [
100+ {
101+ type : 'text' ,
102+ name : 'author' ,
103+ message : 'Project author' ,
104+ default : `${ username } <${ email } >` ,
105+ required : true
106+ } ,
107+ {
108+ type : 'text' ,
109+ name : 'extensionName' ,
110+ message : 'Extension name' ,
111+ default : selectedDatabaseName ,
112+ required : true
113+ } ,
114+ {
115+ type : 'text' ,
116+ name : 'metaExtensionName' ,
117+ message : 'Meta extension name' ,
118+ default : `${ selectedDatabaseName } -service` ,
119+ required : true
120+ }
121+ ] ) ;
122+
123+ // Fetch schemas via GraphQL
124+ const schemaRows = await metaClient . fetchAllNodes < { id : string ; schemaName : string ; name : string } > (
125+ 'schemas' ,
126+ 'id\nschemaName\nname' ,
127+ { databaseId }
128+ ) ;
129+
130+ // Convert camelCase to snake_case for schema rows
131+ const pgSchemaRows = schemaRows . map ( s => graphqlRowToPostgresRow ( s ) ) as Array < { id : string ; schema_name : string ; name : string } > ;
132+
133+ // Normalize comma-separated schema_names string into an array for checkbox override
134+ if ( typeof argv . schema_names === 'string' ) {
135+ argv . schema_names = argv . schema_names . split ( ',' ) . map ( ( s : string ) => s . trim ( ) ) . filter ( Boolean ) ;
111136 }
112- ] ) ;
113-
114- const schemasResult = await selectedDb . query (
115- `SELECT * FROM metaschema_public.schema WHERE database_id = $1` ,
116- [ dbInfo . database_ids [ 0 ] ]
117- ) ;
118-
119- const { schema_names } = await prompter . prompt ( { } as any , [
120- {
121- type : 'checkbox' ,
122- name : 'schema_names' ,
123- message : 'Select schema_name(s)' ,
124- options : schemasResult . rows . map ( s => s . schema_name ) ,
125- default : schemasResult . rows . map ( s => s . schema_name ) ,
126- required : true
137+
138+ const { schema_names } = await prompter . prompt ( argv , [
139+ {
140+ type : 'checkbox' ,
141+ name : 'schema_names' ,
142+ message : 'Select schema_name(s)' ,
143+ options : pgSchemaRows . map ( s => s . schema_name ) ,
144+ default : pgSchemaRows . map ( s => s . schema_name ) ,
145+ required : true
146+ }
147+ ] ) ;
148+
149+ const outdir = resolve ( project . workspacePath , 'packages/' ) ;
150+
151+ await exportGraphQL ( {
152+ project,
153+ metaEndpoint : graphqlEndpoint ,
154+ migrateEndpoint,
155+ migrateHeaders : {
156+ ...( migrateHost ? { Host : migrateHost } : { } ) ,
157+ ...( migrateHeadersRaw ? JSON . parse ( migrateHeadersRaw ) : { } )
158+ } ,
159+ token,
160+ headers : { 'X-Meta-Schema' : 'true' } ,
161+ databaseId,
162+ databaseName : selectedDatabaseName ,
163+ schema_names,
164+ schemas : pgSchemaRows ,
165+ author,
166+ outdir,
167+ extensionName,
168+ metaExtensionName,
169+ prompter,
170+ argv,
171+ username
172+ } ) ;
173+ } else {
174+ // =========================================================================
175+ // SQL export mode (original behavior)
176+ // =========================================================================
177+ const options = getEnvOptions ( ) ;
178+
179+ const db = await getPgPool ( {
180+ database : 'postgres'
181+ } ) ;
182+
183+ const databasesResult = await db . query ( `
184+ SELECT datname FROM pg_catalog.pg_database
185+ WHERE datistemplate = FALSE AND datname NOT IN ('postgres')
186+ AND datname !~ '^pg_';
187+ ` ) ;
188+
189+ // If --database_ids is provided but --databases is not, auto-detect
190+ // which postgres database contains the matching metaschema database name.
191+ if ( argv . database_ids && ! argv . databases ) {
192+ const targetName = argv . database_ids ;
193+ for ( const row of databasesResult . rows ) {
194+ try {
195+ const candidatePool = await getPgPool ( { database : row . datname } ) ;
196+ const check = await candidatePool . query (
197+ `SELECT name FROM metaschema_public.database WHERE name = $1 LIMIT 1` ,
198+ [ targetName ]
199+ ) ;
200+ if ( check . rows . length > 0 ) {
201+ argv . databases = row . datname ;
202+ break ;
203+ }
204+ } catch {
205+ // Skip databases that don't have metaschema_public
206+ }
207+ }
208+ if ( ! argv . databases ) {
209+ console . error ( `Could not find database "${ targetName } " in any postgres database.` ) ;
210+ prompter . close ( ) ;
211+ return ;
212+ }
213+ }
214+
215+ const { databases : dbname } = await prompter . prompt ( argv , [
216+ {
217+ type : 'list' ,
218+ name : 'databases' ,
219+ message : 'Select a database' ,
220+ options : databasesResult . rows . map ( row => row . datname ) ,
221+ required : true
222+ }
223+ ] ) ;
224+ const selectedDb = await getPgPool ( {
225+ database : dbname
226+ } ) ;
227+
228+ const dbsResult = await selectedDb . query ( `
229+ SELECT id, name FROM metaschema_public.database;
230+ ` ) ;
231+
232+ const { database_ids : selectedDatabaseName } = await prompter . prompt ( argv , [
233+ {
234+ type : 'list' ,
235+ name : 'database_ids' ,
236+ message : 'Select database_id' ,
237+ options : dbsResult . rows . map ( db => db . name ) ,
238+ required : true
239+ }
240+ ] ) ;
241+
242+ const selectedDatabase = dbsResult . rows . find ( db => db . name === selectedDatabaseName ) ;
243+
244+ const dbInfo = {
245+ dbname,
246+ databaseName : selectedDatabaseName ,
247+ database_ids : [ selectedDatabase ! . id ]
248+ } ;
249+
250+ const { author, extensionName, metaExtensionName } = await prompter . prompt ( argv , [
251+ {
252+ type : 'text' ,
253+ name : 'author' ,
254+ message : 'Project author' ,
255+ default : `${ username } <${ email } >` ,
256+ required : true
257+ } ,
258+ {
259+ type : 'text' ,
260+ name : 'extensionName' ,
261+ message : 'Extension name' ,
262+ default : selectedDatabaseName || dbname ,
263+ required : true
264+ } ,
265+ {
266+ type : 'text' ,
267+ name : 'metaExtensionName' ,
268+ message : 'Meta extension name' ,
269+ default : `${ selectedDatabaseName || dbname } -service` ,
270+ required : true
271+ }
272+ ] ) ;
273+
274+ const schemasResult = await selectedDb . query (
275+ `SELECT * FROM metaschema_public.schema WHERE database_id = $1` ,
276+ [ dbInfo . database_ids [ 0 ] ]
277+ ) ;
278+
279+ // Normalize comma-separated schema_names string into an array for checkbox override
280+ if ( typeof argv . schema_names === 'string' ) {
281+ argv . schema_names = argv . schema_names . split ( ',' ) . map ( ( s : string ) => s . trim ( ) ) . filter ( Boolean ) ;
127282 }
128- ] ) ;
129-
130- const outdir = resolve ( project . workspacePath , 'packages/' ) ;
131-
132- await exportMigrations ( {
133- project,
134- options,
135- dbInfo,
136- author,
137- schema_names,
138- outdir,
139- extensionName,
140- metaExtensionName,
141- prompter
142- } ) ;
283+
284+ const { schema_names } = await prompter . prompt ( argv , [
285+ {
286+ type : 'checkbox' ,
287+ name : 'schema_names' ,
288+ message : 'Select schema_name(s)' ,
289+ options : schemasResult . rows . map ( s => s . schema_name ) ,
290+ default : schemasResult . rows . map ( s => s . schema_name ) ,
291+ required : true
292+ }
293+ ] ) ;
294+
295+ const outdir = resolve ( project . workspacePath , 'packages/' ) ;
296+
297+ await exportMigrations ( {
298+ project,
299+ options,
300+ dbInfo,
301+ author,
302+ schema_names,
303+ outdir,
304+ extensionName,
305+ metaExtensionName,
306+ prompter,
307+ argv,
308+ username
309+ } ) ;
310+ }
143311
144312 prompter . close ( ) ;
145313
0 commit comments