bun-plugin-stx 0.2.736 → 0.2.740
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/render-cache-store.d.ts +47 -0
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +250 -62
- package/package.json +3 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A digest of everything an entry's signature does not cover. Files are
|
|
3
|
+
* described by path, mtime and size; a missing file is part of the answer
|
|
4
|
+
* too, since creating one (a new `.env.local`) can change what renders.
|
|
5
|
+
*/
|
|
6
|
+
export declare function renderInputsFingerprint(inputs: RenderInputs): Promise<string>;
|
|
7
|
+
/** Put this process's build id where the rendering process put its own. */
|
|
8
|
+
export declare function restampBuildId(html: string, renderedWith: string, current: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* One file per entry, read on demand. A dashboard's worth of pages is tens of
|
|
11
|
+
* megabytes of HTML, so nothing is loaded up front and a page nobody opens
|
|
12
|
+
* costs nothing.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRenderCacheStore(options: {
|
|
15
|
+
dir: string
|
|
16
|
+
inputs: () => RenderInputs
|
|
17
|
+
buildId: () => string
|
|
18
|
+
}): RenderCacheStore;
|
|
19
|
+
export declare interface PersistableEntry {
|
|
20
|
+
html: string
|
|
21
|
+
signature: Map<string, number>
|
|
22
|
+
status: number
|
|
23
|
+
headers?: Record<string, string>
|
|
24
|
+
}
|
|
25
|
+
export declare interface RenderInputs {
|
|
26
|
+
roots: readonly string[]
|
|
27
|
+
files: readonly string[]
|
|
28
|
+
extra: unknown
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Taken before a render reads anything, and handed back to `write`.
|
|
32
|
+
*
|
|
33
|
+
* The fingerprint is settled first, so it can only be older than what the
|
|
34
|
+
* render read, never newer. An older one no longer matches after the change
|
|
35
|
+
* that made it older, which costs a miss; a newer one would vouch for HTML
|
|
36
|
+
* rendered from files it never described.
|
|
37
|
+
*/
|
|
38
|
+
export declare interface RenderTicket {
|
|
39
|
+
generation: number
|
|
40
|
+
fingerprint: string
|
|
41
|
+
}
|
|
42
|
+
export declare interface RenderCacheStore {
|
|
43
|
+
begin: () => Promise<RenderTicket>
|
|
44
|
+
read: (key: string) => Promise<PersistableEntry | undefined>
|
|
45
|
+
write: (key: string, entry: PersistableEntry, ticket: RenderTicket) => Promise<void>
|
|
46
|
+
invalidate: () => void
|
|
47
|
+
}
|
package/dist/serve.d.ts
CHANGED
|
@@ -361,6 +361,7 @@ export declare interface ServeOptions {
|
|
|
361
361
|
watchDirs?: string[]
|
|
362
362
|
renderCache?: boolean
|
|
363
363
|
renderCacheVary?: 'request' | 'source'
|
|
364
|
+
renderCachePersist?: boolean
|
|
364
365
|
renderCacheLimit?: number
|
|
365
366
|
clientAddress?: (req: Request, server: ServeServer) => string | null | undefined
|
|
366
367
|
cssCacheLimit?: number
|
package/dist/serve.js
CHANGED
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
// src/serve.ts
|
|
10
10
|
var {serve: bunServe, Glob } = globalThis.Bun;
|
|
11
11
|
import { existsSync as existsSync2, watch as fsWatch, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
12
|
+
import nodeFs2 from "fs/promises";
|
|
13
|
+
import nodePath2 from "path";
|
|
14
14
|
import process20 from "process";
|
|
15
15
|
|
|
16
16
|
// ../../node_modules/bunfig/dist/index.js
|
|
17
17
|
import { existsSync, statSync } from "fs";
|
|
18
18
|
import { resolve } from "path";
|
|
19
|
-
import
|
|
19
|
+
import process2 from "process";
|
|
20
20
|
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
21
21
|
import { homedir as homedir3 } from "os";
|
|
22
22
|
import { dirname as dirname4, resolve as resolve12 } from "path";
|
|
@@ -33,7 +33,7 @@ import { homedir } from "os";
|
|
|
33
33
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
34
34
|
import process6 from "process";
|
|
35
35
|
import { join, relative, resolve as resolve2 } from "path";
|
|
36
|
-
import
|
|
36
|
+
import process22 from "process";
|
|
37
37
|
import { existsSync as existsSync22, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
38
38
|
import { dirname, resolve as resolve5 } from "path";
|
|
39
39
|
import process3 from "process";
|
|
@@ -352,7 +352,7 @@ function getConfigPaths(options) {
|
|
|
352
352
|
return patterns.flatMap((pattern) => CONFIG_FILE_EXTENSIONS.map((extension) => resolve(options.directory, `${pattern}${extension}`)));
|
|
353
353
|
}
|
|
354
354
|
function getLocalConfigDirectories(options = {}) {
|
|
355
|
-
const cwd = resolve(options.cwd ||
|
|
355
|
+
const cwd = resolve(options.cwd || process2.cwd());
|
|
356
356
|
return Array.from(new Set([
|
|
357
357
|
cwd,
|
|
358
358
|
resolve(cwd, "config"),
|
|
@@ -851,12 +851,12 @@ async function loadConfig({
|
|
|
851
851
|
return defaultConfig;
|
|
852
852
|
}
|
|
853
853
|
function getProjectRoot(filePath, options = {}) {
|
|
854
|
-
let path =
|
|
854
|
+
let path = process22.cwd();
|
|
855
855
|
while (path.includes("storage"))
|
|
856
856
|
path = resolve2(path, "..");
|
|
857
857
|
const finalPath = resolve2(path, filePath || "");
|
|
858
858
|
if (options?.relative)
|
|
859
|
-
return relative(
|
|
859
|
+
return relative(process22.cwd(), finalPath);
|
|
860
860
|
return finalPath;
|
|
861
861
|
}
|
|
862
862
|
async function loadConfig2() {
|
|
@@ -864,7 +864,7 @@ async function loadConfig2() {
|
|
|
864
864
|
const loadedConfig = await loadConfig({
|
|
865
865
|
name: "clarity",
|
|
866
866
|
defaultConfig,
|
|
867
|
-
cwd:
|
|
867
|
+
cwd: process22.cwd(),
|
|
868
868
|
endpoint: "",
|
|
869
869
|
headers: {}
|
|
870
870
|
});
|
|
@@ -5389,7 +5389,7 @@ var init_dist = __esm(async () => {
|
|
|
5389
5389
|
};
|
|
5390
5390
|
defaultConfigDir = resolve5(process3.cwd(), "config");
|
|
5391
5391
|
defaultGeneratedDir = resolve5(process3.cwd(), "src/generated");
|
|
5392
|
-
defaultLogDirectory =
|
|
5392
|
+
defaultLogDirectory = process22.env.CLARITY_LOG_DIR || join(getProjectRoot(), "logs");
|
|
5393
5393
|
defaultConfig = {
|
|
5394
5394
|
level: "info",
|
|
5395
5395
|
defaultName: "clarity",
|
|
@@ -8881,10 +8881,39 @@ async function servedTemplatesUseImageBuiltins(root, scope) {
|
|
|
8881
8881
|
options.additionalComponentsDirs = options.additionalComponentsDirs.map(absolute);
|
|
8882
8882
|
const directories = new Map;
|
|
8883
8883
|
const sources = new Map;
|
|
8884
|
+
const listings = new Map;
|
|
8885
|
+
const isFileCache = new Map;
|
|
8886
|
+
function listing(dir) {
|
|
8887
|
+
let entries = listings.get(dir);
|
|
8888
|
+
if (!entries) {
|
|
8889
|
+
entries = readdir4(dir, { withFileTypes: true }).then((list) => ({ exact: new Map(list.map((entry) => [entry.name, entry])), folded: new Set(list.map((entry) => entry.name.toLowerCase())) }), () => null);
|
|
8890
|
+
listings.set(dir, entries);
|
|
8891
|
+
}
|
|
8892
|
+
return entries;
|
|
8893
|
+
}
|
|
8894
|
+
function isFile(file) {
|
|
8895
|
+
let known = isFileCache.get(file);
|
|
8896
|
+
if (!known) {
|
|
8897
|
+
known = (async () => {
|
|
8898
|
+
const entries = await listing(path.dirname(file));
|
|
8899
|
+
if (!entries)
|
|
8900
|
+
return false;
|
|
8901
|
+
const name = path.basename(file);
|
|
8902
|
+
const entry = entries.exact.get(name);
|
|
8903
|
+
if (entry?.isFile())
|
|
8904
|
+
return true;
|
|
8905
|
+
if (entry?.isSymbolicLink() || !entry && entries.folded.has(name.toLowerCase()))
|
|
8906
|
+
return stat4(file).then((result) => result.isFile(), () => false);
|
|
8907
|
+
return false;
|
|
8908
|
+
})();
|
|
8909
|
+
isFileCache.set(file, known);
|
|
8910
|
+
}
|
|
8911
|
+
return known;
|
|
8912
|
+
}
|
|
8884
8913
|
async function firstFile(candidates) {
|
|
8885
8914
|
for (const candidate of candidates) {
|
|
8886
8915
|
const file = absolute(candidate);
|
|
8887
|
-
if (await
|
|
8916
|
+
if (await isFile(file))
|
|
8888
8917
|
return file;
|
|
8889
8918
|
}
|
|
8890
8919
|
}
|
|
@@ -8914,12 +8943,22 @@ async function servedTemplatesUseImageBuiltins(root, scope) {
|
|
|
8914
8943
|
}
|
|
8915
8944
|
return files;
|
|
8916
8945
|
}
|
|
8917
|
-
|
|
8946
|
+
const resolutions = new Map;
|
|
8947
|
+
function component(name, file, page, imported) {
|
|
8918
8948
|
const pascal = name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
8919
8949
|
for (const alias of [name, name.toLowerCase(), pascal, pascalToKebab(name)]) {
|
|
8920
8950
|
if (imported.has(alias))
|
|
8921
|
-
return imported.get(alias);
|
|
8951
|
+
return Promise.resolve(imported.get(alias));
|
|
8922
8952
|
}
|
|
8953
|
+
const key = `${name}\x00${path.dirname(page)}\x00${path.dirname(file)}`;
|
|
8954
|
+
let resolving = resolutions.get(key);
|
|
8955
|
+
if (!resolving) {
|
|
8956
|
+
resolving = resolveComponent(name, pascal, file, page);
|
|
8957
|
+
resolutions.set(key, resolving);
|
|
8958
|
+
}
|
|
8959
|
+
return resolving;
|
|
8960
|
+
}
|
|
8961
|
+
async function resolveComponent(name, pascal, file, page) {
|
|
8923
8962
|
if (name.startsWith("./") || name.startsWith("../"))
|
|
8924
8963
|
return firstFile([path.resolve(path.dirname(file), name.endsWith(".stx") ? name : `${name}.stx`)]);
|
|
8925
8964
|
const names = [...new Set([name, pascal, pascalToKebab(name)].map((value) => `${value.replace(/\.stx$/, "")}.stx`))];
|
|
@@ -9023,6 +9062,94 @@ async function servedTemplatesUseImageBuiltins(root, scope) {
|
|
|
9023
9062
|
}
|
|
9024
9063
|
return false;
|
|
9025
9064
|
}
|
|
9065
|
+
|
|
9066
|
+
// src/render-cache-store.ts
|
|
9067
|
+
import nodeFs from "fs/promises";
|
|
9068
|
+
import nodePath from "path";
|
|
9069
|
+
import { createHash } from "crypto";
|
|
9070
|
+
var WATCHED_EXTENSIONS = new Set([".stx", ".md", ".html", ".js", ".ts", ".tsx", ".mts", ".cts", ".json"]);
|
|
9071
|
+
async function renderInputsFingerprint(inputs) {
|
|
9072
|
+
const lines = [];
|
|
9073
|
+
for (const root of new Set(inputs.roots)) {
|
|
9074
|
+
const absolute = nodePath.resolve(root);
|
|
9075
|
+
const entries = await nodeFs.readdir(absolute, { recursive: true, withFileTypes: true }).catch(() => null);
|
|
9076
|
+
if (!entries) {
|
|
9077
|
+
lines.push(`root ${absolute} missing`);
|
|
9078
|
+
continue;
|
|
9079
|
+
}
|
|
9080
|
+
const files = entries.filter((entry) => entry.isFile() && WATCHED_EXTENSIONS.has(nodePath.extname(entry.name))).map((entry) => nodePath.join(entry.parentPath, entry.name)).filter((file) => !nodePath.relative(absolute, nodePath.dirname(file)).split(nodePath.sep).some((part) => part === "node_modules" || part.startsWith(".")));
|
|
9081
|
+
const stats = await Promise.all(files.map((file) => nodeFs.stat(file).catch(() => null)));
|
|
9082
|
+
files.forEach((file, index) => lines.push(`file ${file} ${stats[index]?.mtimeMs ?? "gone"} ${stats[index]?.size ?? 0}`));
|
|
9083
|
+
}
|
|
9084
|
+
for (const file of new Set(inputs.files)) {
|
|
9085
|
+
const stat = await nodeFs.stat(file).catch(() => null);
|
|
9086
|
+
lines.push(`input ${nodePath.resolve(file)} ${stat ? `${stat.mtimeMs} ${stat.size}` : "missing"}`);
|
|
9087
|
+
}
|
|
9088
|
+
lines.sort();
|
|
9089
|
+
lines.push(`extra ${JSON.stringify(inputs.extra)}`);
|
|
9090
|
+
return createHash("sha256").update(lines.join(`
|
|
9091
|
+
`)).digest("hex");
|
|
9092
|
+
}
|
|
9093
|
+
function restampBuildId(html, renderedWith, current) {
|
|
9094
|
+
if (renderedWith === current)
|
|
9095
|
+
return html;
|
|
9096
|
+
const before = `<meta name="stx-build" content="${renderedWith}">`;
|
|
9097
|
+
const at = html.indexOf(before);
|
|
9098
|
+
if (at === -1)
|
|
9099
|
+
return html;
|
|
9100
|
+
return `${html.slice(0, at)}<meta name="stx-build" content="${current}">${html.slice(at + before.length)}`;
|
|
9101
|
+
}
|
|
9102
|
+
function createRenderCacheStore(options) {
|
|
9103
|
+
let generation = 0;
|
|
9104
|
+
let fingerprint = null;
|
|
9105
|
+
const current = () => fingerprint ??= renderInputsFingerprint(options.inputs());
|
|
9106
|
+
const fileFor = (key) => nodePath.join(options.dir, `${createHash("sha256").update(key).digest("hex")}.json`);
|
|
9107
|
+
return {
|
|
9108
|
+
async begin() {
|
|
9109
|
+
const at = generation;
|
|
9110
|
+
return { generation: at, fingerprint: await current() };
|
|
9111
|
+
},
|
|
9112
|
+
async read(key) {
|
|
9113
|
+
const file = fileFor(key);
|
|
9114
|
+
const stored = await Bun.file(file).json().catch(() => null);
|
|
9115
|
+
if (!stored)
|
|
9116
|
+
return;
|
|
9117
|
+
if (stored.v !== 1 || stored.key !== key || stored.fingerprint !== await current()) {
|
|
9118
|
+
await nodeFs.rm(file, { force: true }).catch(() => {});
|
|
9119
|
+
return;
|
|
9120
|
+
}
|
|
9121
|
+
return {
|
|
9122
|
+
html: restampBuildId(stored.html, stored.buildId, options.buildId()),
|
|
9123
|
+
signature: new Map(stored.signature),
|
|
9124
|
+
status: stored.status,
|
|
9125
|
+
headers: stored.headers
|
|
9126
|
+
};
|
|
9127
|
+
},
|
|
9128
|
+
async write(key, entry, ticket) {
|
|
9129
|
+
if (ticket.generation !== generation)
|
|
9130
|
+
return;
|
|
9131
|
+
const stored = {
|
|
9132
|
+
v: 1,
|
|
9133
|
+
key,
|
|
9134
|
+
fingerprint: ticket.fingerprint,
|
|
9135
|
+
buildId: options.buildId(),
|
|
9136
|
+
html: entry.html,
|
|
9137
|
+
signature: [...entry.signature],
|
|
9138
|
+
status: entry.status,
|
|
9139
|
+
headers: entry.headers
|
|
9140
|
+
};
|
|
9141
|
+
await nodeFs.mkdir(options.dir, { recursive: true });
|
|
9142
|
+
const file = fileFor(key);
|
|
9143
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
9144
|
+
await Bun.write(temporary, JSON.stringify(stored));
|
|
9145
|
+
await nodeFs.rename(temporary, file);
|
|
9146
|
+
},
|
|
9147
|
+
invalidate() {
|
|
9148
|
+
generation++;
|
|
9149
|
+
fingerprint = null;
|
|
9150
|
+
}
|
|
9151
|
+
};
|
|
9152
|
+
}
|
|
9026
9153
|
// src/page-middleware.ts
|
|
9027
9154
|
var DEFAULT_PRIORITY = 10;
|
|
9028
9155
|
function middlewarePriority(middleware) {
|
|
@@ -9262,9 +9389,9 @@ function publicFileExists(requestPath, publicDir) {
|
|
|
9262
9389
|
}
|
|
9263
9390
|
if (decoded.includes("\x00"))
|
|
9264
9391
|
return false;
|
|
9265
|
-
const publicRoot =
|
|
9266
|
-
const resolved =
|
|
9267
|
-
const inside = resolved === publicRoot || resolved.startsWith(`${publicRoot}${
|
|
9392
|
+
const publicRoot = nodePath2.resolve(process20.cwd(), publicDir);
|
|
9393
|
+
const resolved = nodePath2.resolve(publicRoot, `.${decoded}`);
|
|
9394
|
+
const inside = resolved === publicRoot || resolved.startsWith(`${publicRoot}${nodePath2.sep}`);
|
|
9268
9395
|
if (!inside)
|
|
9269
9396
|
return false;
|
|
9270
9397
|
try {
|
|
@@ -9418,7 +9545,7 @@ function boundedCache(limit) {
|
|
|
9418
9545
|
}
|
|
9419
9546
|
function pageExcluder(exclude, cwd = process20.cwd()) {
|
|
9420
9547
|
const canonical = (entry) => {
|
|
9421
|
-
const resolved =
|
|
9548
|
+
const resolved = nodePath2.resolve(cwd, entry);
|
|
9422
9549
|
try {
|
|
9423
9550
|
return realpathSync2(resolved);
|
|
9424
9551
|
} catch {
|
|
@@ -9430,7 +9557,7 @@ function pageExcluder(exclude, cwd = process20.cwd()) {
|
|
|
9430
9557
|
return () => false;
|
|
9431
9558
|
return (file) => {
|
|
9432
9559
|
const resolved = canonical(file);
|
|
9433
|
-
return roots.some((root) => resolved === root || resolved.startsWith(`${root}${
|
|
9560
|
+
return roots.some((root) => resolved === root || resolved.startsWith(`${root}${nodePath2.sep}`));
|
|
9434
9561
|
};
|
|
9435
9562
|
}
|
|
9436
9563
|
function resolveServeOpenUrl(port, openPath = "/") {
|
|
@@ -9665,9 +9792,9 @@ async function serve(options) {
|
|
|
9665
9792
|
}
|
|
9666
9793
|
const stxModule = options.stxModule ? Promise.resolve(options.stxModule) : defaultStxModule;
|
|
9667
9794
|
const imageCacheDir = process20.env.STX_IMAGE_CACHE_DIR?.trim();
|
|
9668
|
-
const imageDeliveryOutputDir = imageCacheDir ?
|
|
9669
|
-
const imagePlaceholderCachePath = imageCacheDir ?
|
|
9670
|
-
const imageDeliveryRoot =
|
|
9795
|
+
const imageDeliveryOutputDir = imageCacheDir ? nodePath2.resolve(imageCacheDir, "image-delivery") : stateDir(process20.cwd(), "image-delivery");
|
|
9796
|
+
const imagePlaceholderCachePath = imageCacheDir ? nodePath2.resolve(imageCacheDir, "image-placeholders.json") : stateDir(process20.cwd(), "image-placeholders.json");
|
|
9797
|
+
const imageDeliveryRoot = nodePath2.join(imageDeliveryOutputDir, "_stx", "images");
|
|
9671
9798
|
let resolvedStxModule = null;
|
|
9672
9799
|
Promise.resolve(stxModule).then((m) => {
|
|
9673
9800
|
resolvedStxModule = m;
|
|
@@ -9694,6 +9821,28 @@ After installing: bun add bun-plugin-stx`);
|
|
|
9694
9821
|
const pendingShellRenders = new Map;
|
|
9695
9822
|
const ENABLE_HTML_CACHE = options.renderCache === true;
|
|
9696
9823
|
const RENDER_CACHE_VARY = options.renderCacheVary ?? "request";
|
|
9824
|
+
const renderCacheStore = ENABLE_HTML_CACHE && RENDER_CACHE_VARY === "source" && options.renderCachePersist === true ? createRenderCacheStore({ dir: stateDir(process20.cwd(), "render-cache"), inputs: () => renderInputs(), buildId: getBuildId }) : null;
|
|
9825
|
+
async function renderTicket(reqCtx) {
|
|
9826
|
+
if (!renderCacheStore || reqCtx && reqCtx.method !== "GET" && reqCtx.method !== "HEAD")
|
|
9827
|
+
return;
|
|
9828
|
+
return renderCacheStore.begin().catch(() => {
|
|
9829
|
+
return;
|
|
9830
|
+
});
|
|
9831
|
+
}
|
|
9832
|
+
async function readRenderCache(key) {
|
|
9833
|
+
const hit = htmlCache.read(key);
|
|
9834
|
+
if (hit || !renderCacheStore)
|
|
9835
|
+
return hit;
|
|
9836
|
+
const kept = await renderCacheStore.read(key).catch(() => {
|
|
9837
|
+
return;
|
|
9838
|
+
});
|
|
9839
|
+
return kept ? htmlCache.remember(key, kept) : undefined;
|
|
9840
|
+
}
|
|
9841
|
+
function rememberRender(key, entry, ticket) {
|
|
9842
|
+
htmlCache.remember(key, entry);
|
|
9843
|
+
if (renderCacheStore && ticket)
|
|
9844
|
+
renderCacheStore.write(key, entry, ticket).catch(() => {});
|
|
9845
|
+
}
|
|
9697
9846
|
const cssCssCache = boundedCache(options.cssCacheLimit ?? 512);
|
|
9698
9847
|
const hmrClients = new Set;
|
|
9699
9848
|
const hmrEncoder = new TextEncoder;
|
|
@@ -9794,6 +9943,7 @@ function __stxOverlay(errs){
|
|
|
9794
9943
|
const watchersStarted = new Map;
|
|
9795
9944
|
const clearRenderCaches = () => {
|
|
9796
9945
|
htmlCache.clear();
|
|
9946
|
+
renderCacheStore?.invalidate();
|
|
9797
9947
|
cssCssCache.clear();
|
|
9798
9948
|
resolvedStxModule?.clearDevCaches?.();
|
|
9799
9949
|
resolvedStxModule?.clearStoreCache?.();
|
|
@@ -9821,12 +9971,12 @@ function __stxOverlay(errs){
|
|
|
9821
9971
|
if (isStxLike || topologyChanged)
|
|
9822
9972
|
sourceFiles = null;
|
|
9823
9973
|
clearRenderCaches();
|
|
9824
|
-
if (layoutsDir && f.endsWith(".stx") &&
|
|
9974
|
+
if (layoutsDir && f.endsWith(".stx") && nodePath2.resolve(f).startsWith(nodePath2.resolve(layoutsDir)))
|
|
9825
9975
|
writeLayoutTypes();
|
|
9826
9976
|
}
|
|
9827
|
-
const resolved =
|
|
9828
|
-
const isStore = resolved.startsWith(
|
|
9829
|
-
const isLayout = !!layoutsDir && resolved.startsWith(
|
|
9977
|
+
const resolved = nodePath2.resolve(dir, f);
|
|
9978
|
+
const isStore = resolved.startsWith(nodePath2.resolve(storesDir)) || composableDirs.some((d) => resolved.startsWith(nodePath2.resolve(d)));
|
|
9979
|
+
const isLayout = !!layoutsDir && resolved.startsWith(nodePath2.resolve(layoutsDir));
|
|
9830
9980
|
if (isCss && !isStxLike)
|
|
9831
9981
|
broadcastHmr({ type: "css", file: f });
|
|
9832
9982
|
else if (isStore)
|
|
@@ -9844,13 +9994,13 @@ function __stxOverlay(errs){
|
|
|
9844
9994
|
const watchPageRoot = (dir) => {
|
|
9845
9995
|
if (production || pageRootWatchersStarted.has(dir))
|
|
9846
9996
|
return;
|
|
9847
|
-
const absolute =
|
|
9848
|
-
const parent =
|
|
9997
|
+
const absolute = nodePath2.resolve(dir);
|
|
9998
|
+
const parent = nodePath2.dirname(absolute);
|
|
9849
9999
|
if (parent === absolute)
|
|
9850
10000
|
return;
|
|
9851
10001
|
try {
|
|
9852
10002
|
const watcher = fsWatch(parent, { recursive: false }, (event, filename) => {
|
|
9853
|
-
if (event !== "rename" || String(filename) !==
|
|
10003
|
+
if (event !== "rename" || String(filename) !== nodePath2.basename(absolute))
|
|
9854
10004
|
return;
|
|
9855
10005
|
watchersStarted.get(dir)?.close();
|
|
9856
10006
|
watchersStarted.delete(dir);
|
|
@@ -9870,9 +10020,9 @@ function __stxOverlay(errs){
|
|
|
9870
10020
|
} catch {}
|
|
9871
10021
|
};
|
|
9872
10022
|
await writeLayoutTypes();
|
|
9873
|
-
const storesDir =
|
|
9874
|
-
const composableDirs = stxConfig.composablesDir ? [
|
|
9875
|
-
|
|
10023
|
+
const storesDir = nodePath2.resolve(stxConfig.root || ".", stxConfig.storesDir || "stores");
|
|
10024
|
+
const composableDirs = stxConfig.composablesDir ? [nodePath2.resolve(stxConfig.root || ".", stxConfig.composablesDir)] : ["composables", "functions"].map((d) => nodePath2.resolve(stxConfig.root || ".", d));
|
|
10025
|
+
const watchedRoots = [
|
|
9876
10026
|
componentsDir,
|
|
9877
10027
|
...additionalComponentsDirs ?? [],
|
|
9878
10028
|
layoutsDir,
|
|
@@ -9882,10 +10032,46 @@ function __stxOverlay(errs){
|
|
|
9882
10032
|
storesDir,
|
|
9883
10033
|
...composableDirs,
|
|
9884
10034
|
...options.watchDirs ?? []
|
|
9885
|
-
])
|
|
9886
|
-
|
|
9887
|
-
continue;
|
|
10035
|
+
].filter((dir) => !!dir);
|
|
10036
|
+
for (const dir of watchedRoots)
|
|
9888
10037
|
startWatcher(dir);
|
|
10038
|
+
function renderInputs() {
|
|
10039
|
+
const cwd = process20.cwd();
|
|
10040
|
+
const configFiles = ["stx", "css"].flatMap((name) => [
|
|
10041
|
+
`${name}.config.ts`,
|
|
10042
|
+
`${name}.config.js`,
|
|
10043
|
+
`.config/${name}.ts`,
|
|
10044
|
+
`config/${name}.ts`
|
|
10045
|
+
]);
|
|
10046
|
+
let stxEntry;
|
|
10047
|
+
try {
|
|
10048
|
+
stxEntry = Bun.resolveSync("@stacksjs/stx", import.meta.dir);
|
|
10049
|
+
} catch {}
|
|
10050
|
+
return {
|
|
10051
|
+
roots: [...watchedRoots, ...patterns.filter((pattern) => !pattern.includes("*"))],
|
|
10052
|
+
files: [
|
|
10053
|
+
...["package.json", "bun.lock", "bun.lockb", "pantry.lock", ".env", ".env.local", ".env.development", ".env.production", ...configFiles].map((file) => nodePath2.join(cwd, file)),
|
|
10054
|
+
import.meta.path,
|
|
10055
|
+
...stxEntry ? [stxEntry] : []
|
|
10056
|
+
],
|
|
10057
|
+
extra: {
|
|
10058
|
+
bun: Bun.version,
|
|
10059
|
+
production,
|
|
10060
|
+
options: {
|
|
10061
|
+
patterns,
|
|
10062
|
+
root: cwd,
|
|
10063
|
+
componentsDir,
|
|
10064
|
+
additionalComponentsDirs,
|
|
10065
|
+
layoutsDir,
|
|
10066
|
+
fallbackLayoutsDir: options.fallbackLayoutsDir,
|
|
10067
|
+
fallbackComponentsDir: options.fallbackComponentsDir,
|
|
10068
|
+
partialsDir,
|
|
10069
|
+
publicDir,
|
|
10070
|
+
watchDirs: options.watchDirs,
|
|
10071
|
+
domains: options.domains
|
|
10072
|
+
}
|
|
10073
|
+
}
|
|
10074
|
+
};
|
|
9889
10075
|
}
|
|
9890
10076
|
const pageMiddlewareByPath = new Map;
|
|
9891
10077
|
const pageMiddlewarePatterns = [];
|
|
@@ -9924,7 +10110,7 @@ function __stxOverlay(errs){
|
|
|
9924
10110
|
}
|
|
9925
10111
|
async function detectPageMiddleware(file) {
|
|
9926
10112
|
try {
|
|
9927
|
-
const src = await
|
|
10113
|
+
const src = await nodeFs2.readFile(file, "utf-8");
|
|
9928
10114
|
const meta = src.match(/definePageMeta\s*\(\s*\{[\s\S]*?\}\s*\)/);
|
|
9929
10115
|
if (!meta)
|
|
9930
10116
|
return [];
|
|
@@ -10164,7 +10350,7 @@ function __stxOverlay(errs){
|
|
|
10164
10350
|
const supportedExtensions = [".stx", ".md", ".html"];
|
|
10165
10351
|
for (const pattern of patterns) {
|
|
10166
10352
|
try {
|
|
10167
|
-
const stat = await
|
|
10353
|
+
const stat = await nodeFs2.stat(pattern).catch(() => null);
|
|
10168
10354
|
if (stat?.isDirectory() || !stat && !pattern.includes("*") && !supportedExtensions.some((ext) => pattern.endsWith(ext)))
|
|
10169
10355
|
watchPageRoot(pattern);
|
|
10170
10356
|
if (stat?.isDirectory()) {
|
|
@@ -10202,7 +10388,7 @@ function __stxOverlay(errs){
|
|
|
10202
10388
|
const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
|
|
10203
10389
|
const router = new Router(process20.cwd(), { pagesDirs, stateDir: stateDirName(), exclude: options.exclude });
|
|
10204
10390
|
if (!options.quiet)
|
|
10205
|
-
console.log(`[stx] Generated ${router.routes.length} routes \u2192 ${
|
|
10391
|
+
console.log(`[stx] Generated ${router.routes.length} routes \u2192 ${nodePath2.join(stateDirName(), "routes.ts")}`);
|
|
10206
10392
|
} catch (e) {}
|
|
10207
10393
|
return kept;
|
|
10208
10394
|
}
|
|
@@ -10213,16 +10399,16 @@ function __stxOverlay(errs){
|
|
|
10213
10399
|
const assetsDir = "./resources/assets";
|
|
10214
10400
|
const targetAssetsDir = stateDir(process20.cwd(), "assets");
|
|
10215
10401
|
try {
|
|
10216
|
-
const assetsExist = await
|
|
10402
|
+
const assetsExist = await nodeFs2.stat(assetsDir).then(() => true).catch(() => false);
|
|
10217
10403
|
if (assetsExist) {
|
|
10218
|
-
await
|
|
10219
|
-
await
|
|
10404
|
+
await nodeFs2.rm(targetAssetsDir, { recursive: true, force: true });
|
|
10405
|
+
await nodeFs2.cp(assetsDir, targetAssetsDir, { recursive: true });
|
|
10220
10406
|
}
|
|
10221
10407
|
} catch {}
|
|
10222
10408
|
}
|
|
10223
10409
|
async function templateSignatureFresh(sig) {
|
|
10224
10410
|
for (const [p, expected] of sig) {
|
|
10225
|
-
const stat = await
|
|
10411
|
+
const stat = await nodeFs2.stat(p).catch(() => null);
|
|
10226
10412
|
if (!stat || stat.mtimeMs !== expected)
|
|
10227
10413
|
return false;
|
|
10228
10414
|
}
|
|
@@ -10231,7 +10417,7 @@ function __stxOverlay(errs){
|
|
|
10231
10417
|
async function buildTemplateSignature(filePath, deps) {
|
|
10232
10418
|
const sig = new Map;
|
|
10233
10419
|
const allPaths = [filePath, ...deps];
|
|
10234
|
-
const stats = await Promise.all(allPaths.map((p) =>
|
|
10420
|
+
const stats = await Promise.all(allPaths.map((p) => nodeFs2.stat(p).catch(() => null)));
|
|
10235
10421
|
for (let i = 0;i < allPaths.length; i++) {
|
|
10236
10422
|
const stat = stats[i];
|
|
10237
10423
|
if (stat)
|
|
@@ -10247,10 +10433,10 @@ function __stxOverlay(errs){
|
|
|
10247
10433
|
return cssModule;
|
|
10248
10434
|
cssLoadAttempted = true;
|
|
10249
10435
|
let dir = process20.cwd();
|
|
10250
|
-
while (dir !==
|
|
10436
|
+
while (dir !== nodePath2.dirname(dir)) {
|
|
10251
10437
|
for (const store of ["node_modules", "pantry"]) {
|
|
10252
10438
|
for (const entry of ENGINE_PACKAGE_ENTRIES) {
|
|
10253
|
-
const candidate =
|
|
10439
|
+
const candidate = nodePath2.join(dir, store, ...entry);
|
|
10254
10440
|
try {
|
|
10255
10441
|
if (!await Bun.file(candidate).exists())
|
|
10256
10442
|
continue;
|
|
@@ -10262,7 +10448,7 @@ function __stxOverlay(errs){
|
|
|
10262
10448
|
} catch {}
|
|
10263
10449
|
}
|
|
10264
10450
|
}
|
|
10265
|
-
dir =
|
|
10451
|
+
dir = nodePath2.dirname(dir);
|
|
10266
10452
|
}
|
|
10267
10453
|
for (const specifier of ENGINE_SPECIFIERS) {
|
|
10268
10454
|
try {
|
|
@@ -10274,7 +10460,7 @@ function __stxOverlay(errs){
|
|
|
10274
10460
|
} catch {}
|
|
10275
10461
|
}
|
|
10276
10462
|
try {
|
|
10277
|
-
const localPath =
|
|
10463
|
+
const localPath = nodePath2.join(process20.env.HOME || "", "Code/Tools/crosswind/packages/toolkit/src/engine/index.ts");
|
|
10278
10464
|
const mod = await import(localPath);
|
|
10279
10465
|
cssModule = { CSSGenerator: mod.CSSGenerator, config: mod.config };
|
|
10280
10466
|
return cssModule;
|
|
@@ -10404,6 +10590,7 @@ function __stxOverlay(errs){
|
|
|
10404
10590
|
reqCtx.responseHeaders = { ...reqCtx.responseHeaders, ...headers };
|
|
10405
10591
|
}
|
|
10406
10592
|
async function processTemplate(filePath, reqCtx) {
|
|
10593
|
+
const ticket = await renderTicket(reqCtx);
|
|
10407
10594
|
const content = await Bun.file(filePath).text();
|
|
10408
10595
|
if (reqCtx)
|
|
10409
10596
|
reqCtx.responseStatus = extractPageResponseStatus(content) ?? 200;
|
|
@@ -10411,7 +10598,7 @@ function __stxOverlay(errs){
|
|
|
10411
10598
|
const isMutating = !!reqCtx && reqCtx.method !== "GET" && reqCtx.method !== "HEAD";
|
|
10412
10599
|
if (ENABLE_HTML_CACHE && !skipCacheHint && !isMutating) {
|
|
10413
10600
|
const cacheKey = htmlCacheKey(filePath, reqCtx);
|
|
10414
|
-
const cachedEntry =
|
|
10601
|
+
const cachedEntry = await readRenderCache(cacheKey);
|
|
10415
10602
|
if (cachedEntry && await templateSignatureFresh(cachedEntry.signature)) {
|
|
10416
10603
|
if (reqCtx) {
|
|
10417
10604
|
reqCtx.responseStatus = cachedEntry.status;
|
|
@@ -10423,7 +10610,7 @@ function __stxOverlay(errs){
|
|
|
10423
10610
|
const { serverScripts, templateContent } = extractServerScriptsFromTemplate(content);
|
|
10424
10611
|
const context = {
|
|
10425
10612
|
__filename: filePath,
|
|
10426
|
-
__dirname:
|
|
10613
|
+
__dirname: nodePath2.dirname(filePath),
|
|
10427
10614
|
__stx_options: stxConfig,
|
|
10428
10615
|
__stx_runtime_head: {}
|
|
10429
10616
|
};
|
|
@@ -10490,7 +10677,7 @@ function __stxOverlay(errs){
|
|
|
10490
10677
|
}
|
|
10491
10678
|
if (ENABLE_HTML_CACHE && !context.__stx_skip_cache && !skipCacheHint && !isMutating && isRenderableCacheCandidate(output) && placeholdersAreReady) {
|
|
10492
10679
|
const signature = await buildTemplateSignature(filePath, dependencies);
|
|
10493
|
-
|
|
10680
|
+
rememberRender(htmlCacheKey(filePath, reqCtx), { html: output, signature, status: reqCtx?.responseStatus ?? 200, headers: reqCtx?.responseHeaders }, ticket);
|
|
10494
10681
|
}
|
|
10495
10682
|
return output;
|
|
10496
10683
|
}
|
|
@@ -10512,7 +10699,7 @@ function __stxOverlay(errs){
|
|
|
10512
10699
|
possibleFiles.push(`${normalizedPath}/index.md`);
|
|
10513
10700
|
possibleFiles.push(`${normalizedPath}/index.html`);
|
|
10514
10701
|
}
|
|
10515
|
-
const filename =
|
|
10702
|
+
const filename = nodePath2.basename(normalizedPath, nodePath2.extname(normalizedPath));
|
|
10516
10703
|
if (filename && !normalizedPath.includes("/")) {
|
|
10517
10704
|
possibleFiles.push(`${filename}.stx`);
|
|
10518
10705
|
possibleFiles.push(`${filename}.md`);
|
|
@@ -10594,6 +10781,7 @@ function __stxOverlay(errs){
|
|
|
10594
10781
|
return null;
|
|
10595
10782
|
}
|
|
10596
10783
|
async function processTemplateDynamic(filePath, paramNames, paramValues, routePath, reqCtx) {
|
|
10784
|
+
const ticket = await renderTicket(reqCtx);
|
|
10597
10785
|
const content = await Bun.file(filePath).text();
|
|
10598
10786
|
if (reqCtx)
|
|
10599
10787
|
reqCtx.responseStatus = extractPageResponseStatus(content) ?? 200;
|
|
@@ -10612,7 +10800,7 @@ function __stxOverlay(errs){
|
|
|
10612
10800
|
if (reqCtx)
|
|
10613
10801
|
reqCtx.params = paramsObj;
|
|
10614
10802
|
if (cacheShell) {
|
|
10615
|
-
const cachedEntry =
|
|
10803
|
+
const cachedEntry = await readRenderCache(filePath);
|
|
10616
10804
|
if (cachedEntry && await templateSignatureFresh(cachedEntry.signature)) {
|
|
10617
10805
|
if (reqCtx) {
|
|
10618
10806
|
reqCtx.responseStatus = cachedEntry.status;
|
|
@@ -10635,7 +10823,7 @@ function __stxOverlay(errs){
|
|
|
10635
10823
|
try {
|
|
10636
10824
|
const context = {
|
|
10637
10825
|
__filename: filePath,
|
|
10638
|
-
__dirname:
|
|
10826
|
+
__dirname: nodePath2.dirname(filePath),
|
|
10639
10827
|
__route: routePath,
|
|
10640
10828
|
__stx_runtime_head: {}
|
|
10641
10829
|
};
|
|
@@ -10707,7 +10895,7 @@ ${output}`;
|
|
|
10707
10895
|
const recordedResponse = readResponseStatus(context) !== undefined || !!readResponseHeaders(context);
|
|
10708
10896
|
if (!context.__stx_skip_cache && !recordedResponse && isRenderableCacheCandidate(output) && placeholdersAreReady) {
|
|
10709
10897
|
const signature = await buildTemplateSignature(filePath, dependencies);
|
|
10710
|
-
|
|
10898
|
+
rememberRender(filePath, { html: output, signature, status: reqCtx?.responseStatus ?? 200, headers: reqCtx?.responseHeaders }, ticket);
|
|
10711
10899
|
}
|
|
10712
10900
|
return fillRouteParams(output, paramsObj);
|
|
10713
10901
|
} finally {
|
|
@@ -10871,7 +11059,7 @@ ${block}`;
|
|
|
10871
11059
|
}
|
|
10872
11060
|
try {
|
|
10873
11061
|
const stx = await stxModule;
|
|
10874
|
-
const publicRoot =
|
|
11062
|
+
const publicRoot = nodePath2.resolve(process20.cwd(), publicDir);
|
|
10875
11063
|
const [placeholderResult, deliveryResult] = await Promise.allSettled([
|
|
10876
11064
|
stx.warmImagePlaceholders(publicRoot, {
|
|
10877
11065
|
cachePath: imagePlaceholderCachePath,
|
|
@@ -11106,8 +11294,8 @@ ${block}`;
|
|
|
11106
11294
|
}
|
|
11107
11295
|
if (!decodedPath || decodedPath.includes("\x00"))
|
|
11108
11296
|
return new Response("Invalid image path", { status: 400 });
|
|
11109
|
-
const resolvedPath =
|
|
11110
|
-
if (!resolvedPath.startsWith(`${imageDeliveryRoot}${
|
|
11297
|
+
const resolvedPath = nodePath2.resolve(imageDeliveryRoot, decodedPath);
|
|
11298
|
+
if (!resolvedPath.startsWith(`${imageDeliveryRoot}${nodePath2.sep}`))
|
|
11111
11299
|
return new Response("Invalid image path", { status: 400 });
|
|
11112
11300
|
try {
|
|
11113
11301
|
const file = Bun.file(resolvedPath);
|
|
@@ -11251,7 +11439,7 @@ ${block}`;
|
|
|
11251
11439
|
}
|
|
11252
11440
|
const body = await req.json();
|
|
11253
11441
|
const { Database } = await import("bun:sqlite");
|
|
11254
|
-
const dbPath =
|
|
11442
|
+
const dbPath = nodePath2.resolve(process20.cwd(), "database/stacks.sqlite");
|
|
11255
11443
|
const db = new Database(dbPath);
|
|
11256
11444
|
try {
|
|
11257
11445
|
const tableInfo = db.query(`PRAGMA table_info("${tableName}")`).all();
|
|
@@ -11544,7 +11732,7 @@ ${trailingPageScripts.join(`
|
|
|
11544
11732
|
return new Response("Invalid asset path", { status: 400 });
|
|
11545
11733
|
for (const assetPath of assetRequestPaths(assetPathname)) {
|
|
11546
11734
|
try {
|
|
11547
|
-
const filePath =
|
|
11735
|
+
const filePath = nodePath2.resolve(process20.cwd(), `.${assetPath}`);
|
|
11548
11736
|
const file = Bun.file(filePath);
|
|
11549
11737
|
if (await file.exists()) {
|
|
11550
11738
|
const ext = getAssetExtension(assetPath);
|
|
@@ -11563,7 +11751,7 @@ ${trailingPageScripts.join(`
|
|
|
11563
11751
|
}
|
|
11564
11752
|
}
|
|
11565
11753
|
if ((req.method === "GET" || req.method === "HEAD") && path !== "/") {
|
|
11566
|
-
const publicRoot =
|
|
11754
|
+
const publicRoot = nodePath2.resolve(process20.cwd(), publicDir);
|
|
11567
11755
|
let safePathname;
|
|
11568
11756
|
try {
|
|
11569
11757
|
safePathname = decodeURIComponent(path);
|
|
@@ -11571,8 +11759,8 @@ ${trailingPageScripts.join(`
|
|
|
11571
11759
|
safePathname = path;
|
|
11572
11760
|
}
|
|
11573
11761
|
if (!safePathname.includes("\x00")) {
|
|
11574
|
-
const resolvedPath =
|
|
11575
|
-
const isInsidePublicRoot = resolvedPath === publicRoot || resolvedPath.startsWith(`${publicRoot}${
|
|
11762
|
+
const resolvedPath = nodePath2.resolve(publicRoot, `.${safePathname}`);
|
|
11763
|
+
const isInsidePublicRoot = resolvedPath === publicRoot || resolvedPath.startsWith(`${publicRoot}${nodePath2.sep}`);
|
|
11576
11764
|
if (isInsidePublicRoot) {
|
|
11577
11765
|
try {
|
|
11578
11766
|
const file = Bun.file(resolvedPath);
|
|
@@ -11853,7 +12041,7 @@ async function projectUsesImageBuiltins(root, scope) {
|
|
|
11853
12041
|
for (const entry of entries) {
|
|
11854
12042
|
if (entry.name.startsWith("."))
|
|
11855
12043
|
continue;
|
|
11856
|
-
const full =
|
|
12044
|
+
const full = nodePath2.join(dir, entry.name);
|
|
11857
12045
|
if (entry.isDirectory()) {
|
|
11858
12046
|
if (!TEMPLATE_SCAN_SKIP.has(entry.name))
|
|
11859
12047
|
pending.push(full);
|
|
@@ -11867,7 +12055,7 @@ async function projectUsesImageBuiltins(root, scope) {
|
|
|
11867
12055
|
} catch {
|
|
11868
12056
|
continue;
|
|
11869
12057
|
}
|
|
11870
|
-
const inComponents =
|
|
12058
|
+
const inComponents = nodePath2.relative(root, dir).split(nodePath2.sep).includes("components");
|
|
11871
12059
|
const component = inComponents ? entry.name.slice(0, -".stx".length) : undefined;
|
|
11872
12060
|
if (!component && IMAGE_BUILTIN_PATTERN.test(source))
|
|
11873
12061
|
return true;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-plugin-stx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.740",
|
|
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",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"typecheck": "bun tsc --noEmit"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@stacksjs/stx": "0.2.
|
|
78
|
-
"stx-router": "0.2.
|
|
77
|
+
"@stacksjs/stx": "0.2.740",
|
|
78
|
+
"stx-router": "0.2.740"
|
|
79
79
|
}
|
|
80
80
|
}
|