jskelet 0.1.1 → 0.1.3

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 (66) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +129 -2
  3. package/README.md +21 -7
  4. package/bin/jskelet.mjs +6 -6
  5. package/docs/03-routing.md +48 -9
  6. package/docs/04-render-ve-sablonlar.md +2 -2
  7. package/docs/05-islands.md +59 -6
  8. package/docs/06-cache.md +240 -26
  9. package/docs/07-yapilandirma.md +108 -7
  10. package/docs/08-build.md +4 -4
  11. package/docs/09-dev-araclari.md +5 -0
  12. package/docs/12-panel-ve-oturum.md +384 -0
  13. package/docs/README.md +25 -2
  14. package/docs/en/01-getting-started.md +292 -0
  15. package/docs/en/02-architecture.md +305 -0
  16. package/docs/en/03-routing.md +493 -0
  17. package/docs/en/04-rendering.md +504 -0
  18. package/docs/en/05-islands.md +492 -0
  19. package/docs/en/06-caching.md +640 -0
  20. package/docs/en/07-configuration.md +789 -0
  21. package/docs/en/08-build.md +383 -0
  22. package/docs/en/09-dev-tools.md +314 -0
  23. package/docs/en/10-deployment.md +332 -0
  24. package/docs/en/11-migration.md +360 -0
  25. package/docs/en/12-dashboards-and-sessions.md +392 -0
  26. package/docs/en/README.md +112 -0
  27. package/package.json +4 -2
  28. package/src/build/build.mjs +1 -1
  29. package/src/build/tasks/client.mjs +2 -2
  30. package/src/build/tasks/fonts.mjs +3 -3
  31. package/src/build/tasks/icons.mjs +1 -1
  32. package/src/build/tasks/images.mjs +2 -2
  33. package/src/client/devtools/overlay.js +196 -164
  34. package/src/client/devtools/report.js +96 -96
  35. package/src/client/form.js +192 -0
  36. package/src/client/index.js +10 -1
  37. package/src/client/registry.js +78 -4
  38. package/src/client/swap.js +188 -0
  39. package/src/config/defaults.js +83 -0
  40. package/src/config/index.js +129 -18
  41. package/src/config/pattern.js +1 -1
  42. package/src/dev-server.mjs +1 -1
  43. package/src/http/control-flow.js +16 -1
  44. package/src/http/cookies.js +257 -0
  45. package/src/http/request-context.js +162 -0
  46. package/src/index.js +26 -2
  47. package/src/init.mjs +32 -31
  48. package/src/log.mjs +8 -2
  49. package/src/logo.png +0 -0
  50. package/src/runtime/alias-hooks.mjs +1 -1
  51. package/src/server/assets.js +1 -1
  52. package/src/server/create-app.js +12 -4
  53. package/src/server/data-cache.js +244 -0
  54. package/src/server/dev/devtools.js +6 -2
  55. package/src/server/dev/report.js +8 -1
  56. package/src/server/dev/version-check.mjs +139 -0
  57. package/src/server/head-hints.js +1 -1
  58. package/src/server/html-cache.js +32 -6
  59. package/src/server/middleware/csrf.js +134 -0
  60. package/src/server/prewarm.js +164 -19
  61. package/src/server/render.js +256 -20
  62. package/src/server/router.js +14 -7
  63. package/src/server/status-page.js +1 -1
  64. package/src/version.mjs +9 -4
  65. package/src/views/components/loader.js +1 -1
  66. package/src/views/helpers/tags.js +53 -1
@@ -14,16 +14,21 @@
14
14
  *
15
15
  * Modül sözleşmesi: default export ya da `register` adlı named export,
16
16
  * `(app, api) => void | Promise<void>` imzasıyla. `api` içinde `route`,
17
- * `renderView`, `renderPage` ve `notFound`/`redirect` hazır gelir, böylece
18
- * route dosyaları framework'ten tek tek import yapmak zorunda kalmaz.
17
+ * `fragment`, `renderView`, `renderPage` ve `notFound`/`redirect` hazır gelir,
18
+ * böylece route dosyaları framework'ten tek tek import yapmak zorunda kalmaz.
19
19
  */
20
20
  import fs from "node:fs";
21
21
  import path from "node:path";
22
22
  import process from "node:process";
23
23
  import { pathToFileURL } from "node:url";
24
24
  import { getConfig } from "../config/index.js";
25
- import { renderPage, renderView, route } from "./render.js";
26
- import { notFound, permanentRedirect, redirect } from "../http/control-flow.js";
25
+ import { fragment, renderPage, renderView, route } from "./render.js";
26
+ import {
27
+ notFound,
28
+ permanentRedirect,
29
+ redirect,
30
+ seeOther,
31
+ } from "../http/control-flow.js";
27
32
 
28
33
  const isDev = process.env.NODE_ENV === "development";
29
34
 
@@ -61,11 +66,13 @@ function discover(dir, out = []) {
61
66
  */
62
67
  const api = {
63
68
  route,
69
+ fragment,
64
70
  renderView,
65
71
  renderPage,
66
72
  notFound,
67
73
  redirect,
68
74
  permanentRedirect,
75
+ seeOther,
69
76
  };
70
77
 
71
78
  /**
@@ -81,7 +88,7 @@ export async function registerRoutes(app) {
81
88
 
82
89
  if (!files.length) {
83
90
  console.warn(
84
- `[router] hiç route modülü bulunamadı — ${path.relative(config.root, config.dirs.routes)}/ boş mu?`,
91
+ `[router] no route modules found — is ${path.relative(config.root, config.dirs.routes)}/ empty?`,
85
92
  );
86
93
  return 0;
87
94
  }
@@ -99,7 +106,7 @@ export async function registerRoutes(app) {
99
106
  // et. Üretimde fırlat — yarım route tablosuyla yayına çıkmak,
100
107
  // sessizce 404 dönen sayfalar demek.
101
108
  if (isDev) {
102
- console.warn(`[router] ${path.basename(file)} yüklenemedi, atlandı`, error);
109
+ console.warn(`[router] ${path.basename(file)} failed to load, skipped`, error);
103
110
  continue;
104
111
  }
105
112
  throw error;
@@ -108,7 +115,7 @@ export async function registerRoutes(app) {
108
115
  const register = module.default ?? module.register;
109
116
  if (typeof register !== "function") {
110
117
  console.warn(
111
- `[router] ${path.basename(file)} default ya da 'register' fonksiyonu dışa açmıyor, atlandı`,
118
+ `[router] ${path.basename(file)} exports neither a default nor a 'register' function, skipped`,
112
119
  );
113
120
  continue;
114
121
  }
@@ -41,7 +41,7 @@ export async function renderStatusPage(status, options = {}) {
41
41
  const { renderPage } = await import("./render.js");
42
42
  return await renderPage({ pathname: `/${status}`, ...page });
43
43
  } catch (error) {
44
- console.error(`[render] ${status} sayfası render edilemedi`, error);
44
+ console.error(`[render] failed to render the ${status} page`, error);
45
45
  return fallbackPage(status);
46
46
  }
47
47
  }
package/src/version.mjs CHANGED
@@ -6,12 +6,17 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { FRAMEWORK_ROOT } from "./config/index.js";
8
8
 
9
- /** @type {string} */
10
- export const FRAMEWORK_VERSION = (() => {
9
+ const manifest = (() => {
11
10
  try {
12
11
  const file = path.join(FRAMEWORK_ROOT, "package.json");
13
- return JSON.parse(fs.readFileSync(file, "utf8")).version ?? "0.0.0";
12
+ return JSON.parse(fs.readFileSync(file, "utf8"));
14
13
  } catch {
15
- return "0.0.0";
14
+ return {};
16
15
  }
17
16
  })();
17
+
18
+ /** @type {string} */
19
+ export const FRAMEWORK_VERSION = manifest.version ?? "0.0.0";
20
+
21
+ /** Paket adı: sürüm kontrolü hangi kayıt defteri girdisine bakacağını buradan bilir. */
22
+ export const FRAMEWORK_PACKAGE = manifest.name ?? "jskelet";
@@ -72,7 +72,7 @@ export async function loadComponents(dir) {
72
72
  const previous = origin.get(name);
73
73
  if (previous && previous !== BARREL && previous !== relative) {
74
74
  console.warn(
75
- `[components] '${name}' iki kez tanımlı: ${previous} ve ${relative} — ikincisi kazanıyor.`,
75
+ `[components] '${name}' is defined twice: ${previous} and ${relative} — the second one wins.`,
76
76
  );
77
77
  }
78
78
 
@@ -4,6 +4,9 @@
4
4
  */
5
5
  import { attrs, esc, cn } from "./html.js";
6
6
  import { asset, getSpriteIds, optimizedImage } from "../../server/assets.js";
7
+ import { getRequestContext, markTainted } from "../../http/request-context.js";
8
+ import { getSignedCookie, randomToken, setSignedCookie } from "../../http/cookies.js";
9
+ import { getConfig } from "../../config/index.js";
7
10
 
8
11
  const isDev = process.env.NODE_ENV === "development";
9
12
 
@@ -160,7 +163,7 @@ export function icon(props) {
160
163
  if (ids.size && !ids.has(id)) {
161
164
  warnedIcons.add(id);
162
165
  console.warn(
163
- `[icon] sprite'ta yok: ${id} — adı sabit yazın ya da build/tasks/icons.mjs taramasına ekleyin.`,
166
+ `[icon] missing from sprite: ${id} — write the name as a literal or add it to the build/tasks/icons.mjs scan.`,
164
167
  );
165
168
  }
166
169
  }
@@ -179,6 +182,55 @@ export function icon(props) {
179
182
  return `<svg${attributes}><use href="${esc(spritePath)}#${esc(id)}"></use></svg>`;
180
183
  }
181
184
 
185
+ /**
186
+ * Formun içine CSRF token'ını gizli alan olarak basar.
187
+ *
188
+ * Token **burada** üretilir ve imzalı cookie olarak yazılır: bir sayfada
189
+ * token gerçekten gerekiyorsa o sayfa zaten kişiye özeldir. Bu yüzden çağrı
190
+ * render'ı işaretler (`tainted`) ve sayfa public HTML cache'ine giremez —
191
+ * aksi hâlde tüm ziyaretçiler cache'ten aynı token'ı alırdı ve çift gönderim
192
+ * kontrolü hiçbir şey doğrulamazdı.
193
+ *
194
+ * `security.csrf.token` kapalıysa boş string döner; şablon her koşulda
195
+ * render edilebilmeli, korumanın açık olması config'in kararı.
196
+ *
197
+ * @returns {string}
198
+ */
199
+ export function csrfField() {
200
+ const context = getRequestContext();
201
+ if (!context?.res) return "";
202
+
203
+ const { csrf } = getConfig().security;
204
+ if (!csrf.token) return "";
205
+
206
+ if (!context.csrfToken) {
207
+ const existing = getSignedCookie(context.res.req, csrf.cookieName);
208
+ const token = existing ?? randomToken(24);
209
+
210
+ if (!existing) {
211
+ try {
212
+ setSignedCookie(context.res, csrf.cookieName, token, {
213
+ // Token'ı istemci cookie'den değil, basılan gizli alandan okur;
214
+ // HttpOnly kalması XSS durumunda bir katman daha demek.
215
+ httpOnly: true,
216
+ });
217
+ } catch (error) {
218
+ console.warn(
219
+ "[csrf] could not emit the token: no secret for signed cookies",
220
+ error instanceof Error ? error.message : error,
221
+ );
222
+ return "";
223
+ }
224
+ }
225
+
226
+ context.csrfToken = token;
227
+ }
228
+
229
+ markTainted("csrfField()");
230
+
231
+ return `<input type="hidden" name="${esc(csrf.fieldName)}" value="${esc(context.csrfToken)}">`;
232
+ }
233
+
182
234
  /**
183
235
  * `ArrowRightIcon` / `ArrowRight` → `arrow-right`
184
236
  * @param {string} name