svelteesp32 1.10.0 → 1.12.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,10 @@ 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
+ > Starting with version v1.12.0, you can use RC file for configuration
16
+
17
+ > Starting with version v1.11.0, you can exclude files by pattern
18
+
15
19
  > Starting with version v1.10.0, we reduced npm dependencies
16
20
 
17
21
  > Starting with version v1.9.0, code generator for esp-idf is available
@@ -37,15 +41,60 @@ This npm package provides a solution for **inserting any JS client application i
37
41
  - Node.js >= 20
38
42
  - npm >= 9
39
43
 
44
+ ### Development
45
+
46
+ #### Testing
47
+
48
+ The project includes comprehensive unit tests using Vitest:
49
+
50
+ ```bash
51
+ # Run tests once
52
+ npm run test
53
+
54
+ # Run tests in watch mode (for development)
55
+ npm run test:watch
56
+
57
+ # Generate coverage report
58
+ npm run test:coverage
59
+ ```
60
+
61
+ **Test Coverage:** ~68% overall with focus on core functionality:
62
+
63
+ - `commandLine.ts`: 84.56% - CLI argument parsing and validation
64
+ - `file.ts`: 100% - File operations and duplicate detection
65
+ - `cppCode.ts`: 96.62% - C++ code generation and templates
66
+ - `consoleColor.ts`: 100% - Console output utilities
67
+
68
+ Coverage reports are generated in the `coverage/` directory and can be viewed by opening `coverage/index.html` in a browser.
69
+
70
+ #### Code Quality
71
+
72
+ ```bash
73
+ # Check formatting
74
+ npm run format:check
75
+
76
+ # Fix formatting
77
+ npm run format:fix
78
+
79
+ # Check linting
80
+ npm run lint:check
81
+
82
+ # Fix linting issues
83
+ npm run lint:fix
84
+
85
+ # Fix all formatting and linting issues
86
+ npm run fix
87
+ ```
88
+
40
89
  ### Usage
41
90
 
42
- **Install package** as devDependency (it is practical if the package is part of the project so that you always receive updates)
91
+ **Install package** as dev dependency (it is practical if the package is part of the project so that you always receive updates)
43
92
 
44
93
  ```bash
45
94
  npm install -D svelteesp32
46
95
  ```
47
96
 
48
- After a successful Svelte build (rollup/webpack/vite) **create an includeable c++ header** file
97
+ After a successful Svelte build (rollup/webpack/vite) **create an includable c++ header** file
49
98
 
50
99
  ```bash
51
100
  // for PsychicHttpServer
@@ -258,6 +307,71 @@ At the same time, it can be an advantage that the content is cached by the brows
258
307
 
259
308
  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.
260
309
 
310
+ ### File Exclusion
311
+
312
+ 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.
313
+
314
+ #### Basic Usage
315
+
316
+ ```bash
317
+ # Exclude source maps
318
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --exclude="*.map"
319
+
320
+ # Exclude documentation files
321
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --exclude="*.md"
322
+
323
+ # Exclude multiple file types (comma-separated)
324
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --exclude="*.map,*.md,*.txt"
325
+
326
+ # Exclude using multiple flags
327
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --exclude="*.map" --exclude="*.md"
328
+
329
+ # Exclude entire directories
330
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --exclude="test/**/*"
331
+
332
+ # Combine multiple approaches
333
+ npx svelteesp32 -e psychic -s ./dist -o ./output.h --exclude="*.map,*.md" --exclude="docs/**/*"
334
+ ```
335
+
336
+ #### Pattern Syntax
337
+
338
+ The exclude patterns use standard glob syntax:
339
+
340
+ - `*.map` - Match all files ending with `.map`
341
+ - `**/*.test.js` - Match all `.test.js` files in any directory
342
+ - `test/**/*` - Match all files in the `test` directory and subdirectories
343
+ - `.DS_Store` - Match specific filename
344
+
345
+ #### Default Exclusions
346
+
347
+ By default, the following system and development files are automatically excluded:
348
+
349
+ - `.DS_Store` (macOS system file)
350
+ - `Thumbs.db` (Windows thumbnail cache)
351
+ - `.git` (Git directory)
352
+ - `.svn` (SVN directory)
353
+ - `*.swp` (Vim swap files)
354
+ - `*~` (Backup files)
355
+ - `.gitignore` (Git ignore file)
356
+ - `.gitattributes` (Git attributes file)
357
+
358
+ Custom exclude patterns are added to these defaults.
359
+
360
+ #### Exclusion Output
361
+
362
+ When files are excluded, you'll see a summary in the build output:
363
+
364
+ ```
365
+ Excluded 5 file(s):
366
+ - assets/index.js.map
367
+ - assets/vendor.js.map
368
+ - README.md
369
+ - docs/guide.md
370
+ - test/unit.test.js
371
+ ```
372
+
373
+ This helps you verify that the correct files are being excluded from your build.
374
+
261
375
  ### C++ defines
262
376
 
263
377
  To make it easy to integrate into a larger c++ project, we have made a couple of variables available as c++ defines.
@@ -307,6 +421,7 @@ You can use the following c++ directives at the project level if you want to con
307
421
  | `-s` | **Source dist folder contains compiled web files** | (required) |
308
422
  | `-e` | The engine for which the include file is created (psychic/psychic2/async/espidf) | psychic |
309
423
  | `-o` | Generated output file with path | `svelteesp32.h` |
424
+ | `--exclude` | Exclude files matching glob pattern (repeatable or comma-separated) | Default system files |
310
425
  | `--etag` | Use ETag header for cache (true/false/compiler) | false |
311
426
  | `--cachetime` | Override no-cache response with a max-age=\<cachetime\> response (seconds) | 0 |
312
427
  | `--gzip` | Compress content with gzip (true/false/compiler) | true |
@@ -314,8 +429,117 @@ You can use the following c++ directives at the project level if you want to con
314
429
  | `--version` | Include a version string in generated header, e.g. `--version=v$npm_package_version` | '' |
315
430
  | `--espmethod` | Name of generated initialization method | `initSvelteStaticFiles` |
316
431
  | `--define` | Prefix of c++ defines (e.g., SVELTEESP32_COUNT) | `SVELTEESP32` |
432
+ | `--config` | Use custom RC file path | `.svelteesp32rc.json` |
317
433
  | `-h` | Show help | |
318
434
 
435
+ ### Configuration File
436
+
437
+ You can store frequently-used options in a configuration file to avoid repeating command line arguments. This is especially useful for CI/CD pipelines and team collaboration.
438
+
439
+ #### Quick Start
440
+
441
+ Create `.svelteesp32rc.json` in your project directory:
442
+
443
+ ```json
444
+ {
445
+ "engine": "psychic",
446
+ "sourcepath": "./dist",
447
+ "outputfile": "./esp32/include/svelteesp32.h",
448
+ "etag": "true",
449
+ "gzip": "true",
450
+ "cachetime": 86400,
451
+ "exclude": ["*.map", "*.md"]
452
+ }
453
+ ```
454
+
455
+ Then simply run:
456
+
457
+ ```bash
458
+ npx svelteesp32
459
+ ```
460
+
461
+ No command line arguments needed!
462
+
463
+ #### Search Locations
464
+
465
+ The tool automatically searches for `.svelteesp32rc.json` in:
466
+
467
+ 1. Current working directory
468
+ 2. User home directory
469
+
470
+ Or specify a custom location:
471
+
472
+ ```bash
473
+ npx svelteesp32 --config=.svelteesp32rc.prod.json
474
+ ```
475
+
476
+ #### Configuration Reference
477
+
478
+ All CLI options can be specified in the RC file using long-form property names:
479
+
480
+ | RC Property | CLI Flag | Type | Example |
481
+ | ------------ | ------------- | ------- | ------------------------------------------------ |
482
+ | `engine` | `-e` | string | `"psychic"`, `"psychic2"`, `"async"`, `"espidf"` |
483
+ | `sourcepath` | `-s` | string | `"./dist"` |
484
+ | `outputfile` | `-o` | string | `"./output.h"` |
485
+ | `etag` | `--etag` | string | `"true"`, `"false"`, `"compiler"` |
486
+ | `gzip` | `--gzip` | string | `"true"`, `"false"`, `"compiler"` |
487
+ | `cachetime` | `--cachetime` | number | `86400` |
488
+ | `created` | `--created` | boolean | `true`, `false` |
489
+ | `version` | `--version` | string | `"v1.0.0"` |
490
+ | `espmethod` | `--espmethod` | string | `"initSvelteStaticFiles"` |
491
+ | `define` | `--define` | string | `"SVELTEESP32"` |
492
+ | `exclude` | `--exclude` | array | `["*.map", "*.md"]` |
493
+
494
+ #### CLI Override
495
+
496
+ Command line arguments always take precedence over RC file values:
497
+
498
+ ```bash
499
+ # Use RC settings but override etag
500
+ npx svelteesp32 --etag=false
501
+
502
+ # Use RC settings but add different exclude pattern
503
+ npx svelteesp32 --exclude="*.txt"
504
+ ```
505
+
506
+ #### Multiple Environments
507
+
508
+ Create different config files for different environments:
509
+
510
+ ```bash
511
+ # Development build
512
+ npx svelteesp32 --config=.svelteesp32rc.dev.json
513
+
514
+ # Production build
515
+ npx svelteesp32 --config=.svelteesp32rc.prod.json
516
+ ```
517
+
518
+ #### Exclude Pattern Behavior
519
+
520
+ **Replace mode**: When you specify `exclude` patterns in RC file or CLI, they completely replace the defaults.
521
+
522
+ - **No exclude specified**: Uses default system exclusions (`.DS_Store`, `Thumbs.db`, `.git`, etc.)
523
+ - **RC file has exclude**: Replaces defaults with RC patterns
524
+ - **CLI has --exclude**: Replaces RC patterns (or defaults if no RC)
525
+
526
+ Example:
527
+
528
+ ```json
529
+ // .svelteesp32rc.json
530
+ { "exclude": ["*.map"] }
531
+ ```
532
+
533
+ Result: Only `*.map` is excluded (default patterns are replaced)
534
+
535
+ To keep defaults, explicitly list them in your RC file:
536
+
537
+ ```json
538
+ {
539
+ "exclude": [".DS_Store", "Thumbs.db", ".git", "*.map", "*.md"]
540
+ }
541
+ ```
542
+
319
543
  ### Q&A
320
544
 
321
545
  - **How big a frontend application can be placed?** If you compress the content with gzip, even a 3-4Mb assets directory can be placed. This is a serious enough amount to serve a complete application.
@@ -9,6 +9,7 @@ interface ICopyFilesArguments {
9
9
  cachetime: number;
10
10
  created: boolean;
11
11
  version: string;
12
+ exclude: string[];
12
13
  help?: boolean;
13
14
  }
14
15
  export declare const cmdLine: ICopyFilesArguments;
@@ -1,11 +1,20 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.cmdLine = void 0;
4
7
  const node_fs_1 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const consoleColor_1 = require("./consoleColor");
5
11
  function showHelp() {
6
12
  console.log(`
7
13
  svelteesp32 - Svelte JS to ESP32 converter
8
14
 
15
+ Configuration:
16
+ --config <path> Use custom RC file (default: search for .svelteesp32rc.json)
17
+
9
18
  Options:
10
19
  -e, --engine <value> The engine for which the include file is created
11
20
  (psychic|psychic2|async|espidf) (default: "psychic")
@@ -18,7 +27,26 @@ Options:
18
27
  --espmethod <name> Name of generated method (default: "initSvelteStaticFiles")
19
28
  --define <prefix> Prefix of c++ defines (default: "SVELTEESP32")
20
29
  --cachetime <seconds> max-age cache time in seconds (default: 0)
30
+ --exclude <pattern> Exclude files matching glob pattern (repeatable or comma-separated)
31
+ Examples: --exclude="*.map" --exclude="test/**/*.ts"
21
32
  -h, --help Shows this help
33
+
34
+ RC File:
35
+ The tool searches for .svelteesp32rc.json in:
36
+ 1. Current directory (./.svelteesp32rc.json)
37
+ 2. User home directory (~/.svelteesp32rc.json)
38
+
39
+ Example RC file (all fields optional):
40
+ {
41
+ "engine": "psychic",
42
+ "sourcepath": "./dist",
43
+ "outputfile": "./output.h",
44
+ "etag": "true",
45
+ "gzip": "true",
46
+ "exclude": ["*.map", "*.md"]
47
+ }
48
+
49
+ CLI arguments override RC file values.
22
50
  `);
23
51
  process.exit(0);
24
52
  }
@@ -32,8 +60,105 @@ function validateTriState(value, name) {
32
60
  return value;
33
61
  throw new Error(`Invalid ${name}: ${value}`);
34
62
  }
63
+ const DEFAULT_EXCLUDE_PATTERNS = [
64
+ '.DS_Store',
65
+ 'Thumbs.db',
66
+ '.git',
67
+ '.svn',
68
+ '*.swp',
69
+ '*~',
70
+ '.gitignore',
71
+ '.gitattributes'
72
+ ];
73
+ function findRcFile(customConfigPath) {
74
+ if (customConfigPath) {
75
+ if ((0, node_fs_1.existsSync)(customConfigPath))
76
+ return customConfigPath;
77
+ throw new Error(`Config file not found: ${customConfigPath}`);
78
+ }
79
+ for (const filename of ['.svelteesp32rc.json', '.svelteesp32rc']) {
80
+ const cwdPath = node_path_1.default.join(process.cwd(), filename);
81
+ if ((0, node_fs_1.existsSync)(cwdPath))
82
+ return cwdPath;
83
+ }
84
+ const homeDirectory = (0, node_os_1.homedir)();
85
+ for (const filename of ['.svelteesp32rc.json', '.svelteesp32rc']) {
86
+ const homePath = node_path_1.default.join(homeDirectory, filename);
87
+ if ((0, node_fs_1.existsSync)(homePath))
88
+ return homePath;
89
+ }
90
+ return undefined;
91
+ }
92
+ function loadRcFile(rcPath) {
93
+ try {
94
+ const content = (0, node_fs_1.readFileSync)(rcPath, 'utf8');
95
+ const config = JSON.parse(content);
96
+ return validateRcConfig(config, rcPath);
97
+ }
98
+ catch (error) {
99
+ if (error instanceof SyntaxError)
100
+ throw new Error(`Invalid JSON in RC file ${rcPath}: ${error.message}`);
101
+ throw error;
102
+ }
103
+ }
104
+ function validateRcConfig(config, rcPath) {
105
+ if (typeof config !== 'object' || config === null)
106
+ throw new Error(`RC file ${rcPath} must contain a JSON object`);
107
+ const configObject = config;
108
+ const validKeys = new Set([
109
+ 'engine',
110
+ 'sourcepath',
111
+ 'outputfile',
112
+ 'espmethod',
113
+ 'define',
114
+ 'gzip',
115
+ 'etag',
116
+ 'cachetime',
117
+ 'created',
118
+ 'version',
119
+ 'exclude'
120
+ ]);
121
+ for (const key of Object.keys(configObject))
122
+ if (!validKeys.has(key))
123
+ console.warn((0, consoleColor_1.yellowLog)(`Warning: Unknown property '${key}' in RC file ${rcPath}`));
124
+ if (configObject['engine'] !== undefined)
125
+ configObject['engine'] = validateEngine(configObject['engine']);
126
+ if (configObject['etag'] !== undefined)
127
+ configObject['etag'] = validateTriState(configObject['etag'], 'etag');
128
+ if (configObject['gzip'] !== undefined)
129
+ configObject['gzip'] = validateTriState(configObject['gzip'], 'gzip');
130
+ if (configObject['cachetime'] !== undefined &&
131
+ (typeof configObject['cachetime'] !== 'number' || Number.isNaN(configObject['cachetime'])))
132
+ throw new TypeError(`Invalid cachetime in RC file: ${configObject['cachetime']}`);
133
+ if (configObject['exclude'] !== undefined) {
134
+ if (!Array.isArray(configObject['exclude']))
135
+ throw new TypeError("'exclude' in RC file must be an array");
136
+ for (const pattern of configObject['exclude'])
137
+ if (typeof pattern !== 'string')
138
+ throw new TypeError('All exclude patterns must be strings');
139
+ }
140
+ return configObject;
141
+ }
35
142
  function parseArguments() {
36
143
  const arguments_ = process.argv.slice(2);
144
+ let customConfigPath;
145
+ for (let index = 0; index < arguments_.length; index++) {
146
+ const argument = arguments_[index];
147
+ if (!argument)
148
+ continue;
149
+ if (argument === '--config' && arguments_[index + 1]) {
150
+ customConfigPath = arguments_[index + 1];
151
+ break;
152
+ }
153
+ if (argument.startsWith('--config=')) {
154
+ customConfigPath = argument.slice('--config='.length);
155
+ break;
156
+ }
157
+ }
158
+ const rcPath = findRcFile(customConfigPath);
159
+ const rcConfig = rcPath ? loadRcFile(rcPath) : {};
160
+ if (rcPath)
161
+ console.log((0, consoleColor_1.cyanLog)(`[SvelteESP32] Using config from: ${rcPath}`));
37
162
  const result = {
38
163
  engine: 'psychic',
39
164
  outputfile: 'svelteesp32.h',
@@ -43,8 +168,32 @@ function parseArguments() {
43
168
  version: '',
44
169
  espmethod: 'initSvelteStaticFiles',
45
170
  define: 'SVELTEESP32',
46
- cachetime: 0
171
+ cachetime: 0,
172
+ exclude: [...DEFAULT_EXCLUDE_PATTERNS]
47
173
  };
174
+ if (rcConfig.engine)
175
+ result.engine = rcConfig.engine;
176
+ if (rcConfig.sourcepath)
177
+ result.sourcepath = rcConfig.sourcepath;
178
+ if (rcConfig.outputfile)
179
+ result.outputfile = rcConfig.outputfile;
180
+ if (rcConfig.etag)
181
+ result.etag = rcConfig.etag;
182
+ if (rcConfig.gzip)
183
+ result.gzip = rcConfig.gzip;
184
+ if (rcConfig.cachetime !== undefined)
185
+ result.cachetime = rcConfig.cachetime;
186
+ if (rcConfig.created !== undefined)
187
+ result.created = rcConfig.created;
188
+ if (rcConfig.version)
189
+ result.version = rcConfig.version;
190
+ if (rcConfig.espmethod)
191
+ result.espmethod = rcConfig.espmethod;
192
+ if (rcConfig.define)
193
+ result.define = rcConfig.define;
194
+ if (rcConfig.exclude && rcConfig.exclude.length > 0)
195
+ result.exclude = [...rcConfig.exclude];
196
+ const cliExclude = [];
48
197
  for (let index = 0; index < arguments_.length; index++) {
49
198
  const argument = arguments_[index];
50
199
  if (!argument)
@@ -59,6 +208,8 @@ function parseArguments() {
59
208
  throw new Error(`Invalid argument format: ${argument}`);
60
209
  const flagName = flag.slice(2);
61
210
  switch (flagName) {
211
+ case 'config':
212
+ break;
62
213
  case 'engine':
63
214
  result.engine = validateEngine(value);
64
215
  break;
@@ -85,10 +236,17 @@ function parseArguments() {
85
236
  break;
86
237
  case 'cachetime':
87
238
  result.cachetime = Number.parseInt(value, 10);
88
- if (Number.isNaN(result.cachetime)) {
239
+ if (Number.isNaN(result.cachetime))
89
240
  throw new TypeError(`Invalid cachetime: ${value}`);
90
- }
91
241
  break;
242
+ case 'exclude': {
243
+ const patterns = value
244
+ .split(',')
245
+ .map((p) => p.trim())
246
+ .filter(Boolean);
247
+ cliExclude.push(...patterns);
248
+ break;
249
+ }
92
250
  default:
93
251
  throw new Error(`Unknown flag: ${flag}`);
94
252
  }
@@ -127,6 +285,9 @@ function parseArguments() {
127
285
  if (!nextArgument || nextArgument.startsWith('-'))
128
286
  throw new Error(`Missing value for flag: ${argument}`);
129
287
  switch (flag) {
288
+ case 'config':
289
+ index++;
290
+ break;
130
291
  case 'engine':
131
292
  result.engine = validateEngine(nextArgument);
132
293
  index++;
@@ -161,11 +322,19 @@ function parseArguments() {
161
322
  break;
162
323
  case 'cachetime':
163
324
  result.cachetime = Number.parseInt(nextArgument, 10);
164
- if (Number.isNaN(result.cachetime)) {
325
+ if (Number.isNaN(result.cachetime))
165
326
  throw new TypeError(`Invalid cachetime: ${nextArgument}`);
166
- }
167
327
  index++;
168
328
  break;
329
+ case 'exclude': {
330
+ const patterns = nextArgument
331
+ .split(',')
332
+ .map((p) => p.trim())
333
+ .filter(Boolean);
334
+ cliExclude.push(...patterns);
335
+ index++;
336
+ break;
337
+ }
169
338
  default:
170
339
  throw new Error(`Unknown flag: --${flag}`);
171
340
  }
@@ -173,8 +342,10 @@ function parseArguments() {
173
342
  }
174
343
  throw new Error(`Unknown argument: ${argument}`);
175
344
  }
345
+ if (cliExclude.length > 0)
346
+ result.exclude = [...cliExclude];
176
347
  if (!result.sourcepath) {
177
- console.error('Error: --sourcepath is required');
348
+ console.error('Error: --sourcepath is required (can be specified in RC file or CLI)');
178
349
  showHelp();
179
350
  }
180
351
  return result;
@@ -1,3 +1,4 @@
1
1
  export declare const greenLog: (s: string) => string;
2
2
  export declare const yellowLog: (s: string) => string;
3
3
  export declare const redLog: (s: string) => string;
4
+ export declare const cyanLog: (s: string) => string;
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.redLog = exports.yellowLog = exports.greenLog = void 0;
3
+ exports.cyanLog = exports.redLog = exports.yellowLog = exports.greenLog = void 0;
4
4
  const greenLog = (s) => `\u001B[32m${s}\u001B[0m`;
5
5
  exports.greenLog = greenLog;
6
6
  const yellowLog = (s) => `\u001B[33m${s}\u001B[0m`;
7
7
  exports.yellowLog = yellowLog;
8
8
  const redLog = (s) => `\u001B[31m${s}\u001B[0m`;
9
9
  exports.redLog = redLog;
10
+ const cyanLog = (s) => `\u001B[36m${s}\u001B[0m`;
11
+ exports.cyanLog = cyanLog;
package/dist/file.js CHANGED
@@ -7,6 +7,7 @@ exports.getFiles = void 0;
7
7
  const node_crypto_1 = require("node:crypto");
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
+ const picomatch_1 = __importDefault(require("picomatch"));
10
11
  const tinyglobby_1 = require("tinyglobby");
11
12
  const commandLine_1 = require("./commandLine");
12
13
  const consoleColor_1 = require("./consoleColor");
@@ -15,19 +16,15 @@ const findSimilarFiles = (files) => {
15
16
  for (const [filename, content] of files.entries()) {
16
17
  const hash = (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
17
18
  const existingFiles = contentComparer.get(hash);
18
- if (existingFiles) {
19
+ if (existingFiles)
19
20
  existingFiles.push(filename);
20
- }
21
- else {
21
+ else
22
22
  contentComparer.set(hash, [filename]);
23
- }
24
23
  }
25
24
  const result = [];
26
- for (const filenames of contentComparer.values()) {
27
- if (filenames.length > 1) {
25
+ for (const filenames of contentComparer.values())
26
+ if (filenames.length > 1)
28
27
  result.push(filenames);
29
- }
30
- }
31
28
  return result;
32
29
  };
33
30
  const shouldSkipFile = (filename, allFilenames) => {
@@ -42,18 +39,46 @@ const shouldSkipFile = (filename, allFilenames) => {
42
39
  }
43
40
  return false;
44
41
  };
42
+ const isExcluded = (filename, excludePatterns) => {
43
+ if (excludePatterns.length === 0)
44
+ return false;
45
+ const normalizedFilename = filename.replace(/\\/g, '/');
46
+ const isMatch = (0, picomatch_1.default)(excludePatterns, {
47
+ dot: true,
48
+ noglobstar: false,
49
+ matchBase: false
50
+ });
51
+ return isMatch(normalizedFilename);
52
+ };
45
53
  const getFiles = () => {
46
54
  const allFilenames = (0, tinyglobby_1.globSync)('**/*', { cwd: commandLine_1.cmdLine.sourcepath, onlyFiles: true, dot: false });
47
- const filenames = allFilenames.filter((filename) => !shouldSkipFile(filename, allFilenames));
55
+ const withoutCompressed = allFilenames.filter((filename) => !shouldSkipFile(filename, allFilenames));
56
+ const excludePatterns = commandLine_1.cmdLine.exclude || [];
57
+ const excludedFiles = [];
58
+ const filenames = withoutCompressed.filter((filename) => {
59
+ if (isExcluded(filename, excludePatterns)) {
60
+ excludedFiles.push(filename);
61
+ return false;
62
+ }
63
+ return true;
64
+ });
65
+ if (excludedFiles.length > 0) {
66
+ console.log(`\nExcluded ${excludedFiles.length} file(s):`);
67
+ const displayLimit = 10;
68
+ for (const file of excludedFiles.slice(0, displayLimit))
69
+ console.log((0, consoleColor_1.cyanLog)(`- ${file}`));
70
+ if (excludedFiles.length > displayLimit)
71
+ console.log((0, consoleColor_1.cyanLog)(`... and ${excludedFiles.length - displayLimit} more`));
72
+ console.log();
73
+ }
48
74
  const result = new Map();
49
75
  for (const filename of filenames) {
50
76
  const filePath = node_path_1.default.join(commandLine_1.cmdLine.sourcepath, filename);
51
77
  result.set(filename, (0, node_fs_1.readFileSync)(filePath, { flag: 'r' }));
52
78
  }
53
79
  const duplicates = findSimilarFiles(result);
54
- for (const sameFiles of duplicates) {
80
+ for (const sameFiles of duplicates)
55
81
  console.log((0, consoleColor_1.yellowLog)(` ${sameFiles.join(', ')} files look like identical`));
56
- }
57
82
  return result;
58
83
  };
59
84
  exports.getFiles = getFiles;
package/dist/index.js CHANGED
@@ -26,9 +26,8 @@ const calculateCompressionRatio = (originalSize, compressedSize) => Math.round((
26
26
  const formatCompressionLog = (filename, padding, originalSize, compressedSize, useGzip) => {
27
27
  const ratio = calculateCompressionRatio(originalSize, compressedSize);
28
28
  const sizeInfo = `(${originalSize} -> ${compressedSize} = ${ratio}%)`;
29
- if (useGzip) {
29
+ if (useGzip)
30
30
  return (0, consoleColor_1.greenLog)(` [${filename}] ${padding} ✓ gzip used ${sizeInfo}`);
31
- }
32
31
  const tooSmall = originalSize <= GZIP_MIN_SIZE ? '(too small) ' : '';
33
32
  return (0, consoleColor_1.yellowLog)(` [${filename}] ${padding} x gzip unused ${tooSmall}${sizeInfo}`);
34
33
  };
@@ -44,12 +43,10 @@ const createSourceEntry = (filename, dataname, content, contentGzip, mimeType, m
44
43
  });
45
44
  const updateExtensionGroup = (extension) => {
46
45
  const group = filesByExtension.find((fe) => fe.extension === extension);
47
- if (group) {
46
+ if (group)
48
47
  group.count += 1;
49
- }
50
- else {
48
+ else
51
49
  filesByExtension.push({ extension, count: 1 });
52
- }
53
50
  };
54
51
  console.log('Collecting source files');
55
52
  const files = (0, file_1.getFiles)();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteesp32",
3
- "version": "1.10.0",
3
+ "version": "1.12.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",
@@ -31,6 +31,9 @@
31
31
  "dev:async": "nodemon src/index.ts -- -e async -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=true --gzip=true --cachetime=86400 --version=v$npm_package_version",
32
32
  "dev:psychic": "nodemon src/index.ts -- -e psychic -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=false --gzip=false --version=v$npm_package_version",
33
33
  "dev:psychic2": "nodemon src/index.ts -- -e psychic2 -s ./demo/svelte/dist -o ./demo/esp32/include/svelteesp32.h --etag=false --gzip=false --version=v$npm_package_version",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "test:coverage": "vitest run --coverage",
34
37
  "test:esp32": "./package.script && ~/.platformio/penv/bin/pio run -d ./demo/esp32",
35
38
  "test:esp32idf": "./package.script && ~/.platformio/penv/bin/pio run -d ./demo/esp32idf",
36
39
  "test:all": "node --run test:esp32 && node --run test:esp32idf",
@@ -41,7 +44,7 @@
41
44
  "lint:check": "eslint .",
42
45
  "lint:fix": "eslint --fix .",
43
46
  "fix": "node --run format:fix && node --run lint:fix && node --run format:fix",
44
- "all": "node --run fix && node --run build",
47
+ "all": "node --run fix && node --run build && node --run test",
45
48
  "npm:reinstall": "rm -rf ./node_modules && rm -f ./package-lock.json && npm i && npm i"
46
49
  },
47
50
  "keywords": [
@@ -60,21 +63,25 @@
60
63
  "@types/mime-types": "^3.0.1",
61
64
  "@types/node": "^24.10.1",
62
65
  "@types/picomatch": "^4.0.2",
63
- "@typescript-eslint/eslint-plugin": "^8.47.0",
64
- "@typescript-eslint/parser": "^8.47.0",
66
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
67
+ "@typescript-eslint/parser": "^8.48.1",
68
+ "@vitest/coverage-v8": "^4.0.15",
65
69
  "eslint": "^9.39.1",
66
70
  "eslint-config-prettier": "^10.1.8",
67
71
  "eslint-plugin-simple-import-sort": "^12.1.1",
68
72
  "eslint-plugin-unicorn": "^62.0.0",
73
+ "memfs": "^4.51.1",
69
74
  "nodemon": "^3.1.11",
70
- "prettier": "^3.6.2",
75
+ "prettier": "^3.7.4",
71
76
  "ts-node": "^10.9.2",
72
- "tsx": "^4.20.6",
73
- "typescript": "^5.9.3"
77
+ "tsx": "^4.21.0",
78
+ "typescript": "^5.9.3",
79
+ "vitest": "^4.0.15"
74
80
  },
75
81
  "dependencies": {
76
82
  "handlebars": "^4.7.8",
77
83
  "mime-types": "^3.0.2",
84
+ "picomatch": "^4.0.3",
78
85
  "tinyglobby": "^0.2.15"
79
86
  }
80
87
  }