svelteesp32 1.16.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  [Changelog](CHANGELOG.md)
8
8
 
9
+ <p align="center">
10
+ <img src="svelteesp32.png" alt="svelteesp32" />
11
+ </p>
12
+
9
13
  ---
10
14
 
11
15
  ## Why SvelteESP32?
@@ -21,7 +25,7 @@
21
25
  - **Smart Caching** — Built-in SHA256 ETags deliver HTTP 304 responses, slashing bandwidth on constrained devices.
22
26
  - **CI/CD Ready** — Simple npm package that slots into any build pipeline.
23
27
  - **Zero Runtime Overhead** — Data served directly from flash. No filesystem reads, no RAM allocation.
24
- - **4 Web Server Engines** — PsychicHttp V1/V2, ESPAsyncWebServer, and native ESP-IDF supported.
28
+ - **3 Web Server Engines** — PsychicHttpServer, ESPAsyncWebServer, and native ESP-IDF supported.
25
29
 
26
30
  ---
27
31
 
@@ -75,13 +79,13 @@ void setup() {
75
79
 
76
80
  ## What's New
77
81
 
82
+ - **v2.0.0** — **BREAKING**: PsychicHttpServer V2 is now the default `psychic` engine. The `psychic2` engine has been removed. Dry run mode, C++ identifier validation, improved MIME type warnings
78
83
  - **v1.16.0** — Size budget constraints (`--maxsize`, `--maxgzipsize`)
79
84
  - **v1.15.0** — `--basepath` for multiple frontends (e.g., `/admin`, `/app`)
80
85
  - **v1.13.0** — npm package variable interpolation in RC files
81
86
  - **v1.12.0** — RC file configuration support
82
87
  - **v1.11.0** — File exclusion patterns
83
88
  - **v1.9.0** — Native ESP-IDF engine
84
- - **v1.5.0** — PsychicHttp V2 support
85
89
 
86
90
  ---
87
91
 
@@ -108,9 +112,6 @@ Choose your web server engine:
108
112
  # PsychicHttpServer (recommended for ESP32)
109
113
  npx svelteesp32 -e psychic -s ./dist -o ./esp32/svelteesp32.h --etag=true
110
114
 
111
- # PsychicHttpServer V2
112
- npx svelteesp32 -e psychic2 -s ./dist -o ./esp32/svelteesp32.h --etag=true
113
-
114
115
  # ESPAsyncWebServer (ESP32 + ESP8266)
115
116
  npx svelteesp32 -e async -s ./dist -o ./esp32/svelteesp32.h --etag=true
116
117
 
@@ -141,7 +142,7 @@ Watch your files get optimized in real-time:
141
142
 
142
143
  ### ESP32 Integration
143
144
 
144
- **PsychicHttpServer (Recommended)**
145
+ **PsychicHttpServer V2 (Recommended)**
145
146
 
146
147
  ```c
147
148
  #include <PsychicHttp.h>
@@ -184,14 +185,14 @@ void app_main() {
184
185
  }
185
186
  ```
186
187
 
187
- Working examples: [Arduino/PlatformIO](demo/esp32) | [ESP-IDF](demo/esp32idf)
188
+ Working examples with LED control via web interface: [Arduino/PlatformIO](demo/esp32) | [ESP-IDF](demo/esp32idf)
188
189
 
189
190
  ### What Gets Generated
190
191
 
191
192
  The generated header file includes everything your ESP needs:
192
193
 
193
194
  ```c
194
- //engine: PsychicHttpServer
195
+ //engine: PsychicHttpServer V2
195
196
  //config: engine=psychic sourcepath=./dist outputfile=./output.h etag=true gzip=true cachetime=0 espmethod=initSvelteStaticFiles define=SVELTEESP32
196
197
  //
197
198
  #define SVELTEESP32_COUNT 5
@@ -232,23 +233,21 @@ const size_t SVELTEESP32_FILE_COUNT = sizeof(SVELTEESP32_FILES) / sizeof(SVELTEE
232
233
  extern "C" void __attribute__((weak)) SVELTEESP32_onFileServed(const char* path, int statusCode) {}
233
234
 
234
235
  void initSvelteStaticFiles(PsychicHttpServer * server) {
235
- server->on("/assets/index-KwubEIf-.js", HTTP_GET, [](PsychicRequest * request) {
236
+ server->on("/assets/index-KwubEIf-.js", HTTP_GET, [](PsychicRequest * request, PsychicResponse * response) {
236
237
  if (request->hasHeader("If-None-Match") &&
237
238
  request->header("If-None-Match").equals(etag_assets_index_KwubEIf__js)) {
238
- PsychicResponse response304(request);
239
- response304.setCode(304);
239
+ response->setCode(304);
240
240
  SVELTEESP32_onFileServed("/assets/index-KwubEIf-.js", 304);
241
- return response304.send();
241
+ return response->send();
242
242
  }
243
243
 
244
- PsychicResponse response(request);
245
- response.setContentType("text/javascript");
246
- response.addHeader("Content-Encoding", "gzip");
247
- response.addHeader("Cache-Control", "no-cache");
248
- response.addHeader("ETag", etag_assets_index_KwubEIf__js);
249
- response.setContent(datagzip_assets_index_KwubEIf__js, 12547);
244
+ response->setContentType("text/javascript");
245
+ response->addHeader("Content-Encoding", "gzip");
246
+ response->addHeader("Cache-Control", "no-cache");
247
+ response->addHeader("ETag", etag_assets_index_KwubEIf__js);
248
+ response->setContent(datagzip_assets_index_KwubEIf__js, 12547);
250
249
  SVELTEESP32_onFileServed("/assets/index-KwubEIf-.js", 200);
251
- return response.send();
250
+ return response->send();
252
251
  });
253
252
 
254
253
  // ... more routes
@@ -259,14 +258,13 @@ void initSvelteStaticFiles(PsychicHttpServer * server) {
259
258
 
260
259
  ## Supported Web Server Engines
261
260
 
262
- | Engine | Flag | Best For | Platform |
263
- | --------------------- | ------------- | ---------------------------- | --------------- |
264
- | **PsychicHttp V1** | `-e psychic` | Maximum performance | ESP32 only |
265
- | **PsychicHttp V2** | `-e psychic2` | Modern API + performance | ESP32 only |
266
- | **ESPAsyncWebServer** | `-e async` | Cross-platform compatibility | ESP32 + ESP8266 |
267
- | **Native ESP-IDF** | `-e espidf` | Pure ESP-IDF projects | ESP32 only |
261
+ | Engine | Flag | Best For | Platform |
262
+ | ------------------------ | ------------ | ---------------------------- | --------------- |
263
+ | **PsychicHttpServer V2** | `-e psychic` | Maximum performance | ESP32 only |
264
+ | **ESPAsyncWebServer** | `-e async` | Cross-platform compatibility | ESP32 + ESP8266 |
265
+ | **Native ESP-IDF** | `-e espidf` | Pure ESP-IDF projects | ESP32 only |
268
266
 
269
- **Recommendation:** For ESP32-only projects, use PsychicHttpServer for the fastest, most stable experience.
267
+ **Recommendation:** For ESP32-only projects, use PsychicHttpServer V2 (`-e psychic`) for the fastest, most stable experience.
270
268
 
271
269
  **Note:** For PsychicHttp, configure `server.config.max_uri_handlers` to match your file count.
272
270
 
@@ -289,7 +287,7 @@ Reduce bandwidth dramatically with HTTP 304 "Not Modified" responses. When a bro
289
287
  - **Minimal overhead** — adds ~1-3% code size for significant bandwidth savings
290
288
  - **Compiler mode** — use `--etag=compiler` and control via `-D SVELTEESP32_ENABLE_ETAG`
291
289
 
292
- All four engines support full ETag validation.
290
+ All three engines support full ETag validation.
293
291
 
294
292
  ### Browser Cache Control
295
293
 
@@ -403,24 +401,25 @@ Called for every response (200 = content served, 304 = cache hit).
403
401
 
404
402
  ## CLI Reference
405
403
 
406
- | Option | Description | Default |
407
- | ---------------- | ------------------------------------------------- | ----------------------- |
408
- | `-s` | Source folder with compiled web files | (required) |
409
- | `-e` | Web server engine (psychic/psychic2/async/espidf) | `psychic` |
410
- | `-o` | Output header file path | `svelteesp32.h` |
411
- | `--etag` | ETag caching (true/false/compiler) | `false` |
412
- | `--gzip` | Gzip compression (true/false/compiler) | `true` |
413
- | `--exclude` | Exclude files by glob pattern | System files |
414
- | `--basepath` | URL prefix for all routes | (none) |
415
- | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
416
- | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
417
- | `--cachetime` | Cache-Control max-age in seconds | `0` |
418
- | `--version` | Version string in header | (none) |
419
- | `--define` | C++ define prefix | `SVELTEESP32` |
420
- | `--espmethod` | Init function name | `initSvelteStaticFiles` |
421
- | `--config` | Custom RC file path | `.svelteesp32rc.json` |
422
- | `--noindexcheck` | Skip index.html validation | `false` |
423
- | `-h` | Show help | |
404
+ | Option | Description | Default |
405
+ | ---------------- | ------------------------------------------------ | ----------------------- |
406
+ | `-s` | Source folder with compiled web files | (required) |
407
+ | `-e` | Web server engine (psychic/async/espidf) | `psychic` |
408
+ | `-o` | Output header file path | `svelteesp32.h` |
409
+ | `--etag` | ETag caching (true/false/compiler) | `false` |
410
+ | `--gzip` | Gzip compression (true/false/compiler) | `true` |
411
+ | `--exclude` | Exclude files by glob pattern | System files |
412
+ | `--basepath` | URL prefix for all routes | (none) |
413
+ | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
414
+ | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
415
+ | `--cachetime` | Cache-Control max-age in seconds | `0` |
416
+ | `--version` | Version string in header | (none) |
417
+ | `--define` | C++ define prefix | `SVELTEESP32` |
418
+ | `--espmethod` | Init function name | `initSvelteStaticFiles` |
419
+ | `--config` | Custom RC file path | `.svelteesp32rc.json` |
420
+ | `--dryrun` | Show summary without writing output | `false` |
421
+ | `--noindexcheck` | Skip index.html validation | `false` |
422
+ | `-h` | Show help | |
424
423
 
425
424
  ---
426
425
 
@@ -439,7 +438,8 @@ Store your settings in `.svelteesp32rc.json` for zero-argument builds:
439
438
  "basepath": "/ui",
440
439
  "maxsize": "400k",
441
440
  "maxgzipsize": "150k",
442
- "noindexcheck": false
441
+ "noindexcheck": false,
442
+ "dryrun": false
443
443
  }
444
444
  ```
445
445
 
@@ -1,5 +1,5 @@
1
1
  interface ICopyFilesArguments {
2
- engine: 'psychic' | 'psychic2' | 'async' | 'espidf';
2
+ engine: 'psychic' | 'async' | 'espidf';
3
3
  sourcepath: string;
4
4
  outputfile: string;
5
5
  espmethod: string;
@@ -14,10 +14,11 @@ interface ICopyFilesArguments {
14
14
  maxSize?: number;
15
15
  maxGzipSize?: number;
16
16
  noIndexCheck?: boolean;
17
+ dryRun?: boolean;
17
18
  help?: boolean;
18
19
  }
19
20
  interface IRcFileConfig {
20
- engine?: 'psychic' | 'psychic2' | 'async' | 'espidf';
21
+ engine?: 'psychic' | 'async' | 'espidf';
21
22
  sourcepath?: string;
22
23
  outputfile?: string;
23
24
  espmethod?: string;
@@ -32,11 +33,13 @@ interface IRcFileConfig {
32
33
  maxsize?: number | string;
33
34
  maxgzipsize?: number | string;
34
35
  noindexcheck?: boolean;
36
+ dryrun?: boolean;
35
37
  }
38
+ declare function validateCppIdentifier(value: string, name: string): string;
36
39
  declare function parseSize(value: string, name: string): number;
37
40
  declare function getNpmPackageVariable(packageJson: Record<string, unknown>, variableName: string): string | undefined;
38
41
  declare function hasNpmVariables(config: IRcFileConfig): boolean;
39
42
  declare function interpolateNpmVariables(config: IRcFileConfig, rcFilePath: string): IRcFileConfig;
40
- export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables, parseSize };
43
+ export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables, parseSize, validateCppIdentifier };
41
44
  export declare function formatConfiguration(cmdLine: ICopyFilesArguments): string;
42
45
  export declare const cmdLine: ICopyFilesArguments;
@@ -8,6 +8,7 @@ exports.getNpmPackageVariable = getNpmPackageVariable;
8
8
  exports.hasNpmVariables = hasNpmVariables;
9
9
  exports.interpolateNpmVariables = interpolateNpmVariables;
10
10
  exports.parseSize = parseSize;
11
+ exports.validateCppIdentifier = validateCppIdentifier;
11
12
  exports.formatConfiguration = formatConfiguration;
12
13
  const node_fs_1 = require("node:fs");
13
14
  const node_os_1 = require("node:os");
@@ -23,7 +24,7 @@ Configuration:
23
24
 
24
25
  Options:
25
26
  -e, --engine <value> The engine for which the include file is created
26
- (psychic|psychic2|async|espidf) (default: "psychic")
27
+ (psychic|async|espidf) (default: "psychic")
27
28
  -s, --sourcepath <path> Source dist folder contains compiled web files (required)
28
29
  -o, --outputfile <path> Generated output file with path (default: "svelteesp32.h")
29
30
  --etag <value> Use ETAG header for cache (true|false|compiler) (default: "false")
@@ -38,6 +39,7 @@ Options:
38
39
  --basepath <path> URL prefix for all routes (e.g., "/ui") (default: "")
39
40
  --maxsize <size> Maximum total uncompressed size (e.g., 400k, 1.5m, 409600)
40
41
  --maxgzipsize <size> Maximum total gzip size (e.g., 150k, 1m, 153600)
42
+ --dryrun Show summary without writing the output file (default: false)
41
43
  -h, --help Shows this help
42
44
 
43
45
  RC File:
@@ -64,7 +66,7 @@ RC File:
64
66
  process.exit(0);
65
67
  }
66
68
  function validateEngine(value) {
67
- if (value === 'psychic' || value === 'psychic2' || value === 'async' || value === 'espidf')
69
+ if (value === 'psychic' || value === 'async' || value === 'espidf')
68
70
  return value;
69
71
  console.error((0, errorMessages_1.getInvalidEngineError)(value));
70
72
  process.exit(1);
@@ -74,6 +76,11 @@ function validateTriState(value, name) {
74
76
  return value;
75
77
  throw new Error(`Invalid ${name}: ${value}`);
76
78
  }
79
+ function validateCppIdentifier(value, name) {
80
+ if (!/^[A-Z_a-z]\w*$/.test(value))
81
+ throw new Error(`${name} must be a valid C++ identifier (letters, digits, underscores, not starting with a digit): ${value}`);
82
+ return value;
83
+ }
77
84
  function validateBasePath(value) {
78
85
  if (value === '')
79
86
  return value;
@@ -258,7 +265,8 @@ function validateRcConfig(config, rcPath) {
258
265
  'basepath',
259
266
  'maxsize',
260
267
  'maxgzipsize',
261
- 'noindexcheck'
268
+ 'noindexcheck',
269
+ 'dryrun'
262
270
  ]);
263
271
  for (const key of Object.keys(configObject))
264
272
  if (!validKeys.has(key))
@@ -269,9 +277,16 @@ function validateRcConfig(config, rcPath) {
269
277
  configObject['etag'] = validateTriState(configObject['etag'], 'etag');
270
278
  if (configObject['gzip'] !== undefined)
271
279
  configObject['gzip'] = validateTriState(configObject['gzip'], 'gzip');
272
- if (configObject['cachetime'] !== undefined &&
273
- (typeof configObject['cachetime'] !== 'number' || Number.isNaN(configObject['cachetime'])))
274
- throw new TypeError(`Invalid cachetime in RC file: ${configObject['cachetime']}`);
280
+ if (configObject['espmethod'] !== undefined)
281
+ validateCppIdentifier(configObject['espmethod'], 'espmethod');
282
+ if (configObject['define'] !== undefined)
283
+ validateCppIdentifier(configObject['define'], 'define');
284
+ if (configObject['cachetime'] !== undefined) {
285
+ if (typeof configObject['cachetime'] !== 'number' || Number.isNaN(configObject['cachetime']))
286
+ throw new TypeError(`Invalid cachetime in RC file: ${configObject['cachetime']}`);
287
+ if (configObject['cachetime'] < 0)
288
+ throw new TypeError(`Invalid cachetime in RC file: ${configObject['cachetime']} (must be non-negative)`);
289
+ }
275
290
  if (configObject['exclude'] !== undefined) {
276
291
  if (!Array.isArray(configObject['exclude']))
277
292
  throw new TypeError("'exclude' in RC file must be an array");
@@ -305,6 +320,8 @@ function validateRcConfig(config, rcPath) {
305
320
  }
306
321
  if (configObject['noindexcheck'] !== undefined && typeof configObject['noindexcheck'] !== 'boolean')
307
322
  throw new TypeError(`Invalid noindexcheck in RC file: ${configObject['noindexcheck']} (must be boolean)`);
323
+ if (configObject['dryrun'] !== undefined && typeof configObject['dryrun'] !== 'boolean')
324
+ throw new TypeError(`Invalid dryrun in RC file: ${configObject['dryrun']} (must be boolean)`);
308
325
  return configObject;
309
326
  }
310
327
  function parseArguments() {
@@ -368,9 +385,67 @@ function parseArguments() {
368
385
  result.maxGzipSize = rcConfig.maxgzipsize;
369
386
  if (rcConfig.noindexcheck !== undefined)
370
387
  result.noIndexCheck = rcConfig.noindexcheck;
388
+ if (rcConfig.dryrun !== undefined)
389
+ result.dryRun = rcConfig.dryrun;
371
390
  if (rcConfig.exclude && rcConfig.exclude.length > 0)
372
391
  result.exclude = [...rcConfig.exclude];
373
392
  const cliExclude = [];
393
+ function applyFlag(flagName, value) {
394
+ switch (flagName) {
395
+ case 'config':
396
+ break;
397
+ case 'engine':
398
+ result.engine = validateEngine(value);
399
+ break;
400
+ case 'sourcepath':
401
+ result.sourcepath = value;
402
+ break;
403
+ case 'outputfile':
404
+ result.outputfile = value;
405
+ break;
406
+ case 'etag':
407
+ result.etag = validateTriState(value, 'etag');
408
+ break;
409
+ case 'gzip':
410
+ result.gzip = validateTriState(value, 'gzip');
411
+ break;
412
+ case 'version':
413
+ result.version = value;
414
+ break;
415
+ case 'espmethod':
416
+ result.espmethod = validateCppIdentifier(value, 'espmethod');
417
+ break;
418
+ case 'define':
419
+ result.define = validateCppIdentifier(value, 'define');
420
+ break;
421
+ case 'cachetime':
422
+ result.cachetime = Number.parseInt(value, 10);
423
+ if (Number.isNaN(result.cachetime))
424
+ throw new TypeError(`Invalid cachetime: ${value}`);
425
+ if (result.cachetime < 0)
426
+ throw new TypeError(`Invalid cachetime: ${value} (must be non-negative)`);
427
+ break;
428
+ case 'exclude': {
429
+ const patterns = value
430
+ .split(',')
431
+ .map((p) => p.trim())
432
+ .filter(Boolean);
433
+ cliExclude.push(...patterns);
434
+ break;
435
+ }
436
+ case 'basepath':
437
+ result.basePath = validateBasePath(value);
438
+ break;
439
+ case 'maxsize':
440
+ result.maxSize = parseSize(value, '--maxsize');
441
+ break;
442
+ case 'maxgzipsize':
443
+ result.maxGzipSize = parseSize(value, '--maxgzipsize');
444
+ break;
445
+ default:
446
+ throw new Error(`Unknown flag: --${flagName}`);
447
+ }
448
+ }
374
449
  for (let index = 0; index < arguments_.length; index++) {
375
450
  const argument = arguments_[index];
376
451
  if (!argument)
@@ -383,59 +458,7 @@ function parseArguments() {
383
458
  const value = parts.slice(1).join('=');
384
459
  if (!flag || !value)
385
460
  throw new Error(`Invalid argument format: ${argument}`);
386
- const flagName = flag.slice(2);
387
- switch (flagName) {
388
- case 'config':
389
- break;
390
- case 'engine':
391
- result.engine = validateEngine(value);
392
- break;
393
- case 'sourcepath':
394
- result.sourcepath = value;
395
- break;
396
- case 'outputfile':
397
- result.outputfile = value;
398
- break;
399
- case 'etag':
400
- result.etag = validateTriState(value, 'etag');
401
- break;
402
- case 'gzip':
403
- result.gzip = validateTriState(value, 'gzip');
404
- break;
405
- case 'version':
406
- result.version = value;
407
- break;
408
- case 'espmethod':
409
- result.espmethod = value;
410
- break;
411
- case 'define':
412
- result.define = value;
413
- break;
414
- case 'cachetime':
415
- result.cachetime = Number.parseInt(value, 10);
416
- if (Number.isNaN(result.cachetime))
417
- throw new TypeError(`Invalid cachetime: ${value}`);
418
- break;
419
- case 'exclude': {
420
- const patterns = value
421
- .split(',')
422
- .map((p) => p.trim())
423
- .filter(Boolean);
424
- cliExclude.push(...patterns);
425
- break;
426
- }
427
- case 'basepath':
428
- result.basePath = validateBasePath(value);
429
- break;
430
- case 'maxsize':
431
- result.maxSize = parseSize(value, '--maxsize');
432
- break;
433
- case 'maxgzipsize':
434
- result.maxGzipSize = parseSize(value, '--maxgzipsize');
435
- break;
436
- default:
437
- throw new Error(`Unknown flag: ${flag}`);
438
- }
461
+ applyFlag(flag.slice(2), value);
439
462
  continue;
440
463
  }
441
464
  if (argument === '--created') {
@@ -446,6 +469,10 @@ function parseArguments() {
446
469
  result.noIndexCheck = true;
447
470
  continue;
448
471
  }
472
+ if (argument === '--dryrun' || argument === '--dry-run') {
473
+ result.dryRun = true;
474
+ continue;
475
+ }
449
476
  if (argument.startsWith('-') && !argument.startsWith('--')) {
450
477
  const flag = argument.slice(1);
451
478
  const nextArgument = arguments_[index + 1];
@@ -474,72 +501,8 @@ function parseArguments() {
474
501
  const nextArgument = arguments_[index + 1];
475
502
  if (!nextArgument || nextArgument.startsWith('-'))
476
503
  throw new Error(`Missing value for flag: ${argument}`);
477
- switch (flag) {
478
- case 'config':
479
- index++;
480
- break;
481
- case 'engine':
482
- result.engine = validateEngine(nextArgument);
483
- index++;
484
- break;
485
- case 'sourcepath':
486
- result.sourcepath = nextArgument;
487
- index++;
488
- break;
489
- case 'outputfile':
490
- result.outputfile = nextArgument;
491
- index++;
492
- break;
493
- case 'etag':
494
- result.etag = validateTriState(nextArgument, 'etag');
495
- index++;
496
- break;
497
- case 'gzip':
498
- result.gzip = validateTriState(nextArgument, 'gzip');
499
- index++;
500
- break;
501
- case 'version':
502
- result.version = nextArgument;
503
- index++;
504
- break;
505
- case 'espmethod':
506
- result.espmethod = nextArgument;
507
- index++;
508
- break;
509
- case 'define':
510
- result.define = nextArgument;
511
- index++;
512
- break;
513
- case 'cachetime':
514
- result.cachetime = Number.parseInt(nextArgument, 10);
515
- if (Number.isNaN(result.cachetime))
516
- throw new TypeError(`Invalid cachetime: ${nextArgument}`);
517
- index++;
518
- break;
519
- case 'exclude': {
520
- const patterns = nextArgument
521
- .split(',')
522
- .map((p) => p.trim())
523
- .filter(Boolean);
524
- cliExclude.push(...patterns);
525
- index++;
526
- break;
527
- }
528
- case 'basepath':
529
- result.basePath = validateBasePath(nextArgument);
530
- index++;
531
- break;
532
- case 'maxsize':
533
- result.maxSize = parseSize(nextArgument, '--maxsize');
534
- index++;
535
- break;
536
- case 'maxgzipsize':
537
- result.maxGzipSize = parseSize(nextArgument, '--maxgzipsize');
538
- index++;
539
- break;
540
- default:
541
- throw new Error(`Unknown flag: --${flag}`);
542
- }
504
+ applyFlag(flag, nextArgument);
505
+ index++;
543
506
  continue;
544
507
  }
545
508
  throw new Error(`Unknown argument: ${argument}`);
package/dist/cppCode.js CHANGED
@@ -120,212 +120,6 @@ extern "C" void __attribute__((weak)) {{definePrefix}}_onFileServed(const char*
120
120
  const psychicTemplate = `
121
121
  //engine: PsychicHttpServer
122
122
  //config: {{{config}}}
123
- //You should use server.config.max_uri_handlers = {{fileCount}}; or higher value to proper handles all files
124
- {{#if created }}
125
- //created: {{now}}
126
- {{/if}}
127
- //
128
- ${commonHeaderSection}
129
-
130
- //
131
- #include <Arduino.h>
132
- #include <PsychicHttp.h>
133
- #include <PsychicHttpsServer.h>
134
-
135
- //
136
- ${dataArraysSection(false)}
137
-
138
- //
139
- ${etagArraysSection}
140
-
141
- //
142
- ${manifestSection}
143
-
144
- //
145
- ${hookSection}
146
-
147
- //
148
- // Http Handlers
149
- void {{methodName}}(PsychicHttpServer * server) {
150
- {{#each sources}}
151
- //
152
- // {{this.filename}}
153
- {{#if this.isDefault}}{{#unless ../basePath}}server->defaultEndpoint = {{/unless}}{{/if}}server->on("{{../basePath}}/{{this.filename}}", HTTP_GET, [](PsychicRequest * request) {
154
-
155
- {{#switch ../etag}}
156
- {{#case "true"}}
157
- if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
158
- PsychicResponse response304(request);
159
- response304.setCode(304);
160
- {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
161
- return response304.send();
162
- }
163
- {{/case}}
164
- {{#case "compiler"}}
165
- #ifdef {{../definePrefix}}_ENABLE_ETAG
166
- if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
167
- PsychicResponse response304(request);
168
- response304.setCode(304);
169
- {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
170
- return response304.send();
171
- }
172
- #endif
173
- {{/case}}
174
- {{/switch}}
175
-
176
- PsychicResponse response(request);
177
- response.setContentType("{{this.mime}}");
178
-
179
- {{#switch ../gzip}}
180
- {{#case "true"}}
181
- {{#if this.isGzip}}
182
- response.addHeader("Content-Encoding", "gzip");
183
- {{/if}}
184
- {{/case}}
185
- {{#case "compiler"}}
186
- {{#if this.isGzip}}
187
- #ifdef {{../definePrefix}}_ENABLE_GZIP
188
- response.addHeader("Content-Encoding", "gzip");
189
- #endif
190
- {{/if}}
191
- {{/case}}
192
- {{/switch}}
193
-
194
- {{#switch ../etag}}
195
- {{#case "true"}}
196
- {{#../cacheTime}}
197
- response.addHeader("Cache-Control", "max-age={{value}}");
198
- {{/../cacheTime}}
199
- {{^../cacheTime}}
200
- response.addHeader("Cache-Control", "no-cache");
201
- {{/../cacheTime}}
202
- response.addHeader("ETag", etag_{{this.dataname}});
203
- {{/case}}
204
- {{#case "compiler"}}
205
- #ifdef {{../definePrefix}}_ENABLE_ETAG
206
- {{#../cacheTime}}
207
- response.addHeader("Cache-Control", "max-age={{value}}");
208
- {{/../cacheTime}}
209
- {{^../cacheTime}}
210
- response.addHeader("Cache-Control", "no-cache");
211
- {{/../cacheTime}}
212
- response.addHeader("ETag", etag_{{this.dataname}});
213
- #endif
214
- {{/case}}
215
- {{/switch}}
216
-
217
- {{#switch ../gzip}}
218
- {{#case "true"}}
219
- response.setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
220
- {{/case}}
221
- {{#case "false"}}
222
- response.setContent(data_{{this.dataname}}, {{this.length}});
223
- {{/case}}
224
- {{#case "compiler"}}
225
- #ifdef {{../definePrefix}}_ENABLE_GZIP
226
- response.setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
227
- #else
228
- response.setContent(data_{{this.dataname}}, {{this.length}});
229
- #endif
230
- {{/case}}
231
- {{/switch}}
232
-
233
- {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 200);
234
- return response.send();
235
- });
236
- {{#if this.isDefault}}{{#if ../basePath}}
237
- //
238
- // {{this.filename}} (base path route)
239
- server->on("{{../basePath}}", HTTP_GET, [](PsychicRequest * request) {
240
-
241
- {{#switch ../etag}}
242
- {{#case "true"}}
243
- if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
244
- PsychicResponse response304(request);
245
- response304.setCode(304);
246
- {{../definePrefix}}_onFileServed("{{../basePath}}", 304);
247
- return response304.send();
248
- }
249
- {{/case}}
250
- {{#case "compiler"}}
251
- #ifdef {{../definePrefix}}_ENABLE_ETAG
252
- if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
253
- PsychicResponse response304(request);
254
- response304.setCode(304);
255
- {{../definePrefix}}_onFileServed("{{../basePath}}", 304);
256
- return response304.send();
257
- }
258
- #endif
259
- {{/case}}
260
- {{/switch}}
261
-
262
- PsychicResponse response(request);
263
- response.setContentType("{{this.mime}}");
264
-
265
- {{#switch ../gzip}}
266
- {{#case "true"}}
267
- {{#if this.isGzip}}
268
- response.addHeader("Content-Encoding", "gzip");
269
- {{/if}}
270
- {{/case}}
271
- {{#case "compiler"}}
272
- {{#if this.isGzip}}
273
- #ifdef {{../definePrefix}}_ENABLE_GZIP
274
- response.addHeader("Content-Encoding", "gzip");
275
- #endif
276
- {{/if}}
277
- {{/case}}
278
- {{/switch}}
279
-
280
- {{#switch ../etag}}
281
- {{#case "true"}}
282
- {{#../cacheTime}}
283
- response.addHeader("Cache-Control", "max-age={{value}}");
284
- {{/../cacheTime}}
285
- {{^../cacheTime}}
286
- response.addHeader("Cache-Control", "no-cache");
287
- {{/../cacheTime}}
288
- response.addHeader("ETag", etag_{{this.dataname}});
289
- {{/case}}
290
- {{#case "compiler"}}
291
- #ifdef {{../definePrefix}}_ENABLE_ETAG
292
- {{#../cacheTime}}
293
- response.addHeader("Cache-Control", "max-age={{value}}");
294
- {{/../cacheTime}}
295
- {{^../cacheTime}}
296
- response.addHeader("Cache-Control", "no-cache");
297
- {{/../cacheTime}}
298
- response.addHeader("ETag", etag_{{this.dataname}});
299
- #endif
300
- {{/case}}
301
- {{/switch}}
302
-
303
- {{#switch ../gzip}}
304
- {{#case "true"}}
305
- response.setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
306
- {{/case}}
307
- {{#case "false"}}
308
- response.setContent(data_{{this.dataname}}, {{this.length}});
309
- {{/case}}
310
- {{#case "compiler"}}
311
- #ifdef {{../definePrefix}}_ENABLE_GZIP
312
- response.setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
313
- #else
314
- response.setContent(data_{{this.dataname}}, {{this.length}});
315
- #endif
316
- {{/case}}
317
- {{/switch}}
318
-
319
- {{../definePrefix}}_onFileServed("{{../basePath}}", 200);
320
- return response.send();
321
- });
322
- {{/if}}{{/if}}
323
-
324
- {{/each}}
325
- }`;
326
- const psychic2Template = `
327
- //engine: PsychicHttpServerV2
328
- //config: {{{config}}}
329
123
  {{#if created }}
330
124
  //created: {{now}}
331
125
  {{/if}}
@@ -704,21 +498,29 @@ const getTemplate = (engine) => {
704
498
  switch (engine) {
705
499
  case 'psychic':
706
500
  return psychicTemplate;
707
- case 'psychic2':
708
- return psychic2Template;
501
+ case 'async':
502
+ return asyncTemplate;
709
503
  case 'espidf':
710
504
  return cppCodeEspIdf_1.espidfTemplate;
711
505
  default:
712
- return asyncTemplate;
506
+ throw new Error(`Unknown engine: ${engine}`);
713
507
  }
714
508
  };
509
+ const bufferToByteString = (buffer) => {
510
+ if (buffer.length === 0)
511
+ return '';
512
+ let result = buffer[0].toString(10);
513
+ for (let index = 1; index < buffer.length; index++)
514
+ result += ',' + buffer[index].toString(10);
515
+ return result;
516
+ };
715
517
  const transformSourceToTemplateData = (s, etag) => ({
716
518
  ...s,
717
519
  length: s.content.length,
718
- bytes: [...s.content].map((v) => `${v.toString(10)}`).join(','),
520
+ bytes: bufferToByteString(s.content),
719
521
  lengthGzip: s.contentGzip.length,
720
- bytesGzip: [...s.contentGzip].map((v) => `${v.toString(10)}`).join(','),
721
- isDefault: s.filename.startsWith('index.htm'),
522
+ bytesGzip: bufferToByteString(s.contentGzip),
523
+ isDefault: s.filename === 'index.html' || s.filename === 'index.htm',
722
524
  gzipSizeForManifest: s.isGzip ? s.contentGzip.length : 0,
723
525
  etagForManifest: etag === 'false' ? 'NULL' : `etag_${s.dataname}`
724
526
  });
@@ -728,7 +530,7 @@ const postProcessCppCode = (code) => code
728
530
  .filter(Boolean)
729
531
  .map((line) => (line === '//' ? '' : line))
730
532
  .join('\n')
731
- .replace(/\\n{2}/, '\n');
533
+ .replace(/\n{2,}/g, '\n');
732
534
  const createHandlebarsHelpers = () => {
733
535
  let switchValue;
734
536
  return {
@@ -752,7 +554,10 @@ const getCppCode = (sources, filesByExtension) => {
752
554
  const template = (0, handlebars_1.compile)(getTemplate(commandLine_1.cmdLine.engine));
753
555
  const templateData = {
754
556
  config: (0, commandLine_1.formatConfiguration)(commandLine_1.cmdLine),
755
- now: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
557
+ now: (() => {
558
+ const d = new Date();
559
+ return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`;
560
+ })(),
756
561
  fileCount: sources.length.toString(),
757
562
  fileSize: sources.reduce((previous, current) => previous + current.content.length, 0).toString(),
758
563
  fileGzipSize: sources.reduce((previous, current) => previous + current.contentGzip.length, 0).toString(),
@@ -1 +1 @@
1
- export declare const espidfTemplate = "\n//engine: espidf\n//config: {{{config}}}\n{{#if created }}\n//created: {{now}}\n{{/if}}\n//\n\n{{#switch etag}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n{{#switch gzip}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n//\n{{#if version }}\n#define {{definePrefix}}_VERSION \"{{version}}\"\n{{/if}}\n#define {{definePrefix}}_COUNT {{fileCount}}\n#define {{definePrefix}}_SIZE {{fileSize}}\n#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}\n\n//\n{{#each sources}}\n#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}\n{{/each}}\n\n//\n{{#each filesByExtension}}\n#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}\n{{/each}}\n\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <esp_err.h>\n#include <esp_http_server.h>\n\n//\n{{#switch gzip}}\n{{#case \"true\"}}\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n#else\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n#endif \n{{/case}}\n{{/switch}}\n\n//\n{{#switch etag}}\n{{#case \"true\"}}\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n#endif\n{{/case}}\n{{/switch}}\n\n// File manifest struct (C-compatible typedef)\ntypedef struct {\n const char* path;\n uint32_t size;\n uint32_t gzipSize;\n const char* etag;\n const char* contentType;\n} {{definePrefix}}_FileInfo;\n\n// File manifest array\nstatic const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {\n{{#each sources}}\n { \"{{../basePath}}/{{this.filename}}\", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, \"{{this.mime}}\" },\n{{/each}}\n};\nstatic const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);\n\n// File served hook - override with your own implementation\n__attribute__((weak)) void {{definePrefix}}_onFileServed(const char* path, int statusCode) {}\n\n{{#each sources}}\n\nstatic esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)\n{\n{{#switch ../etag}}\n{{#case \"true\"}}\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 304);\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 304);\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n #endif\n{{/case}}\n{{/switch}}\n httpd_resp_set_type(req, \"{{this.mime}}\");\n{{#switch ../gzip}}\n{{#case \"true\"}}\n{{#if this.isGzip}}\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n{{/if}}\n{{/case}}\n{{#case \"compiler\"}}\n {{#if this.isGzip}}\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n #endif \n {{/if}}\n{{/case}}\n{{/switch}}\n\n{{#switch ../etag}}\n{{#case \"true\"}}\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n #endif \n{{/case}}\n{{/switch}}\n\n{{#switch ../gzip}}\n{{#case \"true\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n{{/case}}\n{{#case \"false\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n{{/case}}\n{{#case \"compiler\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n #else\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n #endif\n{{/case}}\n{{/switch}}\n return ESP_OK;\n}\n\n{{#if this.isDefault}}\nstatic const httpd_uri_t route_def_{{this.datanameUpperCase}} = {\n .uri = \"{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n{{/if}}\n\nstatic const httpd_uri_t route_{{this.datanameUpperCase}} = {\n .uri = \"{{../basePath}}/{{this.filename}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n\n{{/each}}\n\n\n\nstatic inline void {{methodName}}(httpd_handle_t server) {\n{{#each sources}}\n{{#if this.isDefault}}\n httpd_register_uri_handler(server, &route_def_{{this.datanameUpperCase}});\n{{/if}}\n httpd_register_uri_handler(server, &route_{{this.datanameUpperCase}});\n{{/each}}\n\n}";
1
+ export declare const espidfTemplate = "\n//engine: espidf\n//config: {{{config}}}\n{{#if created }}\n//created: {{now}}\n{{/if}}\n//\n\n{{#switch etag}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n{{#switch gzip}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n//\n{{#if version }}\n#define {{definePrefix}}_VERSION \"{{version}}\"\n{{/if}}\n#define {{definePrefix}}_COUNT {{fileCount}}\n#define {{definePrefix}}_SIZE {{fileSize}}\n#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}\n\n//\n{{#each sources}}\n#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}\n{{/each}}\n\n//\n{{#each filesByExtension}}\n#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}\n{{/each}}\n\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <esp_err.h>\n#include <esp_http_server.h>\n\n//\n{{#switch gzip}}\n{{#case \"true\"}}\n {{#each sources}}\nconst unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n {{#each sources}}\nconst unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n {{#each sources}}\nconst unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n#else\n {{#each sources}}\nconst unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n#endif \n{{/case}}\n{{/switch}}\n\n//\n{{#switch etag}}\n{{#case \"true\"}}\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n#endif\n{{/case}}\n{{/switch}}\n\n// File manifest struct (C-compatible typedef)\ntypedef struct {\n const char* path;\n uint32_t size;\n uint32_t gzipSize;\n const char* etag;\n const char* contentType;\n} {{definePrefix}}_FileInfo;\n\n// File manifest array\nstatic const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {\n{{#each sources}}\n { \"{{../basePath}}/{{this.filename}}\", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, \"{{this.mime}}\" },\n{{/each}}\n};\nstatic const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);\n\n// File served hook - override with your own implementation\n__attribute__((weak)) void {{definePrefix}}_onFileServed(const char* path, int statusCode) {}\n\n{{#each sources}}\n\nstatic esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)\n{\n{{#switch ../etag}}\n{{#case \"true\"}}\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (hdr_value == NULL) { httpd_resp_send_500(req); return ESP_FAIL; }\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 304);\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (hdr_value == NULL) { httpd_resp_send_500(req); return ESP_FAIL; }\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 304);\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n #endif\n{{/case}}\n{{/switch}}\n httpd_resp_set_type(req, \"{{this.mime}}\");\n{{#switch ../gzip}}\n{{#case \"true\"}}\n{{#if this.isGzip}}\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n{{/if}}\n{{/case}}\n{{#case \"compiler\"}}\n {{#if this.isGzip}}\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n #endif \n {{/if}}\n{{/case}}\n{{/switch}}\n\n{{#switch ../etag}}\n{{#case \"true\"}}\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n #endif \n{{/case}}\n{{/switch}}\n\n{{#switch ../gzip}}\n{{#case \"true\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n httpd_resp_send(req, (const char *)datagzip_{{this.dataname}}, {{this.lengthGzip}});\n{{/case}}\n{{#case \"false\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n httpd_resp_send(req, (const char *)data_{{this.dataname}}, {{this.length}});\n{{/case}}\n{{#case \"compiler\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_send(req, (const char *)datagzip_{{this.dataname}}, {{this.lengthGzip}});\n #else\n httpd_resp_send(req, (const char *)data_{{this.dataname}}, {{this.length}});\n #endif\n{{/case}}\n{{/switch}}\n return ESP_OK;\n}\n\n{{#if this.isDefault}}\nstatic const httpd_uri_t route_def_{{this.datanameUpperCase}} = {\n .uri = \"{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n{{/if}}\n\nstatic const httpd_uri_t route_{{this.datanameUpperCase}} = {\n .uri = \"{{../basePath}}/{{this.filename}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n\n{{/each}}\n\n\n\nstatic inline void {{methodName}}(httpd_handle_t server) {\n{{#each sources}}\n{{#if this.isDefault}}\n httpd_register_uri_handler(server, &route_def_{{this.datanameUpperCase}});\n{{/if}}\n httpd_register_uri_handler(server, &route_{{this.datanameUpperCase}});\n{{/each}}\n\n}";
@@ -63,22 +63,22 @@ exports.espidfTemplate = `
63
63
  {{#switch gzip}}
64
64
  {{#case "true"}}
65
65
  {{#each sources}}
66
- const char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
66
+ const unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
67
67
  {{/each}}
68
68
  {{/case}}
69
69
  {{#case "false"}}
70
70
  {{#each sources}}
71
- const char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
71
+ const unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
72
72
  {{/each}}
73
73
  {{/case}}
74
74
  {{#case "compiler"}}
75
75
  #ifdef {{definePrefix}}_ENABLE_GZIP
76
76
  {{#each sources}}
77
- const char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
77
+ const unsigned char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };
78
78
  {{/each}}
79
79
  #else
80
80
  {{#each sources}}
81
- const char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
81
+ const unsigned char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
82
82
  {{/each}}
83
83
  #endif
84
84
  {{/case}}
@@ -131,6 +131,7 @@ static esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)
131
131
  size_t hdr_len = httpd_req_get_hdr_value_len(req, "If-None-Match");
132
132
  if (hdr_len > 0) {
133
133
  char* hdr_value = malloc(hdr_len + 1);
134
+ if (hdr_value == NULL) { httpd_resp_send_500(req); return ESP_FAIL; }
134
135
  if (httpd_req_get_hdr_value_str(req, "If-None-Match", hdr_value, hdr_len + 1) == ESP_OK) {
135
136
  if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {
136
137
  free(hdr_value);
@@ -148,6 +149,7 @@ static esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)
148
149
  size_t hdr_len = httpd_req_get_hdr_value_len(req, "If-None-Match");
149
150
  if (hdr_len > 0) {
150
151
  char* hdr_value = malloc(hdr_len + 1);
152
+ if (hdr_value == NULL) { httpd_resp_send_500(req); return ESP_FAIL; }
151
153
  if (httpd_req_get_hdr_value_str(req, "If-None-Match", hdr_value, hdr_len + 1) == ESP_OK) {
152
154
  if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {
153
155
  free(hdr_value);
@@ -204,18 +206,18 @@ static esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)
204
206
  {{#switch ../gzip}}
205
207
  {{#case "true"}}
206
208
  {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 200);
207
- httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});
209
+ httpd_resp_send(req, (const char *)datagzip_{{this.dataname}}, {{this.lengthGzip}});
208
210
  {{/case}}
209
211
  {{#case "false"}}
210
212
  {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 200);
211
- httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});
213
+ httpd_resp_send(req, (const char *)data_{{this.dataname}}, {{this.length}});
212
214
  {{/case}}
213
215
  {{#case "compiler"}}
214
216
  {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 200);
215
217
  #ifdef {{../definePrefix}}_ENABLE_GZIP
216
- httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});
218
+ httpd_resp_send(req, (const char *)datagzip_{{this.dataname}}, {{this.lengthGzip}});
217
219
  #else
218
- httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});
220
+ httpd_resp_send(req, (const char *)data_{{this.dataname}}, {{this.length}});
219
221
  #endif
220
222
  {{/case}}
221
223
  {{/switch}}
@@ -2,4 +2,4 @@ export declare function getMissingIndexError(engine: string): string;
2
2
  export declare function getInvalidEngineError(attempted: string): string;
3
3
  export declare function getSourcepathNotFoundError(sourcepath: string, reason: 'not_found' | 'not_directory'): string;
4
4
  export declare function getSizeBudgetExceededError(type: 'size' | 'gzipSize', limit: number, actual: number): string;
5
- export declare function getMaxUriHandlersHint(engine: string, routeCount: number): string;
5
+ export declare function getMaxUriHandlersHint(engine: string, routeCount: number, espmethod?: string): string;
@@ -13,7 +13,6 @@ const consoleColor_1 = require("./consoleColor");
13
13
  function getEngineName(engine) {
14
14
  const names = {
15
15
  psychic: 'PsychicHttpServer',
16
- psychic2: 'PsychicHttpServer V2',
17
16
  async: 'ESPAsyncWebServer',
18
17
  espidf: 'ESP-IDF'
19
18
  };
@@ -24,9 +23,6 @@ function getMissingIndexError(engine) {
24
23
  psychic: ` 1. Add an index.html file to your source directory
25
24
  2. The file will automatically be set as the default route ("/")
26
25
  3. PsychicHttpServer uses: server->defaultEndpoint = ...`,
27
- psychic2: ` 1. Add an index.html file to your source directory
28
- 2. The file will automatically be set as the default route ("/")
29
- 3. PsychicHttpServer V2 uses: server->defaultEndpoint = ...`,
30
26
  async: ` 1. Add an index.html file to your source directory
31
27
  2. The file will automatically create a "/" route handler
32
28
  3. ESPAsyncWebServer uses: server.on("/", HTTP_GET, ...)`,
@@ -55,7 +51,6 @@ function getInvalidEngineError(attempted) {
55
51
 
56
52
  Valid engines are:
57
53
  ${(0, consoleColor_1.cyanLog)('• psychic')} - PsychicHttpServer (ESP32 only, fastest performance)
58
- ${(0, consoleColor_1.cyanLog)('• psychic2')} - PsychicHttpServer V2 (ESP32 only, modern API)
59
54
  ${(0, consoleColor_1.cyanLog)('• async')} - ESPAsyncWebServer (ESP32/ESP8266 compatible)
60
55
  ${(0, consoleColor_1.cyanLog)('• espidf')} - Native ESP-IDF web server (ESP32 only, no Arduino)
61
56
 
@@ -136,22 +131,18 @@ CI integration:
136
131
  This non-zero exit code allows build pipelines to fail early when
137
132
  the frontend exceeds allocated flash space.`);
138
133
  }
139
- function getMaxUriHandlersHint(engine, routeCount) {
134
+ function getMaxUriHandlersHint(engine, routeCount, espmethod = 'initSvelteStaticFiles') {
140
135
  const recommended = routeCount + 5;
141
136
  const hints = {
142
137
  psychic: `PsychicHttpServer server;
143
138
  server.config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
144
- initSvelteStaticFiles(&server);
145
- server.listen(80);`,
146
- psychic2: `PsychicHttpServer server;
147
- server.config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
148
- initSvelteStaticFiles(&server);
139
+ ${espmethod}(&server);
149
140
  server.listen(80);`,
150
141
  espidf: `httpd_config_t config = HTTPD_DEFAULT_CONFIG();
151
142
  config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
152
143
  httpd_handle_t server = NULL;
153
144
  httpd_start(&server, &config);
154
- initSvelteStaticFiles(server);`
145
+ ${espmethod}(server);`
155
146
  };
156
147
  const hint = hints[engine];
157
148
  if (!hint)
package/dist/file.d.ts CHANGED
@@ -1 +1,5 @@
1
- export declare const getFiles: () => Map<string, Buffer>;
1
+ export type FileData = {
2
+ content: Buffer;
3
+ hash: string;
4
+ };
5
+ export declare const getFiles: () => Map<string, FileData>;
package/dist/file.js CHANGED
@@ -14,13 +14,12 @@ const consoleColor_1 = require("./consoleColor");
14
14
  const errorMessages_1 = require("./errorMessages");
15
15
  const findSimilarFiles = (files) => {
16
16
  const contentComparer = new Map();
17
- for (const [filename, content] of files.entries()) {
18
- const hash = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
19
- const existingFiles = contentComparer.get(hash);
17
+ for (const [filename, fileData] of files.entries()) {
18
+ const existingFiles = contentComparer.get(fileData.hash);
20
19
  if (existingFiles)
21
20
  existingFiles.push(filename);
22
21
  else
23
- contentComparer.set(hash, [filename]);
22
+ contentComparer.set(fileData.hash, [filename]);
24
23
  }
25
24
  const result = [];
26
25
  for (const filenames of contentComparer.values())
@@ -30,7 +29,7 @@ const findSimilarFiles = (files) => {
30
29
  };
31
30
  const shouldSkipFile = (filename, allFilenames) => {
32
31
  const extension = node_path_1.default.extname(filename);
33
- const compressedExtensions = ['.gz', '.brottli', '.br'];
32
+ const compressedExtensions = ['.gz', '.brotli', '.br'];
34
33
  if (compressedExtensions.includes(extension)) {
35
34
  const original = filename.slice(0, -1 * extension.length);
36
35
  if (allFilenames.includes(original)) {
@@ -72,10 +71,14 @@ const getFiles = () => {
72
71
  console.log((0, consoleColor_1.cyanLog)(`... and ${excludedFiles.length - displayLimit} more`));
73
72
  console.log();
74
73
  }
74
+ else if (excludePatterns.length > 0)
75
+ console.log((0, consoleColor_1.yellowLog)(`Warning: --exclude patterns matched no files: ${excludePatterns.join(', ')}`));
75
76
  const result = new Map();
76
77
  for (const filename of filenames) {
77
78
  const filePath = node_path_1.default.join(commandLine_1.cmdLine.sourcepath, filename);
78
- result.set(filename, (0, node_fs_1.readFileSync)(filePath, { flag: 'r' }));
79
+ const content = (0, node_fs_1.readFileSync)(filePath, { flag: 'r' });
80
+ const hash = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
81
+ result.set(filename, { content, hash });
79
82
  }
80
83
  const duplicates = findSimilarFiles(result);
81
84
  for (const sameFiles of duplicates)
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- import { CppCodeSource } from './cppCode';
1
+ import { CppCodeSource, ExtensionGroups } from './cppCode';
2
2
  declare const shouldUseGzip: (originalSize: number, compressedSize: number) => boolean;
3
3
  declare const calculateCompressionRatio: (originalSize: number, compressedSize: number) => number;
4
4
  declare const formatCompressionLog: (filename: string, padding: string, originalSize: number, compressedSize: number, useGzip: boolean) => string;
5
+ declare const formatSize: (bytes: number) => string;
5
6
  declare const createSourceEntry: (filename: string, dataname: string, content: Buffer, contentGzip: Buffer, mimeType: string, sha256: string, isGzip: boolean) => CppCodeSource;
6
- declare const updateExtensionGroup: (extension: string) => void;
7
- export { calculateCompressionRatio, createSourceEntry, formatCompressionLog, shouldUseGzip, updateExtensionGroup };
7
+ declare const updateExtensionGroup: (filesByExtension: ExtensionGroups, extension: string) => void;
8
+ export declare function main(): void;
9
+ export { calculateCompressionRatio, createSourceEntry, formatCompressionLog, formatSize, shouldUseGzip, updateExtensionGroup };
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.updateExtensionGroup = exports.shouldUseGzip = exports.formatCompressionLog = exports.createSourceEntry = exports.calculateCompressionRatio = void 0;
7
- const node_crypto_1 = require("node:crypto");
6
+ exports.updateExtensionGroup = exports.shouldUseGzip = exports.formatSize = exports.formatCompressionLog = exports.createSourceEntry = exports.calculateCompressionRatio = void 0;
7
+ exports.main = main;
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const node_zlib_1 = require("node:zlib");
@@ -16,13 +16,6 @@ const errorMessages_1 = require("./errorMessages");
16
16
  const file_1 = require("./file");
17
17
  const GZIP_MIN_SIZE = 1024;
18
18
  const GZIP_MIN_REDUCTION_RATIO = 0.85;
19
- const summary = {
20
- filecount: 0,
21
- size: 0,
22
- gzipsize: 0
23
- };
24
- const sources = [];
25
- const filesByExtension = [];
26
19
  const shouldUseGzip = (originalSize, compressedSize) => originalSize > GZIP_MIN_SIZE && compressedSize < originalSize * GZIP_MIN_REDUCTION_RATIO;
27
20
  exports.shouldUseGzip = shouldUseGzip;
28
21
  const calculateCompressionRatio = (originalSize, compressedSize) => Math.round((compressedSize / originalSize) * 100);
@@ -36,6 +29,12 @@ const formatCompressionLog = (filename, padding, originalSize, compressedSize, u
36
29
  return (0, consoleColor_1.yellowLog)(` [${filename}] ${padding} x gzip unused ${tooSmall}${sizeInfo}`);
37
30
  };
38
31
  exports.formatCompressionLog = formatCompressionLog;
32
+ const formatSize = (bytes) => {
33
+ if (bytes < 1024)
34
+ return `${bytes}B`;
35
+ return `${Math.round(bytes / 1024)}kB`;
36
+ };
37
+ exports.formatSize = formatSize;
39
38
  const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, sha256, isGzip) => ({
40
39
  filename,
41
40
  dataname,
@@ -47,7 +46,7 @@ const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, s
47
46
  sha256
48
47
  });
49
48
  exports.createSourceEntry = createSourceEntry;
50
- const updateExtensionGroup = (extension) => {
49
+ const updateExtensionGroup = (filesByExtension, extension) => {
51
50
  const group = filesByExtension.find((fe) => fe.extension === extension);
52
51
  if (group)
53
52
  group.count += 1;
@@ -55,47 +54,66 @@ const updateExtensionGroup = (extension) => {
55
54
  filesByExtension.push({ extension, count: 1 });
56
55
  };
57
56
  exports.updateExtensionGroup = updateExtensionGroup;
58
- console.log('Collecting source files');
59
- const files = (0, file_1.getFiles)();
60
- if (files.size === 0) {
61
- console.error(`Directory ${commandLine_1.cmdLine.sourcepath} is empty`);
62
- process.exit(1);
63
- }
64
- console.log();
65
- console.log('Translation to header file');
66
- const longestFilename = [...files.keys()].reduce((p, c) => Math.max(c.length, p), 0);
67
- for (const [originalFilename, content] of files) {
68
- const mimeType = (0, mime_types_1.lookup)(originalFilename) || 'text/plain';
69
- summary.filecount++;
70
- const filename = originalFilename.replace(/\\/g, '/');
71
- const dataname = filename.replace(/[!&()+./@{}~-]/g, '_');
72
- let extension = node_path_1.default.extname(filename).toUpperCase();
73
- if (extension.startsWith('.'))
74
- extension = extension.slice(1);
75
- updateExtensionGroup(extension);
76
- const sha256 = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
77
- summary.size += content.length;
78
- const zipContent = (0, node_zlib_1.gzipSync)(content, { level: 9 });
79
- summary.gzipsize += zipContent.length;
80
- const useGzip = shouldUseGzip(content.length, zipContent.length);
81
- sources.push(createSourceEntry(filename, dataname, content, zipContent, mimeType, sha256, useGzip));
82
- const padding = ' '.repeat(longestFilename - originalFilename.length);
83
- console.log(formatCompressionLog(originalFilename, padding, content.length, zipContent.length, useGzip));
84
- }
85
- console.log('');
86
- filesByExtension.sort((left, right) => left.extension.localeCompare(right.extension));
87
- if (commandLine_1.cmdLine.maxSize !== undefined && summary.size > commandLine_1.cmdLine.maxSize) {
88
- console.error((0, errorMessages_1.getSizeBudgetExceededError)('size', commandLine_1.cmdLine.maxSize, summary.size));
89
- process.exit(1);
90
- }
91
- if (commandLine_1.cmdLine.maxGzipSize !== undefined && summary.gzipsize > commandLine_1.cmdLine.maxGzipSize) {
92
- console.error((0, errorMessages_1.getSizeBudgetExceededError)('gzipSize', commandLine_1.cmdLine.maxGzipSize, summary.gzipsize));
93
- process.exit(1);
57
+ function main() {
58
+ const summary = {
59
+ filecount: 0,
60
+ size: 0,
61
+ gzipsize: 0
62
+ };
63
+ const sources = [];
64
+ const filesByExtension = [];
65
+ console.log('Collecting source files');
66
+ const files = (0, file_1.getFiles)();
67
+ if (files.size === 0) {
68
+ console.error(`Directory ${commandLine_1.cmdLine.sourcepath} is empty`);
69
+ process.exit(1);
70
+ }
71
+ console.log();
72
+ console.log('Translation to header file');
73
+ const longestFilename = [...files.keys()].reduce((p, c) => Math.max(c.length, p), 0);
74
+ for (const [originalFilename, fileData] of files) {
75
+ const { content, hash: sha256 } = fileData;
76
+ const mimeType = (0, mime_types_1.lookup)(originalFilename) || 'text/plain';
77
+ if (!(0, mime_types_1.lookup)(originalFilename))
78
+ console.log((0, consoleColor_1.yellowLog)(` [${originalFilename}] unknown MIME type for extension '${node_path_1.default.extname(originalFilename)}', using text/plain`));
79
+ summary.filecount++;
80
+ const filename = originalFilename.replace(/\\/g, '/');
81
+ let dataname = filename.replace(/\W/g, '_');
82
+ if (/^\d/.test(dataname))
83
+ dataname = '_' + dataname;
84
+ let extension = node_path_1.default.extname(filename).toUpperCase();
85
+ if (extension.startsWith('.'))
86
+ extension = extension.slice(1);
87
+ updateExtensionGroup(filesByExtension, extension);
88
+ summary.size += content.length;
89
+ const zipContent = (0, node_zlib_1.gzipSync)(content, { level: 9 });
90
+ const useGzip = shouldUseGzip(content.length, zipContent.length);
91
+ summary.gzipsize += useGzip ? zipContent.length : content.length;
92
+ sources.push(createSourceEntry(filename, dataname, content, zipContent, mimeType, sha256, useGzip));
93
+ const padding = ' '.repeat(longestFilename - originalFilename.length);
94
+ console.log(formatCompressionLog(originalFilename, padding, content.length, zipContent.length, useGzip));
95
+ }
96
+ console.log('');
97
+ filesByExtension.sort((left, right) => left.extension.localeCompare(right.extension));
98
+ if (commandLine_1.cmdLine.maxSize !== undefined && summary.size > commandLine_1.cmdLine.maxSize) {
99
+ console.error((0, errorMessages_1.getSizeBudgetExceededError)('size', commandLine_1.cmdLine.maxSize, summary.size));
100
+ process.exit(1);
101
+ }
102
+ if (commandLine_1.cmdLine.maxGzipSize !== undefined && summary.gzipsize > commandLine_1.cmdLine.maxGzipSize) {
103
+ console.error((0, errorMessages_1.getSizeBudgetExceededError)('gzipSize', commandLine_1.cmdLine.maxGzipSize, summary.gzipsize));
104
+ process.exit(1);
105
+ }
106
+ if (commandLine_1.cmdLine.dryRun) {
107
+ console.log(`[DRY RUN] ${summary.filecount} files, ${formatSize(summary.size)} original size, ${formatSize(summary.gzipsize)} gzip size`);
108
+ console.log(`[DRY RUN] Would write to ${commandLine_1.cmdLine.outputfile}`);
109
+ return;
110
+ }
111
+ const cppFile = (0, cppCode_1.getCppCode)(sources, filesByExtension);
112
+ (0, node_fs_1.mkdirSync)(node_path_1.default.normalize(node_path_1.default.dirname(commandLine_1.cmdLine.outputfile)), { recursive: true });
113
+ (0, node_fs_1.writeFileSync)(commandLine_1.cmdLine.outputfile, cppFile, { flush: true, encoding: 'utf8' });
114
+ console.log(`${summary.filecount} files, ${formatSize(summary.size)} original size, ${formatSize(summary.gzipsize)} gzip size`);
115
+ console.log(`${commandLine_1.cmdLine.outputfile} ${formatSize(cppFile.length)} size`);
116
+ if (commandLine_1.cmdLine.engine === 'psychic' || commandLine_1.cmdLine.engine === 'espidf')
117
+ console.log('\n' + (0, errorMessages_1.getMaxUriHandlersHint)(commandLine_1.cmdLine.engine, sources.length, commandLine_1.cmdLine.espmethod));
94
118
  }
95
- const cppFile = (0, cppCode_1.getCppCode)(sources, filesByExtension);
96
- (0, node_fs_1.mkdirSync)(node_path_1.default.normalize(node_path_1.default.dirname(commandLine_1.cmdLine.outputfile)), { recursive: true });
97
- (0, node_fs_1.writeFileSync)(commandLine_1.cmdLine.outputfile, cppFile, { flush: true, encoding: 'utf8' });
98
- console.log(`${summary.filecount} files, ${Math.round(summary.size / 1024)}kB original size, ${Math.round(summary.gzipsize / 1024)}kB gzip size`);
99
- console.log(`${commandLine_1.cmdLine.outputfile} ${Math.round(cppFile.length / 1024)}kB size`);
100
- if (commandLine_1.cmdLine.engine === 'psychic' || commandLine_1.cmdLine.engine === 'psychic2' || commandLine_1.cmdLine.engine === 'espidf')
101
- console.log('\n' + (0, errorMessages_1.getMaxUriHandlersHint)(commandLine_1.cmdLine.engine, sources.length));
119
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "1.16.1",
3
+ "version": "2.0.0",
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",
@@ -30,7 +30,6 @@
30
30
  "scripts": {
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
- "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
33
  "test": "vitest run",
35
34
  "test:watch": "vitest",
36
35
  "test:coverage": "vitest run --coverage",
@@ -56,12 +55,11 @@
56
55
  "esp8266",
57
56
  "webserver",
58
57
  "psychichttpserver",
59
- "psychichttpserverV2",
60
58
  "espasyncwebserver"
61
59
  ],
62
60
  "devDependencies": {
63
61
  "@types/mime-types": "^3.0.1",
64
- "@types/node": "^25.1.0",
62
+ "@types/node": "^25.2.2",
65
63
  "@types/picomatch": "^4.0.2",
66
64
  "@typescript-eslint/eslint-plugin": "^8.54.0",
67
65
  "@typescript-eslint/parser": "^8.54.0",