jskelet 0.5.5 → 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 +95 -0
  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 +40 -18
  11. package/docs/08-build.md +15 -9
  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 +40 -20
  23. package/docs/en/08-build.md +16 -10
  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 +11 -1
  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 +1 -1
  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/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
 
@@ -274,7 +278,7 @@ Yerel dosya adları:
274
278
  (Phosphor ve `icon()` ile uyum için önerilen kutu).
275
279
  - Taranan dizinler varsayılan olarak `views`, `client`, `routes`, `lib`,
276
280
  `features`, `shared`; `icons.scan` ile değiştirilebilir. Taranan uzantılar:
277
- `.ejs`, `.jsk`, `.js`, `.mjs`.
281
+ `.ejs`, `.jsk`, `.js`, `.mjs`, `.ts`, `.mts`.
278
282
  - Ağırlıklar: `thin`, `light`, `regular`, `bold`, `fill`, `duotone`. Tanınmayan
279
283
  bir ağırlık `regular` sayılır.
280
284
 
@@ -339,7 +343,9 @@ orijinal dosyaya döner. Watch turunda hiç çalışmaz.
339
343
  `images.remote.allowHosts` verilirse `createApp` `/_jskelet/image` ucunu
340
344
  mount eder. CMS / CDN kapakları build'e girmediği için `image()` bu host'lardaki
341
345
  URL'leri `?url=&w=` biçiminde yeniden yazar; uç sharp ile webp üretir ve
342
- `.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).
343
349
 
344
350
  ## Precompress
345
351
 
@@ -21,6 +21,10 @@ jskelet dev
21
21
  bastırır) ve TTY varsa `JSKELET_COLOR=1` (borulanmış çıktıda renk zorlar)
22
22
  geçirilir.
23
23
 
24
+ Port (`PORT`, varsayılan `3000`) doluysa sunucu **başlamaz**; hata satırında
25
+ PID ve `--murder` ipucu vardır. `jskelet dev --murder` dinleyiciyi öldürüp aynı
26
+ porta bağlanır (başka bir terminalde unutulmuş süreç için).
27
+
24
28
  Açılış sırası: banner → build adımları → sunucu hazır → `Ready` özeti. Özet hem
25
29
  build hem sunucu hazır olduğunda basılır; aksi hâlde arkadan gelen build
26
30
  satırlarının arasında kalıyordu.
@@ -70,7 +74,7 @@ WATCH_DIRS = [
70
74
  Ayrıca `jskelet.config.mjs` dosyasının kendisi izlenir: config değişince hem
71
75
  sunucu hem build yeni ayarlarla açılmalı.
72
76
 
73
- İzlenen uzantılar: `.js`, `.mjs`, `.json`, `.ejs`.
77
+ İzlenen uzantılar: `.js`, `.mjs`, `.json`, `.jsk`, `.ejs`.
74
78
 
75
79
  `views` de izlenir çünkü bileşenlerin çoğu `views/components/**.js` içinde ve bu
76
80
  modüller sunucuya bir kez import edildiği için, restart olmadan yapılan
@@ -23,6 +23,10 @@ kendisi çalıştırır. Docker imajında build zaten yapıldığı için bu bir
23
23
  amaç `npm start`ı doğrudan çalıştıran birinin stilsiz bir sayfayla
24
24
  karşılaşmaması.
25
25
 
26
+ Port doluysa süreç **başlamaz** (PID + ipucu). `jskelet start --murder` o
27
+ porttaki dinleyiciyi öldürüp bağlar — geliştirmede unutulmuş bir süreç için;
28
+ üretim orkestratöründe genelde gerekmez.
29
+
26
30
  Sunucu hazır olduğunda tek satır basar:
27
31
 
28
32
  ```
@@ -63,7 +67,8 @@ değerin geçerli olduğunu belirsizleştirir; prod imajında `.env` bulundurmam
63
67
  temizidir.
64
68
 
65
69
  **Gizli anahtarlar `clientEnv` listesine konmamalıdır:** oradaki değerler client
66
- bundle'a düz metin olarak gömülür ([08-build.md](./08-build.md)).
70
+ bundle'a düz metin olarak gömülür ([08-build.md](./08-build.md)). Secret benzeri
71
+ isimler (`SECRET`, `API_KEY`, …) artık build'i düşürür.
67
72
 
68
73
  ## Docker
69
74
 
package/docs/11-tasima.md CHANGED
@@ -7,6 +7,29 @@ alt kümesine benzetildi — `next.config` sözdizimi, Metadata API, `notFound()
7
7
  `revalidate`, `cache()` gibi kavramlar tanıdık gelecek. Farkların *nedenleri*
8
8
  [02-mimari.md](./02-mimari.md)'de.
9
9
 
10
+ ## `jskelet migrate` (codemod)
11
+
12
+ App Router ağacına karşı codemod'u çalıştırın. Babel (`@babel/parser`,
13
+ `@babel/types`) JSkelet ile birlikte gelir — ek kurulum yok.
14
+
15
+ ```bash
16
+ npx jskelet migrate scan ../my-next-app
17
+ npx jskelet migrate apply ../my-next-app --out . --write
18
+ npx jskelet migrate config ../my-next-app --write
19
+ ```
20
+
21
+ | Komut | Ne yapar |
22
+ | --- | --- |
23
+ | `migrate` / `migrate scan` | Sayfa, layout, `"use client"` modülleri ve engelleri (iç içe layout, Server Actions, Suspense) listeler. |
24
+ | `migrate apply` | **Otomatik çeviri:** `page.*` → feature controller + `.jsk`; presentational bileşenler → `views/components/*.js`; client → island `mount()` iskeleti. Varsayılan dry-run; yazmak için `--write`. Üzerine yazmaz (çakışmada `.migrate` soneki). |
25
+ | `migrate config` | `next.config`'ten `jskelet.config.mjs` taslağı (`headers` / `redirects` / `rewrites`, `images.widths`, `NEXT_PUBLIC_*` → `clientEnv`). |
26
+
27
+ Bayraklar: `--out <dir>`, `--only pages,components,islands`, `--json`, `--strict` (partial/skipped → exit 1).
28
+
29
+ **Otomatik çevrilenler:** `className`, `{{ }}` / `{{{ }}}`, `{#if}` / `{#each}`, `next/image` → `<Image />`, `next/link` → `<Link />`, `dangerouslySetInnerHTML`, `revalidate`, basit controller prelude.
30
+
31
+ **Çevrilmeyenler (raporlanır):** React hooks, Server Actions, iç içe layout düzleştirme, Streaming/Suspense, client routing. Dosya başına `ok` / `partial` / `skipped`.
32
+
10
33
  ## Karşılık tablosu
11
34
 
12
35
  ### Yapılandırma
@@ -30,8 +53,8 @@ alt kümesine benzetildi — `next.config` sözdizimi, Metadata API, `notFound()
30
53
  | `app/page.js` (dosya bazlı routing) | `routes/*.mjs` içinde `app.get(...)` | Sıra açık yazılır ([03](./03-routing.md)) |
31
54
  | `app/[slug]/page.js` | `app.get("/:slug", route(...))` | Express desen sözdizimi |
32
55
  | `params`, `searchParams` | `ctx.params`, `ctx.query` | Controller'ın tek argümanı |
33
- | `layout.js` | `views/layout.ejs` + `hooks.layoutContext()` | Tek layout; iç içe layout yok |
34
- | Sunucu bileşeni (RSC) | Controller + EJS şablonu + `views/components/**` | Fonksiyon HTML string döndürür |
56
+ | `layout.js` | `views/layout.jsk` + `hooks.layoutContext()` | Tek layout; iç içe layout yok |
57
+ | Sunucu bileşeni (RSC) | Controller + `.jsk` şablonu + `views/components/**` | Fonksiyon HTML string döndürür |
35
58
  | İstemci bileşeni (`"use client"`) | Island (`data-island` + `mount`) | Sayfanın tamamı hidre edilmez ([05](./05-islands.md)) |
36
59
  | `notFound()` | `notFound()` | Aynı ad, aynı kontrol akışı |
37
60
  | `redirect()` | `redirect()` (307) | Kalıcı için `permanentRedirect()` (308) |
@@ -87,10 +110,13 @@ Bunları taşıma planında baştan hesaba katın:
87
110
 
88
111
  - **React'in kendisi.** Bileşenler HTML string döndüren fonksiyonlara dönüşür.
89
112
  JSX yok, hook yok, sanal DOM yok.
90
- - **TypeScript.** Proje düz JS + JSDoc. `jsconfig.json` içinde `checkJs: true`
91
- ile editörden tip kontrolü alırsınız.
92
- - **İç içe layout'lar.** Tek bir layout var; ortak bölümleri EJS `include` ya da
93
- bileşen fonksiyonlarıyla paylaşırsınız.
113
+ - **TypeScript.** Framework kaynağı düz JS + JSDoc'tur ve tüketiciler için
114
+ `.d.ts` yayınlar. Client entry/island'lar `.ts` / `.mts` olabilir (esbuild tip
115
+ siler; manifest anahtarı `*.js` kalır). Sunucu route, hook ve
116
+ `jskelet.config.mjs` Node ESM JavaScript kalır — orada editör denetimi için
117
+ `jsconfig.json` içinde `checkJs: true` kullanın.
118
+ - **İç içe layout'lar.** Tek bir layout var; ortak bölümleri `{#include}` ya da
119
+ bileşen fonksiyonlarıyla paylaşırsınız (legacy EJS’te `include`).
94
120
  - **Streaming / Suspense / kısmi prerender.** Yanıt tek parça üretilir.
95
121
  - **İstemci tarafı yönlendirme.** Gezinme gerçek sayfa yüklemesidir. Sunucu HTML'i
96
122
  önbellekten geldiği için pratikte çok hızlıdır, ama SPA geçişleri yoktur.
@@ -169,12 +195,12 @@ export default function register(app, { route, notFound }) {
169
195
  }
170
196
  ```
171
197
 
172
- ```ejs
173
- <%# views/pages/article.ejs %>
198
+ ```jsk
199
+ {# views/pages/article.jsk #}
174
200
  <article class="wrapper">
175
- <h1 class="text-3xl font-bold"><%= article.title %></h1>
176
- <%- image({ src: article.cover, alt: article.title, priority: true, width: 1200, height: 630 }) %>
177
- <div><%- article.body %></div>
201
+ <h1 class="text-3xl font-bold">{{ article.title }}</h1>
202
+ <Image :src="article.cover" :alt="article.title" priority :width="1200" :height="630" />
203
+ <div>{{{ article.body }}}</div>
178
204
  </article>
179
205
  ```
180
206
 
@@ -188,12 +214,16 @@ de aynı yazıyı isterse tek upstream isteği yapılmasını sağlar
188
214
 
189
215
  Yeni bir dizinde `npx jskelet init` çalıştırın ve `jskelet dev`in açıldığını
190
216
  görün. Mevcut Next projesini olduğu gibi bırakın; taşıma paralel yürüsün.
217
+ İsterseniz önce `jskelet migrate scan <next-root>` ile sayfa ve engel listesine bakın.
191
218
 
192
219
  `jsconfig.json` içindeki `paths` alias'larınızı taşıyın — `@/` gibi önekler hem
193
220
  sunucuda hem bundle'da aynı şekilde çalışır ([02-mimari.md](./02-mimari.md)).
194
221
 
195
222
  ### 2. `next.config.mjs`'i çevir (1-2 saat)
196
223
 
224
+ `jskelet migrate config <next-root> --write` çoğunu taslaklar; ardından gözden
225
+ geçirin:
226
+
197
227
  `headers()`, `redirects()` ve `rewrites()` bölümleri neredeyse birebir kopyalanır.
198
228
  Desen sözdizimini kontrol edin: JSkelet `:slug`, `:path*`, `/a-:b` ve
199
229
  `/:path*.svg` biçimlerini destekler; daha karmaşık `path-to-regexp` ifadeleri
@@ -214,9 +244,9 @@ değildir; olduğu gibi kopyalanır. İki değişiklik yapın:
214
244
 
215
245
  ### 4. Layout'u kur (yarım gün)
216
246
 
217
- `app/layout.jsx`'i `views/layout.ejs`'e çevirin. Framework'ün varsayılan
218
- layout'unu (`node_modules/jskelet/src/templates/layout.ejs`) kopyalayıp
219
- üzerine yazmak en hızlı yol.
247
+ `app/layout.jsx`'i `views/layout.jsk`'e çevirin (veya `migrate apply` taslağını
248
+ kullanın). Framework'ün varsayılan layout'unu (`jskelet/layout` → `.jsk`)
249
+ kopyalayıp üzerine yazmak en hızlı yol.
220
250
 
221
251
  `layout.jsx` içinde veri çekiyorsanız (navigasyon, site ayarları) bunu
222
252
  `hooks.layoutContext()` içine taşıyın: gövde render'ıyla paralel çalışır ve
@@ -227,6 +257,9 @@ Global metadata varsayılanlarını (`titleTemplate`, `siteUrl`, `description`)
227
257
 
228
258
  ### 5. Bileşenleri çevir (en uzun adım)
229
259
 
260
+ `jskelet migrate apply --only components --write` hooks'suz presentational
261
+ bileşenleri çevirir. Gerisini elle bitirin:
262
+
230
263
  Her React bileşeni bir fonksiyona dönüşür:
231
264
 
232
265
  ```jsx
@@ -260,8 +293,9 @@ Bileşenleri küçük ve saf tutun; veri çekmeyi controller'da bırakın.
260
293
 
261
294
  ### 6. Sayfaları taşı (sayfa başına saatler)
262
295
 
263
- Her `page.jsx` bir controller + bir EJS şablonuna bölünür. Sırayı düşünerek
264
- dosyalayın:
296
+ `jskelet migrate apply --only pages --write` her `page.*` dosyasını feature
297
+ controller + `.jsk` şablonuna böler. `partial` / `skipped` satırlarını gözden
298
+ geçirip TODO'ları bitirin. Sırayı düşünerek dosyalayın:
265
299
 
266
300
  ```
267
301
  routes/
@@ -337,7 +371,7 @@ redirect kurallarının doğruluğunu ölçmek için işe yarar.
337
371
  ## Taşıma sırasında sık yapılan hatalar
338
372
 
339
373
  - **`esc()` unutmak.** JSX'ten gelen alışkanlıkla `${value}` yazmak XSS demektir.
340
- Şablonlarda `<%= %>` (kaçışlı) ile `<%- %>` (ham) ayrımına dikkat edin.
374
+ `.jsk`'de `{{ }}` (kaçışlı) / `{{{ }}}` (ham); bileşenlerde `esc()` kendiniz.
341
375
  - **`@source` eklemeden yeni bir dizin açmak.** Sınıflar sessizce düşer.
342
376
  - **Yakalayıcı route'u yanlış sıraya koymak.** `/:slug` her zaman en sonda.
343
377
  - **Sayfanın tamamını island yapmak.** Kazanç sunucu HTML'inin tam olmasından
@@ -143,7 +143,9 @@ export default {
143
143
  sharedCookieRoots: [".investvio.com", ".localhost"],
144
144
  },
145
145
  auth: {
146
- crossSubdomainHandoff: true, // POST /_jskelet/auth/handoff
146
+ crossSubdomainHandoff: {
147
+ allowedCookieNames: ["sid"], // zorunlu allowlist
148
+ },
147
149
  },
148
150
  };
149
151
  ```
@@ -207,15 +209,19 @@ okunur. Yazımdan sonra **read-back** yapılır; tarayıcı Domain'i reddettiyse
207
209
 
208
210
  `auth.crossSubdomainHandoff` açıkken:
209
211
 
210
- 1. `POST /_jskelet/auth/handoff` `{ name, value, next }` → `{ url }` (`?handoff=` ekli)
212
+ 1. `POST /_jskelet/auth/handoff` `{ name, value, next }` → `{ url }` (`?handoff=` ekli).
213
+ Mint, CSRF middleware'inden **sonra** mount edilir; `name`
214
+ `allowedCookieNames` içinde ve RFC 6265 token olmalı.
211
215
  2. Hedef host'ta GET middleware bileti tek kullanımlık tüketir, cookie yazar
212
216
  (önce shared Domain, olmazsa host-only), `handoff` query'siz 303
213
217
 
214
218
  `next` yalnızca aynı `sharedCookieRoots` altındaki host'lara izinli. Bilet
215
- ~60 sn, süreç belleğinde. JWT URL'ye konmaz.
219
+ ~60 sn, süreç belleğinde; bekleyen bilet ve IP başına mint sınırı vardır.
220
+ JWT URL'ye konmaz.
216
221
 
217
222
  `window.name` köprüsü cookie'siz yedek: kaynakta `handoffViaWindowName`,
218
- hedeefte `consumeWindowNameHandoff`.
223
+ hedeefte `consumeWindowNameHandoff`. Cross-origin tab'da `window.name`
224
+ okunabilir kalır — mümkünse sunucu handoff tercih edin.
219
225
 
220
226
  ## CSRF
221
227
 
package/docs/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # JSkelet belgeleri
2
2
 
3
3
  JSkelet, SEO ve hız odaklı siteler için "framework'süz hissettiren" bir
4
- framework: Express 5 + EJS ile sunucuda tam HTML üretir, etkileşimi vanilla JS
5
- island'larla ekler, CSS'i Tailwind v4 ile tek bir stylesheet'e derler ve ISR
6
- yerine süreç belleğinde yaşayan, stale-while-revalidate'li bir HTML TTL cache
7
- kullanır. React yok, TypeScript yok; düz JavaScript ve JSDoc.
4
+ framework: Express 5 + build-time `.jsk` ile sunucuda tam HTML üretir (EJS
5
+ opsiyonel legacy peer), etkileşimi vanilla JS island'larla ekler, CSS'i
6
+ Tailwind v4 ile tek bir stylesheet'e derler ve ISR yerine süreç belleğinde
7
+ yaşayan, stale-while-revalidate'li bir HTML TTL cache kullanır. React yok;
8
+ framework kaynağı düz JavaScript + JSDoc'tur. Uygulama tarafında client
9
+ island/entry'ler TypeScript yazılabilir ve paket `.d.ts` yayınlar.
8
10
 
9
11
  Bu dizin framework'ün tam referansıdır. Sıralı okumak için baştan başlayın;
10
12
  belirli bir konuyu arıyorsanız doğrudan ilgili başlığa gidin.
@@ -19,7 +21,7 @@ eşlenik tutuluyor; birini değiştiriyorsan diğerini de değiştir.
19
21
  | [01-baslangic.md](./01-baslangic.md) | Kurulum, `jskelet init`, ilk route, ilk island, dizin yapısı, CLI komutları |
20
22
  | [02-mimari.md](./02-mimari.md) | Mimari kararlar ve gerekçeleri: island modeli, tam sunucu HTML'i, cache stratejisi, middleware sırası |
21
23
  | [03-routing.md](./03-routing.md) | Route modülü sözleşmesi, yükleme sırası, controller sözleşmesi, `ctx`, `notFound`/`redirect`, config redirects/rewrites |
22
- | [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md) | EJS layout, sayfalar, otomatik bileşen kaydı, `html`/`tags` yardımcıları, metadata → `<head>`, hook'lar |
24
+ | [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md) | `.jsk` layout/sayfalar, otomatik bileşen kaydı, `html`/`tags`, metadata → `<head>`, hook'lar; EJS legacy |
23
25
  | [05-islands.md](./05-islands.md) | `data-island` sözleşmesi, hidrasyon stratejileri, `client/entries/*`, `createStore`, DOM yardımcıları, `startSafeImages` |
24
26
  | [06-cache.md](./06-cache.md) | `withHtmlCache`, `revalidate`, stale-while-revalidate, cache anahtarı, `X-JSkelet-Cache`, istek içi cache, degraded render, prewarm |
25
27
  | [07-yapilandirma.md](./07-yapilandirma.md) | `jskelet.config.mjs` tam referansı, `source` desen sözdizimi, ortam değişkenleri tablosu |
@@ -255,11 +255,12 @@ ESM resolve hooks (`--import`) at process start.
255
255
 
256
256
  | Command | What it does |
257
257
  | --- | --- |
258
- | `jskelet dev` | Build watch + server, in a single terminal. Live reload, CSS hot-swap, dev overlay. `NODE_ENV=development`. |
258
+ | `jskelet dev` | Build watch + server, in a single terminal. Live reload, CSS hot-swap, dev overlay. `NODE_ENV=development`. Refuses to start if the port is busy; `--murder` kills the listener and binds. |
259
259
  | `jskelet build` | One-shot prod build: fonts → icon sprite → CSS → client JS → images → manifest → precompress. `production` if `NODE_ENV` is not given. |
260
- | `jskelet start` | Prod server. If there is no build output it produces it first. `production` if `NODE_ENV` is not given. |
260
+ | `jskelet start` | Prod server. If there is no build output it produces it first. `production` if `NODE_ENV` is not given. Same port behaviour as `dev` (`--murder`). |
261
261
  | `jskelet init` | Installs a feature-first `.jsk` skeleton into the current directory; leaves existing files alone. |
262
262
  | `jskelet generate` | Scaffolds a `feature` / `page` / `island`. |
263
+ | `jskelet migrate` | Next.js App Router → JSkelet codemod (`scan` / `apply` / `config`). See [11-migration.md](./11-migration.md). |
263
264
 
264
265
  An unknown command, or a call with no arguments, prints the usage text.
265
266
 
@@ -287,7 +288,7 @@ only these specifiers:
287
288
  | `jskelet/tags` | `link`, `image`, `icon`, `preloadImage`, `toKebab` |
288
289
  | `jskelet/log` | Console output helpers (`banner`, `event`, `task`, `size`, `ms`, …) |
289
290
  | `jskelet/register` | Alias + extension hooks via `node --import jskelet/register` |
290
- | `jskelet/layout` | The path to the framework's default `layout.ejs` file |
291
+ | `jskelet/layout` | The path to the framework's default `layout.jsk` file |
291
292
 
292
293
  ## What's next
293
294
 
@@ -51,7 +51,7 @@ Request
51
51
  │ └─ withHtmlCache TTL + stale-while-revalidate
52
52
  │ └─ withUpstreamTracking
53
53
  │ └─ withRequestCache
54
- │ └─ controller → renderPage → EJS
54
+ │ └─ controller → renderPage → .jsk (or legacy EJS)
55
55
  ├─ 404 → hooks.notFound()
56
56
  └─ error handling redirect/notFound + 500 fallback
57
57
  ```
@@ -270,10 +270,10 @@ hard-to-diagnose problems like "why is there no stylesheet".
270
270
 
271
271
  ## Why this dependency list
272
272
 
273
- There are four runtime dependencies: `express`, `ejs`, `esbuild`,
274
- `tailwind-merge`. Everything else (Tailwind, PostCSS, lightningcss, sharp, the
275
- Phosphor icons) is an **optional peer dependency**, and if it is absent the
276
- corresponding build step is skipped.
273
+ There are three runtime dependencies: `express`, `esbuild`, `tailwind-merge`.
274
+ `ejs` is an optional peer only for legacy `.ejs` templates. Everything else
275
+ (Tailwind, PostCSS, lightningcss, sharp, the Phosphor icons) is an **optional
276
+ peer dependency**, and if it is absent the corresponding build step is skipped.
277
277
 
278
278
  Two decisions deserve a separate explanation:
279
279
 
@@ -213,7 +213,7 @@ fields:
213
213
 
214
214
  | Field | Type | Default | Meaning |
215
215
  | --- | --- | --- | --- |
216
- | `view` | `string` | — | The template path under `views/`, without the extension: `"pages/home"` → `views/pages/home.ejs`. |
216
+ | `view` | `string` | — | The template path under `views/`, without the extension: `"pages/home"` → `views/pages/home.jsk` (else legacy `.ejs`). |
217
217
  | `data` | `object` | `{}` | Data passed to the template as locals. |
218
218
  | `metadata` | `object` | `{}` | Turned into `<head>` tags; it overrides the output of `hooks.metadata()`. Schema: [04-rendering.md](./04-rendering.md). |
219
219
  | `status` | `number` | `200` | The HTTP status code. Only 200 is written to the cache. |
@@ -332,13 +332,18 @@ handler kicks in, logs the error and returns the framework's own error page with
332
332
  `Cache-Control: no-store`. The status code is read from the error's `statusCode`
333
333
  (or `status`) field; if it is not in the 400–599 range, 500 is used.
334
334
 
335
- The framework's page is deliberately plain: the status code, a one-line heading
336
- and a one-line description. It carries no brand name, no navigation and no error
337
- detail — the innards of the server are not opened up to the visitor. The
338
- language comes from `brand.lang` (`tr` and `en` are built in, others fall back
339
- to `en`).
335
+ **Development** (`NODE_ENV=development`, i.e. `jskelet dev`): for 5xx responses
336
+ the built-in 500 page and `hooks.error()` are skipped; a diagnostic page with
337
+ the message, stack trace, and any `cause` chain is returned instead. 4xx (404
338
+ and friends) still use the usual status page in development.
340
339
 
341
- To provide your own page, `hooks.error()`:
340
+ **Production**: the framework's page is deliberately plain — status code, a
341
+ one-line heading and a one-line description. It carries no brand name, no
342
+ navigation and no error detail; the innards of the server are not opened up to
343
+ the visitor. The language comes from `brand.lang` (`tr` and `en` are built in,
344
+ others fall back to `en`).
345
+
346
+ To provide your own page, `hooks.error()` (production / 4xx only):
342
347
 
343
348
  ```js
344
349
  // jskelet.config.mjs
@@ -52,7 +52,7 @@ controller data → imported render(data, helpers) → HTML
52
52
  {/if}
53
53
 
54
54
  {#each items as item, i}
55
- <li data-i="{{ i }}">{{ item }}</li>
55
+ <li :data-i="i">{{ item }}</li>
56
56
  {/each}
57
57
 
58
58
  <Link href="/" text="Home" />
@@ -68,7 +68,7 @@ controller data → imported render(data, helpers) → HTML
68
68
  | Loop | `{#each list as item}` or `as item, i` |
69
69
  | Include | `{#include "partials/header"}` (compiled `.jsk`) |
70
70
  | Component | PascalCase tag; `:prop="expr"`, `prop="literal"`, boolean `disabled` |
71
- | Built-ins | `Link`, `Image`, `Icon`, `CsrfField`, `PreloadImage` |
71
+ | Built-ins | `Link`, `Image`, `Icon`, `CsrfField`, `PreloadImage`, `Stylesheets`, `BodyScripts`, `JsonLd` |
72
72
 
73
73
  The expression language is intentionally small (access, compare, ternary,
74
74
  `.length`). No assignments, object literals, or arbitrary calls — keep logic in
@@ -104,12 +104,15 @@ See the extension README for details.
104
104
 
105
105
  If a compiled `.jsk` exists for a view id it wins; otherwise `.ejs` is rendered
106
106
  with EJS. Existing apps keep working unchanged. `jskelet init` scaffolds `.jsk`.
107
+ Legacy `.ejs` needs the optional `ejs` peer installed in the application
108
+ (`npm install ejs`); without it only `.jsk` templates run.
107
109
 
108
110
  ## The EJS engine (legacy)
109
111
 
110
- EJS remains supported. The engine is set up once on the first render; the
111
- component scan touches the file system, so it cannot be done on every request
112
- and cannot be computed before the config is loaded.
112
+ EJS remains supported as an **optional peer dependency** for legacy templates.
113
+ The engine is set up once on the first render; the component scan touches the
114
+ file system, so it cannot be done on every request and cannot be computed
115
+ before the config is loaded.
113
116
 
114
117
  Settings:
115
118
 
@@ -130,66 +133,54 @@ normal flow because the dev server restarts the process.
130
133
 
131
134
  1. `jskelet.config.mjs` → if `layout` is given, it is used. The path is
132
135
  resolved relative to the **parent directory of the views directory**: if
133
- `views` is the default, `layout: "views/custom.ejs"` → `<root>/views/custom.ejs`.
136
+ `views` is the default, `layout: "views/custom.jsk"` → `<root>/views/custom.jsk`.
134
137
  2. If it is not given and `views/layout.jsk` exists (compiled), that is used.
135
- 3. Else if `views/layout.ejs` exists, that is used.
138
+ 3. Else if `views/layout.ejs` exists (legacy), that is used.
136
139
  4. If that does not exist either, the framework's own minimal layout is used
137
- (`node_modules/jskelet/src/templates/layout.ejs`, also reachable through the
140
+ (`node_modules/jskelet/src/templates/layout.jsk`, also reachable through the
138
141
  `jskelet/layout` specifier).
139
142
 
140
143
  These fallbacks exist so that a new project can work with a single route. The
141
144
  most practical way to move to your own layout is to copy that file to
142
- `views/layout.ejs` or author `views/layout.jsk`.
145
+ `views/layout.jsk`.
143
146
 
144
147
  ### The framework's default layout
145
148
 
146
- ```ejs
149
+ ```html
147
150
  <!DOCTYPE html>
148
- <html lang="<%= lang %>">
151
+ <html :lang="lang">
149
152
  <head>
150
153
  <meta charset="utf-8">
151
154
  <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
- <% }); %>
155
+ {{{ extraHead }}}
156
+ <Stylesheets :styles="styles" />
157
+ {{{ headMeta }}}
158
+ <JsonLd :items="structuredData" />
165
159
  </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
- <% } %>
160
+ <body :class="bodyClass">
161
+ {{{ body }}}
162
+ <BodyScripts :entries="entries" :devtools="devtools" :devBasePath="devBasePath" />
177
163
  </body>
178
164
  </html>
179
165
  ```
180
166
 
167
+ The `.jsk` expression language has no function calls, so asset loops live in the
168
+ built-in `<Stylesheets />`, `<BodyScripts />` and `<JsonLd />` tags instead of
169
+ inline `hasAsset` / `asset` / `forEach` in the layout.
170
+
181
171
  Points to watch:
182
172
 
183
173
  - **`extraHead` comes first.** Delaying resource hints (`preconnect`, LCP
184
174
  `preload`) writes straight into LCP.
185
- - **Global `app.css` is render-blocking**, with the reasoning in
186
- [02-architecture.md](./02-architecture.md). Controller `styles: [...]` adds
187
- page sheets the same way. If the build has not run, `hasAsset` is false and
188
- the tag is never emitted.
189
- - **The `hasAsset` checks** keep the page from requesting files that 404 when
190
- the build is missing.
191
- - **The devtools script** is emitted only when `NODE_ENV=development`; it does
192
- not exist at all in production output.
175
+ - **`<Stylesheets />` emits global `app.css` (render-blocking)** plus controller
176
+ `styles: [...]`, with the reasoning in
177
+ [02-architecture.md](./02-architecture.md). If the build has not run,
178
+ `hasAsset` is false inside the tag and nothing is emitted.
179
+ - **`<BodyScripts />` emits `main.js`, page `entries`, and the
180
+ development-only overlay.** The overlay script exists only when
181
+ `NODE_ENV=development`; it is absent from production output.
182
+ - **`<JsonLd />` turns `structuredData` into safe
183
+ `application/ld+json` scripts.**
193
184
 
194
185
  ### Layout locals
195
186
 
@@ -219,30 +210,27 @@ of bug where every page thinks it is the home page and renders the logo as an
219
210
  ## Page templates
220
211
 
221
212
  The `view` field gives the path under `views/` without an extension:
222
- `"pages/home"` → `views/pages/home.ejs`. The locals passed to the template are
223
- the contents of the `data` field plus `metadata` — **not** the layout locals.
224
- The page template still has access to all helpers and components.
213
+ `"pages/home"` → `views/pages/home.jsk` (else legacy `home.ejs`). The locals
214
+ passed to the template are the contents of the `data` field plus `metadata` —
215
+ **not** the layout locals. The page template still has access to all helpers
216
+ and components.
225
217
 
226
- ```ejs
227
- <%# views/pages/home.ejs %>
218
+ ```html
219
+ {# views/pages/home.jsk #}
228
220
  <section class="wrapper">
229
- <h1 class="text-3xl font-bold"><%= heading %></h1>
221
+ <h1 class="text-3xl font-bold">{{ heading }}</h1>
230
222
 
231
- <%# `list` is defined in views/components/list.js; no import needed. %>
232
- <%- list({ items }) %>
223
+ {# `list` is defined in views/components/list.js; no import needed. #}
224
+ <List :items="items" />
233
225
 
234
226
  <div class="mt-8" data-island="counter" data-island-props='{"start":5}'></div>
235
227
  </section>
236
228
  ```
237
229
 
238
- Do not mix up the two output forms in EJS:
239
-
240
- - `<%= value %>` — HTML escaped. **Always** this for user/upstream data.
241
- - `<%- html %>` — raw. Only for HTML strings you produced yourself and know to
242
- be safe (component calls, `headMeta`, `body`).
243
-
244
- Because `async: true` is on, `await` can also be used inside a template, but
245
- keeping data fetching in the controller makes diagnosis easier.
230
+ In `.jsk`, `{{ }}` escapes and `{{{ }}}` emits raw HTML (trusted strings only).
231
+ Legacy EJS keeps `<%= %>` / `<%- %>` with the same meaning; because `async: true`
232
+ is on there, `await` can also be used inside an `.ejs` template, but keeping
233
+ data fetching in the controller makes diagnosis easier.
246
234
 
247
235
  ## Components: `views/components/**`
248
236
 
@@ -123,25 +123,30 @@ will still mount.
123
123
  ```
124
124
  client/
125
125
  ├── entries/
126
- │ ├── main.js the shared bootstrap loaded on every page
126
+ │ ├── main.js shared bootstrap on every page (or main.ts)
127
127
  │ └── chart.js only on the pages that ask for it
128
128
  └── islands/
129
- ├── counter.js
129
+ ├── counter.ts .js or .ts
130
130
  └── chart.js
131
131
  ```
132
132
 
133
- **Every file** under `client/entries/*.js` **is an esbuild entry**. `main.js`
134
- is loaded by the layout on every page (if it is in the manifest). Extra entries
135
- are loaded only on the pages that ask for them:
133
+ **Every file** under `client/entries/*.{js,ts,mts}` **is an esbuild entry**.
134
+ `main.js` (or `main.ts`) is loaded by the layout on every page (if it is in the
135
+ manifest). Extra entries are loaded only on the pages that ask for them. Two
136
+ extensions for the same stem (`main.js` + `main.ts`) fail the build.
136
137
 
137
138
  ```js
138
- // controller
139
+ // controller — the manifest key is always *.js
139
140
  return { view: "pages/markets", entries: ["chart.js"] };
140
141
  ```
141
142
 
142
143
  The layout resolves every name in the `entries` array with `asset(entry)` and
143
- emits a `<script type="module">`. The name is the manifest key, that is, the
144
- file name itself (`chart.js`), not its hashed form.
144
+ emits a `<script type="module">`. The name is the manifest key (`chart.js`);
145
+ even when the source is `chart.ts`, the unhashed key stays `.js`.
146
+
147
+ Shared `@/lib` modules imported on the server must stay **`.js`** — the Node
148
+ runtime does not resolve `.ts`; TypeScript is compiled only on the esbuild
149
+ client path.
145
150
 
146
151
  Code splitting (`splitting: true`) is on: modules shared by two entries end up
147
152
  in a common chunk and are not downloaded twice.