svelteesp32 1.13.0 → 1.14.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
@@ -12,6 +12,23 @@ In order to be able to easily update OTA, it is important - from the users' poin
12
12
 
13
13
  This npm package provides a solution for **inserting any JS client application into the ESP web server** (PsychicHttp and also ESPAsyncWebServer (https://github.com/ESP32Async/ESPAsyncWebServer) and ESP-IDF available, PsychicHttp is the default). For this, JS, html, css, font, assets, etc. files must be converted to binary byte array. Npm mode is easy to use and easy to **integrate into your CI/CD pipeline**.
14
14
 
15
+ **Quick Comparison:**
16
+
17
+ | Feature | SvelteESP32 | Traditional Filesystem (SPIFFS/LittleFS) |
18
+ | --------------------- | ------------------------------------------ | ---------------------------------------- |
19
+ | **Single Binary OTA** | ✓ Everything embedded in firmware | ✗ Requires separate partition upload |
20
+ | **Gzip Compression** | ✓ Automatic build-time (>15% reduction) | Manual or runtime compression |
21
+ | **ETag Support** | ✓ Built-in SHA256 ETags with 304 responses | Manual implementation required |
22
+ | **CI/CD Integration** | ✓ npm package, simple build step | Complex with upload_fs tools |
23
+ | **Memory Efficiency** | Flash only (PROGMEM/const arrays) | Flash partition + filesystem overhead |
24
+ | **Performance** | Direct byte array serving | Filesystem read overhead |
25
+ | **Setup Complexity** | Include header, call init function | Partition setup, upload tools, handlers |
26
+
27
+ **When to use:**
28
+
29
+ - **SvelteESP32**: Single-binary OTA updates, CI/CD pipelines, static web content that doesn't change at runtime
30
+ - **SPIFFS/LittleFS**: Dynamic content, user-uploadable files, configuration that changes at runtime
31
+
15
32
  > Starting with version v1.13.0, RC files support npm package variable interpolation
16
33
 
17
34
  > Starting with version v1.12.0, you can use RC file for configuration
@@ -223,6 +240,21 @@ const uint8_t datagzip_assets_index_KwubEIf__js[12547] = {0x1f, 0x8b, 0x8, 0x0,
223
240
  const uint8_t datagzip_assets_index_Soe6cpLA_css[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
224
241
  const char * etag_assets_index_KwubEIf__js = "387b88e345cc56ef9091...";
225
242
  const char * etag_assets_index_Soe6cpLA_css = "d4f23bc45ef67890ab12...";
243
+
244
+ // File manifest for runtime introspection
245
+ struct SVELTEESP32_FileInfo {
246
+ const char* path;
247
+ uint32_t size;
248
+ uint32_t gzipSize;
249
+ const char* etag;
250
+ const char* contentType;
251
+ };
252
+ const SVELTEESP32_FileInfo SVELTEESP32_FILES[] = {
253
+ { "/assets/index-KwubEIf-.js", 38850, 12547, etag_assets_index_KwubEIf__js, "text/javascript" },
254
+ { "/assets/index-Soe6cpLA.css", 32494, 5368, etag_assets_index_Soe6cpLA_css, "text/css" },
255
+ ...
256
+ };
257
+ const size_t SVELTEESP32_FILE_COUNT = sizeof(SVELTEESP32_FILES) / sizeof(SVELTEESP32_FILES[0]);
226
258
  ...
227
259
 
228
260
  void initSvelteStaticFiles(PsychicHttpServer * server) {
@@ -309,6 +341,18 @@ At the same time, it can be an advantage that the content is cached by the brows
309
341
 
310
342
  Typically, the entry point for web applications is the **index.htm or index.html** file. This does not need to be listed in the browser's address bar because web servers know that this file should be served by default. Svelteesp32 also does this: if there is an index.htm or index.html file, it sets it as the main file to be served. So using `http://esp_xxx.local` or just entering the `http://x.y.w.z/` IP address will serve this main file.
311
343
 
344
+ **Validation**: By default, svelteesp32 validates that an `index.html` or `index.htm` file exists in your source directory (in the root or any subdirectory). This ensures users won't get a 404 error when visiting your ESP32's root URL.
345
+
346
+ **Skipping Validation**: If you're building an API-only application (REST endpoints without a web UI) or using a different entry point (e.g., `main.html`), you can skip this validation with the `--no-index-check` flag:
347
+
348
+ ```bash
349
+ # API-only application (no web UI)
350
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --no-index-check
351
+
352
+ # Custom entry point (users must visit /main.html explicitly)
353
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --no-index-check
354
+ ```
355
+
312
356
  ### File Exclusion
313
357
 
314
358
  The `--exclude` option allows you to exclude files from being embedded in the ESP32 firmware using glob patterns. This is useful for excluding source maps, documentation, and test files that shouldn't be part of the deployed application.
@@ -416,23 +460,92 @@ You can include a blocker error if a named file accidentally missing from the bu
416
460
 
417
461
  You can use the following c++ directives at the project level if you want to configure the usage there: `SVELTEESP32_ENABLE_ETAG` and `SVELTEESP32_ENABLE_GZIP`. (Do not forget `--etag=compiler` or `--gzip=compiler` command line arg!)
418
462
 
463
+ ### File Manifest
464
+
465
+ The generated header includes a file manifest struct and array that allows runtime introspection of all embedded static assets. This is useful for logging, runtime diagnostics, or building a JSON endpoint that lists all served files.
466
+
467
+ **Generated Structure:**
468
+
469
+ ```c
470
+ // File manifest struct
471
+ struct SVELTEESP32_FileInfo {
472
+ const char* path; // URL path (e.g., "/index.html")
473
+ uint32_t size; // Original file size in bytes
474
+ uint32_t gzipSize; // Compressed size (0 if not gzipped)
475
+ const char* etag; // ETag pointer (nullptr if etag disabled)
476
+ const char* contentType; // MIME type (e.g., "text/html")
477
+ };
478
+
479
+ // File manifest array
480
+ const SVELTEESP32_FileInfo SVELTEESP32_FILES[] = { ... };
481
+ const size_t SVELTEESP32_FILE_COUNT = sizeof(SVELTEESP32_FILES) / sizeof(SVELTEESP32_FILES[0]);
482
+ ```
483
+
484
+ **Usage Example - List All Files:**
485
+
486
+ ```c
487
+ #include "svelteesp32.h"
488
+
489
+ void listFiles() {
490
+ Serial.printf("Embedded files (%d):\n", SVELTEESP32_FILE_COUNT);
491
+ for (size_t i = 0; i < SVELTEESP32_FILE_COUNT; i++) {
492
+ const auto& f = SVELTEESP32_FILES[i];
493
+ Serial.printf(" %s (%d bytes", f.path, f.size);
494
+ if (f.gzipSize > 0) {
495
+ Serial.printf(", gzip: %d bytes", f.gzipSize);
496
+ }
497
+ Serial.printf(", type: %s)\n", f.contentType);
498
+ }
499
+ }
500
+ ```
501
+
502
+ **Usage Example - JSON Endpoint:**
503
+
504
+ ```c
505
+ server->on("/api/files", HTTP_GET, [](PsychicRequest* request) {
506
+ String json = "[";
507
+ for (size_t i = 0; i < SVELTEESP32_FILE_COUNT; i++) {
508
+ const auto& f = SVELTEESP32_FILES[i];
509
+ if (i > 0) json += ",";
510
+ json += "{\"path\":\"" + String(f.path) + "\",";
511
+ json += "\"size\":" + String(f.size) + ",";
512
+ json += "\"gzipSize\":" + String(f.gzipSize) + ",";
513
+ json += "\"contentType\":\"" + String(f.contentType) + "\"}";
514
+ }
515
+ json += "]";
516
+ PsychicResponse response(request);
517
+ response.setContentType("application/json");
518
+ response.setContent(json.c_str(), json.length());
519
+ return response.send();
520
+ });
521
+ ```
522
+
523
+ **Notes:**
524
+
525
+ - The manifest is always generated (no CLI flag needed)
526
+ - `gzipSize` is 0 when the file is not gzipped (either too small, poor compression ratio, or gzip disabled)
527
+ - `etag` is `NULL` when etag is disabled (`--etag=false`)
528
+ - The struct and array names use the `--define` prefix (default: `SVELTEESP32`)
529
+ - For ESP-IDF engine, C-compatible `typedef struct` syntax is used instead of C++ `struct`
530
+
419
531
  ### Command line options
420
532
 
421
- | Option | Description | default |
422
- | ------------- | ------------------------------------------------------------------------------------ | ----------------------- |
423
- | `-s` | **Source dist folder contains compiled web files** | (required) |
424
- | `-e` | The engine for which the include file is created (psychic/psychic2/async/espidf) | psychic |
425
- | `-o` | Generated output file with path | `svelteesp32.h` |
426
- | `--exclude` | Exclude files matching glob pattern (repeatable or comma-separated) | Default system files |
427
- | `--etag` | Use ETag header for cache (true/false/compiler) | false |
428
- | `--cachetime` | Override no-cache response with a max-age=\<cachetime\> response (seconds) | 0 |
429
- | `--gzip` | Compress content with gzip (true/false/compiler) | true |
430
- | `--created` | Include creation time in generated header | false |
431
- | `--version` | Include a version string in generated header, e.g. `--version=v$npm_package_version` | '' |
432
- | `--espmethod` | Name of generated initialization method | `initSvelteStaticFiles` |
433
- | `--define` | Prefix of c++ defines (e.g., SVELTEESP32_COUNT) | `SVELTEESP32` |
434
- | `--config` | Use custom RC file path | `.svelteesp32rc.json` |
435
- | `-h` | Show help | |
533
+ | Option | Description | default |
534
+ | ------------------ | ------------------------------------------------------------------------------------ | ----------------------- |
535
+ | `-s` | **Source dist folder contains compiled web files** | (required) |
536
+ | `-e` | The engine for which the include file is created (psychic/psychic2/async/espidf) | psychic |
537
+ | `-o` | Generated output file with path | `svelteesp32.h` |
538
+ | `--exclude` | Exclude files matching glob pattern (repeatable or comma-separated) | Default system files |
539
+ | `--etag` | Use ETag header for cache (true/false/compiler) | false |
540
+ | `--cachetime` | Override no-cache response with a max-age=\<cachetime\> response (seconds) | 0 |
541
+ | `--gzip` | Compress content with gzip (true/false/compiler) | true |
542
+ | `--created` | Include creation time in generated header | false |
543
+ | `--version` | Include a version string in generated header, e.g. `--version=v$npm_package_version` | '' |
544
+ | `--espmethod` | Name of generated initialization method | `initSvelteStaticFiles` |
545
+ | `--define` | Prefix of c++ defines (e.g., SVELTEESP32_COUNT) | `SVELTEESP32` |
546
+ | `--config` | Use custom RC file path | `.svelteesp32rc.json` |
547
+ | `--no-index-check` | Skip validation for index.html/index.htm (for API-only or custom entry points) | false |
548
+ | `-h` | Show help | |
436
549
 
437
550
  ### Configuration File
438
551
 
@@ -479,19 +592,20 @@ npx svelteesp32 --config=.svelteesp32rc.prod.json
479
592
 
480
593
  All CLI options can be specified in the RC file using long-form property names:
481
594
 
482
- | RC Property | CLI Flag | Type | Example |
483
- | ------------ | ------------- | ------- | ------------------------------------------------ |
484
- | `engine` | `-e` | string | `"psychic"`, `"psychic2"`, `"async"`, `"espidf"` |
485
- | `sourcepath` | `-s` | string | `"./dist"` |
486
- | `outputfile` | `-o` | string | `"./output.h"` |
487
- | `etag` | `--etag` | string | `"true"`, `"false"`, `"compiler"` |
488
- | `gzip` | `--gzip` | string | `"true"`, `"false"`, `"compiler"` |
489
- | `cachetime` | `--cachetime` | number | `86400` |
490
- | `created` | `--created` | boolean | `true`, `false` |
491
- | `version` | `--version` | string | `"v1.0.0"` |
492
- | `espmethod` | `--espmethod` | string | `"initSvelteStaticFiles"` |
493
- | `define` | `--define` | string | `"SVELTEESP32"` |
494
- | `exclude` | `--exclude` | array | `["*.map", "*.md"]` |
595
+ | RC Property | CLI Flag | Type | Example |
596
+ | -------------- | ------------------ | ------- | ------------------------------------------------ |
597
+ | `engine` | `-e` | string | `"psychic"`, `"psychic2"`, `"async"`, `"espidf"` |
598
+ | `sourcepath` | `-s` | string | `"./dist"` |
599
+ | `outputfile` | `-o` | string | `"./output.h"` |
600
+ | `etag` | `--etag` | string | `"true"`, `"false"`, `"compiler"` |
601
+ | `gzip` | `--gzip` | string | `"true"`, `"false"`, `"compiler"` |
602
+ | `cachetime` | `--cachetime` | number | `86400` |
603
+ | `created` | `--created` | boolean | `true`, `false` |
604
+ | `version` | `--version` | string | `"v1.0.0"` |
605
+ | `espmethod` | `--espmethod` | string | `"initSvelteStaticFiles"` |
606
+ | `define` | `--define` | string | `"SVELTEESP32"` |
607
+ | `exclude` | `--exclude` | array | `["*.map", "*.md"]` |
608
+ | `noIndexCheck` | `--no-index-check` | boolean | `true`, `false` |
495
609
 
496
610
  #### CLI Override
497
611
 
@@ -10,6 +10,7 @@ interface ICopyFilesArguments {
10
10
  created: boolean;
11
11
  version: string;
12
12
  exclude: string[];
13
+ noIndexCheck?: boolean;
13
14
  help?: boolean;
14
15
  }
15
16
  interface IRcFileConfig {
@@ -12,6 +12,7 @@ const node_fs_1 = require("node:fs");
12
12
  const node_os_1 = require("node:os");
13
13
  const node_path_1 = __importDefault(require("node:path"));
14
14
  const consoleColor_1 = require("./consoleColor");
15
+ const errorMessages_1 = require("./errorMessages");
15
16
  function showHelp() {
16
17
  console.log(`
17
18
  svelteesp32 - Svelte JS to ESP32 converter
@@ -57,7 +58,8 @@ RC File:
57
58
  function validateEngine(value) {
58
59
  if (value === 'psychic' || value === 'psychic2' || value === 'async' || value === 'espidf')
59
60
  return value;
60
- throw new Error(`Invalid engine: ${value}`);
61
+ console.error((0, errorMessages_1.getInvalidEngineError)(value));
62
+ process.exit(1);
61
63
  }
62
64
  function validateTriState(value, name) {
63
65
  if (value === 'true' || value === 'false' || value === 'compiler')
@@ -349,6 +351,10 @@ function parseArguments() {
349
351
  result.created = true;
350
352
  continue;
351
353
  }
354
+ if (argument === '--no-index-check') {
355
+ result.noIndexCheck = true;
356
+ continue;
357
+ }
352
358
  if (argument.startsWith('-') && !argument.startsWith('--')) {
353
359
  const flag = argument.slice(1);
354
360
  const nextArgument = arguments_[index + 1];
@@ -465,8 +471,12 @@ function formatConfiguration(cmdLine) {
465
471
  return parts.join(' ');
466
472
  }
467
473
  exports.cmdLine = parseArguments();
468
- if (!(0, node_fs_1.existsSync)(exports.cmdLine.sourcepath) || !(0, node_fs_1.statSync)(exports.cmdLine.sourcepath).isDirectory()) {
469
- console.error(`Directory ${exports.cmdLine.sourcepath} not exists or not a directory`);
474
+ if (!(0, node_fs_1.existsSync)(exports.cmdLine.sourcepath)) {
475
+ console.error((0, errorMessages_1.getSourcepathNotFoundError)(exports.cmdLine.sourcepath, 'not_found'));
476
+ process.exit(1);
477
+ }
478
+ if (!(0, node_fs_1.statSync)(exports.cmdLine.sourcepath).isDirectory()) {
479
+ console.error((0, errorMessages_1.getSourcepathNotFoundError)(exports.cmdLine.sourcepath, 'not_directory'));
470
480
  process.exit(1);
471
481
  }
472
482
  console.log(`[SvelteESP32] Generate code for ${exports.cmdLine.engine} engine`);
package/dist/cppCode.d.ts CHANGED
@@ -6,7 +6,7 @@ export type CppCodeSource = {
6
6
  content: Buffer;
7
7
  contentGzip: Buffer;
8
8
  isGzip: boolean;
9
- md5: string;
9
+ sha256: string;
10
10
  };
11
11
  export type CppCodeSources = CppCodeSource[];
12
12
  export type ExtensionGroup = {
package/dist/cppCode.js CHANGED
@@ -81,7 +81,7 @@ const etagArraysSection = `
81
81
  {{#switch etag}}
82
82
  {{#case "true"}}
83
83
  {{#each sources}}
84
- const char * etag_{{this.dataname}} = "{{this.md5}}";
84
+ const char * etag_{{this.dataname}} = "{{this.sha256}}";
85
85
  {{/each}}
86
86
  {{/case}}
87
87
  {{#case "false"}}
@@ -89,12 +89,30 @@ const char * etag_{{this.dataname}} = "{{this.md5}}";
89
89
  {{#case "compiler"}}
90
90
  #ifdef {{definePrefix}}_ENABLE_ETAG
91
91
  {{#each sources}}
92
- const char * etag_{{this.dataname}} = "{{this.md5}}";
92
+ const char * etag_{{this.dataname}} = "{{this.sha256}}";
93
93
  {{/each}}
94
94
  #endif
95
95
  {{/case}}
96
96
  {{/switch}}
97
97
  `;
98
+ const manifestSection = `
99
+ // File manifest struct
100
+ struct {{definePrefix}}_FileInfo {
101
+ const char* path;
102
+ uint32_t size;
103
+ uint32_t gzipSize;
104
+ const char* etag;
105
+ const char* contentType;
106
+ };
107
+
108
+ // File manifest array
109
+ const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {
110
+ {{#each sources}}
111
+ { "/{{this.filename}}", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, "{{this.mime}}" },
112
+ {{/each}}
113
+ };
114
+ const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);
115
+ `;
98
116
  const psychicTemplate = `
99
117
  //engine: PsychicHttpServer
100
118
  //config: {{{config}}}
@@ -116,6 +134,9 @@ ${dataArraysSection(false)}
116
134
  //
117
135
  ${etagArraysSection}
118
136
 
137
+ //
138
+ ${manifestSection}
139
+
119
140
  //
120
141
  // Http Handlers
121
142
  void {{methodName}}(PsychicHttpServer * server) {
@@ -225,6 +246,9 @@ ${dataArraysSection(false)}
225
246
  //
226
247
  ${etagArraysSection}
227
248
 
249
+ //
250
+ ${manifestSection}
251
+
228
252
  //
229
253
  // Http Handlers
230
254
  void {{methodName}}(PsychicHttpServer * server) {
@@ -330,6 +354,9 @@ ${dataArraysSection(true)}
330
354
  //
331
355
  ${etagArraysSection}
332
356
 
357
+ //
358
+ ${manifestSection}
359
+
333
360
  //
334
361
  // Http Handlers
335
362
  void {{methodName}}(AsyncWebServer * server) {
@@ -489,13 +516,15 @@ const getTemplate = (engine) => {
489
516
  return asyncTemplate;
490
517
  }
491
518
  };
492
- const transformSourceToTemplateData = (s) => ({
519
+ const transformSourceToTemplateData = (s, etag) => ({
493
520
  ...s,
494
521
  length: s.content.length,
495
522
  bytes: [...s.content].map((v) => `${v.toString(10)}`).join(','),
496
523
  lengthGzip: s.contentGzip.length,
497
524
  bytesGzip: [...s.contentGzip].map((v) => `${v.toString(10)}`).join(','),
498
- isDefault: s.filename.startsWith('index.htm')
525
+ isDefault: s.filename.startsWith('index.htm'),
526
+ gzipSizeForManifest: s.isGzip ? s.contentGzip.length : 0,
527
+ etagForManifest: etag === 'false' ? 'NULL' : `etag_${s.dataname}`
499
528
  });
500
529
  const postProcessCppCode = (code) => code
501
530
  .split('\n')
@@ -531,7 +560,7 @@ const getCppCode = (sources, filesByExtension) => {
531
560
  fileCount: sources.length.toString(),
532
561
  fileSize: sources.reduce((previous, current) => previous + current.content.length, 0).toString(),
533
562
  fileGzipSize: sources.reduce((previous, current) => previous + current.contentGzip.length, 0).toString(),
534
- sources: sources.map((s) => transformSourceToTemplateData(s)),
563
+ sources: sources.map((s) => transformSourceToTemplateData(s, commandLine_1.cmdLine.etag)),
535
564
  filesByExtension,
536
565
  etag: commandLine_1.cmdLine.etag,
537
566
  gzip: commandLine_1.cmdLine.gzip,
@@ -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}}\nconst char * etag_{{this.dataname}} = \"{{this.md5}}\";\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n {{#each sources}}\nconst char * etag_{{this.dataname}} = \"{{this.md5}}\";\n {{/each}}\n#endif \n{{/case}}\n{{/switch}}\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 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 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 httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n{{/case}}\n{{#case \"false\"}}\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n{{/case}}\n{{#case \"compiler\"}}\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 = \"/\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n{{/if}}\n\nstatic const httpd_uri_t route_{{this.datanameUpperCase}} = {\n .uri = \"/{{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 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 { \"/{{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{{#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 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 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 httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n{{/case}}\n{{#case \"false\"}}\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n{{/case}}\n{{#case \"compiler\"}}\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 = \"/\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n{{/if}}\n\nstatic const httpd_uri_t route_{{this.datanameUpperCase}} = {\n .uri = \"/{{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}";
@@ -88,7 +88,7 @@ const char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };
88
88
  {{#switch etag}}
89
89
  {{#case "true"}}
90
90
  {{#each sources}}
91
- const char * etag_{{this.dataname}} = "{{this.md5}}";
91
+ static const char etag_{{this.dataname}}[] = "{{this.sha256}}";
92
92
  {{/each}}
93
93
  {{/case}}
94
94
  {{#case "false"}}
@@ -96,12 +96,29 @@ const char * etag_{{this.dataname}} = "{{this.md5}}";
96
96
  {{#case "compiler"}}
97
97
  #ifdef {{definePrefix}}_ENABLE_ETAG
98
98
  {{#each sources}}
99
- const char * etag_{{this.dataname}} = "{{this.md5}}";
99
+ static const char etag_{{this.dataname}}[] = "{{this.sha256}}";
100
100
  {{/each}}
101
- #endif
101
+ #endif
102
102
  {{/case}}
103
103
  {{/switch}}
104
104
 
105
+ // File manifest struct (C-compatible typedef)
106
+ typedef struct {
107
+ const char* path;
108
+ uint32_t size;
109
+ uint32_t gzipSize;
110
+ const char* etag;
111
+ const char* contentType;
112
+ } {{definePrefix}}_FileInfo;
113
+
114
+ // File manifest array
115
+ static const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {
116
+ {{#each sources}}
117
+ { "/{{this.filename}}", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, "{{this.mime}}" },
118
+ {{/each}}
119
+ };
120
+ static const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);
121
+
105
122
  {{#each sources}}
106
123
 
107
124
  static esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)
@@ -0,0 +1,4 @@
1
+ export declare function getMissingIndexError(engine: string): string;
2
+ export declare function getInvalidEngineError(attempted: string): string;
3
+ export declare function getSourcepathNotFoundError(sourcepath: string, reason: 'not_found' | 'not_directory'): string;
4
+ export declare function getMaxUriHandlersHint(engine: string, routeCount: number): string;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getMissingIndexError = getMissingIndexError;
7
+ exports.getInvalidEngineError = getInvalidEngineError;
8
+ exports.getSourcepathNotFoundError = getSourcepathNotFoundError;
9
+ exports.getMaxUriHandlersHint = getMaxUriHandlersHint;
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const consoleColor_1 = require("./consoleColor");
12
+ function getEngineName(engine) {
13
+ const names = {
14
+ psychic: 'PsychicHttpServer',
15
+ psychic2: 'PsychicHttpServer V2',
16
+ async: 'ESPAsyncWebServer',
17
+ espidf: 'ESP-IDF'
18
+ };
19
+ return names[engine] ?? engine;
20
+ }
21
+ function getMissingIndexError(engine) {
22
+ const hints = {
23
+ psychic: ` 1. Add an index.html file to your source directory
24
+ 2. The file will automatically be set as the default route ("/")
25
+ 3. PsychicHttpServer uses: server->defaultEndpoint = ...`,
26
+ psychic2: ` 1. Add an index.html file to your source directory
27
+ 2. The file will automatically be set as the default route ("/")
28
+ 3. PsychicHttpServer V2 uses: server->defaultEndpoint = ...`,
29
+ async: ` 1. Add an index.html file to your source directory
30
+ 2. The file will automatically create a "/" route handler
31
+ 3. ESPAsyncWebServer uses: server.on("/", HTTP_GET, ...)`,
32
+ espidf: ` 1. Add an index.html file to your source directory
33
+ 2. The file will register both "/" and "/index.html" routes
34
+ 3. ESP-IDF uses: httpd_register_uri_handler(server, &route_def_...)`
35
+ };
36
+ const hint = hints[engine] ?? hints['psychic'];
37
+ return ((0, consoleColor_1.redLog)('[ERROR] No index.html or index.htm found in source files') +
38
+ `
39
+
40
+ Why this matters:
41
+ Web applications typically need a default entry point. Without index.html,
42
+ users visiting http://your-esp32/ will get a 404 error.
43
+
44
+ How to fix (for ${getEngineName(engine)}):
45
+ ${hint}
46
+
47
+ Alternative:
48
+ If you use a different entry point (e.g., main.html), you can add --no-index-check flag,
49
+ but users must navigate to http://your-esp32/main.html explicitly.`);
50
+ }
51
+ function getInvalidEngineError(attempted) {
52
+ return ((0, consoleColor_1.redLog)(`[ERROR] Invalid engine: '${attempted}'`) +
53
+ `
54
+
55
+ Valid engines are:
56
+ ${(0, consoleColor_1.cyanLog)('• psychic')} - PsychicHttpServer (ESP32 only, fastest performance)
57
+ ${(0, consoleColor_1.cyanLog)('• psychic2')} - PsychicHttpServer V2 (ESP32 only, modern API)
58
+ ${(0, consoleColor_1.cyanLog)('• async')} - ESPAsyncWebServer (ESP32/ESP8266 compatible)
59
+ ${(0, consoleColor_1.cyanLog)('• espidf')} - Native ESP-IDF web server (ESP32 only, no Arduino)
60
+
61
+ How to fix:
62
+ npx svelteesp32 --engine=psychic --sourcepath=./dist
63
+
64
+ Example RC file (.svelteesp32rc.json):
65
+ {
66
+ "engine": "psychic",
67
+ "sourcepath": "./dist"
68
+ }
69
+
70
+ Documentation: https://github.com/hpieroni/svelteesp32#readme`);
71
+ }
72
+ function getSourcepathNotFoundError(sourcepath, reason) {
73
+ if (reason === 'not_directory')
74
+ return ((0, consoleColor_1.redLog)(`[ERROR] Source path is not a directory: '${sourcepath}'`) +
75
+ `
76
+
77
+ The --sourcepath option must point to a directory containing your built web files,
78
+ not an individual file.
79
+
80
+ How to fix:
81
+ npx svelteesp32 --sourcepath=./dist --engine=psychic`);
82
+ const resolvedPath = node_path_1.default.resolve(sourcepath);
83
+ const currentDirectory = process.cwd();
84
+ return ((0, consoleColor_1.redLog)(`[ERROR] Source directory not found: '${sourcepath}'`) +
85
+ `
86
+
87
+ Why this matters:
88
+ SvelteESP32 needs your compiled web assets (HTML, CSS, JS) to convert them
89
+ into C++ header files for the ESP32.
90
+
91
+ How to fix:
92
+ 1. Build your frontend application first:
93
+ • Svelte: npm run build
94
+ • React: npm run build
95
+ • Vue: npm run build
96
+ • Angular: ng build
97
+
98
+ 2. Verify the build output directory exists:
99
+ ${(0, consoleColor_1.cyanLog)(`ls -la ${sourcepath}`)}
100
+
101
+ 3. Check your build tool configuration:
102
+ • Vite: vite.config.js → build.outDir
103
+ • Webpack: webpack.config.js → output.path
104
+ • Rollup: rollup.config.js → output.dir
105
+
106
+ 4. Update your svelteesp32 command to match:
107
+ ${(0, consoleColor_1.cyanLog)(`npx svelteesp32 --sourcepath=./build --engine=psychic`)}
108
+
109
+ Current directory: ${currentDirectory}
110
+ Attempted path: ${resolvedPath} (resolved)`);
111
+ }
112
+ function getMaxUriHandlersHint(engine, routeCount) {
113
+ const recommended = routeCount + 5;
114
+ const hints = {
115
+ psychic: `PsychicHttpServer server;
116
+ server.config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
117
+ initSvelteStaticFiles(&server);
118
+ server.listen(80);`,
119
+ psychic2: `PsychicHttpServer server;
120
+ server.config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
121
+ initSvelteStaticFiles(&server);
122
+ server.listen(80);`,
123
+ espidf: `httpd_config_t config = HTTPD_DEFAULT_CONFIG();
124
+ config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
125
+ httpd_handle_t server = NULL;
126
+ httpd_start(&server, &config);
127
+ initSvelteStaticFiles(server);`
128
+ };
129
+ const hint = hints[engine];
130
+ if (!hint)
131
+ return '';
132
+ return ((0, consoleColor_1.yellowLog)('[CONFIG TIP] max_uri_handlers configuration') +
133
+ `
134
+
135
+ Your generated code includes ${routeCount} routes. Make sure your server can handle them:
136
+
137
+ For ${getEngineName(engine)}:
138
+ ${hint}
139
+
140
+ Recommended formula: max_uri_handlers = file_count + 5 (safety margin)
141
+
142
+ Runtime symptoms if too low:
143
+ • Routes not registered (HTTP 404 errors)
144
+ • ESP_ERR_HTTPD_HANDLERS_FULL error in logs
145
+ • Some files load, others don't (random behavior)`);
146
+ }
package/dist/file.js CHANGED
@@ -11,6 +11,7 @@ const picomatch_1 = __importDefault(require("picomatch"));
11
11
  const tinyglobby_1 = require("tinyglobby");
12
12
  const commandLine_1 = require("./commandLine");
13
13
  const consoleColor_1 = require("./consoleColor");
14
+ const errorMessages_1 = require("./errorMessages");
14
15
  const findSimilarFiles = (files) => {
15
16
  const contentComparer = new Map();
16
17
  for (const [filename, content] of files.entries()) {
@@ -79,6 +80,11 @@ const getFiles = () => {
79
80
  const duplicates = findSimilarFiles(result);
80
81
  for (const sameFiles of duplicates)
81
82
  console.log((0, consoleColor_1.yellowLog)(` ${sameFiles.join(', ')} files look like identical`));
83
+ const hasIndex = [...result.keys()].some((f) => f === 'index.html' || f === 'index.htm' || f.endsWith('/index.html') || f.endsWith('/index.htm'));
84
+ if (!hasIndex && !commandLine_1.cmdLine.noIndexCheck) {
85
+ console.error('\n' + (0, errorMessages_1.getMissingIndexError)(commandLine_1.cmdLine.engine));
86
+ process.exit(1);
87
+ }
82
88
  return result;
83
89
  };
84
90
  exports.getFiles = getFiles;
package/dist/index.d.ts CHANGED
@@ -1 +1,7 @@
1
- export {};
1
+ import { CppCodeSource } from './cppCode';
2
+ declare const shouldUseGzip: (originalSize: number, compressedSize: number) => boolean;
3
+ declare const calculateCompressionRatio: (originalSize: number, compressedSize: number) => number;
4
+ declare const formatCompressionLog: (filename: string, padding: string, originalSize: number, compressedSize: number, useGzip: boolean) => string;
5
+ 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 };
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ 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;
6
7
  const node_crypto_1 = require("node:crypto");
7
8
  const node_fs_1 = require("node:fs");
8
9
  const node_path_1 = __importDefault(require("node:path"));
@@ -11,6 +12,7 @@ const mime_types_1 = require("mime-types");
11
12
  const commandLine_1 = require("./commandLine");
12
13
  const consoleColor_1 = require("./consoleColor");
13
14
  const cppCode_1 = require("./cppCode");
15
+ const errorMessages_1 = require("./errorMessages");
14
16
  const file_1 = require("./file");
15
17
  const GZIP_MIN_SIZE = 1024;
16
18
  const GZIP_MIN_REDUCTION_RATIO = 0.85;
@@ -22,7 +24,9 @@ const summary = {
22
24
  const sources = [];
23
25
  const filesByExtension = [];
24
26
  const shouldUseGzip = (originalSize, compressedSize) => originalSize > GZIP_MIN_SIZE && compressedSize < originalSize * GZIP_MIN_REDUCTION_RATIO;
27
+ exports.shouldUseGzip = shouldUseGzip;
25
28
  const calculateCompressionRatio = (originalSize, compressedSize) => Math.round((compressedSize / originalSize) * 100);
29
+ exports.calculateCompressionRatio = calculateCompressionRatio;
26
30
  const formatCompressionLog = (filename, padding, originalSize, compressedSize, useGzip) => {
27
31
  const ratio = calculateCompressionRatio(originalSize, compressedSize);
28
32
  const sizeInfo = `(${originalSize} -> ${compressedSize} = ${ratio}%)`;
@@ -31,7 +35,8 @@ const formatCompressionLog = (filename, padding, originalSize, compressedSize, u
31
35
  const tooSmall = originalSize <= GZIP_MIN_SIZE ? '(too small) ' : '';
32
36
  return (0, consoleColor_1.yellowLog)(` [${filename}] ${padding} x gzip unused ${tooSmall}${sizeInfo}`);
33
37
  };
34
- const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, md5, isGzip) => ({
38
+ exports.formatCompressionLog = formatCompressionLog;
39
+ const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, sha256, isGzip) => ({
35
40
  filename,
36
41
  dataname,
37
42
  datanameUpperCase: dataname.toUpperCase(),
@@ -39,8 +44,9 @@ const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, m
39
44
  contentGzip: isGzip ? contentGzip : content,
40
45
  isGzip,
41
46
  mime: mimeType,
42
- md5
47
+ sha256
43
48
  });
49
+ exports.createSourceEntry = createSourceEntry;
44
50
  const updateExtensionGroup = (extension) => {
45
51
  const group = filesByExtension.find((fe) => fe.extension === extension);
46
52
  if (group)
@@ -48,6 +54,7 @@ const updateExtensionGroup = (extension) => {
48
54
  else
49
55
  filesByExtension.push({ extension, count: 1 });
50
56
  };
57
+ exports.updateExtensionGroup = updateExtensionGroup;
51
58
  console.log('Collecting source files');
52
59
  const files = (0, file_1.getFiles)();
53
60
  if (files.size === 0) {
@@ -66,12 +73,12 @@ for (const [originalFilename, content] of files) {
66
73
  if (extension.startsWith('.'))
67
74
  extension = extension.slice(1);
68
75
  updateExtensionGroup(extension);
69
- const md5 = (0, node_crypto_1.createHash)('md5').update(content).digest('hex');
76
+ const sha256 = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
70
77
  summary.size += content.length;
71
78
  const zipContent = (0, node_zlib_1.gzipSync)(content, { level: 9 });
72
79
  summary.gzipsize += zipContent.length;
73
80
  const useGzip = shouldUseGzip(content.length, zipContent.length);
74
- sources.push(createSourceEntry(filename, dataname, content, zipContent, mimeType, md5, useGzip));
81
+ sources.push(createSourceEntry(filename, dataname, content, zipContent, mimeType, sha256, useGzip));
75
82
  const padding = ' '.repeat(longestFilename - originalFilename.length);
76
83
  console.log(formatCompressionLog(originalFilename, padding, content.length, zipContent.length, useGzip));
77
84
  }
@@ -82,3 +89,5 @@ const cppFile = (0, cppCode_1.getCppCode)(sources, filesByExtension);
82
89
  (0, node_fs_1.writeFileSync)(commandLine_1.cmdLine.outputfile, cppFile, { flush: true, encoding: 'utf8' });
83
90
  console.log(`${summary.filecount} files, ${Math.round(summary.size / 1024)}kB original size, ${Math.round(summary.gzipsize / 1024)}kB gzip size`);
84
91
  console.log(`${commandLine_1.cmdLine.outputfile} ${Math.round(cppFile.length / 1024)}kB size`);
92
+ if (commandLine_1.cmdLine.engine === 'psychic' || commandLine_1.cmdLine.engine === 'psychic2' || commandLine_1.cmdLine.engine === 'espidf')
93
+ console.log('\n' + (0, errorMessages_1.getMaxUriHandlersHint)(commandLine_1.cmdLine.engine, sources.length));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "1.13.0",
3
+ "version": "1.14.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",
@@ -61,22 +61,22 @@
61
61
  ],
62
62
  "devDependencies": {
63
63
  "@types/mime-types": "^3.0.1",
64
- "@types/node": "^24.10.1",
64
+ "@types/node": "^25.1.0",
65
65
  "@types/picomatch": "^4.0.2",
66
- "@typescript-eslint/eslint-plugin": "^8.48.1",
67
- "@typescript-eslint/parser": "^8.48.1",
68
- "@vitest/coverage-v8": "^4.0.15",
69
- "eslint": "^9.39.1",
66
+ "@typescript-eslint/eslint-plugin": "^8.54.0",
67
+ "@typescript-eslint/parser": "^8.54.0",
68
+ "@vitest/coverage-v8": "^4.0.18",
69
+ "eslint": "^9.39.2",
70
70
  "eslint-config-prettier": "^10.1.8",
71
71
  "eslint-plugin-simple-import-sort": "^12.1.1",
72
72
  "eslint-plugin-unicorn": "^62.0.0",
73
- "memfs": "^4.51.1",
73
+ "memfs": "^4.56.10",
74
74
  "nodemon": "^3.1.11",
75
- "prettier": "^3.7.4",
75
+ "prettier": "^3.8.1",
76
76
  "ts-node": "^10.9.2",
77
77
  "tsx": "^4.21.0",
78
78
  "typescript": "^5.9.3",
79
- "vitest": "^4.0.15"
79
+ "vitest": "^4.0.18"
80
80
  },
81
81
  "dependencies": {
82
82
  "handlebars": "^4.7.8",