Skip to content

Commit 61c8a04

Browse files
committed
feat: error messages
1 parent 7806355 commit 61c8a04

File tree

10 files changed

+665
-16
lines changed

10 files changed

+665
-16
lines changed

CHANGELOG.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.13.1] - 2025-12-11
11+
12+
### Added
13+
14+
- **Enhanced Error Messages with Framework-Specific Hints**: Comprehensive error messages with actionable "How to fix" guidance for all 4 engines (psychic, psychic2, async, espidf)
15+
- **Missing index.html Validation**: Automatic check for default entry point with engine-specific routing examples
16+
- Framework-specific hints: `server->defaultEndpoint` (psychic/psychic2), `server.on("/")` (async), `httpd_register_uri_handler` (espidf)
17+
- Detects index.html/index.htm in root or subdirectories
18+
- `--no-index-check` flag to bypass validation for API-only applications
19+
- Clear explanation of why index.html matters and alternative solutions
20+
- **Invalid Engine Error**: Enhanced error message with complete engine reference
21+
- Lists all 4 valid engines with descriptions and platform compatibility
22+
- Example commands and RC file format
23+
- Documentation link for further reference
24+
- **Sourcepath Not Found Error**: Detailed troubleshooting guidance
25+
- Build tool configuration hints (Vite, Webpack, Rollup)
26+
- Framework-specific build commands (Svelte, React, Vue, Angular)
27+
- Shows current directory and resolved path for debugging
28+
- Separate messages for "not found" vs "not a directory" scenarios
29+
- **max_uri_handlers Configuration Hints**: Console output after successful generation
30+
- Only shown for engines that require it (psychic, psychic2, espidf)
31+
- Calculates recommended value: `routeCount + 5` (safety margin)
32+
- Engine-specific configuration examples with code snippets
33+
- Lists runtime symptoms to watch for (404 errors, ESP_ERR_HTTPD_HANDLERS_FULL)
34+
- New `src/errorMessages.ts` module (~180 lines) with pure, testable functions:
35+
- `getMissingIndexError()` - Engine-specific index.html error messages
36+
- `getInvalidEngineError()` - Comprehensive invalid engine guidance
37+
- `getSourcepathNotFoundError()` - Build tool and framework hints
38+
- `getMaxUriHandlersHint()` - Configuration guidance for handler limits
39+
- Consistent multi-line error format with color-coded sections
40+
- 32 comprehensive unit tests in `test/unit/errorMessages.test.ts` with 100% coverage:
41+
- Tests for all 4 engines (psychic, psychic2, async, espidf)
42+
- Message content validation (error title, explanations, hints)
43+
- Edge cases (unknown engines, empty strings)
44+
- Framework-specific code snippet verification
45+
- Enhanced test suite with 156 total tests passing:
46+
- Updated `test/unit/commandLine.test.ts` with enhanced error validation
47+
- Updated `test/unit/file.test.ts` with index.html validation tests
48+
- All tests use proper TypeScript types (replaced `any` with `vi.mocked()`)
49+
50+
### Changed
51+
52+
- Improved developer experience with clear, actionable error messages
53+
- Error messages now include framework-specific code examples
54+
- Console output uses color-coded sections for better readability
55+
- Validation happens early with helpful guidance before processing begins
56+
- Updated `src/commandLine.ts` to use enhanced error messages (lines 84-85, 559-567)
57+
- Updated `src/file.ts` with index.html validation (lines 117-125)
58+
- Updated `src/index.ts` to show max_uri_handlers hints (lines 150-153)
59+
60+
### Fixed
61+
62+
- Linting errors: renamed `currentDir` to `currentDirectory` (unicorn/prevent-abbreviations)
63+
- Test type safety: replaced explicit `any` types with `vi.mocked()` helper
64+
1065
## [1.13.0] - 2025-12-04
1166

1267
### Added
@@ -374,6 +429,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
374429
- CLI interface with `-s`, `-e`, `-o` options
375430
- `index.html` automatic default route handling
376431

432+
[1.13.1]: https://github.com/BCsabaEngine/svelteesp32/compare/v1.13.0...v1.13.1
377433
[1.13.0]: https://github.com/BCsabaEngine/svelteesp32/compare/v1.12.1...v1.13.0
378434
[1.12.1]: https://github.com/BCsabaEngine/svelteesp32/compare/v1.12.0...v1.12.1
379435
[1.12.0]: https://github.com/BCsabaEngine/svelteesp32/compare/v1.11.0...v1.12.0

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "svelteesp32",
3-
"version": "1.13.0",
3+
"version": "1.13.1",
44
"description": "Convert Svelte (or any frontend) JS application to serve it from ESP32 webserver (PsychicHttp)",
55
"author": "BCsabaEngine",
66
"license": "ISC",

src/commandLine.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { homedir } from 'node:os';
33
import path from 'node:path';
44

55
import { cyanLog, yellowLog } from './consoleColor';
6+
import { getInvalidEngineError, getSourcepathNotFoundError } from './errorMessages';
67

78
interface ICopyFilesArguments {
89
engine: 'psychic' | 'psychic2' | 'async' | 'espidf';
@@ -16,6 +17,7 @@ interface ICopyFilesArguments {
1617
created: boolean;
1718
version: string;
1819
exclude: string[];
20+
noIndexCheck?: boolean;
1921
help?: boolean;
2022
}
2123

@@ -79,7 +81,8 @@ RC File:
7981
function validateEngine(value: string): 'psychic' | 'psychic2' | 'async' | 'espidf' {
8082
if (value === 'psychic' || value === 'psychic2' || value === 'async' || value === 'espidf') return value;
8183

82-
throw new Error(`Invalid engine: ${value}`);
84+
console.error(getInvalidEngineError(value));
85+
process.exit(1);
8386
}
8487

8588
function validateTriState(value: string, name: string): 'true' | 'false' | 'compiler' {
@@ -414,6 +417,11 @@ function parseArguments(): ICopyFilesArguments {
414417
continue;
415418
}
416419

420+
if (argument === '--no-index-check') {
421+
result.noIndexCheck = true;
422+
continue;
423+
}
424+
417425
// Handle -flag value format
418426
if (argument.startsWith('-') && !argument.startsWith('--')) {
419427
const flag = argument.slice(1);
@@ -548,8 +556,13 @@ export function formatConfiguration(cmdLine: ICopyFilesArguments): string {
548556

549557
export const cmdLine = parseArguments();
550558

551-
if (!existsSync(cmdLine.sourcepath) || !statSync(cmdLine.sourcepath).isDirectory()) {
552-
console.error(`Directory ${cmdLine.sourcepath} not exists or not a directory`);
559+
if (!existsSync(cmdLine.sourcepath)) {
560+
console.error(getSourcepathNotFoundError(cmdLine.sourcepath, 'not_found'));
561+
process.exit(1);
562+
}
563+
564+
if (!statSync(cmdLine.sourcepath).isDirectory()) {
565+
console.error(getSourcepathNotFoundError(cmdLine.sourcepath, 'not_directory'));
553566
process.exit(1);
554567
}
555568

src/errorMessages.ts

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import path from 'node:path';
2+
3+
import { cyanLog, redLog, yellowLog } from './consoleColor';
4+
5+
/**
6+
* Get human-readable engine name
7+
*/
8+
function getEngineName(engine: string): string {
9+
const names: Record<string, string> = {
10+
psychic: 'PsychicHttpServer',
11+
psychic2: 'PsychicHttpServer V2',
12+
async: 'ESPAsyncWebServer',
13+
espidf: 'ESP-IDF'
14+
};
15+
return names[engine] ?? engine;
16+
}
17+
18+
/**
19+
* Error: Missing index.html or index.htm
20+
*/
21+
export function getMissingIndexError(engine: string): string {
22+
const hints: Record<string, string> = {
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+
27+
psychic2: ` 1. Add an index.html file to your source directory
28+
2. The file will automatically be set as the default route ("/")
29+
3. PsychicHttpServer V2 uses: server->defaultEndpoint = ...`,
30+
31+
async: ` 1. Add an index.html file to your source directory
32+
2. The file will automatically create a "/" route handler
33+
3. ESPAsyncWebServer uses: server.on("/", HTTP_GET, ...)`,
34+
35+
espidf: ` 1. Add an index.html file to your source directory
36+
2. The file will register both "/" and "/index.html" routes
37+
3. ESP-IDF uses: httpd_register_uri_handler(server, &route_def_...)`
38+
};
39+
40+
const hint = hints[engine] ?? hints['psychic'];
41+
42+
return (
43+
redLog('[ERROR] No index.html or index.htm found in source files') +
44+
`
45+
46+
Why this matters:
47+
Web applications typically need a default entry point. Without index.html,
48+
users visiting http://your-esp32/ will get a 404 error.
49+
50+
How to fix (for ${getEngineName(engine)}):
51+
${hint}
52+
53+
Alternative:
54+
If you use a different entry point (e.g., main.html), you can add --no-index-check flag,
55+
but users must navigate to http://your-esp32/main.html explicitly.`
56+
);
57+
}
58+
59+
/**
60+
* Error: Invalid engine specified
61+
*/
62+
export function getInvalidEngineError(attempted: string): string {
63+
return (
64+
redLog(`[ERROR] Invalid engine: '${attempted}'`) +
65+
`
66+
67+
Valid engines are:
68+
${cyanLog('• psychic')} - PsychicHttpServer (ESP32 only, fastest performance)
69+
${cyanLog('• psychic2')} - PsychicHttpServer V2 (ESP32 only, modern API)
70+
${cyanLog('• async')} - ESPAsyncWebServer (ESP32/ESP8266 compatible)
71+
${cyanLog('• espidf')} - Native ESP-IDF web server (ESP32 only, no Arduino)
72+
73+
How to fix:
74+
npx svelteesp32 --engine=psychic --sourcepath=./dist
75+
76+
Example RC file (.svelteesp32rc.json):
77+
{
78+
"engine": "psychic",
79+
"sourcepath": "./dist"
80+
}
81+
82+
Documentation: https://github.com/hpieroni/svelteesp32#readme`
83+
);
84+
}
85+
86+
/**
87+
* Error: Source path not found or not a directory
88+
*/
89+
export function getSourcepathNotFoundError(sourcepath: string, reason: 'not_found' | 'not_directory'): string {
90+
if (reason === 'not_directory')
91+
return (
92+
redLog(`[ERROR] Source path is not a directory: '${sourcepath}'`) +
93+
`
94+
95+
The --sourcepath option must point to a directory containing your built web files,
96+
not an individual file.
97+
98+
How to fix:
99+
npx svelteesp32 --sourcepath=./dist --engine=psychic`
100+
);
101+
102+
const resolvedPath = path.resolve(sourcepath);
103+
const currentDirectory = process.cwd();
104+
105+
return (
106+
redLog(`[ERROR] Source directory not found: '${sourcepath}'`) +
107+
`
108+
109+
Why this matters:
110+
SvelteESP32 needs your compiled web assets (HTML, CSS, JS) to convert them
111+
into C++ header files for the ESP32.
112+
113+
How to fix:
114+
1. Build your frontend application first:
115+
• Svelte: npm run build
116+
• React: npm run build
117+
• Vue: npm run build
118+
• Angular: ng build
119+
120+
2. Verify the build output directory exists:
121+
${cyanLog(`ls -la ${sourcepath}`)}
122+
123+
3. Check your build tool configuration:
124+
• Vite: vite.config.js → build.outDir
125+
• Webpack: webpack.config.js → output.path
126+
• Rollup: rollup.config.js → output.dir
127+
128+
4. Update your svelteesp32 command to match:
129+
${cyanLog(`npx svelteesp32 --sourcepath=./build --engine=psychic`)}
130+
131+
Current directory: ${currentDirectory}
132+
Attempted path: ${resolvedPath} (resolved)`
133+
);
134+
}
135+
136+
/**
137+
* Hint: max_uri_handlers configuration (console output, not an error)
138+
*/
139+
export function getMaxUriHandlersHint(engine: string, routeCount: number): string {
140+
const recommended = routeCount + 5;
141+
142+
const hints: Record<string, string> = {
143+
psychic: `PsychicHttpServer server;
144+
server.config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
145+
initSvelteStaticFiles(&server);
146+
server.listen(80);`,
147+
148+
psychic2: `PsychicHttpServer server;
149+
server.config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
150+
initSvelteStaticFiles(&server);
151+
server.listen(80);`,
152+
153+
espidf: `httpd_config_t config = HTTPD_DEFAULT_CONFIG();
154+
config.max_uri_handlers = ${recommended}; // Default is 8, you need at least ${routeCount}
155+
httpd_handle_t server = NULL;
156+
httpd_start(&server, &config);
157+
initSvelteStaticFiles(server);`
158+
};
159+
160+
const hint = hints[engine];
161+
if (!hint) return ''; // No hint for async engine (no limit)
162+
163+
return (
164+
yellowLog('[CONFIG TIP] max_uri_handlers configuration') +
165+
`
166+
167+
Your generated code includes ${routeCount} routes. Make sure your server can handle them:
168+
169+
For ${getEngineName(engine)}:
170+
${hint}
171+
172+
Recommended formula: max_uri_handlers = file_count + 5 (safety margin)
173+
174+
Runtime symptoms if too low:
175+
• Routes not registered (HTTP 404 errors)
176+
• ESP_ERR_HTTPD_HANDLERS_FULL error in logs
177+
• Some files load, others don't (random behavior)`
178+
);
179+
}

src/file.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { globSync } from 'tinyglobby';
77

88
import { cmdLine } from './commandLine';
99
import { cyanLog, redLog, yellowLog } from './consoleColor';
10+
import { getMissingIndexError } from './errorMessages';
1011

1112
/**
1213
* Find files with identical content based on SHA256 hash
@@ -113,5 +114,15 @@ export const getFiles = (): Map<string, Buffer> => {
113114
const duplicates = findSimilarFiles(result);
114115
for (const sameFiles of duplicates) console.log(yellowLog(` ${sameFiles.join(', ')} files look like identical`));
115116

117+
// Check for index.html or index.htm (in root or subdirectories)
118+
const hasIndex = [...result.keys()].some(
119+
(f) => f === 'index.html' || f === 'index.htm' || f.endsWith('/index.html') || f.endsWith('/index.htm')
120+
);
121+
122+
if (!hasIndex && !cmdLine.noIndexCheck) {
123+
console.error('\n' + getMissingIndexError(cmdLine.engine));
124+
process.exit(1);
125+
}
126+
116127
return result;
117128
};

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { lookup as mimeLookup } from 'mime-types';
99
import { cmdLine } from './commandLine';
1010
import { greenLog, yellowLog } from './consoleColor';
1111
import { CppCodeSource, CppCodeSources, ExtensionGroups, getCppCode } from './cppCode';
12+
import { getMaxUriHandlersHint } from './errorMessages';
1213
import { getFiles } from './file';
1314

1415
// Compression thresholds
@@ -145,3 +146,7 @@ console.log(
145146
);
146147

147148
console.log(`${cmdLine.outputfile} ${Math.round(cppFile.length / 1024)}kB size`);
149+
150+
// Show max_uri_handlers hint for applicable engines
151+
if (cmdLine.engine === 'psychic' || cmdLine.engine === 'psychic2' || cmdLine.engine === 'espidf')
152+
console.log('\n' + getMaxUriHandlersHint(cmdLine.engine, sources.length));

0 commit comments

Comments
 (0)