svelteesp32 1.14.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.
@@ -10,6 +10,9 @@ interface ICopyFilesArguments {
10
10
  created: boolean;
11
11
  version: string;
12
12
  exclude: string[];
13
+ basePath: string;
14
+ maxSize?: number;
15
+ maxGzipSize?: number;
13
16
  noIndexCheck?: boolean;
14
17
  help?: boolean;
15
18
  }
@@ -25,10 +28,14 @@ interface IRcFileConfig {
25
28
  created?: boolean;
26
29
  version?: string;
27
30
  exclude?: string[];
31
+ basePath?: string;
32
+ maxSize?: number | string;
33
+ maxGzipSize?: number | string;
28
34
  }
35
+ declare function parseSize(value: string, name: string): number;
29
36
  declare function getNpmPackageVariable(packageJson: Record<string, unknown>, variableName: string): string | undefined;
30
37
  declare function hasNpmVariables(config: IRcFileConfig): boolean;
31
38
  declare function interpolateNpmVariables(config: IRcFileConfig, rcFilePath: string): IRcFileConfig;
32
- export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables };
39
+ export { getNpmPackageVariable, hasNpmVariables, interpolateNpmVariables, parseSize };
33
40
  export declare function formatConfiguration(cmdLine: ICopyFilesArguments): string;
34
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,6 +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"
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)
37
41
  -h, --help Shows this help
38
42
 
39
43
  RC File:
@@ -66,6 +70,36 @@ function validateTriState(value, name) {
66
70
  return value;
67
71
  throw new Error(`Invalid ${name}: ${value}`);
68
72
  }
73
+ function validateBasePath(value) {
74
+ if (value === '')
75
+ return value;
76
+ if (!value.startsWith('/'))
77
+ throw new Error(`basePath must start with /: ${value}`);
78
+ if (value.endsWith('/'))
79
+ throw new Error(`basePath must not end with /: ${value}`);
80
+ if (value.includes('//'))
81
+ throw new Error(`basePath must not contain //: ${value}`);
82
+ return value;
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
+ }
69
103
  const DEFAULT_EXCLUDE_PATTERNS = [
70
104
  '.DS_Store',
71
105
  'Thumbs.db',
@@ -136,6 +170,7 @@ function hasNpmVariables(config) {
136
170
  checkStringForNpmVariable(config.espmethod) ||
137
171
  checkStringForNpmVariable(config.define) ||
138
172
  checkStringForNpmVariable(config.version) ||
173
+ checkStringForNpmVariable(config.basePath) ||
139
174
  (Array.isArray(config.exclude) && config.exclude.some((pattern) => checkStringForNpmVariable(pattern))) ||
140
175
  false);
141
176
  }
@@ -155,6 +190,8 @@ function interpolateNpmVariables(config, rcFilePath) {
155
190
  affectedFields.push('espmethod');
156
191
  if (config.define?.includes('$npm_package_'))
157
192
  affectedFields.push('define');
193
+ if (config.basePath?.includes('$npm_package_'))
194
+ affectedFields.push('basePath');
158
195
  if (config.exclude)
159
196
  for (const [index, pattern] of config.exclude.entries())
160
197
  if (pattern.includes('$npm_package_'))
@@ -179,6 +216,8 @@ function interpolateNpmVariables(config, rcFilePath) {
179
216
  result.define = interpolateString(result.define);
180
217
  if (result.version)
181
218
  result.version = interpolateString(result.version);
219
+ if (result.basePath)
220
+ result.basePath = interpolateString(result.basePath);
182
221
  if (result.exclude)
183
222
  result.exclude = result.exclude.map((pattern) => interpolateString(pattern));
184
223
  return result;
@@ -211,7 +250,10 @@ function validateRcConfig(config, rcPath) {
211
250
  'cachetime',
212
251
  'created',
213
252
  'version',
214
- 'exclude'
253
+ 'exclude',
254
+ 'basePath',
255
+ 'maxSize',
256
+ 'maxGzipSize'
215
257
  ]);
216
258
  for (const key of Object.keys(configObject))
217
259
  if (!validKeys.has(key))
@@ -232,6 +274,30 @@ function validateRcConfig(config, rcPath) {
232
274
  if (typeof pattern !== 'string')
233
275
  throw new TypeError('All exclude patterns must be strings');
234
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
+ }
235
301
  return configObject;
236
302
  }
237
303
  function parseArguments() {
@@ -264,7 +330,8 @@ function parseArguments() {
264
330
  espmethod: 'initSvelteStaticFiles',
265
331
  define: 'SVELTEESP32',
266
332
  cachetime: 0,
267
- exclude: [...DEFAULT_EXCLUDE_PATTERNS]
333
+ exclude: [...DEFAULT_EXCLUDE_PATTERNS],
334
+ basePath: ''
268
335
  };
269
336
  if (rcConfig.engine)
270
337
  result.engine = rcConfig.engine;
@@ -286,6 +353,12 @@ function parseArguments() {
286
353
  result.espmethod = rcConfig.espmethod;
287
354
  if (rcConfig.define)
288
355
  result.define = rcConfig.define;
356
+ if (rcConfig.basePath !== undefined)
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;
289
362
  if (rcConfig.exclude && rcConfig.exclude.length > 0)
290
363
  result.exclude = [...rcConfig.exclude];
291
364
  const cliExclude = [];
@@ -342,6 +415,15 @@ function parseArguments() {
342
415
  cliExclude.push(...patterns);
343
416
  break;
344
417
  }
418
+ case 'basepath':
419
+ result.basePath = validateBasePath(value);
420
+ break;
421
+ case 'maxsize':
422
+ result.maxSize = parseSize(value, '--maxsize');
423
+ break;
424
+ case 'maxgzipsize':
425
+ result.maxGzipSize = parseSize(value, '--maxgzipsize');
426
+ break;
345
427
  default:
346
428
  throw new Error(`Unknown flag: ${flag}`);
347
429
  }
@@ -351,7 +433,7 @@ function parseArguments() {
351
433
  result.created = true;
352
434
  continue;
353
435
  }
354
- if (argument === '--no-index-check') {
436
+ if (argument === '--noindexcheck') {
355
437
  result.noIndexCheck = true;
356
438
  continue;
357
439
  }
@@ -434,6 +516,18 @@ function parseArguments() {
434
516
  index++;
435
517
  break;
436
518
  }
519
+ case 'basepath':
520
+ result.basePath = validateBasePath(nextArgument);
521
+ index++;
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;
437
531
  default:
438
532
  throw new Error(`Unknown flag: --${flag}`);
439
533
  }
@@ -466,6 +560,12 @@ function formatConfiguration(cmdLine) {
466
560
  parts.push(`espmethod=${cmdLine.espmethod}`);
467
561
  if (cmdLine.define)
468
562
  parts.push(`define=${cmdLine.define}`);
563
+ if (cmdLine.basePath)
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}`);
469
569
  if (cmdLine.exclude.length > 0)
470
570
  parts.push(`exclude=[${cmdLine.exclude.join(', ')}]`);
471
571
  return parts.join(' ');
package/dist/cppCode.js CHANGED
@@ -108,11 +108,15 @@ struct {{definePrefix}}_FileInfo {
108
108
  // File manifest array
109
109
  const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {
110
110
  {{#each sources}}
111
- { "/{{this.filename}}", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, "{{this.mime}}" },
111
+ { "{{../basePath}}/{{this.filename}}", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, "{{this.mime}}" },
112
112
  {{/each}}
113
113
  };
114
114
  const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);
115
115
  `;
116
+ const hookSection = `
117
+ // File served hook - override with your own implementation
118
+ extern "C" void __attribute__((weak)) {{definePrefix}}_onFileServed(const char* path, int statusCode) {}
119
+ `;
116
120
  const psychicTemplate = `
117
121
  //engine: PsychicHttpServer
118
122
  //config: {{{config}}}
@@ -137,19 +141,23 @@ ${etagArraysSection}
137
141
  //
138
142
  ${manifestSection}
139
143
 
144
+ //
145
+ ${hookSection}
146
+
140
147
  //
141
148
  // Http Handlers
142
149
  void {{methodName}}(PsychicHttpServer * server) {
143
150
  {{#each sources}}
144
151
  //
145
152
  // {{this.filename}}
146
- {{#if this.isDefault}}server->defaultEndpoint = {{/if}}server->on("/{{this.filename}}", HTTP_GET, [](PsychicRequest * request) {
153
+ {{#if this.isDefault}}{{#unless ../basePath}}server->defaultEndpoint = {{/unless}}{{/if}}server->on("{{../basePath}}/{{this.filename}}", HTTP_GET, [](PsychicRequest * request) {
147
154
 
148
155
  {{#switch ../etag}}
149
156
  {{#case "true"}}
150
157
  if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
151
158
  PsychicResponse response304(request);
152
159
  response304.setCode(304);
160
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
153
161
  return response304.send();
154
162
  }
155
163
  {{/case}}
@@ -158,6 +166,7 @@ void {{methodName}}(PsychicHttpServer * server) {
158
166
  if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
159
167
  PsychicResponse response304(request);
160
168
  response304.setCode(304);
169
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
161
170
  return response304.send();
162
171
  }
163
172
  #endif
@@ -177,7 +186,7 @@ void {{methodName}}(PsychicHttpServer * server) {
177
186
  {{#if this.isGzip}}
178
187
  #ifdef {{../definePrefix}}_ENABLE_GZIP
179
188
  response.addHeader("Content-Encoding", "gzip");
180
- #endif
189
+ #endif
181
190
  {{/if}}
182
191
  {{/case}}
183
192
  {{/switch}}
@@ -201,7 +210,93 @@ void {{methodName}}(PsychicHttpServer * server) {
201
210
  response.addHeader("Cache-Control", "no-cache");
202
211
  {{/../cacheTime}}
203
212
  response.addHeader("ETag", etag_{{this.dataname}});
204
- #endif
213
+ #endif
214
+ {{/case}}
215
+ {{/switch}}
216
+
217
+ {{#switch ../gzip}}
218
+ {{#case "true"}}
219
+ response.setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
220
+ {{/case}}
221
+ {{#case "false"}}
222
+ response.setContent(data_{{this.dataname}}, {{this.length}});
223
+ {{/case}}
224
+ {{#case "compiler"}}
225
+ #ifdef {{../definePrefix}}_ENABLE_GZIP
226
+ response.setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
227
+ #else
228
+ response.setContent(data_{{this.dataname}}, {{this.length}});
229
+ #endif
230
+ {{/case}}
231
+ {{/switch}}
232
+
233
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 200);
234
+ return response.send();
235
+ });
236
+ {{#if this.isDefault}}{{#if ../basePath}}
237
+ //
238
+ // {{this.filename}} (base path route)
239
+ server->on("{{../basePath}}", HTTP_GET, [](PsychicRequest * request) {
240
+
241
+ {{#switch ../etag}}
242
+ {{#case "true"}}
243
+ if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
244
+ PsychicResponse response304(request);
245
+ response304.setCode(304);
246
+ {{../definePrefix}}_onFileServed("{{../basePath}}", 304);
247
+ return response304.send();
248
+ }
249
+ {{/case}}
250
+ {{#case "compiler"}}
251
+ #ifdef {{../definePrefix}}_ENABLE_ETAG
252
+ if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
253
+ PsychicResponse response304(request);
254
+ response304.setCode(304);
255
+ {{../definePrefix}}_onFileServed("{{../basePath}}", 304);
256
+ return response304.send();
257
+ }
258
+ #endif
259
+ {{/case}}
260
+ {{/switch}}
261
+
262
+ PsychicResponse response(request);
263
+ response.setContentType("{{this.mime}}");
264
+
265
+ {{#switch ../gzip}}
266
+ {{#case "true"}}
267
+ {{#if this.isGzip}}
268
+ response.addHeader("Content-Encoding", "gzip");
269
+ {{/if}}
270
+ {{/case}}
271
+ {{#case "compiler"}}
272
+ {{#if this.isGzip}}
273
+ #ifdef {{../definePrefix}}_ENABLE_GZIP
274
+ response.addHeader("Content-Encoding", "gzip");
275
+ #endif
276
+ {{/if}}
277
+ {{/case}}
278
+ {{/switch}}
279
+
280
+ {{#switch ../etag}}
281
+ {{#case "true"}}
282
+ {{#../cacheTime}}
283
+ response.addHeader("Cache-Control", "max-age={{value}}");
284
+ {{/../cacheTime}}
285
+ {{^../cacheTime}}
286
+ response.addHeader("Cache-Control", "no-cache");
287
+ {{/../cacheTime}}
288
+ response.addHeader("ETag", etag_{{this.dataname}});
289
+ {{/case}}
290
+ {{#case "compiler"}}
291
+ #ifdef {{../definePrefix}}_ENABLE_ETAG
292
+ {{#../cacheTime}}
293
+ response.addHeader("Cache-Control", "max-age={{value}}");
294
+ {{/../cacheTime}}
295
+ {{^../cacheTime}}
296
+ response.addHeader("Cache-Control", "no-cache");
297
+ {{/../cacheTime}}
298
+ response.addHeader("ETag", etag_{{this.dataname}});
299
+ #endif
205
300
  {{/case}}
206
301
  {{/switch}}
207
302
 
@@ -217,12 +312,14 @@ void {{methodName}}(PsychicHttpServer * server) {
217
312
  response.setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
218
313
  #else
219
314
  response.setContent(data_{{this.dataname}}, {{this.length}});
220
- #endif
315
+ #endif
221
316
  {{/case}}
222
317
  {{/switch}}
223
318
 
319
+ {{../definePrefix}}_onFileServed("{{../basePath}}", 200);
224
320
  return response.send();
225
321
  });
322
+ {{/if}}{{/if}}
226
323
 
227
324
  {{/each}}
228
325
  }`;
@@ -249,18 +346,22 @@ ${etagArraysSection}
249
346
  //
250
347
  ${manifestSection}
251
348
 
349
+ //
350
+ ${hookSection}
351
+
252
352
  //
253
353
  // Http Handlers
254
354
  void {{methodName}}(PsychicHttpServer * server) {
255
355
  {{#each sources}}
256
356
  //
257
357
  // {{this.filename}}
258
- {{#if this.isDefault}}server->defaultEndpoint = {{/if}}server->on("/{{this.filename}}", HTTP_GET, [](PsychicRequest * request, PsychicResponse * response) {
358
+ {{#if this.isDefault}}{{#unless ../basePath}}server->defaultEndpoint = {{/unless}}{{/if}}server->on("{{../basePath}}/{{this.filename}}", HTTP_GET, [](PsychicRequest * request, PsychicResponse * response) {
259
359
 
260
360
  {{#switch ../etag}}
261
361
  {{#case "true"}}
262
362
  if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
263
363
  response->setCode(304);
364
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
264
365
  return response->send();
265
366
  }
266
367
  {{/case}}
@@ -268,6 +369,7 @@ void {{methodName}}(PsychicHttpServer * server) {
268
369
  #ifdef {{../definePrefix}}_ENABLE_ETAG
269
370
  if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
270
371
  response->setCode(304);
372
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
271
373
  return response->send();
272
374
  }
273
375
  #endif
@@ -286,7 +388,7 @@ void {{methodName}}(PsychicHttpServer * server) {
286
388
  {{#if this.isGzip}}
287
389
  #ifdef {{../definePrefix}}_ENABLE_GZIP
288
390
  response->addHeader("Content-Encoding", "gzip");
289
- #endif
391
+ #endif
290
392
  {{/if}}
291
393
  {{/case}}
292
394
  {{/switch}}
@@ -310,7 +412,7 @@ void {{methodName}}(PsychicHttpServer * server) {
310
412
  response->addHeader("Cache-Control", "no-cache");
311
413
  {{/../cacheTime}}
312
414
  response->addHeader("ETag", etag_{{this.dataname}});
313
- #endif
415
+ #endif
314
416
  {{/case}}
315
417
  {{/switch}}
316
418
 
@@ -326,12 +428,97 @@ void {{methodName}}(PsychicHttpServer * server) {
326
428
  response->setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
327
429
  #else
328
430
  response->setContent(data_{{this.dataname}}, {{this.length}});
329
- #endif
431
+ #endif
330
432
  {{/case}}
331
433
  {{/switch}}
332
434
 
435
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 200);
333
436
  return response->send();
334
437
  });
438
+ {{#if this.isDefault}}{{#if ../basePath}}
439
+ //
440
+ // {{this.filename}} (base path route)
441
+ server->on("{{../basePath}}", HTTP_GET, [](PsychicRequest * request, PsychicResponse * response) {
442
+
443
+ {{#switch ../etag}}
444
+ {{#case "true"}}
445
+ if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
446
+ response->setCode(304);
447
+ {{../definePrefix}}_onFileServed("{{../basePath}}", 304);
448
+ return response->send();
449
+ }
450
+ {{/case}}
451
+ {{#case "compiler"}}
452
+ #ifdef {{../definePrefix}}_ENABLE_ETAG
453
+ if (request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag_{{this.dataname}})) {
454
+ response->setCode(304);
455
+ {{../definePrefix}}_onFileServed("{{../basePath}}", 304);
456
+ return response->send();
457
+ }
458
+ #endif
459
+ {{/case}}
460
+ {{/switch}}
461
+
462
+ response->setContentType("{{this.mime}}");
463
+
464
+ {{#switch ../gzip}}
465
+ {{#case "true"}}
466
+ {{#if this.isGzip}}
467
+ response->addHeader("Content-Encoding", "gzip");
468
+ {{/if}}
469
+ {{/case}}
470
+ {{#case "compiler"}}
471
+ {{#if this.isGzip}}
472
+ #ifdef {{../definePrefix}}_ENABLE_GZIP
473
+ response->addHeader("Content-Encoding", "gzip");
474
+ #endif
475
+ {{/if}}
476
+ {{/case}}
477
+ {{/switch}}
478
+
479
+ {{#switch ../etag}}
480
+ {{#case "true"}}
481
+ {{#../cacheTime}}
482
+ response->addHeader("Cache-Control", "max-age={{value}}");
483
+ {{/../cacheTime}}
484
+ {{^../cacheTime}}
485
+ response->addHeader("Cache-Control", "no-cache");
486
+ {{/../cacheTime}}
487
+ response->addHeader("ETag", etag_{{this.dataname}});
488
+ {{/case}}
489
+ {{#case "compiler"}}
490
+ #ifdef {{../definePrefix}}_ENABLE_ETAG
491
+ {{#../cacheTime}}
492
+ response->addHeader("Cache-Control", "max-age={{value}}");
493
+ {{/../cacheTime}}
494
+ {{^../cacheTime}}
495
+ response->addHeader("Cache-Control", "no-cache");
496
+ {{/../cacheTime}}
497
+ response->addHeader("ETag", etag_{{this.dataname}});
498
+ #endif
499
+ {{/case}}
500
+ {{/switch}}
501
+
502
+ {{#switch ../gzip}}
503
+ {{#case "true"}}
504
+ response->setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
505
+ {{/case}}
506
+ {{#case "false"}}
507
+ response->setContent(data_{{this.dataname}}, {{this.length}});
508
+ {{/case}}
509
+ {{#case "compiler"}}
510
+ #ifdef {{../definePrefix}}_ENABLE_GZIP
511
+ response->setContent(datagzip_{{this.dataname}}, {{this.lengthGzip}});
512
+ #else
513
+ response->setContent(data_{{this.dataname}}, {{this.length}});
514
+ #endif
515
+ {{/case}}
516
+ {{/switch}}
517
+
518
+ {{../definePrefix}}_onFileServed("{{../basePath}}", 200);
519
+ return response->send();
520
+ });
521
+ {{/if}}{{/if}}
335
522
 
336
523
  {{/each}}
337
524
  }`;
@@ -357,18 +544,22 @@ ${etagArraysSection}
357
544
  //
358
545
  ${manifestSection}
359
546
 
547
+ //
548
+ ${hookSection}
549
+
360
550
  //
361
551
  // Http Handlers
362
552
  void {{methodName}}(AsyncWebServer * server) {
363
553
  {{#each sources}}
364
554
  //
365
555
  // {{this.filename}}
366
- server->on("/{{this.filename}}", HTTP_GET, [](AsyncWebServerRequest * request) {
556
+ server->on("{{../basePath}}/{{this.filename}}", HTTP_GET, [](AsyncWebServerRequest * request) {
367
557
 
368
558
  {{#switch ../etag}}
369
559
  {{#case "true"}}
370
560
  const AsyncWebHeader* h = request->getHeader("If-None-Match");
371
561
  if (h && h->value().equals(etag_{{this.dataname}})) {
562
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
372
563
  request->send(304);
373
564
  return;
374
565
  }
@@ -377,6 +568,7 @@ void {{methodName}}(AsyncWebServer * server) {
377
568
  #ifdef {{../definePrefix}}_ENABLE_ETAG
378
569
  const AsyncWebHeader* h = request->getHeader("If-None-Match");
379
570
  if (h && h->value().equals(etag_{{this.dataname}})) {
571
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 304);
380
572
  request->send(304);
381
573
  return;
382
574
  }
@@ -429,15 +621,17 @@ void {{methodName}}(AsyncWebServer * server) {
429
621
  {{/case}}
430
622
  {{/switch}}
431
623
 
624
+ {{../definePrefix}}_onFileServed("{{../basePath}}/{{this.filename}}", 200);
432
625
  request->send(response);
433
626
  });
434
627
  {{#if this.isDefault}}
435
- server->on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
628
+ server->on("{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}", HTTP_GET, [](AsyncWebServerRequest * request) {
436
629
 
437
630
  {{#switch ../etag}}
438
631
  {{#case "true"}}
439
632
  const AsyncWebHeader* h = request->getHeader("If-None-Match");
440
633
  if (h && h->value().equals(etag_{{this.dataname}})) {
634
+ {{../definePrefix}}_onFileServed("{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}", 304);
441
635
  request->send(304);
442
636
  return;
443
637
  }
@@ -446,6 +640,7 @@ void {{methodName}}(AsyncWebServer * server) {
446
640
  #ifdef {{../definePrefix}}_ENABLE_ETAG
447
641
  const AsyncWebHeader* h = request->getHeader("If-None-Match");
448
642
  if (h && h->value().equals(etag_{{this.dataname}})) {
643
+ {{../definePrefix}}_onFileServed("{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}", 304);
449
644
  request->send(304);
450
645
  return;
451
646
  }
@@ -498,6 +693,7 @@ void {{methodName}}(AsyncWebServer * server) {
498
693
  {{/case}}
499
694
  {{/switch}}
500
695
 
696
+ {{../definePrefix}}_onFileServed("{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}", 200);
501
697
  request->send(response);
502
698
  });
503
699
  {{/if}}
@@ -568,7 +764,8 @@ const getCppCode = (sources, filesByExtension) => {
568
764
  version: commandLine_1.cmdLine.version,
569
765
  methodName: commandLine_1.cmdLine.espmethod,
570
766
  cacheTime: commandLine_1.cmdLine.cachetime ? { value: commandLine_1.cmdLine.cachetime } : undefined,
571
- definePrefix: commandLine_1.cmdLine.define
767
+ definePrefix: commandLine_1.cmdLine.define,
768
+ basePath: commandLine_1.cmdLine.basePath
572
769
  };
573
770
  const rawCode = template(templateData, { helpers: createHandlebarsHelpers() });
574
771
  return postProcessCppCode(rawCode);
@@ -1 +1 @@
1
- export declare const espidfTemplate = "\n//engine: espidf\n//config: {{{config}}}\n{{#if created }}\n//created: {{now}}\n{{/if}}\n//\n\n{{#switch etag}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n{{#switch gzip}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n//\n{{#if version }}\n#define {{definePrefix}}_VERSION \"{{version}}\"\n{{/if}}\n#define {{definePrefix}}_COUNT {{fileCount}}\n#define {{definePrefix}}_SIZE {{fileSize}}\n#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}\n\n//\n{{#each sources}}\n#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}\n{{/each}}\n\n//\n{{#each filesByExtension}}\n#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}\n{{/each}}\n\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <esp_err.h>\n#include <esp_http_server.h>\n\n//\n{{#switch gzip}}\n{{#case \"true\"}}\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n#else\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n#endif \n{{/case}}\n{{/switch}}\n\n//\n{{#switch etag}}\n{{#case \"true\"}}\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n#endif\n{{/case}}\n{{/switch}}\n\n// File manifest struct (C-compatible typedef)\ntypedef struct {\n const char* path;\n uint32_t size;\n uint32_t gzipSize;\n const char* etag;\n const char* contentType;\n} {{definePrefix}}_FileInfo;\n\n// File manifest array\nstatic const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {\n{{#each sources}}\n { \"/{{this.filename}}\", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, \"{{this.mime}}\" },\n{{/each}}\n};\nstatic const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);\n\n{{#each sources}}\n\nstatic esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)\n{\n{{#switch ../etag}}\n{{#case \"true\"}}\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n #endif\n{{/case}}\n{{/switch}}\n httpd_resp_set_type(req, \"{{this.mime}}\");\n{{#switch ../gzip}}\n{{#case \"true\"}}\n{{#if this.isGzip}}\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n{{/if}}\n{{/case}}\n{{#case \"compiler\"}}\n {{#if this.isGzip}}\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n #endif \n {{/if}}\n{{/case}}\n{{/switch}}\n\n{{#switch ../etag}}\n{{#case \"true\"}}\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n #endif \n{{/case}}\n{{/switch}}\n\n{{#switch ../gzip}}\n{{#case \"true\"}}\n httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n{{/case}}\n{{#case \"false\"}}\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n #else\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n #endif \n{{/case}}\n{{/switch}}\n return ESP_OK;\n}\n\n{{#if this.isDefault}}\nstatic const httpd_uri_t route_def_{{this.datanameUpperCase}} = {\n .uri = \"/\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n{{/if}}\n\nstatic const httpd_uri_t route_{{this.datanameUpperCase}} = {\n .uri = \"/{{this.filename}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n\n{{/each}}\n\n\n\nstatic inline void {{methodName}}(httpd_handle_t server) {\n{{#each sources}}\n{{#if this.isDefault}}\n httpd_register_uri_handler(server, &route_def_{{this.datanameUpperCase}});\n{{/if}}\n httpd_register_uri_handler(server, &route_{{this.datanameUpperCase}});\n{{/each}}\n\n}";
1
+ export declare const espidfTemplate = "\n//engine: espidf\n//config: {{{config}}}\n{{#if created }}\n//created: {{now}}\n{{/if}}\n//\n\n{{#switch etag}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n#warning {{definePrefix}}_ENABLE_ETAG has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n{{#switch gzip}}\n{{#case \"true\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched ON\n#endif\n{{/case}}\n{{#case \"false\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n#warning {{definePrefix}}_ENABLE_GZIP has no effect because it is permanently switched OFF\n#endif\n{{/case}}\n{{/switch}}\n\n//\n{{#if version }}\n#define {{definePrefix}}_VERSION \"{{version}}\"\n{{/if}}\n#define {{definePrefix}}_COUNT {{fileCount}}\n#define {{definePrefix}}_SIZE {{fileSize}}\n#define {{definePrefix}}_SIZE_GZIP {{fileGzipSize}}\n\n//\n{{#each sources}}\n#define {{../definePrefix}}_FILE_{{this.datanameUpperCase}}\n{{/each}}\n\n//\n{{#each filesByExtension}}\n#define {{../definePrefix}}_{{this.extension}}_FILES {{this.count}}\n{{/each}}\n\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <esp_err.h>\n#include <esp_http_server.h>\n\n//\n{{#switch gzip}}\n{{#case \"true\"}}\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_GZIP\n {{#each sources}}\nconst char datagzip_{{this.dataname}}[{{this.lengthGzip}}] = { {{this.bytesGzip}} };\n {{/each}}\n#else\n {{#each sources}}\nconst char data_{{this.dataname}}[{{this.length}}] = { {{this.bytes}} };\n {{/each}}\n#endif \n{{/case}}\n{{/switch}}\n\n//\n{{#switch etag}}\n{{#case \"true\"}}\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n{{/case}}\n{{#case \"false\"}}\n{{/case}}\n{{#case \"compiler\"}}\n#ifdef {{definePrefix}}_ENABLE_ETAG\n {{#each sources}}\nstatic const char etag_{{this.dataname}}[] = \"{{this.sha256}}\";\n {{/each}}\n#endif\n{{/case}}\n{{/switch}}\n\n// File manifest struct (C-compatible typedef)\ntypedef struct {\n const char* path;\n uint32_t size;\n uint32_t gzipSize;\n const char* etag;\n const char* contentType;\n} {{definePrefix}}_FileInfo;\n\n// File manifest array\nstatic const {{definePrefix}}_FileInfo {{definePrefix}}_FILES[] = {\n{{#each sources}}\n { \"{{../basePath}}/{{this.filename}}\", {{this.length}}, {{this.gzipSizeForManifest}}, {{this.etagForManifest}}, \"{{this.mime}}\" },\n{{/each}}\n};\nstatic const size_t {{definePrefix}}_FILE_COUNT = sizeof({{definePrefix}}_FILES) / sizeof({{definePrefix}}_FILES[0]);\n\n// File served hook - override with your own implementation\n__attribute__((weak)) void {{definePrefix}}_onFileServed(const char* path, int statusCode) {}\n\n{{#each sources}}\n\nstatic esp_err_t file_handler_{{this.datanameUpperCase}} (httpd_req_t *req)\n{\n{{#switch ../etag}}\n{{#case \"true\"}}\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 304);\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n size_t hdr_len = httpd_req_get_hdr_value_len(req, \"If-None-Match\");\n if (hdr_len > 0) {\n char* hdr_value = malloc(hdr_len + 1);\n if (httpd_req_get_hdr_value_str(req, \"If-None-Match\", hdr_value, hdr_len + 1) == ESP_OK) {\n if (strcmp(hdr_value, etag_{{this.dataname}}) == 0) {\n free(hdr_value);\n httpd_resp_set_status(req, \"304 Not Modified\");\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 304);\n httpd_resp_send(req, NULL, 0);\n return ESP_OK;\n }\n }\n free(hdr_value);\n }\n #endif\n{{/case}}\n{{/switch}}\n httpd_resp_set_type(req, \"{{this.mime}}\");\n{{#switch ../gzip}}\n{{#case \"true\"}}\n{{#if this.isGzip}}\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n{{/if}}\n{{/case}}\n{{#case \"compiler\"}}\n {{#if this.isGzip}}\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_set_hdr(req, \"Content-Encoding\", \"gzip\");\n #endif \n {{/if}}\n{{/case}}\n{{/switch}}\n\n{{#switch ../etag}}\n{{#case \"true\"}}\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n{{/case}}\n{{#case \"compiler\"}}\n #ifdef {{../definePrefix}}_ENABLE_ETAG\n{{#../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"max-age={{value}}\");\n{{/../cacheTime}}\n{{^../cacheTime}}\n httpd_resp_set_hdr(req, \"Cache-Control\", \"no-cache\");\n{{/../cacheTime}}\n httpd_resp_set_hdr(req, \"ETag\", etag_{{this.dataname}});\n #endif \n{{/case}}\n{{/switch}}\n\n{{#switch ../gzip}}\n{{#case \"true\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n{{/case}}\n{{#case \"false\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n{{/case}}\n{{#case \"compiler\"}}\n {{../definePrefix}}_onFileServed(\"{{../basePath}}/{{this.filename}}\", 200);\n #ifdef {{../definePrefix}}_ENABLE_GZIP\n httpd_resp_send(req, datagzip_{{this.dataname}}, {{this.lengthGzip}});\n #else\n httpd_resp_send(req, data_{{this.dataname}}, {{this.length}});\n #endif\n{{/case}}\n{{/switch}}\n return ESP_OK;\n}\n\n{{#if this.isDefault}}\nstatic const httpd_uri_t route_def_{{this.datanameUpperCase}} = {\n .uri = \"{{#if ../basePath}}{{../basePath}}{{else}}/{{/if}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n{{/if}}\n\nstatic const httpd_uri_t route_{{this.datanameUpperCase}} = {\n .uri = \"{{../basePath}}/{{this.filename}}\",\n .method = HTTP_GET,\n .handler = file_handler_{{this.datanameUpperCase}},\n};\n\n{{/each}}\n\n\n\nstatic inline void {{methodName}}(httpd_handle_t server) {\n{{#each sources}}\n{{#if this.isDefault}}\n httpd_register_uri_handler(server, &route_def_{{this.datanameUpperCase}});\n{{/if}}\n httpd_register_uri_handler(server, &route_{{this.datanameUpperCase}});\n{{/each}}\n\n}";