svelteesp32 2.3.3 → 2.4.1

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,7 @@ 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
91
92
  - **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
93
  - **v2.2.0** — SPA routing catch-all (`--spa`) for client-side routers on all four engines
93
94
  - **v2.1.0** — New Arduino WebServer engine (`-e webserver`), dependency updates
@@ -241,10 +242,10 @@ The generated header file includes everything your ESP needs:
241
242
  #include <PsychicHttp.h>
242
243
  #include <PsychicHttpsServer.h>
243
244
 
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...";
245
+ static const uint8_t datagzip_assets_index_KwubEIf__js[12547] = {0x1f, 0x8b, 0x8, 0x0, ...
246
+ static const uint8_t datagzip_assets_index_Soe6cpLA_css[5368] = {0x1f, 0x8b, 0x8, 0x0, 0x0, ...
247
+ static const char etag_assets_index_KwubEIf__js[] = "387b88e345cc56ef9091...";
248
+ static const char etag_assets_index_Soe6cpLA_css[] = "d4f23bc45ef67890ab12...";
248
249
 
249
250
  // File manifest for runtime introspection
250
251
  struct SVELTEESP32_FileInfo {
@@ -424,6 +425,63 @@ What gets generated per engine:
424
425
 
425
426
  **Note:** `--spa` requires `index.html` or `index.htm` in the source directory — a warning is printed if it is missing.
426
427
 
428
+ ### Analyze Mode (CI Size Budget Checks)
429
+
430
+ Use `--analyze` in CI to validate firmware size budgets without producing any output file:
431
+
432
+ ```bash
433
+ npx svelteesp32 -e psychic -s ./dist --maxsize=400k --maxgzipsize=150k --analyze
434
+ ```
435
+
436
+ Sample output:
437
+
438
+ ```
439
+ index.html Original Gzip
440
+ ──────────────────────────────────── ──────── ────────
441
+ assets/index-KwubEIf-.js 37.9kB 12.3kB
442
+ assets/index-Soe6cpLA.css 31.7kB 5.2kB
443
+ favicon.png 32.5kB 32.5kB [no gzip]
444
+ index.html 0.5kB 0.3kB
445
+ ────────────────────────────────────────────────────────
446
+ Total 102.6kB 50.3kB
447
+ Budget (maxsize) 400.0kB - ✓ PASS
448
+ Budget (maxgzipsize) - 150.0kB ✓ PASS
449
+ ```
450
+
451
+ Exits with code **1** if any budget is exceeded — CI fails automatically. Mutually exclusive with `--dryrun`.
452
+
453
+ ### JSON Manifest
454
+
455
+ Add `--manifest` to write a companion `.manifest.json` file alongside the header (same directory, same base name):
456
+
457
+ ```bash
458
+ npx svelteesp32 -e psychic -s ./dist -o ./esp32/svelteesp32.h --manifest
459
+ # also writes ./esp32/svelteesp32.manifest.json
460
+ ```
461
+
462
+ The manifest records build metadata and per-file details for tooling and dashboards:
463
+
464
+ ```json
465
+ {
466
+ "generated": "2026-04-26T12:00:00.000Z",
467
+ "engine": "psychic",
468
+ "etag": "false",
469
+ "gzip": "true",
470
+ "filecount": 4,
471
+ "size": 104960,
472
+ "gzipSize": 51507,
473
+ "files": [
474
+ {
475
+ "path": "/assets/index-KwubEIf-.js",
476
+ "mime": "text/javascript",
477
+ "size": 38850,
478
+ "gzipSize": 12547,
479
+ "isGzip": true
480
+ }
481
+ ]
482
+ }
483
+ ```
484
+
427
485
  ### C++ Build-Time Validation
428
486
 
429
487
  Catch configuration issues at compile time with generated defines:
@@ -473,28 +531,31 @@ Called for every response (200 = content served, 304 = cache hit).
473
531
 
474
532
  ## CLI Reference
475
533
 
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 | |
534
+ | Option | Description | Default |
535
+ | -------------------- | -------------------------------------------------------------------------------------- | ----------------------- |
536
+ | `-s` | Source folder with compiled web files | (required) |
537
+ | `-e` | Web server engine (psychic/async/espidf/webserver) | `psychic` |
538
+ | `-o` | Output header file path | `svelteesp32.h` |
539
+ | `--etag` | ETag caching (true/false/compiler) | `false` |
540
+ | `--gzip` | Gzip compression (true/false/compiler) | `true` |
541
+ | `--created` | Include creation timestamp in header | `false` |
542
+ | `--exclude` | Exclude files by glob pattern | (none) |
543
+ | `--basepath` | URL prefix for all routes | (none) |
544
+ | `--maxsize` | Max total uncompressed size (e.g., `400k`, `1m`) | (none) |
545
+ | `--maxgzipsize` | Max total gzip size (e.g., `150k`, `500k`) | (none) |
546
+ | `--cachetime` | Cache-Control max-age in seconds (all files) | `0` |
547
+ | `--cachetime-html` | max-age for HTML files (overrides `--cachetime`) | (unset) |
548
+ | `--cachetime-assets` | max-age for non-HTML files (overrides `--cachetime`) | (unset) |
549
+ | `--version` | Version string in header | (none) |
550
+ | `--define` | C++ define prefix | `SVELTEESP32` |
551
+ | `--espmethod` | Init function name | `initSvelteStaticFiles` |
552
+ | `--config` | Custom RC file path | `.svelteesp32rc.json` |
553
+ | `--dryrun` | Show route table + summary without writing output | `false` |
554
+ | `--analyze` | Print per-file size table and budget status, no output written; exits 1 if over budget | `false` |
555
+ | `--manifest` | Write companion `.manifest.json` alongside the header | `false` |
556
+ | `--spa` | Serve index.html for unmatched routes (SPA routing) | `false` |
557
+ | `--noindexcheck` | Skip index.html validation | `false` |
558
+ | `-h` | Show help | |
498
559
 
499
560
  ---
500
561
 
@@ -518,10 +579,14 @@ Store your settings in `.svelteesp32rc.json` for zero-argument builds:
518
579
  "cachetimeassets": 31536000,
519
580
  "noindexcheck": false,
520
581
  "dryrun": false,
521
- "spa": false
582
+ "analyze": false,
583
+ "spa": false,
584
+ "manifest": false
522
585
  }
523
586
  ```
524
587
 
588
+ Boolean fields (`noindexcheck`, `dryrun`, `analyze`, `spa`, `manifest`, `created`) accept native JSON booleans (`true`/`false`) or their string equivalents (`"true"`/`"false"`), matching the existing behaviour of `etag` and `gzip`.
589
+
525
590
  Then just run:
526
591
 
527
592
  ```bash
@@ -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 {
@@ -31,15 +33,17 @@ interface IRcFileConfig {
31
33
  cachetime?: number;
32
34
  cachetimehtml?: number;
33
35
  cachetimeassets?: number;
34
- created?: boolean;
36
+ created?: boolean | 'true' | 'false';
35
37
  version?: string;
36
38
  exclude?: string[];
37
39
  basepath?: string;
38
40
  maxsize?: number | string;
39
41
  maxgzipsize?: number | string;
40
- noindexcheck?: boolean;
41
- dryrun?: boolean;
42
- spa?: boolean;
42
+ noindexcheck?: boolean | 'true' | 'false';
43
+ dryrun?: boolean | 'true' | 'false';
44
+ analyze?: boolean | 'true' | 'false';
45
+ spa?: boolean | 'true' | 'false';
46
+ manifest?: boolean | 'true' | 'false';
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:
@@ -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))
@@ -329,12 +333,36 @@ function validateRcConfig(config, rcPath) {
329
333
  }
330
334
  validateSizeOption(configObject, 'maxsize');
331
335
  validateSizeOption(configObject, 'maxgzipsize');
332
- if (configObject['noindexcheck'] !== undefined && typeof configObject['noindexcheck'] !== 'boolean')
336
+ if (configObject['created'] !== undefined &&
337
+ typeof configObject['created'] !== 'boolean' &&
338
+ configObject['created'] !== 'true' &&
339
+ configObject['created'] !== 'false')
340
+ throw new TypeError(`Invalid created in RC file: ${configObject['created']} (must be boolean)`);
341
+ if (configObject['noindexcheck'] !== undefined &&
342
+ typeof configObject['noindexcheck'] !== 'boolean' &&
343
+ configObject['noindexcheck'] !== 'true' &&
344
+ configObject['noindexcheck'] !== 'false')
333
345
  throw new TypeError(`Invalid noindexcheck in RC file: ${configObject['noindexcheck']} (must be boolean)`);
334
- if (configObject['dryrun'] !== undefined && typeof configObject['dryrun'] !== 'boolean')
346
+ if (configObject['dryrun'] !== undefined &&
347
+ typeof configObject['dryrun'] !== 'boolean' &&
348
+ configObject['dryrun'] !== 'true' &&
349
+ configObject['dryrun'] !== 'false')
335
350
  throw new TypeError(`Invalid dryrun in RC file: ${configObject['dryrun']} (must be boolean)`);
336
- if (configObject['spa'] !== undefined && typeof configObject['spa'] !== 'boolean')
351
+ if (configObject['analyze'] !== undefined &&
352
+ typeof configObject['analyze'] !== 'boolean' &&
353
+ configObject['analyze'] !== 'true' &&
354
+ configObject['analyze'] !== 'false')
355
+ throw new TypeError(`Invalid analyze in RC file: ${configObject['analyze']} (must be boolean)`);
356
+ if (configObject['spa'] !== undefined &&
357
+ typeof configObject['spa'] !== 'boolean' &&
358
+ configObject['spa'] !== 'true' &&
359
+ configObject['spa'] !== 'false')
337
360
  throw new TypeError(`Invalid spa in RC file: ${configObject['spa']} (must be boolean)`);
361
+ if (configObject['manifest'] !== undefined &&
362
+ typeof configObject['manifest'] !== 'boolean' &&
363
+ configObject['manifest'] !== 'true' &&
364
+ configObject['manifest'] !== 'false')
365
+ throw new TypeError(`Invalid manifest in RC file: ${configObject['manifest']} (must be boolean)`);
338
366
  if (configObject['outputfile'] !== undefined && node_path_1.default.isAbsolute(configObject['outputfile']))
339
367
  throw new Error(`'outputfile' in RC file must be a relative path (use --output CLI flag for absolute paths): ${configObject['outputfile']}`);
340
368
  return configObject;
@@ -392,7 +420,7 @@ function parseArguments() {
392
420
  if (rcConfig.cachetimeassets !== undefined)
393
421
  result.cachetimeAssets = rcConfig.cachetimeassets;
394
422
  if (rcConfig.created !== undefined)
395
- result.created = rcConfig.created;
423
+ result.created = rcConfig.created === true || rcConfig.created === 'true';
396
424
  if (rcConfig.version)
397
425
  result.version = rcConfig.version;
398
426
  if (rcConfig.espmethod)
@@ -406,11 +434,15 @@ function parseArguments() {
406
434
  if (rcConfig.maxgzipsize !== undefined)
407
435
  result.maxGzipSize = rcConfig.maxgzipsize;
408
436
  if (rcConfig.noindexcheck !== undefined)
409
- result.noIndexCheck = rcConfig.noindexcheck;
437
+ result.noIndexCheck = rcConfig.noindexcheck === true || rcConfig.noindexcheck === 'true';
410
438
  if (rcConfig.dryrun !== undefined)
411
- result.dryRun = rcConfig.dryrun;
439
+ result.dryRun = rcConfig.dryrun === true || rcConfig.dryrun === 'true';
440
+ if (rcConfig.analyze !== undefined)
441
+ result.analyze = rcConfig.analyze === true || rcConfig.analyze === 'true';
412
442
  if (rcConfig.spa !== undefined)
413
- result.spa = rcConfig.spa;
443
+ result.spa = rcConfig.spa === true || rcConfig.spa === 'true';
444
+ if (rcConfig.manifest !== undefined)
445
+ result.manifest = rcConfig.manifest === true || rcConfig.manifest === 'true';
414
446
  if (rcConfig.exclude && rcConfig.exclude.length > 0)
415
447
  result.exclude = [...rcConfig.exclude];
416
448
  const cliExclude = [];
@@ -513,10 +545,18 @@ function parseArguments() {
513
545
  result.dryRun = true;
514
546
  continue;
515
547
  }
548
+ if (argument === '--analyze') {
549
+ result.analyze = true;
550
+ continue;
551
+ }
516
552
  if (argument === '--spa') {
517
553
  result.spa = true;
518
554
  continue;
519
555
  }
556
+ if (argument === '--manifest') {
557
+ result.manifest = true;
558
+ continue;
559
+ }
520
560
  if (argument.startsWith('-') && !argument.startsWith('--')) {
521
561
  const flag = argument.slice(1);
522
562
  const nextArgument = arguments_[index + 1];
@@ -557,6 +597,8 @@ function parseArguments() {
557
597
  console.error('Error: --sourcepath is required (can be specified in RC file or CLI)');
558
598
  showHelp();
559
599
  }
600
+ if (result.dryRun && result.analyze)
601
+ throw new Error('--analyze and --dryrun are mutually exclusive. Use --analyze for CI budget checks or --dryrun for a developer route preview.');
560
602
  return result;
561
603
  }
562
604
  function formatConfiguration(cmdLine) {
@@ -588,6 +630,8 @@ function formatConfiguration(cmdLine) {
588
630
  parts.push(`maxGzipSize=${cmdLine.maxGzipSize}`);
589
631
  if (cmdLine.spa)
590
632
  parts.push(`spa=${cmdLine.spa}`);
633
+ if (cmdLine.analyze)
634
+ parts.push(`analyze=${cmdLine.analyze}`);
591
635
  if (cmdLine.exclude.length > 0)
592
636
  parts.push(`exclude=[${cmdLine.exclude.join(', ')}]`);
593
637
  return parts.join(' ').replace(/[\n\r]/g, ' ');
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.3",
3
+ "version": "2.4.1",
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.2",
65
+ "@types/node": "^25.6.0",
66
66
  "@types/picomatch": "^4.0.3",
67
- "@typescript-eslint/eslint-plugin": "^8.58.0",
68
- "@typescript-eslint/parser": "^8.58.0",
69
- "@vitest/coverage-v8": "^4.1.2",
70
- "eslint": "^10.2.0",
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
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": "^6.0.2",
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
  }