jskelet 0.6.3 → 0.6.4
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 +136 -136
- package/CHANGELOG.md +628 -620
- package/LICENSE +21 -21
- package/README.md +2 -0
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -310
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +667 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1467 -1443
- package/docs/07-yapilandirma.md +1208 -1197
- package/docs/08-build.md +429 -429
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +348 -338
- package/docs/12-panel-ve-oturum.md +479 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -329
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +675 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1476 -1453
- package/docs/en/07-configuration.md +1229 -1219
- package/docs/en/08-build.md +447 -447
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +351 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +489 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +541 -534
- package/src/config/index.js +1500 -1469
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +232 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -70
- package/src/server/cache-control.js +45 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -553
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -233
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1196
- package/src/server/image-optimizer.js +500 -500
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -193
- package/src/server/logs/pipeline.js +165 -165
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -121
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +369 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1082
- package/src/server/redis.js +588 -588
- package/src/server/render.js +910 -910
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +6 -0
- package/types/config/index.d.ts +6 -0
- package/types/server/cache-control.d.ts +28 -0
- package/types/server/og-image.d.ts +5 -0
package/src/server/disk-cache.js
CHANGED
|
@@ -1,233 +1,233 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Redis yokken L2: aynı brotli gövde `.jskelet/cache/<buildId>/` altına yazılır.
|
|
3
|
-
*
|
|
4
|
-
* Tek makinenin yeniden açılışını karşılar. Birden fazla instance aynı
|
|
5
|
-
* dizini paylaşmaz — o iş Redis'te kalır. Redis o türü paylaşıyorsa disk
|
|
6
|
-
* devreye girmez; iki kopya birbirini ezmesin.
|
|
7
|
-
*
|
|
8
|
-
* Dosya adı anahtarın sha256'sı. Başta anahtarın kendisi durur ki desenle
|
|
9
|
-
* silmek için gövdeyi çözmek gerekmesin. Süre Redis'teki `PX` yerine
|
|
10
|
-
* girdinin `expiresAt` alanındadır; süresi dolmuş dosya okununca silinir.
|
|
11
|
-
*/
|
|
12
|
-
import crypto from "node:crypto";
|
|
13
|
-
import fs from "node:fs/promises";
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
import { getConfig } from "../config/index.js";
|
|
16
|
-
import { getBuildId } from "./assets.js";
|
|
17
|
-
import { decodeCacheValue, encodeCacheValue } from "./cache-blob.js";
|
|
18
|
-
import { redisShares } from "./redis.js";
|
|
19
|
-
|
|
20
|
-
/** @type {string | null} `null` → config'den çöz. Testler mutlak yol verir. */
|
|
21
|
-
let testRoot = null;
|
|
22
|
-
|
|
23
|
-
let pruned = false;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @param {string | null} root Mutlak dizin, ya da config yoluna dönmek için `null`.
|
|
27
|
-
*/
|
|
28
|
-
export function setDiskCacheRootForTests(root) {
|
|
29
|
-
testRoot = root;
|
|
30
|
-
pruned = false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @returns {string | null}
|
|
35
|
-
*/
|
|
36
|
-
function directory() {
|
|
37
|
-
if (typeof testRoot === "string") return testRoot;
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
return path.join(getConfig().dirs.generated, "cache", getBuildId());
|
|
41
|
-
} catch {
|
|
42
|
-
// Birim testleri config yüklemez. Disk yazısı o zaman da kapalı kalır.
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @param {"html" | "data"} kind
|
|
49
|
-
* @returns {boolean}
|
|
50
|
-
*/
|
|
51
|
-
export function diskShares(kind) {
|
|
52
|
-
if (redisShares(kind)) return false;
|
|
53
|
-
return directory() !== null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* @param {string} key
|
|
58
|
-
* @returns {string}
|
|
59
|
-
*/
|
|
60
|
-
function fileName(key) {
|
|
61
|
-
return crypto.createHash("sha256").update(key).digest("hex");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* @param {"html" | "data"} kind
|
|
66
|
-
* @param {string} key
|
|
67
|
-
* @returns {string | null}
|
|
68
|
-
*/
|
|
69
|
-
function filePath(kind, key) {
|
|
70
|
-
const root = directory();
|
|
71
|
-
if (!root) return null;
|
|
72
|
-
return path.join(root, kind, fileName(key));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @param {string} key
|
|
77
|
-
* @param {string | Buffer} payload
|
|
78
|
-
* @returns {Buffer}
|
|
79
|
-
*/
|
|
80
|
-
function frame(key, payload) {
|
|
81
|
-
const keyBuf = Buffer.from(key);
|
|
82
|
-
const header = Buffer.alloc(4);
|
|
83
|
-
header.writeUInt32BE(keyBuf.length);
|
|
84
|
-
const body = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
85
|
-
return Buffer.concat([header, keyBuf, body]);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* @param {Buffer} raw
|
|
90
|
-
* @returns {{ key: string, body: Buffer } | null}
|
|
91
|
-
*/
|
|
92
|
-
function unframe(raw) {
|
|
93
|
-
if (raw.length < 4) return null;
|
|
94
|
-
const keyLen = raw.readUInt32BE(0);
|
|
95
|
-
if (keyLen < 1 || keyLen > 65536 || raw.length < 4 + keyLen) return null;
|
|
96
|
-
return {
|
|
97
|
-
key: raw.subarray(4, 4 + keyLen).toString("utf8"),
|
|
98
|
-
body: raw.subarray(4 + keyLen),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Eski build kimliğinin dizinini siler. Redis'te o anahtarlar TTL ile ölür;
|
|
104
|
-
* dosyada karşılığı bu. Dev'de kimlik `dev` kaldığı için prune bir şey silmez.
|
|
105
|
-
*
|
|
106
|
-
* @param {string} dir
|
|
107
|
-
*/
|
|
108
|
-
function pruneOldBuilds(dir) {
|
|
109
|
-
if (pruned || testRoot) return;
|
|
110
|
-
pruned = true;
|
|
111
|
-
|
|
112
|
-
const parent = path.dirname(dir);
|
|
113
|
-
const current = path.basename(dir);
|
|
114
|
-
|
|
115
|
-
fs.readdir(parent, { withFileTypes: true })
|
|
116
|
-
.then((entries) =>
|
|
117
|
-
Promise.all(
|
|
118
|
-
entries
|
|
119
|
-
.filter((entry) => entry.isDirectory() && entry.name !== current)
|
|
120
|
-
.map((entry) => fs.rm(path.join(parent, entry.name), { recursive: true, force: true })),
|
|
121
|
-
),
|
|
122
|
-
)
|
|
123
|
-
.catch(() => {
|
|
124
|
-
// Dizin henüz yoksa ya da silinemiyorsa L1 çalışmaya devam eder.
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* @param {"html" | "data"} kind
|
|
130
|
-
* @param {string} key
|
|
131
|
-
* @returns {Promise<unknown | null>}
|
|
132
|
-
*/
|
|
133
|
-
export async function diskGetJson(kind, key) {
|
|
134
|
-
const file = filePath(kind, key);
|
|
135
|
-
if (!file) return null;
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
const framed = unframe(await fs.readFile(file));
|
|
139
|
-
if (!framed) return null;
|
|
140
|
-
return decodeCacheValue(framed.body);
|
|
141
|
-
} catch {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Ateşle-unut. Dönüş değeri testler bekleyebilsin diye durur; istek yolu
|
|
148
|
-
* beklemez.
|
|
149
|
-
*
|
|
150
|
-
* @param {"html" | "data"} kind
|
|
151
|
-
* @param {string} key
|
|
152
|
-
* @param {unknown} value
|
|
153
|
-
* @returns {Promise<void>}
|
|
154
|
-
*/
|
|
155
|
-
export function diskSetJson(kind, key, value) {
|
|
156
|
-
const root = directory();
|
|
157
|
-
const file = filePath(kind, key);
|
|
158
|
-
if (!root || !file) return Promise.resolve();
|
|
159
|
-
|
|
160
|
-
pruneOldBuilds(root);
|
|
161
|
-
|
|
162
|
-
const encoded = encodeCacheValue(value);
|
|
163
|
-
if (encoded === null) return Promise.resolve();
|
|
164
|
-
|
|
165
|
-
const write = (/** @type {string | Buffer} */ payload) => {
|
|
166
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
167
|
-
return fs
|
|
168
|
-
.mkdir(path.dirname(file), { recursive: true })
|
|
169
|
-
.then(() => fs.writeFile(tmp, frame(key, payload)))
|
|
170
|
-
.then(() => fs.rename(tmp, file))
|
|
171
|
-
.catch((error) => {
|
|
172
|
-
console.warn(
|
|
173
|
-
"[disk-cache] write failed",
|
|
174
|
-
error instanceof Error ? error.message : error,
|
|
175
|
-
);
|
|
176
|
-
});
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
if (typeof encoded === "string" || Buffer.isBuffer(encoded)) return write(encoded);
|
|
180
|
-
return encoded.then(write);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* @param {"html" | "data"} kind
|
|
185
|
-
* @param {string[]} keys
|
|
186
|
-
*/
|
|
187
|
-
export function diskDrop(kind, keys) {
|
|
188
|
-
if (!directory() || !keys.length) return;
|
|
189
|
-
|
|
190
|
-
for (const key of keys) {
|
|
191
|
-
const file = filePath(kind, key);
|
|
192
|
-
if (file) fs.rm(file, { force: true }).catch(() => {});
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* @param {"html" | "data"} kind
|
|
198
|
-
* @param {(key: string) => boolean} [match] Verilmezse türün tamamı silinir.
|
|
199
|
-
* @returns {Promise<number>}
|
|
200
|
-
*/
|
|
201
|
-
export async function diskDropMatching(kind, match) {
|
|
202
|
-
const root = directory();
|
|
203
|
-
if (!root) return 0;
|
|
204
|
-
|
|
205
|
-
const folder = path.join(root, kind);
|
|
206
|
-
/** @type {string[]} */
|
|
207
|
-
let names;
|
|
208
|
-
try {
|
|
209
|
-
names = await fs.readdir(folder);
|
|
210
|
-
} catch {
|
|
211
|
-
return 0;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
let dropped = 0;
|
|
215
|
-
|
|
216
|
-
await Promise.all(
|
|
217
|
-
names.map(async (name) => {
|
|
218
|
-
const file = path.join(folder, name);
|
|
219
|
-
try {
|
|
220
|
-
if (match) {
|
|
221
|
-
const framed = unframe(await fs.readFile(file));
|
|
222
|
-
if (!framed || !match(framed.key)) return;
|
|
223
|
-
}
|
|
224
|
-
await fs.rm(file, { force: true });
|
|
225
|
-
dropped += 1;
|
|
226
|
-
} catch {
|
|
227
|
-
// Yarışta silinmiş dosya sorun değil.
|
|
228
|
-
}
|
|
229
|
-
}),
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
return dropped;
|
|
233
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Redis yokken L2: aynı brotli gövde `.jskelet/cache/<buildId>/` altına yazılır.
|
|
3
|
+
*
|
|
4
|
+
* Tek makinenin yeniden açılışını karşılar. Birden fazla instance aynı
|
|
5
|
+
* dizini paylaşmaz — o iş Redis'te kalır. Redis o türü paylaşıyorsa disk
|
|
6
|
+
* devreye girmez; iki kopya birbirini ezmesin.
|
|
7
|
+
*
|
|
8
|
+
* Dosya adı anahtarın sha256'sı. Başta anahtarın kendisi durur ki desenle
|
|
9
|
+
* silmek için gövdeyi çözmek gerekmesin. Süre Redis'teki `PX` yerine
|
|
10
|
+
* girdinin `expiresAt` alanındadır; süresi dolmuş dosya okununca silinir.
|
|
11
|
+
*/
|
|
12
|
+
import crypto from "node:crypto";
|
|
13
|
+
import fs from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { getConfig } from "../config/index.js";
|
|
16
|
+
import { getBuildId } from "./assets.js";
|
|
17
|
+
import { decodeCacheValue, encodeCacheValue } from "./cache-blob.js";
|
|
18
|
+
import { redisShares } from "./redis.js";
|
|
19
|
+
|
|
20
|
+
/** @type {string | null} `null` → config'den çöz. Testler mutlak yol verir. */
|
|
21
|
+
let testRoot = null;
|
|
22
|
+
|
|
23
|
+
let pruned = false;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string | null} root Mutlak dizin, ya da config yoluna dönmek için `null`.
|
|
27
|
+
*/
|
|
28
|
+
export function setDiskCacheRootForTests(root) {
|
|
29
|
+
testRoot = root;
|
|
30
|
+
pruned = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @returns {string | null}
|
|
35
|
+
*/
|
|
36
|
+
function directory() {
|
|
37
|
+
if (typeof testRoot === "string") return testRoot;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return path.join(getConfig().dirs.generated, "cache", getBuildId());
|
|
41
|
+
} catch {
|
|
42
|
+
// Birim testleri config yüklemez. Disk yazısı o zaman da kapalı kalır.
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {"html" | "data"} kind
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
export function diskShares(kind) {
|
|
52
|
+
if (redisShares(kind)) return false;
|
|
53
|
+
return directory() !== null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} key
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
function fileName(key) {
|
|
61
|
+
return crypto.createHash("sha256").update(key).digest("hex");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {"html" | "data"} kind
|
|
66
|
+
* @param {string} key
|
|
67
|
+
* @returns {string | null}
|
|
68
|
+
*/
|
|
69
|
+
function filePath(kind, key) {
|
|
70
|
+
const root = directory();
|
|
71
|
+
if (!root) return null;
|
|
72
|
+
return path.join(root, kind, fileName(key));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} key
|
|
77
|
+
* @param {string | Buffer} payload
|
|
78
|
+
* @returns {Buffer}
|
|
79
|
+
*/
|
|
80
|
+
function frame(key, payload) {
|
|
81
|
+
const keyBuf = Buffer.from(key);
|
|
82
|
+
const header = Buffer.alloc(4);
|
|
83
|
+
header.writeUInt32BE(keyBuf.length);
|
|
84
|
+
const body = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
85
|
+
return Buffer.concat([header, keyBuf, body]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {Buffer} raw
|
|
90
|
+
* @returns {{ key: string, body: Buffer } | null}
|
|
91
|
+
*/
|
|
92
|
+
function unframe(raw) {
|
|
93
|
+
if (raw.length < 4) return null;
|
|
94
|
+
const keyLen = raw.readUInt32BE(0);
|
|
95
|
+
if (keyLen < 1 || keyLen > 65536 || raw.length < 4 + keyLen) return null;
|
|
96
|
+
return {
|
|
97
|
+
key: raw.subarray(4, 4 + keyLen).toString("utf8"),
|
|
98
|
+
body: raw.subarray(4 + keyLen),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Eski build kimliğinin dizinini siler. Redis'te o anahtarlar TTL ile ölür;
|
|
104
|
+
* dosyada karşılığı bu. Dev'de kimlik `dev` kaldığı için prune bir şey silmez.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} dir
|
|
107
|
+
*/
|
|
108
|
+
function pruneOldBuilds(dir) {
|
|
109
|
+
if (pruned || testRoot) return;
|
|
110
|
+
pruned = true;
|
|
111
|
+
|
|
112
|
+
const parent = path.dirname(dir);
|
|
113
|
+
const current = path.basename(dir);
|
|
114
|
+
|
|
115
|
+
fs.readdir(parent, { withFileTypes: true })
|
|
116
|
+
.then((entries) =>
|
|
117
|
+
Promise.all(
|
|
118
|
+
entries
|
|
119
|
+
.filter((entry) => entry.isDirectory() && entry.name !== current)
|
|
120
|
+
.map((entry) => fs.rm(path.join(parent, entry.name), { recursive: true, force: true })),
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
.catch(() => {
|
|
124
|
+
// Dizin henüz yoksa ya da silinemiyorsa L1 çalışmaya devam eder.
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {"html" | "data"} kind
|
|
130
|
+
* @param {string} key
|
|
131
|
+
* @returns {Promise<unknown | null>}
|
|
132
|
+
*/
|
|
133
|
+
export async function diskGetJson(kind, key) {
|
|
134
|
+
const file = filePath(kind, key);
|
|
135
|
+
if (!file) return null;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const framed = unframe(await fs.readFile(file));
|
|
139
|
+
if (!framed) return null;
|
|
140
|
+
return decodeCacheValue(framed.body);
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Ateşle-unut. Dönüş değeri testler bekleyebilsin diye durur; istek yolu
|
|
148
|
+
* beklemez.
|
|
149
|
+
*
|
|
150
|
+
* @param {"html" | "data"} kind
|
|
151
|
+
* @param {string} key
|
|
152
|
+
* @param {unknown} value
|
|
153
|
+
* @returns {Promise<void>}
|
|
154
|
+
*/
|
|
155
|
+
export function diskSetJson(kind, key, value) {
|
|
156
|
+
const root = directory();
|
|
157
|
+
const file = filePath(kind, key);
|
|
158
|
+
if (!root || !file) return Promise.resolve();
|
|
159
|
+
|
|
160
|
+
pruneOldBuilds(root);
|
|
161
|
+
|
|
162
|
+
const encoded = encodeCacheValue(value);
|
|
163
|
+
if (encoded === null) return Promise.resolve();
|
|
164
|
+
|
|
165
|
+
const write = (/** @type {string | Buffer} */ payload) => {
|
|
166
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
167
|
+
return fs
|
|
168
|
+
.mkdir(path.dirname(file), { recursive: true })
|
|
169
|
+
.then(() => fs.writeFile(tmp, frame(key, payload)))
|
|
170
|
+
.then(() => fs.rename(tmp, file))
|
|
171
|
+
.catch((error) => {
|
|
172
|
+
console.warn(
|
|
173
|
+
"[disk-cache] write failed",
|
|
174
|
+
error instanceof Error ? error.message : error,
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
if (typeof encoded === "string" || Buffer.isBuffer(encoded)) return write(encoded);
|
|
180
|
+
return encoded.then(write);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {"html" | "data"} kind
|
|
185
|
+
* @param {string[]} keys
|
|
186
|
+
*/
|
|
187
|
+
export function diskDrop(kind, keys) {
|
|
188
|
+
if (!directory() || !keys.length) return;
|
|
189
|
+
|
|
190
|
+
for (const key of keys) {
|
|
191
|
+
const file = filePath(kind, key);
|
|
192
|
+
if (file) fs.rm(file, { force: true }).catch(() => {});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {"html" | "data"} kind
|
|
198
|
+
* @param {(key: string) => boolean} [match] Verilmezse türün tamamı silinir.
|
|
199
|
+
* @returns {Promise<number>}
|
|
200
|
+
*/
|
|
201
|
+
export async function diskDropMatching(kind, match) {
|
|
202
|
+
const root = directory();
|
|
203
|
+
if (!root) return 0;
|
|
204
|
+
|
|
205
|
+
const folder = path.join(root, kind);
|
|
206
|
+
/** @type {string[]} */
|
|
207
|
+
let names;
|
|
208
|
+
try {
|
|
209
|
+
names = await fs.readdir(folder);
|
|
210
|
+
} catch {
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let dropped = 0;
|
|
215
|
+
|
|
216
|
+
await Promise.all(
|
|
217
|
+
names.map(async (name) => {
|
|
218
|
+
const file = path.join(folder, name);
|
|
219
|
+
try {
|
|
220
|
+
if (match) {
|
|
221
|
+
const framed = unframe(await fs.readFile(file));
|
|
222
|
+
if (!framed || !match(framed.key)) return;
|
|
223
|
+
}
|
|
224
|
+
await fs.rm(file, { force: true });
|
|
225
|
+
dropped += 1;
|
|
226
|
+
} catch {
|
|
227
|
+
// Yarışta silinmiş dosya sorun değil.
|
|
228
|
+
}
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return dropped;
|
|
233
|
+
}
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Legacy EJS şablon motoru — opsiyonel peer.
|
|
3
|
-
*
|
|
4
|
-
* `.jsk`-only uygulamalar `ejs` kurmadan çalışır. Bir `.ejs` view veya layout
|
|
5
|
-
* istendiğinde bu modül uygulamadan (yoksa framework'ten) `ejs` yükler;
|
|
6
|
-
* paket yoksa göç yolunu gösteren net bir hata verir.
|
|
7
|
-
*/
|
|
8
|
-
import { createRequire } from "node:module";
|
|
9
|
-
import { pathToFileURL } from "node:url";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { getConfig, FRAMEWORK_ROOT } from "../config/index.js";
|
|
12
|
-
import { tryImportFromApp } from "../build/resolve-peer.mjs";
|
|
13
|
-
|
|
14
|
-
/** @type {import('ejs') | null | undefined} */
|
|
15
|
-
let cached;
|
|
16
|
-
|
|
17
|
-
const MISSING =
|
|
18
|
-
"EJS templates require the optional peer dependency `ejs`. " +
|
|
19
|
-
"Install it (`npm i ejs`) or migrate the template to `.jsk`.";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @returns {Promise<import('ejs')>}
|
|
23
|
-
*/
|
|
24
|
-
export async function loadEjs() {
|
|
25
|
-
if (cached) return cached;
|
|
26
|
-
if (cached === null) throw new Error(MISSING);
|
|
27
|
-
|
|
28
|
-
const config = getConfig();
|
|
29
|
-
let mod = await tryImportFromApp(config.root, "ejs");
|
|
30
|
-
|
|
31
|
-
// Framework kendi test/dev ortamında peer olarak tutuyor olabilir.
|
|
32
|
-
if (!mod) {
|
|
33
|
-
try {
|
|
34
|
-
const require = createRequire(path.join(FRAMEWORK_ROOT, "package.json"));
|
|
35
|
-
mod = await import(pathToFileURL(require.resolve("ejs")).href);
|
|
36
|
-
} catch {
|
|
37
|
-
mod = null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!mod) {
|
|
42
|
-
cached = null;
|
|
43
|
-
throw new Error(MISSING);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
cached = /** @type {import('ejs')} */ (mod.default ?? mod);
|
|
47
|
-
return cached;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @param {string} file
|
|
52
|
-
* @param {Record<string, unknown>} data
|
|
53
|
-
* @param {object} options
|
|
54
|
-
* @returns {Promise<string>}
|
|
55
|
-
*/
|
|
56
|
-
export async function renderEjsFile(file, data, options) {
|
|
57
|
-
const ejs = await loadEjs();
|
|
58
|
-
return ejs.renderFile(file, data, options);
|
|
59
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Legacy EJS şablon motoru — opsiyonel peer.
|
|
3
|
+
*
|
|
4
|
+
* `.jsk`-only uygulamalar `ejs` kurmadan çalışır. Bir `.ejs` view veya layout
|
|
5
|
+
* istendiğinde bu modül uygulamadan (yoksa framework'ten) `ejs` yükler;
|
|
6
|
+
* paket yoksa göç yolunu gösteren net bir hata verir.
|
|
7
|
+
*/
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { getConfig, FRAMEWORK_ROOT } from "../config/index.js";
|
|
12
|
+
import { tryImportFromApp } from "../build/resolve-peer.mjs";
|
|
13
|
+
|
|
14
|
+
/** @type {import('ejs') | null | undefined} */
|
|
15
|
+
let cached;
|
|
16
|
+
|
|
17
|
+
const MISSING =
|
|
18
|
+
"EJS templates require the optional peer dependency `ejs`. " +
|
|
19
|
+
"Install it (`npm i ejs`) or migrate the template to `.jsk`.";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @returns {Promise<import('ejs')>}
|
|
23
|
+
*/
|
|
24
|
+
export async function loadEjs() {
|
|
25
|
+
if (cached) return cached;
|
|
26
|
+
if (cached === null) throw new Error(MISSING);
|
|
27
|
+
|
|
28
|
+
const config = getConfig();
|
|
29
|
+
let mod = await tryImportFromApp(config.root, "ejs");
|
|
30
|
+
|
|
31
|
+
// Framework kendi test/dev ortamında peer olarak tutuyor olabilir.
|
|
32
|
+
if (!mod) {
|
|
33
|
+
try {
|
|
34
|
+
const require = createRequire(path.join(FRAMEWORK_ROOT, "package.json"));
|
|
35
|
+
mod = await import(pathToFileURL(require.resolve("ejs")).href);
|
|
36
|
+
} catch {
|
|
37
|
+
mod = null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!mod) {
|
|
42
|
+
cached = null;
|
|
43
|
+
throw new Error(MISSING);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
cached = /** @type {import('ejs')} */ (mod.default ?? mod);
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {string} file
|
|
52
|
+
* @param {Record<string, unknown>} data
|
|
53
|
+
* @param {object} options
|
|
54
|
+
* @returns {Promise<string>}
|
|
55
|
+
*/
|
|
56
|
+
export async function renderEjsFile(file, data, options) {
|
|
57
|
+
const ejs = await loadEjs();
|
|
58
|
+
return ejs.renderFile(file, data, options);
|
|
59
|
+
}
|