@agung_dhewe/webapps 1.1.2

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.
Files changed (130) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +2 -0
  3. package/jsconfig.json +10 -0
  4. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.css +2 -0
  5. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js +11 -0
  6. package/libs/fgta5js-dist/fgta5js-v1.8.3.min.js.map +1 -0
  7. package/libs/fgta5js-dist/fonts/karla-italic-latin-ext.woff2 +0 -0
  8. package/libs/fgta5js-dist/fonts/karla-italic-latin.woff2 +0 -0
  9. package/libs/fgta5js-dist/fonts/karla-normal-latin-ext.woff2 +0 -0
  10. package/libs/fgta5js-dist/fonts/karla-normal-latin.woff2 +0 -0
  11. package/libs/fgta5js-dist/fonts/karla.css +142 -0
  12. package/libs/webmodule/module-edit.css +163 -0
  13. package/libs/webmodule/module-footer.css +22 -0
  14. package/libs/webmodule/module-list.css +25 -0
  15. package/libs/webmodule/module.css +52 -0
  16. package/libs/webmodule/module.js +195 -0
  17. package/libs/webmodule/pagehelper.mjs +45 -0
  18. package/modules/generator/appgen-components.mjs +142 -0
  19. package/modules/generator/appgen-icons.mjs +6 -0
  20. package/modules/generator/appgen-io.mjs +784 -0
  21. package/modules/generator/appgen-ui-search.mjs +173 -0
  22. package/modules/generator/appgen-ui-unique.mjs +153 -0
  23. package/modules/generator/appgen-ui.mjs +1181 -0
  24. package/modules/generator/generator-context.mjs +18 -0
  25. package/modules/generator/generator-designtemplate.html +1508 -0
  26. package/modules/generator/generator-ext.html +0 -0
  27. package/modules/generator/generator-ext.mjs +3 -0
  28. package/modules/generator/generator.css +642 -0
  29. package/modules/generator/generator.mjs +195 -0
  30. package/modules/generator/generator.png +0 -0
  31. package/modules/generator/generatorEdit.html +185 -0
  32. package/modules/generator/generatorEdit.mjs +238 -0
  33. package/modules/generator/generatorList.html +32 -0
  34. package/modules/generator/generatorList.mjs +243 -0
  35. package/modules/login/login.css +11 -0
  36. package/modules/login/login.html +12 -0
  37. package/modules/login/login.mjs +111 -0
  38. package/package.json +46 -0
  39. package/percobaan/simmpan-ke-minio.js +24 -0
  40. package/src/api.js +80 -0
  41. package/src/apis/generator.api.js +226 -0
  42. package/src/apis/login.api.js +109 -0
  43. package/src/bucket.js +24 -0
  44. package/src/context.js +26 -0
  45. package/src/datalog.sql +22 -0
  46. package/src/datarecords.js +0 -0
  47. package/src/db.js +61 -0
  48. package/src/generator/createApiExtenderModule.js +54 -0
  49. package/src/generator/createApiModule.js +218 -0
  50. package/src/generator/createIcon.js +62 -0
  51. package/src/generator/createInfoAboutExtender.js +42 -0
  52. package/src/generator/createInfoLogs.js +41 -0
  53. package/src/generator/createInfoRecordExtender.js +41 -0
  54. package/src/generator/createModuleContext.js +48 -0
  55. package/src/generator/createModuleDetilEditHtml.js +110 -0
  56. package/src/generator/createModuleDetilEditMjs.js +172 -0
  57. package/src/generator/createModuleDetilListHtml.js +146 -0
  58. package/src/generator/createModuleDetilListMjs.js +73 -0
  59. package/src/generator/createModuleEjs.js +51 -0
  60. package/src/generator/createModuleExtenderHtml.js +43 -0
  61. package/src/generator/createModuleExtenderMjs.js +43 -0
  62. package/src/generator/createModuleHeaderEditHtml.js +148 -0
  63. package/src/generator/createModuleHeaderEditMjs.js +197 -0
  64. package/src/generator/createModuleHeaderListHtml.js +144 -0
  65. package/src/generator/createModuleHeaderListMjs.js +67 -0
  66. package/src/generator/createModuleMjs.js +67 -0
  67. package/src/generator/createModuleRollup.js +42 -0
  68. package/src/generator/createProgramData.js +96 -0
  69. package/src/generator/createTable.js +156 -0
  70. package/src/generator/ddl.js +475 -0
  71. package/src/generator/helper.js +149 -0
  72. package/src/generator/templates/__rollup-module.ejs +90 -0
  73. package/src/generator/templates/api-extender-module.js.ejs +0 -0
  74. package/src/generator/templates/api-module.js.ejs +818 -0
  75. package/src/generator/templates/module-context.ejs +16 -0
  76. package/src/generator/templates/module-ext-about.ejs +1 -0
  77. package/src/generator/templates/module-ext-record.ejs +1 -0
  78. package/src/generator/templates/module-ext.html.ejs +3 -0
  79. package/src/generator/templates/module-ext.mjs.ejs +21 -0
  80. package/src/generator/templates/module-logs.ejs +14 -0
  81. package/src/generator/templates/module.ejs.ejs +48 -0
  82. package/src/generator/templates/module.mjs.ejs +256 -0
  83. package/src/generator/templates/moduleDetilEdit.html.ejs +34 -0
  84. package/src/generator/templates/moduleDetilEdit.mjs.ejs +792 -0
  85. package/src/generator/templates/moduleDetilList.html.ejs +26 -0
  86. package/src/generator/templates/moduleDetilList.mjs.ejs +319 -0
  87. package/src/generator/templates/moduleHeaderEdit.html.ejs +53 -0
  88. package/src/generator/templates/moduleHeaderEdit.mjs.ejs +807 -0
  89. package/src/generator/templates/moduleHeaderList.html.ejs +24 -0
  90. package/src/generator/templates/moduleHeaderList.mjs.ejs +308 -0
  91. package/src/generator/templates/sqlAddField.ejs +3 -0
  92. package/src/generator/templates/sqlAddForeignKey.ejs +12 -0
  93. package/src/generator/templates/sqlAddUniqueIndex.ejs +4 -0
  94. package/src/generator/templates/sqlCreateTable.ejs +9 -0
  95. package/src/generator/templates/sqlDropForeignKey.ejs +3 -0
  96. package/src/generator/templates/sqlDropUniqueIndex.ejs +4 -0
  97. package/src/generator/templates/sqlModifyField.ejs +6 -0
  98. package/src/generator/trygenerate.js +83 -0
  99. package/src/generator/worker.js +389 -0
  100. package/src/helper.js +82 -0
  101. package/src/logger.js +39 -0
  102. package/src/router.js +84 -0
  103. package/src/routers/defaultLoginApi.js +29 -0
  104. package/src/routers/defaultLoginAsset.js +18 -0
  105. package/src/routers/defaultLoginPage.js +36 -0
  106. package/src/routers/defaultRootIndex.js +16 -0
  107. package/src/routers/downloadHandler.js +51 -0
  108. package/src/routers/fileUploadApi.js +15 -0
  109. package/src/routers/generatorApi.js +30 -0
  110. package/src/routers/generatorAsset.js +18 -0
  111. package/src/routers/generatorPage.js +37 -0
  112. package/src/routers/handleError.js +43 -0
  113. package/src/routers/handleModuleNotfound.js +12 -0
  114. package/src/routers/moduleApi.js +34 -0
  115. package/src/routers/modulePage.js +102 -0
  116. package/src/sequencerdoc.js +311 -0
  117. package/src/sequencerline.js +214 -0
  118. package/src/session.js +57 -0
  119. package/src/startup.js +59 -0
  120. package/src/webapps.js +239 -0
  121. package/src/workermanager.js +83 -0
  122. package/templates/_lib_debug.ejs +11 -0
  123. package/templates/_lib_production.ejs +5 -0
  124. package/templates/application.page.ejs +143 -0
  125. package/templates/generator.page.ejs +131 -0
  126. package/templates/index.page.ejs +24 -0
  127. package/templates/login.page.ejs +102 -0
  128. package/templates/moduleError.ejs +16 -0
  129. package/templates/moduleNotfound.ejs +14 -0
  130. package/webapps.code-workspace +11 -0
@@ -0,0 +1,226 @@
1
+ import pgp from 'pg-promise'
2
+
3
+ import * as path from 'path'
4
+ import db from '../db.js'
5
+ import Api from '../api.js'
6
+ import * as helper from '../helper.js'
7
+ import { runDetachedWorker } from '../workermanager.js'
8
+ import context from '../context.js'
9
+ import sqlUtil from '@agung_dhewe/pgsqlc'
10
+
11
+
12
+ import jwt from 'jsonwebtoken';
13
+
14
+ const MINUTES = 60 * 1000
15
+
16
+ const moduleName = 'generator'
17
+ const generateTimeoutMs = 5 * MINUTES
18
+
19
+ // api: account
20
+ export default class extends Api {
21
+ constructor(req, res, next) {
22
+ super(req, res, next);
23
+ Api.cekLogin(req)
24
+
25
+ }
26
+
27
+ // dipanggil dengan model snake syntax
28
+ // contoh: header-list
29
+ // header-open-data
30
+ async init(body) { return await generator_init(this, body) }
31
+ async list(body) { return await generator_list(this, body) }
32
+ async open(body) { return await generator_open(this, body) }
33
+ async save(body) { return await generator_save(this, body) }
34
+ async generate(body) { return await generator_generate(this, body) }
35
+
36
+ }
37
+
38
+
39
+ async function generator_init(self, body) {
40
+ const req = self.req
41
+
42
+ // set sid untuk session ini, diperlukan ini agar session aktif
43
+ req.session.sid = req.sessionID
44
+
45
+
46
+ try {
47
+ // ambil data app dari database
48
+ const sql = 'select apps_id, apps_url, apps_name, apps_directory from core."apps"'
49
+ const result = await db.any(sql)
50
+
51
+ const appsUrls = {}
52
+ for (let row of result) {
53
+ appsUrls[row.apps_id] = {
54
+ url: row.apps_url,
55
+ directory: row.apps_directory,
56
+ name: row.apps_name
57
+ }
58
+ }
59
+
60
+ return {
61
+ userId: req.session.user.userId,
62
+ userName: req.session.user.userName,
63
+ userFullname: req.session.userFullname,
64
+ sid: req.session.sid ,
65
+ notifierId: Api.generateNotifierId(moduleName, req.sessionID),
66
+ notifierSocket: req.app.locals.appConfig.notifierSocket,
67
+ targetDirectory: context.getRootDirectory(),
68
+ appsUrls: appsUrls
69
+ }
70
+
71
+ } catch (err) {
72
+ throw err
73
+ }
74
+
75
+
76
+ }
77
+
78
+
79
+ async function generator_list(self, body) {
80
+ const { criteria={}, limit=0, offset=0, columns=[], sort={} } = body
81
+ const searchMap = {
82
+ searchtext: `generator_modulename ILIKE '%' || \${searchtext} || '%' OR generator_id=try_cast_bigint(\${searchtext}, 0)`,
83
+ appname: `generator_appname=\${appname}`
84
+ };
85
+
86
+ try {
87
+
88
+ // hilangkan criteria '' atau null
89
+ for (var cname in criteria) {
90
+ if (criteria[cname]==='' || criteria[cname]===null) {
91
+ delete criteria[cname]
92
+ }
93
+ }
94
+
95
+ // const orderby = {
96
+ // _modifydate: 'desc',
97
+ // ...sort
98
+ // }
99
+
100
+ sort._modifydate = 'desc'
101
+
102
+ var max_rows = limit==0 ? 50 : limit
103
+ const tablename = 'core."generator"'
104
+ const {whereClause, queryParams} = sqlUtil.createWhereClause(criteria, searchMap)
105
+ const sql = sqlUtil.createSqlSelect({tablename, columns, whereClause, sort, limit:max_rows+1, offset, queryParams})
106
+ const rows = await db.any(sql, queryParams);
107
+
108
+
109
+ var i = 0
110
+ const data = []
111
+ for (var row of rows) {
112
+ i++
113
+ if (i>max_rows) { break }
114
+ data.push(row)
115
+ }
116
+
117
+ var nextoffset = null
118
+ if (rows.length>max_rows) {
119
+ nextoffset = offset+max_rows
120
+ }
121
+
122
+ return {
123
+ criteria: criteria,
124
+ limit: max_rows,
125
+ nextoffset: nextoffset,
126
+ data: data
127
+ }
128
+
129
+ } catch (err) {
130
+ throw err
131
+ }
132
+ }
133
+
134
+ async function generator_open(self, body) {
135
+ try {
136
+ const { id } = body
137
+ const queryParams = {generator_id: id}
138
+ const sql = 'select * from core."generator" where generator_id = \${generator_id}'
139
+ const data = await db.one(sql, queryParams);
140
+
141
+ if (data==null) { throw new Error("data tidak ditemukan") }
142
+
143
+ return data
144
+ } catch (err) {
145
+ throw err
146
+ }
147
+ }
148
+
149
+ async function generator_save(self, body) {
150
+ const { data } = body
151
+ const tablename = 'core."generator"'
152
+ const req = self.req
153
+ const user_id = req.session.user.userId
154
+
155
+
156
+ try {
157
+ sqlUtil.connect(db)
158
+
159
+
160
+
161
+ const id = `${data.id}`
162
+
163
+ delete data.id
164
+ const obj = {
165
+ generator_appname: data.appname,
166
+ generator_modulename: data.name,
167
+ generator_data: JSON.stringify(data),
168
+ }
169
+
170
+ let cmd
171
+ if (id=='') {
172
+ obj._createby = user_id
173
+ obj._createdate = (new Date()).toISOString()
174
+ cmd = sqlUtil.createInsertCommand(tablename, obj, ['generator_id'])
175
+ } else {
176
+ obj.generator_id = id
177
+ obj._modifyby = user_id
178
+ obj._modifydate = (new Date()).toISOString()
179
+ cmd = sqlUtil.createUpdateCommand(tablename, obj, ['generator_id'])
180
+ }
181
+ const result = await cmd.execute(obj)
182
+
183
+ return result
184
+ } catch (err) {
185
+ throw err
186
+ }
187
+ }
188
+
189
+
190
+ async function generator_generate(self, body) {
191
+ const req = self.req
192
+ const { data, clientId } = body
193
+ const id = `${data.id}`
194
+ const user_id = req.session.user.userId
195
+ const user_name = req.session.user.userFullname
196
+ const ipaddress = req.ip
197
+
198
+ try {
199
+ if (id=='') {
200
+ throw new Error('save data dahulu sebelum generate')
201
+ }
202
+
203
+ // sebelumnya save dahulu
204
+ const result = await generator_save(self, body)
205
+ const generator_id = result.generator_id
206
+
207
+ // generate di detached thread
208
+ const generatorWorker = path.join(context.getWebappsDirectory(), 'src', 'generator', 'worker.js')
209
+ const notifierServer = req.app.locals.appConfig.notifierServer
210
+ runDetachedWorker(generatorWorker, notifierServer, clientId, {
211
+ generator_id: generator_id,
212
+ user_id: user_id,
213
+ user_name: user_name,
214
+ ipaddress: ipaddress,
215
+ timeout: generateTimeoutMs,
216
+ jeda: 0.5, // jeda 1 detik per masing-masing generate
217
+ })
218
+
219
+
220
+ return {
221
+ generator_id: generator_id
222
+ }
223
+ } catch (err) {
224
+ throw err
225
+ }
226
+ }
@@ -0,0 +1,109 @@
1
+ import pgp from 'pg-promise';
2
+
3
+ import sqlUtil from '@agung_dhewe/pgsqlc'
4
+ import context from '../context.js'
5
+ import db from '../db.js'
6
+ import Api from '../api.js'
7
+ import bcrypt from 'bcrypt';
8
+
9
+
10
+
11
+ export default class extends Api {
12
+ constructor(req, res, next) {
13
+ super(req, res, next);
14
+
15
+ this.currentState = {}
16
+ try {
17
+ Api.cekLogin(req)
18
+ this.currentState.isLogin = true
19
+ } catch (err) {
20
+ // tidak perlu throw error, karna hanya untuk cek sudah login apa belum
21
+ this.currentState.isLogin = false
22
+ }
23
+
24
+ }
25
+
26
+ // dipanggil dengan model snake syntax
27
+ // contoh: header-list
28
+ // header-open-data
29
+ async init(body) { return await login_init(this, body) }
30
+ async doLogin(body) { return await login_doLogin(this, body) }
31
+ async doLogout(body) { return await login_doLogout(this, body) }
32
+ }
33
+
34
+
35
+ async function login_init(self, body) {
36
+ const req = self.req
37
+ if (self.currentState.isLogin) {
38
+ req.session.sid = req.sessionID
39
+ }
40
+
41
+ return {
42
+ isLogin: self.currentState.isLogin
43
+ }
44
+ }
45
+
46
+ async function login_doLogin(self, body) {
47
+ try {
48
+ const {username, password} = body
49
+
50
+ const sql = 'select * from core.user where user_name = ${username}'
51
+ const param = { username }
52
+ const row = await db.oneOrNone(sql, param)
53
+
54
+ if (row==null) {
55
+ return null
56
+ }
57
+
58
+
59
+ const userId = row.user_id
60
+ const userName = row.user_name
61
+ const userFullname = row.user_fullname
62
+ const hashedPassword = row.user_password
63
+ const developerAccess = row.user_isdev
64
+
65
+ const match = await bcrypt.compare(password, hashedPassword);
66
+ if (!match) {
67
+ return null
68
+ }
69
+
70
+
71
+ // simpan di session
72
+ self.req.session.user = {
73
+ userId,
74
+ userName,
75
+ userFullname,
76
+ developerAccess,
77
+ isLogin: true
78
+ }
79
+
80
+ return self.req.session.user
81
+
82
+ // // dummy login dulu
83
+ // if (username=='agung') {
84
+ // // setup session
85
+ // const user_id = '2590000000000000001'
86
+ // self.req.session.user = {
87
+ // userId: user_id,
88
+ // userName: 'agung',
89
+ // userFullname: 'Agung Nugroho',
90
+ // isLogin: true
91
+ // }
92
+
93
+ // return self.req.session.user
94
+ // } else {
95
+ // return null
96
+ // }
97
+ } catch (err) {
98
+ throw err
99
+ }
100
+ }
101
+
102
+ async function login_doLogout(self, body) {
103
+ try {
104
+ self.req.session.user = null
105
+ return true
106
+ } catch (err) {
107
+ throw err
108
+ }
109
+ }
package/src/bucket.js ADDED
@@ -0,0 +1,24 @@
1
+ import dotenv from 'dotenv';
2
+ import { Client } from 'minio';
3
+
4
+ dotenv.config();
5
+
6
+
7
+ const bucketHost = process.env.BUCKET_HOST
8
+ const bucketPort = process.env.BUCKET_PORT
9
+ const bucketSecure = process.env.BUCKET_SECURE === 'true'
10
+ const bucketUsername = process.env.BUCKET_USERNAME
11
+ const bucketSecret = process.env.BUCKET_SECRET
12
+
13
+
14
+ const minioClient = new Client({
15
+ endPoint: bucketHost,
16
+ port: bucketPort,
17
+ useSSL: bucketSecure,
18
+ accessKey: bucketUsername,
19
+ secretKey: bucketSecret,
20
+ });
21
+
22
+
23
+ export default minioClient
24
+
package/src/context.js ADDED
@@ -0,0 +1,26 @@
1
+ let __dir
2
+ let __rootDirectory
3
+
4
+
5
+
6
+ let fnParseModuleRequest
7
+
8
+ export default {
9
+ setWebappsDirectory: (dirname)=>{ __dir=dirname },
10
+ getWebappsDirectory: () => { return __dir },
11
+
12
+ setRootDirectory: (rootdir) => { __rootDirectory=rootdir },
13
+ getRootDirectory: () => { return __rootDirectory },
14
+
15
+
16
+ setFnParseModuleRequest: (fn) => {
17
+ fnParseModuleRequest = fn
18
+ },
19
+ getFnParseModuleRequest: (fn) => {
20
+ return fnParseModuleRequest
21
+ }
22
+ }
23
+
24
+
25
+
26
+
@@ -0,0 +1,22 @@
1
+ create table datalog (
2
+ log_time timestamptz not null,
3
+ log_user_id text,
4
+ log_user_name text,
5
+ log_action text,
6
+ log_ipaddress text,
7
+ log_module text,
8
+ log_table text,
9
+ log_id text,
10
+ log_remark text,
11
+ log_executiontime int,
12
+ log_metadata JSONB
13
+ );
14
+
15
+ CREATE INDEX idx_datalog_module_table_id
16
+ ON datalog (log_module, log_table, log_id);
17
+
18
+
19
+ -- buat jadi hyper table
20
+ SELECT create_hypertable('datalog', 'log_time');
21
+
22
+
File without changes
package/src/db.js ADDED
@@ -0,0 +1,61 @@
1
+ import dotenv from 'dotenv';
2
+ import pgp from 'pg-promise';
3
+
4
+ dotenv.config();
5
+
6
+ const initOptions = {
7
+ // Misalnya, event untuk memantau query atau error
8
+ // query: (e) => {
9
+ // console.log('QUERY:', e.query);
10
+ // },
11
+ // error: (err, e) => {
12
+ // console.log('ERROR:', err, e.query);
13
+ // }
14
+ };
15
+
16
+ const pgpInstance = pgp(initOptions); // <-- Panggil pgp() hanya satu kali di sini
17
+
18
+
19
+ const configDb = {
20
+ port: process.env.DB_PORT,
21
+ host: process.env.DB_HOST,
22
+ database: process.env.DB_NAME,
23
+ user: process.env.DB_USER,
24
+ password: process.env.DB_PASS,
25
+ }
26
+
27
+ const configDbLog = {
28
+ port: process.env.LOGGER_DB_PORT,
29
+ host: process.env.LOGGER_DB_HOST,
30
+ database: process.env.LOGGER_DB_NAME,
31
+ user: process.env.LOGGER_DB_USER,
32
+ password: process.env.LOGGER_DB_PASS,
33
+ }
34
+
35
+
36
+ const db = pgpInstance(configDb);
37
+ export default db
38
+
39
+ export const dblog = pgpInstance(configDbLog);
40
+
41
+
42
+
43
+ db.connect()
44
+ .then(obj => {
45
+ console.log('Connected to Primary Database!');
46
+ obj.done(); // Klien dikembalikan ke pool
47
+ })
48
+ .catch(error => {
49
+ console.error("\n\x1b[31mError!\x1b[0m\ncannot connect to Database:", error.message || error, "\n");
50
+ process.exit(1);
51
+ });
52
+
53
+ dblog.connect()
54
+ .then(obj => {
55
+ console.log('Connected to Logger Database!');
56
+ obj.done(); // Klien dikembalikan ke pool
57
+ })
58
+ .catch(error => {
59
+ console.error('\n\x1b[31mError!\x1b[0m cannot\nconnect to Logger Database:', error.message || error, "\n");
60
+ process.exit(1);
61
+ });
@@ -0,0 +1,54 @@
1
+ import { kebabToCamel, isFileExist, getSectionData } from './helper.js'
2
+ import { fileURLToPath } from 'url';
3
+ import path from 'path'
4
+ import fs from 'fs/promises'
5
+ import ejs from 'ejs'
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ export async function createApiExtenderModule(context, options) {
11
+ const overwrite = options.overwrite===true
12
+ const moduleName = context.moduleName
13
+ const title = context.title
14
+ const targetFile = path.join(context.apiExtenderDir, `${moduleName}.apiext.js`)
15
+
16
+ try {
17
+ // cek dulu apakah file ada
18
+ var fileExists = await isFileExist(targetFile)
19
+ if (fileExists && !overwrite) {
20
+ context.postMessage({message: `skip file: '${targetFile}`})
21
+ return
22
+ }
23
+
24
+ // reporting progress to parent process
25
+ context.postMessage({message: `generating file: '${targetFile}`})
26
+
27
+
28
+ // start geneate program code
29
+ let sections = []
30
+ for (var entityName in context.entities) {
31
+ // console.log(context.entities[entityName])
32
+ sections.push(getSectionData(moduleName, entityName, context.entities[entityName], 'list'))
33
+ sections.push(getSectionData(moduleName, entityName, context.entities[entityName], 'edit'))
34
+ }
35
+
36
+ const variables = {
37
+ timeGenerated: context.timeGenerated,
38
+ title: title,
39
+ moduleName: moduleName,
40
+ sections: sections
41
+ }
42
+
43
+
44
+ const tplFilePath = path.join(__dirname, 'templates', 'api-extender-module.js.ejs')
45
+ const template = await fs.readFile(tplFilePath, 'utf-8');
46
+ const content = ejs.render(template, variables)
47
+
48
+
49
+ await fs.writeFile(targetFile, content, 'utf8');
50
+ } catch (err) {
51
+ throw err
52
+ }
53
+
54
+ }