bb-com-extension 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/bundle/index.js +442 -0
- package/index.ts +220 -0
- package/nodemon.json +9 -0
- package/package.json +24 -0
- package/src/ComLoaderTemplate.ts +125 -0
- package/tsconfig.json +21 -0
- package/webpack.config.mjs +63 -0
package/bundle/index.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import * as __WEBPACK_EXTERNAL_MODULE_fs__ from "fs";
|
|
2
|
+
/******/ var __webpack_modules__ = ({
|
|
3
|
+
|
|
4
|
+
/***/ "fs"
|
|
5
|
+
/*!*********************!*\
|
|
6
|
+
!*** external "fs" ***!
|
|
7
|
+
\*********************/
|
|
8
|
+
(module) {
|
|
9
|
+
|
|
10
|
+
module.exports = __WEBPACK_EXTERNAL_MODULE_fs__;
|
|
11
|
+
|
|
12
|
+
/***/ },
|
|
13
|
+
|
|
14
|
+
/***/ "./dist/src/ComLoaderTemplate.js"
|
|
15
|
+
/*!***************************************!*\
|
|
16
|
+
!*** ./dist/src/ComLoaderTemplate.js ***!
|
|
17
|
+
\***************************************/
|
|
18
|
+
(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) {
|
|
19
|
+
|
|
20
|
+
__webpack_require__.r(__webpack_exports__);
|
|
21
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
22
|
+
/* harmony export */ "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__)
|
|
23
|
+
/* harmony export */ });
|
|
24
|
+
/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__() {
|
|
25
|
+
return `import { ComManager, ComType, ComMacro } from 'ellipsis-com'
|
|
26
|
+
import {autowired, named, factory} from 'ellipsis-ioc'
|
|
27
|
+
|
|
28
|
+
// TODO: Adopt a propper logging system.
|
|
29
|
+
let debug: (...args: any[]) => void
|
|
30
|
+
export function enableDebug(enable = true) {
|
|
31
|
+
if (enable) {
|
|
32
|
+
debug = console.log;
|
|
33
|
+
} else {
|
|
34
|
+
debug = () => { };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
enableDebug(false)
|
|
38
|
+
|
|
39
|
+
const comManager = new ComManager()
|
|
40
|
+
|
|
41
|
+
@named('com-loader') // named so that it autocreates an instance
|
|
42
|
+
export class ComLoader {
|
|
43
|
+
|
|
44
|
+
@autowired('openapi-doc')
|
|
45
|
+
openapiDoc:any
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
comManager.init(this.loadTypes())
|
|
50
|
+
}, 1000)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@factory('com-manager') // make the manage available for injection
|
|
54
|
+
getComManager() {
|
|
55
|
+
return comManager
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load the com types from the OpenAPI document.
|
|
60
|
+
* TODO: This method needs to be moved into the bb-com-extension.
|
|
61
|
+
* @returns
|
|
62
|
+
*/
|
|
63
|
+
loadTypes(): ComType[] {
|
|
64
|
+
const types: ComType[] = []
|
|
65
|
+
|
|
66
|
+
// Check for com types in the OpenAPI document:
|
|
67
|
+
if(!this.openapiDoc['x-bb-com-types']) {
|
|
68
|
+
debug("No com types found in OpenAPI document.")
|
|
69
|
+
return []
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Iterate over all com types in the OpenAPI document:
|
|
73
|
+
Object.keys(this.openapiDoc['x-bb-com-types']).forEach( (name: string) => {
|
|
74
|
+
const comType = this.openapiDoc['x-bb-com-types'][name]
|
|
75
|
+
|
|
76
|
+
// Ensure the baud rate is a number:
|
|
77
|
+
if(typeof comType.baud === 'string') {
|
|
78
|
+
comType.baud = parseInt(comType.baud)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract all macros that specify this com type by name:
|
|
82
|
+
const macros = {} as {[method: string]: ComMacro[]}
|
|
83
|
+
Object.values(this.openapiDoc.paths).forEach( (path: any) => {
|
|
84
|
+
Object.keys(path)
|
|
85
|
+
.filter(method => ['get', 'post', 'put', 'patch', 'delete'].includes(method))
|
|
86
|
+
.filter(method => path[method]['x-bb-com-macro'])
|
|
87
|
+
.forEach(method => {
|
|
88
|
+
if(!path[method]['x-bb-com-macro'].commands ||
|
|
89
|
+
!path[method]['x-bb-com-macro'].responses ||
|
|
90
|
+
path[method]['x-bb-com-macro'].commands.length !== path[method]['x-bb-com-macro'].responses.length)
|
|
91
|
+
{
|
|
92
|
+
throw new Error('x-bb-com-macro commands and responses must be arrays of the same length in openapi.json at path '+path+'/'+method+'.')
|
|
93
|
+
}
|
|
94
|
+
if(!path[method].operationId) {
|
|
95
|
+
throw new Error(\`OperationId missing for method \${method} at path \${path} in openapi.json.\`)
|
|
96
|
+
}
|
|
97
|
+
macros[path[method].operationId] = (path[method]['x-bb-com-macro'].commands as string[]).map((c, i) => (
|
|
98
|
+
new ComMacro(c, new RegExp(path[method]['x-bb-com-macro'].responses[i].replace(/^\\/|\\/$/g, ''), 'gm'))
|
|
99
|
+
))
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Init macro:
|
|
104
|
+
if(!comType.initCommands || comType.initCommands.length === 0) {
|
|
105
|
+
throw new Error(\`Com type '\${name}' is missing init commands.\`)
|
|
106
|
+
}
|
|
107
|
+
if(!comType.initResponses || comType.initResponses.length === 0) {
|
|
108
|
+
throw new Error(\`Com type '\${name}' is missing init responses.\`)
|
|
109
|
+
}
|
|
110
|
+
if(comType.initCommands.length !== comType.initResponses.length) {
|
|
111
|
+
throw new Error(\`Com type '\${name}' init commands and responses must be arrays of the same length.\`)
|
|
112
|
+
}
|
|
113
|
+
macros.init = []
|
|
114
|
+
comType.initCommands.forEach((cmd: string, i: number) => {
|
|
115
|
+
// Extract flags and strip slashes if present:
|
|
116
|
+
let response = comType.initResponses[i] as string
|
|
117
|
+
let flags = ''
|
|
118
|
+
if(response.startsWith('/')) {
|
|
119
|
+
const lastSlash = response.lastIndexOf('/')
|
|
120
|
+
if(lastSlash < response.length - 1) {
|
|
121
|
+
flags = response.substring(lastSlash + 1)
|
|
122
|
+
}
|
|
123
|
+
response = response.substring(1, lastSlash)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add the macro:
|
|
127
|
+
macros.init.push(
|
|
128
|
+
new ComMacro(
|
|
129
|
+
cmd,
|
|
130
|
+
new RegExp(response, flags)
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Add the com type:
|
|
136
|
+
types.push(new ComType(
|
|
137
|
+
name,
|
|
138
|
+
comType.baud,
|
|
139
|
+
macros as {[method: string]: ComMacro[], init: ComMacro[]},
|
|
140
|
+
comType.startupDelay
|
|
141
|
+
))
|
|
142
|
+
debug(\`Loaded com type \${comType.toString()}\`);
|
|
143
|
+
})
|
|
144
|
+
return types
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
/***/ }
|
|
152
|
+
|
|
153
|
+
/******/ });
|
|
154
|
+
/************************************************************************/
|
|
155
|
+
/******/ // The module cache
|
|
156
|
+
/******/ var __webpack_module_cache__ = {};
|
|
157
|
+
/******/
|
|
158
|
+
/******/ // The require function
|
|
159
|
+
/******/ function __webpack_require__(moduleId) {
|
|
160
|
+
/******/ // Check if module is in cache
|
|
161
|
+
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
162
|
+
/******/ if (cachedModule !== undefined) {
|
|
163
|
+
/******/ return cachedModule.exports;
|
|
164
|
+
/******/ }
|
|
165
|
+
/******/ // Check if module exists (development only)
|
|
166
|
+
/******/ if (__webpack_modules__[moduleId] === undefined) {
|
|
167
|
+
/******/ var e = new Error("Cannot find module '" + moduleId + "'");
|
|
168
|
+
/******/ e.code = 'MODULE_NOT_FOUND';
|
|
169
|
+
/******/ throw e;
|
|
170
|
+
/******/ }
|
|
171
|
+
/******/ // Create a new module (and put it into the cache)
|
|
172
|
+
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
173
|
+
/******/ // no module.id needed
|
|
174
|
+
/******/ // no module.loaded needed
|
|
175
|
+
/******/ exports: {}
|
|
176
|
+
/******/ };
|
|
177
|
+
/******/
|
|
178
|
+
/******/ // Execute the module function
|
|
179
|
+
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
180
|
+
/******/
|
|
181
|
+
/******/ // Return the exports of the module
|
|
182
|
+
/******/ return module.exports;
|
|
183
|
+
/******/ }
|
|
184
|
+
/******/
|
|
185
|
+
/************************************************************************/
|
|
186
|
+
/******/ /* webpack/runtime/define property getters */
|
|
187
|
+
/******/ (() => {
|
|
188
|
+
/******/ // define getter functions for harmony exports
|
|
189
|
+
/******/ __webpack_require__.d = (exports, definition) => {
|
|
190
|
+
/******/ for(var key in definition) {
|
|
191
|
+
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
192
|
+
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
193
|
+
/******/ }
|
|
194
|
+
/******/ }
|
|
195
|
+
/******/ };
|
|
196
|
+
/******/ })();
|
|
197
|
+
/******/
|
|
198
|
+
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
|
199
|
+
/******/ (() => {
|
|
200
|
+
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
201
|
+
/******/ })();
|
|
202
|
+
/******/
|
|
203
|
+
/******/ /* webpack/runtime/make namespace object */
|
|
204
|
+
/******/ (() => {
|
|
205
|
+
/******/ // define __esModule on exports
|
|
206
|
+
/******/ __webpack_require__.r = (exports) => {
|
|
207
|
+
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
|
208
|
+
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
209
|
+
/******/ }
|
|
210
|
+
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
|
211
|
+
/******/ };
|
|
212
|
+
/******/ })();
|
|
213
|
+
/******/
|
|
214
|
+
/************************************************************************/
|
|
215
|
+
var __webpack_exports__ = {};
|
|
216
|
+
// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk.
|
|
217
|
+
(() => {
|
|
218
|
+
/*!***********************!*\
|
|
219
|
+
!*** ./dist/index.js ***!
|
|
220
|
+
\***********************/
|
|
221
|
+
__webpack_require__.r(__webpack_exports__);
|
|
222
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
223
|
+
/* harmony export */ "default": () => (/* binding */ BBComExtension)
|
|
224
|
+
/* harmony export */ });
|
|
225
|
+
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! fs */ "fs");
|
|
226
|
+
/* harmony import */ var _src_ComLoaderTemplate_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./src/ComLoaderTemplate.js */ "./dist/src/ComLoaderTemplate.js");
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
function logp(message) {
|
|
230
|
+
console.log(message);
|
|
231
|
+
}
|
|
232
|
+
function logerror(message) {
|
|
233
|
+
console.error(message);
|
|
234
|
+
}
|
|
235
|
+
class BBComExtension {
|
|
236
|
+
config;
|
|
237
|
+
bbDoc;
|
|
238
|
+
openapiDoc;
|
|
239
|
+
constructor(config, // CliConfig
|
|
240
|
+
bbDoc, openapiDoc) {
|
|
241
|
+
this.config = config;
|
|
242
|
+
this.bbDoc = bbDoc;
|
|
243
|
+
this.openapiDoc = openapiDoc;
|
|
244
|
+
}
|
|
245
|
+
getConfig() {
|
|
246
|
+
return {
|
|
247
|
+
name: "bb-com-extension",
|
|
248
|
+
types: {
|
|
249
|
+
'com-type': 'Type of serial device.',
|
|
250
|
+
'com-macro': 'A macro to send over the serial link.',
|
|
251
|
+
'com-utils': 'Helper utilities for accessing serial communications.'
|
|
252
|
+
},
|
|
253
|
+
options: {
|
|
254
|
+
'--init-commands <string>': 'The serial command to send to initialise the device.',
|
|
255
|
+
'--init-responses <string>': 'The expected response from the device after sending the init command, given as a regular expression.',
|
|
256
|
+
'--commands <string>': 'The sequence of commands to send in the macro, separated by semicolons.',
|
|
257
|
+
'--responses <string>': 'Expected responses for each command given as regular expressions, separated by semicolons.',
|
|
258
|
+
'--baud <string>': 'The baud rate for the serial connection.',
|
|
259
|
+
'--method <string>': 'The HTTP method to use (GET, POST, etc.) for a macro.',
|
|
260
|
+
'--startup-delay <number>': 'Optional delay in milliseconds to wait after opening the port before sending the init commands, to allow devices extra time to boot up.'
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
add(type) {
|
|
265
|
+
if (type === 'com-type') {
|
|
266
|
+
this.addType();
|
|
267
|
+
}
|
|
268
|
+
else if (type === 'com-macro') {
|
|
269
|
+
this.addMacro();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* bb add com-type --path /serial --baud 9600 --init "INIT_CMD" --init-response "/OK/"
|
|
274
|
+
*/
|
|
275
|
+
addType() {
|
|
276
|
+
if (!this.openapiDoc['x-bb-com-types']) {
|
|
277
|
+
this.openapiDoc['x-bb-com-types'] = {};
|
|
278
|
+
}
|
|
279
|
+
this.openapiDoc['x-bb-com-types'][this.config.name] = {
|
|
280
|
+
baud: this.config.baud,
|
|
281
|
+
startupDelay: this.config.startupDelay,
|
|
282
|
+
initCommands: this.config.initCommands.split(';'),
|
|
283
|
+
initResponses: this.parseResponses(this.config.initResponses)
|
|
284
|
+
};
|
|
285
|
+
logp(`Added com type '${this.config.name}'.`);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* bb add com-macro --path init --commands "CMD1;CMD2;CMD3" --responses "/RESP1/;/RESP2/;/RESP3/"
|
|
289
|
+
*/
|
|
290
|
+
addMacro() {
|
|
291
|
+
if (!this.config.path) {
|
|
292
|
+
logerror("Please provide a path for the macro using --path.");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const openApiPath = this.blackboxToOpenapiPath(this.config.path);
|
|
296
|
+
if (!openApiPath) {
|
|
297
|
+
logerror(`Path ${this.config.path} not found in OpenAPI document.`);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const path = this.openapiDoc.paths[openApiPath];
|
|
301
|
+
if (!path) {
|
|
302
|
+
logerror(`Path ${this.config.path} not found in OpenAPI document.`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const method = this.config.method;
|
|
306
|
+
if (!method || !path[method.toLowerCase()]) {
|
|
307
|
+
logerror(`Method '${method}' not found at path ${this.config.path} in OpenAPI document.`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (path[method.toLowerCase()]['x-bb-com-macro']) {
|
|
311
|
+
logerror(`A com macro already exists at path ${this.config.path} for method ${method}.`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (!this.config.commands || !this.config.responses) {
|
|
315
|
+
logerror("Please provide both commands and responses for the macro using --commands and --responses.");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const macroDef = {
|
|
319
|
+
name: this.config.name,
|
|
320
|
+
commands: this.config.commands.split(';'),
|
|
321
|
+
responses: this.parseResponses(this.config.responses)
|
|
322
|
+
};
|
|
323
|
+
path[method.toLowerCase()]['x-bb-com-macro'] = macroDef;
|
|
324
|
+
logp(`Added com macro at path '${this.config.path}' with ${macroDef.commands.length} command(s).`);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Extract the responses as regular expressions.
|
|
328
|
+
* Responses will be in the form: /response1/;/response2/;/response3/
|
|
329
|
+
* @param responsesStr
|
|
330
|
+
* @returns
|
|
331
|
+
*/
|
|
332
|
+
parseResponses(responsesStr) {
|
|
333
|
+
if (!responsesStr.startsWith('/') || !responsesStr.endsWith('/')) {
|
|
334
|
+
logerror("Responses must be provided as regular expressions enclosed in slashes (/).");
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
return responsesStr.substring(1, responsesStr.length - 1).split('/;/').map((resp) => resp.trim());
|
|
338
|
+
}
|
|
339
|
+
delete(type) {
|
|
340
|
+
console.log(`Deleting com of type '${type} - NOT YET IMPLEMENTED.`);
|
|
341
|
+
}
|
|
342
|
+
blackboxToOpenapiPath(bbPath) {
|
|
343
|
+
for (let p in this.openapiDoc.paths) {
|
|
344
|
+
if (p.replaceAll(/\{[^}]+\}/gm, '#') === bbPath) {
|
|
345
|
+
return p;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
generate(type) {
|
|
351
|
+
if (type !== 'com-utils') {
|
|
352
|
+
logerror(`Generation for type '${type}' not supported.`);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
// Create ComManager.ts if it doesn't exist:
|
|
356
|
+
if (!fs__WEBPACK_IMPORTED_MODULE_0__["default"].existsSync('gensrc/ComLoader.ts')) {
|
|
357
|
+
fs__WEBPACK_IMPORTED_MODULE_0__["default"].writeFileSync('gensrc/ComLoader.ts', (0,_src_ComLoaderTemplate_js__WEBPACK_IMPORTED_MODULE_1__["default"])());
|
|
358
|
+
logp("Generated gensrc/ComLoader.ts for serial communications.");
|
|
359
|
+
}
|
|
360
|
+
// Add ellipsis-com to package.json dependencies if not already present:
|
|
361
|
+
const packageJson = JSON.parse(fs__WEBPACK_IMPORTED_MODULE_0__["default"].readFileSync('package.json', 'utf-8'));
|
|
362
|
+
if (!packageJson.dependencies) {
|
|
363
|
+
packageJson.dependencies = {};
|
|
364
|
+
}
|
|
365
|
+
if (!packageJson.dependencies['ellipsis-com']) {
|
|
366
|
+
packageJson.dependencies['ellipsis-com'] = '^0.1.0';
|
|
367
|
+
fs__WEBPACK_IMPORTED_MODULE_0__["default"].writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
|
|
368
|
+
logp("Added 'ellipsis-com' to package.json dependencies.\nPlease run 'npm install' to install the new dependency.");
|
|
369
|
+
}
|
|
370
|
+
/*
|
|
371
|
+
// Generate services for each path with a com-type:
|
|
372
|
+
Object.keys(this.openapiDoc.paths).forEach( path => {
|
|
373
|
+
|
|
374
|
+
// Skip if the service file already exists:
|
|
375
|
+
const serviceName = path.substring(path.lastIndexOf('/')+1)
|
|
376
|
+
const serviceFileName = `src/${serviceName.charAt(0).toUpperCase() + serviceName.slice(1)}Service.ts`
|
|
377
|
+
if(fs.existsSync(serviceFileName)) {
|
|
378
|
+
logp(`Service file ${serviceFileName} already exists - skipping generation.`)
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const parentPathObj = this.openapiDoc.paths[path]
|
|
383
|
+
const comType = parentPathObj['x-bb-com-type']
|
|
384
|
+
if(!comType) {
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Extract methods for the parent service node:
|
|
389
|
+
const methods = Object.keys(parentPathObj)
|
|
390
|
+
.filter((method: string) => ['get', 'post', 'put', 'delete', 'patch'].includes(method.toLowerCase()))
|
|
391
|
+
.reduce((methods: any[], method) => {
|
|
392
|
+
if (parentPathObj[method]['x-bb-com-macro']) {
|
|
393
|
+
const operationId = parentPathObj[method].operationId
|
|
394
|
+
methods.push({
|
|
395
|
+
path,
|
|
396
|
+
method,
|
|
397
|
+
operationId
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
return methods
|
|
401
|
+
}, [])
|
|
402
|
+
|
|
403
|
+
// Extract methods for the child object nodes:
|
|
404
|
+
if(parentPathObj['x-bb-service']?.access !== 'unique') {
|
|
405
|
+
Object.keys(this.openapiDoc.paths)
|
|
406
|
+
.filter( p => p.startsWith(path+'/') ) // child nodes
|
|
407
|
+
.forEach( p => {
|
|
408
|
+
const pathObj = this.openapiDoc.paths[p]
|
|
409
|
+
Object.keys(pathObj)
|
|
410
|
+
.filter((method: string) => ['get', 'post', 'put', 'delete', 'patch'].includes(method.toLowerCase()))
|
|
411
|
+
.filter( method => !pathObj[method]['x-bb-com-macro']?.name || pathObj[method]['x-bb-com-macro'].name === comType.name) // Either the name is not specific (default to parent) or it matches the com-type name of the parent.
|
|
412
|
+
.forEach( method => {
|
|
413
|
+
if (pathObj[method]['x-bb-com-macro']) {
|
|
414
|
+
const operationId = pathObj[method].operationId
|
|
415
|
+
methods.push({
|
|
416
|
+
path: p,
|
|
417
|
+
method,
|
|
418
|
+
operationId
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Generate service code:
|
|
426
|
+
const serviceCode = serviceTemplate(this.openapiDoc, {
|
|
427
|
+
serviceName,
|
|
428
|
+
bbName: comType.name,
|
|
429
|
+
methods: methods
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
// Write service file:
|
|
433
|
+
fs.writeFileSync(serviceFileName, serviceCode)
|
|
434
|
+
logp(`Generated com service class ${serviceFileName}.`)
|
|
435
|
+
})*/
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
})();
|
|
440
|
+
|
|
441
|
+
const __webpack_exports__default = __webpack_exports__["default"];
|
|
442
|
+
export { __webpack_exports__default as default };
|
package/index.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import comLoaderTemplate from './src/ComLoaderTemplate.js'
|
|
3
|
+
|
|
4
|
+
function logp(message: string) {
|
|
5
|
+
console.log(message)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function logerror(message: string) {
|
|
9
|
+
console.error(message)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default class BBComExtension {
|
|
13
|
+
constructor(
|
|
14
|
+
public config: any, // CliConfig
|
|
15
|
+
public bbDoc: any,
|
|
16
|
+
public openapiDoc: any
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
getConfig() {
|
|
20
|
+
return {
|
|
21
|
+
name: "bb-com-extension",
|
|
22
|
+
types: {
|
|
23
|
+
'com-type': 'Type of serial device.',
|
|
24
|
+
'com-macro': 'A macro to send over the serial link.',
|
|
25
|
+
'com-utils': 'Helper utilities for accessing serial communications.'
|
|
26
|
+
},
|
|
27
|
+
options: {
|
|
28
|
+
'--init-commands <string>': 'The serial command to send to initialise the device.',
|
|
29
|
+
'--init-responses <string>': 'The expected response from the device after sending the init command, given as a regular expression.',
|
|
30
|
+
'--commands <string>': 'The sequence of commands to send in the macro, separated by semicolons.',
|
|
31
|
+
'--responses <string>': 'Expected responses for each command given as regular expressions, separated by semicolons.',
|
|
32
|
+
'--baud <string>': 'The baud rate for the serial connection.',
|
|
33
|
+
'--method <string>': 'The HTTP method to use (GET, POST, etc.) for a macro.',
|
|
34
|
+
'--startup-delay <number>': 'Optional delay in milliseconds to wait after opening the port before sending the init commands, to allow devices extra time to boot up.'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
add(type: string) {
|
|
40
|
+
if(type === 'com-type') {
|
|
41
|
+
this.addType()
|
|
42
|
+
} else if(type === 'com-macro') {
|
|
43
|
+
this.addMacro()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* bb add com-type --path /serial --baud 9600 --init "INIT_CMD" --init-response "/OK/"
|
|
49
|
+
*/
|
|
50
|
+
addType() {
|
|
51
|
+
if(!this.openapiDoc['x-bb-com-types']) {
|
|
52
|
+
this.openapiDoc['x-bb-com-types'] = {}
|
|
53
|
+
}
|
|
54
|
+
this.openapiDoc['x-bb-com-types'][this.config.name] = {
|
|
55
|
+
baud: this.config.baud,
|
|
56
|
+
startupDelay: this.config.startupDelay,
|
|
57
|
+
initCommands: this.config.initCommands.split(';'),
|
|
58
|
+
initResponses: this.parseResponses(this.config.initResponses)
|
|
59
|
+
}
|
|
60
|
+
logp(`Added com type '${this.config.name}'.`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* bb add com-macro --path init --commands "CMD1;CMD2;CMD3" --responses "/RESP1/;/RESP2/;/RESP3/"
|
|
65
|
+
*/
|
|
66
|
+
addMacro() {
|
|
67
|
+
if(!this.config.path) {
|
|
68
|
+
logerror("Please provide a path for the macro using --path.")
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
const openApiPath = this.blackboxToOpenapiPath(this.config.path)
|
|
72
|
+
if(!openApiPath) {
|
|
73
|
+
logerror(`Path ${this.config.path} not found in OpenAPI document.`)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
const path = this.openapiDoc.paths[openApiPath]
|
|
77
|
+
if(!path) {
|
|
78
|
+
logerror(`Path ${this.config.path} not found in OpenAPI document.`)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const method = this.config.method
|
|
82
|
+
if(!method || !path[method.toLowerCase()]) {
|
|
83
|
+
logerror(`Method '${method}' not found at path ${this.config.path} in OpenAPI document.`)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if(path[method.toLowerCase()]['x-bb-com-macro']) {
|
|
87
|
+
logerror(`A com macro already exists at path ${this.config.path} for method ${method}.`)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
if(!this.config.commands || !this.config.responses) {
|
|
91
|
+
logerror("Please provide both commands and responses for the macro using --commands and --responses.")
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const macroDef = {
|
|
96
|
+
name: this.config.name,
|
|
97
|
+
commands: this.config.commands.split(';'),
|
|
98
|
+
responses: this.parseResponses(this.config.responses)
|
|
99
|
+
}
|
|
100
|
+
path[method.toLowerCase()]['x-bb-com-macro'] = macroDef
|
|
101
|
+
logp(`Added com macro at path '${this.config.path}' with ${macroDef.commands.length} command(s).`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract the responses as regular expressions.
|
|
106
|
+
* Responses will be in the form: /response1/;/response2/;/response3/
|
|
107
|
+
* @param responsesStr
|
|
108
|
+
* @returns
|
|
109
|
+
*/
|
|
110
|
+
private parseResponses(responsesStr: any) {
|
|
111
|
+
if(!responsesStr.startsWith('/') || !responsesStr.endsWith('/')) {
|
|
112
|
+
logerror("Responses must be provided as regular expressions enclosed in slashes (/).")
|
|
113
|
+
return []
|
|
114
|
+
}
|
|
115
|
+
return responsesStr.substring(1, responsesStr.length - 1).split('/;/').map( (resp: string) => resp.trim() )
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
delete(type: string) {
|
|
119
|
+
console.log(`Deleting com of type '${type} - NOT YET IMPLEMENTED.`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
blackboxToOpenapiPath(bbPath: string) {
|
|
123
|
+
for(let p in this.openapiDoc.paths) {
|
|
124
|
+
if(p.replaceAll(/\{[^}]+\}/gm, '#') === bbPath) {
|
|
125
|
+
return p
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
generate(type: string) {
|
|
132
|
+
if(type !== 'com-utils') {
|
|
133
|
+
logerror(`Generation for type '${type}' not supported.`)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Create ComManager.ts if it doesn't exist:
|
|
138
|
+
if(!fs.existsSync('gensrc/ComLoader.ts')) {
|
|
139
|
+
fs.writeFileSync('gensrc/ComLoader.ts', comLoaderTemplate())
|
|
140
|
+
logp("Generated gensrc/ComLoader.ts for serial communications.")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add ellipsis-com to package.json dependencies if not already present:
|
|
144
|
+
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
|
|
145
|
+
if(!packageJson.dependencies) {
|
|
146
|
+
packageJson.dependencies = {}
|
|
147
|
+
}
|
|
148
|
+
if(!packageJson.dependencies['ellipsis-com']) {
|
|
149
|
+
packageJson.dependencies['ellipsis-com'] = '^0.1.0'
|
|
150
|
+
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2))
|
|
151
|
+
logp("Added 'ellipsis-com' to package.json dependencies.\nPlease run 'npm install' to install the new dependency.")
|
|
152
|
+
}
|
|
153
|
+
/*
|
|
154
|
+
// Generate services for each path with a com-type:
|
|
155
|
+
Object.keys(this.openapiDoc.paths).forEach( path => {
|
|
156
|
+
|
|
157
|
+
// Skip if the service file already exists:
|
|
158
|
+
const serviceName = path.substring(path.lastIndexOf('/')+1)
|
|
159
|
+
const serviceFileName = `src/${serviceName.charAt(0).toUpperCase() + serviceName.slice(1)}Service.ts`
|
|
160
|
+
if(fs.existsSync(serviceFileName)) {
|
|
161
|
+
logp(`Service file ${serviceFileName} already exists - skipping generation.`)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const parentPathObj = this.openapiDoc.paths[path]
|
|
166
|
+
const comType = parentPathObj['x-bb-com-type']
|
|
167
|
+
if(!comType) {
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Extract methods for the parent service node:
|
|
172
|
+
const methods = Object.keys(parentPathObj)
|
|
173
|
+
.filter((method: string) => ['get', 'post', 'put', 'delete', 'patch'].includes(method.toLowerCase()))
|
|
174
|
+
.reduce((methods: any[], method) => {
|
|
175
|
+
if (parentPathObj[method]['x-bb-com-macro']) {
|
|
176
|
+
const operationId = parentPathObj[method].operationId
|
|
177
|
+
methods.push({
|
|
178
|
+
path,
|
|
179
|
+
method,
|
|
180
|
+
operationId
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
return methods
|
|
184
|
+
}, [])
|
|
185
|
+
|
|
186
|
+
// Extract methods for the child object nodes:
|
|
187
|
+
if(parentPathObj['x-bb-service']?.access !== 'unique') {
|
|
188
|
+
Object.keys(this.openapiDoc.paths)
|
|
189
|
+
.filter( p => p.startsWith(path+'/') ) // child nodes
|
|
190
|
+
.forEach( p => {
|
|
191
|
+
const pathObj = this.openapiDoc.paths[p]
|
|
192
|
+
Object.keys(pathObj)
|
|
193
|
+
.filter((method: string) => ['get', 'post', 'put', 'delete', 'patch'].includes(method.toLowerCase()))
|
|
194
|
+
.filter( method => !pathObj[method]['x-bb-com-macro']?.name || pathObj[method]['x-bb-com-macro'].name === comType.name) // Either the name is not specific (default to parent) or it matches the com-type name of the parent.
|
|
195
|
+
.forEach( method => {
|
|
196
|
+
if (pathObj[method]['x-bb-com-macro']) {
|
|
197
|
+
const operationId = pathObj[method].operationId
|
|
198
|
+
methods.push({
|
|
199
|
+
path: p,
|
|
200
|
+
method,
|
|
201
|
+
operationId
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Generate service code:
|
|
209
|
+
const serviceCode = serviceTemplate(this.openapiDoc, {
|
|
210
|
+
serviceName,
|
|
211
|
+
bbName: comType.name,
|
|
212
|
+
methods: methods
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// Write service file:
|
|
216
|
+
fs.writeFileSync(serviceFileName, serviceCode)
|
|
217
|
+
logp(`Generated com service class ${serviceFileName}.`)
|
|
218
|
+
})*/
|
|
219
|
+
}
|
|
220
|
+
}
|
package/nodemon.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bb-com-extension",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Blackbox extension to allow creation of com port based services.",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev-build": "nodemon",
|
|
9
|
+
"build": "tsc && webpack",
|
|
10
|
+
"test": "jest"
|
|
11
|
+
},
|
|
12
|
+
"author": "Ben Millar",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^24.9.2",
|
|
16
|
+
"@types/webpack-node-externals": "^3.0.4",
|
|
17
|
+
"nodemon": "^3.1.10",
|
|
18
|
+
"ts-loader": "^9.5.4",
|
|
19
|
+
"typescript": "^5.9.3",
|
|
20
|
+
"webpack": "^5.102.1",
|
|
21
|
+
"webpack-cli": "^6.0.1",
|
|
22
|
+
"webpack-node-externals": "^3.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export default function () {
|
|
2
|
+
return `import { ComManager, ComType, ComMacro } from 'ellipsis-com'
|
|
3
|
+
import {autowired, named, factory} from 'ellipsis-ioc'
|
|
4
|
+
|
|
5
|
+
// TODO: Adopt a propper logging system.
|
|
6
|
+
let debug: (...args: any[]) => void
|
|
7
|
+
export function enableDebug(enable = true) {
|
|
8
|
+
if (enable) {
|
|
9
|
+
debug = console.log;
|
|
10
|
+
} else {
|
|
11
|
+
debug = () => { };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
enableDebug(false)
|
|
15
|
+
|
|
16
|
+
const comManager = new ComManager()
|
|
17
|
+
|
|
18
|
+
@named('com-loader') // named so that it autocreates an instance
|
|
19
|
+
export class ComLoader {
|
|
20
|
+
|
|
21
|
+
@autowired('openapi-doc')
|
|
22
|
+
openapiDoc:any
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
comManager.init(this.loadTypes())
|
|
27
|
+
}, 1000)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@factory('com-manager') // make the manage available for injection
|
|
31
|
+
getComManager() {
|
|
32
|
+
return comManager
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load the com types from the OpenAPI document.
|
|
37
|
+
* TODO: This method needs to be moved into the bb-com-extension.
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
loadTypes(): ComType[] {
|
|
41
|
+
const types: ComType[] = []
|
|
42
|
+
|
|
43
|
+
// Check for com types in the OpenAPI document:
|
|
44
|
+
if(!this.openapiDoc['x-bb-com-types']) {
|
|
45
|
+
debug("No com types found in OpenAPI document.")
|
|
46
|
+
return []
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Iterate over all com types in the OpenAPI document:
|
|
50
|
+
Object.keys(this.openapiDoc['x-bb-com-types']).forEach( (name: string) => {
|
|
51
|
+
const comType = this.openapiDoc['x-bb-com-types'][name]
|
|
52
|
+
|
|
53
|
+
// Ensure the baud rate is a number:
|
|
54
|
+
if(typeof comType.baud === 'string') {
|
|
55
|
+
comType.baud = parseInt(comType.baud)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Extract all macros that specify this com type by name:
|
|
59
|
+
const macros = {} as {[method: string]: ComMacro[]}
|
|
60
|
+
Object.values(this.openapiDoc.paths).forEach( (path: any) => {
|
|
61
|
+
Object.keys(path)
|
|
62
|
+
.filter(method => ['get', 'post', 'put', 'patch', 'delete'].includes(method))
|
|
63
|
+
.filter(method => path[method]['x-bb-com-macro'])
|
|
64
|
+
.forEach(method => {
|
|
65
|
+
if(!path[method]['x-bb-com-macro'].commands ||
|
|
66
|
+
!path[method]['x-bb-com-macro'].responses ||
|
|
67
|
+
path[method]['x-bb-com-macro'].commands.length !== path[method]['x-bb-com-macro'].responses.length)
|
|
68
|
+
{
|
|
69
|
+
throw new Error('x-bb-com-macro commands and responses must be arrays of the same length in openapi.json at path '+path+'/'+method+'.')
|
|
70
|
+
}
|
|
71
|
+
if(!path[method].operationId) {
|
|
72
|
+
throw new Error(\`OperationId missing for method \${method} at path \${path} in openapi.json.\`)
|
|
73
|
+
}
|
|
74
|
+
macros[path[method].operationId] = (path[method]['x-bb-com-macro'].commands as string[]).map((c, i) => (
|
|
75
|
+
new ComMacro(c, new RegExp(path[method]['x-bb-com-macro'].responses[i].replace(/^\\/|\\/$/g, ''), 'gm'))
|
|
76
|
+
))
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Init macro:
|
|
81
|
+
if(!comType.initCommands || comType.initCommands.length === 0) {
|
|
82
|
+
throw new Error(\`Com type '\${name}' is missing init commands.\`)
|
|
83
|
+
}
|
|
84
|
+
if(!comType.initResponses || comType.initResponses.length === 0) {
|
|
85
|
+
throw new Error(\`Com type '\${name}' is missing init responses.\`)
|
|
86
|
+
}
|
|
87
|
+
if(comType.initCommands.length !== comType.initResponses.length) {
|
|
88
|
+
throw new Error(\`Com type '\${name}' init commands and responses must be arrays of the same length.\`)
|
|
89
|
+
}
|
|
90
|
+
macros.init = []
|
|
91
|
+
comType.initCommands.forEach((cmd: string, i: number) => {
|
|
92
|
+
// Extract flags and strip slashes if present:
|
|
93
|
+
let response = comType.initResponses[i] as string
|
|
94
|
+
let flags = ''
|
|
95
|
+
if(response.startsWith('/')) {
|
|
96
|
+
const lastSlash = response.lastIndexOf('/')
|
|
97
|
+
if(lastSlash < response.length - 1) {
|
|
98
|
+
flags = response.substring(lastSlash + 1)
|
|
99
|
+
}
|
|
100
|
+
response = response.substring(1, lastSlash)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Add the macro:
|
|
104
|
+
macros.init.push(
|
|
105
|
+
new ComMacro(
|
|
106
|
+
cmd,
|
|
107
|
+
new RegExp(response, flags)
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Add the com type:
|
|
113
|
+
types.push(new ComType(
|
|
114
|
+
name,
|
|
115
|
+
comType.baud,
|
|
116
|
+
macros as {[method: string]: ComMacro[], init: ComMacro[]},
|
|
117
|
+
comType.startupDelay
|
|
118
|
+
))
|
|
119
|
+
debug(\`Loaded com type \${comType.toString()}\`);
|
|
120
|
+
})
|
|
121
|
+
return types
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
`
|
|
125
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"module": "nodeNext",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"types": ["node"]
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"src/**/*",
|
|
15
|
+
"index.ts"
|
|
16
|
+
],
|
|
17
|
+
"exclude": [
|
|
18
|
+
"node_modules",
|
|
19
|
+
"**/*.spec.ts"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import nodeExternals from 'webpack-node-externals';
|
|
5
|
+
|
|
6
|
+
// __dirname is not available in ES Modules, so we need to recreate it.
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Read dependencies from package.json to exclude them from the bundle
|
|
11
|
+
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
|
|
12
|
+
const dependencies = Object.keys(pkg.dependencies || {});
|
|
13
|
+
const peerDependencies = Object.keys(pkg.peerDependencies || {});
|
|
14
|
+
const externals = dependencies.concat(peerDependencies);
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
// Sets the bundling mode.
|
|
18
|
+
mode: 'development',
|
|
19
|
+
// IMPORTANT: Set `devtool` to `false` to disable the `eval` source map.
|
|
20
|
+
devtool: false,
|
|
21
|
+
// Enable experimental module output
|
|
22
|
+
experiments: {
|
|
23
|
+
outputModule: true,
|
|
24
|
+
},
|
|
25
|
+
// Your module's main entry point.
|
|
26
|
+
entry: './dist/index.js',
|
|
27
|
+
// How the module is outputted.
|
|
28
|
+
output: {
|
|
29
|
+
filename: 'index.js',
|
|
30
|
+
path: path.resolve(__dirname, 'bundle'),
|
|
31
|
+
// IMPORTANT: Set the library type to 'module' for ESM output
|
|
32
|
+
library: {
|
|
33
|
+
type: 'module',
|
|
34
|
+
},
|
|
35
|
+
// Explicitly set chunk format to module for a clean build
|
|
36
|
+
chunkFormat: 'module',
|
|
37
|
+
},
|
|
38
|
+
// Tells Webpack to handle .ts and .js files.
|
|
39
|
+
resolve: {
|
|
40
|
+
extensions: ['.ts', '.js'],
|
|
41
|
+
},
|
|
42
|
+
module: {
|
|
43
|
+
rules: [
|
|
44
|
+
{
|
|
45
|
+
test: /\.ts$/,
|
|
46
|
+
use: 'ts-loader',
|
|
47
|
+
exclude: /node_modules/,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
// Specifies the target environment as Node.js.
|
|
52
|
+
target: 'node23',
|
|
53
|
+
// Exclude external dependencies from the bundle.
|
|
54
|
+
externals: [
|
|
55
|
+
nodeExternals(),
|
|
56
|
+
...externals,
|
|
57
|
+
],
|
|
58
|
+
// Optionally, add optimization settings for readability.
|
|
59
|
+
optimization: {
|
|
60
|
+
// Disable minification for readable output.
|
|
61
|
+
minimize: false,
|
|
62
|
+
},
|
|
63
|
+
};
|