jskelet 0.6.11 → 0.6.12

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
@@ -19,9 +19,11 @@ one is listed under a **Breaking** heading.
19
19
  `JSKELET_S3_*`, then ECS task-role / EC2 instance-profile metadata). The
20
20
  Assets upload step always appears on production builds; when off it logs a
21
21
  one-line reason (`skipped (no build.assets.upload.bucket)`,
22
- `skipped (credentials missing)`, …). A URL in `upload.bucket` is rejected
23
- with a warning (CDN origin belongs in `prefix`). Watch mode never uploads.
24
- `.br` / `.gz` siblings are skipped.
22
+ `skipped (credentials missing (no env keys, no ECS task role …))`, …).
23
+ Docker/`RUN jskelet build` has no task role — upload needs CI role or a
24
+ runtime upload. A URL in `upload.bucket` is rejected with a warning (CDN
25
+ origin belongs in `prefix`). Watch mode never uploads. `.br` / `.gz`
26
+ siblings are skipped.
25
27
 
26
28
  ### Changed
27
29
 
@@ -55,34 +55,41 @@ ayarlamayı düşünmeniz gerekenler:
55
55
 
56
56
  ### Asset'leri S3 / CloudFront'a basmak
57
57
 
58
- Varsayılan kapalıdır. Config'te prefix + bucket; CI'da yalnızca credential:
58
+ Varsayılan kapalıdır. Config'te prefix + bucket; credential env key **veya**
59
+ IAM role:
59
60
 
60
61
  ```js
61
- // jskelet.config.mjs
62
+ // jskelet.config.mjs — uygulama env'lerini buraya bağla
62
63
  build: {
63
64
  assets: {
64
- prefix: "https://static.ornek.com/js",
65
+ prefix: process.env.NEXT_PUBLIC_STATIC_ASSETS_BASE_URL, // https://static.…
65
66
  upload: {
66
- bucket: "static.ornek.com", // veya "static.ornek.com/js"
67
+ bucket: process.env.S3_BUCKET_NAME, // static.investvio.com
67
68
  keyPrefix: "js",
68
- region: "eu-central-1",
69
+ region: process.env.AWS_REGION || "eu-central-1",
70
+ endpoint: process.env.AWS_S3_ENDPOINT || undefined,
69
71
  },
70
72
  },
71
73
  },
72
74
  ```
73
75
 
74
- ```bash
75
- # CI / local: explicit keys. ECS/EC2'de task/instance role yeter — key şart değil.
76
- AWS_ACCESS_KEY_ID=…
77
- AWS_SECRET_ACCESS_KEY=…
78
- # AWS_SESSION_TOKEN=…
79
- ```
76
+ **Task role ne zaman çalışır?** ECS task role /
77
+ `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` yalnızca **görev çalışırken** vardır.
78
+ Dockerfile içindeki `RUN jskelet build` veya CI build agent'ında bu URI
79
+ yoktur → `skipped (credentials missing (no env keys, no ECS task role …))`.
80
+ AWS SDK'nın runtime'da rolü görmesi, build adımının rolü görmesi demek değildir.
81
+
82
+ Pratik seçenekler:
83
+
84
+ 1. **CI'da rol ile build** (CodeBuild / GitHub OIDC) — static key yok, upload
85
+ build job'unda biter.
86
+ 2. **Image'da yalnızca derle**, upload'u ECS start'ta ayrı koş (task role o an
87
+ vardır).
88
+ 3. CI'da geçici `AWS_*` key (tercih edilmez).
80
89
 
81
- `jskelet build` bitince `public/assets/` (ve config'te `include` edilen
82
- dizinler) S3'e kopyalanır; origin'deki dosyalar da durur. Bucket veya
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.
90
+ `jskelet build` bitince `public/assets/` S3'e kopyalanır; origin kopyası durur.
91
+ Bucket/credential yoksa adım `skipped (…)` basar. Watch / `jskelet dev` upload
92
+ yapmaz. CSP'ye CDN origin ekleyin.
86
93
 
87
94
  Production'da dosya veya S3 sink açıldığında HTTP access log middleware
88
95
  otomatik mount edilir (`logs.kinds` içinde `http` varsa). Admin paneli ring'i
@@ -56,34 +56,40 @@ considering in production:
56
56
 
57
57
  ### Publishing assets to S3 / CloudFront
58
58
 
59
- Off by default. Prefix + bucket in config; credentials only in CI:
59
+ Off by default. Prefix + bucket in config; credentials from env keys **or** an
60
+ IAM role:
60
61
 
61
62
  ```js
62
- // jskelet.config.mjs
63
+ // jskelet.config.mjs — wire your app env vars here
63
64
  build: {
64
65
  assets: {
65
- prefix: "https://static.example.com/js",
66
+ prefix: process.env.NEXT_PUBLIC_STATIC_ASSETS_BASE_URL, // https://static.…
66
67
  upload: {
67
- bucket: "static.example.com", // or "static.example.com/js"
68
+ bucket: process.env.S3_BUCKET_NAME, // static.example.com
68
69
  keyPrefix: "js",
69
- region: "eu-central-1",
70
+ region: process.env.AWS_REGION || "eu-central-1",
71
+ endpoint: process.env.AWS_S3_ENDPOINT || undefined,
70
72
  },
71
73
  },
72
74
  },
73
75
  ```
74
76
 
75
- ```bash
76
- # CI / local: explicit keys. On ECS/EC2 a task/instance role is enough — no keys required.
77
- AWS_ACCESS_KEY_ID=…
78
- AWS_SECRET_ACCESS_KEY=…
79
- # AWS_SESSION_TOKEN=…
80
- ```
77
+ **When does the task role work?** The ECS task role /
78
+ `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` exist only while the **task is
79
+ running**. A `RUN jskelet build` inside Docker or a CI build agent has no such
80
+ URI → `skipped (credentials missing (no env keys, no ECS task role …))`. The AWS
81
+ SDK seeing the role at runtime does not mean the build step sees it.
82
+
83
+ Practical options:
84
+
85
+ 1. **Build with a CI role** (CodeBuild / GitHub OIDC) — no static keys; upload
86
+ finishes in the job.
87
+ 2. **Compile in the image**, run upload at ECS start (task role is present then).
88
+ 3. Temporary `AWS_*` keys in CI (not preferred).
81
89
 
82
- When `jskelet build` finishes, files under `public/assets/` (and any other
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`.
90
+ When `jskelet build` finishes, `public/assets/` is copied to S3; origin copies
91
+ remain. Missing bucket/credentials log `skipped (…)`. Watch / `jskelet dev`
92
+ never upload. Add the CDN origin to CSP.
87
93
 
88
94
  When a file or S3 sink is enabled in production, the HTTP access log middleware
89
95
  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.11",
3
+ "version": "0.6.12",
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",
@@ -12,7 +12,10 @@
12
12
  */
13
13
  import fs from "node:fs";
14
14
  import path from "node:path";
15
- import { resolveS3Credentials } from "../../server/logs/s3-credentials.js";
15
+ import {
16
+ resolveS3Credentials,
17
+ s3CredentialsMissingReason,
18
+ } from "../../server/logs/s3-credentials.js";
16
19
  import { putObject } from "../../server/logs/s3-put.js";
17
20
  import * as log from "../../log.mjs";
18
21
  import { paths } from "../paths.mjs";
@@ -127,7 +130,7 @@ export async function uploadAssets(config, options = {}) {
127
130
  const resolve = options.resolveCredentials ?? resolveS3Credentials;
128
131
  const credentials = await resolve();
129
132
  if (!upload.bucket || !credentials) {
130
- log.detail("skipped (credentials missing)");
133
+ log.detail(`skipped (${s3CredentialsMissingReason()})`);
131
134
  return;
132
135
  }
133
136
 
@@ -16,7 +16,9 @@ import process from "node:process";
16
16
  * sessionToken: string | null }} AwsCredentials
17
17
  */
18
18
 
19
- const METADATA_TIMEOUT_MS = 400;
19
+ // Local'de IMDS yokken hızlı vazgeç; ECS'te URI varken biraz daha sabır.
20
+ const IMDS_TIMEOUT_MS = 400;
21
+ const ECS_TIMEOUT_MS = 2_000;
20
22
  const ECS_METADATA_HOST = "http://169.254.170.2";
21
23
  const EC2_METADATA_HOST = "http://169.254.169.254";
22
24
 
@@ -72,7 +74,7 @@ function fromAwsCredentialJson(data) {
72
74
  * @param {number} [timeoutMs]
73
75
  * @returns {Promise<unknown | null>}
74
76
  */
75
- async function fetchJson(url, init = {}, timeoutMs = METADATA_TIMEOUT_MS) {
77
+ async function fetchJson(url, init = {}, timeoutMs = IMDS_TIMEOUT_MS) {
76
78
  const controller = new AbortController();
77
79
  const timer = setTimeout(() => controller.abort(), timeoutMs);
78
80
  try {
@@ -92,7 +94,7 @@ async function fetchJson(url, init = {}, timeoutMs = METADATA_TIMEOUT_MS) {
92
94
  * @param {number} [timeoutMs]
93
95
  * @returns {Promise<string | null>}
94
96
  */
95
- async function fetchText(url, init = {}, timeoutMs = METADATA_TIMEOUT_MS) {
97
+ async function fetchText(url, init = {}, timeoutMs = IMDS_TIMEOUT_MS) {
96
98
  const controller = new AbortController();
97
99
  const timer = setTimeout(() => controller.abort(), timeoutMs);
98
100
  try {
@@ -125,7 +127,7 @@ export async function readS3CredentialsFromEcs() {
125
127
  readTokenFile(process.env.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE);
126
128
  if (token) headers.Authorization = token;
127
129
 
128
- const data = await fetchJson(url, { headers });
130
+ const data = await fetchJson(url, { headers }, ECS_TIMEOUT_MS);
129
131
  if (!data || typeof data !== "object") return null;
130
132
  return fromAwsCredentialJson(/** @type {Record<string, unknown>} */ (data));
131
133
  }
@@ -179,6 +181,21 @@ export async function readS3CredentialsFromImds() {
179
181
  return fromAwsCredentialJson(/** @type {Record<string, unknown>} */ (data));
180
182
  }
181
183
 
184
+ /**
185
+ * Credential neden bulunamadı — upload skip satırı için.
186
+ *
187
+ * @returns {string}
188
+ */
189
+ export function s3CredentialsMissingReason() {
190
+ if (
191
+ envText(process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) ||
192
+ envText(process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI)
193
+ ) {
194
+ return "credentials missing (ECS metadata failed)";
195
+ }
196
+ return "credentials missing (no env keys, no ECS task role — upload needs runtime/CI role, not docker build)";
197
+ }
198
+
182
199
  /**
183
200
  * Env → ECS task role → EC2 instance profile.
184
201
  *
@@ -19,6 +19,12 @@ export declare function readS3CredentialsFromEcs(): Promise<AwsCredentials | nul
19
19
  * @returns {Promise<AwsCredentials | null>}
20
20
  */
21
21
  export declare function readS3CredentialsFromImds(): Promise<AwsCredentials | null>;
22
+ /**
23
+ * Credential neden bulunamadı — upload skip satırı için.
24
+ *
25
+ * @returns {string}
26
+ */
27
+ export declare function s3CredentialsMissingReason(): string;
22
28
  /**
23
29
  * Env → ECS task role → EC2 instance profile.
24
30
  *