bun-plugin-stx 0.2.320 → 0.2.322

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/dist/serve.d.ts CHANGED
@@ -227,6 +227,17 @@ export declare function isRenderableCacheCandidate(html: string): boolean;
227
227
  */
228
228
  export declare function usableErrorPage(rendered: string | null): string | null;
229
229
  export declare function bundleBrowserAsset(entrypoint: string): Promise<Response>;
230
+ /**
231
+ * The bind budget from an option or config value. Anything that is not a
232
+ * finite, non-negative number falls back to the default rather than to
233
+ * "forever", since an unbounded wait is the failure the budget exists to stop.
234
+ */
235
+ export declare function resolveImageWarmupBindBudget(value: unknown): number;
236
+ /**
237
+ * Whether `promise` settles within `ms`. It is not cancelled either way: the
238
+ * caller decides what to do with work that is still running.
239
+ */
240
+ export declare function settleWithin(promise: Promise<unknown>, ms: number): Promise<boolean>;
230
241
  /**
231
242
  * Start the STX development server
232
243
  * @param options Server options with patterns and port
@@ -235,9 +246,17 @@ export declare function serve(options: ServeOptions): Promise<void>;
235
246
  /**
236
247
  * Whether any `.stx` template under `root` uses `<StxImage>` or `@image`.
237
248
  *
238
- * Stops at the first hit. Skips dependencies, build output and hidden
239
- * directories (the stx state directory among them), so it reads the project's
240
- * own templates, including a framework's templates vendored into it.
249
+ * Skips dependencies, build output and hidden directories (the stx state
250
+ * directory among them), so it reads the project's own templates, including a
251
+ * framework's templates vendored into it.
252
+ *
253
+ * A component that wraps `<StxImage>` counts only when something renders it.
254
+ * Stacks vendors its `Image.stx` component into every app, twice (the
255
+ * framework defaults and the pantry install tree), so reading a definition as
256
+ * a use turned the pass on for every Stacks app: one that only ever wrote
257
+ * plain `<img>` tags spent over seven minutes of each cold boot encoding
258
+ * 213MB of variants that no page could ask for. A use is followed through
259
+ * wrappers of wrappers, and a page, layout or partial using one counts.
241
260
  */
242
261
  export declare function projectUsesImageBuiltins(root: string): Promise<boolean>;
243
262
  /**
@@ -256,6 +275,8 @@ export declare const ROUTE_PARAMS_PLACEHOLDER: '__STX_ROUTE_PARAMS__';
256
275
  export declare const CSRF_COOKIE: 'X-CSRF-Token';
257
276
  /** `/_stx/runtime.js`, `/_stx/router.<16 hex>.js`, … — the shared client scripts. */
258
277
  export declare const SHARED_SCRIPT_PATH: unknown;
278
+ /** Default for `imageWarmupBindBudgetMs`: well inside ts-cloud's 180s bind window. */
279
+ export declare const DEFAULT_IMAGE_WARMUP_BIND_BUDGET_MS: unknown;
259
280
  /**
260
281
  * Build the candidate regexes for a dynamic route file (stacksjs/stx#1927).
261
282
  *
@@ -314,6 +335,7 @@ export declare interface ServeOptions {
314
335
  cssCacheLimit?: number
315
336
  prewarmRenderCache?: boolean | number
316
337
  imageWarmupGraceMs?: number
338
+ imageWarmupBindBudgetMs?: number
317
339
  imageWarmup?: boolean | 'auto'
318
340
  publicDir?: string
319
341
  quiet?: boolean
package/dist/serve.js CHANGED
@@ -9216,6 +9216,28 @@ async function bundleBrowserAsset(entrypoint) {
9216
9216
  }
9217
9217
  });
9218
9218
  }
9219
+ var DEFAULT_IMAGE_WARMUP_BIND_BUDGET_MS = 45000;
9220
+ function resolveImageWarmupBindBudget(value) {
9221
+ const ms = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
9222
+ return typeof ms === "number" && Number.isFinite(ms) && ms >= 0 ? ms : DEFAULT_IMAGE_WARMUP_BIND_BUDGET_MS;
9223
+ }
9224
+ async function settleWithin(promise, ms) {
9225
+ let timer;
9226
+ const settled = promise.then(() => true, () => true);
9227
+ try {
9228
+ return await Promise.race([
9229
+ settled,
9230
+ new Promise((resolve13) => {
9231
+ timer = setTimeout(resolve13, ms, false);
9232
+ })
9233
+ ]);
9234
+ } finally {
9235
+ clearTimeout(timer);
9236
+ }
9237
+ }
9238
+ function formatSeconds(ms) {
9239
+ return `${(ms / 1000).toFixed(ms < 1e4 ? 1 : 0)}s`;
9240
+ }
9219
9241
  async function serve(options) {
9220
9242
  const stxConfig = await loadConfig6({
9221
9243
  name: "stx",
@@ -9260,6 +9282,7 @@ async function serve(options) {
9260
9282
  let placeholdersAreReady = false;
9261
9283
  const imageWarmupSetting = options.imageWarmup ?? stxConfig.imageWarmup ?? "auto";
9262
9284
  const imageWarmupGraceMs = Math.max(0, options.imageWarmupGraceMs ?? 1000);
9285
+ const imageWarmupBindBudgetMs = resolveImageWarmupBindBudget(options.imageWarmupBindBudgetMs ?? stxConfig.imageWarmupBindBudgetMs);
9263
9286
  function awaitImageWarmup() {
9264
9287
  if (placeholdersAreReady || imageWarmupGraceMs === 0)
9265
9288
  return;
@@ -10373,9 +10396,10 @@ ${block}`;
10373
10396
  const publicRoot = nodePath.resolve(process19.cwd(), publicDir);
10374
10397
  const [placeholderResult, deliveryResult] = await Promise.allSettled([
10375
10398
  stx.warmImagePlaceholders(publicRoot, {
10376
- cachePath: imagePlaceholderCachePath
10399
+ cachePath: imagePlaceholderCachePath,
10400
+ offThread: true
10377
10401
  }),
10378
- stx.prepareImageDelivery(publicRoot, imageDeliveryOutputDir)
10402
+ stx.prepareImageDelivery(publicRoot, imageDeliveryOutputDir, { offThread: true })
10379
10403
  ]);
10380
10404
  const derived = placeholderResult.status === "fulfilled" ? placeholderResult.value : 0;
10381
10405
  if (derived > 0 && !production)
@@ -10387,8 +10411,17 @@ ${block}`;
10387
10411
  markPlaceholdersReady();
10388
10412
  }
10389
10413
  }
10390
- if (production)
10391
- await runImageWarmup();
10414
+ if (production) {
10415
+ const warmupStartedAt = performance.now();
10416
+ const warmup = runImageWarmup();
10417
+ const bound = await settleWithin(warmup, imageWarmupBindBudgetMs);
10418
+ if (!bound) {
10419
+ console.warn(`[stx] image warm-up still running after ${formatSeconds(imageWarmupBindBudgetMs)}; binding now and finishing it in the background. Images fall back to the original files until it completes. A cold image cache (first deploy, a cleared STX_IMAGE_CACHE_DIR, an encoder upgrade) is the usual cause; the next start reuses what this one encodes.`);
10420
+ warmup.then(() => {
10421
+ console.log(`[stx] image warm-up finished after ${formatSeconds(performance.now() - warmupStartedAt)}`);
10422
+ });
10423
+ }
10424
+ }
10392
10425
  const portAutoIncrement = options.autoIncrementPort ?? true;
10393
10426
  const maxPortAttempts = portAutoIncrement === false ? 1 : typeof portAutoIncrement === "number" ? Math.max(1, portAutoIncrement) : 10;
10394
10427
  let actualPort = port;
@@ -11182,8 +11215,20 @@ if (import.meta.main) {
11182
11215
  }
11183
11216
  var IMAGE_BUILTIN_PATTERN = /<(?:StxImage|Image|stx-image|stx-img)[\s/>]|@image\s*\(/;
11184
11217
  var TEMPLATE_SCAN_SKIP = new Set(["node_modules", "dist", "build", "coverage", "vendor"]);
11218
+ function escapeRegExp(value) {
11219
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11220
+ }
11221
+ function kebabCase(name) {
11222
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
11223
+ }
11224
+ function usesComponent(source, name) {
11225
+ const kebab = kebabCase(name);
11226
+ const spellings = (kebab.includes("-") ? [name, kebab] : [name]).map(escapeRegExp).join("|");
11227
+ return new RegExp(`<(?:${spellings})[\\s/>]|@component\\(\\s*['"](?:${spellings})['"]`).test(source);
11228
+ }
11185
11229
  async function projectUsesImageBuiltins(root) {
11186
11230
  const { readdir: readdir4, readFile } = await import("fs/promises");
11231
+ const templates = [];
11187
11232
  const pending = [root];
11188
11233
  while (pending.length > 0) {
11189
11234
  const dir = pending.pop();
@@ -11204,10 +11249,38 @@ async function projectUsesImageBuiltins(root) {
11204
11249
  }
11205
11250
  if (!entry.isFile() || !entry.name.endsWith(".stx"))
11206
11251
  continue;
11252
+ let source;
11207
11253
  try {
11208
- if (IMAGE_BUILTIN_PATTERN.test(await readFile(full, "utf8")))
11209
- return true;
11210
- } catch {}
11254
+ source = await readFile(full, "utf8");
11255
+ } catch {
11256
+ continue;
11257
+ }
11258
+ const inComponents = nodePath.relative(root, dir).split(nodePath.sep).includes("components");
11259
+ const component = inComponents ? entry.name.slice(0, -".stx".length) : undefined;
11260
+ if (!component && IMAGE_BUILTIN_PATTERN.test(source))
11261
+ return true;
11262
+ templates.push({ source, component });
11263
+ }
11264
+ }
11265
+ const imageComponents = new Set;
11266
+ const queue = [];
11267
+ for (const template of templates) {
11268
+ if (template.component && IMAGE_BUILTIN_PATTERN.test(template.source) && !imageComponents.has(template.component)) {
11269
+ imageComponents.add(template.component);
11270
+ queue.push(template.component);
11271
+ }
11272
+ }
11273
+ while (queue.length > 0) {
11274
+ const name = queue.shift();
11275
+ for (const template of templates) {
11276
+ if (template.component === name || !usesComponent(template.source, name))
11277
+ continue;
11278
+ if (!template.component)
11279
+ return true;
11280
+ if (!imageComponents.has(template.component)) {
11281
+ imageComponents.add(template.component);
11282
+ queue.push(template.component);
11283
+ }
11211
11284
  }
11212
11285
  }
11213
11286
  return false;
@@ -11216,12 +11289,14 @@ export {
11216
11289
  usableErrorPage,
11217
11290
  staticCacheControl,
11218
11291
  sharedScriptCacheControl,
11292
+ settleWithin,
11219
11293
  serverScriptsReadParams,
11220
11294
  serve,
11221
11295
  serializeRouteParams,
11222
11296
  routeSpecificity,
11223
11297
  responseTakesCsrfCookie,
11224
11298
  resolveServeOpenUrl,
11299
+ resolveImageWarmupBindBudget,
11225
11300
  render404Page,
11226
11301
  publicFileExists,
11227
11302
  projectUsesImageBuiltins,
@@ -11241,5 +11316,6 @@ export {
11241
11316
  boundedCache,
11242
11317
  SHARED_SCRIPT_PATH,
11243
11318
  ROUTE_PARAMS_PLACEHOLDER,
11319
+ DEFAULT_IMAGE_WARMUP_BIND_BUDGET_MS,
11244
11320
  CSRF_COOKIE
11245
11321
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-plugin-stx",
3
3
  "type": "module",
4
- "version": "0.2.320",
4
+ "version": "0.2.322",
5
5
  "description": "A Bun plugin that allows for using Laravel Blade-like syntax.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",