jskelet 0.6.8 → 0.6.10

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
@@ -10,14 +10,17 @@ one is listed under a **Breaking** heading.
10
10
 
11
11
  ### Added
12
12
 
13
- - Optional CDN asset publishing: `build.assets.prefix` /
14
- `JSKELET_ASSET_PREFIX` prefixes `asset()` and optimised image URLs (manifest
15
- stays relative on disk). When `JSKELET_ASSETS_BUCKET` (or
16
- `build.assets.upload`) is set, production `jskelet build` PutObjects
17
- `public/assets/` (and optional `include` dirs) to S3 after precompress.
18
- Credentials prefer standard `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` /
19
- `AWS_SESSION_TOKEN`, then `JSKELET_S3_*`. Missing bucket or credentials fail
20
- the build; watch mode never uploads. `.br` / `.gz` siblings are skipped.
13
+ - Optional CDN asset publishing via `build.assets`: `prefix` rewrites
14
+ `asset()` / optimised image URLs (manifest stays relative on disk);
15
+ `upload.bucket` / `keyPrefix` / `region` / `endpoint` come from config only.
16
+ Production `jskelet build` PutObjects `public/assets/` (and optional
17
+ `include` dirs) after precompress. Only credentials are read from env
18
+ (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`, then
19
+ `JSKELET_S3_*`). The Assets upload step always appears on production builds;
20
+ when off it logs a one-line reason (`skipped (no build.assets.upload.bucket)`,
21
+ `skipped (credentials missing)`, …). A URL in `upload.bucket` is rejected
22
+ with a warning (CDN origin belongs in `prefix`). Watch mode never uploads.
23
+ `.br` / `.gz` siblings are skipped.
21
24
 
22
25
  ### Changed
23
26
 
@@ -96,10 +96,11 @@ export default {
96
96
  groups: { charts: ["chart.js"] },
97
97
  },
98
98
  assets: {
99
- prefix: process.env.JSKELET_ASSET_PREFIX,
99
+ prefix: "https://static.ornek.com/js",
100
100
  upload: {
101
- enabled: Boolean(process.env.JSKELET_ASSETS_BUCKET),
102
- bucket: process.env.JSKELET_ASSETS_BUCKET,
101
+ bucket: "static.ornek.com", // S3 bucket adı (noktalı olabilir)
102
+ keyPrefix: "js",
103
+ region: "eu-central-1",
103
104
  },
104
105
  },
105
106
  },
@@ -628,19 +629,21 @@ değişmez** — asset'ler origin'den servis edilir.
628
629
 
629
630
  | Alan | Tip | Varsayılan | Anlamı |
630
631
  | --- | --- | --- | --- |
631
- | `assets.prefix` | `string` | `""` | `asset()` / optimize görsel URL öneki (sonunda `/` yok). `JSKELET_ASSET_PREFIX` ezer. Manifest diskte göreli kalır. |
632
- | `assets.upload.enabled` | `boolean` | `false` | `true` veya `JSKELET_ASSETS_BUCKET` doluysa açılır; açık `false` ezer. |
633
- | `assets.upload.bucket` | `string \| null` | `null` | Bucket veya `bucket/key/prefix`. `JSKELET_ASSETS_BUCKET` ezer. |
634
- | `assets.upload.keyPrefix` | `string` | `""` | Yolda verilmediyse nesne anahtarı öneki (`prefix` takma adı da kabul). |
635
- | `assets.upload.region` | `string` | `us-east-1` | `AWS_REGION` / `AWS_DEFAULT_REGION` / `JSKELET_S3_REGION`; custom endpoint'te varsayılan `auto`. |
636
- | `assets.upload.endpoint` | `string \| null` | `null` | S3-uyumlu API; `AWS_ENDPOINT_URL` / `JSKELET_S3_API_URL` ezer. |
632
+ | `assets.prefix` | `string` | `""` | CDN origin; `asset()` URL öneki (sonunda `/` yok). Yalnızca config. Manifest diskte göreli kalır. |
633
+ | `assets.upload.enabled` | `boolean` | `false` | `true` veya config'te `bucket` doluysa açılır; açık `false` ezer. |
634
+ | `assets.upload.bucket` | `string \| null` | `null` | **S3 bucket adı** (`static.ornek.com` gibi noktalı adlar geçerli) veya `bucket/key/prefix`. `https://…` şeması kabul edilmez — o `prefix`. |
635
+ | `assets.upload.keyPrefix` | `string` | `""` | Nesne anahtarı öneki; bucket yolunda verilmişse o kazanır (`prefix` takma adı da kabul). |
636
+ | `assets.upload.region` | `string` | `us-east-1` | Config; custom `endpoint` varken varsayılan `auto`. |
637
+ | `assets.upload.endpoint` | `string \| null` | `null` | S3-uyumlu API (R2 / MinIO); config. |
637
638
  | `assets.upload.include` | `string[]` | `["assets"]` | `public/` altı dizinler (`assets`, isteğe `fonts`, …). |
638
639
  | `assets.upload.cacheControl` | `string` | `public, max-age=31536000, immutable` | PutObject `Cache-Control`. |
639
640
 
640
- Credential config'e yazılmaz. Öncelik: `AWS_ACCESS_KEY_ID` /
641
- `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`, yoksa `JSKELET_S3_*`. Upload açık
642
- iken bucket veya credential yoksa **build düşer**. Watch turunda upload yok.
643
- `.br` / `.gz` yüklenmez. Ayrıntı: [08-build.md](./08-build.md),
641
+ **Yalnızca credential env'den gelir:** `AWS_ACCESS_KEY_ID` /
642
+ `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` (yoksa `JSKELET_S3_*`). Prefix,
643
+ bucket, region vb. config'te yazılır. Production build'de Assets upload adımı
644
+ her zaman görünür; kapalıysa `skipped (no build.assets.upload.bucket)` /
645
+ `skipped (credentials missing)` gibi tek satır sebep basılır. Watch'ta adım
646
+ yok. `.br` / `.gz` yüklenmez. Ayrıntı: [08-build.md](./08-build.md),
644
647
  [10-dagitim.md](./10-dagitim.md).
645
648
 
646
649
  ```js
@@ -656,10 +659,12 @@ build: {
656
659
  },
657
660
  },
658
661
  assets: {
659
- prefix: process.env.JSKELET_ASSET_PREFIX, // https://dxxxx.cloudfront.net
662
+ // HTML: /assets/app.css → https://static.ornek.com/js/assets/app.css
663
+ prefix: "https://static.ornek.com/js",
660
664
  upload: {
661
- enabled: Boolean(process.env.JSKELET_ASSETS_BUCKET),
662
- bucket: process.env.JSKELET_ASSETS_BUCKET, // my-bucket veya my-bucket/static
665
+ bucket: "static.ornek.com", // veya "static.ornek.com/js" → keyPrefix js/
666
+ keyPrefix: "js", // S3 key: js/assets/…
667
+ region: "eu-central-1",
663
668
  include: ["assets", "fonts"],
664
669
  },
665
670
  },
@@ -1227,13 +1232,9 @@ basılmaz.
1227
1232
  | `DEV_GATE` | `devGate` | kapalı | `1` gate'i açar, `0` config'te açık olsa da kapatır. `DEV_TOKEN` tek başına açmaz. [09](./09-dev-araclari.md) |
1228
1233
  | `DEV_TOKEN` | `devGate`, `prewarm` | — | Gate açıkken beklenen sır. Yoksa veya gate kapalıysa site açık kalır. Isıtma, gate açıkken token'ı çerez olarak taşır. [09](./09-dev-araclari.md) |
1229
1234
  | `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) |
1230
- | `JSKELET_ASSET_PREFIX` | `build.assets.prefix` | — | CDN origin; `asset()` URL'lerine eklenir (sonunda `/` yok) |
1231
- | `JSKELET_ASSETS_BUCKET` | `build.assets.upload` | — | Asset upload hedefi: bucket veya `bucket/prefix`. Credential ile birlikte production build sonunda PutObject |
1232
1235
  | `AWS_ACCESS_KEY_ID` | S3 PutObject | — | Standart AWS imza anahtarı (log + asset upload); `JSKELET_S3_*`'dan önce |
1233
1236
  | `AWS_SECRET_ACCESS_KEY` | S3 PutObject | — | Standart AWS imza sırrı |
1234
1237
  | `AWS_SESSION_TOKEN` | S3 PutObject | — | Geçici credential (OIDC / STS) |
1235
- | `AWS_REGION` / `AWS_DEFAULT_REGION` | `build.assets.upload` | — | Asset upload bölgesi |
1236
- | `AWS_ENDPOINT_URL` | `build.assets.upload` | — | S3-uyumlu endpoint (asset upload) |
1237
1238
  | `JSKELET_LOG_BUCKET` | `logs.s3` | — | Log hedefi: bucket ya da `bucket/prefix` yolu. Credential ile birlikte varsa sink otomatik açılır |
1238
1239
  | `JSKELET_S3_BUCKET` | `logs.s3` | — | `JSKELET_LOG_BUCKET` yoksa bucket; `JSKELET_S3_KEY_PREFIX` ile birleşir |
1239
1240
  | `JSKELET_S3_KEY_PREFIX` | `logs.s3` | — | `JSKELET_S3_BUCKET` ile kullanılır (`bucket/prefix`) |
package/docs/08-build.md CHANGED
@@ -20,7 +20,7 @@ davranışı burada. Çıktının çalışma anında nasıl servis edildiği
20
20
  5. Images config.images !== false, watch değil ve sharp kurulu ise
21
21
  6. Manifest .jskelet/manifest.json
22
22
  7. Precompress watch değilse
23
- 8. Assets upload build.assets.upload açıksa (watch değil)
23
+ 8. Assets upload watch değilse (kapalıysa `skipped (sebep)`)
24
24
  ```
25
25
 
26
26
  Şablon derlemesi asset taramasından **önce** biter; istek yolunda parse yoktur.
@@ -34,7 +34,8 @@ Sıra rastgele değil:
34
34
  - **Precompress, upload'tan hemen önce:** origin hâlâ `.br`/`.gz` üretir; S3
35
35
  adımı yalnızca ham dosyaları kopyalar.
36
36
  - **Görseller watch turunda hiç çalışmaz:** `sharp` ile yeniden kodlama pahalı.
37
- - **Assets upload watch'ta yok:** yalnızca production `jskelet build`.
37
+ - **Assets upload watch'ta yok:** yalnızca production `jskelet build`; kapalıysa
38
+ yine satır basılır (`skipped (no build.assets.upload.bucket)` vb.).
38
39
 
39
40
  Görevler yalnızca ilgili yapılandırma varsa çalışır. Font tanımlamayan bir proje
40
41
  font adımını hiç görmez; bu, "framework her projeye kendi varsayımlarını
@@ -77,24 +78,24 @@ dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
77
78
  ```
78
79
 
79
80
  - `asset(name)` manifest'te varsa hash'li URL'i, yoksa `/assets/<name>` döner.
80
- `build.assets.prefix` / `JSKELET_ASSET_PREFIX` doluysa bu göreli yolun önüne
81
- CDN origin eklenir (manifest diskte göreli kalır).
81
+ `build.assets.prefix` doluysa bu göreli yolun önüne CDN origin eklenir
82
+ (manifest diskte göreli kalır).
82
83
  - `hasAsset(name)` manifest'te olup olmadığını söyler.
83
84
 
84
85
  ### CDN prefix ve S3 upload
85
86
 
86
- Varsayılan kapalıdır. Açılınca:
87
+ Varsayılan kapalıdır. Prefix / bucket / keyPrefix config'te; yalnızca
88
+ credential env'den (`AWS_*`, yoksa `JSKELET_S3_*`). Açılınca:
87
89
 
88
90
  1. Production build `public/assets/` (ve `include` listesindeki diğer dizinler)
89
91
  altındaki dosyaları S3'e PutObject ile kopyalar.
90
- 2. Çalışma anında `asset()` / optimize görseller `JSKELET_ASSET_PREFIX` ile
92
+ 2. Çalışma anında `asset()` / optimize görseller `build.assets.prefix` ile
91
93
  absolute CDN URL basar. Entry CDN'deyse esbuild chunk'ları göreli yolla aynı
92
94
  host'a gider.
93
95
 
94
- Credential: `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` (önce), yoksa
95
- `JSKELET_S3_*`. Bucket: `JSKELET_ASSETS_BUCKET`. Config sözlüğü:
96
- [07-yapilandirma.md](./07-yapilandirma.md#build). Dağıtım notları:
97
- [10-dagitim.md](./10-dagitim.md).
96
+ Bucket adı noktalı olabilir (`static.ornek.com`); `https://…` şeması
97
+ `prefix`'e yazılır. Config sözlüğü: [07-yapilandirma.md](./07-yapilandirma.md#build).
98
+ Dağıtım notları: [10-dagitim.md](./10-dagitim.md).
98
99
 
99
100
  Build çalışmadıysa uygulama yine ayağa kalkar: `hasAsset()` false olur, layout
100
101
  stylesheet ve script etiketlerini hiç basmaz. `jskelet build` unutulduğunda hata
@@ -51,17 +51,27 @@ ayarlamayı düşünmeniz gerekenler:
51
51
  | `PREWARM_MAX` | Site boyutuna göre | Açılışta ısıtılacak sayfa sayısı |
52
52
  | `PREWARM_INTERVAL_SECONDS` | `0` ya da uzun bir değer | Hiç ziyaret edilmeyen sayfaları sıcak tutmak isterseniz |
53
53
  | `DEV_GATE` + `DEV_TOKEN` | Yalnızca staging'de | Yayına açılmamış ortamı gizler. Token tek başına siteyi kilitlemez |
54
- | `JSKELET_S3_*` / `AWS_*` | Access log veya asset'leri S3'e yazıyorsanız | Credential; AWS deploy'da `AWS_ACCESS_KEY_ID` vb. yeter. Ayrıntı [07](./07-yapilandirma.md) |
55
- | `JSKELET_ASSETS_BUCKET` | Hash'li asset'leri S3/CloudFront'a basıyorsanız | Production `jskelet build` sonunda PutObject; `JSKELET_ASSET_PREFIX` ile HTML CDN URL basar |
54
+ | `JSKELET_S3_*` / `AWS_*` | Access log veya asset'leri S3'e yazıyorsanız | Yalnızca credential; bucket/prefix `jskelet.config.mjs` → `build.assets`. Ayrıntı [07](./07-yapilandirma.md) |
56
55
 
57
56
  ### Asset'leri S3 / CloudFront'a basmak
58
57
 
59
- Varsayılan kapalıdır. CI veya prod build ortamında:
58
+ Varsayılan kapalıdır. Config'te prefix + bucket; CI'da yalnızca credential:
59
+
60
+ ```js
61
+ // jskelet.config.mjs
62
+ build: {
63
+ assets: {
64
+ prefix: "https://static.ornek.com/js",
65
+ upload: {
66
+ bucket: "static.ornek.com", // veya "static.ornek.com/js"
67
+ keyPrefix: "js",
68
+ region: "eu-central-1",
69
+ },
70
+ },
71
+ },
72
+ ```
60
73
 
61
74
  ```bash
62
- JSKELET_ASSETS_BUCKET=my-app-assets # veya my-app-assets/static
63
- JSKELET_ASSET_PREFIX=https://dxxxx.cloudfront.net
64
- AWS_REGION=eu-central-1
65
75
  AWS_ACCESS_KEY_ID=…
66
76
  AWS_SECRET_ACCESS_KEY=…
67
77
  # geçici credential ise:
@@ -70,9 +80,9 @@ AWS_SECRET_ACCESS_KEY=…
70
80
 
71
81
  `jskelet build` bitince `public/assets/` (ve config'te `include` edilen
72
82
  dizinler) S3'e kopyalanır; origin'deki dosyalar da durur. Bucket veya
73
- credential eksikken upload açıksa build düşer. Watch / `jskelet dev` upload
74
- yapmaz. CSP kullanıyorsanız CDN origin'ini `script-src` / `style-src` /
75
- `img-src` ve `preconnect` listesine ekleyin.
83
+ credential yoksa adım `skipped (…)` ile sebep basar, build devam eder. Watch /
84
+ `jskelet dev` upload yapmaz. CSP kullanıyorsanız CDN origin'ini `script-src` /
85
+ `style-src` / `img-src` ve `preconnect` listesine ekleyin.
76
86
 
77
87
  Production'da dosya veya S3 sink açıldığında HTTP access log middleware
78
88
  otomatik mount edilir (`logs.kinds` içinde `http` varsa). Admin paneli ring'i
@@ -100,10 +100,11 @@ export default {
100
100
  groups: { charts: ["chart.js"] },
101
101
  },
102
102
  assets: {
103
- prefix: process.env.JSKELET_ASSET_PREFIX,
103
+ prefix: "https://static.example.com/js",
104
104
  upload: {
105
- enabled: Boolean(process.env.JSKELET_ASSETS_BUCKET),
106
- bucket: process.env.JSKELET_ASSETS_BUCKET,
105
+ bucket: "static.example.com", // S3 bucket name (dots are fine)
106
+ keyPrefix: "js",
107
+ region: "eu-central-1",
107
108
  },
108
109
  },
109
110
  },
@@ -641,20 +642,22 @@ changes** — assets stay on the origin.
641
642
 
642
643
  | Field | Type | Default | Meaning |
643
644
  | --- | --- | --- | --- |
644
- | `assets.prefix` | `string` | `""` | Prefix for `asset()` / optimised image URLs (no trailing `/`). `JSKELET_ASSET_PREFIX` overrides. The on-disk manifest stays relative. |
645
- | `assets.upload.enabled` | `boolean` | `false` | Turns on when `true` or when `JSKELET_ASSETS_BUCKET` is set; explicit `false` wins. |
646
- | `assets.upload.bucket` | `string \| null` | `null` | Bucket or `bucket/key/prefix`. `JSKELET_ASSETS_BUCKET` overrides. |
647
- | `assets.upload.keyPrefix` | `string` | `""` | Object key prefix when not given in the path (`prefix` is accepted as an alias). |
648
- | `assets.upload.region` | `string` | `us-east-1` | `AWS_REGION` / `AWS_DEFAULT_REGION` / `JSKELET_S3_REGION`; defaults to `auto` with a custom endpoint. |
649
- | `assets.upload.endpoint` | `string \| null` | `null` | S3-compatible API; `AWS_ENDPOINT_URL` / `JSKELET_S3_API_URL` overrides. |
645
+ | `assets.prefix` | `string` | `""` | CDN origin; prefix for `asset()` URLs (no trailing `/`). Config only. The on-disk manifest stays relative. |
646
+ | `assets.upload.enabled` | `boolean` | `false` | Turns on when `true` or when `bucket` is set in config; explicit `false` wins. |
647
+ | `assets.upload.bucket` | `string \| null` | `null` | **S3 bucket name** (dotted names like `static.example.com` are fine) or `bucket/key/prefix`. A `https://…` scheme is rejected — that belongs in `prefix`. |
648
+ | `assets.upload.keyPrefix` | `string` | `""` | Object key prefix; a path on the bucket wins (`prefix` is accepted as an alias). |
649
+ | `assets.upload.region` | `string` | `us-east-1` | Config; defaults to `auto` when `endpoint` is set. |
650
+ | `assets.upload.endpoint` | `string \| null` | `null` | S3-compatible API (R2 / MinIO); config. |
650
651
  | `assets.upload.include` | `string[]` | `["assets"]` | Directories under `public/` (`assets`, optionally `fonts`, …). |
651
652
  | `assets.upload.cacheControl` | `string` | `public, max-age=31536000, immutable` | PutObject `Cache-Control`. |
652
653
 
653
- Credentials are not written in the config. Preference order:
654
- `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`, then
655
- `JSKELET_S3_*`. When upload is on, a missing bucket or credentials **fails the
656
- build**. No upload on watch passes. `.br` / `.gz` siblings are not uploaded.
657
- Details: [08-build.md](./08-build.md), [10-deployment.md](./10-deployment.md).
654
+ **Only credentials come from the environment:** `AWS_ACCESS_KEY_ID` /
655
+ `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` (else `JSKELET_S3_*`). Prefix,
656
+ bucket, region, etc. are written in the config. On a production build the Assets
657
+ upload step always appears; when off it logs a one-line reason such as
658
+ `skipped (no build.assets.upload.bucket)` or `skipped (credentials missing)`.
659
+ No upload on watch passes. `.br` / `.gz` siblings are not uploaded. Details:
660
+ [08-build.md](./08-build.md), [10-deployment.md](./10-deployment.md).
658
661
 
659
662
  ```js
660
663
  build: {
@@ -669,10 +672,12 @@ build: {
669
672
  },
670
673
  },
671
674
  assets: {
672
- prefix: process.env.JSKELET_ASSET_PREFIX, // https://dxxxx.cloudfront.net
675
+ // HTML: /assets/app.css → https://static.example.com/js/assets/app.css
676
+ prefix: "https://static.example.com/js",
673
677
  upload: {
674
- enabled: Boolean(process.env.JSKELET_ASSETS_BUCKET),
675
- bucket: process.env.JSKELET_ASSETS_BUCKET, // my-bucket or my-bucket/static
678
+ bucket: "static.example.com", // or "static.example.com/js" → keyPrefix js/
679
+ keyPrefix: "js", // S3 key: js/assets/…
680
+ region: "eu-central-1",
676
681
  include: ["assets", "fonts"],
677
682
  },
678
683
  },
@@ -1249,13 +1254,9 @@ and no warning is printed.
1249
1254
  | `DEV_GATE` | `devGate` | off | `1` turns the gate on, `0` turns it off even when config enabled it. `DEV_TOKEN` alone does not turn it on. [09](./09-dev-tools.md) |
1250
1255
  | `DEV_TOKEN` | `devGate`, `prewarm` | — | The secret expected while the gate is on. If it is missing, or the gate is off, the site stays public. Prewarming carries the token as a cookie only while the gate is on. [09](./09-dev-tools.md) |
1251
1256
  | `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) |
1252
- | `JSKELET_ASSET_PREFIX` | `build.assets.prefix` | — | CDN origin prepended to `asset()` URLs (no trailing `/`) |
1253
- | `JSKELET_ASSETS_BUCKET` | `build.assets.upload` | — | Asset upload target: bucket or `bucket/prefix`. With credentials, production build PutObject |
1254
1257
  | `AWS_ACCESS_KEY_ID` | S3 PutObject | — | Standard AWS signing key (logs + asset upload); before `JSKELET_S3_*` |
1255
1258
  | `AWS_SECRET_ACCESS_KEY` | S3 PutObject | — | Standard AWS signing secret |
1256
1259
  | `AWS_SESSION_TOKEN` | S3 PutObject | — | Temporary credentials (OIDC / STS) |
1257
- | `AWS_REGION` / `AWS_DEFAULT_REGION` | `build.assets.upload` | — | Asset upload region |
1258
- | `AWS_ENDPOINT_URL` | `build.assets.upload` | — | S3-compatible endpoint (asset upload) |
1259
1260
  | `JSKELET_LOG_BUCKET` | `logs.s3` | — | Log target: bucket or `bucket/prefix` path. With credentials, the sink turns on automatically |
1260
1261
  | `JSKELET_S3_BUCKET` | `logs.s3` | — | Bucket when `JSKELET_LOG_BUCKET` is unset; joins with `JSKELET_S3_KEY_PREFIX` |
1261
1262
  | `JSKELET_S3_KEY_PREFIX` | `logs.s3` | — | Used with `JSKELET_S3_BUCKET` (`bucket/prefix`) |
@@ -20,7 +20,7 @@ build is in [09-dev-tools.md](./09-dev-tools.md).
20
20
  5. Images if config.images !== false, not watch, and sharp is installed
21
21
  6. Manifest .jskelet/manifest.json
22
22
  7. Precompress if not watch
23
- 8. Assets upload if build.assets.upload is on (not watch)
23
+ 8. Assets upload if not watch (logs `skipped (reason)` when off)
24
24
  ```
25
25
 
26
26
  Template compilation finishes **before** asset scanning; there is no parse on
@@ -34,7 +34,8 @@ The order is not arbitrary:
34
34
  - **Precompress runs just before upload:** the origin still gets `.br`/`.gz`;
35
35
  the S3 step copies only the raw files.
36
36
  - **Images never run on a watch pass:** re-encoding with `sharp` is expensive.
37
- - **Assets upload never runs on watch:** only production `jskelet build`.
37
+ - **Assets upload never runs on watch:** only production `jskelet build`; when
38
+ off the step still prints (`skipped (no build.assets.upload.bucket)`, etc.).
38
39
 
39
40
  Tasks only run if the relevant configuration exists. A project that does not
40
41
  define any fonts never sees the font step; this is the build-side counterpart of
@@ -80,25 +81,24 @@ They are passed to templates automatically; in server code,
80
81
  ```
81
82
 
82
83
  - `asset(name)` returns the hashed URL if it is in the manifest, otherwise
83
- `/assets/<name>`. When `build.assets.prefix` / `JSKELET_ASSET_PREFIX` is set,
84
- that relative path is prefixed with the CDN origin (the on-disk manifest stays
85
- relative).
84
+ `/assets/<name>`. When `build.assets.prefix` is set, that relative path is
85
+ prefixed with the CDN origin (the on-disk manifest stays relative).
86
86
  - `hasAsset(name)` tells you whether it is in the manifest.
87
87
 
88
88
  ### CDN prefix and S3 upload
89
89
 
90
- Off by default. When enabled:
90
+ Off by default. Prefix / bucket / keyPrefix come from config; only credentials
91
+ come from the environment (`AWS_*`, else `JSKELET_S3_*`). When enabled:
91
92
 
92
93
  1. A production build PutObjects files under `public/assets/` (and any other
93
94
  directories in `include`) to S3.
94
95
  2. At runtime `asset()` / optimised images emit absolute CDN URLs via
95
- `JSKELET_ASSET_PREFIX`. When the entry script is on the CDN, esbuild chunks
96
+ `build.assets.prefix`. When the entry script is on the CDN, esbuild chunks
96
97
  resolve relatively on the same host.
97
98
 
98
- Credentials: `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` first, else
99
- `JSKELET_S3_*`. Bucket: `JSKELET_ASSETS_BUCKET`. Config reference:
100
- [07-configuration.md](./07-configuration.md#build). Deploy notes:
101
- [10-deployment.md](./10-deployment.md).
99
+ Bucket names may include dots (`static.example.com`); put `https://…` in
100
+ `prefix`. Config reference: [07-configuration.md](./07-configuration.md#build).
101
+ Deploy notes: [10-deployment.md](./10-deployment.md).
102
102
 
103
103
  If the build has not run, the application still comes up: `hasAsset()` is false
104
104
  and the layout never emits the stylesheet and script tags. When `jskelet build`
@@ -52,17 +52,27 @@ considering in production:
52
52
  | `PREWARM_MAX` | Depends on site size | Number of pages warmed at startup |
53
53
  | `PREWARM_INTERVAL_SECONDS` | `0` or a long value | If you want to keep never-visited pages warm |
54
54
  | `DEV_GATE` + `DEV_TOKEN` | Staging only | Hides an environment that is not public yet. The token alone does not lock the site |
55
- | `JSKELET_S3_*` / `AWS_*` | If you write access logs or assets to S3 | Credentials; on AWS, `AWS_ACCESS_KEY_ID` etc. are enough. Details in [07](./07-configuration.md) |
56
- | `JSKELET_ASSETS_BUCKET` | If you publish hashed assets to S3/CloudFront | PutObject at the end of production `jskelet build`; `JSKELET_ASSET_PREFIX` makes HTML emit CDN URLs |
55
+ | `JSKELET_S3_*` / `AWS_*` | If you write access logs or assets to S3 | Credentials only; bucket/prefix live in `jskelet.config.mjs` → `build.assets`. Details in [07](./07-configuration.md) |
57
56
 
58
57
  ### Publishing assets to S3 / CloudFront
59
58
 
60
- Off by default. In CI or a production build environment:
59
+ Off by default. Prefix + bucket in config; credentials only in CI:
60
+
61
+ ```js
62
+ // jskelet.config.mjs
63
+ build: {
64
+ assets: {
65
+ prefix: "https://static.example.com/js",
66
+ upload: {
67
+ bucket: "static.example.com", // or "static.example.com/js"
68
+ keyPrefix: "js",
69
+ region: "eu-central-1",
70
+ },
71
+ },
72
+ },
73
+ ```
61
74
 
62
75
  ```bash
63
- JSKELET_ASSETS_BUCKET=my-app-assets # or my-app-assets/static
64
- JSKELET_ASSET_PREFIX=https://dxxxx.cloudfront.net
65
- AWS_REGION=eu-central-1
66
76
  AWS_ACCESS_KEY_ID=…
67
77
  AWS_SECRET_ACCESS_KEY=…
68
78
  # for temporary credentials:
@@ -70,10 +80,10 @@ AWS_SECRET_ACCESS_KEY=…
70
80
  ```
71
81
 
72
82
  When `jskelet build` finishes, files under `public/assets/` (and any other
73
- `include` directories) are copied to S3; origin copies remain. If upload is on
74
- and the bucket or credentials are missing, the build fails. Watch /
75
- `jskelet dev` never upload. If you use CSP, add the CDN origin to
76
- `script-src` / `style-src` / `img-src` and to `preconnect`.
83
+ `include` directories) are copied to S3; origin copies remain. If the bucket or
84
+ credentials are missing, the step logs `skipped (…)` with the reason and the
85
+ build continues. Watch / `jskelet dev` never upload. If you use CSP, add the CDN
86
+ origin to `script-src` / `style-src` / `img-src` and to `preconnect`.
77
87
 
78
88
  When a file or S3 sink is enabled in production, the HTTP access log middleware
79
89
  mounts automatically (if `http` is in `logs.kinds`). The admin panel ring is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jskelet",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "A framework that feels like no framework: Express 5 + build-time .jsk SSR (optional EJS peer), vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -110,8 +110,9 @@ if (!watch) {
110
110
  const { precompressAssets } = await import("./tasks/precompress.mjs");
111
111
  await task("Precompress", precompressAssets);
112
112
 
113
+ // Her production build'de satır görünür; kapalıysa `skipped (sebep)`.
113
114
  // CDN'e basılacak dosyalar precompress'ten sonra hazır; `.br`/`.gz` yüklenmez.
114
- if (config.build?.assets?.upload?.enabled) {
115
+ {
115
116
  const { uploadAssets } = await import("./tasks/assets-upload.mjs");
116
117
  await task("Assets upload", () => uploadAssets(config));
117
118
  }
@@ -2,8 +2,8 @@
2
2
  * Production build sonunda hash'li varlıkları S3'e PutObject ile kopyalar.
3
3
  *
4
4
  * Watch turunda çalışmaz (çağıran sorumlu). Credential env'den gelir
5
- * (`AWS_*` önce, `JSKELET_S3_*` yedek). Bucket yoksa veya credential yoksa
6
- * build düşer — CDN prefix ile boş bucket'a deploy olmasın diye.
5
+ * (`AWS_*` önce, `JSKELET_S3_*` yedek). Bucket / credential yoksa veya upload
6
+ * kapalıysa adım yine görünür: `skipped (…)` ile sebep basılır, build düşmez.
7
7
  *
8
8
  * `.br` / `.gz` kardeşleri yüklenmez: edge kendi sıkıştırmasını yapsın;
9
9
  * origin precompress Express için kalır.
@@ -12,6 +12,7 @@ import fs from "node:fs";
12
12
  import path from "node:path";
13
13
  import { readS3CredentialsFromEnv } from "../../config/index.js";
14
14
  import { putObject } from "../../server/logs/s3-put.js";
15
+ import * as log from "../../log.mjs";
15
16
  import { paths } from "../paths.mjs";
16
17
 
17
18
  const CONCURRENCY = 8;
@@ -87,27 +88,46 @@ function resolveIncludeDir(name) {
87
88
  }
88
89
 
89
90
  /**
91
+ * Upload yapılmayacaksa kısa sebep; yapılacaksa `null`.
92
+ *
90
93
  * @param {import('../../config/index.js').ResolvedConfig} config
91
- * @param {{ put?: typeof putObject }} [options]
92
- * @returns {Promise<string>} Özet satır (log.task için)
94
+ * @returns {string | null}
93
95
  */
94
- export async function uploadAssets(config, options = {}) {
96
+ export function assetsUploadSkipReason(config) {
95
97
  const upload = config.build?.assets?.upload;
96
- if (!upload?.enabled) return "skipped";
97
98
 
98
- if (!upload.bucket) {
99
- throw new Error(
100
- "[assets] upload enabled but no bucket — set JSKELET_ASSETS_BUCKET " +
101
- "or build.assets.upload.bucket",
102
- );
99
+ if (!upload?.enabled) {
100
+ // Bucket yoksa asıl eksik alanı söyle — `enabled: false` çoğu zaman
101
+ // bucket tanımlanmadığı için dolaylıdır.
102
+ if (!upload?.bucket) return "no build.assets.upload.bucket";
103
+ return "build.assets.upload.enabled is false";
104
+ }
105
+
106
+ if (!upload.bucket) return "no build.assets.upload.bucket";
107
+
108
+ if (!readS3CredentialsFromEnv()) return "credentials missing";
109
+
110
+ return null;
111
+ }
112
+
113
+ /**
114
+ * @param {import('../../config/index.js').ResolvedConfig} config
115
+ * @param {{ put?: typeof putObject }} [options]
116
+ * @returns {Promise<void>}
117
+ */
118
+ export async function uploadAssets(config, options = {}) {
119
+ const reason = assetsUploadSkipReason(config);
120
+ if (reason) {
121
+ log.detail(`skipped (${reason})`);
122
+ return;
103
123
  }
104
124
 
125
+ const upload = config.build.assets.upload;
105
126
  const credentials = readS3CredentialsFromEnv();
106
- if (!credentials) {
107
- throw new Error(
108
- "[assets] upload enabled but credentials missing — set " +
109
- "AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY or JSKELET_S3_*",
110
- );
127
+ // skipReason null iken credential ve bucket vardır; TS/akış için daralt.
128
+ if (!upload.bucket || !credentials) {
129
+ log.detail("skipped (credentials missing)");
130
+ return;
111
131
  }
112
132
 
113
133
  const put = options.put ?? putObject;
@@ -131,7 +151,10 @@ export async function uploadAssets(config, options = {}) {
131
151
  }
132
152
  }
133
153
 
134
- if (!jobs.length) return "0 files";
154
+ if (!jobs.length) {
155
+ log.detail("0 files");
156
+ return;
157
+ }
135
158
 
136
159
  let uploaded = 0;
137
160
  let index = 0;
@@ -163,5 +186,5 @@ export async function uploadAssets(config, options = {}) {
163
186
 
164
187
  const target = `${upload.bucket}/${keyPrefix}`.replace(/\/+/g, "/");
165
188
  const via = upload.endpoint ? ` @ ${upload.endpoint}` : "";
166
- return `${uploaded} → s3://${target}${via}`;
189
+ log.detail(`${uploaded} → s3://${target}${via}`);
167
190
  }
@@ -498,10 +498,11 @@ export const DEFAULT_BUILD = {
498
498
  chunkGroup: null,
499
499
  },
500
500
  assets: {
501
- /** CDN / CloudFront origin; sonunda `/` yok. Env: `JSKELET_ASSET_PREFIX`. */
501
+ /** CDN / CloudFront origin; sonunda `/` yok. Yalnızca config. */
502
502
  prefix: "",
503
503
  upload: {
504
504
  enabled: false,
505
+ /** S3 bucket adı (CDN URL değil). Yalnızca config; credential env'den. */
505
506
  bucket: null,
506
507
  /** Nesne anahtarı öneki (`bucket/path` split'inden veya config). */
507
508
  keyPrefix: "",
@@ -180,7 +180,7 @@ const CONFIG_FILE = "jskelet.config.mjs";
180
180
  * @property {string} cacheControl
181
181
  *
182
182
  * @typedef {object} BuildAssetsConfig
183
- * @property {string} prefix CDN origin (`JSKELET_ASSET_PREFIX`); sonunda `/` yok.
183
+ * @property {string} prefix CDN origin (`build.assets.prefix`); sonunda `/` yok.
184
184
  * @property {BuildAssetsUploadConfig} upload
185
185
  *
186
186
  * @typedef {object} BuildConfig
@@ -1186,7 +1186,34 @@ function defaultBuildClient() {
1186
1186
  }
1187
1187
 
1188
1188
  /**
1189
- * CDN prefix + S3 upload (`build.assets`). Credential config'e yazılmaz.
1189
+ * Asset upload bucket'ı. CDN URL buraya yazılmaz — o `build.assets.prefix`.
1190
+ *
1191
+ * @param {unknown} value
1192
+ * @returns {{ bucket: string | null, prefix: string | null }}
1193
+ */
1194
+ function normalizeAssetsBucket(value) {
1195
+ const text = typeof value === "string" ? value.trim() : "";
1196
+ if (!text) return { bucket: null, prefix: null };
1197
+
1198
+ // Noktalı bucket adları geçerli (`static.investvio.com`). Reddedilen yalnızca
1199
+ // şemalı URL — CDN origin `build.assets.prefix`'te kalır.
1200
+ if (/^https?:\/\//i.test(text)) {
1201
+ console.warn(
1202
+ "[config] build.assets.upload.bucket looks like a URL — use the S3 " +
1203
+ "bucket name without a scheme (e.g. \"static.investvio.com\" or " +
1204
+ "\"static.investvio.com/js\"); put https://… in build.assets.prefix",
1205
+ );
1206
+ return { bucket: null, prefix: null };
1207
+ }
1208
+
1209
+ return splitS3BucketPath(text);
1210
+ }
1211
+
1212
+ /**
1213
+ * CDN prefix + S3 upload (`build.assets`).
1214
+ *
1215
+ * Prefix / bucket / keyPrefix / region / endpoint **yalnızca config**'ten.
1216
+ * Credential env'den (`AWS_*`, yoksa `JSKELET_S3_*`) — config'e yazılmaz.
1190
1217
  *
1191
1218
  * @param {unknown} raw
1192
1219
  * @returns {BuildAssetsConfig}
@@ -1201,10 +1228,9 @@ export function normalizeBuildAssets(raw) {
1201
1228
  console.warn("[config] build.assets must be an object, ignoring it");
1202
1229
  }
1203
1230
 
1204
- const envPrefix = envText(process.env.JSKELET_ASSET_PREFIX);
1205
- const configPrefix =
1206
- typeof source.prefix === "string" ? source.prefix.trim() : "";
1207
- const prefix = (envPrefix ?? configPrefix).replace(/\/+$/, "");
1231
+ const prefix = (
1232
+ typeof source.prefix === "string" ? source.prefix.trim() : ""
1233
+ ).replace(/\/+$/, "");
1208
1234
 
1209
1235
  const uploadRaw =
1210
1236
  source.upload != null &&
@@ -1220,10 +1246,7 @@ export function normalizeBuildAssets(raw) {
1220
1246
  console.warn("[config] build.assets.upload must be an object, ignoring it");
1221
1247
  }
1222
1248
 
1223
- const envBucket = envText(process.env.JSKELET_ASSETS_BUCKET);
1224
- const bucketPath = splitS3BucketPath(
1225
- envBucket ?? envText(uploadRaw.bucket),
1226
- );
1249
+ const bucketPath = normalizeAssetsBucket(uploadRaw.bucket);
1227
1250
 
1228
1251
  const configKeyPrefix =
1229
1252
  typeof uploadRaw.keyPrefix === "string" && uploadRaw.keyPrefix
@@ -1236,17 +1259,15 @@ export function normalizeBuildAssets(raw) {
1236
1259
  : `${uploadRaw.prefix}/`
1237
1260
  : DEFAULT_BUILD.assets.upload.keyPrefix;
1238
1261
 
1239
- const endpoint =
1240
- envText(process.env.AWS_ENDPOINT_URL) ??
1241
- envText(process.env.JSKELET_S3_API_URL) ??
1242
- envText(uploadRaw.endpoint);
1262
+ // Bucket yolundaki önek (`my-bucket/js`) `keyPrefix`'i ezer — tek alanda
1263
+ // hem kova hem klasör vermek için.
1264
+ const keyPrefix = bucketPath.prefix ?? configKeyPrefix;
1265
+
1266
+ const endpoint = envText(uploadRaw.endpoint);
1243
1267
 
1244
1268
  // Gerçek AWS'te region imza için gerekir; custom endpoint'te `auto` yaygın.
1245
1269
  const region =
1246
1270
  envText(uploadRaw.region) ??
1247
- envText(process.env.AWS_REGION) ??
1248
- envText(process.env.AWS_DEFAULT_REGION) ??
1249
- envText(process.env.JSKELET_S3_REGION) ??
1250
1271
  (endpoint ? "auto" : DEFAULT_BUILD.assets.upload.region);
1251
1272
 
1252
1273
  /** @type {string[]} */
@@ -1268,8 +1289,7 @@ export function normalizeBuildAssets(raw) {
1268
1289
  ? uploadRaw.cacheControl.trim()
1269
1290
  : DEFAULT_BUILD.assets.upload.cacheControl;
1270
1291
 
1271
- // `JSKELET_ASSETS_BUCKET` doluysa config'te `enabled` unutulmuş olsa bile aç.
1272
- // Açık `enabled: false` ezer.
1292
+ // Config'te bucket varsa `enabled` unutulmuş olsa bile aç. Açık `false` ezer.
1273
1293
  const enabled =
1274
1294
  uploadRaw.enabled === true ||
1275
1295
  (uploadRaw.enabled !== false && Boolean(bucketPath.bucket));
@@ -1279,7 +1299,7 @@ export function normalizeBuildAssets(raw) {
1279
1299
  upload: {
1280
1300
  enabled,
1281
1301
  bucket: bucketPath.bucket,
1282
- keyPrefix: bucketPath.prefix ?? configKeyPrefix,
1302
+ keyPrefix,
1283
1303
  region,
1284
1304
  endpoint,
1285
1305
  include,
@@ -6,9 +6,9 @@
6
6
  * basmaz. Böylece `jskelet build` unutulduğunda hata yerine stilsiz ama
7
7
  * çalışan bir sayfa görürsünüz.
8
8
  *
9
- * `build.assets.prefix` / `JSKELET_ASSET_PREFIX` doluysa HTML'e basılan
10
- * URL'lere CDN origin eklenir; manifest diskte göreli kalır (çok ortamlı
11
- * tek artifact). Dosya sistemi okumaları (`getSpriteIds`) göreli yolu kullanır.
9
+ * `build.assets.prefix` doluysa HTML'e basılan URL'lere CDN origin eklenir;
10
+ * manifest diskte göreli kalır. Dosya sistemi okumaları (`getSpriteIds`)
11
+ * göreli yolu kullanır.
12
12
  */
13
13
  import fs from "node:fs";
14
14
  import path from "node:path";
@@ -448,10 +448,11 @@ export declare const DEFAULT_BUILD: {
448
448
  chunkGroup: ((id: string) => string | null | undefined) | null;
449
449
  };
450
450
  assets: {
451
- /** CDN / CloudFront origin; sonunda `/` yok. Env: `JSKELET_ASSET_PREFIX`. */
451
+ /** CDN / CloudFront origin; sonunda `/` yok. Yalnızca config. */
452
452
  prefix: string;
453
453
  upload: {
454
454
  enabled: boolean;
455
+ /** S3 bucket adı (CDN URL değil). Yalnızca config; credential env'den. */
455
456
  bucket: null;
456
457
  /** Nesne anahtarı öneki (`bucket/path` split'inden veya config). */
457
458
  keyPrefix: string;
@@ -241,7 +241,7 @@ export type BuildAssetsUploadConfig = {
241
241
  };
242
242
  export type BuildAssetsConfig = {
243
243
  /**
244
- * CDN origin (`JSKELET_ASSET_PREFIX`); sonunda `/` yok.
244
+ * CDN origin (`build.assets.prefix`); sonunda `/` yok.
245
245
  */
246
246
  prefix: string;
247
247
  upload: BuildAssetsUploadConfig;
@@ -313,7 +313,10 @@ export type SecurityConfig = {
313
313
  };
314
314
  };
315
315
  /**
316
- * CDN prefix + S3 upload (`build.assets`). Credential config'e yazılmaz.
316
+ * CDN prefix + S3 upload (`build.assets`).
317
+ *
318
+ * Prefix / bucket / keyPrefix / region / endpoint **yalnızca config**'ten.
319
+ * Credential env'den (`AWS_*`, yoksa `JSKELET_S3_*`) — config'e yazılmaz.
317
320
  *
318
321
  * @param {unknown} raw
319
322
  * @returns {BuildAssetsConfig}