svelteesp32 2.3.2 → 2.4.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
@@ -88,6 +88,10 @@ void setup() {
88
88
 
89
89
  ## What's New
90
90
 
91
+ - **v2.4.0** — `--analyze` for CI size budget checks (per-file table, exits 1 on over-budget); `--manifest` to write a companion JSON manifest alongside the header
92
+ - **v2.3.3** — TypeScript 6 upgrade; `module`/`moduleResolution` updated to `Node16`, target raised to `ES2023`
93
+ - **v2.3.2** — Security hardening: symlink traversal blocked, RC file CWD warning, absolute `outputfile` rejected in RC files, per-file 50 MB size limit
94
+ - **v2.3.1** — Fixes: `--version` and `--basepath` input validation, `formatConfiguration` newline safety
91
95
  - **v2.3.0** — `--cachetime-html` and `--cachetime-assets` for per-type cache control (e.g. `no-cache` for HTML, 1-year for content-hashed JS/CSS)
92
96
  - **v2.2.0** — SPA routing catch-all (`--spa`) for client-side routers on all four engines
93
97
  - **v2.1.0** — New Arduino WebServer engine (`-e webserver`), dependency updates
@@ -241,10 +245,10 @@ The generated header file includes everything your ESP needs:
241
245
  #include <PsychicHttp.h>
242
246
  #include <PsychicHttpsServer.h>
243
247
 
244
- const uint8_t datagzip_assets_index_KwubEIf__js[12547] = {0x1f, 0x8b, 0x8, 0x0, ...
245
- const uint8_t datagzip_assets_index_Soe6cpLA_css[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
246
- const char * etag_assets_index_KwubEIf__js = "387b88e345cc56ef9091...";
247
- const char * etag_assets_index_Soe6cpLA_css = "d4f23bc45ef67890ab12...";
248
+ static const uint8_t datagzip_assets_index_KwubEIf__js[12547] = {0x1f, 0x8b, 0x8, 0x0, ...
249
+ static const uint8_t datagzip_assets_index_Soe6cpLA_css[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
250
+ static const char etag_assets_index_KwubEIf__js[] = "387b88e345cc56ef9091...";
251
+ static const char etag_assets_index_Soe6cpLA_css[] = "d4f23bc45ef67890ab12...";
248
252
 
249
253
  // File manifest for runtime introspection
250
254
  struct SVELTEESP32_FileInfo {
@@ -424,6 +428,63 @@ What gets generated per engine:
424
428
 
425
429
  **Note:** `--spa` requires `index.html` or `index.htm` in the source directory — a warning is printed if it is missing.
426
430
 
431
+ ### Analyze Mode (CI Size Budget Checks)
432
+
433
+ Use `--analyze` in CI to validate firmware size budgets without producing any output file:
434
+
435
+ ```bash
436
+ npx svelteesp32 -e psychic -s ./dist --maxsize=400k --maxgzipsize=150k --analyze
437
+ ```
438
+
439
+ Sample output:
440
+
441
+ ```
442
+ index.html Original Gzip
443
+ ──────────────────────────────────── ──────── ────────
444
+ assets/index-KwubEIf-.js 37.9kB 12.3kB
445
+ assets/index-Soe6cpLA.css 31.7kB 5.2kB
446
+ favicon.png 32.5kB 32.5kB [no gzip]
447
+ index.html 0.5kB 0.3kB
448
+ ────────────────────────────────────────────────────────
449
+ Total 102.6kB 50.3kB
450
+ Budget (maxsize) 400.0kB - ✓ PASS
451
+ Budget (maxgzipsize) - 150.0kB ✓ PASS
452
+ ```
453
+
454
+ Exits with code **1** if any budget is exceeded — CI fails automatically. Mutually exclusive with `--dryrun`.
455
+
456
+ ### JSON Manifest
457
+
458
+ Add `--manifest` to write a companion `.manifest.json` file alongside the header (same directory, same base name):
459
+
460
+ ```bash
461
+ npx svelteesp32 -e psychic -s ./dist -o ./esp32/svelteesp32.h --manifest
462
+ # also writes ./esp32/svelteesp32.manifest.json
463
+ ```
464
+
465
+ The manifest records build metadata and per-file details for tooling and dashboards:
466
+
467
+ ```json
468
+ {
469
+ "generated": "2026-04-26T12:00:00.000Z",
470
+ "engine": "psychic",
471
+ "etag": "false",
472
+ "gzip": "true",
473
+ "filecount": 4,
474
+ "size": 104960,
475
+ "gzipSize": 51507,
476
+ "files": [
477
+ {
478
+ "path": "/assets/index-KwubEIf-.js",
479
+ "mime": "text/javascript",
480
+ "size": 38850,
481
+ "gzipSize": 12547,
482
+ "isGzip": true
483
+ }
484
+ ]
485
+ }
486
+ ```
487
+
427
488
  ### C++ Build-Time Validation
428
489
 
429
490
  Catch configuration issues at compile time with generated defines:
@@ -473,28 +534,31 @@ Called for every response (200 = content served, 304 = cache hit).
473
534
 
474
535
  ## CLI Reference
475
536
 
476
- | Option | Description | Default |
477
- | -------------------- | ---------------------------------------------------- | ----------------------- |
478
- | `-s` | Source folder with compiled web files | (required) |
479
- | `-e` | Web server engine (psychic/async/espidf/webserver) | `psychic` |
480
- | `-o` | Output header file path | `svelteesp32.h` |
481
- | `--etag` | ETag caching (true/false/compiler) | `false` |
482
- | `--gzip` | Gzip compression (true/false/compiler) | `true` |
483
- | `--exclude` | Exclude files by glob pattern | (none) |
484
- | `--basepath` | URL prefix for all routes | (none) |
485
- | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
486
- | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
487
- | `--cachetime` | Cache-Control max-age in seconds (all files) | `0` |
488
- | `--cachetime-html` | max-age for HTML files (overrides `--cachetime`) | (unset) |
489
- | `--cachetime-assets` | max-age for non-HTML files (overrides `--cachetime`) | (unset) |
490
- | `--version` | Version string in header | (none) |
491
- | `--define` | C++ define prefix | `SVELTEESP32` |
492
- | `--espmethod` | Init function name | `initSvelteStaticFiles` |
493
- | `--config` | Custom RC file path | `.svelteesp32rc.json` |
494
- | `--dryrun` | Show route table + summary without writing output | `false` |
495
- | `--spa` | Serve index.html for unmatched routes (SPA routing) | `false` |
496
- | `--noindexcheck` | Skip index.html validation | `false` |
497
- | `-h` | Show help | |
537
+ | Option | Description | Default |
538
+ | -------------------- | -------------------------------------------------------------------------------------- | ----------------------- |
539
+ | `-s` | Source folder with compiled web files | (required) |
540
+ | `-e` | Web server engine (psychic/async/espidf/webserver) | `psychic` |
541
+ | `-o` | Output header file path | `svelteesp32.h` |
542
+ | `--etag` | ETag caching (true/false/compiler) | `false` |
543
+ | `--gzip` | Gzip compression (true/false/compiler) | `true` |
544
+ | `--created` | Include creation timestamp in header | `false` |
545
+ | `--exclude` | Exclude files by glob pattern | (none) |
546
+ | `--basepath` | URL prefix for all routes | (none) |
547
+ | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
548
+ | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
549
+ | `--cachetime` | Cache-Control max-age in seconds (all files) | `0` |
550
+ | `--cachetime-html` | max-age for HTML files (overrides `--cachetime`) | (unset) |
551
+ | `--cachetime-assets` | max-age for non-HTML files (overrides `--cachetime`) | (unset) |
552
+ | `--version` | Version string in header | (none) |
553
+ | `--define` | C++ define prefix | `SVELTEESP32` |
554
+ | `--espmethod` | Init function name | `initSvelteStaticFiles` |
555
+ | `--config` | Custom RC file path | `.svelteesp32rc.json` |
556
+ | `--dryrun` | Show route table + summary without writing output | `false` |
557
+ | `--analyze` | Print per-file size table and budget status, no output written; exits 1 if over budget | `false` |
558
+ | `--manifest` | Write companion `.manifest.json` alongside the header | `false` |
559
+ | `--spa` | Serve index.html for unmatched routes (SPA routing) | `false` |
560
+ | `--noindexcheck` | Skip index.html validation | `false` |
561
+ | `-h` | Show help | |
498
562
 
499
563
  ---
500
564
 
@@ -518,7 +582,9 @@ Store your settings in `.svelteesp32rc.json` for zero-argument builds:
518
582
  "cachetimeassets": 31536000,
519
583
  "noindexcheck": false,
520
584
  "dryrun": false,
521
- "spa": false
585
+ "analyze": false,
586
+ "spa": false,
587
+ "manifest": false
522
588
  }
523
589
  ```
524
590
 
@@ -17,7 +17,9 @@ interface ICopyFilesArguments {
17
17
  maxGzipSize?: number;
18
18
  noIndexCheck?: boolean;
19
19
  dryRun?: boolean;
20
+ analyze?: boolean;
20
21
  spa?: boolean;
22
+ manifest?: boolean;
21
23
  help?: boolean;
22
24
  }
23
25
  interface IRcFileConfig {
@@ -39,7 +41,9 @@ interface IRcFileConfig {
39
41
  maxgzipsize?: number | string;
40
42
  noindexcheck?: boolean;
41
43
  dryrun?: boolean;
44
+ analyze?: boolean;
42
45
  spa?: boolean;
46
+ manifest?: boolean;
43
47
  }
44
48
  declare function validateCppIdentifier(value: string, name: string): string;
45
49
  declare function parseSize(value: string, name: string): number;
@@ -25,7 +25,7 @@ Configuration:
25
25
  Options:
26
26
  -e, --engine <value> The engine for which the include file is created
27
27
  (psychic|async|espidf|webserver) (default: "psychic")
28
- -s, --sourcepath <path> Source dist folder contains compiled web files (required)
28
+ -s, --sourcepath <path> Source dist folder with compiled web files (required)
29
29
  -o, --outputfile <path> Generated output file with path (default: "svelteesp32.h")
30
30
  --etag <value> Use ETAG header for cache (true|false|compiler) (default: "false")
31
31
  --gzip <value> Compress content with gzip (true|false|compiler) (default: "true")
@@ -42,8 +42,10 @@ Options:
42
42
  --maxsize <size> Maximum total uncompressed size (e.g., 400k, 1.5m, 409600)
43
43
  --maxgzipsize <size> Maximum total gzip size (e.g., 150k, 1m, 153600)
44
44
  --dryrun Show summary without writing the output file (default: false)
45
+ --analyze Print per-file size table and budget status without writing (default: false)
45
46
  --spa Serve index.html for unmatched routes (SPA routing) (default: false)
46
- -h, --help Shows this help
47
+ --manifest Write companion JSON manifest file alongside the header (default: false)
48
+ -h, --help Show this help
47
49
 
48
50
  RC File:
49
51
  The tool searches for .svelteesp32rc.json in:
@@ -218,7 +220,7 @@ function interpolateNpmVariables(config, rcFilePath) {
218
220
  const packageJson = parsePackageJson(packageJsonPath);
219
221
  const interpolateString = (value) => {
220
222
  const regex = /\$npm_package_[\dA-Za-z]+(?:_[a-z][\dA-Za-z]*)*/g;
221
- return value.replace(regex, (match) => getNpmPackageVariable(packageJson, match) ?? match);
223
+ return value.replaceAll(regex, (match) => getNpmPackageVariable(packageJson, match) ?? match);
222
224
  };
223
225
  const result = { ...config };
224
226
  if (result.sourcepath)
@@ -287,7 +289,9 @@ function validateRcConfig(config, rcPath) {
287
289
  'maxgzipsize',
288
290
  'noindexcheck',
289
291
  'dryrun',
290
- 'spa'
292
+ 'analyze',
293
+ 'spa',
294
+ 'manifest'
291
295
  ]);
292
296
  for (const key of Object.keys(configObject))
293
297
  if (!validKeys.has(key))
@@ -333,8 +337,12 @@ function validateRcConfig(config, rcPath) {
333
337
  throw new TypeError(`Invalid noindexcheck in RC file: ${configObject['noindexcheck']} (must be boolean)`);
334
338
  if (configObject['dryrun'] !== undefined && typeof configObject['dryrun'] !== 'boolean')
335
339
  throw new TypeError(`Invalid dryrun in RC file: ${configObject['dryrun']} (must be boolean)`);
340
+ if (configObject['analyze'] !== undefined && typeof configObject['analyze'] !== 'boolean')
341
+ throw new TypeError(`Invalid analyze in RC file: ${configObject['analyze']} (must be boolean)`);
336
342
  if (configObject['spa'] !== undefined && typeof configObject['spa'] !== 'boolean')
337
343
  throw new TypeError(`Invalid spa in RC file: ${configObject['spa']} (must be boolean)`);
344
+ if (configObject['manifest'] !== undefined && typeof configObject['manifest'] !== 'boolean')
345
+ throw new TypeError(`Invalid manifest in RC file: ${configObject['manifest']} (must be boolean)`);
338
346
  if (configObject['outputfile'] !== undefined && node_path_1.default.isAbsolute(configObject['outputfile']))
339
347
  throw new Error(`'outputfile' in RC file must be a relative path (use --output CLI flag for absolute paths): ${configObject['outputfile']}`);
340
348
  return configObject;
@@ -409,8 +417,12 @@ function parseArguments() {
409
417
  result.noIndexCheck = rcConfig.noindexcheck;
410
418
  if (rcConfig.dryrun !== undefined)
411
419
  result.dryRun = rcConfig.dryrun;
420
+ if (rcConfig.analyze !== undefined)
421
+ result.analyze = rcConfig.analyze;
412
422
  if (rcConfig.spa !== undefined)
413
423
  result.spa = rcConfig.spa;
424
+ if (rcConfig.manifest !== undefined)
425
+ result.manifest = rcConfig.manifest;
414
426
  if (rcConfig.exclude && rcConfig.exclude.length > 0)
415
427
  result.exclude = [...rcConfig.exclude];
416
428
  const cliExclude = [];
@@ -513,10 +525,18 @@ function parseArguments() {
513
525
  result.dryRun = true;
514
526
  continue;
515
527
  }
528
+ if (argument === '--analyze') {
529
+ result.analyze = true;
530
+ continue;
531
+ }
516
532
  if (argument === '--spa') {
517
533
  result.spa = true;
518
534
  continue;
519
535
  }
536
+ if (argument === '--manifest') {
537
+ result.manifest = true;
538
+ continue;
539
+ }
520
540
  if (argument.startsWith('-') && !argument.startsWith('--')) {
521
541
  const flag = argument.slice(1);
522
542
  const nextArgument = arguments_[index + 1];
@@ -557,6 +577,8 @@ function parseArguments() {
557
577
  console.error('Error: --sourcepath is required (can be specified in RC file or CLI)');
558
578
  showHelp();
559
579
  }
580
+ if (result.dryRun && result.analyze)
581
+ throw new Error('--analyze and --dryrun are mutually exclusive. Use --analyze for CI budget checks or --dryrun for a developer route preview.');
560
582
  return result;
561
583
  }
562
584
  function formatConfiguration(cmdLine) {
@@ -588,6 +610,8 @@ function formatConfiguration(cmdLine) {
588
610
  parts.push(`maxGzipSize=${cmdLine.maxGzipSize}`);
589
611
  if (cmdLine.spa)
590
612
  parts.push(`spa=${cmdLine.spa}`);
613
+ if (cmdLine.analyze)
614
+ parts.push(`analyze=${cmdLine.analyze}`);
591
615
  if (cmdLine.exclude.length > 0)
592
616
  parts.push(`exclude=[${cmdLine.exclude.join(', ')}]`);
593
617
  return parts.join(' ').replace(/[\n\r]/g, ' ');
package/dist/cppCode.js CHANGED
@@ -990,7 +990,7 @@ const postProcessCppCode = (code) => code
990
990
  .filter(Boolean)
991
991
  .map((line) => (line === '//' ? '' : line))
992
992
  .join('\n')
993
- .replace(/\n{2,}/g, '\n');
993
+ .replaceAll(/\n{2,}/g, '\n');
994
994
  const createHandlebarsHelpers = () => {
995
995
  let switchValue;
996
996
  return {
package/dist/index.d.ts CHANGED
@@ -1,10 +1,17 @@
1
1
  import { CppCodeSource, CppCodeSources, ExtensionGroups } from './cppCode';
2
+ type ProcessingSummary = {
3
+ filecount: number;
4
+ size: number;
5
+ gzipsize: number;
6
+ };
2
7
  declare const shouldUseGzip: (originalSize: number, compressedSize: number) => boolean;
3
8
  declare const calculateCompressionRatio: (originalSize: number, compressedSize: number) => number;
4
9
  declare const formatCompressionLog: (filename: string, padding: string, originalSize: number, compressedSize: number, useGzip: boolean) => string;
5
10
  declare const formatSize: (bytes: number) => string;
11
+ declare const formatSizePrecise: (bytes: number) => string;
6
12
  declare const createSourceEntry: (filename: string, dataname: string, content: Buffer, contentGzip: Buffer, mimeType: string, sha256: string, isGzip: boolean) => CppCodeSource;
7
13
  declare const updateExtensionGroup: (filesByExtension: ExtensionGroups, extension: string) => void;
8
14
  declare const formatDryRunRoutes: (sources: CppCodeSources, engine: "psychic" | "async" | "espidf" | "webserver", basePath: string, spa: boolean) => string;
15
+ declare const formatAnalyzeTable: (sources: CppCodeSources, summary: ProcessingSummary, maxSize: number | undefined, maxGzipSize: number | undefined) => string;
9
16
  export declare function main(): void;
10
- export { calculateCompressionRatio, createSourceEntry, formatCompressionLog, formatDryRunRoutes, formatSize, shouldUseGzip, updateExtensionGroup };
17
+ export { calculateCompressionRatio, createSourceEntry, formatAnalyzeTable, formatCompressionLog, formatDryRunRoutes, formatSize, formatSizePrecise, shouldUseGzip, updateExtensionGroup };
package/dist/index.js CHANGED
@@ -3,7 +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.formatSize = exports.formatDryRunRoutes = exports.formatCompressionLog = exports.createSourceEntry = exports.calculateCompressionRatio = void 0;
6
+ exports.updateExtensionGroup = exports.shouldUseGzip = exports.formatSizePrecise = exports.formatSize = exports.formatDryRunRoutes = exports.formatCompressionLog = exports.formatAnalyzeTable = exports.createSourceEntry = exports.calculateCompressionRatio = void 0;
7
7
  exports.main = main;
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = __importDefault(require("node:path"));
@@ -35,6 +35,12 @@ const formatSize = (bytes) => {
35
35
  return `${Math.round(bytes / 1024)}kB`;
36
36
  };
37
37
  exports.formatSize = formatSize;
38
+ const formatSizePrecise = (bytes) => {
39
+ if (bytes < 1024)
40
+ return `${bytes}B`;
41
+ return `${(bytes / 1024).toFixed(1)}kB`;
42
+ };
43
+ exports.formatSizePrecise = formatSizePrecise;
38
44
  const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, sha256, isGzip) => ({
39
45
  filename,
40
46
  dataname,
@@ -94,6 +100,39 @@ const formatDryRunRoutes = (sources, engine, basePath, spa) => {
94
100
  .join('\n');
95
101
  };
96
102
  exports.formatDryRunRoutes = formatDryRunRoutes;
103
+ const formatAnalyzeTable = (sources, summary, maxSize, maxGzipSize) => {
104
+ const rows = sources.map((s) => ({
105
+ file: s.filename,
106
+ orig: formatSizePrecise(s.content.length),
107
+ gzip: formatSizePrecise(s.isGzip ? s.contentGzip.length : s.content.length),
108
+ tag: s.isGzip ? '' : '[no gzip]'
109
+ }));
110
+ const fileWidth = Math.max(4, ...rows.map((r) => r.file.length), 'Total'.length);
111
+ const origWidth = Math.max(8, ...rows.map((r) => r.orig.length), formatSizePrecise(summary.size).length);
112
+ const gzipWidth = Math.max(8, ...rows.map((r) => r.gzip.length), formatSizePrecise(summary.gzipsize).length);
113
+ const separator = `${'─'.repeat(fileWidth)} ${'─'.repeat(origWidth)} ${'─'.repeat(gzipWidth)}`;
114
+ const header = `${'File'.padEnd(fileWidth)} ${'Original'.padEnd(origWidth)} ${'Gzip'.padEnd(gzipWidth)}`;
115
+ const dataRows = rows.map((r) => {
116
+ const tagPart = r.tag ? ` ${r.tag}` : '';
117
+ return `${r.file.padEnd(fileWidth)} ${r.orig.padEnd(origWidth)} ${r.gzip.padEnd(gzipWidth)}${tagPart}`.trimEnd();
118
+ });
119
+ const totalOrig = formatSizePrecise(summary.size);
120
+ const totalGzip = formatSizePrecise(summary.gzipsize);
121
+ const totalRow = `${'Total'.padEnd(fileWidth)} ${totalOrig.padEnd(origWidth)} ${totalGzip.padEnd(gzipWidth)}`;
122
+ const lines = [header, separator, ...dataRows, separator, totalRow];
123
+ if (maxSize !== undefined) {
124
+ const pass = summary.size <= maxSize;
125
+ const budgetRow = `${'Budget (maxsize)'.padEnd(fileWidth)} ${formatSizePrecise(maxSize).padEnd(origWidth)} ${'-'.padEnd(gzipWidth)} ${pass ? '✓ PASS' : '✗ FAIL'}`;
126
+ lines.push(budgetRow);
127
+ }
128
+ if (maxGzipSize !== undefined) {
129
+ const pass = summary.gzipsize <= maxGzipSize;
130
+ const budgetRow = `${'Budget (maxgzipsize)'.padEnd(fileWidth)} ${'-'.padEnd(origWidth)} ${formatSizePrecise(maxGzipSize).padEnd(gzipWidth)} ${pass ? '✓ PASS' : '✗ FAIL'}`;
131
+ lines.push(budgetRow);
132
+ }
133
+ return lines.join('\n');
134
+ };
135
+ exports.formatAnalyzeTable = formatAnalyzeTable;
97
136
  function main() {
98
137
  const summary = {
99
138
  filecount: 0,
@@ -138,6 +177,14 @@ function main() {
138
177
  }
139
178
  console.log('');
140
179
  filesByExtension.sort((left, right) => left.extension.localeCompare(right.extension));
180
+ if (commandLine_1.cmdLine.analyze) {
181
+ console.log(formatAnalyzeTable(sources, summary, commandLine_1.cmdLine.maxSize, commandLine_1.cmdLine.maxGzipSize));
182
+ const overBudget = (commandLine_1.cmdLine.maxSize !== undefined && summary.size > commandLine_1.cmdLine.maxSize) ||
183
+ (commandLine_1.cmdLine.maxGzipSize !== undefined && summary.gzipsize > commandLine_1.cmdLine.maxGzipSize);
184
+ if (overBudget)
185
+ process.exit(1);
186
+ return;
187
+ }
141
188
  if (commandLine_1.cmdLine.maxSize !== undefined && summary.size > commandLine_1.cmdLine.maxSize) {
142
189
  console.error((0, errorMessages_1.getSizeBudgetExceededError)('size', commandLine_1.cmdLine.maxSize, summary.size));
143
190
  process.exit(1);
@@ -161,6 +208,27 @@ function main() {
161
208
  (0, node_fs_1.writeFileSync)(commandLine_1.cmdLine.outputfile, cppFile, { flush: true, encoding: 'utf8' });
162
209
  console.log(`${summary.filecount} files, ${formatSize(summary.size)} original size, ${formatSize(summary.gzipsize)} gzip size`);
163
210
  console.log(`${commandLine_1.cmdLine.outputfile} ${formatSize(cppFile.length)} size`);
211
+ if (commandLine_1.cmdLine.manifest) {
212
+ const manifestPath = node_path_1.default.join(node_path_1.default.dirname(commandLine_1.cmdLine.outputfile), node_path_1.default.basename(commandLine_1.cmdLine.outputfile, node_path_1.default.extname(commandLine_1.cmdLine.outputfile)) + '.manifest.json');
213
+ const manifest = {
214
+ generated: new Date().toISOString(),
215
+ engine: commandLine_1.cmdLine.engine,
216
+ etag: commandLine_1.cmdLine.etag,
217
+ gzip: commandLine_1.cmdLine.gzip,
218
+ filecount: summary.filecount,
219
+ size: summary.size,
220
+ gzipSize: summary.gzipsize,
221
+ files: sources.map((s) => ({
222
+ path: s.filename,
223
+ mime: s.mime,
224
+ size: s.content.length,
225
+ gzipSize: s.isGzip ? s.contentGzip.length : s.content.length,
226
+ isGzip: s.isGzip
227
+ }))
228
+ };
229
+ (0, node_fs_1.writeFileSync)(manifestPath, JSON.stringify(manifest, undefined, 2), { encoding: 'utf8' });
230
+ console.log(`${manifestPath} manifest written`);
231
+ }
164
232
  if (commandLine_1.cmdLine.engine === 'psychic' || commandLine_1.cmdLine.engine === 'espidf')
165
233
  console.log('\n' + (0, errorMessages_1.getMaxUriHandlersHint)(commandLine_1.cmdLine.engine, sources.length, commandLine_1.cmdLine.espmethod));
166
234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "2.3.2",
3
+ "version": "2.4.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",
@@ -62,27 +62,27 @@
62
62
  "@eslint/eslintrc": "^3.3.5",
63
63
  "@eslint/js": "^10.0.1",
64
64
  "@types/mime-types": "^3.0.1",
65
- "@types/node": "^25.5.0",
66
- "@types/picomatch": "^4.0.2",
67
- "@typescript-eslint/eslint-plugin": "^8.57.2",
68
- "@typescript-eslint/parser": "^8.57.2",
69
- "@vitest/coverage-v8": "^4.1.2",
70
- "eslint": "^10.1.0",
65
+ "@types/node": "^25.6.0",
66
+ "@types/picomatch": "^4.0.3",
67
+ "@typescript-eslint/eslint-plugin": "^8.59.0",
68
+ "@typescript-eslint/parser": "^8.59.0",
69
+ "@vitest/coverage-v8": "^4.1.5",
70
+ "eslint": "^10.2.1",
71
71
  "eslint-config-prettier": "^10.1.8",
72
- "eslint-plugin-simple-import-sort": "^12.1.1",
72
+ "eslint-plugin-simple-import-sort": "^13.0.0",
73
73
  "eslint-plugin-unicorn": "^64.0.0",
74
- "memfs": "^4.57.1",
74
+ "memfs": "^4.57.2",
75
75
  "nodemon": "^3.1.14",
76
- "prettier": "^3.8.1",
76
+ "prettier": "^3.8.3",
77
77
  "ts-node": "^10.9.2",
78
78
  "tsx": "^4.21.0",
79
- "typescript": "^5.9.3",
80
- "vitest": "^4.1.2"
79
+ "typescript": "^6.0.3",
80
+ "vitest": "^4.1.5"
81
81
  },
82
82
  "dependencies": {
83
83
  "handlebars": "^4.7.9",
84
84
  "mime-types": "^3.0.2",
85
85
  "picomatch": "^4.0.4",
86
- "tinyglobby": "^0.2.15"
86
+ "tinyglobby": "^0.2.16"
87
87
  }
88
88
  }