svelteesp32 1.9.0 → 1.9.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.
- package/README.md +100 -41
- package/dist/cppCode.js +84 -225
- package/dist/file.js +32 -21
- package/dist/index.js +37 -32
- package/package.json +17 -15
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ This npm package provides a solution for **inserting any JS client application i
|
|
|
18
18
|
|
|
19
19
|
> Starting with version v1.7.0, with the cachetime command line option, you can set whether the browser can cache pages
|
|
20
20
|
|
|
21
|
-
> Starting with version v1.6.0, mime
|
|
21
|
+
> Starting with version v1.6.0, mime-types package properly handles MIME types (application/javascript -> text/javascript)
|
|
22
22
|
|
|
23
23
|
> Starting with version v1.5.0, PsychicHttp v2 is also supported.
|
|
24
24
|
|
|
@@ -30,6 +30,11 @@ This npm package provides a solution for **inserting any JS client application i
|
|
|
30
30
|
|
|
31
31
|
> Starting with version v1.1.0, the ETag header is also supported.
|
|
32
32
|
|
|
33
|
+
### Requirements
|
|
34
|
+
|
|
35
|
+
- Node.js >= 20
|
|
36
|
+
- npm >= 9
|
|
37
|
+
|
|
33
38
|
### Usage
|
|
34
39
|
|
|
35
40
|
**Install package** as devDependency (it is practical if the package is part of the project so that you always receive updates)
|
|
@@ -54,19 +59,25 @@ npx svelteesp32 -e async -s ../svelteapp/dist -o ../esp32project/svelteesp32.h -
|
|
|
54
59
|
npx svelteesp32 -e espidf -s ../svelteapp/dist -o ../esp32project/svelteesp32.h --etag=true
|
|
55
60
|
```
|
|
56
61
|
|
|
57
|
-
During the **translation process**, the processed file details are visible, and at the end, the result shows the ESP's memory allocation (gzip size)
|
|
62
|
+
During the **translation process**, the processed file details are visible with compression ratios, and at the end, the result shows the ESP's memory allocation (gzip size)
|
|
58
63
|
|
|
59
64
|
```
|
|
60
|
-
[assets/index-KwubEIf-.js] ✓ gzip used (38850 -> 12547)
|
|
61
|
-
[assets/index-Soe6cpLA.css] ✓ gzip used (32494 -> 5368)
|
|
62
|
-
[favicon.png] x gzip unused (33249 -> 33282)
|
|
63
|
-
[index.html] x gzip unused (too small) (472 -> 308)
|
|
64
|
-
[roboto_regular.json] ✓ gzip used (363757 -> 93567)
|
|
65
|
+
[assets/index-KwubEIf-.js] ✓ gzip used (38850 -> 12547 = 32%)
|
|
66
|
+
[assets/index-Soe6cpLA.css] ✓ gzip used (32494 -> 5368 = 17%)
|
|
67
|
+
[favicon.png] x gzip unused (33249 -> 33282 = 100%)
|
|
68
|
+
[index.html] x gzip unused (too small) (472 -> 308 = 65%)
|
|
69
|
+
[roboto_regular.json] ✓ gzip used (363757 -> 93567 = 26%)
|
|
65
70
|
|
|
66
71
|
5 files, 458kB original size, 142kB gzip size
|
|
67
72
|
../../../Arduino/EspSvelte/svelteesp32.h 842kB size
|
|
68
73
|
```
|
|
69
74
|
|
|
75
|
+
The tool automatically:
|
|
76
|
+
|
|
77
|
+
- Compresses files with gzip level 9 when beneficial (>1024 bytes and >15% reduction)
|
|
78
|
+
- Detects and reports duplicate files using SHA256 hashing
|
|
79
|
+
- Skips pre-compressed files (.gz, .br, .brottli) if the original exists
|
|
80
|
+
|
|
70
81
|
**Include svelteesp32.h** into your Arduino or PlatformIO c++ project (copy it next to the main c++ file)
|
|
71
82
|
|
|
72
83
|
```c
|
|
@@ -111,59 +122,107 @@ void setup()
|
|
|
111
122
|
}
|
|
112
123
|
```
|
|
113
124
|
|
|
114
|
-
|
|
125
|
+
or for ESP-IDF native:
|
|
126
|
+
|
|
127
|
+
```c
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
#include <esp_http_server.h>
|
|
131
|
+
#include "svelteesp32.h"
|
|
132
|
+
|
|
133
|
+
httpd_handle_t server = NULL;
|
|
134
|
+
|
|
135
|
+
...
|
|
136
|
+
|
|
137
|
+
void app_main()
|
|
138
|
+
{
|
|
139
|
+
...
|
|
115
140
|
|
|
116
|
-
|
|
141
|
+
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|
142
|
+
httpd_start(&server, &config);
|
|
143
|
+
|
|
144
|
+
initSvelteStaticFiles(server);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
You can find minimal buildable example projects in [demo/esp32](demo/esp32) (Arduino/PlatformIO) and [demo/esp32idf](demo/esp32idf) (ESP-IDF native) folders.
|
|
149
|
+
|
|
150
|
+
The content of **generated file** (do not edit, just use):
|
|
117
151
|
|
|
118
152
|
```c
|
|
153
|
+
//engine: PsychicHttpServer
|
|
154
|
+
//cmdline: -e psychic -s ./dist -o ./output.h --etag=true --gzip=true
|
|
155
|
+
//
|
|
119
156
|
#define SVELTEESP32_COUNT 5
|
|
120
|
-
#define SVELTEESP32_SIZE
|
|
157
|
+
#define SVELTEESP32_SIZE 468822
|
|
158
|
+
#define SVELTEESP32_SIZE_GZIP 145633
|
|
121
159
|
#define SVELTEESP32_FILE_INDEX_HTML
|
|
122
160
|
#define SVELTEESP32_HTML_FILES 1
|
|
161
|
+
#define SVELTEESP32_CSS_FILES 1
|
|
162
|
+
#define SVELTEESP32_JS_FILES 1
|
|
123
163
|
...
|
|
124
164
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
165
|
+
#include <Arduino.h>
|
|
166
|
+
#include <PsychicHttp.h>
|
|
167
|
+
#include <PsychicHttpsServer.h>
|
|
168
|
+
|
|
169
|
+
const uint8_t datagzip_assets_index_KwubEIf__js[12547] = {0x1f, 0x8b, 0x8, 0x0, ...
|
|
170
|
+
const uint8_t datagzip_assets_index_Soe6cpLA_css[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
|
|
171
|
+
const char * etag_assets_index_KwubEIf__js = "387b88e345cc56ef9091...";
|
|
172
|
+
const char * etag_assets_index_Soe6cpLA_css = "d4f23bc45ef67890ab12...";
|
|
173
|
+
...
|
|
128
174
|
|
|
129
175
|
void initSvelteStaticFiles(PsychicHttpServer * server) {
|
|
130
|
-
server->on("/assets/index-KwubEIf-.js", HTTP_GET, [](PsychicRequest * request)
|
|
131
|
-
|
|
132
|
-
|
|
176
|
+
server->on("/assets/index-KwubEIf-.js", HTTP_GET, [](PsychicRequest * request) {
|
|
177
|
+
if (request->hasHeader("If-None-Match") &&
|
|
178
|
+
request->header("If-None-Match") == String(etag_assets_index_KwubEIf__js)) {
|
|
133
179
|
PsychicResponse response304(request);
|
|
134
180
|
response304.setCode(304);
|
|
135
181
|
return response304.send();
|
|
136
182
|
}
|
|
137
183
|
|
|
138
184
|
PsychicResponse response(request);
|
|
139
|
-
response.setContentType("
|
|
185
|
+
response.setContentType("text/javascript");
|
|
140
186
|
response.addHeader("Content-Encoding", "gzip");
|
|
141
|
-
response.
|
|
187
|
+
response.addHeader("Cache-Control", "no-cache");
|
|
188
|
+
response.addHeader("ETag", etag_assets_index_KwubEIf__js);
|
|
189
|
+
response.setContent(datagzip_assets_index_KwubEIf__js, 12547);
|
|
142
190
|
return response.send();
|
|
143
191
|
});
|
|
144
192
|
|
|
145
|
-
server->on("/assets/index-Soe6cpLA.css", HTTP_GET, [](PsychicRequest * request)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
193
|
+
server->on("/assets/index-Soe6cpLA.css", HTTP_GET, [](PsychicRequest * request) {
|
|
194
|
+
if (request->hasHeader("If-None-Match") &&
|
|
195
|
+
request->header("If-None-Match") == String(etag_assets_index_Soe6cpLA_css)) {
|
|
196
|
+
PsychicResponse response304(request);
|
|
197
|
+
response304.setCode(304);
|
|
198
|
+
return response304.send();
|
|
149
199
|
}
|
|
150
200
|
|
|
151
201
|
PsychicResponse response(request);
|
|
152
202
|
response.setContentType("text/css");
|
|
153
203
|
response.addHeader("Content-Encoding", "gzip");
|
|
154
|
-
response.
|
|
204
|
+
response.addHeader("Cache-Control", "no-cache");
|
|
205
|
+
response.addHeader("ETag", etag_assets_index_Soe6cpLA_css);
|
|
206
|
+
response.setContent(datagzip_assets_index_Soe6cpLA_css, 5368);
|
|
155
207
|
return response.send();
|
|
156
208
|
});
|
|
157
209
|
|
|
158
|
-
|
|
210
|
+
// ... more routes
|
|
159
211
|
}
|
|
160
212
|
```
|
|
161
213
|
|
|
162
214
|
### Engines and ESP variants
|
|
163
215
|
|
|
164
|
-
|
|
216
|
+
Four web server engines are supported:
|
|
217
|
+
|
|
218
|
+
- **`-e psychic`** (default): PsychicHttpServer V1 - Fast ESP32-only server using ESP-IDF internally. Recommended for ESP32 projects.
|
|
219
|
+
- **`-e psychic2`**: PsychicHttpServer V2 - Updated version with improved API
|
|
220
|
+
- **`-e async`**: ESPAsyncWebServer (https://github.com/ESP32Async/ESPAsyncWebServer) - Popular async server supporting both ESP32 and ESP8266/ESP8285
|
|
221
|
+
- **`-e espidf`**: Native ESP-IDF web server - For projects using ESP-IDF framework directly (not Arduino)
|
|
222
|
+
|
|
223
|
+
If you **only work on ESP32**, we recommend using PsychicHttpServer (`-e psychic` or `-e psychic2`), which uses the native ESP-IDF web server internally for significantly faster and more stable operation.
|
|
165
224
|
|
|
166
|
-
|
|
225
|
+
**Note for PsychicHttpServer users:** The generated header includes a comment suggesting to configure `server.config.max_uri_handlers` to match or exceed your file count to ensure all routes are properly registered.
|
|
167
226
|
|
|
168
227
|
### Gzip
|
|
169
228
|
|
|
@@ -239,19 +298,19 @@ You can use the following c++ directives at the project level if you want to con
|
|
|
239
298
|
|
|
240
299
|
### Command line options
|
|
241
300
|
|
|
242
|
-
| Option | Description
|
|
243
|
-
| ------------- |
|
|
244
|
-
| `-s` | **Source dist folder contains compiled web files**
|
|
245
|
-
| `-e` | The engine for which the include file is created (psychic/psychic2/async)
|
|
246
|
-
| `-o` | Generated output file with path
|
|
247
|
-
| `--etag` | Use ETag header for cache (true/false/compiler)
|
|
248
|
-
| `--cachetime` | Override no-cache response with a max-age
|
|
249
|
-
| `--gzip` | Compress content with gzip (true/false/compiler)
|
|
250
|
-
| `--created` | Include creation time
|
|
251
|
-
| `--version` | Include a version string, `--version=v$npm_package_version`
|
|
252
|
-
| `--espmethod` | Name of generated method
|
|
253
|
-
| `--define` | Prefix of c++ defines
|
|
254
|
-
| `-h` | Show help
|
|
301
|
+
| Option | Description | default |
|
|
302
|
+
| ------------- | ------------------------------------------------------------------------------------ | ----------------------- |
|
|
303
|
+
| `-s` | **Source dist folder contains compiled web files** | (required) |
|
|
304
|
+
| `-e` | The engine for which the include file is created (psychic/psychic2/async/espidf) | psychic |
|
|
305
|
+
| `-o` | Generated output file with path | `svelteesp32.h` |
|
|
306
|
+
| `--etag` | Use ETag header for cache (true/false/compiler) | false |
|
|
307
|
+
| `--cachetime` | Override no-cache response with a max-age=\<cachetime\> response (seconds) | 0 |
|
|
308
|
+
| `--gzip` | Compress content with gzip (true/false/compiler) | true |
|
|
309
|
+
| `--created` | Include creation time in generated header | false |
|
|
310
|
+
| `--version` | Include a version string in generated header, e.g. `--version=v$npm_package_version` | '' |
|
|
311
|
+
| `--espmethod` | Name of generated initialization method | `initSvelteStaticFiles` |
|
|
312
|
+
| `--define` | Prefix of c++ defines (e.g., SVELTEESP32_COUNT) | `SVELTEESP32` |
|
|
313
|
+
| `-h` | Show help | |
|
|
255
314
|
|
|
256
315
|
### Q&A
|
|
257
316
|
|
|
@@ -259,9 +318,9 @@ You can use the following c++ directives at the project level if you want to con
|
|
|
259
318
|
|
|
260
319
|
- **How fast is cpp file compilation?** The cpp (.h) file can be large, but it can be compiled in a few seconds on any machine. If you don't modify your svelte/react app, it will use the already compiled cpp file (not recompile). This does not increase the speed of ESP32/ESP8266 development.
|
|
261
320
|
|
|
262
|
-
- **Does the solution use PROGMEM?**
|
|
321
|
+
- **Does the solution use PROGMEM?** It depends on the engine. For PsychicHttpServer (`psychic`, `psychic2`) and ESP-IDF (`espidf`) engines, const arrays are used which are automatically placed in program memory on ESP32. For ESPAsyncWebServer (`async`), PROGMEM directive is explicitly used to support ESP8266/ESP8285. In both cases, the file data is stored in flash memory, not RAM, so your heap and stack remain available for your application.
|
|
263
322
|
|
|
264
|
-
- **Why is the .h file so big?** The source files are always larger than the binary compiled from them.
|
|
323
|
+
- **Why is the .h file so big?** The source files are always larger than the binary compiled from them. The .h file contains byte arrays in text format (comma-separated decimal numbers), which takes more space than the binary data itself. The actual memory allocation is shown in the header file defines (SVELTEESP32_SIZE for uncompressed, SVELTEESP32_SIZE_GZIP for compressed).
|
|
265
324
|
|
|
266
325
|
- **Is collaboration between groups supported?** Yes, the Frontend team produces the application, the use of svelteesp32 is part of the build process. Then, provided with a version number, the .h file is placed in git, which the ESP team translates into the platformio application.
|
|
267
326
|
|
package/dist/cppCode.js
CHANGED
|
@@ -4,15 +4,7 @@ exports.getCppCode = void 0;
|
|
|
4
4
|
const handlebars_1 = require("handlebars");
|
|
5
5
|
const commandLine_1 = require("./commandLine");
|
|
6
6
|
const cppCodeEspIdf_1 = require("./cppCodeEspIdf");
|
|
7
|
-
const
|
|
8
|
-
//engine: PsychicHttpServer
|
|
9
|
-
//cmdline: {{{commandLine}}}
|
|
10
|
-
//You should use server.config.max_uri_handlers = {{fileCount}}; or higher value to proper handles all files
|
|
11
|
-
{{#if created }}
|
|
12
|
-
//created: {{now}}
|
|
13
|
-
{{/if}}
|
|
14
|
-
//
|
|
15
|
-
|
|
7
|
+
const commonHeaderSection = `
|
|
16
8
|
{{#switch etag}}
|
|
17
9
|
{{#case "true"}}
|
|
18
10
|
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
@@ -56,38 +48,36 @@ const psychicTemplate = `
|
|
|
56
48
|
{{#each filesByExtension}}
|
|
57
49
|
#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}
|
|
58
50
|
{{/each}}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
#include <PsychicHttpsServer.h>
|
|
64
|
-
|
|
65
|
-
//
|
|
51
|
+
`;
|
|
52
|
+
const dataArraysSection = (progmem = false) => {
|
|
53
|
+
const memDirective = progmem ? ' PROGMEM' : '';
|
|
54
|
+
return `
|
|
66
55
|
{{#switch gzip}}
|
|
67
56
|
{{#case "true"}}
|
|
68
57
|
{{#each sources}}
|
|
69
|
-
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
|
|
58
|
+
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}]${memDirective} = { {{this.bytesGzip}} };
|
|
70
59
|
{{/each}}
|
|
71
60
|
{{/case}}
|
|
72
61
|
{{#case "false"}}
|
|
73
62
|
{{#each sources}}
|
|
74
|
-
const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
63
|
+
const uint8_t data_{{this.dataname}}[{{this.length}}]${memDirective} = { {{this.bytes}} };
|
|
75
64
|
{{/each}}
|
|
76
65
|
{{/case}}
|
|
77
66
|
{{#case "compiler"}}
|
|
78
67
|
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
79
68
|
{{#each sources}}
|
|
80
|
-
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
|
|
69
|
+
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}]${memDirective} = { {{this.bytesGzip}} };
|
|
81
70
|
{{/each}}
|
|
82
71
|
#else
|
|
83
72
|
{{#each sources}}
|
|
84
|
-
const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
73
|
+
const uint8_t data_{{this.dataname}}[{{this.length}}]${memDirective} = { {{this.bytes}} };
|
|
85
74
|
{{/each}}
|
|
86
|
-
#endif
|
|
75
|
+
#endif
|
|
87
76
|
{{/case}}
|
|
88
77
|
{{/switch}}
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
`;
|
|
79
|
+
};
|
|
80
|
+
const etagArraysSection = `
|
|
91
81
|
{{#switch etag}}
|
|
92
82
|
{{#case "true"}}
|
|
93
83
|
{{#each sources}}
|
|
@@ -101,9 +91,30 @@ const char * etag_{{this.dataname}} = "{{this.md5}}";
|
|
|
101
91
|
{{#each sources}}
|
|
102
92
|
const char * etag_{{this.dataname}} = "{{this.md5}}";
|
|
103
93
|
{{/each}}
|
|
104
|
-
#endif
|
|
94
|
+
#endif
|
|
105
95
|
{{/case}}
|
|
106
96
|
{{/switch}}
|
|
97
|
+
`;
|
|
98
|
+
const psychicTemplate = `
|
|
99
|
+
//engine: PsychicHttpServer
|
|
100
|
+
//cmdline: {{{commandLine}}}
|
|
101
|
+
//You should use server.config.max_uri_handlers = {{fileCount}}; or higher value to proper handles all files
|
|
102
|
+
{{#if created }}
|
|
103
|
+
//created: {{now}}
|
|
104
|
+
{{/if}}
|
|
105
|
+
//
|
|
106
|
+
${commonHeaderSection}
|
|
107
|
+
|
|
108
|
+
//
|
|
109
|
+
#include <Arduino.h>
|
|
110
|
+
#include <PsychicHttp.h>
|
|
111
|
+
#include <PsychicHttpsServer.h>
|
|
112
|
+
|
|
113
|
+
//
|
|
114
|
+
${dataArraysSection(false)}
|
|
115
|
+
|
|
116
|
+
//
|
|
117
|
+
${etagArraysSection}
|
|
107
118
|
|
|
108
119
|
//
|
|
109
120
|
// Http Handlers
|
|
@@ -201,50 +212,7 @@ const psychic2Template = `
|
|
|
201
212
|
//created: {{now}}
|
|
202
213
|
{{/if}}
|
|
203
214
|
//
|
|
204
|
-
|
|
205
|
-
{{#switch etag}}
|
|
206
|
-
{{#case "true"}}
|
|
207
|
-
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
208
|
-
#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON
|
|
209
|
-
#endif
|
|
210
|
-
{{/case}}
|
|
211
|
-
{{#case "false"}}
|
|
212
|
-
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
213
|
-
#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF
|
|
214
|
-
#endif
|
|
215
|
-
{{/case}}
|
|
216
|
-
{{/switch}}
|
|
217
|
-
|
|
218
|
-
{{#switch gzip}}
|
|
219
|
-
{{#case "true"}}
|
|
220
|
-
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
221
|
-
#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON
|
|
222
|
-
#endif
|
|
223
|
-
{{/case}}
|
|
224
|
-
{{#case "false"}}
|
|
225
|
-
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
226
|
-
#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF
|
|
227
|
-
#endif
|
|
228
|
-
{{/case}}
|
|
229
|
-
{{/switch}}
|
|
230
|
-
|
|
231
|
-
//
|
|
232
|
-
{{#if version }}
|
|
233
|
-
#define {{definePrefix}}_VERSION "{{version}}"
|
|
234
|
-
{{/if}}
|
|
235
|
-
#define {{definePrefix}}_COUNT {{fileCount}}
|
|
236
|
-
#define {{definePrefix}}_SIZE {{fileSize}}
|
|
237
|
-
#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}
|
|
238
|
-
|
|
239
|
-
//
|
|
240
|
-
{{#each sources}}
|
|
241
|
-
#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}
|
|
242
|
-
{{/each}}
|
|
243
|
-
|
|
244
|
-
//
|
|
245
|
-
{{#each filesByExtension}}
|
|
246
|
-
#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}
|
|
247
|
-
{{/each}}
|
|
215
|
+
${commonHeaderSection}
|
|
248
216
|
|
|
249
217
|
//
|
|
250
218
|
#include <Arduino.h>
|
|
@@ -252,47 +220,10 @@ const psychic2Template = `
|
|
|
252
220
|
#include <PsychicHttpsServer.h>
|
|
253
221
|
|
|
254
222
|
//
|
|
255
|
-
{
|
|
256
|
-
{{#case "true"}}
|
|
257
|
-
{{#each sources}}
|
|
258
|
-
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
|
|
259
|
-
{{/each}}
|
|
260
|
-
{{/case}}
|
|
261
|
-
{{#case "false"}}
|
|
262
|
-
{{#each sources}}
|
|
263
|
-
const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
264
|
-
{{/each}}
|
|
265
|
-
{{/case}}
|
|
266
|
-
{{#case "compiler"}}
|
|
267
|
-
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
268
|
-
{{#each sources}}
|
|
269
|
-
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
|
|
270
|
-
{{/each}}
|
|
271
|
-
#else
|
|
272
|
-
{{#each sources}}
|
|
273
|
-
const uint8_t data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
|
|
274
|
-
{{/each}}
|
|
275
|
-
#endif
|
|
276
|
-
{{/case}}
|
|
277
|
-
{{/switch}}
|
|
223
|
+
${dataArraysSection(false)}
|
|
278
224
|
|
|
279
225
|
//
|
|
280
|
-
{
|
|
281
|
-
{{#case "true"}}
|
|
282
|
-
{{#each sources}}
|
|
283
|
-
const char * etag_{{this.dataname}} = "{{this.md5}}";
|
|
284
|
-
{{/each}}
|
|
285
|
-
{{/case}}
|
|
286
|
-
{{#case "false"}}
|
|
287
|
-
{{/case}}
|
|
288
|
-
{{#case "compiler"}}
|
|
289
|
-
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
290
|
-
{{#each sources}}
|
|
291
|
-
const char * etag_{{this.dataname}} = "{{this.md5}}";
|
|
292
|
-
{{/each}}
|
|
293
|
-
#endif
|
|
294
|
-
{{/case}}
|
|
295
|
-
{{/switch}}
|
|
226
|
+
${etagArraysSection}
|
|
296
227
|
|
|
297
228
|
//
|
|
298
229
|
// Http Handlers
|
|
@@ -387,97 +318,17 @@ const asyncTemplate = `
|
|
|
387
318
|
//created: {{now}}
|
|
388
319
|
{{/if}}
|
|
389
320
|
//
|
|
390
|
-
|
|
391
|
-
{{#switch etag}}
|
|
392
|
-
{{#case "true"}}
|
|
393
|
-
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
394
|
-
#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON
|
|
395
|
-
#endif
|
|
396
|
-
{{/case}}
|
|
397
|
-
{{#case "false"}}
|
|
398
|
-
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
399
|
-
#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF
|
|
400
|
-
#endif
|
|
401
|
-
{{/case}}
|
|
402
|
-
{{/switch}}
|
|
403
|
-
|
|
404
|
-
{{#switch gzip}}
|
|
405
|
-
{{#case "true"}}
|
|
406
|
-
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
407
|
-
#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON
|
|
408
|
-
#endif
|
|
409
|
-
{{/case}}
|
|
410
|
-
{{#case "false"}}
|
|
411
|
-
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
412
|
-
#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF
|
|
413
|
-
#endif
|
|
414
|
-
{{/case}}
|
|
415
|
-
{{/switch}}
|
|
416
|
-
|
|
417
|
-
//
|
|
418
|
-
{{#if version }}
|
|
419
|
-
#define {{definePrefix}}_VERSION "{{version}}"
|
|
420
|
-
{{/if}}
|
|
421
|
-
#define {{definePrefix}}_COUNT {{fileCount}}
|
|
422
|
-
#define {{definePrefix}}_SIZE {{fileSize}}
|
|
423
|
-
#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
{{#each sources}}
|
|
427
|
-
#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}
|
|
428
|
-
{{/each}}
|
|
429
|
-
|
|
430
|
-
//
|
|
431
|
-
{{#each filesByExtension}}
|
|
432
|
-
#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}
|
|
433
|
-
{{/each}}
|
|
321
|
+
${commonHeaderSection}
|
|
434
322
|
|
|
435
323
|
//
|
|
436
324
|
#include <Arduino.h>
|
|
437
325
|
#include <ESPAsyncWebServer.h>
|
|
438
326
|
|
|
439
327
|
//
|
|
440
|
-
{
|
|
441
|
-
{{#case "true"}}
|
|
442
|
-
{{#each sources}}
|
|
443
|
-
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] PROGMEM = { {{this.bytesGzip}} };
|
|
444
|
-
{{/each}}
|
|
445
|
-
{{/case}}
|
|
446
|
-
{{#case "false"}}
|
|
447
|
-
{{#each sources}}
|
|
448
|
-
const uint8_t data_{{this.dataname}}[{{this.length}}] PROGMEM = { {{this.bytes}} };
|
|
449
|
-
{{/each}}
|
|
450
|
-
{{/case}}
|
|
451
|
-
{{#case "compiler"}}
|
|
452
|
-
#ifdef {{definePrefix}}_ENABLE_GZIP
|
|
453
|
-
{{#each sources}}
|
|
454
|
-
const uint8_t datagzip_{{this.dataname}}[{{this.lengthGzip}}] PROGMEM = { {{this.bytesGzip}} };
|
|
455
|
-
{{/each}}
|
|
456
|
-
#else
|
|
457
|
-
{{#each sources}}
|
|
458
|
-
const uint8_t data_{{this.dataname}}[{{this.length}}] PROGMEM = { {{this.bytes}} };
|
|
459
|
-
{{/each}}
|
|
460
|
-
#endif
|
|
461
|
-
{{/case}}
|
|
462
|
-
{{/switch}}
|
|
328
|
+
${dataArraysSection(true)}
|
|
463
329
|
|
|
464
330
|
//
|
|
465
|
-
{
|
|
466
|
-
{{#case "true"}}
|
|
467
|
-
{{#each sources}}
|
|
468
|
-
const char * etag_{{this.dataname}} = "{{this.md5}}";
|
|
469
|
-
{{/each}}
|
|
470
|
-
{{/case}}
|
|
471
|
-
{{#case "false"}}
|
|
472
|
-
{{/case}}
|
|
473
|
-
{{#case "compiler"}}
|
|
474
|
-
#ifdef {{definePrefix}}_ENABLE_ETAG
|
|
475
|
-
{{#each sources}}
|
|
476
|
-
const char * etag_{{this.dataname}} = "{{this.md5}}";
|
|
477
|
-
{{/each}}
|
|
478
|
-
#endif
|
|
479
|
-
{{/case}}
|
|
480
|
-
{{/switch}}
|
|
331
|
+
${etagArraysSection}
|
|
481
332
|
|
|
482
333
|
//
|
|
483
334
|
// Http Handlers
|
|
@@ -570,33 +421,26 @@ const getTemplate = (engine) => {
|
|
|
570
421
|
return asyncTemplate;
|
|
571
422
|
}
|
|
572
423
|
};
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
})
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
created: commandLine_1.cmdLine.created,
|
|
592
|
-
version: commandLine_1.cmdLine.version,
|
|
593
|
-
methodName: commandLine_1.cmdLine.espmethod,
|
|
594
|
-
cacheTime: commandLine_1.cmdLine.cachetime ? { value: commandLine_1.cmdLine.cachetime } : undefined,
|
|
595
|
-
definePrefix: commandLine_1.cmdLine.define
|
|
596
|
-
}, {
|
|
597
|
-
helpers: {
|
|
424
|
+
const transformSourceToTemplateData = (s) => ({
|
|
425
|
+
...s,
|
|
426
|
+
length: s.content.length,
|
|
427
|
+
bytes: [...s.content].map((v) => `${v.toString(10)}`).join(','),
|
|
428
|
+
lengthGzip: s.contentGzip.length,
|
|
429
|
+
bytesGzip: [...s.contentGzip].map((v) => `${v.toString(10)}`).join(','),
|
|
430
|
+
isDefault: s.filename.startsWith('index.htm')
|
|
431
|
+
});
|
|
432
|
+
const postProcessCppCode = (code) => code
|
|
433
|
+
.split('\n')
|
|
434
|
+
.map((line) => line.trimEnd())
|
|
435
|
+
.filter(Boolean)
|
|
436
|
+
.map((line) => (line === '//' ? '' : line))
|
|
437
|
+
.join('\n')
|
|
438
|
+
.replace(/\\n{2}/, '\n');
|
|
439
|
+
const createHandlebarsHelpers = () => {
|
|
440
|
+
let switchValue;
|
|
441
|
+
return {
|
|
598
442
|
ifeq: function (a, b, options) {
|
|
599
|
-
if (a
|
|
443
|
+
if (a === b)
|
|
600
444
|
return options.fn(this);
|
|
601
445
|
return options.inverse(this);
|
|
602
446
|
},
|
|
@@ -605,16 +449,31 @@ const getCppCode = (sources, filesByExtension) => (0, handlebars_1.compile)(getT
|
|
|
605
449
|
return options.fn(this);
|
|
606
450
|
},
|
|
607
451
|
case: function (value, options) {
|
|
608
|
-
if (value
|
|
452
|
+
if (value === switchValue)
|
|
609
453
|
return options.fn(this);
|
|
610
454
|
return options.inverse(this);
|
|
611
455
|
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
.
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
const getCppCode = (sources, filesByExtension) => {
|
|
459
|
+
const template = (0, handlebars_1.compile)(getTemplate(commandLine_1.cmdLine.engine));
|
|
460
|
+
const templateData = {
|
|
461
|
+
commandLine: process.argv.slice(2).join(' '),
|
|
462
|
+
now: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
|
|
463
|
+
fileCount: sources.length.toString(),
|
|
464
|
+
fileSize: sources.reduce((previous, current) => previous + current.content.length, 0).toString(),
|
|
465
|
+
fileGzipSize: sources.reduce((previous, current) => previous + current.contentGzip.length, 0).toString(),
|
|
466
|
+
sources: sources.map((s) => transformSourceToTemplateData(s)),
|
|
467
|
+
filesByExtension,
|
|
468
|
+
etag: commandLine_1.cmdLine.etag,
|
|
469
|
+
gzip: commandLine_1.cmdLine.gzip,
|
|
470
|
+
created: commandLine_1.cmdLine.created,
|
|
471
|
+
version: commandLine_1.cmdLine.version,
|
|
472
|
+
methodName: commandLine_1.cmdLine.espmethod,
|
|
473
|
+
cacheTime: commandLine_1.cmdLine.cachetime ? { value: commandLine_1.cmdLine.cachetime } : undefined,
|
|
474
|
+
definePrefix: commandLine_1.cmdLine.define
|
|
475
|
+
};
|
|
476
|
+
const rawCode = template(templateData, { helpers: createHandlebarsHelpers() });
|
|
477
|
+
return postProcessCppCode(rawCode);
|
|
478
|
+
};
|
|
620
479
|
exports.getCppCode = getCppCode;
|
package/dist/file.js
CHANGED
|
@@ -14,35 +14,46 @@ const findSimilarFiles = (files) => {
|
|
|
14
14
|
const contentComparer = new Map();
|
|
15
15
|
for (const [filename, content] of files.entries()) {
|
|
16
16
|
const hash = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const existingFiles = contentComparer.get(hash);
|
|
18
|
+
if (existingFiles) {
|
|
19
|
+
existingFiles.push(filename);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
20
22
|
contentComparer.set(hash, [filename]);
|
|
23
|
+
}
|
|
21
24
|
}
|
|
22
25
|
const result = [];
|
|
23
|
-
for (const filenames of contentComparer.values())
|
|
24
|
-
if (filenames.length > 1)
|
|
26
|
+
for (const filenames of contentComparer.values()) {
|
|
27
|
+
if (filenames.length > 1) {
|
|
25
28
|
result.push(filenames);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
26
31
|
return result;
|
|
27
32
|
};
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
33
|
+
const shouldSkipFile = (filename, allFilenames) => {
|
|
34
|
+
const extension = node_path_1.default.extname(filename);
|
|
35
|
+
const compressedExtensions = ['.gz', '.brottli', '.br'];
|
|
36
|
+
if (compressedExtensions.includes(extension)) {
|
|
37
|
+
const original = filename.slice(0, -1 * extension.length);
|
|
38
|
+
if (allFilenames.includes(original)) {
|
|
39
|
+
console.log((0, consoleColor_1.redLog)(` ${filename} skipped because is perhaps a compressed version of ${original}`));
|
|
40
|
+
return true;
|
|
38
41
|
}
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
const getFiles = () => {
|
|
46
|
+
const allFilenames = (0, glob_1.globSync)('**/*', { cwd: commandLine_1.cmdLine.sourcepath, nodir: true });
|
|
47
|
+
const filenames = allFilenames.filter((filename) => !shouldSkipFile(filename, allFilenames));
|
|
41
48
|
const result = new Map();
|
|
42
|
-
for (const filename of filenames)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
for (const filename of filenames) {
|
|
50
|
+
const filePath = node_path_1.default.join(commandLine_1.cmdLine.sourcepath, filename);
|
|
51
|
+
result.set(filename, (0, node_fs_1.readFileSync)(filePath, { flag: 'r' }));
|
|
52
|
+
}
|
|
53
|
+
const duplicates = findSimilarFiles(result);
|
|
54
|
+
for (const sameFiles of duplicates) {
|
|
55
|
+
console.log((0, consoleColor_1.yellowLog)(` ${sameFiles.join(', ')} files look like identical`));
|
|
56
|
+
}
|
|
46
57
|
return result;
|
|
47
58
|
};
|
|
48
59
|
exports.getFiles = getFiles;
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const commandLine_1 = require("./commandLine");
|
|
|
12
12
|
const consoleColor_1 = require("./consoleColor");
|
|
13
13
|
const cppCode_1 = require("./cppCode");
|
|
14
14
|
const file_1 = require("./file");
|
|
15
|
+
const GZIP_MIN_SIZE = 1024;
|
|
16
|
+
const GZIP_MIN_REDUCTION_RATIO = 0.85;
|
|
15
17
|
const summary = {
|
|
16
18
|
filecount: 0,
|
|
17
19
|
size: 0,
|
|
@@ -19,6 +21,36 @@ const summary = {
|
|
|
19
21
|
};
|
|
20
22
|
const sources = [];
|
|
21
23
|
const filesByExtension = [];
|
|
24
|
+
const shouldUseGzip = (originalSize, compressedSize) => originalSize > GZIP_MIN_SIZE && compressedSize < originalSize * GZIP_MIN_REDUCTION_RATIO;
|
|
25
|
+
const calculateCompressionRatio = (originalSize, compressedSize) => Math.round((compressedSize / originalSize) * 100);
|
|
26
|
+
const formatCompressionLog = (filename, padding, originalSize, compressedSize, useGzip) => {
|
|
27
|
+
const ratio = calculateCompressionRatio(originalSize, compressedSize);
|
|
28
|
+
const sizeInfo = `(${originalSize} -> ${compressedSize} = ${ratio}%)`;
|
|
29
|
+
if (useGzip) {
|
|
30
|
+
return (0, consoleColor_1.greenLog)(` [${filename}] ${padding} ✓ gzip used ${sizeInfo}`);
|
|
31
|
+
}
|
|
32
|
+
const tooSmall = originalSize <= GZIP_MIN_SIZE ? '(too small) ' : '';
|
|
33
|
+
return (0, consoleColor_1.yellowLog)(` [${filename}] ${padding} x gzip unused ${tooSmall}${sizeInfo}`);
|
|
34
|
+
};
|
|
35
|
+
const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, md5, isGzip) => ({
|
|
36
|
+
filename,
|
|
37
|
+
dataname,
|
|
38
|
+
datanameUpperCase: dataname.toUpperCase(),
|
|
39
|
+
content,
|
|
40
|
+
contentGzip: isGzip ? contentGzip : content,
|
|
41
|
+
isGzip,
|
|
42
|
+
mime: mimeType,
|
|
43
|
+
md5
|
|
44
|
+
});
|
|
45
|
+
const updateExtensionGroup = (extension) => {
|
|
46
|
+
const group = filesByExtension.find((fe) => fe.extension === extension);
|
|
47
|
+
if (group) {
|
|
48
|
+
group.count += 1;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
filesByExtension.push({ extension, count: 1 });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
22
54
|
console.log('Collecting source files');
|
|
23
55
|
const files = (0, file_1.getFiles)();
|
|
24
56
|
if (files.size === 0) {
|
|
@@ -36,42 +68,15 @@ for (const [originalFilename, content] of files) {
|
|
|
36
68
|
let extension = node_path_1.default.extname(filename).toUpperCase();
|
|
37
69
|
if (extension.startsWith('.'))
|
|
38
70
|
extension = extension.slice(1);
|
|
39
|
-
|
|
40
|
-
if (group)
|
|
41
|
-
group.count += 1;
|
|
42
|
-
else
|
|
43
|
-
filesByExtension.push({ extension, count: 1 });
|
|
71
|
+
updateExtensionGroup(extension);
|
|
44
72
|
const md5 = (0, node_crypto_1.createHash)('md5').update(content).digest('hex');
|
|
45
73
|
summary.size += content.length;
|
|
46
74
|
const zipContent = (0, node_zlib_1.gzipSync)(content, { level: 9 });
|
|
47
75
|
summary.gzipsize += zipContent.length;
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
dataname,
|
|
53
|
-
datanameUpperCase: dataname.toUpperCase(),
|
|
54
|
-
content,
|
|
55
|
-
contentGzip: zipContent,
|
|
56
|
-
isGzip: true,
|
|
57
|
-
mime: mimeType,
|
|
58
|
-
md5
|
|
59
|
-
});
|
|
60
|
-
console.log((0, consoleColor_1.greenLog)(` [${originalFilename}] ${' '.repeat(longestFilename - originalFilename.length)} ✓ gzip used (${content.length} -> ${zipContent.length} = ${zipRatio}%)`));
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
sources.push({
|
|
64
|
-
filename,
|
|
65
|
-
dataname,
|
|
66
|
-
datanameUpperCase: dataname.toUpperCase(),
|
|
67
|
-
content,
|
|
68
|
-
contentGzip: content,
|
|
69
|
-
isGzip: false,
|
|
70
|
-
mime: mimeType,
|
|
71
|
-
md5
|
|
72
|
-
});
|
|
73
|
-
console.log((0, consoleColor_1.yellowLog)(` [${originalFilename}] ${' '.repeat(longestFilename - originalFilename.length)} x gzip unused ${content.length <= 1024 ? `(too small) ` : ''}(${content.length} -> ${zipContent.length} = ${zipRatio}%)`));
|
|
74
|
-
}
|
|
76
|
+
const useGzip = shouldUseGzip(content.length, zipContent.length);
|
|
77
|
+
sources.push(createSourceEntry(filename, dataname, content, zipContent, mimeType, md5, useGzip));
|
|
78
|
+
const padding = ' '.repeat(longestFilename - originalFilename.length);
|
|
79
|
+
console.log(formatCompressionLog(originalFilename, padding, content.length, zipContent.length, useGzip));
|
|
75
80
|
}
|
|
76
81
|
console.log('');
|
|
77
82
|
filesByExtension.sort((left, right) => left.extension.localeCompare(right.extension));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelteesp32",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "Convert Svelte (or any frontend) JS application to serve it from ESP32 webserver (PsychicHttp)",
|
|
5
5
|
"author": "BCsabaEngine",
|
|
6
6
|
"license": "ISC",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"engines": {
|
|
11
|
-
"node": ">=
|
|
11
|
+
"node": ">=20",
|
|
12
12
|
"npm": ">=9"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"dev:async": "nodemon src/index.ts -- -e async -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=true --gzip=true --cachetime=86400 --version=v$npm_package_version",
|
|
32
32
|
"dev:psychic": "nodemon src/index.ts -- -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=false --gzip=false --version=v$npm_package_version",
|
|
33
33
|
"dev:psychic2": "nodemon src/index.ts -- -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=false --gzip=false --version=v$npm_package_version",
|
|
34
|
-
"test:
|
|
34
|
+
"test:esp32": "./package.script && ~/.platformio/penv/bin/pio run -d ./demo/esp32",
|
|
35
|
+
"test:esp32idf": "./package.script && ~/.platformio/penv/bin/pio run -d ./demo/esp32idf",
|
|
36
|
+
"test:all": "npm run test:esp32 && npm run test:esp32idf",
|
|
35
37
|
"clean": "tsc --build --clean",
|
|
36
38
|
"build": "tsc --build --clean && tsc --build --force",
|
|
37
39
|
"format:check": "prettier --check .",
|
|
@@ -54,22 +56,22 @@
|
|
|
54
56
|
"espasyncwebserver"
|
|
55
57
|
],
|
|
56
58
|
"devDependencies": {
|
|
57
|
-
"@types/mime-types": "^
|
|
58
|
-
"@types/node": "^
|
|
59
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
60
|
-
"@typescript-eslint/parser": "^8.
|
|
61
|
-
"eslint": "^9.
|
|
62
|
-
"eslint-config-prettier": "^10.1.
|
|
59
|
+
"@types/mime-types": "^3.0.1",
|
|
60
|
+
"@types/node": "^24.8.1",
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
|
62
|
+
"@typescript-eslint/parser": "^8.46.1",
|
|
63
|
+
"eslint": "^9.38.0",
|
|
64
|
+
"eslint-config-prettier": "^10.1.8",
|
|
63
65
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
64
|
-
"eslint-plugin-unicorn": "^
|
|
65
|
-
"nodemon": "^3.1.
|
|
66
|
-
"prettier": "^3.
|
|
66
|
+
"eslint-plugin-unicorn": "^61.0.2",
|
|
67
|
+
"nodemon": "^3.1.10",
|
|
68
|
+
"prettier": "^3.6.2",
|
|
67
69
|
"ts-node": "^10.9.2",
|
|
68
|
-
"tsx": "^4.
|
|
69
|
-
"typescript": "^5.
|
|
70
|
+
"tsx": "^4.20.6",
|
|
71
|
+
"typescript": "^5.9.3"
|
|
70
72
|
},
|
|
71
73
|
"dependencies": {
|
|
72
|
-
"glob": "^11.0.
|
|
74
|
+
"glob": "^11.0.3",
|
|
73
75
|
"handlebars": "^4.7.8",
|
|
74
76
|
"mime-types": "^3.0.1",
|
|
75
77
|
"ts-command-line-args": "^2.5.1"
|