jskelet 0.5.4 → 0.6.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.
Files changed (149) hide show
  1. package/AGENTS.md +18 -13
  2. package/CHANGELOG.md +387 -385
  3. package/README.md +9 -7
  4. package/bin/jskelet.mjs +24 -10
  5. package/docs/01-baslangic.md +4 -3
  6. package/docs/02-mimari.md +4 -3
  7. package/docs/03-routing.md +11 -6
  8. package/docs/04-render-ve-sablonlar.md +35 -43
  9. package/docs/05-islands.md +12 -8
  10. package/docs/07-yapilandirma.md +53 -25
  11. package/docs/08-build.md +40 -18
  12. package/docs/09-dev-araclari.md +5 -1
  13. package/docs/10-dagitim.md +6 -1
  14. package/docs/11-tasima.md +51 -17
  15. package/docs/12-panel-ve-oturum.md +10 -4
  16. package/docs/README.md +7 -5
  17. package/docs/en/01-getting-started.md +4 -3
  18. package/docs/en/02-architecture.md +5 -5
  19. package/docs/en/03-routing.md +12 -7
  20. package/docs/en/04-rendering.md +47 -59
  21. package/docs/en/05-islands.md +13 -8
  22. package/docs/en/07-configuration.md +55 -27
  23. package/docs/en/08-build.md +43 -21
  24. package/docs/en/09-dev-tools.md +6 -1
  25. package/docs/en/10-deployment.md +6 -1
  26. package/docs/en/11-migration.md +51 -16
  27. package/docs/en/12-dashboards-and-sessions.md +9 -4
  28. package/docs/en/README.md +7 -5
  29. package/package.json +49 -14
  30. package/src/build/tasks/client.mjs +91 -10
  31. package/src/build/tasks/icons.mjs +152 -18
  32. package/src/client/index.js +2 -2
  33. package/src/compile/codegen.js +4 -0
  34. package/src/compile/compile-all.js +12 -21
  35. package/src/compile/expr.js +5 -0
  36. package/src/compile/parse.js +64 -8
  37. package/src/compile/resolve.js +3 -0
  38. package/src/config/defaults.js +12 -2
  39. package/src/config/index.js +31 -3
  40. package/src/dev-server.mjs +26 -3
  41. package/src/http/cookies-entry.js +1 -0
  42. package/src/http/cookies.js +18 -0
  43. package/src/logo.png +0 -0
  44. package/src/migrate/apply.mjs +262 -0
  45. package/src/migrate/babel.mjs +79 -0
  46. package/src/migrate/classify.mjs +155 -0
  47. package/src/migrate/config.mjs +126 -0
  48. package/src/migrate/fs-walk.mjs +191 -0
  49. package/src/migrate/parse.mjs +26 -0
  50. package/src/migrate/scan.mjs +177 -0
  51. package/src/migrate/transform/expr-source.mjs +168 -0
  52. package/src/migrate/transform/island.mjs +67 -0
  53. package/src/migrate/transform/jsx-to-component.mjs +302 -0
  54. package/src/migrate/transform/jsx-to-jsk.mjs +330 -0
  55. package/src/migrate/transform/page-split.mjs +435 -0
  56. package/src/migrate/write.mjs +81 -0
  57. package/src/migrate.mjs +171 -0
  58. package/src/server/auth/handoff.js +94 -11
  59. package/src/server/create-app.js +28 -10
  60. package/src/server/ejs-adapter.js +59 -0
  61. package/src/server/image-optimizer.js +94 -26
  62. package/src/server/port-guard.js +255 -0
  63. package/src/server/render.js +27 -9
  64. package/src/server/status-page.js +105 -4
  65. package/src/start.mjs +18 -3
  66. package/src/templates/layout.ejs +8 -28
  67. package/src/templates/layout.jsk +30 -0
  68. package/src/templates/layout.render.js +41 -0
  69. package/src/views/helpers/tags.js +86 -3
  70. package/types/build/resolve-peer.d.mts +13 -0
  71. package/types/client/dom.d.ts +55 -0
  72. package/types/client/form.d.ts +19 -0
  73. package/types/client/index.d.ts +20 -0
  74. package/types/client/registry.d.ts +53 -0
  75. package/types/client/safe-image.d.ts +19 -0
  76. package/types/client/shared-cookie.d.ts +82 -0
  77. package/types/client/store.d.ts +18 -0
  78. package/types/client/swap.d.ts +46 -0
  79. package/types/compile/codegen.d.ts +32 -0
  80. package/types/compile/compile-all.d.ts +42 -0
  81. package/types/compile/errors.d.ts +30 -0
  82. package/types/compile/expr.d.ts +67 -0
  83. package/types/compile/index.d.ts +10 -0
  84. package/types/compile/parse.d.ts +82 -0
  85. package/types/compile/resolve.d.ts +46 -0
  86. package/types/compile/scan-exports.d.ts +9 -0
  87. package/types/config/defaults.d.ts +449 -0
  88. package/types/config/index.d.ts +299 -0
  89. package/types/config/pattern.d.ts +38 -0
  90. package/types/http/control-flow.d.ts +45 -0
  91. package/types/http/cookies-entry.d.ts +5 -0
  92. package/types/http/cookies.d.ts +113 -0
  93. package/types/http/request-cache.d.ts +13 -0
  94. package/types/http/request-context.d.ts +67 -0
  95. package/types/http/shared-cookie.d.ts +73 -0
  96. package/types/index.d.ts +30 -0
  97. package/types/log.d.mts +153 -0
  98. package/types/server/admin/actions.d.ts +16 -0
  99. package/types/server/admin/auth.d.ts +52 -0
  100. package/types/server/admin/event-log.d.ts +38 -0
  101. package/types/server/admin/gate.d.ts +43 -0
  102. package/types/server/admin/inventory.d.ts +40 -0
  103. package/types/server/admin/mount.d.ts +6 -0
  104. package/types/server/admin/router.d.ts +6 -0
  105. package/types/server/admin/snapshot.d.ts +6 -0
  106. package/types/server/assets.d.ts +47 -0
  107. package/types/server/auth/handoff.d.ts +12 -0
  108. package/types/server/cache-deps.d.ts +16 -0
  109. package/types/server/cache-vary.d.ts +30 -0
  110. package/types/server/cloudflare.d.ts +163 -0
  111. package/types/server/create-app.d.ts +25 -0
  112. package/types/server/data-cache.d.ts +116 -0
  113. package/types/server/dev/devtools.d.ts +44 -0
  114. package/types/server/dev/report.d.ts +229 -0
  115. package/types/server/dev/socket.d.ts +17 -0
  116. package/types/server/dev/version-check.d.mts +15 -0
  117. package/types/server/ejs-adapter.d.ts +11 -0
  118. package/types/server/head-hints.d.ts +40 -0
  119. package/types/server/html-cache.d.ts +173 -0
  120. package/types/server/image-optimizer.d.ts +68 -0
  121. package/types/server/logs/access-middleware.d.ts +7 -0
  122. package/types/server/logs/file-sink.d.ts +17 -0
  123. package/types/server/logs/pipeline.d.ts +37 -0
  124. package/types/server/logs/s3-put.d.ts +85 -0
  125. package/types/server/logs/s3-sink.d.ts +26 -0
  126. package/types/server/metadata.d.ts +38 -0
  127. package/types/server/middleware/compression.d.ts +17 -0
  128. package/types/server/middleware/csrf.d.ts +4 -0
  129. package/types/server/middleware/dev-gate.d.ts +2 -0
  130. package/types/server/middleware/headers.d.ts +2 -0
  131. package/types/server/middleware/redirects.d.ts +2 -0
  132. package/types/server/middleware/static-precompressed.d.ts +5 -0
  133. package/types/server/middleware/trailing-slash.d.ts +11 -0
  134. package/types/server/middleware/upstream-proxy.d.ts +21 -0
  135. package/types/server/og-image.d.ts +149 -0
  136. package/types/server/port-guard.d.ts +50 -0
  137. package/types/server/prewarm.d.ts +128 -0
  138. package/types/server/redis.d.ts +163 -0
  139. package/types/server/render.d.ts +101 -0
  140. package/types/server/router.d.ts +5 -0
  141. package/types/server/status-page.d.ts +24 -0
  142. package/types/server/upstream-limiter.d.ts +123 -0
  143. package/types/server/upstream-tracking.d.ts +42 -0
  144. package/types/shared/cookie-domain.d.ts +29 -0
  145. package/types/templates/layout.render.d.ts +7 -0
  146. package/types/version.d.mts +10 -0
  147. package/types/views/components/loader.d.ts +5 -0
  148. package/types/views/helpers/html.d.ts +39 -0
  149. package/types/views/helpers/tags.d.ts +127 -0
package/README.md CHANGED
@@ -4,11 +4,13 @@
4
4
  the product.
5
5
 
6
6
  JSkelet renders **complete HTML** on an Express 5 server from build-time
7
- **`.jsk` templates** (EJS still works), adds interactivity through vanilla JS
8
- **islands**, compiles CSS into a **single Tailwind v4 stylesheet**, and instead
9
- of ISR keeps an in-process **HTML TTL cache** with stale-while-revalidate — plus
10
- optional Redis sharing and path-based invalidation. No React, no TypeScript —
11
- plain JavaScript with JSDoc.
7
+ **`.jsk` templates** (optional EJS peer for legacy `.ejs`), adds interactivity
8
+ through vanilla JS **islands**, compiles CSS into a **single Tailwind v4
9
+ stylesheet**, and instead of ISR keeps an in-process **HTML TTL cache** with
10
+ stale-while-revalidate — plus optional Redis sharing and path-based
11
+ invalidation. No React — the framework source is plain JavaScript with JSDoc;
12
+ apps can write client islands and entries in TypeScript, and the published
13
+ package ships declaration files.
12
14
 
13
15
  [![npm version](https://img.shields.io/npm/v/jskelet)](https://www.npmjs.com/package/jskelet)
14
16
  [![Node.js 22+](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org)
@@ -282,9 +284,9 @@ The complete reference — every field, default and failure mode — is
282
284
 
283
285
  | Command | What it does |
284
286
  | --- | --- |
285
- | `jskelet dev` | Watch build plus server, live reload, devtools overlay |
287
+ | `jskelet dev` | Watch build plus server, live reload, devtools overlay. `--murder` kills whatever already holds `PORT` and starts. |
286
288
  | `jskelet build` | Production build: templates → fonts → sprite → CSS → JS → images → manifest → precompress |
287
- | `jskelet start` | Production server; builds first if output is missing |
289
+ | `jskelet start` | Production server; builds first if output is missing. `--murder` same as for `dev`. |
288
290
  | `jskelet init` | Scaffolds a feature-first `.jsk` skeleton into the current directory |
289
291
  | `jskelet generate` | Scaffolds a `feature` / `page` / `island` |
290
292
 
package/bin/jskelet.mjs CHANGED
@@ -1,12 +1,13 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * JSkelet CLI.
4
4
  *
5
- * jskelet dev build watch + sunucu, canlı yenileme, dev overlay
6
- * jskelet build tek seferlik prod build (fontlar, sprite, CSS, JS, görseller)
7
- * jskelet start prod sunucu (build eksikse önce üretir)
5
+ * jskelet dev [--murder] build watch + sunucu, canlı yenileme, dev overlay
6
+ * jskelet build tek seferlik prod build (fontlar, sprite, CSS, JS, görseller)
7
+ * jskelet start [--murder] prod sunucu (build eksikse önce üretir)
8
8
  * jskelet init bulunduğun dizine minimal iskelet kurar
9
9
  * jskelet generate feature / page / island iskeleti
10
+ * jskelet migrate Next.js App Router → JSkelet codemod
10
11
  *
11
12
  * Alt komutlar ayrı süreçlerde çalışır. Sebep: `dev` iki uzun ömürlü süreci
12
13
  * (build watch + sunucu) yönetiyor ve sunucunun ESM resolve hook'larına
@@ -102,15 +103,28 @@ switch (command) {
102
103
  break;
103
104
  }
104
105
 
106
+ case "migrate": {
107
+ const { migrate } = await import("../src/migrate.mjs");
108
+ try {
109
+ await migrate(process.cwd(), rest);
110
+ } catch (error) {
111
+ process.stderr.write(`${error instanceof Error ? error.message : error}\n`);
112
+ process.exit(1);
113
+ }
114
+ break;
115
+ }
116
+
105
117
  default: {
106
118
  const known = command ? `unknown command: ${command}\n\n` : "";
107
119
  process.stderr.write(
108
- `${known}usage: jskelet <dev|build|start|init|generate>\n\n` +
109
- " dev build watch + server (live reload, dev overlay)\n" +
110
- " build production build\n" +
111
- " start production server\n" +
112
- " init scaffold a minimal skeleton in the current directory\n" +
113
- " generate scaffold feature | page | island\n",
120
+ `${known}usage: jskelet <dev|build|start|init|generate|migrate> [options]\n\n` +
121
+ " dev [--murder] build watch + server (live reload, dev overlay)\n" +
122
+ " build production build\n" +
123
+ " start [--murder] production server\n" +
124
+ " init scaffold a minimal skeleton in the current directory\n" +
125
+ " generate scaffold feature | page | island\n" +
126
+ " migrate Next.js App Router → JSkelet codemod (scan | apply | config)\n\n" +
127
+ " --murder if the listen port is busy, kill the listener and start\n",
114
128
  );
115
129
  process.exit(command ? 1 : 0);
116
130
  }
@@ -249,11 +249,12 @@ resolve hook'larına (`--import`) süreç başlangıcında ihtiyaç duyması.
249
249
 
250
250
  | Komut | Ne yapar |
251
251
  | --- | --- |
252
- | `jskelet dev` | Build watch + sunucu, tek terminalde. Canlı yenileme, CSS hot-swap, dev overlay. `NODE_ENV=development`. |
252
+ | `jskelet dev` | Build watch + sunucu, tek terminalde. Canlı yenileme, CSS hot-swap, dev overlay. `NODE_ENV=development`. Port doluysa başlamaz; `--murder` dinleyiciyi öldürüp bağlar. |
253
253
  | `jskelet build` | Tek seferlik prod build: fontlar → ikon sprite → CSS → client JS → görseller → manifest → precompress. `NODE_ENV` verilmemişse `production`. |
254
- | `jskelet start` | Prod sunucu. Build çıktısı yoksa önce üretir. `NODE_ENV` verilmemişse `production`. |
254
+ | `jskelet start` | Prod sunucu. Build çıktısı yoksa önce üretir. `NODE_ENV` verilmemişse `production`. Port davranışı `dev` ile aynı (`--murder`). |
255
255
  | `jskelet init` | Bulunduğun dizine feature-first `.jsk` iskeleti kurar; var olan dosyalara dokunmaz. |
256
256
  | `jskelet generate` | `feature` / `page` / `island` iskeleti üretir. |
257
+ | `jskelet migrate` | Next.js App Router → JSkelet codemod (`scan` / `apply` / `config`). [11-tasima.md](./11-tasima.md). |
257
258
 
258
259
  Bilinmeyen bir komut ya da argümansız çağrı kullanım metnini basar.
259
260
 
@@ -280,7 +281,7 @@ yalnızca bu belirteçleri kullanın:
280
281
  | `jskelet/tags` | `link`, `image`, `icon`, `preloadImage`, `toKebab` |
281
282
  | `jskelet/log` | Konsol çıktısı yardımcıları (`banner`, `event`, `task`, `size`, `ms`, …) |
282
283
  | `jskelet/register` | `node --import jskelet/register` ile alias + uzantı hook'ları |
283
- | `jskelet/layout` | Framework'ün varsayılan `layout.ejs` dosyasının yolu |
284
+ | `jskelet/layout` | Framework'ün varsayılan `layout.jsk` dosyasının yolu |
284
285
 
285
286
  ## Sırada ne var
286
287
 
package/docs/02-mimari.md CHANGED
@@ -51,7 +51,7 @@ JSkelet bu gözlemi mimarinin merkezine alır:
51
51
  │ └─ withHtmlCache TTL + stale-while-revalidate
52
52
  │ └─ withUpstreamTracking
53
53
  │ └─ withRequestCache
54
- │ └─ controller → renderPage → EJS
54
+ │ └─ controller → renderPage → .jsk (veya legacy EJS)
55
55
  ├─ 404 → hooks.notFound()
56
56
  └─ hata yönetimi redirect/notFound + 500 fallback
57
57
  ```
@@ -254,8 +254,9 @@ teşhisi zor sorunlara dönüşüyor.
254
254
 
255
255
  ## Neden bu bağımlılık listesi
256
256
 
257
- Çalışma zamanı bağımlılıkları dörttür: `express`, `ejs`, `esbuild`,
258
- `tailwind-merge`. Geri kalan her şey (Tailwind, PostCSS, lightningcss, sharp,
257
+ Çalışma zamanı bağımlılıkları üçtür: `express`, `esbuild`,
258
+ `tailwind-merge`. `ejs` yalnızca legacy `.ejs` şablonları için opsiyonel peer'dır.
259
+ Geri kalan her şey (Tailwind, PostCSS, lightningcss, sharp,
259
260
  Phosphor ikonları) **opsiyonel peer bağımlılığıdır** ve yoksa ilgili build adımı
260
261
  atlanır.
261
262
 
@@ -210,7 +210,7 @@ Controller `async (ctx) => sayfa` biçimindedir ve şu alanları döndürebilir:
210
210
 
211
211
  | Alan | Tip | Varsayılan | Anlamı |
212
212
  | --- | --- | --- | --- |
213
- | `view` | `string` | — | `views/` altındaki şablon yolu, uzantısız: `"pages/home"` → `views/pages/home.ejs`. |
213
+ | `view` | `string` | — | `views/` altındaki şablon yolu, uzantısız: `"pages/home"` → `views/pages/home.jsk` (yoksa legacy `.ejs`). |
214
214
  | `data` | `object` | `{}` | Şablona local olarak geçen veriler. |
215
215
  | `metadata` | `object` | `{}` | `<head>` etiketlerine çevrilir; `hooks.metadata()` çıktısının üzerine biner. Şema: [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md). |
216
216
  | `status` | `number` | `200` | HTTP durum kodu. Yalnızca 200 önbelleğe yazılır. |
@@ -326,12 +326,17 @@ hata yöneticisi devreye girer, hatayı loglar ve framework'ün kendi hata sayfa
326
326
  `Cache-Control: no-store` ile döner. Durum kodu hatanın `statusCode` (ya da
327
327
  `status`) alanından okunur; 400–599 aralığında değilse 500 kullanılır.
328
328
 
329
- Framework'ün sayfası bilinçli olarak yalın: durum kodu, tek satır başlık ve tek
330
- satır açıklama. Marka adı, gezinme ya da hata ayrıntısı taşımaz — sunucunun içi
331
- ziyaretçiye açılmaz. Dil `brand.lang`ten gelir (`tr` ve `en` hazır, diğerleri
332
- `en`e düşer).
329
+ **Development** (`NODE_ENV=development`, yani `jskelet dev`): 5xx yanıtlarında
330
+ gömülü 500 sayfası ve `hooks.error()` atlanır; mesaj, yığın izi ve varsa
331
+ `cause` zinciri içeren bir teşhis sayfası döner. 4xx (404 vb.) development'ta
332
+ da her zamanki gibi durum sayfasıdır.
333
333
 
334
- Kendi sayfanı vermek için `hooks.error()`:
334
+ **Production**: framework'ün sayfası bilinçli olarak yalın — durum kodu, tek
335
+ satır başlık ve tek satır açıklama. Marka adı, gezinme ya da hata ayrıntısı
336
+ taşımaz; sunucunun içi ziyaretçiye açılmaz. Dil `brand.lang`ten gelir (`tr` ve
337
+ `en` hazır, diğerleri `en`e düşer).
338
+
339
+ Kendi sayfanı vermek için `hooks.error()` (yalnızca production / 4xx):
335
340
 
336
341
  ```js
337
342
  // jskelet.config.mjs
@@ -51,7 +51,7 @@ controller data → import edilmiş render(data, helpers) → HTML
51
51
  {/if}
52
52
 
53
53
  {#each items as item, i}
54
- <li data-i="{{ i }}">{{ item }}</li>
54
+ <li :data-i="i">{{ item }}</li>
55
55
  {/each}
56
56
 
57
57
  <Link href="/" text="Home" />
@@ -99,19 +99,30 @@ code --install-extension extensions/vscode-jsk
99
99
 
100
100
  Ayrıntılar uzantı README'sinde.
101
101
 
102
+ ### Yerleşik layout etiketleri
103
+
104
+ `.jsk` ifade dilinde `asset()` / `hasAsset()` çağrılamaz. Layout’ta stylesheet,
105
+ script ve JSON-LD döngüleri için yerleşikler:
106
+
107
+ | Etiket | Props | Çıktı |
108
+ | --- | --- | --- |
109
+ | `Stylesheets` | `styles` | `app.css` + sayfa sheet’leri (`data-jskelet-css`) |
110
+ | `BodyScripts` | `entries`, `devtools`, `devBasePath` | `main.js`, entry’ler, isteğe bağlı overlay |
111
+ | `JsonLd` | `items` (`structuredData`) | `application/ld+json` script’leri |
112
+
102
113
  ### EJS ile birlikte yaşam
103
114
 
104
115
  Aynı `view` id için derlenmiş `.jsk` varsa o kullanılır; yoksa `.ejs` dosyası
105
- EJS ile render edilir. Mevcut uygulamalar değişmeden çalışır. `jskelet init`
106
- yeni iskeleti `.jsk` ile kurar.
116
+ **yalnızca `ejs` peer’i kuruluysa** render edilir. `jskelet init` yeni iskeleti
117
+ `.jsk` ile kurar.
107
118
 
108
- ## EJS motoru (legacy)
119
+ ## EJS motoru (legacy peer)
109
120
 
110
- EJS hâlâ desteklenir. Motor ilk render'da bir kez kurulur; bileşen taraması
111
- dosya sistemine dokunduğu için her istekte yapılamaz ve config yüklenmeden
112
- hesaplanamaz.
121
+ EJS opsiyonel peer bağımlılıktır (`npm i ejs`). `.jsk`-only uygulamalar kurmak
122
+ zorunda değildir. Bir `.ejs` view veya layout istendiğinde paket uygulamadan
123
+ yüklenir; yoksa göç yolunu gösteren bir hata fırlatılır.
113
124
 
114
- Ayarlar:
125
+ Motor ilk EJS render’da bir kez kurulur. Ayarlar:
115
126
 
116
127
  | Ayar | Değer | Sebebi |
117
128
  | --- | --- | --- |
@@ -120,9 +131,7 @@ Ayarlar:
120
131
  | `rmWhitespace` | `true` | çıktı boyutu |
121
132
  | `async` | `true` | şablon içinde `await` kullanılabilir |
122
133
 
123
- Gömülü kullanımlar (test, script) için `resetRenderEngine()` dışa açık: bileşen
124
- dosyaları değişince kaydı yeniler. Dev sunucusu süreci yeniden başlattığı için
125
- normal akışta gerekmez.
134
+ Gömülü kullanımlar (test, script) için `resetRenderEngine()` dışa açık.
126
135
 
127
136
  ## Layout
128
137
 
@@ -130,50 +139,33 @@ normal akışta gerekmez.
130
139
 
131
140
  1. `jskelet.config.mjs` → `layout` verilmişse o kullanılır. Yol, **views
132
141
  dizininin üst dizinine** göre çözülür: `views` varsayılansa
133
- `layout: "views/ozel.ejs"` → `<root>/views/ozel.ejs`.
142
+ `layout: "views/ozel.jsk"` → `<root>/views/ozel.jsk`.
134
143
  2. Verilmemişse `views/layout.jsk` (derlenmiş) varsa o kullanılır.
135
- 3. Yoksa `views/layout.ejs` varsa o kullanılır.
144
+ 3. Yoksa `views/layout.ejs` varsa o kullanılır (EJS peer gerekir).
136
145
  4. O da yoksa framework'ün kendi minimal layout'u kullanılır
137
- (`node_modules/jskelet/src/templates/layout.ejs`, ayrıca
138
- `jskelet/layout` belirteciyle de erişilebilir).
146
+ (`node_modules/jskelet/src/templates/layout.jsk`, `jskelet/layout`;
147
+ legacy kopya `jskelet/layout/ejs`).
139
148
 
140
149
  Üçüncü seçenek yeni bir projenin tek route ile çalışabilmesi için var. Kendi
141
- layout'unuza geçmenin en pratik yolu o dosyayı `views/layout.ejs` olarak
150
+ layout'unuza geçmenin en pratik yolu o dosyayı `views/layout.jsk` olarak
142
151
  kopyalamaktır.
143
152
 
144
153
  ### Framework'ün varsayılan layout'u
145
154
 
146
- ```ejs
155
+ ```jsk
147
156
  <!DOCTYPE html>
148
- <html lang="<%= lang %>">
157
+ <html :lang="lang">
149
158
  <head>
150
159
  <meta charset="utf-8">
151
160
  <meta name="viewport" content="width=device-width, initial-scale=1">
152
- <%- extraHead %>
153
- <% if (hasAsset('app.css')) { %>
154
- <link rel="stylesheet" href="<%= asset('app.css') %>" data-jskelet-css="app.css">
155
- <% } %>
156
- <% styles.forEach(function (sheet) { %>
157
- <% if (hasAsset(sheet)) { %>
158
- <link rel="stylesheet" href="<%= asset(sheet) %>" data-jskelet-css="<%= sheet %>">
159
- <% } %>
160
- <% }); %>
161
- <%- headMeta %>
162
- <% structuredData.forEach(function (item) { %>
163
- <script type="application/ld+json"><%- jsonScript(item) %></script>
164
- <% }); %>
161
+ {{{ extraHead }}}
162
+ <Stylesheets :styles="styles" />
163
+ {{{ headMeta }}}
164
+ <JsonLd :items="structuredData" />
165
165
  </head>
166
- <body class="<%= bodyClass %>">
167
- <%- body %>
168
- <% if (hasAsset('main.js')) { %>
169
- <script type="module" src="<%= asset('main.js') %>"></script>
170
- <% } %>
171
- <% entries.forEach(function (entry) { %>
172
- <script type="module" src="<%= asset(entry) %>"></script>
173
- <% }); %>
174
- <% if (devtools) { %>
175
- <script type="module" src="<%= devBasePath %>/overlay.js"></script>
176
- <% } %>
166
+ <body :class="bodyClass">
167
+ {{{ body }}}
168
+ <BodyScripts :entries="entries" :devtools="devtools" :devBasePath="devBasePath" />
177
169
  </body>
178
170
  </html>
179
171
  ```
@@ -187,7 +179,7 @@ Dikkat edilecek noktalar:
187
179
  sheet'leri de aynı şekilde basılır. Build çalışmadıysa `hasAsset` false olur
188
180
  ve etiket hiç basılmaz.
189
181
  - **`hasAsset` kontrolleri** build eksikken sayfanın 404 veren dosyaları
190
- istememesini sağlar.
182
+ istememesini sağlar (`Stylesheets` / `BodyScripts` içinde).
191
183
  - **Devtools script'i** yalnızca `NODE_ENV=development` iken basılır; prod
192
184
  çıktısında hiç yoktur.
193
185
 
@@ -118,25 +118,29 @@ Pratik sonucu: bir modal'ı `hidden` başlatabilirsiniz, island'ı yine bağlan
118
118
  ```
119
119
  client/
120
120
  ├── entries/
121
- │ ├── main.js her sayfada yüklenen ortak bootstrap
121
+ │ ├── main.js her sayfada yüklenen ortak bootstrap (veya main.ts)
122
122
  │ └── chart.js yalnızca isteyen sayfalarda
123
123
  └── islands/
124
- ├── counter.js
124
+ ├── counter.ts .js veya .ts
125
125
  └── chart.js
126
126
  ```
127
127
 
128
- `client/entries/*.js` içindeki **her dosya bir esbuild entry'sidir**. `main.js`
129
- layout tarafından her sayfada yüklenir (manifest'te varsa). Ek entry'ler
130
- yalnızca onları isteyen sayfalarda yüklenir:
128
+ `client/entries/*.{js,ts,mts}` içindeki **her dosya bir esbuild entry'sidir**.
129
+ `main.js` (veya `main.ts`) layout tarafından her sayfada yüklenir (manifest'te
130
+ varsa). Ek entry'ler yalnızca onları isteyen sayfalarda yüklenir. Aynı stem için
131
+ iki uzantı (`main.js` + `main.ts`) build hatasıdır.
131
132
 
132
133
  ```js
133
- // controller
134
+ // controller — manifest anahtarı her zaman *.js kalır
134
135
  return { view: "pages/markets", entries: ["chart.js"] };
135
136
  ```
136
137
 
137
138
  Layout `entries` dizisindeki her adı `asset(entry)` ile çözüp bir
138
- `<script type="module">` basar. Ad manifest anahtarıdır, yani dosya adının
139
- kendisi (`chart.js`), hash'li hâli değil.
139
+ `<script type="module">` basar. Ad manifest anahtarıdır (`chart.js`), kaynak
140
+ dosya `chart.ts` olsa bile hash'siz anahtar `.js` kalır.
141
+
142
+ Paylaşılan `@/lib` modülleri sunucuda da import ediliyorsa **`.js` kalsın** —
143
+ Node runtime `.ts` çözmez; `.ts` yalnızca esbuild client hattında derlenir.
140
144
 
141
145
  Kod bölme (`splitting: true`) açık: iki entry'nin paylaştığı modüller ortak bir
142
146
  chunk'a çıkar ve iki kez indirilmez.
@@ -51,7 +51,7 @@ export default {
51
51
  lang: "tr",
52
52
  },
53
53
 
54
- layout: "views/layout.ejs",
54
+ layout: "views/layout.jsk",
55
55
  routes: ["./routes/10-pages.mjs", "./routes/99-catch-all.mjs"],
56
56
  trailingSlash: false,
57
57
 
@@ -88,7 +88,7 @@ export default {
88
88
  watch: ["data"],
89
89
 
90
90
  fonts: [{ family: "Inter", weights: [400, 600, 700] }],
91
- icons: { scan: ["views", "client", "routes", "lib"] },
91
+ icons: { dir: "icons", scan: ["views", "client", "routes", "lib"] },
92
92
  images: { widths: [400, 800, 1200], quality: 78, skip: ["indirmeler"] },
93
93
  clientEnv: ["PUBLIC_WS_URL"],
94
94
 
@@ -208,26 +208,32 @@ session id taşımak için handoff köprüsünü açar.
208
208
 
209
209
  | Alan | Tip | Varsayılan | Anlamı |
210
210
  | --- | --- | --- | --- |
211
- | `crossSubdomainHandoff` | `boolean \| object` | `false` | `true` veya `{ ttlSeconds?, path?, maxValueBytes? }` → `POST /_jskelet/auth/handoff` + `?handoff=` redeem |
211
+ | `crossSubdomainHandoff` | `boolean \| object` | `false` | Açıkken `POST /_jskelet/auth/handoff` + `?handoff=` redeem. Object: `allowedCookieNames` (zorunlu), `ttlSeconds?`, `path?`, `maxValueBytes?`, `maxPendingTickets?`, `maxMintsPerIpPerMinute?` |
212
212
 
213
213
  ```js
214
214
  auth: {
215
- crossSubdomainHandoff: { ttlSeconds: 60 },
215
+ crossSubdomainHandoff: {
216
+ allowedCookieNames: ["sid"],
217
+ ttlSeconds: 60,
218
+ },
216
219
  },
217
220
  ```
218
221
 
219
- Ayrıntı ve `window.name` yedeği: [12-panel-ve-oturum.md](./12-panel-ve-oturum.md).
222
+ Mint uç noktası CSRF middleware'inden **sonra** mount edilir (origin kontrolü).
223
+ Cookie adı allowlist dışındaysa veya RFC 6265 token değilse 400. Ayrıntı:
224
+ [12-panel-ve-oturum.md](./12-panel-ve-oturum.md).
220
225
 
221
226
  ## `layout`
222
227
 
223
228
  **Tip:** `string` — **Varsayılan:** yok (otomatik çözüm)
224
229
 
225
- Layout `.ejs` dosyasının yolu. Verilen değer **views dizininin üst dizinine**
226
- göre çözülür, yani varsayılan `views` ile `"views/ozel.ejs"` →
227
- `<root>/views/ozel.ejs`.
230
+ Layout dosyasının yolu (`.jsk` veya legacy `.ejs`). Verilen değer **views
231
+ dizininin üst dizinine** göre çözülür, yani varsayılan `views` ile
232
+ `"views/ozel.jsk"` → `<root>/views/ozel.jsk`.
228
233
 
229
- Verilmezse sırayla: `views/layout.jsk`, `views/layout.ejs`, yoksa framework'ün
230
- minimal layout'u. Ayrıntı: [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md).
234
+ Verilmezse sırayla: `views/layout.jsk`, `views/layout.ejs` (legacy), yoksa
235
+ framework'ün `src/templates/layout.jsk` varsayılanı. Ayrıntı:
236
+ [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md).
231
237
 
232
238
  ## `routes`
233
239
 
@@ -328,7 +334,7 @@ var.
328
334
  | `trustProxy` | `boolean` | `true` | Express'in `trust proxy` ayarı. Ters proxy arkasında doğru protokol ve istemci IP'si için gerekli. |
329
335
  | `cookieSecret` | `string \| null` | `null` | İmzalı cookie sırrı. Verilmezse `JSKELET_SECRET` okunur. |
330
336
  | `csrf.enabled` | `boolean` | `true` | Origin/`Sec-Fetch-Site` kontrolü. |
331
- | `csrf.token` | `boolean` | `false` | Çift gönderim token'ı katmanı. |
337
+ | `csrf.token` | `boolean` | `false` | Çift gönderim token'ı katmanı. Cookie oturumlu formlarda **açın**. |
332
338
  | `csrf.allowedOrigins` | `string[]` | `[]` | Kendi host'umuzun yanında kabul edilen origin'ler. |
333
339
  | `csrf.exclude` | `string[]` | `[]` | Kontrolden muaf yollar; `source` desen sözdizimi. |
334
340
  | `csrf.cookieName` | `string` | `"csrf_token"` | Token cookie'sinin adı. |
@@ -336,14 +342,17 @@ var.
336
342
  | `csrf.headerName` | `string` | `"x-csrf-token"` | Token'ın kabul edildiği başlık. |
337
343
 
338
344
  `trustProxy` doğrudan internete açık bir sunucuda **kapatılmalı**: açıkken
339
- istemci kendi `X-Forwarded-For` başlığını uydurabilir ve rate limit ile audit
340
- log yanlış adresi görür.
345
+ istemci kendi `X-Forwarded-For` / `X-Forwarded-Proto` / Host başlığını
346
+ uydurabilir; rate limit, admin IP allowlist, Secure cookie ve cache `vary.host`
347
+ yanlış adresi görür. Ters proxy (nginx, Caddy, Cloudflare) arkasındaysa `true`
348
+ doğru varsayılandır.
341
349
 
342
350
  CSRF kontrolü yalnızca çapraz site olduğu **belli** olan istekleri reddeder —
343
351
  `Origin` uyuşmuyorsa ya da `Sec-Fetch-Site: cross-site` geldiyse. İkisi de yoksa
344
352
  istek geçer, çünkü tarayıcılar çapraz origin bir POST'ta `Origin`'i her zaman
345
- gönderirken webhook'lar hiç göndermez. Yine de tarayıcıdan gelmeyen uçları
346
- `csrf.exclude` listesine yazmak niyeti okunur kılıyor.
353
+ gönderirken webhook'lar hiç göndermez. Cookie ile oturum açan panel/form
354
+ uygulamalarında `csrf.token: true` + `csrfField()` ikinci katmandır; webhook
355
+ uçlarını `csrf.exclude` listesine yazın.
347
356
 
348
357
  ## `navigation`
349
358
 
@@ -487,21 +496,27 @@ fonts: [
487
496
 
488
497
  ## `icons`
489
498
 
490
- **Tip:** `{ scan?: string[] } | false` — **Varsayılan:** `{}`
499
+ **Tip:** `{ scan?: string[], dir?: string } | false` — **Varsayılan:** `{ dir: "icons" }`
491
500
 
492
- Phosphor SVG sprite üretimi.
501
+ SVG ikon sprite üretimi. Kaynak **XOR** seçilir: `icons.dir` dizini varsa
502
+ yalnızca oradaki düz SVG'ler; yoksa `@phosphor-icons/core` (kuruluysa).
493
503
 
494
504
  | Değer | Sonuç |
495
505
  | --- | --- |
496
- | `{}` (varsayılan) | Sprite üretilir; taranan dizinler `["views", "client", "routes", "lib", "features", "shared"]` |
506
+ | `{}` (varsayılan) | `dir: "icons"`; taranan dizinler `["views", "client", "routes", "lib", "features", "shared"]` |
507
+ | `{ dir: "assets/icons" }` | Yerel SVG kökü değiştirilir |
497
508
  | `{ scan: [...] }` | Taranan dizinler değiştirilir |
498
509
  | `false` | Sprite adımı tamamen atlanır |
499
510
 
500
- `@phosphor-icons/core` uygulamanın `node_modules`'ünde yoksa adım sessizce
501
- atlanır. Ayrıntı: [08-build.md](./08-build.md).
511
+ Yerel dizin (varsa) düz dosya adları kullanır: `house.svg` → `house:regular`,
512
+ `house-bold.svg` → `house:bold`. Boş bir `icons/` dizini Phosphor'a düşmez —
513
+ dizini silmek fallback'i açar. Ayrıntı: [08-build.md](./08-build.md).
502
514
 
503
515
  ```js
504
- icons: { scan: ["views", "client", "routes", "lib", "content"] }
516
+ icons: {
517
+ dir: "icons",
518
+ scan: ["views", "client", "routes", "lib", "content"],
519
+ }
505
520
  ```
506
521
 
507
522
  ## `images`
@@ -534,7 +549,9 @@ icons: { scan: ["views", "client", "routes", "lib", "content"] }
534
549
 
535
550
  `false` verilirse görsel adımı hiç çalışmaz. Build adımı `sharp` gerektirir ve
536
551
  watch turunda hiç çalışmaz. Remote açıksa `sharp` **runtime**'da da gerekir;
537
- yoksa optimizer kaynak URL'ye 302 yönlendirir. Ayrıntı: [08-build.md](./08-build.md).
552
+ yoksa optimizer kaynak URL'ye 302 yönlendirir. Fetch, redirect'leri otomatik
553
+ takip etmez: her hop `allowHosts` ve private adres kontrolünden geçer.
554
+ Ayrıntı: [08-build.md](./08-build.md).
538
555
 
539
556
  ```js
540
557
  images: {
@@ -567,6 +584,9 @@ clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
567
584
  ```
568
585
 
569
586
  **Buraya gizli anahtar koymayın** — değerler bundle'da düz metin olarak durur.
587
+ İsimlerinde `SECRET`, `PASSWORD`, `TOKEN`, `API_KEY`, `PRIVATE` vb. geçen
588
+ anahtarlar build sırasında **reddeder** (`PUBLIC` / `PUBLISHABLE` içerenler
589
+ muaf).
570
590
 
571
591
  ## `headers()`
572
592
 
@@ -575,7 +595,8 @@ clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
575
595
 
576
596
  Yol desenine göre yanıt başlıkları. Framework yalnızca statik dosyalara uzun
577
597
  ömürlü cache yazar; bunun dışındaki her başlık (CSP, COOP, HSTS,
578
- X-Frame-Options…) buradan gelir ve varsayılanların üstüne biner.
598
+ X-Frame-Options…) buradan gelir ve varsayılanların üstüne biner. Üretim
599
+ sitelerinde en azından aşağıdaki güvenlik başlıklarını tanımlayın.
579
600
 
580
601
  Eşleşen **tüm** kurallar uygulanır (redirect'lerin aksine ilk eşleşmede
581
602
  durulmaz), sırayla; aynı başlığı iki kural yazarsa sonraki kazanır.
@@ -590,11 +611,18 @@ async headers() {
590
611
  source: "/:path*",
591
612
  headers: [
592
613
  { key: "X-Frame-Options", value: "SAMEORIGIN" },
614
+ { key: "X-Content-Type-Options", value: "nosniff" },
593
615
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
616
+ {
617
+ key: "Permissions-Policy",
618
+ value: "camera=(), microphone=(), geolocation=()",
619
+ },
594
620
  {
595
621
  key: "Content-Security-Policy",
596
- value: "default-src 'self'; img-src 'self' https://cdn.ornek.com data:",
622
+ value: "default-src 'self'; img-src 'self' https://cdn.ornek.com data:; script-src 'self'",
597
623
  },
624
+ // Yalnızca HTTPS terminasyonu sizin kontrolünüzdeyse:
625
+ // { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
598
626
  ],
599
627
  },
600
628
  {
@@ -1075,7 +1103,7 @@ basılmaz.
1075
1103
  | Değişken | Kim okur | Varsayılan | Anlamı |
1076
1104
  | --- | --- | --- | --- |
1077
1105
  | `NODE_ENV` | her yer | `production` (start/build), `development` (dev) | Dev overlay, EJS cache, manifest yeniden okuma, route hata davranışı ve prewarm varsayılanlarını belirler. `jskelet dev` bunu kendisi ayarlar — `cross-env` gerekmez. |
1078
- | `PORT` | `startServer` | `3000` | Dinlenecek port |
1106
+ | `PORT` | `startServer` | `3000` | Dinlenecek port. Doluysa süreç başlamaz; `jskelet start|dev --murder` dinleyiciyi öldürür |
1079
1107
  | `HOST` | `startServer` | `::` | Bağlanılacak arayüz. Varsayılan çift yığın dinler (IPv6 + IPv4); IPv6 yoksa `0.0.0.0`'a düşer |
1080
1108
  | `JSKELET_SECRET` | `jskelet/cookies` | — | İmzalı cookie sırrı. `security.cookieSecret` verilmediğinde buradan okunur; ikisi de yoksa imzalı cookie API'si hata verir. [12](./12-panel-ve-oturum.md) |
1081
1109
  | `DEV_TOKEN` | `devGate`, `prewarm` | — | Ayarlıysa token taşımayan her isteğe 404 döner. Isıtma token'ı çerez olarak taşır. [09](./09-dev-araclari.md) |
package/docs/08-build.md CHANGED
@@ -167,8 +167,9 @@ etmezdi. Değişiklikler 120 ms birleştirilir.
167
167
 
168
168
  ## Client JS — esbuild
169
169
 
170
- `client/entries/*.js` içindeki her `.js` dosyası bir entry'dir. Dizin yoksa ya da
171
- boşsa adım atlanır.
170
+ `client/entries/*.{js,ts,mts}` içindeki her kaynak dosya bir entry'dir (`.tsx`
171
+ yok). Manifest anahtarı her zaman `*.js` olur (`main.ts` → `main.js`). Aynı stem
172
+ için birden fazla uzantı build hatasıdır. Dizin yoksa ya da boşsa adım atlanır.
172
173
 
173
174
  esbuild ayarları:
174
175
 
@@ -178,7 +179,7 @@ esbuild ayarları:
178
179
  | `format` | `esm` | `type="module"` script'ler |
179
180
  | `target` | `chrome111`, `edge111`, `firefox111`, `safari16.4` | ESM + dinamik import + `IntersectionObserver` island modelinin alt sınırı; daha eskisine transpile etmek çıktıyı büyütüp hiçbir ziyaretçi kazandırmıyor |
180
181
  | `minify` | `true` | — |
181
- | `sourcemap` | `true` | Tarayıcıda teşhis |
182
+ | `sourcemap` | yalnızca `NODE_ENV=development` | Prod'da `.map` dosyaları `public/assets` altında yayınlanmaz |
182
183
  | `entryNames` | `[name].[hash]` | `immutable` cache |
183
184
  | `chunkNames` | `chunks/[name].[hash]` | — |
184
185
  | `legalComments` | `none` | — |
@@ -189,16 +190,19 @@ esbuild ayarları:
189
190
  ### `@/` alias'ı
190
191
 
191
192
  esbuild tarafında `@/` proje köküne çözülür ve uzantı tamamlama yapılır
192
- (`.js`, `.mjs`, `.json`, `/index.js`). Node tarafındaki `alias-hooks.mjs` ile
193
- aynı davranış, böylece `lib/` altındaki modüller hem sunucuda hem tarayıcıda
194
- aynı import stilini kullanabilir.
193
+ (`.js`, `.mjs`, `.ts`, `.mts`, `.json`, `/index.js`, `/index.ts`). Node
194
+ `alias-hooks.mjs` sunucuda yalnızca `.js` / `.mjs` / `.json` çözer; paylaşılan
195
+ `@/lib` dosyaları bu yüzden `.js` kalmalıdır. Client-only `.ts` import'ları
196
+ esbuild hattında çalışır.
195
197
 
196
198
  ### `clientEnv` gömülmesi
197
199
 
198
200
  Tarayıcıda `process` yoktur; sunucuyla paylaşılan modüller yine de `process.env`
199
201
  okur. `config.clientEnv` ile bildirilen anahtarlar ve `NODE_ENV` build zamanında
200
202
  tek nesne olarak define edilir, yani listede olmayan bir anahtar okunduğunda
201
- çökme yerine `undefined` döner. Ayrıntı: [07-yapilandirma.md](./07-yapilandirma.md).
203
+ çökme yerine `undefined` döner. İsimleri secret benzeri olan anahtarlar
204
+ (`SECRET`, `API_KEY`, …) build'i düşürür; `PUBLIC` / `PUBLISHABLE` içerenler
205
+ muaf. Ayrıntı: [07-yapilandirma.md](./07-yapilandirma.md).
202
206
 
203
207
  ### Manifest anahtarları
204
208
 
@@ -249,16 +253,32 @@ bu dosyalara otomatik olarak `immutable` cache yazılır.
249
253
 
250
254
  ## İkon sprite
251
255
 
252
- `@phosphor-icons/core` içindeki tek tek SVG'lerden, **yalnızca kaynakta
253
- kullanılan** ikonlar için `<symbol>` seti üretir. Tüm seti göndermek 1500+ ikon,
254
- yani birkaç megabayt; kullanım taraması sprite'ı tipik olarak 10-30 sembolde
255
- tutuyor.
256
+ **Yalnızca kaynakta kullanılan** ikonlar için bir `<symbol>` seti üretir. Tüm
257
+ seti göndermek 1500+ ikon, yani birkaç megabayt; kullanım taraması sprite'ı
258
+ tipik olarak 10-30 sembolde tutuyor. Çıktı hash'li `sprite.svg` olarak
259
+ `public/assets/` altına yazılır ve precompress kapsamına girer.
260
+
261
+ Kaynak **XOR** seçilir — ikisi birleştirilmez:
262
+
263
+ 1. `icons.dir` (varsayılan `icons/`) **dizin olarak varsa** yalnızca oradaki
264
+ düz SVG'ler. Boş dizin Phosphor'a düşmez; fallback için dizini silin.
265
+ 2. Aksi hâlde `@phosphor-icons/core` (uygulamanın `node_modules`'ünden). Kurulu
266
+ değilse adım sessizce atlanır.
267
+
268
+ Yerel dosya adları:
269
+
270
+ | Dosya | Sprite anahtarı |
271
+ | --- | --- |
272
+ | `icons/house.svg` | `house:regular` |
273
+ | `icons/house-regular.svg` | `house:regular` |
274
+ | `icons/arrow-right-bold.svg` | `arrow-right:bold` |
256
275
 
257
276
  - Sembol id'si: `<kebab-ad>-<weight>`, örn. `arrow-right-bold`.
258
- - Paket **uygulamanın** `node_modules`'ünden çözülür (ikon seti uygulamanın
259
- devDependency'si); kurulu değilse adım sessizce atlanır.
260
- - Taranan dizinler varsayılan olarak `views`, `client`, `routes`, `lib`;
261
- `icons.scan` ile değiştirilebilir. Taranan uzantılar: `.ejs`, `.js`, `.mjs`.
277
+ - `viewBox` kaynak SVG'den `<symbol>`'e taşınır; yoksa `0 0 256 256`
278
+ (Phosphor ve `icon()` ile uyum için önerilen kutu).
279
+ - Taranan dizinler varsayılan olarak `views`, `client`, `routes`, `lib`,
280
+ `features`, `shared`; `icons.scan` ile değiştirilebilir. Taranan uzantılar:
281
+ `.ejs`, `.jsk`, `.js`, `.mjs`, `.ts`, `.mts`.
262
282
  - Ağırlıklar: `thin`, `light`, `regular`, `bold`, `fill`, `duotone`. Tanınmayan
263
283
  bir ağırlık `regular` sayılır.
264
284
 
@@ -285,7 +305,7 @@ Bu uyarıyı görürseniz ya adı sabit yazın, ya `icons.scan` listesine ilgili
285
305
  dizini ekleyin, ya da adı bir yapılandırma alanında `icon: "XLogo"` biçiminde
286
306
  tutun.
287
307
 
288
- Phosphor'da bulunamayan adlar build sonunda özet olarak uyarılır:
308
+ Kaynakta bulunamayan adlar build sonunda özet olarak uyarılır:
289
309
  `N icons missing → …`
290
310
 
291
311
  ## Görsel optimizasyonu
@@ -323,7 +343,9 @@ orijinal dosyaya döner. Watch turunda hiç çalışmaz.
323
343
  `images.remote.allowHosts` verilirse `createApp` `/_jskelet/image` ucunu
324
344
  mount eder. CMS / CDN kapakları build'e girmediği için `image()` bu host'lardaki
325
345
  URL'leri `?url=&w=` biçiminde yeniden yazar; uç sharp ile webp üretir ve
326
- `.jskelet/image-cache/` altına yazar. Ayrıntı: [07-yapilandirma.md](./07-yapilandirma.md).
346
+ `.jskelet/image-cache/` altına yazar. Upstream fetch redirect'leri elle takip
347
+ edilir: her hop allowlist + private IP / DNS kontrolünden geçer (açık redirect
348
+ SSRF kapalı). Ayrıntı: [07-yapilandirma.md](./07-yapilandirma.md).
327
349
 
328
350
  ## Precompress
329
351
 
@@ -355,7 +377,7 @@ Bu dosyaları `staticPrecompressed` middleware'i servis eder; kopya yoksa istek
355
377
  | `tailwindcss` | CSS (peer) | Tailwind direktifleri çözülemez |
356
378
  | `lightningcss` | CSS minifikasyonu | Tailwind çıktısı kullanılır, birkaç kB daha büyük |
357
379
  | `sharp` | Görsel optimizasyonu | Adım atlanır; `image()` orijinali kullanır |
358
- | `@phosphor-icons/core` | İkon sprite | Adım atlanır; `icon()` boş `<use>` üretir |
380
+ | `@phosphor-icons/core` | İkon sprite (yerel `icons/` yoksa) | Adım atlanır; `icon()` boş `<use>` üretir |
359
381
 
360
382
  CSS kullanmayacaksanız `paths.styles` dosyasını hiç oluşturmayın: adım uyarıyla
361
383
  atlanır ve postcss'e ihtiyaç kalmaz.