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.
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +129 -2
- package/README.md +21 -7
- package/bin/jskelet.mjs +6 -6
- package/docs/03-routing.md +48 -9
- package/docs/04-render-ve-sablonlar.md +2 -2
- package/docs/05-islands.md +59 -6
- package/docs/06-cache.md +240 -26
- package/docs/07-yapilandirma.md +108 -7
- package/docs/08-build.md +4 -4
- package/docs/09-dev-araclari.md +5 -0
- package/docs/12-panel-ve-oturum.md +384 -0
- package/docs/README.md +25 -2
- package/docs/en/01-getting-started.md +292 -0
- package/docs/en/02-architecture.md +305 -0
- package/docs/en/03-routing.md +493 -0
- package/docs/en/04-rendering.md +504 -0
- package/docs/en/05-islands.md +492 -0
- package/docs/en/06-caching.md +640 -0
- package/docs/en/07-configuration.md +789 -0
- package/docs/en/08-build.md +383 -0
- package/docs/en/09-dev-tools.md +314 -0
- package/docs/en/10-deployment.md +332 -0
- package/docs/en/11-migration.md +360 -0
- package/docs/en/12-dashboards-and-sessions.md +392 -0
- package/docs/en/README.md +112 -0
- package/package.json +4 -2
- package/src/build/build.mjs +1 -1
- package/src/build/tasks/client.mjs +2 -2
- package/src/build/tasks/fonts.mjs +3 -3
- package/src/build/tasks/icons.mjs +1 -1
- package/src/build/tasks/images.mjs +2 -2
- package/src/client/devtools/overlay.js +196 -164
- package/src/client/devtools/report.js +96 -96
- package/src/client/form.js +192 -0
- package/src/client/index.js +10 -1
- package/src/client/registry.js +78 -4
- package/src/client/swap.js +188 -0
- package/src/config/defaults.js +83 -0
- package/src/config/index.js +129 -18
- package/src/config/pattern.js +1 -1
- package/src/dev-server.mjs +1 -1
- package/src/http/control-flow.js +16 -1
- package/src/http/cookies.js +257 -0
- package/src/http/request-context.js +162 -0
- package/src/index.js +26 -2
- package/src/init.mjs +32 -31
- package/src/log.mjs +8 -2
- package/src/logo.png +0 -0
- package/src/runtime/alias-hooks.mjs +1 -1
- package/src/server/assets.js +1 -1
- package/src/server/create-app.js +12 -4
- package/src/server/data-cache.js +244 -0
- package/src/server/dev/devtools.js +6 -2
- package/src/server/dev/report.js +8 -1
- package/src/server/dev/version-check.mjs +139 -0
- package/src/server/head-hints.js +1 -1
- package/src/server/html-cache.js +32 -6
- package/src/server/middleware/csrf.js +134 -0
- package/src/server/prewarm.js +164 -19
- package/src/server/render.js +256 -20
- package/src/server/router.js +14 -7
- package/src/server/status-page.js +1 -1
- package/src/version.mjs +9 -4
- package/src/views/components/loader.js +1 -1
- package/src/views/helpers/tags.js +53 -1
package/src/server/router.js
CHANGED
|
@@ -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,
|
|
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 {
|
|
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]
|
|
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)}
|
|
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
|
|
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}
|
|
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
|
-
|
|
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"))
|
|
12
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
14
13
|
} catch {
|
|
15
|
-
return
|
|
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}'
|
|
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
|
|
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
|