jskelet 0.6.10 → 0.6.11

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
@@ -16,8 +16,9 @@ one is listed under a **Breaking** heading.
16
16
  Production `jskelet build` PutObjects `public/assets/` (and optional
17
17
  `include` dirs) after precompress. Only credentials are read from env
18
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)`,
19
+ `JSKELET_S3_*`, then ECS task-role / EC2 instance-profile metadata). The
20
+ Assets upload step always appears on production builds; when off it logs a
21
+ one-line reason (`skipped (no build.assets.upload.bucket)`,
21
22
  `skipped (credentials missing)`, …). A URL in `upload.bucket` is rejected
22
23
  with a warning (CDN origin belongs in `prefix`). Watch mode never uploads.
23
24
  `.br` / `.gz` siblings are skipped.
@@ -638,13 +638,13 @@ değişmez** — asset'ler origin'den servis edilir.
638
638
  | `assets.upload.include` | `string[]` | `["assets"]` | `public/` altı dizinler (`assets`, isteğe `fonts`, …). |
639
639
  | `assets.upload.cacheControl` | `string` | `public, max-age=31536000, immutable` | PutObject `Cache-Control`. |
640
640
 
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),
647
- [10-dagitim.md](./10-dagitim.md).
641
+ **Credential config'e yazılmaz.** Zincir: `AWS_ACCESS_KEY_ID` /
642
+ `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` (yoksa `JSKELET_S3_*`) → ECS/Fargate
643
+ task role (`AWS_CONTAINER_CREDENTIALS_*`) → EC2 instance profile (IMDSv2).
644
+ Prefix, bucket, region vb. config'te yazılır. Task role varken env key şart
645
+ değil. Production build'de Assets upload adımı her zaman görünür; kapalıysa
646
+ `skipped (…)` ile sebep basılır. Watch'ta adım yok. `.br` / `.gz` yüklenmez.
647
+ Ayrıntı: [08-build.md](./08-build.md), [10-dagitim.md](./10-dagitim.md).
648
648
 
649
649
  ```js
650
650
  build: {
package/docs/08-build.md CHANGED
@@ -84,8 +84,8 @@ dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
84
84
 
85
85
  ### CDN prefix ve S3 upload
86
86
 
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
+ Varsayılan kapalıdır. Prefix / bucket / keyPrefix config'te; credential
88
+ env → ECS task role → EC2 IMDS zincirinden. Açılınca:
89
89
 
90
90
  1. Production build `public/assets/` (ve `include` listesindeki diğer dizinler)
91
91
  altındaki dosyaları S3'e PutObject ile kopyalar.
@@ -72,9 +72,9 @@ build: {
72
72
  ```
73
73
 
74
74
  ```bash
75
+ # CI / local: explicit keys. ECS/EC2'de task/instance role yeter — key şart değil.
75
76
  AWS_ACCESS_KEY_ID=…
76
77
  AWS_SECRET_ACCESS_KEY=…
77
- # geçici credential ise:
78
78
  # AWS_SESSION_TOKEN=…
79
79
  ```
80
80
 
@@ -651,13 +651,14 @@ changes** — assets stay on the origin.
651
651
  | `assets.upload.include` | `string[]` | `["assets"]` | Directories under `public/` (`assets`, optionally `fonts`, …). |
652
652
  | `assets.upload.cacheControl` | `string` | `public, max-age=31536000, immutable` | PutObject `Cache-Control`. |
653
653
 
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).
654
+ **Credentials are not written in the config.** Chain: `AWS_ACCESS_KEY_ID` /
655
+ `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` (else `JSKELET_S3_*`) → ECS/Fargate
656
+ task role (`AWS_CONTAINER_CREDENTIALS_*`) → EC2 instance profile (IMDSv2).
657
+ Prefix, bucket, region, etc. live in the config. With a task role, env keys are
658
+ not required. On a production build the Assets upload step always appears; when
659
+ off it logs `skipped (…)`. No upload on watch passes. `.br` / `.gz` siblings are
660
+ not uploaded. Details: [08-build.md](./08-build.md),
661
+ [10-deployment.md](./10-deployment.md).
661
662
 
662
663
  ```js
663
664
  build: {
@@ -87,8 +87,8 @@ They are passed to templates automatically; in server code,
87
87
 
88
88
  ### CDN prefix and S3 upload
89
89
 
90
- Off by default. Prefix / bucket / keyPrefix come from config; only credentials
91
- come from the environment (`AWS_*`, else `JSKELET_S3_*`). When enabled:
90
+ Off by default. Prefix / bucket / keyPrefix come from config; credentials
91
+ resolve from env → ECS task role → EC2 IMDS. When enabled:
92
92
 
93
93
  1. A production build PutObjects files under `public/assets/` (and any other
94
94
  directories in `include`) to S3.
@@ -73,9 +73,9 @@ build: {
73
73
  ```
74
74
 
75
75
  ```bash
76
+ # CI / local: explicit keys. On ECS/EC2 a task/instance role is enough — no keys required.
76
77
  AWS_ACCESS_KEY_ID=…
77
78
  AWS_SECRET_ACCESS_KEY=…
78
- # for temporary credentials:
79
79
  # AWS_SESSION_TOKEN=…
80
80
  ```
81
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jskelet",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
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",
@@ -1,16 +1,18 @@
1
1
  /**
2
2
  * Production build sonunda hash'li varlıkları S3'e PutObject ile kopyalar.
3
3
  *
4
- * Watch turunda çalışmaz (çağıran sorumlu). Credential env'den gelir
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.
4
+ * Watch turunda çalışmaz (çağıran sorumlu). Credential: env (`AWS_*` /
5
+ * `JSKELET_S3_*`) → ECS task role → EC2 instance profile. Bucket yoksa veya
6
+ * upload kapalıysa adım yine görünür: `skipped (…)` ile sebep basılır.
7
+ * Credential yalnızca env+metadata zinciri boşsa skip; task role varken
8
+ * env key şart değil.
7
9
  *
8
10
  * `.br` / `.gz` kardeşleri yüklenmez: edge kendi sıkıştırmasını yapsın;
9
11
  * origin precompress Express için kalır.
10
12
  */
11
13
  import fs from "node:fs";
12
14
  import path from "node:path";
13
- import { readS3CredentialsFromEnv } from "../../config/index.js";
15
+ import { resolveS3Credentials } from "../../server/logs/s3-credentials.js";
14
16
  import { putObject } from "../../server/logs/s3-put.js";
15
17
  import * as log from "../../log.mjs";
16
18
  import { paths } from "../paths.mjs";
@@ -88,7 +90,7 @@ function resolveIncludeDir(name) {
88
90
  }
89
91
 
90
92
  /**
91
- * Upload yapılmayacaksa kısa sebep; yapılacaksa `null`.
93
+ * Config kapısı — credential burada bakılmaz (task role async).
92
94
  *
93
95
  * @param {import('../../config/index.js').ResolvedConfig} config
94
96
  * @returns {string | null}
@@ -105,14 +107,13 @@ export function assetsUploadSkipReason(config) {
105
107
 
106
108
  if (!upload.bucket) return "no build.assets.upload.bucket";
107
109
 
108
- if (!readS3CredentialsFromEnv()) return "credentials missing";
109
-
110
110
  return null;
111
111
  }
112
112
 
113
113
  /**
114
114
  * @param {import('../../config/index.js').ResolvedConfig} config
115
- * @param {{ put?: typeof putObject }} [options]
115
+ * @param {{ put?: typeof putObject,
116
+ * resolveCredentials?: typeof resolveS3Credentials }} [options]
116
117
  * @returns {Promise<void>}
117
118
  */
118
119
  export async function uploadAssets(config, options = {}) {
@@ -123,15 +124,17 @@ export async function uploadAssets(config, options = {}) {
123
124
  }
124
125
 
125
126
  const upload = config.build.assets.upload;
126
- const credentials = readS3CredentialsFromEnv();
127
- // skipReason null iken credential ve bucket vardır; TS/akış için daralt.
127
+ const resolve = options.resolveCredentials ?? resolveS3Credentials;
128
+ const credentials = await resolve();
128
129
  if (!upload.bucket || !credentials) {
129
130
  log.detail("skipped (credentials missing)");
130
131
  return;
131
132
  }
132
133
 
133
134
  const put = options.put ?? putObject;
134
- const keyPrefix = (upload.keyPrefix || "").replace(/^\/+/, "");
135
+ // Normalize bazen atlanır (test / elle config); önek `/` ile bitsin.
136
+ let keyPrefix = (upload.keyPrefix || "").replace(/^\/+/, "");
137
+ if (keyPrefix && !keyPrefix.endsWith("/")) keyPrefix += "/";
135
138
 
136
139
  /** @type {{ file: string, key: string }[]} */
137
140
  const jobs = [];
@@ -33,6 +33,10 @@ import path from "node:path";
33
33
  import process from "node:process";
34
34
  import { pathToFileURL } from "node:url";
35
35
  import { compilePattern, matchPattern } from "./pattern.js";
36
+ import {
37
+ readS3CredentialsFromEnv,
38
+ resolveS3Credentials,
39
+ } from "../server/logs/s3-credentials.js";
36
40
  import {
37
41
  DEFAULT_ADMIN,
38
42
  DEFAULT_AUTH,
@@ -64,6 +68,8 @@ import {
64
68
  DEFAULT_UPSTREAM_LIMIT,
65
69
  } from "./defaults.js";
66
70
 
71
+ export { readS3CredentialsFromEnv, resolveS3Credentials };
72
+
67
73
  /** Framework paketinin kökü — kendi şablonlarına ve varlıklarına erişir. */
68
74
  export const FRAMEWORK_ROOT = path.resolve(import.meta.dirname, "..", "..");
69
75
 
@@ -527,32 +533,6 @@ function envText(value) {
527
533
  return typeof value === "string" && value ? value : null;
528
534
  }
529
535
 
530
- /**
531
- * S3 imza credential'ları. AWS deploy'da standart env önce gelir; R2/MinIO
532
- * için `JSKELET_S3_*` yedek. IAM instance/task role (IMDS) desteklenmez —
533
- * CI'da `configure-aws-credentials` gibi araçlar zaten `AWS_*` basar.
534
- *
535
- * @returns {{ accessKeyId: string, secretAccessKey: string,
536
- * sessionToken: string | null } | null}
537
- */
538
- export function readS3CredentialsFromEnv() {
539
- const accessKeyId =
540
- envText(process.env.AWS_ACCESS_KEY_ID) ??
541
- envText(process.env.JSKELET_S3_ACCESS_KEY_ID);
542
- const secretAccessKey =
543
- envText(process.env.AWS_SECRET_ACCESS_KEY) ??
544
- envText(process.env.JSKELET_S3_SECRET_ACCESS_KEY) ??
545
- envText(process.env.JSKELET_S3_ACCESS_SECRET);
546
- if (!accessKeyId || !secretAccessKey) return null;
547
- return {
548
- accessKeyId,
549
- secretAccessKey,
550
- sessionToken:
551
- envText(process.env.AWS_SESSION_TOKEN) ??
552
- envText(process.env.JSKELET_S3_SESSION_TOKEN),
553
- };
554
- }
555
-
556
536
  /**
557
537
  * Log hedefi. Öncelik: `JSKELET_LOG_BUCKET` → `JSKELET_S3_BUCKET`
558
538
  * (+ isteğe bağlı `JSKELET_S3_KEY_PREFIX`).
@@ -6,9 +6,9 @@
6
6
  */
7
7
  import process from "node:process";
8
8
  import * as log from "../../log.mjs";
9
- import { readS3CredentialsFromEnv } from "../../config/index.js";
10
9
  import { createFileSink } from "./file-sink.js";
11
10
  import { createS3Sink } from "./s3-sink.js";
11
+ import { resolveS3Credentials } from "./s3-credentials.js";
12
12
 
13
13
  /**
14
14
  * @typedef {import('../../config/index.js').LogsConfig} LogsConfig
@@ -73,14 +73,15 @@ export async function configureLogs(config) {
73
73
  }
74
74
 
75
75
  if (logs.s3.enabled) {
76
- const credentials = readS3CredentialsFromEnv();
76
+ const credentials = await resolveS3Credentials();
77
77
  if (!logs.s3.bucket) {
78
78
  console.warn(
79
79
  "[logs] s3.enabled but no bucket (set JSKELET_LOG_BUCKET, JSKELET_S3_BUCKET or logs.s3.bucket)",
80
80
  );
81
81
  } else if (!credentials) {
82
82
  console.warn(
83
- "[logs] s3.enabled but credentials missing — set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY or JSKELET_S3_*",
83
+ "[logs] s3.enabled but credentials missing — set AWS_ACCESS_KEY_ID/" +
84
+ "AWS_SECRET_ACCESS_KEY or JSKELET_S3_*, or run with an ECS/EC2 IAM role",
84
85
  );
85
86
  } else {
86
87
  const region = logs.s3.region || "auto";
@@ -0,0 +1,195 @@
1
+ /**
2
+ * S3 imza credential zinciri — SDK yok.
3
+ *
4
+ * 1. Env: `AWS_*` sonra `JSKELET_S3_*`
5
+ * 2. ECS/Fargate task role: `AWS_CONTAINER_CREDENTIALS_*`
6
+ * 3. EC2 instance profile: IMDSv2 (kısa timeout; local'de asılı kalmaz)
7
+ *
8
+ * EKS IRSA (`AWS_WEB_IDENTITY_TOKEN_FILE`) bilinçli olarak yok — STS çağrısı
9
+ * ayrı yüzey; CI genelde zaten `AWS_*` basar.
10
+ */
11
+ import fs from "node:fs";
12
+ import process from "node:process";
13
+
14
+ /**
15
+ * @typedef {{ accessKeyId: string, secretAccessKey: string,
16
+ * sessionToken: string | null }} AwsCredentials
17
+ */
18
+
19
+ const METADATA_TIMEOUT_MS = 400;
20
+ const ECS_METADATA_HOST = "http://169.254.170.2";
21
+ const EC2_METADATA_HOST = "http://169.254.169.254";
22
+
23
+ /**
24
+ * @param {unknown} value
25
+ * @returns {string | null}
26
+ */
27
+ function envText(value) {
28
+ return typeof value === "string" && value ? value : null;
29
+ }
30
+
31
+ /**
32
+ * @returns {AwsCredentials | null}
33
+ */
34
+ export function readS3CredentialsFromEnv() {
35
+ const accessKeyId =
36
+ envText(process.env.AWS_ACCESS_KEY_ID) ??
37
+ envText(process.env.JSKELET_S3_ACCESS_KEY_ID);
38
+ const secretAccessKey =
39
+ envText(process.env.AWS_SECRET_ACCESS_KEY) ??
40
+ envText(process.env.JSKELET_S3_SECRET_ACCESS_KEY) ??
41
+ envText(process.env.JSKELET_S3_ACCESS_SECRET);
42
+ if (!accessKeyId || !secretAccessKey) return null;
43
+ return {
44
+ accessKeyId,
45
+ secretAccessKey,
46
+ sessionToken:
47
+ envText(process.env.AWS_SESSION_TOKEN) ??
48
+ envText(process.env.JSKELET_S3_SESSION_TOKEN),
49
+ };
50
+ }
51
+
52
+ /**
53
+ * @param {Record<string, unknown>} data
54
+ * @returns {AwsCredentials | null}
55
+ */
56
+ function fromAwsCredentialJson(data) {
57
+ const accessKeyId =
58
+ typeof data.AccessKeyId === "string" ? data.AccessKeyId : null;
59
+ const secretAccessKey =
60
+ typeof data.SecretAccessKey === "string" ? data.SecretAccessKey : null;
61
+ if (!accessKeyId || !secretAccessKey) return null;
62
+ const token =
63
+ (typeof data.Token === "string" && data.Token) ||
64
+ (typeof data.SessionToken === "string" && data.SessionToken) ||
65
+ null;
66
+ return { accessKeyId, secretAccessKey, sessionToken: token };
67
+ }
68
+
69
+ /**
70
+ * @param {string} url
71
+ * @param {RequestInit} [init]
72
+ * @param {number} [timeoutMs]
73
+ * @returns {Promise<unknown | null>}
74
+ */
75
+ async function fetchJson(url, init = {}, timeoutMs = METADATA_TIMEOUT_MS) {
76
+ const controller = new AbortController();
77
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
78
+ try {
79
+ const response = await fetch(url, { ...init, signal: controller.signal });
80
+ if (!response.ok) return null;
81
+ return await response.json();
82
+ } catch {
83
+ return null;
84
+ } finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * @param {string} url
91
+ * @param {RequestInit} [init]
92
+ * @param {number} [timeoutMs]
93
+ * @returns {Promise<string | null>}
94
+ */
95
+ async function fetchText(url, init = {}, timeoutMs = METADATA_TIMEOUT_MS) {
96
+ const controller = new AbortController();
97
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
98
+ try {
99
+ const response = await fetch(url, { ...init, signal: controller.signal });
100
+ if (!response.ok) return null;
101
+ return (await response.text()).trim() || null;
102
+ } catch {
103
+ return null;
104
+ } finally {
105
+ clearTimeout(timer);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * ECS task role / container credential provider.
111
+ *
112
+ * @returns {Promise<AwsCredentials | null>}
113
+ */
114
+ export async function readS3CredentialsFromEcs() {
115
+ const relative = envText(process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
116
+ const full = envText(process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI);
117
+ if (!relative && !full) return null;
118
+
119
+ const url = full ?? `${ECS_METADATA_HOST}${relative}`;
120
+
121
+ /** @type {Record<string, string>} */
122
+ const headers = {};
123
+ const token =
124
+ envText(process.env.AWS_CONTAINER_AUTHORIZATION_TOKEN) ??
125
+ readTokenFile(process.env.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE);
126
+ if (token) headers.Authorization = token;
127
+
128
+ const data = await fetchJson(url, { headers });
129
+ if (!data || typeof data !== "object") return null;
130
+ return fromAwsCredentialJson(/** @type {Record<string, unknown>} */ (data));
131
+ }
132
+
133
+ /**
134
+ * @param {unknown} pathValue
135
+ * @returns {string | null}
136
+ */
137
+ function readTokenFile(pathValue) {
138
+ const file = envText(pathValue);
139
+ if (!file) return null;
140
+ try {
141
+ const text = fs.readFileSync(file, "utf8").trim();
142
+ return text || null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * EC2 instance profile via IMDSv2 (IMDSv1'e düşmez).
150
+ *
151
+ * @returns {Promise<AwsCredentials | null>}
152
+ */
153
+ export async function readS3CredentialsFromImds() {
154
+ const token = await fetchText(
155
+ `${EC2_METADATA_HOST}/latest/api/token`,
156
+ {
157
+ method: "PUT",
158
+ headers: { "X-aws-ec2-metadata-token-ttl-seconds": "21600" },
159
+ },
160
+ );
161
+ if (!token) return null;
162
+
163
+ const headers = { "X-aws-ec2-metadata-token": token };
164
+ const roleName = await fetchText(
165
+ `${EC2_METADATA_HOST}/latest/meta-data/iam/security-credentials/`,
166
+ { headers },
167
+ );
168
+ if (!roleName) return null;
169
+
170
+ // Birden fazla satır olursa ilki.
171
+ const role = roleName.split("\n")[0]?.trim();
172
+ if (!role || role.includes("..") || role.includes("/")) return null;
173
+
174
+ const data = await fetchJson(
175
+ `${EC2_METADATA_HOST}/latest/meta-data/iam/security-credentials/${encodeURIComponent(role)}`,
176
+ { headers },
177
+ );
178
+ if (!data || typeof data !== "object") return null;
179
+ return fromAwsCredentialJson(/** @type {Record<string, unknown>} */ (data));
180
+ }
181
+
182
+ /**
183
+ * Env → ECS task role → EC2 instance profile.
184
+ *
185
+ * @returns {Promise<AwsCredentials | null>}
186
+ */
187
+ export async function resolveS3Credentials() {
188
+ const fromEnv = readS3CredentialsFromEnv();
189
+ if (fromEnv) return fromEnv;
190
+
191
+ const fromEcs = await readS3CredentialsFromEcs();
192
+ if (fromEcs) return fromEcs;
193
+
194
+ return readS3CredentialsFromImds();
195
+ }
@@ -2,9 +2,8 @@
2
2
  * Minimal S3 PutObject — yalnızca SigV4 + `fetch`.
3
3
  *
4
4
  * `@aws-sdk/client-s3` taşınmıyor: tek operasyon için SDK'nın maliyeti ve
5
- * peer yüzeyi gereksiz. Credential zinciri env'den okunur (`AWS_*` önce,
6
- * sonra `JSKELET_S3_*`); IAM role / instance metadata yok — CI'da
7
- * `configure-aws-credentials` gibi araçlar key basar.
5
+ * peer yüzeyi gereksiz. Credential `resolveS3Credentials` ile gelir
6
+ * (env → ECS task role → EC2 IMDS).
8
7
  */
9
8
  import crypto from "node:crypto";
10
9
 
@@ -1,4 +1,6 @@
1
+ import { readS3CredentialsFromEnv, resolveS3Credentials } from "../server/logs/s3-credentials.js";
1
2
  import { DEFAULT_ADMIN, DEFAULT_CLOUDFLARE, DEFAULT_UPSTREAM_LIMIT } from "./defaults.js";
3
+ export { readS3CredentialsFromEnv, resolveS3Credentials };
2
4
  /** Framework paketinin kökü — kendi şablonlarına ve varlıklarına erişir. */
3
5
  export declare const FRAMEWORK_ROOT: string;
4
6
  export type Eagerness = "conservative" | "moderate" | "eager";
@@ -278,19 +280,6 @@ export declare function splitS3BucketPath(value: string | null): {
278
280
  bucket: string | null;
279
281
  prefix: string | null;
280
282
  };
281
- /**
282
- * S3 imza credential'ları. AWS deploy'da standart env önce gelir; R2/MinIO
283
- * için `JSKELET_S3_*` yedek. IAM instance/task role (IMDS) desteklenmez —
284
- * CI'da `configure-aws-credentials` gibi araçlar zaten `AWS_*` basar.
285
- *
286
- * @returns {{ accessKeyId: string, secretAccessKey: string,
287
- * sessionToken: string | null } | null}
288
- */
289
- export declare function readS3CredentialsFromEnv(): {
290
- accessKeyId: string;
291
- secretAccessKey: string;
292
- sessionToken: string | null;
293
- } | null;
294
283
  /**
295
284
  * Kalıcı log sink'leri. Bozuk bir `kinds` listesi siteyi düşürmemeli —
296
285
  * bilinmeyen girdiler atılır; hiç geçerli tür kalmazsa varsayılana dönülür.
@@ -0,0 +1,27 @@
1
+ export type AwsCredentials = {
2
+ accessKeyId: string;
3
+ secretAccessKey: string;
4
+ sessionToken: string | null;
5
+ };
6
+ /**
7
+ * @returns {AwsCredentials | null}
8
+ */
9
+ export declare function readS3CredentialsFromEnv(): AwsCredentials | null;
10
+ /**
11
+ * ECS task role / container credential provider.
12
+ *
13
+ * @returns {Promise<AwsCredentials | null>}
14
+ */
15
+ export declare function readS3CredentialsFromEcs(): Promise<AwsCredentials | null>;
16
+ /**
17
+ * EC2 instance profile via IMDSv2 (IMDSv1'e düşmez).
18
+ *
19
+ * @returns {Promise<AwsCredentials | null>}
20
+ */
21
+ export declare function readS3CredentialsFromImds(): Promise<AwsCredentials | null>;
22
+ /**
23
+ * Env → ECS task role → EC2 instance profile.
24
+ *
25
+ * @returns {Promise<AwsCredentials | null>}
26
+ */
27
+ export declare function resolveS3Credentials(): Promise<AwsCredentials | null>;