@timber-js/app 0.2.0-alpha.154 → 0.2.0-alpha.156

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.
Files changed (70) hide show
  1. package/agent-skill.md +344 -0
  2. package/dist/_chunks/{build-output-helper-B20ZuTSA.js → build-output-helper-Rp9r7gSA.js} +62 -9
  3. package/dist/_chunks/build-output-helper-Rp9r7gSA.js.map +1 -0
  4. package/dist/adapters/build-output-helper.d.ts +1 -0
  5. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  6. package/dist/adapters/cloudflare.d.ts.map +1 -1
  7. package/dist/adapters/cloudflare.js +35 -5
  8. package/dist/adapters/cloudflare.js.map +1 -1
  9. package/dist/adapters/nitro.js +1 -1
  10. package/dist/adapters/shared.d.ts +1 -11
  11. package/dist/adapters/shared.d.ts.map +1 -1
  12. package/dist/cli.d.ts +9 -1
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +41 -4
  15. package/dist/cli.js.map +1 -1
  16. package/dist/fonts/google.d.ts.map +1 -1
  17. package/dist/index.js +43 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/plugins/fonts.d.ts.map +1 -1
  20. package/dist/server/asset-headers.d.ts +7 -3
  21. package/dist/server/asset-headers.d.ts.map +1 -1
  22. package/dist/server/internal.js +12 -6
  23. package/dist/server/internal.js.map +1 -1
  24. package/dist/server/pipeline-phases.d.ts.map +1 -1
  25. package/docs/api/30-api-server.mdx +233 -0
  26. package/docs/api/31-api-client.mdx +161 -0
  27. package/docs/api/32-api-cache.mdx +193 -0
  28. package/docs/api/33-api-search-params.mdx +141 -0
  29. package/docs/api/34-api-config.mdx +189 -0
  30. package/docs/api/35-api-typescript.mdx +170 -0
  31. package/docs/api/36-cli.mdx +143 -0
  32. package/docs/learn/00-introduction.mdx +81 -0
  33. package/docs/learn/01-quick-start.mdx +94 -0
  34. package/docs/learn/02-pages-and-layouts.mdx +138 -0
  35. package/docs/learn/03-fetching-data.mdx +211 -0
  36. package/docs/learn/03b-access-control.mdx +130 -0
  37. package/docs/learn/04-loading-states.mdx +67 -0
  38. package/docs/learn/04b-the-flush-point.mdx +115 -0
  39. package/docs/learn/05-typed-params.mdx +275 -0
  40. package/docs/learn/06-forms-and-actions.mdx +150 -0
  41. package/docs/learn/08-streaming.mdx +146 -0
  42. package/docs/learn/09-caching.mdx +121 -0
  43. package/docs/learn/10-middleware.mdx +156 -0
  44. package/docs/learn/11-error-handling.mdx +147 -0
  45. package/docs/learn/12-client-navigation.mdx +168 -0
  46. package/docs/learn/13-configuration.mdx +117 -0
  47. package/docs/learn/14-deploying.mdx +206 -0
  48. package/docs/more/01-advanced-routing.mdx +139 -0
  49. package/docs/more/02-advanced-forms.mdx +137 -0
  50. package/docs/more/03-coming-from-nextjs.mdx +186 -0
  51. package/docs/more/04-metadata-and-fonts.mdx +193 -0
  52. package/docs/more/04b-mdx.mdx +209 -0
  53. package/docs/more/05-content-collections.mdx +90 -0
  54. package/docs/more/06-instrumentation.mdx +188 -0
  55. package/docs/more/07-security.mdx +123 -0
  56. package/docs/more/40-why-timber.mdx +50 -0
  57. package/docs/more/41-timber-vs-nextjs.mdx +81 -0
  58. package/docs/more/42-timber-vs-others.mdx +68 -0
  59. package/docs/more/50-ai-agent-instructions.mdx +169 -0
  60. package/llms.txt +55 -0
  61. package/package.json +8 -4
  62. package/src/adapters/build-output-helper.ts +52 -2
  63. package/src/adapters/cloudflare.ts +34 -4
  64. package/src/adapters/shared.ts +34 -6
  65. package/src/cli.ts +62 -3
  66. package/src/fonts/google.ts +56 -5
  67. package/src/plugins/fonts.ts +6 -0
  68. package/src/server/asset-headers.ts +7 -3
  69. package/src/server/pipeline-phases.ts +10 -2
  70. package/dist/_chunks/build-output-helper-B20ZuTSA.js.map +0 -1
package/src/cli.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  // timber preview — Serve the production build
12
12
  // timber check — Validate types + routes without building
13
13
 
14
- const COMMANDS = ['dev', 'build', 'preview', 'check', 'schema'] as const;
14
+ const COMMANDS = ['dev', 'build', 'preview', 'check', 'schema', 'init'] as const;
15
15
  type Command = (typeof COMMANDS)[number];
16
16
 
17
17
  export interface ParsedArgs {
@@ -37,7 +37,7 @@ export interface CommandOptions {
37
37
  export function parseArgs(args: string[]): ParsedArgs {
38
38
  if (args.length === 0) {
39
39
  throw new Error(
40
- 'No command provided. Usage: timber <dev|build|preview|check|schema> [--config <path>]'
40
+ 'No command provided. Usage: timber <dev|build|preview|check|schema|init> [--config <path>]'
41
41
  );
42
42
  }
43
43
 
@@ -64,7 +64,7 @@ export function parseArgs(args: string[]): ParsedArgs {
64
64
  throw new Error(
65
65
  `Unknown option: ${arg}. The timber CLI only accepts --config <path>.${portHint}`
66
66
  );
67
- } else if (command === 'schema') {
67
+ } else if (command === 'schema' || command === 'init') {
68
68
  rest.push(arg);
69
69
  } else {
70
70
  throw new Error(`Unexpected argument: ${arg}. Usage: timber ${command} [--config <path>]`);
@@ -235,6 +235,62 @@ export async function runSchema(args: string[]): Promise<void> {
235
235
  }
236
236
  }
237
237
 
238
+ // ─── Init Subcommand ─────────────────────────────────────────────────────────
239
+
240
+ /**
241
+ * `timber init` — write AGENTS.md (and CLAUDE.md pointer) to the project root
242
+ * so AI coding agents can discover timber.js documentation and conventions.
243
+ *
244
+ * The content is read from the installed @timber-js/app package itself, so it
245
+ * stays in sync with the installed version.
246
+ */
247
+ export async function runInit(): Promise<void> {
248
+ const { readFileSync, writeFileSync, existsSync } = await import('node:fs');
249
+ const { resolve, dirname } = await import('node:path');
250
+ const { fileURLToPath } = await import('node:url');
251
+
252
+ const root = process.cwd();
253
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
254
+
255
+ const agentSkillPath = resolve(packageRoot, 'agent-skill.md');
256
+ if (!existsSync(agentSkillPath)) {
257
+ throw new Error(
258
+ 'agent-skill.md not found in @timber-js/app package. ' +
259
+ 'This may be a development install — try reinstalling.'
260
+ );
261
+ }
262
+
263
+ const agentSkill = readFileSync(agentSkillPath, 'utf-8');
264
+
265
+ const agentsPath = resolve(root, 'AGENTS.md');
266
+ const claudePath = resolve(root, 'CLAUDE.md');
267
+
268
+ const wrote: string[] = [];
269
+
270
+ if (existsSync(agentsPath)) {
271
+ console.log('[timber] AGENTS.md already exists — skipping (delete it first to regenerate)');
272
+ } else {
273
+ writeFileSync(agentsPath, agentSkill);
274
+ wrote.push('AGENTS.md');
275
+ }
276
+
277
+ if (existsSync(claudePath)) {
278
+ console.log('[timber] CLAUDE.md already exists — skipping (delete it first to regenerate)');
279
+ } else {
280
+ writeFileSync(claudePath, 'Read AGENTS.md for project context and conventions.\n');
281
+ wrote.push('CLAUDE.md');
282
+ }
283
+
284
+ if (wrote.length > 0) {
285
+ console.log(
286
+ `[timber] Created ${wrote.join(' and ')} — AI agents can now discover timber.js docs.`
287
+ );
288
+ console.log('[timber] Full documentation: node_modules/@timber-js/app/docs/');
289
+ } else {
290
+ console.log('[timber] Nothing to do — both files already exist.');
291
+ }
292
+ }
293
+
238
294
  // ─── Main Entry Point ────────────────────────────────────────────────────────
239
295
 
240
296
  async function main(): Promise<void> {
@@ -257,6 +313,9 @@ async function main(): Promise<void> {
257
313
  case 'schema':
258
314
  await runSchema(parsed.rest);
259
315
  break;
316
+ case 'init':
317
+ await runInit();
318
+ break;
260
319
  }
261
320
  }
262
321
 
@@ -21,6 +21,28 @@ import { join } from 'node:path';
21
21
  import type { ExtractedFont, FontFaceDescriptor } from './types.js';
22
22
  import { fontUrlPrefix } from './paths.js';
23
23
 
24
+ /**
25
+ * Extract a recognizable error code (ETIMEDOUT, ENOTFOUND, etc.) from a
26
+ * network error's cause chain. Node's undici-based `fetch` wraps the
27
+ * system error inside `cause` (sometimes as an AggregateError).
28
+ */
29
+ function extractNetworkErrorCode(err: unknown): string | undefined {
30
+ if (!(err instanceof Error)) return undefined;
31
+ const code = (err as NodeJS.ErrnoException).code;
32
+ if (code) return code;
33
+ const cause = (err as { cause?: unknown }).cause;
34
+ if (cause instanceof AggregateError) {
35
+ for (const inner of cause.errors) {
36
+ const innerCode = (inner as NodeJS.ErrnoException).code;
37
+ if (innerCode) return innerCode;
38
+ }
39
+ }
40
+ if (cause instanceof Error) {
41
+ return (cause as NodeJS.ErrnoException).code;
42
+ }
43
+ return undefined;
44
+ }
45
+
24
46
  /** Google Fonts CSS API v2 base URL. */
25
47
  const GOOGLE_FONTS_API = 'https://fonts.googleapis.com/css2';
26
48
 
@@ -112,9 +134,20 @@ export function buildGoogleFontsUrl(font: ExtractedFont): string {
112
134
  * We parse each block to extract the font URL, unicode-range, and subset label.
113
135
  */
114
136
  export async function fetchGoogleFontsCss(url: string): Promise<GoogleFontFace[]> {
115
- const response = await fetch(url, {
116
- headers: { 'User-Agent': WOFF2_USER_AGENT },
117
- });
137
+ let response: Response;
138
+ try {
139
+ response = await fetch(url, {
140
+ headers: { 'User-Agent': WOFF2_USER_AGENT },
141
+ });
142
+ } catch (err) {
143
+ const code = extractNetworkErrorCode(err);
144
+ throw new Error(
145
+ `[timber] Failed to fetch Google Fonts CSS from ${url}` +
146
+ (code ? ` (${code})` : '') +
147
+ `. Build requires internet access for font optimization.` +
148
+ ` If building offline, set TIMBER_SKIP_FONTS=1 to skip font downloading.`
149
+ );
150
+ }
118
151
 
119
152
  if (!response.ok) {
120
153
  throw new Error(
@@ -205,7 +238,18 @@ export function cacheKey(face: GoogleFontFace): string {
205
238
  * Download a single font file from its URL.
206
239
  */
207
240
  export async function downloadFontFile(url: string): Promise<Buffer> {
208
- const response = await fetch(url);
241
+ let response: Response;
242
+ try {
243
+ response = await fetch(url);
244
+ } catch (err) {
245
+ const code = extractNetworkErrorCode(err);
246
+ throw new Error(
247
+ `[timber] Failed to download font file from ${url}` +
248
+ (code ? ` (${code})` : '') +
249
+ `. Build requires internet access for font optimization.` +
250
+ ` If building offline, set TIMBER_SKIP_FONTS=1 to skip font downloading.`
251
+ );
252
+ }
209
253
  if (!response.ok) {
210
254
  throw new Error(`Failed to download font from ${url}: ${response.status}`);
211
255
  }
@@ -232,7 +276,14 @@ export async function downloadAndCacheFonts(
232
276
 
233
277
  for (const font of googleFonts) {
234
278
  const apiUrl = buildGoogleFontsUrl(font);
235
- const faces = await fetchGoogleFontsCss(apiUrl);
279
+ let faces: GoogleFontFace[];
280
+ try {
281
+ faces = await fetchGoogleFontsCss(apiUrl);
282
+ } catch (err) {
283
+ throw new Error(
284
+ `[timber] Failed to download font '${font.family}': ${err instanceof Error ? err.message : String(err)}`
285
+ );
286
+ }
236
287
  const filtered = filterBySubsets(faces, font.subsets);
237
288
 
238
289
  for (const face of filtered) {
@@ -159,9 +159,15 @@ export function timberFonts(ctx: PluginContext): Plugin {
159
159
  return '';
160
160
  }
161
161
 
162
+ const skipFonts = process.env.TIMBER_SKIP_FONTS === '1';
163
+
162
164
  for (const font of pipeline.googleFonts()) {
163
165
  if (font.importer !== importer) continue;
164
166
  if (pipeline.hasFaces(font.id)) continue;
167
+ if (skipFonts && !ctx.dev) {
168
+ console.warn(`[timber-fonts] Skipping font "${font.family}" (TIMBER_SKIP_FONTS=1)`);
169
+ continue;
170
+ }
165
171
  try {
166
172
  if (ctx.dev) {
167
173
  const faces = await resolveDevFontFaces(font);
@@ -8,9 +8,13 @@
8
8
  * Unhashed assets (e.g. /favicon.ico) get a shorter cache with revalidation:
9
9
  * Cache-Control: public, max-age=3600, must-revalidate
10
10
  *
11
- * This is applied automatically by the framework for static assets served
12
- * from the build output. Page responses are NOT affected — those are
13
- * controlled by the developer via middleware.ts or proxy.ts.
11
+ * The `_headers` file lists hashed asset paths (/assets/*, /_timber/fonts/*) as
12
+ * wildcard rules and unhashed files as per-file rules. At runtime, adapters
13
+ * that see static file requests (nitro, preview) also set Cache-Control via
14
+ * `getAssetCacheControl()`. On Cloudflare Workers Static Assets, the worker
15
+ * never sees static file requests — the `_headers` file is the only mechanism.
16
+ * Page responses are NOT affected — those are controlled by the developer
17
+ * via middleware.ts or proxy.ts.
14
18
  *
15
19
  * Design docs: 18-build-system.md, 06-caching.md
16
20
  */
@@ -290,13 +290,21 @@ export async function handleRequest(
290
290
  // immutable header bag (e.g. user returns Response.redirect()).
291
291
  if (handlerResult instanceof Response) {
292
292
  if (method === 'HEAD') {
293
+ const headHeaders = new Headers(handlerResult.headers);
294
+ if (handlerResult.ok && !headHeaders.has('Cache-Control')) {
295
+ headHeaders.set('Cache-Control', 'public, max-age=14400, must-revalidate');
296
+ }
293
297
  return new Response(null, {
294
298
  status: handlerResult.status,
295
299
  statusText: handlerResult.statusText,
296
- headers: new Headers(handlerResult.headers),
300
+ headers: headHeaders,
297
301
  });
298
302
  }
299
- return cloneWithMutableHeaders(handlerResult);
303
+ const res = cloneWithMutableHeaders(handlerResult);
304
+ if (res.ok && !res.headers.has('Cache-Control')) {
305
+ res.headers.set('Cache-Control', 'public, max-age=14400, must-revalidate');
306
+ }
307
+ return res;
300
308
  }
301
309
  // Otherwise, serialize based on content type. The type discriminator
302
310
  // here is the metadata route's declared content type (from the file
@@ -1 +0,0 @@
1
- {"version":3,"file":"build-output-helper-B20ZuTSA.js","names":[],"sources":["../../src/adapters/shared.ts","../../src/adapters/build-output-helper.ts"],"sourcesContent":["// Shared adapter utilities — leaf module with zero deps on server/, client/,\n// or any Vite-dependent code.\n//\n// Adapters are loaded by Node at Vite startup time, before Vite's module\n// resolver is available. This module uses only Node built-ins and can be\n// imported by both adapters via a relative path.\n//\n// IMPORTANT: Do NOT add imports from ../server/, ../client/, ../plugins/,\n// or any module that transitively depends on Vite. A test in\n// tests/adapters/shared-no-forbidden-imports.test.ts enforces this.\n\n// ─── Static asset cache headers ──────────────────────────────────────────\n\n/** Cache-Control value for hashed (immutable) assets. */\nexport const IMMUTABLE_CACHE = 'public, max-age=31536000, immutable';\n\n/** Cache-Control value for unhashed static assets. */\nexport const STATIC_CACHE = 'public, max-age=3600, must-revalidate';\n\n/**\n * Generate a `_headers` file for static asset cache control.\n *\n * The `_headers` file is a platform convention supported by Cloudflare Workers\n * Static Assets, Cloudflare Pages, and Netlify. It maps URL patterns to\n * HTTP response headers.\n *\n * Vite places all hashed chunks under `/assets/` — these get immutable caching.\n * Everything else (favicon.ico, robots.txt, etc.) gets a shorter cache.\n */\nexport function generateHeadersFile(): string {\n return `# Auto-generated by @timber-js/app — static asset cache headers.\n# See design/25-production-deployments.md §\"CDN / Edge Cache\"\n\n/assets/*\n Cache-Control: ${IMMUTABLE_CACHE}\n\n/*\n Cache-Control: ${STATIC_CACHE}\n`;\n}\n","// Shared build-output helper for adapter buildOutput() methods.\n//\n// Steps 1–5 of every adapter's buildOutput are platform-agnostic:\n// 1. Copy client assets to the public/static directory\n// 2. Write _headers file for static asset cache control\n// 3. Copy RSC + SSR server bundles\n// 4. Write manifest-init module (if present)\n//\n// Only the final steps (entry codegen, platform config) vary per adapter.\n//\n// IMPORTANT: This module must remain a leaf — no imports from ../server/,\n// ../client/, or Vite-dependent code. See adapters/shared.ts header.\n\nimport { writeFile, mkdir, cp, readdir, readFile } from 'node:fs/promises';\nimport { join, basename } from 'node:path';\nimport { generateHeadersFile } from './shared.js';\nimport type { TimberConfig } from './types';\n\n/** Options for the shared build output steps. */\nexport interface BuildOutputBaseOptions {\n /** Resolved timber config. */\n config: TimberConfig;\n /** Root build directory (e.g. `.timber/build`). */\n buildDir: string;\n /** Adapter-specific output directory (e.g. `.timber/build/nitro`). */\n outDir: string;\n /**\n * Name of the public/static directory within outDir.\n * Nitro uses 'public', Cloudflare uses 'static'.\n */\n publicDirName: string;\n}\n\n/**\n * Run the platform-agnostic build output steps shared by all adapters:\n *\n * 1. Create the output directory\n * 2. Copy client assets to the public/static directory\n * 3. Write the `_headers` file for static asset cache control\n * 4. Copy RSC and SSR server bundles into the output directory\n * 5. Write the manifest-init module if present\n *\n * Returns the resolved public directory path for further adapter-specific use.\n */\nexport async function runSharedBuildSteps(opts: BuildOutputBaseOptions): Promise<string> {\n const { config, buildDir, outDir, publicDirName } = opts;\n\n // 1. Create the output directory.\n await mkdir(outDir, { recursive: true });\n\n // 2. Copy client assets to public/static directory.\n // When client JavaScript is disabled, skip .js files — only CSS,\n // fonts, images, and other static assets are needed.\n const clientDir = join(buildDir, 'client');\n const publicDir = join(outDir, publicDirName);\n await mkdir(publicDir, { recursive: true });\n // Find WASM files that are referenced by client JS (legitimate client WASM)\n // vs orphaned server-only WASM (e.g. @takumi-rs for OG image generation).\n // Only strip orphaned WASM to avoid breaking client-side WASM usage.\n const referencedWasm = await findReferencedWasm(clientDir);\n\n await cp(clientDir, publicDir, {\n recursive: true,\n filter: (src: string) => {\n if (src.endsWith('.wasm') && !referencedWasm.has(basename(src))) {\n return false;\n }\n if (config.clientJavascriptDisabled && src.endsWith('.js')) return false;\n return true;\n },\n }).catch((e: NodeJS.ErrnoException) => {\n // Client dir may not exist when client JavaScript is disabled\n if (e.code !== 'ENOENT') throw e;\n });\n\n // 3. Write _headers file for static asset cache control.\n await writeFile(join(publicDir, '_headers'), generateHeadersFile());\n\n // 4. Copy RSC + SSR server bundles into the output directory.\n await cp(join(buildDir, 'rsc'), join(outDir, 'rsc'), { recursive: true });\n await cp(join(buildDir, 'ssr'), join(outDir, 'ssr'), { recursive: true }).catch(\n (e: NodeJS.ErrnoException) => {\n // SSR dir may not exist for static-only builds\n if (e.code !== 'ENOENT') throw e;\n }\n );\n\n // 5. Write manifest-init module if present.\n if (config.manifestInit) {\n await writeFile(join(outDir, '_timber-manifest-init.js'), config.manifestInit);\n }\n\n return publicDir;\n}\n\n/**\n * Scan client JS files for references to .wasm filenames.\n * Returns the set of WASM basenames that are actually imported/referenced\n * by client code and must be preserved in the static output.\n */\nasync function findReferencedWasm(clientDir: string): Promise<Set<string>> {\n const referenced = new Set<string>();\n\n let files: string[];\n try {\n files = await collectFiles(clientDir);\n } catch {\n return referenced;\n }\n\n const jsFiles = files.filter((f) => f.endsWith('.js'));\n const wasmFiles = files.filter((f) => f.endsWith('.wasm')).map((f) => basename(f));\n if (wasmFiles.length === 0) return referenced;\n\n for (const jsFile of jsFiles) {\n const content = await readFile(jsFile, 'utf-8');\n for (const wasmName of wasmFiles) {\n if (content.includes(wasmName)) {\n referenced.add(wasmName);\n }\n }\n }\n\n return referenced;\n}\n\nasync function collectFiles(dir: string): Promise<string[]> {\n const result: string[] = [];\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n result.push(...(await collectFiles(fullPath)));\n } else {\n result.push(fullPath);\n }\n }\n return result;\n}\n"],"mappings":";;;;AAcA,IAAa,kBAAkB;;AAG/B,IAAa,eAAe;;;;;;;;;;;AAY5B,SAAgB,sBAA8B;CAC5C,OAAO;;;;mBAIU,gBAAgB;;;mBAGhB,aAAa;;AAEhC;;;;;;;;;;;;;;ACKA,eAAsB,oBAAoB,MAA+C;CACvF,MAAM,EAAE,QAAQ,UAAU,QAAQ,kBAAkB;CAGpD,MAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;CAKvC,MAAM,YAAY,KAAK,UAAU,QAAQ;CACzC,MAAM,YAAY,KAAK,QAAQ,aAAa;CAC5C,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;CAI1C,MAAM,iBAAiB,MAAM,mBAAmB,SAAS;CAEzD,MAAM,GAAG,WAAW,WAAW;EAC7B,WAAW;EACX,SAAS,QAAgB;GACvB,IAAI,IAAI,SAAS,OAAO,KAAK,CAAC,eAAe,IAAI,SAAS,GAAG,CAAC,GAC5D,OAAO;GAET,IAAI,OAAO,4BAA4B,IAAI,SAAS,KAAK,GAAG,OAAO;GACnE,OAAO;EACT;CACF,CAAC,EAAE,OAAO,MAA6B;EAErC,IAAI,EAAE,SAAS,UAAU,MAAM;CACjC,CAAC;CAGD,MAAM,UAAU,KAAK,WAAW,UAAU,GAAG,oBAAoB,CAAC;CAGlE,MAAM,GAAG,KAAK,UAAU,KAAK,GAAG,KAAK,QAAQ,KAAK,GAAG,EAAE,WAAW,KAAK,CAAC;CACxE,MAAM,GAAG,KAAK,UAAU,KAAK,GAAG,KAAK,QAAQ,KAAK,GAAG,EAAE,WAAW,KAAK,CAAC,EAAE,OACvE,MAA6B;EAE5B,IAAI,EAAE,SAAS,UAAU,MAAM;CACjC,CACF;CAGA,IAAI,OAAO,cACT,MAAM,UAAU,KAAK,QAAQ,0BAA0B,GAAG,OAAO,YAAY;CAG/E,OAAO;AACT;;;;;;AAOA,eAAe,mBAAmB,WAAyC;CACzE,MAAM,6BAAa,IAAI,IAAY;CAEnC,IAAI;CACJ,IAAI;EACF,QAAQ,MAAM,aAAa,SAAS;CACtC,QAAQ;EACN,OAAO;CACT;CAEA,MAAM,UAAU,MAAM,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC;CACrD,MAAM,YAAY,MAAM,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC,EAAE,KAAK,MAAM,SAAS,CAAC,CAAC;CACjF,IAAI,UAAU,WAAW,GAAG,OAAO;CAEnC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,UAAU,MAAM,SAAS,QAAQ,OAAO;EAC9C,KAAK,MAAM,YAAY,WACrB,IAAI,QAAQ,SAAS,QAAQ,GAC3B,WAAW,IAAI,QAAQ;CAG7B;CAEA,OAAO;AACT;AAEA,eAAe,aAAa,KAAgC;CAC1D,MAAM,SAAmB,CAAC;CAC1B,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAC1D,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,KAAK,MAAM,IAAI;EACrC,IAAI,MAAM,YAAY,GACpB,OAAO,KAAK,GAAI,MAAM,aAAa,QAAQ,CAAE;OAE7C,OAAO,KAAK,QAAQ;CAExB;CACA,OAAO;AACT"}