svelteesp32 1.12.1 → 1.13.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 +144 -28
- package/dist/commandLine.d.ts +18 -1
- package/dist/commandLine.js +106 -4
- package/dist/errorMessages.d.ts +4 -0
- package/dist/errorMessages.js +146 -0
- package/dist/file.js +6 -0
- package/dist/index.js +3 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -12,6 +12,25 @@ In order to be able to easily update OTA, it is important - from the users' poin
|
|
|
12
12
|
|
|
13
13
|
This npm package provides a solution for **inserting any JS client application into the ESP web server** (PsychicHttp and also ESPAsyncWebServer (https://github.com/ESP32Async/ESPAsyncWebServer) and ESP-IDF available, PsychicHttp is the default). For this, JS, html, css, font, assets, etc. files must be converted to binary byte array. Npm mode is easy to use and easy to **integrate into your CI/CD pipeline**.
|
|
14
14
|
|
|
15
|
+
**Quick Comparison:**
|
|
16
|
+
|
|
17
|
+
| Feature | SvelteESP32 | Traditional Filesystem (SPIFFS/LittleFS) |
|
|
18
|
+
| --------------------- | --------------------------------------- | ---------------------------------------- |
|
|
19
|
+
| **Single Binary OTA** | ✓ Everything embedded in firmware | ✗ Requires separate partition upload |
|
|
20
|
+
| **Gzip Compression** | ✓ Automatic build-time (>15% reduction) | Manual or runtime compression |
|
|
21
|
+
| **ETag Support** | ✓ Built-in MD5 ETags with 304 responses | Manual implementation required |
|
|
22
|
+
| **CI/CD Integration** | ✓ npm package, simple build step | Complex with upload_fs tools |
|
|
23
|
+
| **Memory Efficiency** | Flash only (PROGMEM/const arrays) | Flash partition + filesystem overhead |
|
|
24
|
+
| **Performance** | Direct byte array serving | Filesystem read overhead |
|
|
25
|
+
| **Setup Complexity** | Include header, call init function | Partition setup, upload tools, handlers |
|
|
26
|
+
|
|
27
|
+
**When to use:**
|
|
28
|
+
|
|
29
|
+
- **SvelteESP32**: Single-binary OTA updates, CI/CD pipelines, static web content that doesn't change at runtime
|
|
30
|
+
- **SPIFFS/LittleFS**: Dynamic content, user-uploadable files, configuration that changes at runtime
|
|
31
|
+
|
|
32
|
+
> Starting with version v1.13.0, RC files support npm package variable interpolation
|
|
33
|
+
|
|
15
34
|
> Starting with version v1.12.0, you can use RC file for configuration
|
|
16
35
|
|
|
17
36
|
> Starting with version v1.11.0, you can exclude files by pattern
|
|
@@ -307,6 +326,18 @@ At the same time, it can be an advantage that the content is cached by the brows
|
|
|
307
326
|
|
|
308
327
|
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.
|
|
309
328
|
|
|
329
|
+
**Validation**: By default, svelteesp32 validates that an `index.html` or `index.htm` file exists in your source directory (in the root or any subdirectory). This ensures users won't get a 404 error when visiting your ESP32's root URL.
|
|
330
|
+
|
|
331
|
+
**Skipping Validation**: If you're building an API-only application (REST endpoints without a web UI) or using a different entry point (e.g., `main.html`), you can skip this validation with the `--no-index-check` flag:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# API-only application (no web UI)
|
|
335
|
+
npx svelteesp32 -e psychic -s ./dist -o ./output.h --no-index-check
|
|
336
|
+
|
|
337
|
+
# Custom entry point (users must visit /main.html explicitly)
|
|
338
|
+
npx svelteesp32 -e psychic -s ./dist -o ./output.h --no-index-check
|
|
339
|
+
```
|
|
340
|
+
|
|
310
341
|
### File Exclusion
|
|
311
342
|
|
|
312
343
|
The `--exclude` option allows you to exclude files from being embedded in the ESP32 firmware using glob patterns. This is useful for excluding source maps, documentation, and test files that shouldn't be part of the deployed application.
|
|
@@ -416,21 +447,22 @@ You can use the following c++ directives at the project level if you want to con
|
|
|
416
447
|
|
|
417
448
|
### Command line options
|
|
418
449
|
|
|
419
|
-
| Option
|
|
420
|
-
|
|
|
421
|
-
| `-s`
|
|
422
|
-
| `-e`
|
|
423
|
-
| `-o`
|
|
424
|
-
| `--exclude`
|
|
425
|
-
| `--etag`
|
|
426
|
-
| `--cachetime`
|
|
427
|
-
| `--gzip`
|
|
428
|
-
| `--created`
|
|
429
|
-
| `--version`
|
|
430
|
-
| `--espmethod`
|
|
431
|
-
| `--define`
|
|
432
|
-
| `--config`
|
|
433
|
-
|
|
|
450
|
+
| Option | Description | default |
|
|
451
|
+
| ------------------ | ------------------------------------------------------------------------------------ | ----------------------- |
|
|
452
|
+
| `-s` | **Source dist folder contains compiled web files** | (required) |
|
|
453
|
+
| `-e` | The engine for which the include file is created (psychic/psychic2/async/espidf) | psychic |
|
|
454
|
+
| `-o` | Generated output file with path | `svelteesp32.h` |
|
|
455
|
+
| `--exclude` | Exclude files matching glob pattern (repeatable or comma-separated) | Default system files |
|
|
456
|
+
| `--etag` | Use ETag header for cache (true/false/compiler) | false |
|
|
457
|
+
| `--cachetime` | Override no-cache response with a max-age=\<cachetime\> response (seconds) | 0 |
|
|
458
|
+
| `--gzip` | Compress content with gzip (true/false/compiler) | true |
|
|
459
|
+
| `--created` | Include creation time in generated header | false |
|
|
460
|
+
| `--version` | Include a version string in generated header, e.g. `--version=v$npm_package_version` | '' |
|
|
461
|
+
| `--espmethod` | Name of generated initialization method | `initSvelteStaticFiles` |
|
|
462
|
+
| `--define` | Prefix of c++ defines (e.g., SVELTEESP32_COUNT) | `SVELTEESP32` |
|
|
463
|
+
| `--config` | Use custom RC file path | `.svelteesp32rc.json` |
|
|
464
|
+
| `--no-index-check` | Skip validation for index.html/index.htm (for API-only or custom entry points) | false |
|
|
465
|
+
| `-h` | Show help | |
|
|
434
466
|
|
|
435
467
|
### Configuration File
|
|
436
468
|
|
|
@@ -477,19 +509,20 @@ npx svelteesp32 --config=.svelteesp32rc.prod.json
|
|
|
477
509
|
|
|
478
510
|
All CLI options can be specified in the RC file using long-form property names:
|
|
479
511
|
|
|
480
|
-
| RC Property
|
|
481
|
-
|
|
|
482
|
-
| `engine`
|
|
483
|
-
| `sourcepath`
|
|
484
|
-
| `outputfile`
|
|
485
|
-
| `etag`
|
|
486
|
-
| `gzip`
|
|
487
|
-
| `cachetime`
|
|
488
|
-
| `created`
|
|
489
|
-
| `version`
|
|
490
|
-
| `espmethod`
|
|
491
|
-
| `define`
|
|
492
|
-
| `exclude`
|
|
512
|
+
| RC Property | CLI Flag | Type | Example |
|
|
513
|
+
| -------------- | ------------------ | ------- | ------------------------------------------------ |
|
|
514
|
+
| `engine` | `-e` | string | `"psychic"`, `"psychic2"`, `"async"`, `"espidf"` |
|
|
515
|
+
| `sourcepath` | `-s` | string | `"./dist"` |
|
|
516
|
+
| `outputfile` | `-o` | string | `"./output.h"` |
|
|
517
|
+
| `etag` | `--etag` | string | `"true"`, `"false"`, `"compiler"` |
|
|
518
|
+
| `gzip` | `--gzip` | string | `"true"`, `"false"`, `"compiler"` |
|
|
519
|
+
| `cachetime` | `--cachetime` | number | `86400` |
|
|
520
|
+
| `created` | `--created` | boolean | `true`, `false` |
|
|
521
|
+
| `version` | `--version` | string | `"v1.0.0"` |
|
|
522
|
+
| `espmethod` | `--espmethod` | string | `"initSvelteStaticFiles"` |
|
|
523
|
+
| `define` | `--define` | string | `"SVELTEESP32"` |
|
|
524
|
+
| `exclude` | `--exclude` | array | `["*.map", "*.md"]` |
|
|
525
|
+
| `noIndexCheck` | `--no-index-check` | boolean | `true`, `false` |
|
|
493
526
|
|
|
494
527
|
#### CLI Override
|
|
495
528
|
|
|
@@ -540,6 +573,89 @@ To keep defaults, explicitly list them in your RC file:
|
|
|
540
573
|
}
|
|
541
574
|
```
|
|
542
575
|
|
|
576
|
+
#### NPM Package Variable Interpolation
|
|
577
|
+
|
|
578
|
+
RC files support automatic variable interpolation from your `package.json`. This allows you to reference package.json fields in your RC configuration using npm-style variable syntax.
|
|
579
|
+
|
|
580
|
+
**Syntax:** `$npm_package_<field_name>`
|
|
581
|
+
|
|
582
|
+
**Supported in:** All string fields (`version`, `define`, `sourcepath`, `outputfile`, `espmethod`, `exclude` patterns)
|
|
583
|
+
|
|
584
|
+
**Example:**
|
|
585
|
+
|
|
586
|
+
```json
|
|
587
|
+
// .svelteesp32rc.json
|
|
588
|
+
{
|
|
589
|
+
"engine": "psychic",
|
|
590
|
+
"version": "v$npm_package_version",
|
|
591
|
+
"define": "$npm_package_name",
|
|
592
|
+
"sourcepath": "./dist",
|
|
593
|
+
"outputfile": "./output.h"
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
With `package.json` containing:
|
|
598
|
+
|
|
599
|
+
```json
|
|
600
|
+
{
|
|
601
|
+
"name": "my-esp32-app",
|
|
602
|
+
"version": "2.1.0"
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
The variables are automatically interpolated to:
|
|
607
|
+
|
|
608
|
+
```json
|
|
609
|
+
{
|
|
610
|
+
"version": "v2.1.0",
|
|
611
|
+
"define": "my_esp32_app"
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**Nested Fields:**
|
|
616
|
+
|
|
617
|
+
You can access nested package.json fields using underscores:
|
|
618
|
+
|
|
619
|
+
```json
|
|
620
|
+
// package.json
|
|
621
|
+
{
|
|
622
|
+
"name": "myapp",
|
|
623
|
+
"repository": {
|
|
624
|
+
"type": "git",
|
|
625
|
+
"url": "https://github.com/user/repo.git"
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// .svelteesp32rc.json
|
|
630
|
+
{
|
|
631
|
+
"version": "$npm_package_repository_type"
|
|
632
|
+
}
|
|
633
|
+
// Results in: "version": "git"
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**Multiple Variables:**
|
|
637
|
+
|
|
638
|
+
Combine multiple variables in a single field:
|
|
639
|
+
|
|
640
|
+
```json
|
|
641
|
+
{
|
|
642
|
+
"version": "$npm_package_name-v$npm_package_version-release"
|
|
643
|
+
}
|
|
644
|
+
// Results in: "my-esp32-app-v2.1.0-release"
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**Requirements:**
|
|
648
|
+
|
|
649
|
+
- `package.json` must exist in the same directory as the RC file
|
|
650
|
+
- If variables are used but `package.json` is not found, an error is thrown with details about which fields contain variables
|
|
651
|
+
- Unknown variables are left unchanged (e.g., `$npm_package_nonexistent` stays as-is)
|
|
652
|
+
|
|
653
|
+
**Use Cases:**
|
|
654
|
+
|
|
655
|
+
- **Version Synchronization:** Keep header version in sync with npm package version
|
|
656
|
+
- **Dynamic Naming:** Use package name for C++ defines automatically
|
|
657
|
+
- **CI/CD Integration:** Reusable RC files across projects with different package names
|
|
658
|
+
|
|
543
659
|
### Q&A
|
|
544
660
|
|
|
545
661
|
- **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.
|
package/dist/commandLine.d.ts
CHANGED
|
@@ -10,8 +10,25 @@ interface ICopyFilesArguments {
|
|
|
10
10
|
created: boolean;
|
|
11
11
|
version: string;
|
|
12
12
|
exclude: string[];
|
|
13
|
+
noIndexCheck?: boolean;
|
|
13
14
|
help?: boolean;
|
|
14
15
|
}
|
|
16
|
+
interface IRcFileConfig {
|
|
17
|
+
engine?: 'psychic' | 'psychic2' | 'async' | 'espidf';
|
|
18
|
+
sourcepath?: string;
|
|
19
|
+
outputfile?: string;
|
|
20
|
+
espmethod?: string;
|
|
21
|
+
define?: string;
|
|
22
|
+
gzip?: 'true' | 'false' | 'compiler';
|
|
23
|
+
etag?: 'true' | 'false' | 'compiler';
|
|
24
|
+
cachetime?: number;
|
|
25
|
+
created?: boolean;
|
|
26
|
+
version?: string;
|
|
27
|
+
exclude?: string[];
|
|
28
|
+
}
|
|
29
|
+
declare function getNpmPackageVariable(packageJson: Record<string, unknown>, variableName: string): string | undefined;
|
|
30
|
+
declare function hasNpmVariables(config: IRcFileConfig): boolean;
|
|
31
|
+
declare function interpolateNpmVariables(config: IRcFileConfig, rcFilePath: string): IRcFileConfig;
|
|
32
|
+
export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables };
|
|
15
33
|
export declare function formatConfiguration(cmdLine: ICopyFilesArguments): string;
|
|
16
34
|
export declare const cmdLine: ICopyFilesArguments;
|
|
17
|
-
export {};
|
package/dist/commandLine.js
CHANGED
|
@@ -4,11 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.cmdLine = void 0;
|
|
7
|
+
exports.getNpmPackageVariable = getNpmPackageVariable;
|
|
8
|
+
exports.hasNpmVariables = hasNpmVariables;
|
|
9
|
+
exports.interpolateNpmVariables = interpolateNpmVariables;
|
|
7
10
|
exports.formatConfiguration = formatConfiguration;
|
|
8
11
|
const node_fs_1 = require("node:fs");
|
|
9
12
|
const node_os_1 = require("node:os");
|
|
10
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
14
|
const consoleColor_1 = require("./consoleColor");
|
|
15
|
+
const errorMessages_1 = require("./errorMessages");
|
|
12
16
|
function showHelp() {
|
|
13
17
|
console.log(`
|
|
14
18
|
svelteesp32 - Svelte JS to ESP32 converter
|
|
@@ -54,7 +58,8 @@ RC File:
|
|
|
54
58
|
function validateEngine(value) {
|
|
55
59
|
if (value === 'psychic' || value === 'psychic2' || value === 'async' || value === 'espidf')
|
|
56
60
|
return value;
|
|
57
|
-
|
|
61
|
+
console.error((0, errorMessages_1.getInvalidEngineError)(value));
|
|
62
|
+
process.exit(1);
|
|
58
63
|
}
|
|
59
64
|
function validateTriState(value, name) {
|
|
60
65
|
if (value === 'true' || value === 'false' || value === 'compiler')
|
|
@@ -90,11 +95,100 @@ function findRcFile(customConfigPath) {
|
|
|
90
95
|
}
|
|
91
96
|
return undefined;
|
|
92
97
|
}
|
|
98
|
+
function findPackageJson(rcFilePath) {
|
|
99
|
+
const rcDirectory = node_path_1.default.dirname(rcFilePath);
|
|
100
|
+
const packageJsonPath = node_path_1.default.join(rcDirectory, 'package.json');
|
|
101
|
+
return (0, node_fs_1.existsSync)(packageJsonPath) ? packageJsonPath : undefined;
|
|
102
|
+
}
|
|
103
|
+
function parsePackageJson(packageJsonPath) {
|
|
104
|
+
try {
|
|
105
|
+
const content = (0, node_fs_1.readFileSync)(packageJsonPath, 'utf8');
|
|
106
|
+
return JSON.parse(content);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
throw new Error(`Failed to parse package.json at ${packageJsonPath}: ${error.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function getNpmPackageVariable(packageJson, variableName) {
|
|
113
|
+
const prefix = '$npm_package_';
|
|
114
|
+
if (!variableName.startsWith(prefix))
|
|
115
|
+
return undefined;
|
|
116
|
+
const pathString = variableName.slice(prefix.length);
|
|
117
|
+
const pathSegments = pathString.split('_');
|
|
118
|
+
let current = packageJson;
|
|
119
|
+
for (const segment of pathSegments) {
|
|
120
|
+
if (current === null || current === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
if (typeof current !== 'object')
|
|
123
|
+
return undefined;
|
|
124
|
+
current = current[segment];
|
|
125
|
+
}
|
|
126
|
+
if (current === null || current === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
return String(current);
|
|
129
|
+
}
|
|
130
|
+
function checkStringForNpmVariable(value) {
|
|
131
|
+
return value?.includes('$npm_package_') ?? false;
|
|
132
|
+
}
|
|
133
|
+
function hasNpmVariables(config) {
|
|
134
|
+
return (checkStringForNpmVariable(config.sourcepath) ||
|
|
135
|
+
checkStringForNpmVariable(config.outputfile) ||
|
|
136
|
+
checkStringForNpmVariable(config.espmethod) ||
|
|
137
|
+
checkStringForNpmVariable(config.define) ||
|
|
138
|
+
checkStringForNpmVariable(config.version) ||
|
|
139
|
+
(Array.isArray(config.exclude) && config.exclude.some((pattern) => checkStringForNpmVariable(pattern))) ||
|
|
140
|
+
false);
|
|
141
|
+
}
|
|
142
|
+
function interpolateNpmVariables(config, rcFilePath) {
|
|
143
|
+
if (!hasNpmVariables(config))
|
|
144
|
+
return config;
|
|
145
|
+
const packageJsonPath = findPackageJson(rcFilePath);
|
|
146
|
+
if (!packageJsonPath) {
|
|
147
|
+
const affectedFields = [];
|
|
148
|
+
if (config.sourcepath?.includes('$npm_package_'))
|
|
149
|
+
affectedFields.push('sourcepath');
|
|
150
|
+
if (config.outputfile?.includes('$npm_package_'))
|
|
151
|
+
affectedFields.push('outputfile');
|
|
152
|
+
if (config.version?.includes('$npm_package_'))
|
|
153
|
+
affectedFields.push('version');
|
|
154
|
+
if (config.espmethod?.includes('$npm_package_'))
|
|
155
|
+
affectedFields.push('espmethod');
|
|
156
|
+
if (config.define?.includes('$npm_package_'))
|
|
157
|
+
affectedFields.push('define');
|
|
158
|
+
if (config.exclude)
|
|
159
|
+
for (const [index, pattern] of config.exclude.entries())
|
|
160
|
+
if (pattern.includes('$npm_package_'))
|
|
161
|
+
affectedFields.push(`exclude[${index}]`);
|
|
162
|
+
throw new Error(`RC file uses npm package variables but package.json not found in ${node_path_1.default.dirname(rcFilePath)}\n` +
|
|
163
|
+
`Variables found in fields: ${affectedFields.join(', ')}\n` +
|
|
164
|
+
`Please ensure package.json exists in the same directory as your RC file.`);
|
|
165
|
+
}
|
|
166
|
+
const packageJson = parsePackageJson(packageJsonPath);
|
|
167
|
+
const interpolateString = (value) => {
|
|
168
|
+
const regex = /\$npm_package_[\dA-Za-z]+(?:_[a-z][\dA-Za-z]*)*/g;
|
|
169
|
+
return value.replace(regex, (match) => getNpmPackageVariable(packageJson, match) ?? match);
|
|
170
|
+
};
|
|
171
|
+
const result = { ...config };
|
|
172
|
+
if (result.sourcepath)
|
|
173
|
+
result.sourcepath = interpolateString(result.sourcepath);
|
|
174
|
+
if (result.outputfile)
|
|
175
|
+
result.outputfile = interpolateString(result.outputfile);
|
|
176
|
+
if (result.espmethod)
|
|
177
|
+
result.espmethod = interpolateString(result.espmethod);
|
|
178
|
+
if (result.define)
|
|
179
|
+
result.define = interpolateString(result.define);
|
|
180
|
+
if (result.version)
|
|
181
|
+
result.version = interpolateString(result.version);
|
|
182
|
+
if (result.exclude)
|
|
183
|
+
result.exclude = result.exclude.map((pattern) => interpolateString(pattern));
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
93
186
|
function loadRcFile(rcPath) {
|
|
94
187
|
try {
|
|
95
188
|
const content = (0, node_fs_1.readFileSync)(rcPath, 'utf8');
|
|
96
189
|
const config = JSON.parse(content);
|
|
97
|
-
|
|
190
|
+
const interpolatedConfig = interpolateNpmVariables(config, rcPath);
|
|
191
|
+
return validateRcConfig(interpolatedConfig, rcPath);
|
|
98
192
|
}
|
|
99
193
|
catch (error) {
|
|
100
194
|
if (error instanceof SyntaxError)
|
|
@@ -257,6 +351,10 @@ function parseArguments() {
|
|
|
257
351
|
result.created = true;
|
|
258
352
|
continue;
|
|
259
353
|
}
|
|
354
|
+
if (argument === '--no-index-check') {
|
|
355
|
+
result.noIndexCheck = true;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
260
358
|
if (argument.startsWith('-') && !argument.startsWith('--')) {
|
|
261
359
|
const flag = argument.slice(1);
|
|
262
360
|
const nextArgument = arguments_[index + 1];
|
|
@@ -373,8 +471,12 @@ function formatConfiguration(cmdLine) {
|
|
|
373
471
|
return parts.join(' ');
|
|
374
472
|
}
|
|
375
473
|
exports.cmdLine = parseArguments();
|
|
376
|
-
if (!(0, node_fs_1.existsSync)(exports.cmdLine.sourcepath)
|
|
377
|
-
console.error(
|
|
474
|
+
if (!(0, node_fs_1.existsSync)(exports.cmdLine.sourcepath)) {
|
|
475
|
+
console.error((0, errorMessages_1.getSourcepathNotFoundError)(exports.cmdLine.sourcepath, 'not_found'));
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
if (!(0, node_fs_1.statSync)(exports.cmdLine.sourcepath).isDirectory()) {
|
|
479
|
+
console.error((0, errorMessages_1.getSourcepathNotFoundError)(exports.cmdLine.sourcepath, 'not_directory'));
|
|
378
480
|
process.exit(1);
|
|
379
481
|
}
|
|
380
482
|
console.log(`[SvelteESP32] Generate code for ${exports.cmdLine.engine} engine`);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function getMissingIndexError(engine: string): string;
|
|
2
|
+
export declare function getInvalidEngineError(attempted: string): string;
|
|
3
|
+
export declare function getSourcepathNotFoundError(sourcepath: string, reason: 'not_found' | 'not_directory'): string;
|
|
4
|
+
export declare function getMaxUriHandlersHint(engine: string, routeCount: number): string;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getMissingIndexError = getMissingIndexError;
|
|
7
|
+
exports.getInvalidEngineError = getInvalidEngineError;
|
|
8
|
+
exports.getSourcepathNotFoundError = getSourcepathNotFoundError;
|
|
9
|
+
exports.getMaxUriHandlersHint = getMaxUriHandlersHint;
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const consoleColor_1 = require("./consoleColor");
|
|
12
|
+
function getEngineName(engine) {
|
|
13
|
+
const names = {
|
|
14
|
+
psychic: 'PsychicHttpServer',
|
|
15
|
+
psychic2: 'PsychicHttpServer V2',
|
|
16
|
+
async: 'ESPAsyncWebServer',
|
|
17
|
+
espidf: 'ESP-IDF'
|
|
18
|
+
};
|
|
19
|
+
return names[engine] ?? engine;
|
|
20
|
+
}
|
|
21
|
+
function getMissingIndexError(engine) {
|
|
22
|
+
const hints = {
|
|
23
|
+
psychic: ` 1. Add an index.html file to your source directory
|
|
24
|
+
2. The file will automatically be set as the default route ("/")
|
|
25
|
+
3. PsychicHttpServer uses: server->defaultEndpoint = ...`,
|
|
26
|
+
psychic2: ` 1. Add an index.html file to your source directory
|
|
27
|
+
2. The file will automatically be set as the default route ("/")
|
|
28
|
+
3. PsychicHttpServer V2 uses: server->defaultEndpoint = ...`,
|
|
29
|
+
async: ` 1. Add an index.html file to your source directory
|
|
30
|
+
2. The file will automatically create a "/" route handler
|
|
31
|
+
3. ESPAsyncWebServer uses: server.on("/", HTTP_GET, ...)`,
|
|
32
|
+
espidf: ` 1. Add an index.html file to your source directory
|
|
33
|
+
2. The file will register both "/" and "/index.html" routes
|
|
34
|
+
3. ESP-IDF uses: httpd_register_uri_handler(server, &route_def_...)`
|
|
35
|
+
};
|
|
36
|
+
const hint = hints[engine] ?? hints['psychic'];
|
|
37
|
+
return ((0, consoleColor_1.redLog)('[ERROR] No index.html or index.htm found in source files') +
|
|
38
|
+
`
|
|
39
|
+
|
|
40
|
+
Why this matters:
|
|
41
|
+
Web applications typically need a default entry point. Without index.html,
|
|
42
|
+
users visiting http://your-esp32/ will get a 404 error.
|
|
43
|
+
|
|
44
|
+
How to fix (for ${getEngineName(engine)}):
|
|
45
|
+
${hint}
|
|
46
|
+
|
|
47
|
+
Alternative:
|
|
48
|
+
If you use a different entry point (e.g., main.html), you can add --no-index-check flag,
|
|
49
|
+
but users must navigate to http://your-esp32/main.html explicitly.`);
|
|
50
|
+
}
|
|
51
|
+
function getInvalidEngineError(attempted) {
|
|
52
|
+
return ((0, consoleColor_1.redLog)(`[ERROR] Invalid engine: '${attempted}'`) +
|
|
53
|
+
`
|
|
54
|
+
|
|
55
|
+
Valid engines are:
|
|
56
|
+
${(0, consoleColor_1.cyanLog)('• psychic')} - PsychicHttpServer (ESP32 only, fastest performance)
|
|
57
|
+
${(0, consoleColor_1.cyanLog)('• psychic2')} - PsychicHttpServer V2 (ESP32 only, modern API)
|
|
58
|
+
${(0, consoleColor_1.cyanLog)('• async')} - ESPAsyncWebServer (ESP32/ESP8266 compatible)
|
|
59
|
+
${(0, consoleColor_1.cyanLog)('• espidf')} - Native ESP-IDF web server (ESP32 only, no Arduino)
|
|
60
|
+
|
|
61
|
+
How to fix:
|
|
62
|
+
npx svelteesp32 --engine=psychic --sourcepath=./dist
|
|
63
|
+
|
|
64
|
+
Example RC file (.svelteesp32rc.json):
|
|
65
|
+
{
|
|
66
|
+
"engine": "psychic",
|
|
67
|
+
"sourcepath": "./dist"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
Documentation: https://github.com/hpieroni/svelteesp32#readme`);
|
|
71
|
+
}
|
|
72
|
+
function getSourcepathNotFoundError(sourcepath, reason) {
|
|
73
|
+
if (reason === 'not_directory')
|
|
74
|
+
return ((0, consoleColor_1.redLog)(`[ERROR] Source path is not a directory: '${sourcepath}'`) +
|
|
75
|
+
`
|
|
76
|
+
|
|
77
|
+
The --sourcepath option must point to a directory containing your built web files,
|
|
78
|
+
not an individual file.
|
|
79
|
+
|
|
80
|
+
How to fix:
|
|
81
|
+
npx svelteesp32 --sourcepath=./dist --engine=psychic`);
|
|
82
|
+
const resolvedPath = node_path_1.default.resolve(sourcepath);
|
|
83
|
+
const currentDirectory = process.cwd();
|
|
84
|
+
return ((0, consoleColor_1.redLog)(`[ERROR] Source directory not found: '${sourcepath}'`) +
|
|
85
|
+
`
|
|
86
|
+
|
|
87
|
+
Why this matters:
|
|
88
|
+
SvelteESP32 needs your compiled web assets (HTML, CSS, JS) to convert them
|
|
89
|
+
into C++ header files for the ESP32.
|
|
90
|
+
|
|
91
|
+
How to fix:
|
|
92
|
+
1. Build your frontend application first:
|
|
93
|
+
• Svelte: npm run build
|
|
94
|
+
• React: npm run build
|
|
95
|
+
• Vue: npm run build
|
|
96
|
+
• Angular: ng build
|
|
97
|
+
|
|
98
|
+
2. Verify the build output directory exists:
|
|
99
|
+
${(0, consoleColor_1.cyanLog)(`ls -la ${sourcepath}`)}
|
|
100
|
+
|
|
101
|
+
3. Check your build tool configuration:
|
|
102
|
+
• Vite: vite.config.js → build.outDir
|
|
103
|
+
• Webpack: webpack.config.js → output.path
|
|
104
|
+
• Rollup: rollup.config.js → output.dir
|
|
105
|
+
|
|
106
|
+
4. Update your svelteesp32 command to match:
|
|
107
|
+
${(0, consoleColor_1.cyanLog)(`npx svelteesp32 --sourcepath=./build --engine=psychic`)}
|
|
108
|
+
|
|
109
|
+
Current directory: ${currentDirectory}
|
|
110
|
+
Attempted path: ${resolvedPath} (resolved)`);
|
|
111
|
+
}
|
|
112
|
+
function getMaxUriHandlersHint(engine, routeCount) {
|
|
113
|
+
const recommended = routeCount + 5;
|
|
114
|
+
const hints = {
|
|
115
|
+
psychic: `PsychicHttpServer server;
|
|
116
|
+
server.config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
|
|
117
|
+
initSvelteStaticFiles(&server);
|
|
118
|
+
server.listen(80);`,
|
|
119
|
+
psychic2: `PsychicHttpServer server;
|
|
120
|
+
server.config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
|
|
121
|
+
initSvelteStaticFiles(&server);
|
|
122
|
+
server.listen(80);`,
|
|
123
|
+
espidf: `httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|
124
|
+
config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
|
|
125
|
+
httpd_handle_t server = NULL;
|
|
126
|
+
httpd_start(&server, &config);
|
|
127
|
+
initSvelteStaticFiles(server);`
|
|
128
|
+
};
|
|
129
|
+
const hint = hints[engine];
|
|
130
|
+
if (!hint)
|
|
131
|
+
return '';
|
|
132
|
+
return ((0, consoleColor_1.yellowLog)('[CONFIG TIP] max_uri_handlers configuration') +
|
|
133
|
+
`
|
|
134
|
+
|
|
135
|
+
Your generated code includes ${routeCount} routes. Make sure your server can handle them:
|
|
136
|
+
|
|
137
|
+
For ${getEngineName(engine)}:
|
|
138
|
+
${hint}
|
|
139
|
+
|
|
140
|
+
Recommended formula: max_uri_handlers = file_count + 5 (safety margin)
|
|
141
|
+
|
|
142
|
+
Runtime symptoms if too low:
|
|
143
|
+
• Routes not registered (HTTP 404 errors)
|
|
144
|
+
• ESP_ERR_HTTPD_HANDLERS_FULL error in logs
|
|
145
|
+
• Some files load, others don't (random behavior)`);
|
|
146
|
+
}
|
package/dist/file.js
CHANGED
|
@@ -11,6 +11,7 @@ const picomatch_1 = __importDefault(require("picomatch"));
|
|
|
11
11
|
const tinyglobby_1 = require("tinyglobby");
|
|
12
12
|
const commandLine_1 = require("./commandLine");
|
|
13
13
|
const consoleColor_1 = require("./consoleColor");
|
|
14
|
+
const errorMessages_1 = require("./errorMessages");
|
|
14
15
|
const findSimilarFiles = (files) => {
|
|
15
16
|
const contentComparer = new Map();
|
|
16
17
|
for (const [filename, content] of files.entries()) {
|
|
@@ -79,6 +80,11 @@ const getFiles = () => {
|
|
|
79
80
|
const duplicates = findSimilarFiles(result);
|
|
80
81
|
for (const sameFiles of duplicates)
|
|
81
82
|
console.log((0, consoleColor_1.yellowLog)(` ${sameFiles.join(', ')} files look like identical`));
|
|
83
|
+
const hasIndex = [...result.keys()].some((f) => f === 'index.html' || f === 'index.htm' || f.endsWith('/index.html') || f.endsWith('/index.htm'));
|
|
84
|
+
if (!hasIndex && !commandLine_1.cmdLine.noIndexCheck) {
|
|
85
|
+
console.error('\n' + (0, errorMessages_1.getMissingIndexError)(commandLine_1.cmdLine.engine));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
82
88
|
return result;
|
|
83
89
|
};
|
|
84
90
|
exports.getFiles = getFiles;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const mime_types_1 = require("mime-types");
|
|
|
11
11
|
const commandLine_1 = require("./commandLine");
|
|
12
12
|
const consoleColor_1 = require("./consoleColor");
|
|
13
13
|
const cppCode_1 = require("./cppCode");
|
|
14
|
+
const errorMessages_1 = require("./errorMessages");
|
|
14
15
|
const file_1 = require("./file");
|
|
15
16
|
const GZIP_MIN_SIZE = 1024;
|
|
16
17
|
const GZIP_MIN_REDUCTION_RATIO = 0.85;
|
|
@@ -82,3 +83,5 @@ const cppFile = (0, cppCode_1.getCppCode)(sources, filesByExtension);
|
|
|
82
83
|
(0, node_fs_1.writeFileSync)(commandLine_1.cmdLine.outputfile, cppFile, { flush: true, encoding: 'utf8' });
|
|
83
84
|
console.log(`${summary.filecount} files, ${Math.round(summary.size / 1024)}kB original size, ${Math.round(summary.gzipsize / 1024)}kB gzip size`);
|
|
84
85
|
console.log(`${commandLine_1.cmdLine.outputfile} ${Math.round(cppFile.length / 1024)}kB size`);
|
|
86
|
+
if (commandLine_1.cmdLine.engine === 'psychic' || commandLine_1.cmdLine.engine === 'psychic2' || commandLine_1.cmdLine.engine === 'espidf')
|
|
87
|
+
console.log('\n' + (0, errorMessages_1.getMaxUriHandlersHint)(commandLine_1.cmdLine.engine, sources.length));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelteesp32",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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",
|
|
@@ -61,10 +61,10 @@
|
|
|
61
61
|
],
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/mime-types": "^3.0.1",
|
|
64
|
-
"@types/node": "^
|
|
64
|
+
"@types/node": "^25.0.0",
|
|
65
65
|
"@types/picomatch": "^4.0.2",
|
|
66
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
67
|
-
"@typescript-eslint/parser": "^8.
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
|
67
|
+
"@typescript-eslint/parser": "^8.49.0",
|
|
68
68
|
"@vitest/coverage-v8": "^4.0.15",
|
|
69
69
|
"eslint": "^9.39.1",
|
|
70
70
|
"eslint-config-prettier": "^10.1.8",
|