jskelet 0.6.14 → 0.6.16
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 +10 -0
- package/docs/07-yapilandirma.md +3 -2
- package/docs/08-build.md +26 -6
- package/docs/en/07-configuration.md +3 -2
- package/docs/en/08-build.md +26 -6
- package/package.json +1 -1
- package/src/build/rewrite-css-urls.mjs +40 -0
- package/src/build/tasks/assets-upload.mjs +34 -1
- package/src/build/tasks/css.mjs +20 -2
- package/src/config/defaults.js +12 -2
- package/src/config/index.js +16 -0
- package/src/server/assets.js +24 -6
- package/src/server/logs/s3-put.js +148 -16
- package/src/views/helpers/tags.js +6 -1
- package/types/config/defaults.d.ts +11 -1
- package/types/config/index.d.ts +4 -0
- package/types/server/assets.d.ts +12 -1
- package/types/server/logs/s3-put.d.ts +34 -0
- package/types/views/helpers/tags.d.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,16 @@ one is listed under a **Breaking** heading.
|
|
|
17
17
|
|
|
18
18
|
### Added
|
|
19
19
|
|
|
20
|
+
- When `build.assets.prefix` is set, the CSS build rewrites root-absolute
|
|
21
|
+
`url()` paths under `static.prefixes` (e.g. `/fonts/…`) to the CDN
|
|
22
|
+
prefix so they match the S3 key (`…/js/fonts/…`). Default upload
|
|
23
|
+
`include` is `["assets", "fonts"]`; prefix also forces `fonts` into
|
|
24
|
+
`include`. Font uploads run PutBucketCors with
|
|
25
|
+
`upload.corsOrigin` (default `"*"`, set `null` to skip).
|
|
26
|
+
- `asset(name, { origin: "document" })` skips `build.assets.prefix` and
|
|
27
|
+
returns the site-origin `/assets/…` path. `icon()` always resolves the
|
|
28
|
+
sprite this way so SVG `<use href>` keeps working when other assets are
|
|
29
|
+
on a CDN (the origin still serves `public/assets/`).
|
|
20
30
|
- Dev overlay **API** tab (between Performance and Server): outbound
|
|
21
31
|
`fetch` calls for the current page — browser and SSR — with host
|
|
22
32
|
rollup, duration, status, and a detail pane on click (component,
|
package/docs/07-yapilandirma.md
CHANGED
|
@@ -629,13 +629,14 @@ değişmez** — asset'ler origin'den servis edilir.
|
|
|
629
629
|
|
|
630
630
|
| Alan | Tip | Varsayılan | Anlamı |
|
|
631
631
|
| --- | --- | --- | --- |
|
|
632
|
-
| `assets.prefix` | `string` | `""` | CDN origin; `asset()` URL öneki (sonunda `/` yok). Yalnızca config. Manifest diskte göreli kalır. |
|
|
632
|
+
| `assets.prefix` | `string` | `""` | CDN origin; `asset()` URL öneki (sonunda `/` yok). Yalnızca config. Manifest diskte göreli kalır. `asset(name, { origin: "document" })` ve `icon()` sprite'ı öneksiz kalır. |
|
|
633
633
|
| `assets.upload.enabled` | `boolean` | `false` | `true` veya config'te `bucket` doluysa açılır; açık `false` ezer. |
|
|
634
634
|
| `assets.upload.bucket` | `string \| null` | `null` | **S3 bucket adı** (`static.ornek.com` gibi noktalı adlar geçerli; PutObject path-style kullanır) veya `bucket/key/prefix`. `https://…` şeması kabul edilmez — o `prefix`. |
|
|
635
635
|
| `assets.upload.keyPrefix` | `string` | `""` | Nesne anahtarı öneki; bucket yolunda verilmişse o kazanır (`prefix` takma adı da kabul). |
|
|
636
636
|
| `assets.upload.region` | `string` | `us-east-1` | Config; custom `endpoint` varken varsayılan `auto`. |
|
|
637
637
|
| `assets.upload.endpoint` | `string \| null` | `null` | S3-uyumlu API (R2 / MinIO); config. |
|
|
638
|
-
| `assets.upload.include` | `string[]` | `["assets"]` | `public/` altı dizinler
|
|
638
|
+
| `assets.upload.include` | `string[]` | `["assets", "fonts"]` | `public/` altı dizinler. `assets.prefix` doluysa `fonts` yoksa eklenir. |
|
|
639
|
+
| `assets.upload.corsOrigin` | `string \| null` | `"*"` | Font upload'ta PutBucketCors `AllowedOrigin`; `null` kapatır (önceki CORS'un üzerine yazar). |
|
|
639
640
|
| `assets.upload.cacheControl` | `string` | `public, max-age=31536000, immutable` | PutObject `Cache-Control`. |
|
|
640
641
|
|
|
641
642
|
**Credential config'e yazılmaz.** Zincir: `AWS_ACCESS_KEY_ID` /
|
package/docs/08-build.md
CHANGED
|
@@ -80,6 +80,10 @@ dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
|
|
|
80
80
|
- `asset(name)` manifest'te varsa hash'li URL'i, yoksa `/assets/<name>` döner.
|
|
81
81
|
`build.assets.prefix` doluysa bu göreli yolun önüne CDN origin eklenir
|
|
82
82
|
(manifest diskte göreli kalır).
|
|
83
|
+
- `asset(name, { origin: "document" })` prefix'i atlar; site origin'indeki
|
|
84
|
+
`/assets/…` yolunu döner. SVG `<use href>` çapraz origin sprite
|
|
85
|
+
yüklemediği için `icon()` sprite'ı her zaman böyle çözer — origin hâlâ
|
|
86
|
+
`public/assets/` servis eder.
|
|
83
87
|
- `hasAsset(name)` manifest'te olup olmadığını söyler.
|
|
84
88
|
|
|
85
89
|
### CDN prefix ve S3 upload
|
|
@@ -87,11 +91,21 @@ dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
|
|
|
87
91
|
Varsayılan kapalıdır. Prefix / bucket / keyPrefix config'te; credential
|
|
88
92
|
env → ECS task role → EC2 IMDS zincirinden. Açılınca:
|
|
89
93
|
|
|
90
|
-
1. Production build `public/assets/`
|
|
91
|
-
altındaki dosyaları S3'e PutObject ile kopyalar.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
1. Production build `public/assets/` ve `public/fonts/` (varsayılan `include`)
|
|
95
|
+
altındaki dosyaları S3'e PutObject ile kopyalar. `assets.prefix` doluysa
|
|
96
|
+
`include`'da `fonts` yoksa otomatik eklenir.
|
|
97
|
+
2. CSS derlemesi `static.prefixes` kök-mutlak `url()` yollarını
|
|
98
|
+
(`/fonts/…`, `/assets/…`) `prefix` ile yeniden yazar — aksi hâlde CDN'deki
|
|
99
|
+
stylesheet `url("/fonts/x")` yolunu host köküne çözer ve `keyPrefix`
|
|
100
|
+
kaybolur. `@font-face` CDN CSS içinde kalabilir; font ile CSS aynı host'ta
|
|
101
|
+
olduğu için ek CORS gerekmez.
|
|
102
|
+
3. Font yüklendiğinde ve `upload.corsOrigin` doluysa (varsayılan `"*"`)
|
|
103
|
+
PutBucketCors bir GET/HEAD kuralı yazar — bucket'taki önceki CORS'un
|
|
104
|
+
**üzerine** yazar; kapatmak için `corsOrigin: null`. HTML'den çapraz
|
|
105
|
+
origin font için bu (veya CloudFront response header) gerekir.
|
|
106
|
+
4. Çalışma anında `asset()` / optimize görseller `build.assets.prefix` ile
|
|
107
|
+
absolute CDN URL basar. `icon()` sprite'ı ve
|
|
108
|
+
`asset(name, { origin: "document" })` site origin'inde kalır.
|
|
95
109
|
|
|
96
110
|
Bucket adı noktalı olabilir (`static.ornek.com`); `https://…` şeması
|
|
97
111
|
`prefix`'e yazılır. Config sözlüğü: [07-yapilandirma.md](./07-yapilandirma.md#build).
|
|
@@ -292,6 +306,10 @@ Kullanımı stylesheet'te elle yazılır:
|
|
|
292
306
|
`.woff2` uzantısı ve `/fonts/` öneki varsayılan `static` kurallarında olduğu için
|
|
293
307
|
bu dosyalara otomatik olarak `immutable` cache yazılır.
|
|
294
308
|
|
|
309
|
+
`build.assets.prefix` açıksa build, CSS'teki `url("/fonts/…")` yollarını CDN
|
|
310
|
+
prefix'iyle yeniden yazar ve fontları S3'e de basar — layout'ta ayrı
|
|
311
|
+
`@font-face` tutmaya gerek kalmaz.
|
|
312
|
+
|
|
295
313
|
## İkon sprite
|
|
296
314
|
|
|
297
315
|
**Yalnızca kaynakta kullanılan** ikonlar için bir `<symbol>` seti üretir. Tüm
|
|
@@ -457,7 +475,9 @@ karşılaşmaması.
|
|
|
457
475
|
- **Bazı Tailwind sınıfları çalışmıyor.** `@source` direktifi eksik olan bir
|
|
458
476
|
dizinde yazılmışlar.
|
|
459
477
|
- **İkon boş görünüyor.** Sprite'ta o sembol yok; dev'de `[icon] missing from sprite`
|
|
460
|
-
uyarısına bakın.
|
|
478
|
+
uyarısına bakın. CDN prefix açıkken sprite bilerek site origin'inde kalır
|
|
479
|
+
(`asset("sprite.svg", { origin: "document" })`); origin `public/assets/`
|
|
480
|
+
servis etmiyorsa `<use>` boş kalır.
|
|
461
481
|
- **Island'lar hiç açılmıyor.** `main.js` manifest'te yok (entry dizini boş ya
|
|
462
482
|
da build atlanmış) veya bir build hatası var.
|
|
463
483
|
- **Dev'de sayfa aniden stilsiz kaldı.** Manifest ile diskteki dosya
|
|
@@ -642,13 +642,14 @@ changes** — assets stay on the origin.
|
|
|
642
642
|
|
|
643
643
|
| Field | Type | Default | Meaning |
|
|
644
644
|
| --- | --- | --- | --- |
|
|
645
|
-
| `assets.prefix` | `string` | `""` | CDN origin; prefix for `asset()` URLs (no trailing `/`). Config only. The on-disk manifest stays relative. |
|
|
645
|
+
| `assets.prefix` | `string` | `""` | CDN origin; prefix for `asset()` URLs (no trailing `/`). Config only. The on-disk manifest stays relative. `asset(name, { origin: "document" })` and the `icon()` sprite stay unprefixed. |
|
|
646
646
|
| `assets.upload.enabled` | `boolean` | `false` | Turns on when `true` or when `bucket` is set in config; explicit `false` wins. |
|
|
647
647
|
| `assets.upload.bucket` | `string \| null` | `null` | **S3 bucket name** (dotted names like `static.example.com` are fine; PutObject uses path-style) or `bucket/key/prefix`. A `https://…` scheme is rejected — that belongs in `prefix`. |
|
|
648
648
|
| `assets.upload.keyPrefix` | `string` | `""` | Object key prefix; a path on the bucket wins (`prefix` is accepted as an alias). |
|
|
649
649
|
| `assets.upload.region` | `string` | `us-east-1` | Config; defaults to `auto` when `endpoint` is set. |
|
|
650
650
|
| `assets.upload.endpoint` | `string \| null` | `null` | S3-compatible API (R2 / MinIO); config. |
|
|
651
|
-
| `assets.upload.include` | `string[]` | `["assets"]` | Directories under `public
|
|
651
|
+
| `assets.upload.include` | `string[]` | `["assets", "fonts"]` | Directories under `public/`. When `assets.prefix` is set, `fonts` is added if missing. |
|
|
652
|
+
| `assets.upload.corsOrigin` | `string \| null` | `"*"` | PutBucketCors `AllowedOrigin` when fonts upload; `null` disables (replaces prior bucket CORS). |
|
|
652
653
|
| `assets.upload.cacheControl` | `string` | `public, max-age=31536000, immutable` | PutObject `Cache-Control`. |
|
|
653
654
|
|
|
654
655
|
**Credentials are not written in the config.** Chain: `AWS_ACCESS_KEY_ID` /
|
package/docs/en/08-build.md
CHANGED
|
@@ -83,6 +83,10 @@ They are passed to templates automatically; in server code,
|
|
|
83
83
|
- `asset(name)` returns the hashed URL if it is in the manifest, otherwise
|
|
84
84
|
`/assets/<name>`. When `build.assets.prefix` is set, that relative path is
|
|
85
85
|
prefixed with the CDN origin (the on-disk manifest stays relative).
|
|
86
|
+
- `asset(name, { origin: "document" })` skips the prefix and returns the
|
|
87
|
+
site-origin `/assets/…` path. SVG `<use href>` cannot load a cross-origin
|
|
88
|
+
sprite, so `icon()` always resolves the sprite this way — the origin still
|
|
89
|
+
serves `public/assets/`.
|
|
86
90
|
- `hasAsset(name)` tells you whether it is in the manifest.
|
|
87
91
|
|
|
88
92
|
### CDN prefix and S3 upload
|
|
@@ -90,11 +94,21 @@ They are passed to templates automatically; in server code,
|
|
|
90
94
|
Off by default. Prefix / bucket / keyPrefix come from config; credentials
|
|
91
95
|
resolve from env → ECS task role → EC2 IMDS. When enabled:
|
|
92
96
|
|
|
93
|
-
1. A production build PutObjects
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
1. A production build PutObjects `public/assets/` and `public/fonts/`
|
|
98
|
+
(default `include`) to S3. When `assets.prefix` is set, `fonts` is added
|
|
99
|
+
to `include` if missing.
|
|
100
|
+
2. The CSS step rewrites root-absolute `url()` paths under `static.prefixes`
|
|
101
|
+
(`/fonts/…`, `/assets/…`) with `prefix` — otherwise a stylesheet on the CDN
|
|
102
|
+
resolves `url("/fonts/x")` against the host root and drops `keyPrefix`.
|
|
103
|
+
`@font-face` can stay in the CDN CSS; font and CSS share a host, so no
|
|
104
|
+
extra CORS is needed for that path.
|
|
105
|
+
3. When fonts are uploaded and `upload.corsOrigin` is set (default `"*"`),
|
|
106
|
+
PutBucketCors writes a GET/HEAD rule — it **replaces** the bucket's
|
|
107
|
+
previous CORS config; set `corsOrigin: null` to skip. Cross-origin fonts
|
|
108
|
+
from HTML still need this (or a CloudFront response-header policy).
|
|
109
|
+
4. At runtime `asset()` / optimised images emit absolute CDN URLs via
|
|
110
|
+
`build.assets.prefix`. The `icon()` sprite and
|
|
111
|
+
`asset(name, { origin: "document" })` stay on the document origin.
|
|
98
112
|
|
|
99
113
|
Bucket names may include dots (`static.example.com`); put `https://…` in
|
|
100
114
|
`prefix`. Config reference: [07-configuration.md](./07-configuration.md#build).
|
|
@@ -300,6 +314,10 @@ Usage is written by hand in the stylesheet:
|
|
|
300
314
|
Because the `.woff2` extension and the `/fonts/` prefix are in the default
|
|
301
315
|
`static` rules, these files automatically get an `immutable` cache.
|
|
302
316
|
|
|
317
|
+
When `build.assets.prefix` is set, the build rewrites `url("/fonts/…")` in CSS
|
|
318
|
+
to the CDN prefix and uploads the font files too — you do not need a separate
|
|
319
|
+
`@font-face` block in the layout.
|
|
320
|
+
|
|
303
321
|
## Icon sprite
|
|
304
322
|
|
|
305
323
|
Produces a `<symbol>` set for **only the icons actually used in the source**.
|
|
@@ -473,7 +491,9 @@ directly does not end up facing an unstyled page.
|
|
|
473
491
|
- **Some Tailwind classes do not work.** They were written in a directory whose
|
|
474
492
|
`@source` directive is missing.
|
|
475
493
|
- **An icon looks empty.** That symbol is not in the sprite; in dev, look for the
|
|
476
|
-
`[icon] missing from sprite` warning.
|
|
494
|
+
`[icon] missing from sprite` warning. With a CDN prefix the sprite stays on
|
|
495
|
+
the document origin on purpose (`asset("sprite.svg", { origin: "document" })`);
|
|
496
|
+
if the origin does not serve `public/assets/`, `<use>` stays blank.
|
|
477
497
|
- **The islands never open.** `main.js` is not in the manifest (the entry
|
|
478
498
|
directory is empty or the build was skipped) or there is a build error.
|
|
479
499
|
- **The page suddenly went unstyled in dev.** The manifest and the file on disk
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jskelet",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.16",
|
|
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",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN prefix varken CSS `url(/fonts/…)` yollarını upload path'iyle hizalar.
|
|
3
|
+
*
|
|
4
|
+
* CDN'deki `app.css` kök-mutlak `/fonts/x.woff2` görünce tarayıcı bunu
|
|
5
|
+
* `https://cdn.example.com/fonts/x` diye çözer — `keyPrefix` (`/js` vb.)
|
|
6
|
+
* kaybolur ve 403 gelir. Prefix + aynı göreli yol (`…/js/fonts/x`) doğru
|
|
7
|
+
* nesneye gider; font ile CSS aynı host'ta olduğu için `@font-face` CORS
|
|
8
|
+
* istemez. HTML'den CDN font yüklemek ayrı: bucket/CloudFront CORS gerekir.
|
|
9
|
+
*/
|
|
10
|
+
import { applyAssetPrefix } from "../server/assets.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} css
|
|
14
|
+
* @param {{ prefix: string, prefixes?: string[] }} options
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function rewriteCssStaticUrls(css, { prefix, prefixes = [] }) {
|
|
18
|
+
if (!prefix || !css) return css;
|
|
19
|
+
|
|
20
|
+
const roots = [...prefixes]
|
|
21
|
+
.filter((entry) => typeof entry === "string" && entry.startsWith("/"))
|
|
22
|
+
.map((entry) => (entry.endsWith("/") ? entry : `${entry}/`))
|
|
23
|
+
.sort((a, b) => b.length - a.length);
|
|
24
|
+
|
|
25
|
+
if (!roots.length) return css;
|
|
26
|
+
|
|
27
|
+
return css.replace(
|
|
28
|
+
/url\(\s*(['"]?)(\/[^)"'\s]+)\1\s*\)/gi,
|
|
29
|
+
(match, quote, urlPath) => {
|
|
30
|
+
const pathOnly = urlPath.split(/[?#]/, 1)[0];
|
|
31
|
+
if (!roots.some((root) => pathOnly === root.slice(0, -1) || pathOnly.startsWith(root))) {
|
|
32
|
+
return match;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const rewritten = applyAssetPrefix(prefix, urlPath);
|
|
36
|
+
const q = quote || '"';
|
|
37
|
+
return `url(${q}${rewritten}${q})`;
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -16,12 +16,22 @@ import {
|
|
|
16
16
|
resolveS3Credentials,
|
|
17
17
|
s3CredentialsMissingReason,
|
|
18
18
|
} from "../../server/logs/s3-credentials.js";
|
|
19
|
-
import { putObject } from "../../server/logs/s3-put.js";
|
|
19
|
+
import { putBucketCors, putObject } from "../../server/logs/s3-put.js";
|
|
20
20
|
import * as log from "../../log.mjs";
|
|
21
21
|
import { paths } from "../paths.mjs";
|
|
22
22
|
|
|
23
23
|
const CONCURRENCY = 8;
|
|
24
24
|
|
|
25
|
+
/** Font MIME — CDN'den HTML çapraz origin yüklerken CORS gerekir. */
|
|
26
|
+
const FONT_MIME = new Set([
|
|
27
|
+
"font/woff",
|
|
28
|
+
"font/woff2",
|
|
29
|
+
"font/ttf",
|
|
30
|
+
"font/otf",
|
|
31
|
+
"application/font-woff",
|
|
32
|
+
"application/font-woff2",
|
|
33
|
+
]);
|
|
34
|
+
|
|
25
35
|
/** @type {Record<string, string>} */
|
|
26
36
|
const MIME = {
|
|
27
37
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -116,6 +126,7 @@ export function assetsUploadSkipReason(config) {
|
|
|
116
126
|
/**
|
|
117
127
|
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
118
128
|
* @param {{ put?: typeof putObject,
|
|
129
|
+
* putCors?: typeof putBucketCors,
|
|
119
130
|
* resolveCredentials?: typeof resolveS3Credentials }} [options]
|
|
120
131
|
* @returns {Promise<void>}
|
|
121
132
|
*/
|
|
@@ -135,6 +146,7 @@ export async function uploadAssets(config, options = {}) {
|
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
const put = options.put ?? putObject;
|
|
149
|
+
const putCors = options.putCors ?? putBucketCors;
|
|
138
150
|
// Normalize bazen atlanır (test / elle config); önek `/` ile bitsin.
|
|
139
151
|
let keyPrefix = (upload.keyPrefix || "").replace(/^\/+/, "");
|
|
140
152
|
if (keyPrefix && !keyPrefix.endsWith("/")) keyPrefix += "/";
|
|
@@ -162,6 +174,27 @@ export async function uploadAssets(config, options = {}) {
|
|
|
162
174
|
return;
|
|
163
175
|
}
|
|
164
176
|
|
|
177
|
+
const uploadsFonts = jobs.some((job) =>
|
|
178
|
+
FONT_MIME.has(contentTypeFor(job.file)),
|
|
179
|
+
);
|
|
180
|
+
if (uploadsFonts && upload.corsOrigin) {
|
|
181
|
+
try {
|
|
182
|
+
await putCors({
|
|
183
|
+
bucket: upload.bucket,
|
|
184
|
+
region: upload.region,
|
|
185
|
+
endpoint: upload.endpoint,
|
|
186
|
+
credentials,
|
|
187
|
+
allowedOrigin: upload.corsOrigin,
|
|
188
|
+
});
|
|
189
|
+
log.detail(`cors ${upload.corsOrigin}`);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
192
|
+
console.warn(
|
|
193
|
+
`[assets] PutBucketCors failed (${message}) — set CDN/bucket CORS manually for cross-origin fonts`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
165
198
|
let uploaded = 0;
|
|
166
199
|
let index = 0;
|
|
167
200
|
|
package/src/build/tasks/css.mjs
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import fs from "node:fs";
|
|
25
25
|
import path from "node:path";
|
|
26
26
|
import { paths, writeAsset, writeManifest } from "../paths.mjs";
|
|
27
|
+
import { rewriteCssStaticUrls } from "../rewrite-css-urls.mjs";
|
|
27
28
|
import { importFromApp, tryImportFromApp } from "../resolve-peer.mjs";
|
|
28
29
|
import * as log from "../../log.mjs";
|
|
29
30
|
|
|
@@ -85,6 +86,23 @@ function listPageStyles(dir) {
|
|
|
85
86
|
.map((name) => path.join(dir, name));
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* `build.assets.prefix` doluysa `static.prefixes` kök-mutlak `url()` yollarını
|
|
91
|
+
* CDN'e çevirir (`/fonts/…` → `https://cdn…/js/fonts/…`).
|
|
92
|
+
*
|
|
93
|
+
* @param {string} css
|
|
94
|
+
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
function applyCdnStaticUrls(css, config) {
|
|
98
|
+
const prefix = config.build?.assets?.prefix ?? "";
|
|
99
|
+
if (!prefix) return css;
|
|
100
|
+
return rewriteCssStaticUrls(css, {
|
|
101
|
+
prefix,
|
|
102
|
+
prefixes: config.static?.prefixes ?? ["/assets/", "/fonts/"],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
/**
|
|
89
107
|
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
90
108
|
* @param {{ watch?: boolean }} [options]
|
|
@@ -101,7 +119,7 @@ export async function buildCss(config, { watch = false } = {}) {
|
|
|
101
119
|
const urls = {};
|
|
102
120
|
let bytes = 0;
|
|
103
121
|
|
|
104
|
-
const appCss = await compile(input);
|
|
122
|
+
const appCss = applyCdnStaticUrls(await compile(input), config);
|
|
105
123
|
const appUrl = writeAsset("app.css", appCss);
|
|
106
124
|
urls["app.css"] = appUrl;
|
|
107
125
|
bytes += Buffer.byteLength(appCss);
|
|
@@ -111,7 +129,7 @@ export async function buildCss(config, { watch = false } = {}) {
|
|
|
111
129
|
|
|
112
130
|
for (const file of listPageStyles(pagesDir)) {
|
|
113
131
|
const name = path.basename(file);
|
|
114
|
-
const css = await compile(file);
|
|
132
|
+
const css = applyCdnStaticUrls(await compile(file), config);
|
|
115
133
|
const url = writeAsset(name, css);
|
|
116
134
|
urls[name] = url;
|
|
117
135
|
bytes += Buffer.byteLength(css);
|
package/src/config/defaults.js
CHANGED
|
@@ -508,9 +508,19 @@ export const DEFAULT_BUILD = {
|
|
|
508
508
|
keyPrefix: "",
|
|
509
509
|
region: "us-east-1",
|
|
510
510
|
endpoint: null,
|
|
511
|
-
/**
|
|
512
|
-
|
|
511
|
+
/**
|
|
512
|
+
* `public/` altı dizin adları. `fonts` varsayılan: CDN CSS'teki
|
|
513
|
+
* `/fonts/…` rewrite'ı upload nesnesiyle hizalansın.
|
|
514
|
+
*/
|
|
515
|
+
include: /** @type {string[]} */ (["assets", "fonts"]),
|
|
513
516
|
cacheControl: "public, max-age=31536000, immutable",
|
|
517
|
+
/**
|
|
518
|
+
* Upload sırasında PutBucketCors ile yazılan `AllowedOrigin`.
|
|
519
|
+
* `null` / `false` → CORS adımı atlanır. Bucket'taki önceki CORS
|
|
520
|
+
* kuralının üzerine yazar — paylaşımlı kovada bilinçli açın.
|
|
521
|
+
* CDN CSS + aynı host font için genelde gerekmez.
|
|
522
|
+
*/
|
|
523
|
+
corsOrigin: /** @type {string | null} */ ("*"),
|
|
514
524
|
},
|
|
515
525
|
},
|
|
516
526
|
};
|
package/src/config/index.js
CHANGED
|
@@ -184,6 +184,7 @@ const CONFIG_FILE = "jskelet.config.mjs";
|
|
|
184
184
|
* @property {string | null} endpoint
|
|
185
185
|
* @property {string[]} include
|
|
186
186
|
* @property {string} cacheControl
|
|
187
|
+
* @property {string | null} corsOrigin Font/CDN PutBucketCors `AllowedOrigin`; `null` kapatır.
|
|
187
188
|
*
|
|
188
189
|
* @typedef {object} BuildAssetsConfig
|
|
189
190
|
* @property {string} prefix CDN origin (`build.assets.prefix`); sonunda `/` yok.
|
|
@@ -1264,11 +1265,25 @@ export function normalizeBuildAssets(raw) {
|
|
|
1264
1265
|
}
|
|
1265
1266
|
}
|
|
1266
1267
|
|
|
1268
|
+
// CDN CSS `/fonts/…` → prefix'li URL yazar; fontlar upload'ta yoksa 403.
|
|
1269
|
+
// Kullanıcı `include: ["assets"]` yazsa bile prefix varken `fonts` eklenir.
|
|
1270
|
+
if (prefix && !include.includes("fonts")) {
|
|
1271
|
+
include = [...include, "fonts"];
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1267
1274
|
const cacheControl =
|
|
1268
1275
|
typeof uploadRaw.cacheControl === "string" && uploadRaw.cacheControl.trim()
|
|
1269
1276
|
? uploadRaw.cacheControl.trim()
|
|
1270
1277
|
: DEFAULT_BUILD.assets.upload.cacheControl;
|
|
1271
1278
|
|
|
1279
|
+
/** @type {string | null} */
|
|
1280
|
+
let corsOrigin = DEFAULT_BUILD.assets.upload.corsOrigin;
|
|
1281
|
+
if (uploadRaw.corsOrigin === null || uploadRaw.corsOrigin === false) {
|
|
1282
|
+
corsOrigin = null;
|
|
1283
|
+
} else if (typeof uploadRaw.corsOrigin === "string") {
|
|
1284
|
+
corsOrigin = uploadRaw.corsOrigin.trim() || null;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1272
1287
|
// Config'te bucket varsa `enabled` unutulmuş olsa bile aç. Açık `false` ezer.
|
|
1273
1288
|
const enabled =
|
|
1274
1289
|
uploadRaw.enabled === true ||
|
|
@@ -1284,6 +1299,7 @@ export function normalizeBuildAssets(raw) {
|
|
|
1284
1299
|
endpoint,
|
|
1285
1300
|
include,
|
|
1286
1301
|
cacheControl,
|
|
1302
|
+
corsOrigin,
|
|
1287
1303
|
},
|
|
1288
1304
|
};
|
|
1289
1305
|
}
|
package/src/server/assets.js
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* `build.assets.prefix` doluysa HTML'e basılan URL'lere CDN origin eklenir;
|
|
10
10
|
* manifest diskte göreli kalır. Dosya sistemi okumaları (`getSpriteIds`)
|
|
11
11
|
* göreli yolu kullanır.
|
|
12
|
+
*
|
|
13
|
+
* Bazı varlıklar bilinçli olarak site origin'inde kalır: SVG `<use href>`
|
|
14
|
+
* çapraz origin sprite yüklemez. `asset(name, { origin: "document" })`
|
|
15
|
+
* prefix'i atlar; `icon()` sprite için bunu kullanır. Origin hâlâ
|
|
16
|
+
* `public/assets/` servis eder.
|
|
12
17
|
*/
|
|
13
18
|
import fs from "node:fs";
|
|
14
19
|
import path from "node:path";
|
|
@@ -17,7 +22,7 @@ import { getConfig } from "../config/index.js";
|
|
|
17
22
|
|
|
18
23
|
const isDev = process.env.NODE_ENV === "development";
|
|
19
24
|
|
|
20
|
-
/** @type {{ manifest: Record<string, string
|
|
25
|
+
/** @type {{ manifest: Record<string, string>, generated: string } | null} */
|
|
21
26
|
let cached = null;
|
|
22
27
|
let warned = false;
|
|
23
28
|
|
|
@@ -38,13 +43,15 @@ function load() {
|
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
return { manifest };
|
|
46
|
+
return { manifest, generated };
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
function state() {
|
|
45
50
|
// Dev'de her istekte yeniden okunur (watch build hash'leri değiştirir);
|
|
46
|
-
// prod'da bir kez.
|
|
47
|
-
|
|
51
|
+
// prod'da bir kez. `generated` yolu değiştiyse (test / başka kök) de
|
|
52
|
+
// yenilenir — aksi hâlde eski manifest takılı kalır.
|
|
53
|
+
const generated = getConfig().dirs.generated;
|
|
54
|
+
if (isDev || !cached || cached.generated !== generated) cached = load();
|
|
48
55
|
return cached;
|
|
49
56
|
}
|
|
50
57
|
|
|
@@ -82,11 +89,22 @@ function assetPath(name) {
|
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
/**
|
|
92
|
+
* @typedef {{ origin?: "cdn" | "document" }} AssetOptions
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Manifest'teki hash'li (veya yedek) URL.
|
|
97
|
+
*
|
|
85
98
|
* @param {string} name Örn. "app.css", "main.js", "sprite.svg"
|
|
99
|
+
* @param {AssetOptions} [options]
|
|
100
|
+
* `origin: "document"` → `build.assets.prefix` uygulanmaz; site
|
|
101
|
+
* origin'indeki `/assets/…` yolu döner (varsayılan `"cdn"`).
|
|
86
102
|
* @returns {string}
|
|
87
103
|
*/
|
|
88
|
-
export function asset(name) {
|
|
89
|
-
|
|
104
|
+
export function asset(name, options = {}) {
|
|
105
|
+
const relative = assetPath(name);
|
|
106
|
+
if (options.origin === "document") return relative;
|
|
107
|
+
return applyAssetPrefix(assetPrefix(), relative);
|
|
90
108
|
}
|
|
91
109
|
|
|
92
110
|
/**
|
|
@@ -14,6 +14,9 @@ import crypto from "node:crypto";
|
|
|
14
14
|
* @typedef {{ bucket: string, key: string, body: string | Buffer,
|
|
15
15
|
* region: string, endpoint?: string | null, credentials: AwsCredentials,
|
|
16
16
|
* contentType?: string, cacheControl?: string | null, now?: Date }} PutObjectInput
|
|
17
|
+
*
|
|
18
|
+
* @typedef {{ bucket: string, region: string, endpoint?: string | null,
|
|
19
|
+
* credentials: AwsCredentials, allowedOrigin: string, now?: Date }} PutBucketCorsInput
|
|
17
20
|
*/
|
|
18
21
|
|
|
19
22
|
/**
|
|
@@ -131,6 +134,24 @@ export function useS3PathStyle(bucket, endpoint) {
|
|
|
131
134
|
return bucket.includes(".");
|
|
132
135
|
}
|
|
133
136
|
|
|
137
|
+
/**
|
|
138
|
+
* @param {{ bucket: string, region: string, endpoint?: string | null }} input
|
|
139
|
+
* @returns {{ host: string, base: string, pathStyle: boolean }}
|
|
140
|
+
*/
|
|
141
|
+
function s3EndpointParts(input) {
|
|
142
|
+
const pathStyle = useS3PathStyle(input.bucket, input.endpoint);
|
|
143
|
+
if (input.endpoint) {
|
|
144
|
+
const base = String(input.endpoint).replace(/\/+$/, "");
|
|
145
|
+
return { host: new URL(input.endpoint).host, base, pathStyle: true };
|
|
146
|
+
}
|
|
147
|
+
if (pathStyle) {
|
|
148
|
+
const host = `s3.${input.region}.amazonaws.com`;
|
|
149
|
+
return { host, base: `https://${host}`, pathStyle: true };
|
|
150
|
+
}
|
|
151
|
+
const host = `${input.bucket}.s3.${input.region}.amazonaws.com`;
|
|
152
|
+
return { host, base: `https://${host}`, pathStyle: false };
|
|
153
|
+
}
|
|
154
|
+
|
|
134
155
|
/**
|
|
135
156
|
* PutObject isteğini imzalar; ağ çağrısı yapmaz — test edilebilir olsun diye.
|
|
136
157
|
*
|
|
@@ -145,22 +166,7 @@ export function buildSignedPutObject(input) {
|
|
|
145
166
|
const payloadHash = sha256Hex(body);
|
|
146
167
|
|
|
147
168
|
const keyPath = encodeS3Path(input.key.replace(/^\/+/, ""));
|
|
148
|
-
const pathStyle =
|
|
149
|
-
|
|
150
|
-
/** @type {string} */
|
|
151
|
-
let host;
|
|
152
|
-
/** @type {string} */
|
|
153
|
-
let base;
|
|
154
|
-
if (input.endpoint) {
|
|
155
|
-
base = String(input.endpoint).replace(/\/+$/, "");
|
|
156
|
-
host = new URL(input.endpoint).host;
|
|
157
|
-
} else if (pathStyle) {
|
|
158
|
-
host = `s3.${input.region}.amazonaws.com`;
|
|
159
|
-
base = `https://${host}`;
|
|
160
|
-
} else {
|
|
161
|
-
host = `${input.bucket}.s3.${input.region}.amazonaws.com`;
|
|
162
|
-
base = `https://${host}`;
|
|
163
|
-
}
|
|
169
|
+
const { host, base, pathStyle } = s3EndpointParts(input);
|
|
164
170
|
|
|
165
171
|
const canonicalUri = pathStyle
|
|
166
172
|
? `/${encodeS3Path(input.bucket)}/${keyPath}`
|
|
@@ -219,6 +225,132 @@ export function buildSignedPutObject(input) {
|
|
|
219
225
|
return { url, headers, body };
|
|
220
226
|
}
|
|
221
227
|
|
|
228
|
+
/**
|
|
229
|
+
* PutBucketCors gövdesi — tek kural, GET/HEAD.
|
|
230
|
+
*
|
|
231
|
+
* @param {string} allowedOrigin
|
|
232
|
+
* @returns {string}
|
|
233
|
+
*/
|
|
234
|
+
export function corsConfigurationXml(allowedOrigin) {
|
|
235
|
+
const origin = String(allowedOrigin)
|
|
236
|
+
.replace(/&/g, "&")
|
|
237
|
+
.replace(/</g, "<")
|
|
238
|
+
.replace(/>/g, ">")
|
|
239
|
+
.replace(/"/g, """);
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
`<?xml version="1.0" encoding="UTF-8"?>` +
|
|
243
|
+
`<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">` +
|
|
244
|
+
`<CORSRule>` +
|
|
245
|
+
`<AllowedOrigin>${origin}</AllowedOrigin>` +
|
|
246
|
+
`<AllowedMethod>GET</AllowedMethod>` +
|
|
247
|
+
`<AllowedMethod>HEAD</AllowedMethod>` +
|
|
248
|
+
`<AllowedHeader>*</AllowedHeader>` +
|
|
249
|
+
`<ExposeHeader>ETag</ExposeHeader>` +
|
|
250
|
+
`<ExposeHeader>Content-Type</ExposeHeader>` +
|
|
251
|
+
`<MaxAgeSeconds>86400</MaxAgeSeconds>` +
|
|
252
|
+
`</CORSRule>` +
|
|
253
|
+
`</CORSConfiguration>`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* PutBucketCors isteğini imzalar (`PUT /?cors` veya path-style `/bucket?cors`).
|
|
259
|
+
*
|
|
260
|
+
* @param {PutBucketCorsInput} input
|
|
261
|
+
* @returns {{ url: string, headers: Record<string, string>, body: Buffer }}
|
|
262
|
+
*/
|
|
263
|
+
export function buildSignedPutBucketCors(input) {
|
|
264
|
+
const body = Buffer.from(corsConfigurationXml(input.allowedOrigin), "utf8");
|
|
265
|
+
const now = input.now ?? new Date();
|
|
266
|
+
const { amzDate, dateStamp } = formatAmzDates(now);
|
|
267
|
+
const payloadHash = sha256Hex(body);
|
|
268
|
+
const { host, base, pathStyle } = s3EndpointParts(input);
|
|
269
|
+
|
|
270
|
+
// AWS: cors alt kaynağının canonical query'si `cors=` (boş değer).
|
|
271
|
+
const canonicalQuerystring = "cors=";
|
|
272
|
+
const canonicalUri = pathStyle ? `/${encodeS3Path(input.bucket)}` : "/";
|
|
273
|
+
|
|
274
|
+
/** @type {Record<string, string>} */
|
|
275
|
+
const headers = {
|
|
276
|
+
host,
|
|
277
|
+
"content-type": "application/xml",
|
|
278
|
+
"content-length": String(body.length),
|
|
279
|
+
"x-amz-content-sha256": payloadHash,
|
|
280
|
+
"x-amz-date": amzDate,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (input.credentials.sessionToken) {
|
|
284
|
+
headers["x-amz-security-token"] = input.credentials.sessionToken;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const { canonicalRequest, signedHeaders } = buildCanonicalRequest({
|
|
288
|
+
method: "PUT",
|
|
289
|
+
canonicalUri,
|
|
290
|
+
canonicalQuerystring,
|
|
291
|
+
headers,
|
|
292
|
+
payloadHash,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const stringToSign = buildStringToSign({
|
|
296
|
+
amzDate,
|
|
297
|
+
dateStamp,
|
|
298
|
+
region: input.region,
|
|
299
|
+
service: "s3",
|
|
300
|
+
canonicalRequest,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const signingKey = deriveSigningKey(
|
|
304
|
+
input.credentials.secretAccessKey,
|
|
305
|
+
dateStamp,
|
|
306
|
+
input.region,
|
|
307
|
+
"s3",
|
|
308
|
+
);
|
|
309
|
+
const signature = hmac("sha256", signingKey, stringToSign).toString("hex");
|
|
310
|
+
const credentialScope = `${dateStamp}/${input.region}/s3/aws4_request`;
|
|
311
|
+
|
|
312
|
+
headers.authorization =
|
|
313
|
+
`AWS4-HMAC-SHA256 Credential=${input.credentials.accessKeyId}/${credentialScope}, ` +
|
|
314
|
+
`SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
315
|
+
|
|
316
|
+
const url = pathStyle
|
|
317
|
+
? `${base}/${encodeS3Path(input.bucket)}?cors`
|
|
318
|
+
: `${base}/?cors`;
|
|
319
|
+
|
|
320
|
+
return { url, headers, body };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Bucket CORS yazar. Mevcut kuralların **üzerine** yazar; paylaşımlı
|
|
325
|
+
* kovada bilinçli kullanılmalı. İzin yoksa çağıran uyarır, upload sürer.
|
|
326
|
+
*
|
|
327
|
+
* @param {PutBucketCorsInput} input
|
|
328
|
+
* @returns {Promise<void>}
|
|
329
|
+
*/
|
|
330
|
+
export async function putBucketCors(input) {
|
|
331
|
+
const signed = buildSignedPutBucketCors(input);
|
|
332
|
+
/** @type {Response} */
|
|
333
|
+
let response;
|
|
334
|
+
try {
|
|
335
|
+
response = await fetch(signed.url, {
|
|
336
|
+
method: "PUT",
|
|
337
|
+
headers: signed.headers,
|
|
338
|
+
body: signed.body,
|
|
339
|
+
});
|
|
340
|
+
} catch (error) {
|
|
341
|
+
const detail =
|
|
342
|
+
error instanceof Error ? error.message : String(error);
|
|
343
|
+
throw new Error(`S3 PutBucketCors fetch failed (${signed.url}): ${detail}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!response.ok) {
|
|
347
|
+
const text = await response.text().catch(() => "");
|
|
348
|
+
throw new Error(
|
|
349
|
+
`S3 PutBucketCors ${response.status}: ${text.slice(0, 200) || response.statusText}`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
222
354
|
/**
|
|
223
355
|
* @param {PutObjectInput} input
|
|
224
356
|
* @returns {Promise<void>}
|
|
@@ -195,6 +195,11 @@ export function preloadImage({ href, imagesrcset, imagesizes }) {
|
|
|
195
195
|
/**
|
|
196
196
|
* Phosphor ikonları — build zamanı üretilen SVG sprite'tan `<use>`.
|
|
197
197
|
* `weight` sprite id'sine dahildir (regular/bold/fill).
|
|
198
|
+
*
|
|
199
|
+
* Sprite URL'si her zaman site origin'indedir (`asset(..., { origin:
|
|
200
|
+
* "document" })`). `<use href>` çapraz origin SVG yüklemez; CDN prefix
|
|
201
|
+
* açıkken bile `public/assets/sprite.<hash>.svg` aynı host'tan gelir.
|
|
202
|
+
*
|
|
198
203
|
* @param {{ name: string, size?: number | string, weight?: string,
|
|
199
204
|
* class?: string, attrs?: Record<string, unknown> }} props
|
|
200
205
|
* @returns {string}
|
|
@@ -208,7 +213,7 @@ export function icon(props) {
|
|
|
208
213
|
attrs: extra = {},
|
|
209
214
|
} = props;
|
|
210
215
|
|
|
211
|
-
const spritePath = asset("sprite.svg");
|
|
216
|
+
const spritePath = asset("sprite.svg", { origin: "document" });
|
|
212
217
|
const id = `${toKebab(name)}-${weight}`;
|
|
213
218
|
|
|
214
219
|
if (isDev && !warnedIcons.has(id)) {
|
|
@@ -458,9 +458,19 @@ export declare const DEFAULT_BUILD: {
|
|
|
458
458
|
keyPrefix: string;
|
|
459
459
|
region: string;
|
|
460
460
|
endpoint: null;
|
|
461
|
-
/**
|
|
461
|
+
/**
|
|
462
|
+
* `public/` altı dizin adları. `fonts` varsayılan: CDN CSS'teki
|
|
463
|
+
* `/fonts/…` rewrite'ı upload nesnesiyle hizalansın.
|
|
464
|
+
*/
|
|
462
465
|
include: string[];
|
|
463
466
|
cacheControl: string;
|
|
467
|
+
/**
|
|
468
|
+
* Upload sırasında PutBucketCors ile yazılan `AllowedOrigin`.
|
|
469
|
+
* `null` / `false` → CORS adımı atlanır. Bucket'taki önceki CORS
|
|
470
|
+
* kuralının üzerine yazar — paylaşımlı kovada bilinçli açın.
|
|
471
|
+
* CDN CSS + aynı host font için genelde gerekmez.
|
|
472
|
+
*/
|
|
473
|
+
corsOrigin: string | null;
|
|
464
474
|
};
|
|
465
475
|
};
|
|
466
476
|
};
|
package/types/config/index.d.ts
CHANGED
|
@@ -240,6 +240,10 @@ export type BuildAssetsUploadConfig = {
|
|
|
240
240
|
endpoint: string | null;
|
|
241
241
|
include: string[];
|
|
242
242
|
cacheControl: string;
|
|
243
|
+
/**
|
|
244
|
+
* Font/CDN PutBucketCors `AllowedOrigin`; `null` kapatır.
|
|
245
|
+
*/
|
|
246
|
+
corsOrigin: string | null;
|
|
243
247
|
};
|
|
244
248
|
export type BuildAssetsConfig = {
|
|
245
249
|
/**
|
package/types/server/assets.d.ts
CHANGED
|
@@ -6,11 +6,22 @@
|
|
|
6
6
|
* @returns {string}
|
|
7
7
|
*/
|
|
8
8
|
export declare function applyAssetPrefix(prefix: string, url: string): string;
|
|
9
|
+
export type AssetOptions = {
|
|
10
|
+
origin?: "cdn" | "document";
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {{ origin?: "cdn" | "document" }} AssetOptions
|
|
14
|
+
*/
|
|
9
15
|
/**
|
|
16
|
+
* Manifest'teki hash'li (veya yedek) URL.
|
|
17
|
+
*
|
|
10
18
|
* @param {string} name Örn. "app.css", "main.js", "sprite.svg"
|
|
19
|
+
* @param {AssetOptions} [options]
|
|
20
|
+
* `origin: "document"` → `build.assets.prefix` uygulanmaz; site
|
|
21
|
+
* origin'indeki `/assets/…` yolu döner (varsayılan `"cdn"`).
|
|
11
22
|
* @returns {string}
|
|
12
23
|
*/
|
|
13
|
-
export declare function asset(name: string): string;
|
|
24
|
+
export declare function asset(name: string, options?: AssetOptions): string;
|
|
14
25
|
/**
|
|
15
26
|
* @param {string} name
|
|
16
27
|
* @returns {boolean}
|
|
@@ -14,6 +14,14 @@ export type PutObjectInput = {
|
|
|
14
14
|
cacheControl?: string | null;
|
|
15
15
|
now?: Date;
|
|
16
16
|
};
|
|
17
|
+
export type PutBucketCorsInput = {
|
|
18
|
+
bucket: string;
|
|
19
|
+
region: string;
|
|
20
|
+
endpoint?: string | null;
|
|
21
|
+
credentials: AwsCredentials;
|
|
22
|
+
allowedOrigin: string;
|
|
23
|
+
now?: Date;
|
|
24
|
+
};
|
|
17
25
|
/**
|
|
18
26
|
* @param {string | Buffer} data
|
|
19
27
|
* @returns {string}
|
|
@@ -91,6 +99,32 @@ export declare function buildSignedPutObject(input: PutObjectInput): {
|
|
|
91
99
|
headers: Record<string, string>;
|
|
92
100
|
body: Buffer;
|
|
93
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* PutBucketCors gövdesi — tek kural, GET/HEAD.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} allowedOrigin
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
export declare function corsConfigurationXml(allowedOrigin: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* PutBucketCors isteğini imzalar (`PUT /?cors` veya path-style `/bucket?cors`).
|
|
111
|
+
*
|
|
112
|
+
* @param {PutBucketCorsInput} input
|
|
113
|
+
* @returns {{ url: string, headers: Record<string, string>, body: Buffer }}
|
|
114
|
+
*/
|
|
115
|
+
export declare function buildSignedPutBucketCors(input: PutBucketCorsInput): {
|
|
116
|
+
url: string;
|
|
117
|
+
headers: Record<string, string>;
|
|
118
|
+
body: Buffer;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Bucket CORS yazar. Mevcut kuralların **üzerine** yazar; paylaşımlı
|
|
122
|
+
* kovada bilinçli kullanılmalı. İzin yoksa çağıran uyarır, upload sürer.
|
|
123
|
+
*
|
|
124
|
+
* @param {PutBucketCorsInput} input
|
|
125
|
+
* @returns {Promise<void>}
|
|
126
|
+
*/
|
|
127
|
+
export declare function putBucketCors(input: PutBucketCorsInput): Promise<void>;
|
|
94
128
|
/**
|
|
95
129
|
* @param {PutObjectInput} input
|
|
96
130
|
* @returns {Promise<void>}
|
|
@@ -60,6 +60,11 @@ export declare function preloadImage({ href, imagesrcset, imagesizes }: {
|
|
|
60
60
|
/**
|
|
61
61
|
* Phosphor ikonları — build zamanı üretilen SVG sprite'tan `<use>`.
|
|
62
62
|
* `weight` sprite id'sine dahildir (regular/bold/fill).
|
|
63
|
+
*
|
|
64
|
+
* Sprite URL'si her zaman site origin'indedir (`asset(..., { origin:
|
|
65
|
+
* "document" })`). `<use href>` çapraz origin SVG yüklemez; CDN prefix
|
|
66
|
+
* açıkken bile `public/assets/sprite.<hash>.svg` aynı host'tan gelir.
|
|
67
|
+
*
|
|
63
68
|
* @param {{ name: string, size?: number | string, weight?: string,
|
|
64
69
|
* class?: string, attrs?: Record<string, unknown> }} props
|
|
65
70
|
* @returns {string}
|