@timber-js/app 0.2.0-alpha.4 → 0.2.0-alpha.5
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/dist/_chunks/debug-gwlJkDuf.js +108 -0
- package/dist/_chunks/debug-gwlJkDuf.js.map +1 -0
- package/dist/_chunks/{format-CwdaB0_2.js → format-DviM89f0.js} +2 -2
- package/dist/_chunks/{format-CwdaB0_2.js.map → format-DviM89f0.js.map} +1 -1
- package/dist/_chunks/{request-context-CZJi4CuK.js → request-context-DIkVh_jG.js} +2 -2
- package/dist/_chunks/{request-context-CZJi4CuK.js.map → request-context-DIkVh_jG.js.map} +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/fonts/local.d.ts +4 -2
- package/dist/fonts/local.d.ts.map +1 -1
- package/dist/index.js +190 -9
- package/dist/index.js.map +1 -1
- package/dist/plugins/entries.d.ts +7 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +2 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +6 -0
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/debug.d.ts +46 -15
- package/dist/server/debug.d.ts.map +1 -1
- package/dist/server/index.js +4 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/fonts/local.ts +7 -3
- package/src/plugins/entries.ts +7 -4
- package/src/plugins/fonts.ts +106 -5
- package/src/plugins/mdx.ts +9 -5
- package/src/server/action-client.ts +7 -4
- package/src/server/debug.ts +55 -17
- package/src/server/rsc-entry/index.ts +17 -6
- package/dist/_chunks/debug-B4WUeqJ-.js +0 -75
- package/dist/_chunks/debug-B4WUeqJ-.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAgFA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAgFA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;AAyZD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BA7P3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AA+PhD,wBAAiE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.5",
|
|
4
4
|
"description": "Vite-native React framework for Cloudflare Workers — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
package/src/fonts/local.ts
CHANGED
|
@@ -100,18 +100,22 @@ export function generateFamilyName(sources: LocalFontSrc[]): string {
|
|
|
100
100
|
* Generate @font-face descriptors for local font sources.
|
|
101
101
|
*
|
|
102
102
|
* Each source entry produces one @font-face rule. The `src` descriptor
|
|
103
|
-
* uses a `url()` pointing to the
|
|
103
|
+
* uses a `url()` pointing to the served path under `/_timber/fonts/`.
|
|
104
|
+
* The `urlPrefix` defaults to `/_timber/fonts` — the path used by both
|
|
105
|
+
* the dev server middleware and the production build output.
|
|
104
106
|
*/
|
|
105
107
|
export function generateLocalFontFaces(
|
|
106
108
|
family: string,
|
|
107
109
|
sources: LocalFontSrc[],
|
|
108
|
-
display: string
|
|
110
|
+
display: string,
|
|
111
|
+
urlPrefix = '/_timber/fonts'
|
|
109
112
|
): FontFaceDescriptor[] {
|
|
110
113
|
return sources.map((entry) => {
|
|
111
114
|
const format = inferFontFormat(entry.path);
|
|
115
|
+
const basename = entry.path.split('/').pop() ?? entry.path;
|
|
112
116
|
return {
|
|
113
117
|
family,
|
|
114
|
-
src: `url('${
|
|
118
|
+
src: `url('${urlPrefix}/${basename}') format('${format}')`,
|
|
115
119
|
weight: entry.weight,
|
|
116
120
|
style: entry.style,
|
|
117
121
|
display,
|
package/src/plugins/entries.ts
CHANGED
|
@@ -130,11 +130,14 @@ function generateConfigModule(ctx: PluginContext): string {
|
|
|
130
130
|
* Checks for instrumentation.ts, .js, and .mjs — matching the same
|
|
131
131
|
* extensions as timber.config.ts detection.
|
|
132
132
|
*/
|
|
133
|
-
function detectInstrumentationFile(root: string): string | null {
|
|
133
|
+
export function detectInstrumentationFile(root: string): string | null {
|
|
134
134
|
const extensions = ['.ts', '.js', '.mjs'];
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
const dirs = [root, resolve(root, 'src')];
|
|
136
|
+
for (const dir of dirs) {
|
|
137
|
+
for (const ext of extensions) {
|
|
138
|
+
const candidate = resolve(dir, `instrumentation${ext}`);
|
|
139
|
+
if (existsSync(candidate)) return candidate;
|
|
140
|
+
}
|
|
138
141
|
}
|
|
139
142
|
return null;
|
|
140
143
|
}
|
package/src/plugins/fonts.ts
CHANGED
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
* Design doc: 24-fonts.md
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { Plugin } from 'vite';
|
|
17
|
+
import type { Plugin, ViteDevServer } from 'vite';
|
|
18
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
19
|
+
import { resolve, normalize } from 'node:path';
|
|
18
20
|
import type { PluginContext } from '#/index.js';
|
|
19
21
|
import type { ExtractedFont, GoogleFontConfig } from '#/fonts/types.js';
|
|
20
22
|
import type { ManifestFontEntry } from '#/server/build-manifest.js';
|
|
21
|
-
import { generateVariableClass, generateFontFamilyClass } from '#/fonts/css.js';
|
|
23
|
+
import { generateVariableClass, generateFontFamilyClass, generateFontFaces } from '#/fonts/css.js';
|
|
22
24
|
import { generateFallbackCss, buildFontStack } from '#/fonts/fallbacks.js';
|
|
23
|
-
import { processLocalFont } from '#/fonts/local.js';
|
|
25
|
+
import { processLocalFont, generateLocalFontFaces } from '#/fonts/local.js';
|
|
24
26
|
import { inferFontFormat } from '#/fonts/local.js';
|
|
25
27
|
import { downloadAndCacheFonts, type CachedFont } from '#/fonts/google.js';
|
|
26
28
|
import {
|
|
@@ -31,8 +33,10 @@ import {
|
|
|
31
33
|
|
|
32
34
|
const VIRTUAL_GOOGLE = '@timber/fonts/google';
|
|
33
35
|
const VIRTUAL_LOCAL = '@timber/fonts/local';
|
|
36
|
+
const VIRTUAL_FONT_CSS = 'virtual:timber-fonts-css';
|
|
34
37
|
const RESOLVED_GOOGLE = '\0@timber/fonts/google';
|
|
35
38
|
const RESOLVED_LOCAL = '\0@timber/fonts/local';
|
|
39
|
+
const RESOLVED_FONT_CSS = '\0virtual:timber-fonts-css';
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
42
|
* Registry of fonts extracted during transform.
|
|
@@ -245,12 +249,20 @@ function generateLocalVirtualModule(): string {
|
|
|
245
249
|
/**
|
|
246
250
|
* Generate the CSS output for all extracted fonts.
|
|
247
251
|
*
|
|
248
|
-
* Includes @font-face rules, fallback @font-face rules,
|
|
252
|
+
* Includes @font-face rules for local fonts, fallback @font-face rules,
|
|
253
|
+
* and scoped classes.
|
|
249
254
|
*/
|
|
250
255
|
export function generateAllFontCss(registry: FontRegistry): string {
|
|
251
256
|
const cssParts: string[] = [];
|
|
252
257
|
|
|
253
258
|
for (const font of registry.values()) {
|
|
259
|
+
// Generate @font-face rules for local fonts
|
|
260
|
+
if (font.provider === 'local' && font.localSources) {
|
|
261
|
+
const faces = generateLocalFontFaces(font.family, font.localSources, font.display);
|
|
262
|
+
const faceCss = generateFontFaces(faces);
|
|
263
|
+
if (faceCss) cssParts.push(faceCss);
|
|
264
|
+
}
|
|
265
|
+
|
|
254
266
|
// Generate fallback @font-face if metrics are available
|
|
255
267
|
const fallbackCss = generateFallbackCss(font.family);
|
|
256
268
|
if (fallbackCss) cssParts.push(fallbackCss);
|
|
@@ -359,23 +371,83 @@ export function timberFonts(ctx: PluginContext): Plugin {
|
|
|
359
371
|
name: 'timber-fonts',
|
|
360
372
|
|
|
361
373
|
/**
|
|
362
|
-
* Resolve `@timber/fonts/google
|
|
374
|
+
* Resolve `@timber/fonts/google`, `@timber/fonts/local`, and the
|
|
375
|
+
* virtual font CSS module to internal IDs.
|
|
363
376
|
*/
|
|
364
377
|
resolveId(id: string) {
|
|
365
378
|
if (id === VIRTUAL_GOOGLE) return RESOLVED_GOOGLE;
|
|
366
379
|
if (id === VIRTUAL_LOCAL) return RESOLVED_LOCAL;
|
|
380
|
+
if (id === VIRTUAL_FONT_CSS) return RESOLVED_FONT_CSS;
|
|
367
381
|
return null;
|
|
368
382
|
},
|
|
369
383
|
|
|
370
384
|
/**
|
|
371
385
|
* Return generated source for font virtual modules.
|
|
386
|
+
*
|
|
387
|
+
* The font CSS virtual module returns the combined @font-face rules,
|
|
388
|
+
* fallback CSS, and scoped classes for all registered fonts.
|
|
372
389
|
*/
|
|
373
390
|
load(id: string) {
|
|
374
391
|
if (id === RESOLVED_GOOGLE) return generateGoogleVirtualModule(registry);
|
|
375
392
|
if (id === RESOLVED_LOCAL) return generateLocalVirtualModule();
|
|
393
|
+
if (id === RESOLVED_FONT_CSS) return generateAllFontCss(registry);
|
|
376
394
|
return null;
|
|
377
395
|
},
|
|
378
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Serve local font files in dev mode under `/_timber/fonts/`.
|
|
399
|
+
*
|
|
400
|
+
* Only files that are registered in the font registry (via localSources)
|
|
401
|
+
* are served. Paths are validated to prevent directory traversal.
|
|
402
|
+
*/
|
|
403
|
+
configureServer(server: ViteDevServer) {
|
|
404
|
+
server.middlewares.use((req, res, next) => {
|
|
405
|
+
const url = req.url;
|
|
406
|
+
if (!url || !url.startsWith('/_timber/fonts/')) return next();
|
|
407
|
+
|
|
408
|
+
const requestedFilename = url.slice('/_timber/fonts/'.length);
|
|
409
|
+
// Reject path traversal attempts
|
|
410
|
+
if (requestedFilename.includes('..') || requestedFilename.includes('/')) {
|
|
411
|
+
res.statusCode = 400;
|
|
412
|
+
res.end('Bad request');
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Find the matching font file in the registry
|
|
417
|
+
for (const font of registry.values()) {
|
|
418
|
+
if (font.provider !== 'local' || !font.localSources) continue;
|
|
419
|
+
for (const src of font.localSources) {
|
|
420
|
+
const basename = src.path.split('/').pop() ?? '';
|
|
421
|
+
if (basename === requestedFilename) {
|
|
422
|
+
const absolutePath = normalize(resolve(src.path));
|
|
423
|
+
// Verify the resolved path hasn't escaped (extra safety)
|
|
424
|
+
if (!existsSync(absolutePath)) {
|
|
425
|
+
res.statusCode = 404;
|
|
426
|
+
res.end('Not found');
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const data = readFileSync(absolutePath);
|
|
430
|
+
const ext = absolutePath.split('.').pop()?.toLowerCase();
|
|
431
|
+
const mimeMap: Record<string, string> = {
|
|
432
|
+
woff2: 'font/woff2',
|
|
433
|
+
woff: 'font/woff',
|
|
434
|
+
ttf: 'font/ttf',
|
|
435
|
+
otf: 'font/otf',
|
|
436
|
+
eot: 'application/vnd.ms-fontopen',
|
|
437
|
+
};
|
|
438
|
+
res.setHeader('Content-Type', mimeMap[ext ?? ''] ?? 'application/octet-stream');
|
|
439
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
|
440
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
441
|
+
res.end(data);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
next();
|
|
448
|
+
});
|
|
449
|
+
},
|
|
450
|
+
|
|
379
451
|
/**
|
|
380
452
|
* Download and cache Google Fonts during production builds.
|
|
381
453
|
*
|
|
@@ -525,6 +597,35 @@ export function timberFonts(ctx: PluginContext): Plugin {
|
|
|
525
597
|
});
|
|
526
598
|
}
|
|
527
599
|
|
|
600
|
+
// Emit local font files as assets
|
|
601
|
+
for (const font of registry.values()) {
|
|
602
|
+
if (font.provider !== 'local' || !font.localSources) continue;
|
|
603
|
+
for (const src of font.localSources) {
|
|
604
|
+
const absolutePath = normalize(resolve(src.path));
|
|
605
|
+
if (!existsSync(absolutePath)) {
|
|
606
|
+
this.warn(`Local font file not found: ${absolutePath}`);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const basename = src.path.split('/').pop() ?? src.path;
|
|
610
|
+
const data = readFileSync(absolutePath);
|
|
611
|
+
this.emitFile({
|
|
612
|
+
type: 'asset',
|
|
613
|
+
fileName: `_timber/fonts/${basename}`,
|
|
614
|
+
source: data,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Emit the combined font CSS as an asset
|
|
620
|
+
const fontCss = generateAllFontCss(registry);
|
|
621
|
+
if (fontCss) {
|
|
622
|
+
this.emitFile({
|
|
623
|
+
type: 'asset',
|
|
624
|
+
fileName: '_timber/fonts/fonts.css',
|
|
625
|
+
source: fontCss,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
528
629
|
if (!ctx.buildManifest) return;
|
|
529
630
|
|
|
530
631
|
// Build a lookup from font family → cached files for manifest entries
|
package/src/plugins/mdx.ts
CHANGED
|
@@ -16,19 +16,23 @@ import type { PluginContext } from '#/index.js';
|
|
|
16
16
|
const MDX_EXTENSIONS = ['mdx', 'md'];
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Check if mdx-components.tsx (or .ts, .jsx, .js) exists at the project root
|
|
19
|
+
* Check if mdx-components.tsx (or .ts, .jsx, .js) exists at the project root
|
|
20
|
+
* or in src/. Root takes precedence, matching Next.js behavior.
|
|
20
21
|
* Returns the absolute path if found, otherwise undefined.
|
|
21
22
|
*/
|
|
22
|
-
function findMdxComponents(root: string): string | undefined {
|
|
23
|
+
export function findMdxComponents(root: string): string | undefined {
|
|
23
24
|
const candidates = [
|
|
24
25
|
'mdx-components.tsx',
|
|
25
26
|
'mdx-components.ts',
|
|
26
27
|
'mdx-components.jsx',
|
|
27
28
|
'mdx-components.js',
|
|
28
29
|
];
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const dirs = [root, join(root, 'src')];
|
|
31
|
+
for (const dir of dirs) {
|
|
32
|
+
for (const name of candidates) {
|
|
33
|
+
const p = join(dir, name);
|
|
34
|
+
if (existsSync(p)) return p;
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
return undefined;
|
|
34
38
|
}
|
|
@@ -184,7 +184,7 @@ async function runActionMiddleware<TCtx>(
|
|
|
184
184
|
// Re-export parseFormData for use throughout the framework
|
|
185
185
|
import { parseFormData } from './form-data.js';
|
|
186
186
|
import { formatSize } from '#/utils/format.js';
|
|
187
|
-
import { isDebug } from './debug.js';
|
|
187
|
+
import { isDebug, isDevMode } from './debug.js';
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
190
|
* Extract validation errors from a schema error.
|
|
@@ -247,12 +247,15 @@ export function handleActionError(error: unknown): ActionResult<never> {
|
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
// In dev, include the message for debugging
|
|
251
|
-
|
|
250
|
+
// In dev, include the message for debugging.
|
|
251
|
+
// Uses isDevMode() — NOT isDebug() — because this data is sent to the
|
|
252
|
+
// browser. TIMBER_DEBUG must never cause error messages to leak to clients.
|
|
253
|
+
// See design/13-security.md principle 4: "Errors don't leak."
|
|
254
|
+
const devMode = isDevMode();
|
|
252
255
|
return {
|
|
253
256
|
serverError: {
|
|
254
257
|
code: 'INTERNAL_ERROR',
|
|
255
|
-
...(
|
|
258
|
+
...(devMode && error instanceof Error ? { data: { message: error.message } } : {}),
|
|
256
259
|
},
|
|
257
260
|
};
|
|
258
261
|
}
|
package/src/server/debug.ts
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime debug flag for timber.js.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* debug/warning logging should be active. This is true in two cases:
|
|
4
|
+
* Two distinct functions for two distinct security levels:
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
* (statically replaced and tree-shaken in production builds — zero cost)
|
|
6
|
+
* ## `isDebug()` — server-side logging only
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Returns true when timber's debug/warning messages should be written to
|
|
9
|
+
* stderr / the server console. This NEVER affects what is sent to the
|
|
10
|
+
* client (no error details, no timing headers, no stack traces).
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Active when any of:
|
|
13
|
+
* - `NODE_ENV !== 'production'` (standard dev mode)
|
|
14
|
+
* - `TIMBER_DEBUG` env var is set to a truthy value at runtime
|
|
15
|
+
* - `timber.config.ts` has `debug: true`
|
|
16
|
+
*
|
|
17
|
+
* ## `isDevMode()` — client-visible dev behavior
|
|
18
|
+
*
|
|
19
|
+
* Returns true ONLY when `NODE_ENV !== 'production'`. This gates anything
|
|
20
|
+
* that changes what clients can observe:
|
|
21
|
+
* - Dev error pages with stack traces (fallback-error.ts)
|
|
22
|
+
* - Detailed Server-Timing headers (pipeline.ts)
|
|
23
|
+
* - Error messages in action INTERNAL_ERROR payloads (action-client.ts)
|
|
24
|
+
* - Pipeline error handler wiring (Vite overlay)
|
|
25
|
+
*
|
|
26
|
+
* `isDevMode()` is statically replaced in production builds → the guarded
|
|
27
|
+
* code is tree-shaken to zero bytes. TIMBER_DEBUG cannot enable it.
|
|
16
28
|
*
|
|
17
29
|
* Usage:
|
|
18
30
|
* In Cloudflare Workers wrangler.toml:
|
|
@@ -25,10 +37,33 @@
|
|
|
25
37
|
* In timber.config.ts:
|
|
26
38
|
* export default { debug: true }
|
|
27
39
|
*
|
|
40
|
+
* See design/13-security.md for the security taxonomy.
|
|
28
41
|
* See design/18-build-system.md for build pipeline details.
|
|
29
42
|
*/
|
|
30
43
|
|
|
31
|
-
// ───
|
|
44
|
+
// ─── Dev Mode (client-visible) ──────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if the application is running in development mode.
|
|
48
|
+
*
|
|
49
|
+
* This is the ONLY function that should gate client-visible dev behavior:
|
|
50
|
+
* - Dev error pages with stack traces
|
|
51
|
+
* - Detailed Server-Timing response headers
|
|
52
|
+
* - Error messages in action `INTERNAL_ERROR` payloads
|
|
53
|
+
* - Pipeline error handler wiring (Vite overlay)
|
|
54
|
+
*
|
|
55
|
+
* Returns `process.env.NODE_ENV !== 'production'`, which is statically
|
|
56
|
+
* replaced by the bundler in production builds. Code guarded by this
|
|
57
|
+
* function is tree-shaken to zero bytes in production.
|
|
58
|
+
*
|
|
59
|
+
* TIMBER_DEBUG does NOT enable this — that would leak server internals
|
|
60
|
+
* to clients. Use `isDebug()` for server-side-only logging.
|
|
61
|
+
*/
|
|
62
|
+
export function isDevMode(): boolean {
|
|
63
|
+
return process.env.NODE_ENV !== 'production';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Debug Flag (server-side logging only) ──────────────────────────────────
|
|
32
67
|
|
|
33
68
|
/**
|
|
34
69
|
* Config-level debug override. Set via `setDebugFromConfig()` during
|
|
@@ -45,19 +80,20 @@ export function setDebugFromConfig(debug: boolean): void {
|
|
|
45
80
|
}
|
|
46
81
|
|
|
47
82
|
/**
|
|
48
|
-
* Check if timber debug logging is active.
|
|
83
|
+
* Check if timber debug logging is active (server-side only).
|
|
49
84
|
*
|
|
50
85
|
* Returns true if ANY of these conditions hold:
|
|
51
86
|
* - NODE_ENV is not 'production' (standard dev mode)
|
|
52
87
|
* - TIMBER_DEBUG environment variable is set to a truthy value at runtime
|
|
53
88
|
* - timber.config.ts has `debug: true`
|
|
54
89
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
90
|
+
* This function controls ONLY server-side logging — messages written to
|
|
91
|
+
* stderr or the server console. It NEVER affects client-visible behavior
|
|
92
|
+
* (error pages, response headers, action payloads). For client-visible
|
|
93
|
+
* behavior, use `isDevMode()`.
|
|
59
94
|
*
|
|
60
|
-
*
|
|
95
|
+
* The TIMBER_DEBUG check is deliberately written as a dynamic property
|
|
96
|
+
* access so bundlers cannot statically replace it.
|
|
61
97
|
*/
|
|
62
98
|
export function isDebug(): boolean {
|
|
63
99
|
// Fast path: dev mode (statically replaced to `true` in dev, `false` in prod)
|
|
@@ -89,7 +125,9 @@ function _readTimberDebugEnv(): boolean {
|
|
|
89
125
|
try {
|
|
90
126
|
const key = 'TIMBER_DEBUG';
|
|
91
127
|
const val =
|
|
92
|
-
typeof process !== 'undefined' && process.env
|
|
128
|
+
typeof process !== 'undefined' && process.env
|
|
129
|
+
? (process.env as Record<string, string | undefined>)[key]
|
|
130
|
+
: undefined;
|
|
93
131
|
if (val && val !== '0' && val !== 'false') return true;
|
|
94
132
|
} catch {
|
|
95
133
|
// process may not exist or env may throw — safe to ignore
|
|
@@ -72,7 +72,7 @@ import { buildRscPayloadResponse } from './rsc-payload.js';
|
|
|
72
72
|
import { renderRscStream } from './rsc-stream.js';
|
|
73
73
|
import { renderSsrResponse } from './ssr-renderer.js';
|
|
74
74
|
import { callSsr } from './ssr-bridge.js';
|
|
75
|
-
import { isDebug, setDebugFromConfig } from '#/server/debug.js';
|
|
75
|
+
import { isDebug, isDevMode, setDebugFromConfig } from '#/server/debug.js';
|
|
76
76
|
|
|
77
77
|
// Dev-only pipeline error handler, set by the dev server after import.
|
|
78
78
|
// In production this is always undefined — no overhead.
|
|
@@ -127,9 +127,6 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
127
127
|
buildManifest: buildManifest as BuildManifest,
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
// Dev logging — initialize OTEL-based dev tracing once at handler creation.
|
|
131
|
-
// In production, isDev is false — no tracing, no overhead.
|
|
132
|
-
// The DevSpanProcessor handles all formatting and stderr output.
|
|
133
130
|
// Initialize debug flag from config before anything else.
|
|
134
131
|
// This allows timber.config.ts `debug: true` to enable debug logging
|
|
135
132
|
// in production without the TIMBER_DEBUG env var.
|
|
@@ -137,10 +134,24 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
137
134
|
setDebugFromConfig(true);
|
|
138
135
|
}
|
|
139
136
|
|
|
140
|
-
|
|
137
|
+
// Two separate flags for two different security levels:
|
|
138
|
+
//
|
|
139
|
+
// isDev (isDevMode) — gates client-visible behavior: dev error pages with
|
|
140
|
+
// stack traces, detailed Server-Timing headers, error messages in action
|
|
141
|
+
// payloads. Statically replaced in production → tree-shaken to zero.
|
|
142
|
+
// TIMBER_DEBUG cannot enable this.
|
|
143
|
+
//
|
|
144
|
+
// debugEnabled (isDebug) — gates server-side logging only: stderr warnings,
|
|
145
|
+
// OTEL dev tracing, console.error fallbacks. TIMBER_DEBUG enables this.
|
|
146
|
+
// Never affects what clients see.
|
|
147
|
+
const isDev = isDevMode();
|
|
148
|
+
const debugEnabled = isDebug();
|
|
141
149
|
const slowPhaseMs = (runtimeConfig as Record<string, unknown>).slowPhaseMs as number | undefined;
|
|
142
150
|
|
|
143
|
-
|
|
151
|
+
// Dev logging — initialize OTEL-based dev tracing once at handler creation.
|
|
152
|
+
// In production with TIMBER_DEBUG, this enables server-side tracing output
|
|
153
|
+
// without exposing anything to clients.
|
|
154
|
+
if (debugEnabled) {
|
|
144
155
|
const devLogMode = resolveLogMode();
|
|
145
156
|
if (devLogMode !== 'quiet') {
|
|
146
157
|
await initDevTracing({ mode: devLogMode, slowPhaseMs });
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
//#region src/server/debug.ts
|
|
2
|
-
/**
|
|
3
|
-
* Runtime debug flag for timber.js.
|
|
4
|
-
*
|
|
5
|
-
* Provides `isDebug()` — a runtime check that returns true when timber's
|
|
6
|
-
* debug/warning logging should be active. This is true in two cases:
|
|
7
|
-
*
|
|
8
|
-
* 1. Development mode: `process.env.NODE_ENV !== 'production'`
|
|
9
|
-
* (statically replaced and tree-shaken in production builds — zero cost)
|
|
10
|
-
*
|
|
11
|
-
* 2. TIMBER_DEBUG flag: A runtime environment variable that survives
|
|
12
|
-
* production builds. When set to any truthy value ("1", "true", etc.),
|
|
13
|
-
* timber's own diagnostics are re-enabled without affecting React's mode.
|
|
14
|
-
*
|
|
15
|
-
* The TIMBER_DEBUG check uses a dynamic property access pattern that
|
|
16
|
-
* prevents the bundler from statically replacing or eliminating it.
|
|
17
|
-
*
|
|
18
|
-
* Usage:
|
|
19
|
-
* In Cloudflare Workers wrangler.toml:
|
|
20
|
-
* [vars]
|
|
21
|
-
* TIMBER_DEBUG = "1"
|
|
22
|
-
*
|
|
23
|
-
* In Node.js:
|
|
24
|
-
* TIMBER_DEBUG=1 node server.js
|
|
25
|
-
*
|
|
26
|
-
* In timber.config.ts:
|
|
27
|
-
* export default { debug: true }
|
|
28
|
-
*
|
|
29
|
-
* See design/18-build-system.md for build pipeline details.
|
|
30
|
-
*/
|
|
31
|
-
/**
|
|
32
|
-
* Config-level debug override. Set via `setDebugFromConfig()` during
|
|
33
|
-
* initialization when timber.config.ts has `debug: true`.
|
|
34
|
-
*/
|
|
35
|
-
var _configDebug = false;
|
|
36
|
-
/**
|
|
37
|
-
* Check if timber debug logging is active.
|
|
38
|
-
*
|
|
39
|
-
* Returns true if ANY of these conditions hold:
|
|
40
|
-
* - NODE_ENV is not 'production' (standard dev mode)
|
|
41
|
-
* - TIMBER_DEBUG environment variable is set to a truthy value at runtime
|
|
42
|
-
* - timber.config.ts has `debug: true`
|
|
43
|
-
*
|
|
44
|
-
* The TIMBER_DEBUG check is deliberately written as a dynamic property
|
|
45
|
-
* access so bundlers cannot statically replace it. The `_envKey` variable
|
|
46
|
-
* prevents the bundler from seeing `process.env.TIMBER_DEBUG` as a
|
|
47
|
-
* compile-time constant.
|
|
48
|
-
*
|
|
49
|
-
* This function is intentionally NOT inlineable — it reads runtime state.
|
|
50
|
-
*/
|
|
51
|
-
function isDebug() {
|
|
52
|
-
if (process.env.NODE_ENV !== "production") return true;
|
|
53
|
-
if (_configDebug) return true;
|
|
54
|
-
return _readTimberDebugEnv();
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Read TIMBER_DEBUG from the environment at runtime.
|
|
58
|
-
*
|
|
59
|
-
* Extracted to a separate function to:
|
|
60
|
-
* 1. Prevent bundler inlining (cross-module function calls are not inlined)
|
|
61
|
-
* 2. Handle platforms where `process` may not exist (Cloudflare Workers)
|
|
62
|
-
* 3. Support globalThis.__TIMBER_DEBUG for programmatic control
|
|
63
|
-
*/
|
|
64
|
-
function _readTimberDebugEnv() {
|
|
65
|
-
if (globalThis.__TIMBER_DEBUG) return true;
|
|
66
|
-
try {
|
|
67
|
-
const val = typeof process !== "undefined" && process.env ? process.env["TIMBER_DEBUG"] : void 0;
|
|
68
|
-
if (val && val !== "0" && val !== "false") return true;
|
|
69
|
-
} catch {}
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
//#endregion
|
|
73
|
-
export { isDebug as t };
|
|
74
|
-
|
|
75
|
-
//# sourceMappingURL=debug-B4WUeqJ-.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-B4WUeqJ-.js","names":[],"sources":["../../src/server/debug.ts"],"sourcesContent":["/**\n * Runtime debug flag for timber.js.\n *\n * Provides `isDebug()` — a runtime check that returns true when timber's\n * debug/warning logging should be active. This is true in two cases:\n *\n * 1. Development mode: `process.env.NODE_ENV !== 'production'`\n * (statically replaced and tree-shaken in production builds — zero cost)\n *\n * 2. TIMBER_DEBUG flag: A runtime environment variable that survives\n * production builds. When set to any truthy value (\"1\", \"true\", etc.),\n * timber's own diagnostics are re-enabled without affecting React's mode.\n *\n * The TIMBER_DEBUG check uses a dynamic property access pattern that\n * prevents the bundler from statically replacing or eliminating it.\n *\n * Usage:\n * In Cloudflare Workers wrangler.toml:\n * [vars]\n * TIMBER_DEBUG = \"1\"\n *\n * In Node.js:\n * TIMBER_DEBUG=1 node server.js\n *\n * In timber.config.ts:\n * export default { debug: true }\n *\n * See design/18-build-system.md for build pipeline details.\n */\n\n// ─── Debug Flag ─────────────────────────────────────────────────────────────\n\n/**\n * Config-level debug override. Set via `setDebugFromConfig()` during\n * initialization when timber.config.ts has `debug: true`.\n */\nlet _configDebug = false;\n\n/**\n * Set the debug flag from timber.config.ts.\n * Called during handler initialization.\n */\nexport function setDebugFromConfig(debug: boolean): void {\n _configDebug = debug;\n}\n\n/**\n * Check if timber debug logging is active.\n *\n * Returns true if ANY of these conditions hold:\n * - NODE_ENV is not 'production' (standard dev mode)\n * - TIMBER_DEBUG environment variable is set to a truthy value at runtime\n * - timber.config.ts has `debug: true`\n *\n * The TIMBER_DEBUG check is deliberately written as a dynamic property\n * access so bundlers cannot statically replace it. The `_envKey` variable\n * prevents the bundler from seeing `process.env.TIMBER_DEBUG` as a\n * compile-time constant.\n *\n * This function is intentionally NOT inlineable — it reads runtime state.\n */\nexport function isDebug(): boolean {\n // Fast path: dev mode (statically replaced to `true` in dev, `false` in prod)\n if (process.env.NODE_ENV !== 'production') return true;\n\n // Config override\n if (_configDebug) return true;\n\n // Runtime env var check — uses dynamic access to prevent static replacement.\n // In production builds, process.env.NODE_ENV is statically replaced, but\n // TIMBER_DEBUG must survive as a runtime check. The dynamic key access\n // pattern ensures the bundler treats this as opaque.\n return _readTimberDebugEnv();\n}\n\n/**\n * Read TIMBER_DEBUG from the environment at runtime.\n *\n * Extracted to a separate function to:\n * 1. Prevent bundler inlining (cross-module function calls are not inlined)\n * 2. Handle platforms where `process` may not exist (Cloudflare Workers)\n * 3. Support globalThis.__TIMBER_DEBUG for programmatic control\n */\nfunction _readTimberDebugEnv(): boolean {\n // globalThis override — useful for programmatic control and testing\n if ((globalThis as Record<string, unknown>).__TIMBER_DEBUG) return true;\n\n // process.env — works in Node.js and platforms that polyfill process\n try {\n const key = 'TIMBER_DEBUG';\n const val =\n typeof process !== 'undefined' && process.env ? (process.env as Record<string, string | undefined>)[key] : undefined;\n if (val && val !== '0' && val !== 'false') return true;\n } catch {\n // process may not exist or env may throw — safe to ignore\n }\n\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,IAAI,eAAe;;;;;;;;;;;;;;;;AAyBnB,SAAgB,UAAmB;AAEjC,KAAA,QAAA,IAAA,aAA6B,aAAc,QAAO;AAGlD,KAAI,aAAc,QAAO;AAMzB,QAAO,qBAAqB;;;;;;;;;;AAW9B,SAAS,sBAA+B;AAEtC,KAAK,WAAuC,eAAgB,QAAO;AAGnE,KAAI;EAEF,MAAM,MACJ,OAAO,YAAY,eAAe,QAAQ,MAAO,QAAQ,IAF/C,kBAEiG,KAAA;AAC7G,MAAI,OAAO,QAAQ,OAAO,QAAQ,QAAS,QAAO;SAC5C;AAIR,QAAO"}
|