@cyklang/cli 0.1.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/README.md +1 -0
- package/aws-ecs.env +38 -0
- package/build/Cmd.d.ts +7 -0
- package/build/Cmd.js +35 -0
- package/build/DBClient.d.ts +10 -0
- package/build/DBClient.js +205 -0
- package/build/InitCommand.d.ts +9 -0
- package/build/InitCommand.js +122 -0
- package/build/ModuleCommand.d.ts +5 -0
- package/build/ModuleCommand.js +202 -0
- package/build/NodeCrypto.d.ts +5 -0
- package/build/NodeCrypto.js +49 -0
- package/build/QueryCommand.d.ts +5 -0
- package/build/QueryCommand.js +62 -0
- package/build/RunCommand.d.ts +5 -0
- package/build/RunCommand.js +112 -0
- package/build/TableCommand.d.ts +5 -0
- package/build/TableCommand.js +140 -0
- package/build/TestCommand copy.d.ts +5 -0
- package/build/TestCommand copy.js +111 -0
- package/build/TestCommand.d.ts +5 -0
- package/build/TestCommand.js +111 -0
- package/build/UserCommand.d.ts +5 -0
- package/build/UserCommand.js +202 -0
- package/build/console.d.ts +13 -0
- package/build/console.js +109 -0
- package/build/cykquery.d.ts +2 -0
- package/build/cykquery.js +145 -0
- package/build/cyktable.d.ts +2 -0
- package/build/cyktable.js +133 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +32 -0
- package/build/module.d.ts +2 -0
- package/build/module.js +290 -0
- package/docker-compose.ecs.yml +44 -0
- package/docker-compose.yml +43 -0
- package/docker.env +38 -0
- package/package.json +38 -0
- package/src/Cmd.ts +33 -0
- package/src/DBClient.ts +220 -0
- package/src/InitCommand.ts +107 -0
- package/src/ModuleCommand.ts +192 -0
- package/src/NodeCrypto.ts +60 -0
- package/src/QueryCommand.ts +62 -0
- package/src/RunCommand.ts +98 -0
- package/src/TableCommand.ts +127 -0
- package/src/TestCommand.ts +98 -0
- package/src/UserCommand.ts +203 -0
- package/src/console.ts +97 -0
- package/src/index.ts +30 -0
- package/tsconfig.json +12 -0
package/src/DBClient.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { BasicType, DBColumn, DBExecuteRequest, DBManager, DBTable, ObjectData, parseXML, PrimitiveData } from "@cyklang/core";
|
|
2
|
+
import loglevel from "loglevel";
|
|
3
|
+
const logger = loglevel.getLogger('DBClient.ts')
|
|
4
|
+
logger.setLevel('debug')
|
|
5
|
+
// DBManager wrapper class
|
|
6
|
+
|
|
7
|
+
export class DBClient {
|
|
8
|
+
dbManager: DBManager
|
|
9
|
+
constructor(dbManager: DBManager) {
|
|
10
|
+
this.dbManager = dbManager
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async selectFromTable(title: string, tableName: string, options:
|
|
14
|
+
{ fields: string, sort?: string | undefined, where?: string | undefined }) {
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
|
|
18
|
+
const objectDataType = this.dbManager.scope.structure.objectDataType;
|
|
19
|
+
const stringDataType = this.dbManager.scope.structure.stringDataType
|
|
20
|
+
const request = new DBExecuteRequest()
|
|
21
|
+
request.selectFromTable = tableName
|
|
22
|
+
|
|
23
|
+
request.parameters.addVariable('fields', stringDataType).data = new PrimitiveData(stringDataType, options.fields)
|
|
24
|
+
request.parameters.addVariable('where', stringDataType).data = new PrimitiveData(stringDataType, options.where)
|
|
25
|
+
if (options.sort !== undefined) {
|
|
26
|
+
const colnames = options.fields.split(',')
|
|
27
|
+
const sortcols = options.sort.split(',')
|
|
28
|
+
let order_by = ''
|
|
29
|
+
for (let ind = 0; ind < sortcols.length; ind++ ) {
|
|
30
|
+
const coli = sortcols[ind]
|
|
31
|
+
const colname = colnames[Number(coli)]
|
|
32
|
+
if (order_by !== '') order_by += ','
|
|
33
|
+
order_by += colname
|
|
34
|
+
}
|
|
35
|
+
logger.debug('order_by '+order_by)
|
|
36
|
+
request.parameters.addVariable('order_by', stringDataType).data = new PrimitiveData(stringDataType, order_by)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// if (options !== undefined) {
|
|
40
|
+
// Object.entries(options).forEach(([key, value]) => {
|
|
41
|
+
// const data = new PrimitiveData(stringDataType, value)
|
|
42
|
+
// request.parameters.addVariable(key, stringDataType).data = data
|
|
43
|
+
// })
|
|
44
|
+
// }
|
|
45
|
+
|
|
46
|
+
const xmlResult = await this.dbManager.dbExecute(request)
|
|
47
|
+
if (xmlResult === undefined) throw 'xmlResult undefined'
|
|
48
|
+
const tagXmlResult = parseXML('xmlResult', xmlResult)
|
|
49
|
+
|
|
50
|
+
const objXmlResult = (await objectDataType.parseData(tagXmlResult, this.dbManager.scope)) as ObjectData
|
|
51
|
+
const objectMeta = (objXmlResult.variables.getData('meta')) as ObjectData
|
|
52
|
+
if (objectMeta === undefined) throw 'objectMeta not found'
|
|
53
|
+
const xmlMeta = objectMeta.variables.getString(tableName)
|
|
54
|
+
if (xmlMeta === undefined) throw 'meta does not have ' + tableName + ' description'
|
|
55
|
+
|
|
56
|
+
const tagDBTable = parseXML('dbTable', xmlMeta)
|
|
57
|
+
const dbTable = new DBTable(tagDBTable)
|
|
58
|
+
|
|
59
|
+
const fields = options.fields.split(',')
|
|
60
|
+
const list = new List()
|
|
61
|
+
|
|
62
|
+
for (let ind = 0; ind < fields.length; ind++) {
|
|
63
|
+
const field = fields[ind]
|
|
64
|
+
const dbColumn = dbTable.columns.filter((dbColumn) => dbColumn.name === field)[0]
|
|
65
|
+
if (dbColumn === undefined) throw 'field ' + field + ' not found'
|
|
66
|
+
list.addDBColumn(dbColumn)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const objDataset = objXmlResult.variables.getData(tableName) as ObjectData
|
|
70
|
+
for (let ind = 0; ind < objDataset.variables.length(); ind++) {
|
|
71
|
+
const record = (objDataset.variables.at(ind)?.data as ObjectData)
|
|
72
|
+
list.addObjectData(record)
|
|
73
|
+
}
|
|
74
|
+
logger.info(list.renderList(title))
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
logger.error(err)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class List {
|
|
84
|
+
columns: Column[] = []
|
|
85
|
+
rows: Row[] = []
|
|
86
|
+
lines: string[] = []
|
|
87
|
+
currentLine: string = ''
|
|
88
|
+
|
|
89
|
+
addDBColumn(dbColumn: DBColumn) {
|
|
90
|
+
const col = new Column(dbColumn.name)
|
|
91
|
+
col.label = dbColumn.label || dbColumn.name
|
|
92
|
+
col.dbColumn = dbColumn
|
|
93
|
+
this.columns.push(col)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
addObjectData(objectData: ObjectData) {
|
|
97
|
+
const cells: BasicType[] = []
|
|
98
|
+
this.columns.forEach((column) => {
|
|
99
|
+
let cell: BasicType = undefined
|
|
100
|
+
const data = objectData.variables.getData(column.name)
|
|
101
|
+
if (data !== undefined && data !== null) {
|
|
102
|
+
cell = (data as PrimitiveData).value
|
|
103
|
+
}
|
|
104
|
+
cells.push(cell)
|
|
105
|
+
})
|
|
106
|
+
this.rows.push(new Row(cells))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
renderList(title: string): string {
|
|
110
|
+
// calculate column.width
|
|
111
|
+
let total_width = 0
|
|
112
|
+
for (let indi = 0; indi < this.columns.length; indi++) {
|
|
113
|
+
const column = this.columns[indi]
|
|
114
|
+
column.width = column.label.length
|
|
115
|
+
this.rows.forEach((row) => {
|
|
116
|
+
const cell = row.cells[indi]
|
|
117
|
+
const renderedCell = this.renderCell(cell)
|
|
118
|
+
if (renderedCell.length > column.width) {
|
|
119
|
+
column.width = renderedCell.length
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
if (total_width > 0) total_width += 3
|
|
123
|
+
total_width += column.width
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// title
|
|
127
|
+
this.lines.push(this.renderCell(title, total_width, 'center'))
|
|
128
|
+
|
|
129
|
+
// header
|
|
130
|
+
this.lines.push(this.renderHeaderRow())
|
|
131
|
+
this.lines.push(this.renderSeparator())
|
|
132
|
+
|
|
133
|
+
// rows
|
|
134
|
+
for (let indj = 0; indj < this.rows.length; indj++ ) {
|
|
135
|
+
const row = this.rows[indj]
|
|
136
|
+
this.lines.push(this.renderRow(row))
|
|
137
|
+
}
|
|
138
|
+
return this.lines.join('\n')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
renderHeaderRow(): string {
|
|
142
|
+
let result = ''
|
|
143
|
+
for (let indi = 0; indi < this.columns.length; indi++ ) {
|
|
144
|
+
const column = this.columns[indi]
|
|
145
|
+
if (result !== '') result += ' | '
|
|
146
|
+
result += this.renderCell(column.label, column.width, 'center')
|
|
147
|
+
}
|
|
148
|
+
return result
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
renderRow(row: Row): string {
|
|
152
|
+
let result = ''
|
|
153
|
+
for (let indi = 0; indi < this.columns.length; indi++ ) {
|
|
154
|
+
const column = this.columns[indi]
|
|
155
|
+
let justify: 'left' | 'right' | 'center' | undefined = 'left'
|
|
156
|
+
if (column.dbColumn?.dataTypeName === 'number' || column?.type === 'number') {
|
|
157
|
+
justify = 'right'
|
|
158
|
+
}
|
|
159
|
+
if (result !== '') result += ' | '
|
|
160
|
+
result += this.renderCell(row.cells[indi], column.width, justify)
|
|
161
|
+
}
|
|
162
|
+
return result
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
renderSeparator(): string {
|
|
166
|
+
let result = ''
|
|
167
|
+
for ( let indi = 0; indi < this.columns.length; indi++ ) {
|
|
168
|
+
const column = this.columns[indi]
|
|
169
|
+
if (result !== '') result += '-+-'
|
|
170
|
+
result += '-'.repeat(column.width)
|
|
171
|
+
}
|
|
172
|
+
return result
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
renderCell(value: BasicType, width?: number | undefined, justify?: 'left' | 'right' | 'center' | undefined): string {
|
|
176
|
+
let result: string
|
|
177
|
+
if (value === undefined) {
|
|
178
|
+
result = ''
|
|
179
|
+
}
|
|
180
|
+
else if (value === null) {
|
|
181
|
+
result = 'null'
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
result = value.toString()
|
|
185
|
+
}
|
|
186
|
+
if (width !== undefined && result.length < width) {
|
|
187
|
+
if (justify === undefined || justify === 'left') {
|
|
188
|
+
result += ' '.repeat(width - result.length)
|
|
189
|
+
}
|
|
190
|
+
else if (justify === 'right') {
|
|
191
|
+
result = ' '.repeat(width - result.length) + result
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// justify === center
|
|
195
|
+
const before = Math.floor((width - result.length) / 2)
|
|
196
|
+
result = ' '.repeat(before) + result + ' '.repeat(width - result.length - before)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return result
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class Column {
|
|
204
|
+
name: string;
|
|
205
|
+
label: string = '';
|
|
206
|
+
dbColumn: DBColumn | undefined;
|
|
207
|
+
type?: string | undefined;
|
|
208
|
+
computed?: string | undefined;
|
|
209
|
+
width: number = 0
|
|
210
|
+
constructor(name: string) {
|
|
211
|
+
this.name = name
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
class Row {
|
|
216
|
+
cells: BasicType[]
|
|
217
|
+
constructor(cells: BasicType[]) {
|
|
218
|
+
this.cells = cells
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import * as fs from 'fs'
|
|
3
|
+
import { getLogger } from "loglevel";
|
|
4
|
+
const logger = getLogger('InitCommand.ts')
|
|
5
|
+
|
|
6
|
+
logger.setLevel('debug')
|
|
7
|
+
|
|
8
|
+
export class InitCommand extends Command {
|
|
9
|
+
constructor(name: string) {
|
|
10
|
+
super(name)
|
|
11
|
+
this.description('initialize a folder with files necessary for a cyklang project')
|
|
12
|
+
.action(async (options: any) => {
|
|
13
|
+
await this.commandInit(options)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
async commandInit(options: any) {
|
|
17
|
+
try {
|
|
18
|
+
await this.writeEnvFile()
|
|
19
|
+
await this.writeDockerComposeFile()
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
logger.error(err)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// developer.mozilla.org Math.random()
|
|
27
|
+
// On renvoie un entier aléatoire entre une valeur min (incluse)
|
|
28
|
+
// et une valeur max (exclue).
|
|
29
|
+
// Attention : si on utilisait Math.round(), on aurait une distribution
|
|
30
|
+
// non uniforme !
|
|
31
|
+
|
|
32
|
+
getRandomInt(min: number, max: number) {
|
|
33
|
+
min = Math.ceil(min);
|
|
34
|
+
max = Math.floor(max);
|
|
35
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
generateToken(): string {
|
|
39
|
+
let result = ''
|
|
40
|
+
for (let ind = 0; ind < 32; ind++ ) {
|
|
41
|
+
result += this.getRandomInt(0,15).toString(16)
|
|
42
|
+
}
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async writeEnvFile() {
|
|
47
|
+
const envFilename = '.env'
|
|
48
|
+
if (fs.existsSync(envFilename) === true) {
|
|
49
|
+
logger.info(envFilename + ' file already exists. Remove it before launching init command')
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
Math.random()
|
|
53
|
+
const content = `# PostgreSql connection
|
|
54
|
+
# used by psql command line utility
|
|
55
|
+
PGHOST="localhost"
|
|
56
|
+
PGPORT="4321"
|
|
57
|
+
PGUSER="postgres"
|
|
58
|
+
PGPASSWORD="postgres"
|
|
59
|
+
|
|
60
|
+
# at postgresql database initialization
|
|
61
|
+
|
|
62
|
+
# POSTGRES_USER="postgres"
|
|
63
|
+
POSTGRES_PASSWORD="postgres"
|
|
64
|
+
|
|
65
|
+
# admin user
|
|
66
|
+
# created at database initialization
|
|
67
|
+
ADMIN_NAME=cyk
|
|
68
|
+
ADMIN_PASSWORD=cyk
|
|
69
|
+
ADMIN_EMAIL=my-email@my-domain
|
|
70
|
+
|
|
71
|
+
# JWT token generation
|
|
72
|
+
# e.g. https://www.allkeysgenerator.com
|
|
73
|
+
# 128 bits Hex
|
|
74
|
+
|
|
75
|
+
SECRET_TOKEN="${this.generateToken()}"
|
|
76
|
+
SECRET_RTOKEN="${this.generateToken()}"
|
|
77
|
+
|
|
78
|
+
# Login Page Config
|
|
79
|
+
LOGIN_IMAGE="https://media.smartbox.com/pim/1000001967971201770883.jpg?thumbor=800x0/filters:quality(90)"
|
|
80
|
+
LOGIN_TITLE="CYK Framework"
|
|
81
|
+
LOGIN_SUBTITLE="Une application pilote du framework CYK"
|
|
82
|
+
LOGIN_APPLI="accounting"
|
|
83
|
+
|
|
84
|
+
# credential used by cyk CLI
|
|
85
|
+
|
|
86
|
+
USER_NAME=cyk
|
|
87
|
+
USER_PASSWORD=cyk
|
|
88
|
+
CYK_PORT="3000"
|
|
89
|
+
DBREMOTE_URL="http://localhost:3000"
|
|
90
|
+
`
|
|
91
|
+
fs.writeFileSync(envFilename, content)
|
|
92
|
+
logger.debug('created ' + envFilename)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async writeDockerComposeFile() {
|
|
96
|
+
const dockerComposeFilename = 'docker-compose.yml'
|
|
97
|
+
if (fs.existsSync(dockerComposeFilename) === true) {
|
|
98
|
+
logger.info(dockerComposeFilename + ' already exists. Remove it before launching init command')
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const modelFilename = __dirname + '/../' + dockerComposeFilename
|
|
103
|
+
const content = fs.readFileSync(modelFilename)
|
|
104
|
+
fs.writeFileSync(dockerComposeFilename, content)
|
|
105
|
+
logger.debug('created ' + dockerComposeFilename)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
import { DBModule, parseXML } from "@cyklang/core"
|
|
3
|
+
import * as fs from "fs"
|
|
4
|
+
import loglevel from 'loglevel'
|
|
5
|
+
import path from "path"
|
|
6
|
+
import { Command } from 'commander'
|
|
7
|
+
import { Cmd } from "./Cmd"
|
|
8
|
+
import { DBClient } from "./DBClient"
|
|
9
|
+
const logger = loglevel.getLogger("ModuleCommand.ts")
|
|
10
|
+
logger.setLevel("debug")
|
|
11
|
+
|
|
12
|
+
export class ModuleCommand extends Command {
|
|
13
|
+
constructor(name: string) {
|
|
14
|
+
super(name)
|
|
15
|
+
this.description('manage modules')
|
|
16
|
+
// upload
|
|
17
|
+
this.addCommand(new ModuleUpload('upload', `Upload local module file(s) to the database.
|
|
18
|
+
Module name is the base name of the file. If a module with the same name already exists, it will be updated.
|
|
19
|
+
Otherwise, a new module is inserted in the database. To rename a module, use option --id to indicate the module you want to rename`)
|
|
20
|
+
)
|
|
21
|
+
this.addCommand(new ModuleUpload('u', 'shortcut for (u)pload'))
|
|
22
|
+
// download
|
|
23
|
+
this.addCommand(new ModuleDownload('download', 'download module files to the current directory'))
|
|
24
|
+
this.addCommand(new ModuleDownload('d', 'shortcut for (d)ownload'))
|
|
25
|
+
// list
|
|
26
|
+
this.addCommand(new ModuleList('list', 'List modules'))
|
|
27
|
+
this.addCommand(new ModuleList('l', 'shortcut for (l)ist'))
|
|
28
|
+
// rename
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getModuleDBName(filePath: string): string {
|
|
34
|
+
let result
|
|
35
|
+
if (filePath.endsWith('.xml') === false) throw filePath + ' does not have .xml extension'
|
|
36
|
+
result = path.basename(filePath, '.xml')
|
|
37
|
+
const identifierRegex = /^[_a-zA-Z][_a-zA-Z0-9]*$/
|
|
38
|
+
if (result.match(identifierRegex) === null) throw 'filename ' + result + ' does not match identifier syntax'
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
interface UploadOptions {
|
|
42
|
+
id: string | undefined
|
|
43
|
+
env: string | undefined
|
|
44
|
+
}
|
|
45
|
+
class ModuleUpload extends Cmd {
|
|
46
|
+
constructor(name: string, description: string) {
|
|
47
|
+
super(name)
|
|
48
|
+
this.description(description)
|
|
49
|
+
.option('-i --id <id>', 'module ID to download')
|
|
50
|
+
.argument('[files...]', 'local module file(s) to upload to the server')
|
|
51
|
+
.action(async (files: any, options: any) => {
|
|
52
|
+
await this.commandUpload(files, options)
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
async commandUpload(files: string[], options: UploadOptions) {
|
|
56
|
+
try {
|
|
57
|
+
await this.prologue(options)
|
|
58
|
+
if (this.dbManager === undefined) throw 'dbManager undefined'
|
|
59
|
+
|
|
60
|
+
if (files.length === 0) throw 'file(s) argument is missing'
|
|
61
|
+
if (options.id !== undefined) {
|
|
62
|
+
if (files.length > 1) throw 'with --id option you can only specify 1 local file'
|
|
63
|
+
const dbModule = await this.dbManager.getModule(options.id)
|
|
64
|
+
dbModule.source = fs.readFileSync(files[0]).toString()
|
|
65
|
+
dbModule.dbname = getModuleDBName(files[0])
|
|
66
|
+
const tag = parseXML(dbModule.dbname, dbModule.source)
|
|
67
|
+
dbModule.access = tag.attributes.ACCESS
|
|
68
|
+
dbModule.description = tag.attributes.DESCRIPTION
|
|
69
|
+
await this.dbManager.dbModuleUpdate(dbModule)
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
for (let ind = 0; ind < files.length; ind++) {
|
|
73
|
+
const file = files[ind]
|
|
74
|
+
const dbname = getModuleDBName(file)
|
|
75
|
+
const source = fs.readFileSync(file).toString()
|
|
76
|
+
const tag = parseXML(dbname, source)
|
|
77
|
+
const dbModuleExist = await this.dbManager.dbModuleExist(dbname)
|
|
78
|
+
if (dbModuleExist === undefined) {
|
|
79
|
+
// insert
|
|
80
|
+
const dbModule = new DBModule()
|
|
81
|
+
dbModule.source = source
|
|
82
|
+
dbModule.dbname = dbname
|
|
83
|
+
dbModule.access = tag.attributes.ACCESS
|
|
84
|
+
dbModule.description = tag.attributes.DESCRIPTION
|
|
85
|
+
logger.debug('module insert ' + dbname + ' access ' + dbModule.access + ' description ' + dbModule.description)
|
|
86
|
+
const id = await this.dbManager.dbModuleInsert(dbModule)
|
|
87
|
+
logger.info('module ' + dbname + ' added with ID ' + id)
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// update
|
|
91
|
+
dbModuleExist.access = tag.attributes.ACCESS
|
|
92
|
+
dbModuleExist.description = tag.attributes.DESCRIPTION
|
|
93
|
+
logger.debug('module update ' + dbname + ' access ' + dbModuleExist.access + ' description ' + dbModuleExist.description)
|
|
94
|
+
await this.dbManager.dbModuleUpdate(dbModuleExist)
|
|
95
|
+
logger.info('module ' + dbname + ' updated')
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
logger.error(err)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface DownloadOptions {
|
|
107
|
+
id: string | undefined
|
|
108
|
+
env: string | undefined
|
|
109
|
+
all: string | undefined
|
|
110
|
+
}
|
|
111
|
+
class ModuleDownload extends Cmd {
|
|
112
|
+
constructor(name: string, description: string) {
|
|
113
|
+
super(name)
|
|
114
|
+
this.description(description)
|
|
115
|
+
.option('-i --id <id>', 'module ID')
|
|
116
|
+
.option('-a --all', 'download all the modules')
|
|
117
|
+
.argument('[files...]', 'module(s) name(s) are derived as the basename(s) of the file(s) path')
|
|
118
|
+
.action(async (files: string[], options: DownloadOptions) => {
|
|
119
|
+
await this.commandDownload(files, options)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
async commandDownload(files: string[], options: DownloadOptions) {
|
|
123
|
+
try {
|
|
124
|
+
await this.prologue(options)
|
|
125
|
+
if (this.dbManager === undefined) throw 'dbManager undefined'
|
|
126
|
+
|
|
127
|
+
if (options.id !== undefined) {
|
|
128
|
+
const dbModule = await this.dbManager.getModule(options.id)
|
|
129
|
+
await this.downloadModule(dbModule.dbname || '', dbModule)
|
|
130
|
+
}
|
|
131
|
+
else if (options.all !== undefined) {
|
|
132
|
+
const modules = await this.dbManager.getModules(undefined)
|
|
133
|
+
for (let ind = 0; ind < modules.length; ind++) {
|
|
134
|
+
const dbModule = modules[ind]
|
|
135
|
+
await this.downloadModule(dbModule.dbname || '', dbModule)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
for (let ind = 0; ind < files.length; ind++) {
|
|
140
|
+
await this.downloadModule(getModuleDBName(files[ind]))
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
logger.error(err)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async downloadModule(dbname: string, dbModule?: DBModule) {
|
|
151
|
+
logger.debug('downloadModule ' + dbname)
|
|
152
|
+
if (dbModule === undefined) {
|
|
153
|
+
dbModule = await this.dbManager?.dbModuleExist(dbname)
|
|
154
|
+
if (dbModule === undefined) throw 'module not found : ' + dbname
|
|
155
|
+
}
|
|
156
|
+
const filePath = dbname + '.xml'
|
|
157
|
+
if (fs.existsSync(filePath) === true) {
|
|
158
|
+
logger.info('file ' + filePath + ' already exists and will not be overwritten ')
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
fs.writeFileSync(filePath, dbModule.source || '')
|
|
162
|
+
logger.info('module ' + dbname + ' has been downloaded and file ' + filePath + ' has been created')
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class ModuleList extends Cmd {
|
|
168
|
+
constructor(name: string, description: string) {
|
|
169
|
+
super(name)
|
|
170
|
+
this.description(description)
|
|
171
|
+
.option('-s --sort <columns>', 'sort list by column numbers (begins by 0) separated by comma')
|
|
172
|
+
.action(async (options: any) => {
|
|
173
|
+
await this.commandList(options)
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async commandList(options: any) {
|
|
178
|
+
try {
|
|
179
|
+
await this.prologue(options)
|
|
180
|
+
if (this.dbManager === undefined) throw 'dbManager undefined'
|
|
181
|
+
|
|
182
|
+
const dbClient = new DBClient(this.dbManager)
|
|
183
|
+
dbClient.selectFromTable('List of Modules', 'cyk_module',
|
|
184
|
+
{ fields: 'module_id,module_dbname,module_access,module_description', sort: options.sort || '1' })
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
logger.error(err)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'
|
|
2
|
+
import { getLogger } from 'loglevel'
|
|
3
|
+
import { CryptoEngine } from '@cyklang/core'
|
|
4
|
+
import { cipherKey } from '@cyklang/core'
|
|
5
|
+
import pako from 'pako'
|
|
6
|
+
import { TextDecoder } from 'util'
|
|
7
|
+
const logger = getLogger('NodeCrypto.ts')
|
|
8
|
+
logger.setLevel('debug')
|
|
9
|
+
|
|
10
|
+
const algorithm = 'aes-128-cbc'
|
|
11
|
+
const iv_length = 16
|
|
12
|
+
|
|
13
|
+
export class NodeCrypto implements CryptoEngine {
|
|
14
|
+
|
|
15
|
+
// last_iv: Buffer | undefined
|
|
16
|
+
|
|
17
|
+
async encrypt(plain: string): Promise<string> {
|
|
18
|
+
|
|
19
|
+
const deflatedData = pako.deflate(plain)
|
|
20
|
+
// logger.debug('deflatedData : ', deflatedData[0], deflatedData[1])
|
|
21
|
+
const iv: Buffer = randomBytes(iv_length)
|
|
22
|
+
// this.last_iv = iv
|
|
23
|
+
|
|
24
|
+
// logger.debug("encrypt iv " + iv.toString('hex'))
|
|
25
|
+
|
|
26
|
+
const cipher = createCipheriv(algorithm, cipherKey, iv)
|
|
27
|
+
let result = cipher.update(deflatedData, undefined, "hex")
|
|
28
|
+
result += cipher.final("hex")
|
|
29
|
+
result += iv.toString('hex')
|
|
30
|
+
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async decrypt(encoded: string): Promise<string> {
|
|
35
|
+
|
|
36
|
+
const iv_hex = encoded.substring(encoded.length - 2 * iv_length)
|
|
37
|
+
const encryptedData = encoded.substring(0, encoded.length - 2 * iv_length)
|
|
38
|
+
|
|
39
|
+
// logger.debug("decrypt iv " + iv_hex)
|
|
40
|
+
const iv = Buffer.from(iv_hex, 'hex')
|
|
41
|
+
|
|
42
|
+
// if (this.last_iv === undefined) throw 'last_iv undefined'
|
|
43
|
+
// if (Buffer.compare(iv, this.last_iv) !== 0) throw 'iv and last_iv are different'
|
|
44
|
+
|
|
45
|
+
const decipher = createDecipheriv(algorithm, cipherKey, iv)
|
|
46
|
+
let deflatedData1 = decipher.update(encryptedData, 'hex')
|
|
47
|
+
|
|
48
|
+
const deflatedData2 = decipher.final()
|
|
49
|
+
const deflatedData = Buffer.concat([deflatedData1, deflatedData2])
|
|
50
|
+
|
|
51
|
+
let inflatedData = deflatedData
|
|
52
|
+
if (deflatedData[0] === 0x78 && deflatedData[1] === 0x9c) {
|
|
53
|
+
inflatedData = Buffer.from(pako.inflate(deflatedData))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result = (new TextDecoder()).decode(inflatedData)
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
import { Script, Structure, DBRemote, DBManager, DBQueryInstruction, parseXML, Tag, Scope } from "@cyklang/core"
|
|
3
|
+
import * as fs from "fs"
|
|
4
|
+
import loglevel from 'loglevel'
|
|
5
|
+
import { Cmd } from './Cmd'
|
|
6
|
+
import { Command } from 'commander'
|
|
7
|
+
const logger = loglevel.getLogger("QueryCommand.ts")
|
|
8
|
+
logger.setLevel("debug")
|
|
9
|
+
|
|
10
|
+
export class QueryCommand extends Command {
|
|
11
|
+
constructor(name: string) {
|
|
12
|
+
super(name)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseXmlFile(filepath: string, basename: string, xmlSource: string): Tag {
|
|
17
|
+
const tag = parseXML(filepath, xmlSource)
|
|
18
|
+
if (tag.name !== 'DB.QUERY') {
|
|
19
|
+
throw Error('tag ' + tag.name + ', should be <db.query>')
|
|
20
|
+
}
|
|
21
|
+
const name = tag.attributes.NAME
|
|
22
|
+
if (name === undefined) {
|
|
23
|
+
throw Error('name attribute is missing')
|
|
24
|
+
}
|
|
25
|
+
if (basename !== name + '.xml') {
|
|
26
|
+
throw Error('filename ' + filepath + ' and name ' + name + ' do not correspond')
|
|
27
|
+
}
|
|
28
|
+
return tag
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function executeXml(structure: Structure, tag: Tag, xmlSource: string) {
|
|
32
|
+
try {
|
|
33
|
+
logger.debug('executeXml ' + tag.attributes.NAME)
|
|
34
|
+
// structure.setInstructionType("print", new PrintInstructionType(logfilename))
|
|
35
|
+
const dbQueryInstructionType = structure.scope.getInstructionType('db.query')
|
|
36
|
+
if (dbQueryInstructionType === undefined) {
|
|
37
|
+
throw Error('dbQueryInstructionType not found')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const scopeParse = new Scope(structure, structure.scope, undefined)
|
|
41
|
+
const dbQueryInst = <DBQueryInstruction>await dbQueryInstructionType.parseInstruction(tag, scopeParse)
|
|
42
|
+
dbQueryInst.source = xmlSource
|
|
43
|
+
const scopeExecute = new Scope(structure, structure.scope, undefined)
|
|
44
|
+
await dbQueryInst.execute(scopeExecute)
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
logger.error(err)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function testQuery(structure: Structure, queryName: string) {
|
|
52
|
+
const xmlScript = `
|
|
53
|
+
<block>
|
|
54
|
+
<object name='result'/>
|
|
55
|
+
<db.execute query='${queryName}' result='result' />
|
|
56
|
+
<print>string(result)</print>
|
|
57
|
+
</block>
|
|
58
|
+
`
|
|
59
|
+
const script = new Script(structure.scope, 'test', xmlScript)
|
|
60
|
+
await script.parseInstructions()
|
|
61
|
+
await script.execute()
|
|
62
|
+
}
|