@cyklang/cli 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/AssetCommand.d.ts +4 -0
- package/build/AssetCommand.js +293 -0
- package/build/Cmd.d.ts +2 -1
- package/build/Cmd.js +3 -3
- package/build/DBClient.d.ts +1 -0
- package/build/DBClient.js +51 -17
- package/build/InitCommand.js +1 -1
- package/build/ModuleCommand.js +7 -6
- package/build/TableCommand.js +235 -45
- package/build/index.js +3 -0
- package/package.json +5 -2
- package/pgdata/entry.csv +22 -0
- package/pgdata/setting.csv +4 -0
- package/src/AssetCommand.ts +307 -0
- package/src/Cmd.ts +4 -3
- package/src/DBClient.ts +70 -31
- package/src/InitCommand.ts +1 -1
- package/src/ModuleCommand.ts +8 -5
- package/src/TableCommand.ts +269 -56
- package/src/index.ts +3 -0
- package/src/index.xml +10 -0
- package/test/cyklang.xsd +439 -0
- package/test/t0.output +4 -0
- package/test/t0.xml +26 -0
- package/test/t01.output +32 -0
- package/test/t01.xml +185 -0
- package/test/t02.output +10 -0
- package/test/t02.xml +88 -0
- package/test/t03.output +9 -0
- package/test/t03.xml +74 -0
- package/test/t04.output +29 -0
- package/test/t04.xml +116 -0
- package/test/t05.output +18 -0
- package/test/t05.xml +41 -0
- package/test/t06.output +4 -0
- package/test/t06.xml +16 -0
- package/test/t07.output +5 -0
- package/test/t07.xml +82 -0
- package/test/t08.output +8 -0
- package/test/t08.xml +57 -0
- package/test/t09.output +10 -0
- package/test/t09.xml +29 -0
- package/test/t10.xml +125 -0
- package/test/t11.output +3 -0
- package/test/t11.xml +25 -0
- package/test/t12.output +7 -0
- package/test/t12.xml +42 -0
- package/test/t13.xml +23 -0
- package/test/t14.output +50 -0
- package/test/t14.xml +82 -0
- package/test/t14.xsd +102 -0
- package/test/t15.xml +47 -0
- package/test/t16.output +49 -0
- package/test/t16.xml +86 -0
- package/test/t17.output +5 -0
- package/test/t17.xml +21 -0
- package/test/t18.output +22 -0
- package/test/t18.xml +59 -0
- package/test/t19.xml +234 -0
- package/test/t20.xml +15 -0
- package/test/t21.output +21 -0
- package/test/t21.xml +71 -0
- package/test/t22.xml +23 -0
- package/test/t23.xml +44 -0
- package/test/t24.xml +10 -0
- package/test/test.json +113 -0
package/src/TableCommand.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
import { DBManager } from "@cyklang/core"
|
|
2
|
+
import { DBManager, DBTable } from "@cyklang/core"
|
|
3
3
|
import * as fs from "fs"
|
|
4
4
|
import loglevel from 'loglevel'
|
|
5
5
|
import { Command } from "commander"
|
|
6
6
|
import { spawn } from "child_process"
|
|
7
7
|
import { Cmd } from "./Cmd"
|
|
8
|
+
import { DBClient } from "./DBClient"
|
|
9
|
+
import { Stream } from "form-data"
|
|
10
|
+
import FormData from 'form-data'
|
|
11
|
+
|
|
8
12
|
const logger = loglevel.getLogger("TableCommand.ts")
|
|
9
13
|
logger.setLevel("debug")
|
|
10
14
|
|
|
@@ -13,40 +17,158 @@ export class TableCommand extends Command {
|
|
|
13
17
|
super(name)
|
|
14
18
|
this.description('import/export table rows').version('0.1')
|
|
15
19
|
this.addCommand(new TableExportCmd())
|
|
20
|
+
this.addCommand(new TableImportCmd())
|
|
21
|
+
this.addCommand(new TableList('list', 'list database tables'))
|
|
22
|
+
this.addCommand(new TableList('l', '(l)ist database tables'))
|
|
23
|
+
this.addCommand(new TableQuery('query', 'query select table content'))
|
|
24
|
+
this.addCommand(new TableQuery('q', 'query select table content'))
|
|
16
25
|
}
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
class TableExportCmd extends Cmd {
|
|
29
|
+
|
|
20
30
|
constructor() {
|
|
21
31
|
super('export')
|
|
22
32
|
this.description('export to CSV file(s)')
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
.argument('<tables...>', 'table(s) to export')
|
|
34
|
+
.option('-f --file <file>', 'name of the file to create, defaults to <table_name>.csv')
|
|
35
|
+
.option('-d --dir <directory>', 'name of the directory where to create exported files')
|
|
36
|
+
.action(async (tables, options) => this.commandExport(tables, options))
|
|
27
37
|
}
|
|
38
|
+
|
|
28
39
|
async commandExport(tables: string[], options: Options) {
|
|
29
40
|
await this.prologue(options)
|
|
30
41
|
if (this.dbManager === undefined) throw 'dbManager undefined'
|
|
31
|
-
await commandImportExport(this.dbManager, 'to', tables, options)
|
|
42
|
+
// await commandImportExport(this.dbManager, 'to', tables, options)
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
if (options.file === undefined && options.dir === undefined) throw '--file or --dir mandatory'
|
|
46
|
+
if (options.file !== undefined && options.dir !== undefined) throw '--file and --dir are mutually exclusive'
|
|
47
|
+
if (tables.length === 0) throw 'at least one table name is mandatory'
|
|
48
|
+
if (tables.length > 1 && options.dir === undefined) throw 'for multiple tables, --dir is mandatory'
|
|
49
|
+
|
|
50
|
+
for (let indi = 0; indi < tables.length; indi++) {
|
|
51
|
+
const tableName = tables[indi]
|
|
52
|
+
const dbTable = await this.dbManager.dbTableExist(tableName)
|
|
53
|
+
if (dbTable === undefined) {
|
|
54
|
+
logger.info('table ' + tableName + ' not found')
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
const filename = buildFilename(dbTable.name, options)
|
|
58
|
+
await this.copyTo(dbTable, filename)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
logger.error(err)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async copyTo(dbTable: DBTable, filename: string) {
|
|
67
|
+
if (this.dbRemote === undefined) throw 'dbRemote undefined'
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const writeStream = fs.createWriteStream(filename)
|
|
71
|
+
|
|
72
|
+
const response = await this.dbRemote.apiServer.get('/admin/export/' + dbTable.name, { responseType: 'stream' })
|
|
73
|
+
const stream = response as Stream
|
|
74
|
+
stream.on('data', (data: Uint8Array) => {
|
|
75
|
+
logger.debug('**** DATA ****', data.length)
|
|
76
|
+
const textDecoder = new TextDecoder()
|
|
77
|
+
const chunk = (textDecoder.decode(data))
|
|
78
|
+
writeStream.write(chunk)
|
|
79
|
+
})
|
|
80
|
+
stream.on('end', () => logger.debug('**** DONE ****'))
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
logger.error(err)
|
|
84
|
+
}
|
|
32
85
|
}
|
|
86
|
+
|
|
33
87
|
}
|
|
34
88
|
|
|
35
89
|
class TableImportCmd extends Cmd {
|
|
90
|
+
|
|
36
91
|
constructor() {
|
|
37
92
|
super('import')
|
|
38
93
|
this.description('import from CSV file(s)')
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
94
|
+
.argument('<tables...>', 'table(s) to import')
|
|
95
|
+
.option('-f --file <file>', 'name of the file to import, defaults to <table_name>.csv')
|
|
96
|
+
.option('-d --dir <directory>', 'name of the directory where to find files to import')
|
|
97
|
+
.action(async (tables, options) => this.commandImport(tables, options))
|
|
42
98
|
}
|
|
99
|
+
|
|
43
100
|
async commandImport(tables: string[], options: Options) {
|
|
44
101
|
await this.prologue(options)
|
|
45
102
|
if (this.dbManager === undefined) throw 'dbManager undefined'
|
|
46
|
-
await commandImportExport(this.dbManager, '
|
|
103
|
+
// await commandImportExport(this.dbManager, 'to', tables, options)
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
if (options.file === undefined && options.dir === undefined) throw '--file or --dir mandatory'
|
|
107
|
+
if (options.file !== undefined && options.dir !== undefined) throw '--file and --dir are mutually exclusive'
|
|
108
|
+
if (tables.length === 0) throw 'at least one table name is mandatory'
|
|
109
|
+
if (tables.length > 1 && options.dir === undefined) throw 'for multiple tables, --dir is mandatory'
|
|
110
|
+
|
|
111
|
+
for (let indi = 0; indi < tables.length; indi++) {
|
|
112
|
+
const tableName = tables[indi]
|
|
113
|
+
const dbTable = await this.dbManager.dbTableExist(tableName)
|
|
114
|
+
if (dbTable === undefined) {
|
|
115
|
+
logger.info('table ' + tableName + ' not found')
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
const filename = buildFilename(dbTable.name, options)
|
|
119
|
+
this.copyFrom(dbTable, filename)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
logger.error(err)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async copyFrom(dbTable: DBTable, filename: string) {
|
|
128
|
+
if (this.dbRemote === undefined) throw 'dbRemote undefined'
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const file = fs.createReadStream(filename)
|
|
132
|
+
const form = new FormData()
|
|
133
|
+
form.append('uploadFile', file)
|
|
134
|
+
|
|
135
|
+
const route = '/admin/import/' + dbTable.name
|
|
136
|
+
const resp = await this.dbRemote.apiServer.post(route, form)
|
|
137
|
+
if (resp.status === 200) {
|
|
138
|
+
logger.debug(filename + ' uploaded')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
logger.error(err)
|
|
143
|
+
}
|
|
47
144
|
}
|
|
48
145
|
}
|
|
49
146
|
|
|
147
|
+
|
|
148
|
+
function buildFilename(tableName: string, options: Options): string {
|
|
149
|
+
let filename
|
|
150
|
+
if (options.file !== undefined) {
|
|
151
|
+
if (fs.existsSync(options.file) && fs.lstatSync(options.file).isDirectory())
|
|
152
|
+
throw '--file ' + options.file + ' is a directory'
|
|
153
|
+
filename = options.file
|
|
154
|
+
}
|
|
155
|
+
else if (options.dir === undefined)
|
|
156
|
+
throw '--file and --dir unspecified'
|
|
157
|
+
else if (fs.existsSync(options.dir) === false)
|
|
158
|
+
throw 'directory ' + options.dir + ' does not exist'
|
|
159
|
+
else if (fs.lstatSync(options.dir).isFile()) {
|
|
160
|
+
throw ("--dir " + options.dir + " is a file ")
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
let directory = options.dir.trim()
|
|
164
|
+
if (directory[directory.length - 1] !== '/') {
|
|
165
|
+
directory += '/'
|
|
166
|
+
}
|
|
167
|
+
filename = directory + tableName + '.csv'
|
|
168
|
+
}
|
|
169
|
+
return filename
|
|
170
|
+
}
|
|
171
|
+
|
|
50
172
|
interface Options {
|
|
51
173
|
env: string | undefined
|
|
52
174
|
file: string | undefined
|
|
@@ -69,59 +191,150 @@ const commandImportExport = async (dbManager: DBManager, direction: 'from' | 'to
|
|
|
69
191
|
logger.info('table ' + tableName + ' not found')
|
|
70
192
|
continue
|
|
71
193
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
lcols += ","
|
|
80
|
-
}
|
|
81
|
-
lcols += dbColumn.name
|
|
82
|
-
}
|
|
83
|
-
lcols += ")"
|
|
194
|
+
psqlCmd(dbTable, direction, options)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
logger.error(err)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
84
201
|
|
|
85
|
-
let psql_cmd = "psql -c"
|
|
86
|
-
if (process.env.HEROKU_CLI !== undefined) {
|
|
87
|
-
psql_cmd = process.env.HEROKU_CLI + ' -c '
|
|
88
|
-
}
|
|
89
202
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
filename = options.file
|
|
95
|
-
}
|
|
96
|
-
else if (options.dir === undefined)
|
|
97
|
-
throw '--file and --dir unspecified'
|
|
98
|
-
else if (fs.existsSync(options.dir) === false)
|
|
99
|
-
throw 'directory ' + options.dir + ' does not exist'
|
|
100
|
-
else if (fs.lstatSync(options.dir).isFile()) {
|
|
101
|
-
logger.debug("--dir " + options.dir + " is a file ")
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
let directory = options.dir.trim()
|
|
105
|
-
if (directory[directory.length - 1] !== '/') {
|
|
106
|
-
directory += '/'
|
|
107
|
-
}
|
|
108
|
-
filename = directory + tableName + '.csv'
|
|
109
|
-
}
|
|
203
|
+
//--------------------------------------------------------------------------------------------------------------------
|
|
204
|
+
// function psqlCmd
|
|
205
|
+
// invoke local install of psql CLI
|
|
206
|
+
//--------------------------------------------------------------------------------------------------------------------
|
|
110
207
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
208
|
+
function psqlCmd(dbTable: DBTable, direction: 'from' | 'to', options: Options,) {
|
|
209
|
+
|
|
210
|
+
let lcols: string | undefined
|
|
211
|
+
for (let indj = 0; indj < dbTable.columns.length; indj++) {
|
|
212
|
+
const dbColumn = dbTable.columns[indj]
|
|
213
|
+
if (lcols === undefined) {
|
|
214
|
+
lcols = "("
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
lcols += ","
|
|
119
218
|
}
|
|
219
|
+
lcols += dbColumn.name
|
|
120
220
|
}
|
|
121
|
-
|
|
122
|
-
|
|
221
|
+
lcols += ")"
|
|
222
|
+
|
|
223
|
+
let psql_cmd = "psql -c"
|
|
224
|
+
if (process.env.HEROKU_CLI !== undefined) {
|
|
225
|
+
psql_cmd = process.env.HEROKU_CLI + ' -c '
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let filename
|
|
229
|
+
if (options.file !== undefined) {
|
|
230
|
+
if (fs.existsSync(options.file) && fs.lstatSync(options.file).isDirectory())
|
|
231
|
+
throw '--file ' + options.file + ' is a directory'
|
|
232
|
+
filename = options.file
|
|
233
|
+
}
|
|
234
|
+
else if (options.dir === undefined)
|
|
235
|
+
throw '--file and --dir unspecified'
|
|
236
|
+
else if (fs.existsSync(options.dir) === false)
|
|
237
|
+
throw 'directory ' + options.dir + ' does not exist'
|
|
238
|
+
else if (fs.lstatSync(options.dir).isFile()) {
|
|
239
|
+
logger.debug("--dir " + options.dir + " is a file ")
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
let directory = options.dir.trim()
|
|
243
|
+
if (directory[directory.length - 1] !== '/') {
|
|
244
|
+
directory += '/'
|
|
245
|
+
}
|
|
246
|
+
filename = directory + dbTable.name + '.csv'
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let copy_cmd = "\\copy " + dbTable.name + lcols + direction + " '" + filename + "' csv header"
|
|
250
|
+
const shell_cmd = psql_cmd + '"' + copy_cmd + '"'
|
|
251
|
+
logger.debug(shell_cmd)
|
|
252
|
+
const child = spawn(shell_cmd.trim(), { stdio: 'inherit', shell: true })
|
|
253
|
+
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
//--------------------------------------------------------------------------------------------------------------------
|
|
257
|
+
// class TableList
|
|
258
|
+
//--------------------------------------------------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
class TableList extends Cmd {
|
|
261
|
+
constructor(name: string, description: string) {
|
|
262
|
+
super(name)
|
|
263
|
+
this.description(description)
|
|
264
|
+
.option('-s --sort <columns>', 'sort list by column numbers (begins with 0) separated by comma')
|
|
265
|
+
.action(async (options: any) => {
|
|
266
|
+
await this.commandList(options)
|
|
267
|
+
})
|
|
123
268
|
}
|
|
124
269
|
|
|
270
|
+
async commandList(options: any) {
|
|
271
|
+
await this.prologue(options)
|
|
272
|
+
if (this.dbManager === undefined) throw 'dbManager undefined'
|
|
273
|
+
|
|
274
|
+
const dbClient = new DBClient(this.dbManager)
|
|
275
|
+
dbClient.selectFromTable('List of Tables', 'cyk_table',
|
|
276
|
+
{ fields: 'table_id,table_name,table_description,table_access', sort: options.sort || '1' }
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
//--------------------------------------------------------------------------------------------------------------------
|
|
282
|
+
// class TableQuery
|
|
283
|
+
//--------------------------------------------------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
class TableQuery extends Cmd {
|
|
286
|
+
|
|
287
|
+
constructor(name: string, description: string) {
|
|
288
|
+
super(name)
|
|
289
|
+
this.description(description)
|
|
290
|
+
.argument('<table>', 'table to query')
|
|
291
|
+
.option('--where <clause_where_sql>', 'criteria in SQL syntax')
|
|
292
|
+
.option('-s --sort <columns>', 'sort by columns positions (begins by 0) separated by comma')
|
|
293
|
+
.option('-w --width <colwidth>', 'columns widths separated by comma')
|
|
294
|
+
.action(async (table, options) => {
|
|
295
|
+
this.commandQuery(table, options)
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async commandQuery(table: string, options: any) {
|
|
300
|
+
try {
|
|
301
|
+
await this.prologue(options)
|
|
302
|
+
if (this.dbManager === undefined) throw 'dbManager undefined'
|
|
303
|
+
|
|
304
|
+
const dbTable = await this.dbManager.dbTableExist(table)
|
|
305
|
+
if (dbTable === undefined) throw 'table ' + table + ' not found'
|
|
125
306
|
|
|
307
|
+
let fields = ''
|
|
308
|
+
|
|
309
|
+
for (let ind = 0; ind < dbTable.columns.length; ind++) {
|
|
310
|
+
const dbColumn = dbTable.columns[ind]
|
|
311
|
+
let ok = false
|
|
312
|
+
let fieldName = dbColumn.name
|
|
313
|
+
if (dbColumn.dbType === 'text' || dbColumn.dbType === 'bytea') {
|
|
314
|
+
ok = false
|
|
315
|
+
fieldName = 'length(' + dbColumn.name + ')'
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
ok = true
|
|
319
|
+
}
|
|
320
|
+
if (ok === true) {
|
|
321
|
+
if (fields !== '') fields += ','
|
|
322
|
+
fields += fieldName
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
logger.debug('commandQuery', table)
|
|
327
|
+
const dbClient = new DBClient(this.dbManager)
|
|
328
|
+
let title = 'Table ' + table
|
|
329
|
+
if (options.where !== undefined) {
|
|
330
|
+
title += ' where ' + options.where
|
|
331
|
+
}
|
|
332
|
+
dbClient.selectFromTable(title, table,
|
|
333
|
+
{ fields: fields, width: options.width, sort: options.sort || '1', where: options.where })
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
logger.error(err)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
126
339
|
}
|
|
127
340
|
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { TableCommand } from './TableCommand'
|
|
|
8
8
|
import { TestCommand } from './TestCommand'
|
|
9
9
|
import { RunCommand } from './RunCommand'
|
|
10
10
|
import { InitCommand } from './InitCommand'
|
|
11
|
+
import { AssetCommand } from './AssetCommand'
|
|
11
12
|
const logger = loglevel.getLogger("index.ts")
|
|
12
13
|
logger.setLevel("debug")
|
|
13
14
|
|
|
@@ -16,6 +17,8 @@ program.name('cyk').description('cyklang CLI')
|
|
|
16
17
|
.version('0.2')
|
|
17
18
|
|
|
18
19
|
program.addCommand(new InitCommand('init'))
|
|
20
|
+
program.addCommand(new AssetCommand('asset', 'manage assets'))
|
|
21
|
+
program.addCommand(new AssetCommand('a', 'manage assets'))
|
|
19
22
|
program.addCommand(new ModuleCommand('module'))
|
|
20
23
|
program.addCommand(new ModuleCommand('m'))
|
|
21
24
|
program.addCommand(new UserCommand('user'))
|
package/src/index.xml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml-model href="https://www.cyklang.net/cyklang.xsd" ?>
|
|
2
|
+
<module>
|
|
3
|
+
<showform>
|
|
4
|
+
<form title="Homepage">
|
|
5
|
+
<string name="title">"Welcome to your new application"</string>
|
|
6
|
+
<string name="subtitle">"Your server and database have been successfully initialized !"</string>
|
|
7
|
+
</form>
|
|
8
|
+
</showform>
|
|
9
|
+
|
|
10
|
+
</module>
|