svelteesp32 1.15.0 → 1.16.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
@@ -75,7 +75,7 @@ void setup() {
75
75
 
76
76
  ## What's New
77
77
 
78
- - **v1.15.0** — `--base-path` for multiple frontends (e.g., `/admin`, `/app`)
78
+ - **v1.15.0** — `--basepath` for multiple frontends (e.g., `/admin`, `/app`)
79
79
  - **v1.13.0** — npm package variable interpolation in RC files
80
80
  - **v1.12.0** — RC file configuration support
81
81
  - **v1.11.0** — File exclusion patterns
@@ -301,10 +301,10 @@ Fine-tune how browsers cache your content:
301
301
 
302
302
  Your `index.html` is automatically served at the root URL — just like any web server. Visit `http://esp32.local/` and your app loads.
303
303
 
304
- **API-only projects?** Skip index validation with `--no-index-check`:
304
+ **API-only projects?** Skip index validation with `--noindexcheck`:
305
305
 
306
306
  ```bash
307
- npx svelteesp32 -e psychic -s ./dist -o ./output.h --no-index-check
307
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --noindexcheck
308
308
  ```
309
309
 
310
310
  ### File Exclusion
@@ -335,8 +335,8 @@ Excluded 3 file(s):
335
335
  Serve multiple web apps from one ESP32 using URL prefixes:
336
336
 
337
337
  ```bash
338
- npx svelteesp32 -s ./admin-dist -o ./admin.h --base-path=/admin
339
- npx svelteesp32 -s ./user-dist -o ./user.h --base-path=/app
338
+ npx svelteesp32 -s ./admin-dist -o ./admin.h --basepath=/admin
339
+ npx svelteesp32 -s ./user-dist -o ./user.h --basepath=/app
340
340
  ```
341
341
 
342
342
  ```c
@@ -402,22 +402,22 @@ Called for every response (200 = content served, 304 = cache hit).
402
402
 
403
403
  ## CLI Reference
404
404
 
405
- | Option | Description | Default |
406
- | ------------------ | ------------------------------------------------- | ----------------------- |
407
- | `-s` | Source folder with compiled web files | (required) |
408
- | `-e` | Web server engine (psychic/psychic2/async/espidf) | `psychic` |
409
- | `-o` | Output header file path | `svelteesp32.h` |
410
- | `--etag` | ETag caching (true/false/compiler) | `false` |
411
- | `--gzip` | Gzip compression (true/false/compiler) | `true` |
412
- | `--exclude` | Exclude files by glob pattern | System files |
413
- | `--base-path` | URL prefix for all routes | (none) |
414
- | `--cachetime` | Cache-Control max-age in seconds | `0` |
415
- | `--version` | Version string in header | (none) |
416
- | `--define` | C++ define prefix | `SVELTEESP32` |
417
- | `--espmethod` | Init function name | `initSvelteStaticFiles` |
418
- | `--config` | Custom RC file path | `.svelteesp32rc.json` |
419
- | `--no-index-check` | Skip index.html validation | `false` |
420
- | `-h` | Show help | |
405
+ | Option | Description | Default |
406
+ | ---------------- | ------------------------------------------------- | ----------------------- |
407
+ | `-s` | Source folder with compiled web files | (required) |
408
+ | `-e` | Web server engine (psychic/psychic2/async/espidf) | `psychic` |
409
+ | `-o` | Output header file path | `svelteesp32.h` |
410
+ | `--etag` | ETag caching (true/false/compiler) | `false` |
411
+ | `--gzip` | Gzip compression (true/false/compiler) | `true` |
412
+ | `--exclude` | Exclude files by glob pattern | System files |
413
+ | `--basepath` | URL prefix for all routes | (none) |
414
+ | `--cachetime` | Cache-Control max-age in seconds | `0` |
415
+ | `--version` | Version string in header | (none) |
416
+ | `--define` | C++ define prefix | `SVELTEESP32` |
417
+ | `--espmethod` | Init function name | `initSvelteStaticFiles` |
418
+ | `--config` | Custom RC file path | `.svelteesp32rc.json` |
419
+ | `--noindexcheck` | Skip index.html validation | `false` |
420
+ | `-h` | Show help | |
421
421
 
422
422
  ---
423
423
 
@@ -11,6 +11,8 @@ interface ICopyFilesArguments {
11
11
  version: string;
12
12
  exclude: string[];
13
13
  basePath: string;
14
+ maxSize?: number;
15
+ maxGzipSize?: number;
14
16
  noIndexCheck?: boolean;
15
17
  help?: boolean;
16
18
  }
@@ -27,10 +29,13 @@ interface IRcFileConfig {
27
29
  version?: string;
28
30
  exclude?: string[];
29
31
  basePath?: string;
32
+ maxSize?: number | string;
33
+ maxGzipSize?: number | string;
30
34
  }
35
+ declare function parseSize(value: string, name: string): number;
31
36
  declare function getNpmPackageVariable(packageJson: Record<string, unknown>, variableName: string): string | undefined;
32
37
  declare function hasNpmVariables(config: IRcFileConfig): boolean;
33
38
  declare function interpolateNpmVariables(config: IRcFileConfig, rcFilePath: string): IRcFileConfig;
34
- export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables };
39
+ export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables, parseSize };
35
40
  export declare function formatConfiguration(cmdLine: ICopyFilesArguments): string;
36
41
  export declare const cmdLine: ICopyFilesArguments;
@@ -7,6 +7,7 @@ exports.cmdLine = void 0;
7
7
  exports.getNpmPackageVariable = getNpmPackageVariable;
8
8
  exports.hasNpmVariables = hasNpmVariables;
9
9
  exports.interpolateNpmVariables = interpolateNpmVariables;
10
+ exports.parseSize = parseSize;
10
11
  exports.formatConfiguration = formatConfiguration;
11
12
  const node_fs_1 = require("node:fs");
12
13
  const node_os_1 = require("node:os");
@@ -34,7 +35,9 @@ Options:
34
35
  --cachetime <seconds> max-age cache time in seconds (default: 0)
35
36
  --exclude <pattern> Exclude files matching glob pattern (repeatable or comma-separated)
36
37
  Examples: --exclude="*.map" --exclude="test/**/*.ts"
37
- --base-path <path> URL prefix for all routes (e.g., "/ui") (default: "")
38
+ --basepath <path> URL prefix for all routes (e.g., "/ui") (default: "")
39
+ --maxsize <size> Maximum total uncompressed size (e.g., 400k, 1.5m, 409600)
40
+ --maxgzipsize <size> Maximum total gzip size (e.g., 150k, 1m, 153600)
38
41
  -h, --help Shows this help
39
42
 
40
43
  RC File:
@@ -78,6 +81,25 @@ function validateBasePath(value) {
78
81
  throw new Error(`basePath must not contain //: ${value}`);
79
82
  return value;
80
83
  }
84
+ function parseSize(value, name) {
85
+ const trimmed = value.trim();
86
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)\s*([KMkm])?$/);
87
+ if (!match || !match[1])
88
+ throw new Error(`${name} must be a positive number with optional k/K (×1024) or m/M (×1024²) suffix: ${value}`);
89
+ const numericPart = Number.parseFloat(match[1]);
90
+ const suffix = match[2]?.toLowerCase();
91
+ let bytes;
92
+ if (suffix === 'k')
93
+ bytes = numericPart * 1024;
94
+ else if (suffix === 'm')
95
+ bytes = numericPart * 1024 * 1024;
96
+ else
97
+ bytes = numericPart;
98
+ bytes = Math.round(bytes);
99
+ if (bytes <= 0 || !Number.isFinite(bytes))
100
+ throw new Error(`${name} must be a positive integer: ${value}`);
101
+ return bytes;
102
+ }
81
103
  const DEFAULT_EXCLUDE_PATTERNS = [
82
104
  '.DS_Store',
83
105
  'Thumbs.db',
@@ -229,7 +251,9 @@ function validateRcConfig(config, rcPath) {
229
251
  'created',
230
252
  'version',
231
253
  'exclude',
232
- 'basePath'
254
+ 'basePath',
255
+ 'maxSize',
256
+ 'maxGzipSize'
233
257
  ]);
234
258
  for (const key of Object.keys(configObject))
235
259
  if (!validKeys.has(key))
@@ -250,6 +274,30 @@ function validateRcConfig(config, rcPath) {
250
274
  if (typeof pattern !== 'string')
251
275
  throw new TypeError('All exclude patterns must be strings');
252
276
  }
277
+ if (configObject['maxSize'] !== undefined) {
278
+ const value = configObject['maxSize'];
279
+ if (typeof value === 'string')
280
+ try {
281
+ configObject['maxSize'] = parseSize(value, 'maxSize');
282
+ }
283
+ catch {
284
+ throw new TypeError(`Invalid maxSize in RC file: ${value} (must be a positive number with optional k/m suffix)`);
285
+ }
286
+ else if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
287
+ throw new TypeError(`Invalid maxSize in RC file: ${value} (must be a positive number)`);
288
+ }
289
+ if (configObject['maxGzipSize'] !== undefined) {
290
+ const value = configObject['maxGzipSize'];
291
+ if (typeof value === 'string')
292
+ try {
293
+ configObject['maxGzipSize'] = parseSize(value, 'maxGzipSize');
294
+ }
295
+ catch {
296
+ throw new TypeError(`Invalid maxGzipSize in RC file: ${value} (must be a positive number with optional k/m suffix)`);
297
+ }
298
+ else if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
299
+ throw new TypeError(`Invalid maxGzipSize in RC file: ${value} (must be a positive number)`);
300
+ }
253
301
  return configObject;
254
302
  }
255
303
  function parseArguments() {
@@ -307,6 +355,10 @@ function parseArguments() {
307
355
  result.define = rcConfig.define;
308
356
  if (rcConfig.basePath !== undefined)
309
357
  result.basePath = validateBasePath(rcConfig.basePath);
358
+ if (rcConfig.maxSize !== undefined)
359
+ result.maxSize = rcConfig.maxSize;
360
+ if (rcConfig.maxGzipSize !== undefined)
361
+ result.maxGzipSize = rcConfig.maxGzipSize;
310
362
  if (rcConfig.exclude && rcConfig.exclude.length > 0)
311
363
  result.exclude = [...rcConfig.exclude];
312
364
  const cliExclude = [];
@@ -363,9 +415,15 @@ function parseArguments() {
363
415
  cliExclude.push(...patterns);
364
416
  break;
365
417
  }
366
- case 'base-path':
418
+ case 'basepath':
367
419
  result.basePath = validateBasePath(value);
368
420
  break;
421
+ case 'maxsize':
422
+ result.maxSize = parseSize(value, '--maxsize');
423
+ break;
424
+ case 'maxgzipsize':
425
+ result.maxGzipSize = parseSize(value, '--maxgzipsize');
426
+ break;
369
427
  default:
370
428
  throw new Error(`Unknown flag: ${flag}`);
371
429
  }
@@ -375,7 +433,7 @@ function parseArguments() {
375
433
  result.created = true;
376
434
  continue;
377
435
  }
378
- if (argument === '--no-index-check') {
436
+ if (argument === '--noindexcheck') {
379
437
  result.noIndexCheck = true;
380
438
  continue;
381
439
  }
@@ -458,10 +516,18 @@ function parseArguments() {
458
516
  index++;
459
517
  break;
460
518
  }
461
- case 'base-path':
519
+ case 'basepath':
462
520
  result.basePath = validateBasePath(nextArgument);
463
521
  index++;
464
522
  break;
523
+ case 'maxsize':
524
+ result.maxSize = parseSize(nextArgument, '--maxsize');
525
+ index++;
526
+ break;
527
+ case 'maxgzipsize':
528
+ result.maxGzipSize = parseSize(nextArgument, '--maxgzipsize');
529
+ index++;
530
+ break;
465
531
  default:
466
532
  throw new Error(`Unknown flag: --${flag}`);
467
533
  }
@@ -496,6 +562,10 @@ function formatConfiguration(cmdLine) {
496
562
  parts.push(`define=${cmdLine.define}`);
497
563
  if (cmdLine.basePath)
498
564
  parts.push(`basePath=${cmdLine.basePath}`);
565
+ if (cmdLine.maxSize !== undefined)
566
+ parts.push(`maxSize=${cmdLine.maxSize}`);
567
+ if (cmdLine.maxGzipSize !== undefined)
568
+ parts.push(`maxGzipSize=${cmdLine.maxGzipSize}`);
499
569
  if (cmdLine.exclude.length > 0)
500
570
  parts.push(`exclude=[${cmdLine.exclude.join(', ')}]`);
501
571
  return parts.join(' ');
@@ -1,4 +1,5 @@
1
1
  export declare function getMissingIndexError(engine: string): string;
2
2
  export declare function getInvalidEngineError(attempted: string): string;
3
3
  export declare function getSourcepathNotFoundError(sourcepath: string, reason: 'not_found' | 'not_directory'): string;
4
+ export declare function getSizeBudgetExceededError(type: 'size' | 'gzipSize', limit: number, actual: number): string;
4
5
  export declare function getMaxUriHandlersHint(engine: string, routeCount: number): string;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getMissingIndexError = getMissingIndexError;
7
7
  exports.getInvalidEngineError = getInvalidEngineError;
8
8
  exports.getSourcepathNotFoundError = getSourcepathNotFoundError;
9
+ exports.getSizeBudgetExceededError = getSizeBudgetExceededError;
9
10
  exports.getMaxUriHandlersHint = getMaxUriHandlersHint;
10
11
  const node_path_1 = __importDefault(require("node:path"));
11
12
  const consoleColor_1 = require("./consoleColor");
@@ -45,7 +46,7 @@ How to fix (for ${getEngineName(engine)}):
45
46
  ${hint}
46
47
 
47
48
  Alternative:
48
- If you use a different entry point (e.g., main.html), you can add --no-index-check flag,
49
+ If you use a different entry point (e.g., main.html), you can add --noindexcheck flag,
49
50
  but users must navigate to http://your-esp32/main.html explicitly.`);
50
51
  }
51
52
  function getInvalidEngineError(attempted) {
@@ -109,6 +110,32 @@ How to fix:
109
110
  Current directory: ${currentDirectory}
110
111
  Attempted path: ${resolvedPath} (resolved)`);
111
112
  }
113
+ function getSizeBudgetExceededError(type, limit, actual) {
114
+ const typeLabel = type === 'size' ? 'Uncompressed' : 'Gzip';
115
+ const flagName = type === 'size' ? '--maxsize' : '--maxgzipsize';
116
+ const overage = actual - limit;
117
+ const overagePercent = Math.round((overage / limit) * 100);
118
+ return ((0, consoleColor_1.redLog)(`[ERROR] ${typeLabel} size budget exceeded`) +
119
+ `
120
+
121
+ Budget: ${limit.toLocaleString()} bytes
122
+ Actual: ${actual.toLocaleString()} bytes
123
+ Overage: ${overage.toLocaleString()} bytes (+${overagePercent}%)
124
+
125
+ Why this matters:
126
+ Size budgets help prevent frontend bloat and ensure your application
127
+ fits within ESP32 flash memory constraints.
128
+
129
+ How to fix:
130
+ 1. Reduce bundle size (tree-shaking, code splitting, smaller dependencies)
131
+ 2. Optimize assets (compress images, minify CSS/JS)
132
+ 3. Remove unused files with --exclude patterns
133
+ 4. Increase the budget: ${flagName}=${actual}
134
+
135
+ CI integration:
136
+ This non-zero exit code allows build pipelines to fail early when
137
+ the frontend exceeds allocated flash space.`);
138
+ }
112
139
  function getMaxUriHandlersHint(engine, routeCount) {
113
140
  const recommended = routeCount + 5;
114
141
  const hints = {
package/dist/index.js CHANGED
@@ -84,6 +84,14 @@ for (const [originalFilename, content] of files) {
84
84
  }
85
85
  console.log('');
86
86
  filesByExtension.sort((left, right) => left.extension.localeCompare(right.extension));
87
+ if (commandLine_1.cmdLine.maxSize !== undefined && summary.size > commandLine_1.cmdLine.maxSize) {
88
+ console.error((0, errorMessages_1.getSizeBudgetExceededError)('size', commandLine_1.cmdLine.maxSize, summary.size));
89
+ process.exit(1);
90
+ }
91
+ if (commandLine_1.cmdLine.maxGzipSize !== undefined && summary.gzipsize > commandLine_1.cmdLine.maxGzipSize) {
92
+ console.error((0, errorMessages_1.getSizeBudgetExceededError)('gzipSize', commandLine_1.cmdLine.maxGzipSize, summary.gzipsize));
93
+ process.exit(1);
94
+ }
87
95
  const cppFile = (0, cppCode_1.getCppCode)(sources, filesByExtension);
88
96
  (0, node_fs_1.mkdirSync)(node_path_1.default.normalize(node_path_1.default.dirname(commandLine_1.cmdLine.outputfile)), { recursive: true });
89
97
  (0, node_fs_1.writeFileSync)(commandLine_1.cmdLine.outputfile, cppFile, { flush: true, encoding: 'utf8' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "1.15.0",
3
+ "version": "1.16.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",