jskelet 0.3.4 → 0.3.5

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/CHANGELOG.md CHANGED
@@ -30,12 +30,11 @@ one is listed under a **Breaking** heading.
30
30
  - Top-level `logs` config for persistent sinks: daily NDJSON files
31
31
  (`logs.file`) and batched S3 PutObject (`logs.s3`) with embedded SigV4 — no
32
32
  `@aws-sdk` dependency. `kinds` selects `http` / `event` / `error`; `console`
33
- toggles runtime stdout lines. `JSKELET_LOG_BUCKET` (and `logs.s3.bucket`) may
34
- be a plain bucket or a `bucket/prefix/…` path. `JSKELET_S3_API_URL` sets the
35
- R2/MinIO endpoint (region defaults to `auto`). Missing credentials warn and
36
- disable the S3 sink without taking the site down. Env: `JSKELET_LOG_BUCKET`,
37
- `JSKELET_S3_ACCESS_KEY_ID`, `JSKELET_S3_SECRET_ACCESS_KEY`,
38
- `JSKELET_S3_SESSION_TOKEN`, `JSKELET_S3_REGION`, `JSKELET_S3_API_URL`.
33
+ toggles runtime stdout lines. `JSKELET_LOG_BUCKET` / `JSKELET_S3_BUCKET`
34
+ (+ optional `JSKELET_S3_KEY_PREFIX`) may be a plain bucket or a
35
+ `bucket/prefix/…` path; with credentials present the sink turns on without
36
+ `enabled: true`. `JSKELET_S3_API_URL` sets the S3-compatible endpoint
37
+ (region defaults to `auto`). Env: `JSKELET_LOG_BUCKET`, `JSKELET_S3_*`.
39
38
  - Admin panel pages under `/_jskelet/admin`: Overview, Cache, Routes, Views,
40
39
  Logs and System. Configurable `allowIps` (exact or CIDR), `blockBots` (default
41
40
  on — crawler UAs get 404 before login), and `logSize`. Live Logs use an
@@ -768,8 +768,8 @@ mevcut davranışını korur. Açıldığında HTTP access log ile framework ola
768
768
  | `s3.enabled` | `boolean` | `false` | S3 batch PutObject sink'i |
769
769
  | `s3.bucket` | `string \| null` | `null` | Bucket ya da `bucket/prefix/…` yolu; `JSKELET_LOG_BUCKET` ezer |
770
770
  | `s3.prefix` | `string` | `"jskelet/logs/"` | Nesne anahtarı öneki (yolda verilmediyse) |
771
- | `s3.region` | `string \| null` | `null` | Bölge; verilmezse `JSKELET_S3_REGION`, endpoint varken `auto` |
772
- | `s3.endpoint` | `string \| null` | `null` | R2 / MinIO API adresi; `JSKELET_S3_API_URL` ezer |
771
+ | `s3.region` | `string \| null` | `"auto"` | Bölge; verilmezse `JSKELET_S3_REGION`, yoksa `auto` |
772
+ | `s3.endpoint` | `string \| null` | `null` | S3-uyumlu API adresi; `JSKELET_S3_API_URL` ezer |
773
773
  | `s3.flushIntervalMs` | `number` | `5000` | Batch flush aralığı |
774
774
  | `s3.maxBatch` | `number` | `100` | Bu kadar satırda erken flush |
775
775
 
@@ -975,12 +975,14 @@ basılmaz.
975
975
  | `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) |
976
976
  | `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) |
977
977
  | `JSKELET_ADMIN` | `createApp` | — | Ayarlıysa yönetim panelini açar; `0` config'te açık olan paneli kapatır. Env config'i ezer, çünkü panel genelde bir arıza sırasında tek seferlik açılır. [06](./06-cache.md) |
978
- | `JSKELET_LOG_BUCKET` | `logs.s3` | — | S3 bucket ya da `ayberkenis/jskelet/logs` gibi `bucket/prefix` yolu; `logs.s3.bucket`'ı ezer (`JSKELET_S3_BUCKET` yedek ad) |
979
- | `JSKELET_S3_ACCESS_KEY_ID` | `logs.s3` | — | S3 PutObject imzası. Yoksa ve `s3.enabled` ise sink kapanır |
980
- | `JSKELET_S3_SECRET_ACCESS_KEY` | `logs.s3` | — | S3 imza sırrı |
981
- | `JSKELET_S3_SESSION_TOKEN` | `logs.s3` | — | Geçici credential'lar için isteğe bağlı |
982
- | `JSKELET_S3_REGION` | `logs.s3` | — | `logs.s3.region` verilmezse; endpoint varken varsayılan `auto` |
983
- | `JSKELET_S3_API_URL` | `logs.s3` | — | R2 / MinIO endpoint; `logs.s3.endpoint`'i ezer |
978
+ | `JSKELET_LOG_BUCKET` | `logs.s3` | — | Log hedefi: bucket ya da `bucket/prefix` yolu. Credential ile birlikte varsa sink otomatik açılır |
979
+ | `JSKELET_S3_BUCKET` | `logs.s3` | — | `JSKELET_LOG_BUCKET` yoksa bucket; `JSKELET_S3_KEY_PREFIX` ile birleşir |
980
+ | `JSKELET_S3_KEY_PREFIX` | `logs.s3` | — | `JSKELET_S3_BUCKET` ile kullanılır (`bucket/prefix`) |
981
+ | `JSKELET_S3_ACCESS_KEY_ID` | `logs.s3` | — | PutObject imzası |
982
+ | `JSKELET_S3_SECRET_ACCESS_KEY` | `logs.s3` | — | İmza sırrı (`JSKELET_S3_ACCESS_SECRET` yedek ad) |
983
+ | `JSKELET_S3_SESSION_TOKEN` | `logs.s3` | — | Geçici credential için isteğe bağlı |
984
+ | `JSKELET_S3_REGION` | `logs.s3` | `auto` | Verilmezse `auto` |
985
+ | `JSKELET_S3_API_URL` | `logs.s3` | — | S3-uyumlu endpoint; `logs.s3.endpoint`'i ezer |
984
986
  | `JSKELET_CLOUDFLARE_KEY` | Cloudflare cache yüzeyi | — | API token. Verilene kadar CDN purge'ü ve edge analitiği kapalıdır; config'teki `apiToken`'ı ezer. Token hiçbir cevapta dönmez. [06](./06-cache.md) |
985
987
  | `JSKELET_CLOUDFLARE_ZONE_ID` | Cloudflare cache yüzeyi | — | Zone kimliği. Token'la birlikte verilmedikçe hiçbir Cloudflare ucu çağrılmaz |
986
988
  | `JSKELET_CLOUDFLARE_HOSTNAME` | Cloudflare cache yüzeyi | — | Purge URL'lerinin kökü. Panel iç bir adresten açılıyorsa gerekir |
@@ -784,8 +784,8 @@ events (`event` / `error`) are written as NDJSON lines to a file and/or S3.
784
784
  | `s3.enabled` | `boolean` | `false` | S3 batch PutObject sink |
785
785
  | `s3.bucket` | `string \| null` | `null` | Bucket or a `bucket/prefix/…` path; `JSKELET_LOG_BUCKET` overrides |
786
786
  | `s3.prefix` | `string` | `"jskelet/logs/"` | Object key prefix (when not given in the path) |
787
- | `s3.region` | `string \| null` | `null` | Region; falls back to `JSKELET_S3_REGION`, or `auto` when an endpoint is set |
788
- | `s3.endpoint` | `string \| null` | `null` | R2 / MinIO API URL; `JSKELET_S3_API_URL` overrides |
787
+ | `s3.region` | `string \| null` | `"auto"` | Region; falls back to `JSKELET_S3_REGION`, otherwise `auto` |
788
+ | `s3.endpoint` | `string \| null` | `null` | S3-compatible API URL; `JSKELET_S3_API_URL` overrides |
789
789
  | `s3.flushIntervalMs` | `number` | `5000` | Batch flush interval |
790
790
  | `s3.maxBatch` | `number` | `100` | Flush early after this many lines |
791
791
 
@@ -995,12 +995,14 @@ and no warning is printed.
995
995
  | `JSKELET_SECRET` | `jskelet/cookies` | — | The signed cookie secret. Read when `security.cookieSecret` is not set; if neither exists, the signed cookie API throws. [12](./12-dashboards-and-sessions.md) |
996
996
  | `DEV_TOKEN` | `devGate`, `prewarm` | — | If set, every request without a token gets a 404. Prewarming carries the token as a cookie. [09](./09-dev-tools.md) |
997
997
  | `JSKELET_ADMIN` | `createApp` | — | When set, turns the admin panel on; `0` turns off a panel enabled in the config. The env wins because the panel is usually opened once during an incident. [06](./06-caching.md) |
998
- | `JSKELET_LOG_BUCKET` | `logs.s3` | — | S3 bucket or a `bucket/prefix` path like `ayberkenis/jskelet/logs`; overrides `logs.s3.bucket` (`JSKELET_S3_BUCKET` is an alias) |
999
- | `JSKELET_S3_ACCESS_KEY_ID` | `logs.s3` | — | Signs S3 PutObject. Missing when `s3.enabled` disables the sink |
1000
- | `JSKELET_S3_SECRET_ACCESS_KEY` | `logs.s3` | — | S3 signing secret |
998
+ | `JSKELET_LOG_BUCKET` | `logs.s3` | — | Log target: bucket or `bucket/prefix` path. With credentials, the sink turns on automatically |
999
+ | `JSKELET_S3_BUCKET` | `logs.s3` | — | Bucket when `JSKELET_LOG_BUCKET` is unset; joins with `JSKELET_S3_KEY_PREFIX` |
1000
+ | `JSKELET_S3_KEY_PREFIX` | `logs.s3` | — | Used with `JSKELET_S3_BUCKET` (`bucket/prefix`) |
1001
+ | `JSKELET_S3_ACCESS_KEY_ID` | `logs.s3` | — | Signs PutObject |
1002
+ | `JSKELET_S3_SECRET_ACCESS_KEY` | `logs.s3` | — | Signing secret (`JSKELET_S3_ACCESS_SECRET` is an alias) |
1001
1003
  | `JSKELET_S3_SESSION_TOKEN` | `logs.s3` | — | Optional, for temporary credentials |
1002
- | `JSKELET_S3_REGION` | `logs.s3` | — | Used when `logs.s3.region` is unset; defaults to `auto` when an endpoint is set |
1003
- | `JSKELET_S3_API_URL` | `logs.s3` | — | R2 / MinIO endpoint; overrides `logs.s3.endpoint` |
1004
+ | `JSKELET_S3_REGION` | `logs.s3` | `auto` | Defaults to `auto` when unset |
1005
+ | `JSKELET_S3_API_URL` | `logs.s3` | — | S3-compatible endpoint; overrides `logs.s3.endpoint` |
1004
1006
  | `JSKELET_CLOUDFLARE_KEY` | Cloudflare cache surface | — | API token. Until it is set, CDN purging and edge analytics stay off; it overrides `apiToken` in the config. The token is never returned in a response. [06](./06-caching.md) |
1005
1007
  | `JSKELET_CLOUDFLARE_ZONE_ID` | Cloudflare cache surface | — | Zone identifier. No Cloudflare endpoint is called unless it is set alongside the token |
1006
1008
  | `JSKELET_CLOUDFLARE_HOSTNAME` | Cloudflare cache surface | — | The root for purge URLs. Required when the panel is opened over an internal address |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jskelet",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "A framework that feels like no framework: Express 5 + EJS server rendering, vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -203,7 +203,7 @@ export const DEFAULT_REDIS = {
203
203
  * `JSKELET_S3_SECRET_ACCESS_KEY` (ve isteğe bağlı `JSKELET_S3_SESSION_TOKEN`).
204
204
  * Bucket için `JSKELET_LOG_BUCKET` (veya `JSKELET_S3_BUCKET`) env'i config'i
205
205
  * ezer ve `ayberkenis/jskelet/logs` gibi `bucket/prefix` yolunu kabul eder.
206
- * Uyumlu API adresi `JSKELET_S3_API_URL` (R2 / MinIO).
206
+ * Uyumlu API adresi `JSKELET_S3_API_URL` (S3-compatible endpoint).
207
207
  */
208
208
  export const DEFAULT_LOGS = {
209
209
  console: true,
@@ -430,6 +430,48 @@ export function splitS3BucketPath(value) {
430
430
  };
431
431
  }
432
432
 
433
+ /**
434
+ * @param {unknown} value
435
+ * @returns {string | null}
436
+ */
437
+ function envText(value) {
438
+ return typeof value === "string" && value ? value : null;
439
+ }
440
+
441
+ /**
442
+ * @returns {{ accessKeyId: string, secretAccessKey: string,
443
+ * sessionToken: string | null } | null}
444
+ */
445
+ export function readS3CredentialsFromEnv() {
446
+ const accessKeyId = envText(process.env.JSKELET_S3_ACCESS_KEY_ID);
447
+ const secretAccessKey =
448
+ envText(process.env.JSKELET_S3_SECRET_ACCESS_KEY) ??
449
+ envText(process.env.JSKELET_S3_ACCESS_SECRET);
450
+ if (!accessKeyId || !secretAccessKey) return null;
451
+ return {
452
+ accessKeyId,
453
+ secretAccessKey,
454
+ sessionToken: envText(process.env.JSKELET_S3_SESSION_TOKEN),
455
+ };
456
+ }
457
+
458
+ /**
459
+ * Log hedefi. Öncelik: `JSKELET_LOG_BUCKET` → `JSKELET_S3_BUCKET`
460
+ * (+ isteğe bağlı `JSKELET_S3_KEY_PREFIX`).
461
+ *
462
+ * @returns {string | null}
463
+ */
464
+ function resolveLogBucketEnv() {
465
+ const logPath = envText(process.env.JSKELET_LOG_BUCKET);
466
+ if (logPath) return logPath;
467
+
468
+ const bucket = envText(process.env.JSKELET_S3_BUCKET);
469
+ if (!bucket) return null;
470
+
471
+ const prefix = envText(process.env.JSKELET_S3_KEY_PREFIX);
472
+ return prefix ? `${bucket}/${prefix}` : bucket;
473
+ }
474
+
433
475
  /**
434
476
  * Kalıcı log sink'leri. Bozuk bir `kinds` listesi siteyi düşürmemeli —
435
477
  * bilinmeyen girdiler atılır; hiç geçerli tür kalmazsa varsayılana dönülür.
@@ -461,13 +503,8 @@ export function normalizeLogs(raw) {
461
503
  const flush = Number(s3Raw.flushIntervalMs);
462
504
  const batch = Number(s3Raw.maxBatch);
463
505
 
464
- /** @param {unknown} value */
465
- const text = (value) => (typeof value === "string" && value ? value : null);
466
-
467
506
  const bucketPath = splitS3BucketPath(
468
- text(process.env.JSKELET_LOG_BUCKET) ??
469
- text(process.env.JSKELET_S3_BUCKET) ??
470
- text(s3Raw.bucket),
507
+ resolveLogBucketEnv() ?? envText(s3Raw.bucket),
471
508
  );
472
509
 
473
510
  const configPrefix =
@@ -478,14 +515,28 @@ export function normalizeLogs(raw) {
478
515
  : DEFAULT_LOGS.s3.prefix;
479
516
 
480
517
  const endpoint =
481
- text(process.env.JSKELET_S3_API_URL) ?? text(s3Raw.endpoint);
518
+ envText(process.env.JSKELET_S3_API_URL) ?? envText(s3Raw.endpoint);
482
519
 
483
- // R2 / uyumlu API'lerde bölge çoğu zaman `auto`; endpoint varken region
484
- // zorunlu tutulursa tek satırlık env kurulumu boşa düşer.
520
+ // Uyumlu API'lerde (Cloudflare R2 vb.) imza bölgesi çoğu zaman `auto`.
521
+ // Region hiçbir kurulumda zorunlu değil.
485
522
  const region =
486
- text(s3Raw.region) ??
487
- text(process.env.JSKELET_S3_REGION) ??
488
- (endpoint ? "auto" : null);
523
+ envText(s3Raw.region) ??
524
+ envText(process.env.JSKELET_S3_REGION) ??
525
+ "auto";
526
+
527
+ const credentials = readS3CredentialsFromEnv();
528
+ // `JSKELET_LOG_BUCKET` (veya S3 bucket) + credential varsa config'te
529
+ // `enabled: true` unutulmuş olsa bile aç. Açık `enabled: false` ezer.
530
+ const envWantsLogs = Boolean(
531
+ envText(process.env.JSKELET_LOG_BUCKET) ||
532
+ envText(process.env.JSKELET_S3_BUCKET),
533
+ );
534
+ const enabled =
535
+ s3Raw.enabled === true ||
536
+ (s3Raw.enabled !== false &&
537
+ envWantsLogs &&
538
+ Boolean(credentials) &&
539
+ Boolean(bucketPath.bucket));
489
540
 
490
541
  return {
491
542
  console: source.console !== false,
@@ -499,7 +550,7 @@ export function normalizeLogs(raw) {
499
550
  rotate: "daily",
500
551
  },
501
552
  s3: {
502
- enabled: s3Raw.enabled === true,
553
+ enabled,
503
554
  bucket: bucketPath.bucket,
504
555
  // Yoldaki önek tek env ile klasör vermeyi mümkün kılar; yoksa config.
505
556
  prefix: bucketPath.prefix ?? configPrefix,
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import process from "node:process";
8
8
  import * as log from "../../log.mjs";
9
+ import { readS3CredentialsFromEnv } from "../../config/index.js";
9
10
  import { createFileSink } from "./file-sink.js";
10
11
  import { createS3Sink } from "./s3-sink.js";
11
12
 
@@ -23,20 +24,6 @@ let sinks = [];
23
24
  let kindSet = new Set();
24
25
  let accessMounted = false;
25
26
 
26
- /**
27
- * @returns {import('./s3-put.js').AwsCredentials | null}
28
- */
29
- function readCredentials() {
30
- const accessKeyId = process.env.JSKELET_S3_ACCESS_KEY_ID;
31
- const secretAccessKey = process.env.JSKELET_S3_SECRET_ACCESS_KEY;
32
- if (!accessKeyId || !secretAccessKey) return null;
33
- return {
34
- accessKeyId,
35
- secretAccessKey,
36
- sessionToken: process.env.JSKELET_S3_SESSION_TOKEN || null,
37
- };
38
- }
39
-
40
27
  /**
41
28
  * Access middleware mount edilmeli mi?
42
29
  *
@@ -79,25 +66,22 @@ export async function configureLogs(config) {
79
66
  }
80
67
 
81
68
  if (logs.s3.enabled) {
82
- const credentials = readCredentials();
69
+ const credentials = readS3CredentialsFromEnv();
83
70
  if (!logs.s3.bucket) {
84
71
  console.warn(
85
- "[logs] s3.enabled but no bucket (set JSKELET_LOG_BUCKET or logs.s3.bucket)",
86
- );
87
- } else if (!logs.s3.region) {
88
- console.warn(
89
- "[logs] s3.enabled but no region (set JSKELET_S3_REGION or logs.s3.region)",
72
+ "[logs] s3.enabled but no bucket (set JSKELET_LOG_BUCKET, JSKELET_S3_BUCKET or logs.s3.bucket)",
90
73
  );
91
74
  } else if (!credentials) {
92
75
  console.warn(
93
- "[logs] s3.enabled but JSKELET_S3_ACCESS_KEY_ID / JSKELET_S3_SECRET_ACCESS_KEY missing — S3 sink disabled",
76
+ "[logs] s3.enabled but credentials missing — set JSKELET_S3_ACCESS_KEY_ID and JSKELET_S3_SECRET_ACCESS_KEY",
94
77
  );
95
78
  } else {
79
+ const region = logs.s3.region || "auto";
96
80
  next.push(
97
81
  createS3Sink({
98
82
  bucket: logs.s3.bucket,
99
83
  prefix: logs.s3.prefix,
100
- region: logs.s3.region,
84
+ region,
101
85
  endpoint: logs.s3.endpoint,
102
86
  credentials,
103
87
  flushIntervalMs: logs.s3.flushIntervalMs,