jskelet 0.4.2 → 0.4.3

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
@@ -24,9 +24,19 @@ one is listed under a **Breaking** heading.
24
24
  query time and a default 24h lookback became `1d` plus network delay — Free
25
25
  zones reject anything wider than one day. Both ends are now pinned from the
26
26
  same clock (`datetime_leq` included).
27
+ - Missing `/assets/*` responses no longer keep the long-lived `immutable`
28
+ Cache-Control that `headersMiddleware` stamps for static prefixes. A deploy
29
+ race (prune-before-write) could 404 a hashed CSS URL for a moment; a CDN then
30
+ cached that HTML 404 for a year and browsers refused it as a stylesheet
31
+ (`MIME type 'text/html'`). Catch-all and `notFound` handlers now set
32
+ `Cache-Control: no-store`. CSS and sprite builds write the new file before
33
+ pruning older hashes so the same content hash never has a gap.
27
34
 
28
35
  ### Added
29
36
 
37
+ - Marketing homepage ops storyboard: Redis L2, `/_jskelet/admin` panel mock and
38
+ Cloudflare purge flow, with tabbed visual scenes animated by the vanilla
39
+ `motion` API (Framer Motion’s non-React package) via an `ops-story` island.
30
40
  - VS Code / Cursor extension skeleton under `extensions/vscode-jsk`: `.jsk`
31
41
  language id, TextMate highlighting (`{{ }}` / `{#if}` / `{#each}` /
32
42
  components), language config, and snippets. Install from that folder or
@@ -144,6 +154,14 @@ one is listed under a **Breaking** heading.
144
154
  island contract, path-based `invalidateHtmlCache` (replacing the outdated
145
155
  “no targeted invalidation” claim), Redis / admin / data-cache callouts, and
146
156
  bilingual doc links under `docs/` and `docs/en/`.
157
+ - Marketing compare live latency demo now measures two same-sized fragments
158
+ (cached vs `no-store`) with an explicit 80 ms simulated upstream inside the
159
+ shared producer — a hit skips that wait so the gap is visible even when RTT
160
+ dominates the wall clock. The island prints transferred bytes, Server-Timing
161
+ `produce` duration, and a View Source section contrasts `__NEXT_DATA__`
162
+ payload tax with plain JSkelet HTML. The measured-weight block also shows an
163
+ estimated Next.js App Router first-load breakdown beside this site’s real
164
+ gzip totals (clearly labelled estimate, not a build from this repo).
147
165
  - Duplicate component named exports (or the same PascalCase tag in two files)
148
166
  now **fail** at build and at server startup instead of warning and letting
149
167
  the second definition win. Overwriting `components/index.js` barrel exports
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jskelet",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "A framework that feels like no framework: Express 5 + build-time .jsk (or EJS) SSR, vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -131,13 +131,23 @@ export function patchManifest(key, url) {
131
131
  /**
132
132
  * Eski hash'li çıktıları temizler.
133
133
  *
134
+ * Yeni dosya **önce** yazılmalı, prune sonra gelmeli: aynı içerik aynı hash'i
135
+ * üretir ve önce silmek `/assets/app.<hash>.css` için kısa bir 404 penceresi
136
+ * açar. CDN o 404'ü `immutable` ile saklarsa (eski headersMiddleware
137
+ * davranışı) tarayıcı bir yıl boyunca stilsiz kalır.
138
+ *
134
139
  * @param {string[]} prefixes
140
+ * @param {{ keep?: string[] }} [options] Korunacak dosya adları (ör. yeni
141
+ * yazılan `app.<hash>.css`); `.br` / `.gz` sonekleri de eşleşir.
135
142
  */
136
- export function pruneAssets(prefixes) {
143
+ export function pruneAssets(prefixes, { keep = [] } = {}) {
137
144
  if (!fs.existsSync(paths.assets)) return;
145
+
138
146
  for (const file of fs.readdirSync(paths.assets)) {
139
- if (prefixes.some((prefix) => file.startsWith(prefix))) {
140
- fs.rmSync(path.join(paths.assets, file), { force: true });
147
+ if (!prefixes.some((prefix) => file.startsWith(prefix))) continue;
148
+ if (keep.some((name) => file === name || file.startsWith(`${name}.`))) {
149
+ continue;
141
150
  }
151
+ fs.rmSync(path.join(paths.assets, file), { force: true });
142
152
  }
143
153
  }
@@ -64,9 +64,11 @@ export async function buildCss(config, { watch = false } = {}) {
64
64
 
65
65
  const run = async () => {
66
66
  const started = Date.now();
67
- pruneAssets(["app."]);
68
67
  const css = await compile(input);
68
+ // Önce yaz, sonra eski hash'leri sil — aynı hash'e düşen içerikte 404
69
+ // penceresi olmasın (CDN immutable zehirlenmesi).
69
70
  const url = writeAsset("app.css", css);
71
+ pruneAssets(["app."], { keep: [path.basename(url)] });
70
72
  return { url, bytes: Buffer.byteLength(css), elapsed: Date.now() - started };
71
73
  };
72
74
 
@@ -192,8 +192,6 @@ export async function buildIconSprite(config) {
192
192
  config.icons?.scan ?? ["views", "client", "routes", "lib", "features", "shared"]
193
193
  ).map((dir) => path.resolve(config.root, dir));
194
194
 
195
- pruneAssets(["sprite."]);
196
-
197
195
  const used = [...scanUsedIcons(scanDirs)].sort();
198
196
  const symbols = [];
199
197
  const missing = [];
@@ -214,6 +212,7 @@ export async function buildIconSprite(config) {
214
212
 
215
213
  const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${symbols.join("")}</svg>`;
216
214
  const url = writeAsset("sprite.svg", sprite);
215
+ pruneAssets(["sprite."], { keep: [path.basename(url)] });
217
216
 
218
217
  log.detail(`${symbols.length} symbols`);
219
218
  if (missing.length) {
@@ -148,7 +148,10 @@ export async function createApp(options = {}) {
148
148
 
149
149
  app.use(async (req, res, next) => {
150
150
  try {
151
- res.status(404).type("html").send(await renderNotFound());
151
+ // headersMiddleware `/assets/*` için immutable basmış olabilir; eksik bir
152
+ // hash'li dosyanın 404'ü CDN'de bir yıl zehirlenmesin.
153
+ res.status(404).setHeader("Cache-Control", "no-store");
154
+ res.type("html").send(await renderNotFound());
152
155
  } catch (error) {
153
156
  next(error);
154
157
  }
@@ -168,7 +171,8 @@ export async function createApp(options = {}) {
168
171
  }
169
172
 
170
173
  if (isNotFoundError(error)) {
171
- res.status(404).type("html").send(await renderNotFound());
174
+ res.status(404).setHeader("Cache-Control", "no-store");
175
+ res.type("html").send(await renderNotFound());
172
176
  return;
173
177
  }
174
178