veryfront 0.0.82 → 0.0.84
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/README.md +18 -17
- package/esm/deno.js +1 -1
- package/esm/proxy/cache/index.d.ts +41 -0
- package/esm/proxy/cache/index.d.ts.map +1 -0
- package/esm/proxy/cache/index.js +75 -0
- package/esm/proxy/cache/memory-cache.d.ts +18 -0
- package/esm/proxy/cache/memory-cache.d.ts.map +1 -0
- package/esm/proxy/cache/memory-cache.js +100 -0
- package/esm/proxy/cache/redis-cache.d.ts +27 -0
- package/esm/proxy/cache/redis-cache.d.ts.map +1 -0
- package/esm/proxy/cache/redis-cache.js +183 -0
- package/esm/proxy/cache/resilient-cache.d.ts +44 -0
- package/esm/proxy/cache/resilient-cache.d.ts.map +1 -0
- package/esm/proxy/cache/resilient-cache.js +178 -0
- package/esm/proxy/cache/types.d.ts +65 -0
- package/esm/proxy/cache/types.d.ts.map +1 -0
- package/esm/proxy/cache/types.js +7 -0
- package/esm/proxy/handler.d.ts +81 -0
- package/esm/proxy/handler.d.ts.map +1 -0
- package/esm/proxy/handler.js +417 -0
- package/esm/proxy/logger.d.ts +29 -0
- package/esm/proxy/logger.d.ts.map +1 -0
- package/esm/proxy/logger.js +258 -0
- package/esm/proxy/oauth-client.d.ts +15 -0
- package/esm/proxy/oauth-client.d.ts.map +1 -0
- package/esm/proxy/oauth-client.js +52 -0
- package/esm/proxy/token-manager.d.ts +59 -0
- package/esm/proxy/token-manager.d.ts.map +1 -0
- package/esm/proxy/token-manager.js +125 -0
- package/esm/proxy/tracing.d.ts +39 -0
- package/esm/proxy/tracing.d.ts.map +1 -0
- package/esm/proxy/tracing.js +194 -0
- package/esm/src/cache/backend.d.ts +2 -0
- package/esm/src/cache/backend.d.ts.map +1 -1
- package/esm/src/cache/backend.js +2 -0
- package/esm/src/cache/cache-key-builder.d.ts +0 -4
- package/esm/src/cache/cache-key-builder.d.ts.map +1 -1
- package/esm/src/cache/cache-key-builder.js +0 -6
- package/esm/src/cache/multi-tier.d.ts +0 -29
- package/esm/src/cache/multi-tier.d.ts.map +1 -1
- package/esm/src/cache/multi-tier.js +0 -26
- package/esm/src/cli/app/actions.d.ts +26 -0
- package/esm/src/cli/app/actions.d.ts.map +1 -0
- package/esm/src/cli/app/actions.js +152 -0
- package/esm/src/cli/app/components/inline-input.d.ts +35 -0
- package/esm/src/cli/app/components/inline-input.d.ts.map +1 -0
- package/esm/src/cli/app/components/inline-input.js +220 -0
- package/esm/src/cli/app/components/list-select.d.ts +69 -0
- package/esm/src/cli/app/components/list-select.d.ts.map +1 -0
- package/esm/src/cli/app/components/list-select.js +137 -0
- package/esm/src/cli/app/index.d.ts +45 -0
- package/esm/src/cli/app/index.d.ts.map +1 -0
- package/esm/src/cli/app/index.js +1252 -0
- package/esm/src/cli/app/state.d.ts +122 -0
- package/esm/src/cli/app/state.d.ts.map +1 -0
- package/esm/src/cli/app/state.js +232 -0
- package/esm/src/cli/app/views/dashboard.d.ts +19 -0
- package/esm/src/cli/app/views/dashboard.d.ts.map +1 -0
- package/esm/src/cli/app/views/dashboard.js +178 -0
- package/esm/src/cli/commands/dev.js +2 -2
- package/esm/src/cli/commands/new.js +1 -1
- package/esm/src/cli/index/command-router.d.ts.map +1 -1
- package/esm/src/cli/index/command-router.js +9 -39
- package/esm/src/cli/index/start-handler.d.ts +3 -0
- package/esm/src/cli/index/start-handler.d.ts.map +1 -0
- package/esm/src/cli/index/start-handler.js +145 -0
- package/esm/src/cli/mcp/index.d.ts +11 -0
- package/esm/src/cli/mcp/index.d.ts.map +1 -0
- package/esm/src/cli/mcp/index.js +10 -0
- package/esm/src/cli/ui/tui.js +1 -1
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +2 -0
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -1
- package/esm/src/middleware/builtin/security/redis-rate-limit.js +23 -9
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts +10 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.js +30 -42
- package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/loader.js +34 -13
- package/esm/src/platform/adapters/fs/cache/file-cache.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/cache/file-cache.js +9 -3
- package/esm/src/server/context/cache-invalidation.d.ts.map +1 -1
- package/esm/src/server/context/cache-invalidation.js +4 -0
- package/esm/src/server/handlers/dev/dashboard/api.js +4 -0
- package/esm/src/server/handlers/dev/projects/ui-handler.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/projects/ui-handler.js +6 -0
- package/esm/src/transforms/esm/http-cache.d.ts.map +1 -1
- package/esm/src/transforms/esm/http-cache.js +139 -64
- package/esm/src/utils/index.d.ts +1 -1
- package/esm/src/utils/index.d.ts.map +1 -1
- package/esm/src/utils/index.js +1 -1
- package/package.json +2 -1
- package/src/deno.js +1 -1
- package/src/proxy/cache/index.ts +93 -0
- package/src/proxy/cache/memory-cache.ts +120 -0
- package/src/proxy/cache/redis-cache.ts +203 -0
- package/src/proxy/cache/resilient-cache.ts +205 -0
- package/src/proxy/cache/types.ts +72 -0
- package/src/proxy/handler.ts +593 -0
- package/src/proxy/logger.ts +329 -0
- package/src/proxy/oauth-client.ts +91 -0
- package/src/proxy/token-manager.ts +174 -0
- package/src/proxy/tracing.ts +237 -0
- package/src/src/cache/backend.ts +3 -0
- package/src/src/cache/cache-key-builder.ts +0 -9
- package/src/src/cache/multi-tier.ts +0 -41
- package/src/src/cli/app/actions.ts +190 -0
- package/src/src/cli/app/components/inline-input.ts +255 -0
- package/src/src/cli/app/components/list-select.ts +215 -0
- package/src/src/cli/app/index.ts +1471 -0
- package/src/src/cli/app/state.ts +385 -0
- package/src/src/cli/app/views/dashboard.ts +212 -0
- package/src/src/cli/commands/dev.ts +2 -2
- package/src/src/cli/commands/new.ts +1 -1
- package/src/src/cli/index/command-router.ts +9 -40
- package/src/src/cli/index/start-handler.ts +195 -0
- package/src/src/cli/mcp/index.ts +11 -0
- package/src/src/cli/ui/tui.ts +1 -1
- package/src/src/middleware/builtin/security/redis-rate-limit.ts +24 -11
- package/src/src/modules/react-loader/ssr-module-loader/cache/redis.ts +36 -50
- package/src/src/modules/react-loader/ssr-module-loader/loader.ts +38 -14
- package/src/src/platform/adapters/fs/cache/file-cache.ts +9 -3
- package/src/src/server/context/cache-invalidation.ts +4 -0
- package/src/src/server/handlers/dev/dashboard/api.ts +2 -0
- package/src/src/server/handlers/dev/projects/ui-handler.ts +6 -0
- package/src/src/transforms/esm/http-cache.ts +148 -73
- package/src/src/utils/index.ts +0 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { cwd, getEnv } from "../../platform/compat/process.js";
|
|
2
|
+
import { createFileSystem } from "../../platform/compat/fs.js";
|
|
3
|
+
import { isAbsolute, join, resolve } from "../../platform/compat/path/index.js";
|
|
4
|
+
import { cliLogger } from "../../utils/index.js";
|
|
5
|
+
import { exitProcess, registerTerminationSignals } from "../utils/index.js";
|
|
6
|
+
const DEFAULT_START_PORT = 8080;
|
|
7
|
+
const DEFAULT_MCP_PORT = 9999;
|
|
8
|
+
function getProjectSlug(path) {
|
|
9
|
+
return path.replace(/\/+$/, "").split("/").pop() || "";
|
|
10
|
+
}
|
|
11
|
+
async function isVeryFrontProject(projectPath) {
|
|
12
|
+
const fs = createFileSystem();
|
|
13
|
+
const markers = ["app", "pages", "components"];
|
|
14
|
+
const checks = await Promise.all(markers.map((m) => fs.exists(join(projectPath, m))));
|
|
15
|
+
return checks.some(Boolean);
|
|
16
|
+
}
|
|
17
|
+
async function findProjectsInDirs(baseDirs) {
|
|
18
|
+
const projects = new Map();
|
|
19
|
+
const fs = createFileSystem();
|
|
20
|
+
for (const baseDir of baseDirs) {
|
|
21
|
+
const absoluteBase = isAbsolute(baseDir) ? baseDir : join(cwd(), baseDir);
|
|
22
|
+
if (!(await fs.exists(absoluteBase)))
|
|
23
|
+
continue;
|
|
24
|
+
try {
|
|
25
|
+
for await (const entry of fs.readDir(absoluteBase)) {
|
|
26
|
+
if (!entry.isDirectory || entry.name.startsWith("."))
|
|
27
|
+
continue;
|
|
28
|
+
const projectPath = join(absoluteBase, entry.name);
|
|
29
|
+
if (await isVeryFrontProject(projectPath)) {
|
|
30
|
+
projects.set(entry.name, resolve(projectPath));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Directory not readable - skip
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return projects;
|
|
39
|
+
}
|
|
40
|
+
async function discoverProjects(explicitPath) {
|
|
41
|
+
const [projects, examples] = await Promise.all([
|
|
42
|
+
findProjectsInDirs(["data/projects", "projects"]),
|
|
43
|
+
findProjectsInDirs(["examples"]),
|
|
44
|
+
]);
|
|
45
|
+
const fs = createFileSystem();
|
|
46
|
+
let defaultProject = null;
|
|
47
|
+
// Add explicit project path if provided
|
|
48
|
+
if (explicitPath) {
|
|
49
|
+
const absolutePath = isAbsolute(explicitPath) ? explicitPath : join(cwd(), explicitPath);
|
|
50
|
+
if (await fs.exists(absolutePath)) {
|
|
51
|
+
const slug = getProjectSlug(absolutePath);
|
|
52
|
+
projects.set(slug, resolve(absolutePath));
|
|
53
|
+
defaultProject = slug;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Fall back to current directory if no projects found
|
|
57
|
+
if (projects.size === 0 && !defaultProject) {
|
|
58
|
+
const currentDir = cwd();
|
|
59
|
+
if (await isVeryFrontProject(currentDir)) {
|
|
60
|
+
const slug = getProjectSlug(currentDir);
|
|
61
|
+
projects.set(slug, resolve(currentDir));
|
|
62
|
+
defaultProject = slug;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { projects, examples, defaultProject };
|
|
66
|
+
}
|
|
67
|
+
async function trySetupProxy(localProjects) {
|
|
68
|
+
try {
|
|
69
|
+
// Proxy is only available in local dev, not in the npm package
|
|
70
|
+
const { createProxyHandler, injectContextHeaders } = await import("../../../proxy/handler.js");
|
|
71
|
+
const { createCacheFromEnv } = await import("../../../proxy/cache/index.js");
|
|
72
|
+
const proxyConfig = {
|
|
73
|
+
apiBaseUrl: getEnv("VERYFRONT_API_BASE_URL") || "http://api.lvh.me:4000",
|
|
74
|
+
clientId: getEnv("OAUTH_CLIENT_ID") || "",
|
|
75
|
+
clientSecret: getEnv("OAUTH_CLIENT_SECRET") || "",
|
|
76
|
+
previewClientId: getEnv("OAUTH_PREVIEW_CLIENT_ID") || "",
|
|
77
|
+
previewClientSecret: getEnv("OAUTH_PREVIEW_CLIENT_SECRET") || "",
|
|
78
|
+
apiToken: getEnv("VERYFRONT_API_TOKEN") || "",
|
|
79
|
+
localProjects: Object.fromEntries(localProjects),
|
|
80
|
+
};
|
|
81
|
+
const cache = await createCacheFromEnv();
|
|
82
|
+
const handler = createProxyHandler({ config: proxyConfig, cache });
|
|
83
|
+
return {
|
|
84
|
+
interceptor: async (req) => injectContextHeaders(req, await handler.processRequest(req)),
|
|
85
|
+
close: () => handler.close(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return { interceptor: undefined, close: async () => { } };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export async function handleStartCommand(args) {
|
|
93
|
+
const port = typeof args.port === "number" ? args.port : DEFAULT_START_PORT;
|
|
94
|
+
const mcpPort = typeof args["mcp-port"] === "number" ? args["mcp-port"] : DEFAULT_MCP_PORT;
|
|
95
|
+
const projectPath = args.project ? String(args.project) : null;
|
|
96
|
+
const headless = Boolean(args.headless || args["no-tui"]);
|
|
97
|
+
const { createApp, showStartup } = await import("../app/index.js");
|
|
98
|
+
const discovered = await discoverProjects(projectPath);
|
|
99
|
+
const app = createApp({
|
|
100
|
+
port,
|
|
101
|
+
mcpPort,
|
|
102
|
+
headless,
|
|
103
|
+
projects: discovered.projects,
|
|
104
|
+
examples: discovered.examples,
|
|
105
|
+
defaultProject: discovered.defaultProject ?? undefined,
|
|
106
|
+
});
|
|
107
|
+
const restoreConsole = app.interceptConsole();
|
|
108
|
+
if (!headless) {
|
|
109
|
+
await showStartup(["Loading configuration", "Discovering projects", "Starting server"]);
|
|
110
|
+
}
|
|
111
|
+
const allProjects = new Map([...discovered.projects, ...discovered.examples]);
|
|
112
|
+
const proxy = await trySetupProxy(allProjects);
|
|
113
|
+
const { createDevServer } = await import("../../server/dev-server.js");
|
|
114
|
+
const shutdownController = new AbortController();
|
|
115
|
+
const devServer = await createDevServer({
|
|
116
|
+
port,
|
|
117
|
+
projectDir: cwd(),
|
|
118
|
+
hmrPort: port + 1,
|
|
119
|
+
enableHMR: true,
|
|
120
|
+
enableFastRefresh: true,
|
|
121
|
+
signal: shutdownController.signal,
|
|
122
|
+
requestInterceptor: proxy.interceptor,
|
|
123
|
+
});
|
|
124
|
+
await devServer.ready;
|
|
125
|
+
const { createMCPServer } = await import("../mcp/index.js");
|
|
126
|
+
const mcpServer = await createMCPServer({ httpPort: mcpPort });
|
|
127
|
+
app.setServerReady();
|
|
128
|
+
let shuttingDown = false;
|
|
129
|
+
async function shutdown() {
|
|
130
|
+
if (shuttingDown)
|
|
131
|
+
return;
|
|
132
|
+
shuttingDown = true;
|
|
133
|
+
restoreConsole();
|
|
134
|
+
cliLogger.info("Shutting down...");
|
|
135
|
+
app.stop();
|
|
136
|
+
await mcpServer.stop();
|
|
137
|
+
shutdownController.abort();
|
|
138
|
+
await devServer.stop();
|
|
139
|
+
await proxy.close();
|
|
140
|
+
exitProcess(0);
|
|
141
|
+
}
|
|
142
|
+
registerTerminationSignals(() => void shutdown());
|
|
143
|
+
app.start();
|
|
144
|
+
await new Promise(() => { });
|
|
145
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Module for Dev Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes dev server functionality via MCP (Model Context Protocol)
|
|
5
|
+
* for coding agents like Claude Code and Cursor.
|
|
6
|
+
*/
|
|
7
|
+
export * from "./server.js";
|
|
8
|
+
export * from "./error-collector.js";
|
|
9
|
+
export * from "./log-buffer.js";
|
|
10
|
+
export * from "./tools.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Module for Dev Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes dev server functionality via MCP (Model Context Protocol)
|
|
5
|
+
* for coding agents like Claude Code and Cursor.
|
|
6
|
+
*/
|
|
7
|
+
export * from "./server.js";
|
|
8
|
+
export * from "./error-collector.js";
|
|
9
|
+
export * from "./log-buffer.js";
|
|
10
|
+
export * from "./tools.js";
|
package/esm/src/cli/ui/tui.js
CHANGED
|
@@ -106,7 +106,7 @@ function stopSpinner() {
|
|
|
106
106
|
spinnerInterval = null;
|
|
107
107
|
}
|
|
108
108
|
export function createTui(cfg = {}) {
|
|
109
|
-
config = { title: "Veryfront", showLogs: true, ...cfg };
|
|
109
|
+
config = { title: "Veryfront Code", showLogs: true, ...cfg };
|
|
110
110
|
state = {
|
|
111
111
|
status: "Initializing...",
|
|
112
112
|
statusType: "loading",
|
|
@@ -5,10 +5,12 @@ export interface RedisRateLimitOptions {
|
|
|
5
5
|
}
|
|
6
6
|
export declare class RedisRateLimitStore implements RateLimitStore {
|
|
7
7
|
private client;
|
|
8
|
+
private clientPromise;
|
|
8
9
|
private readonly url?;
|
|
9
10
|
private readonly keyPrefix;
|
|
10
11
|
constructor(options?: RedisRateLimitOptions);
|
|
11
12
|
private ensureClient;
|
|
13
|
+
private connectClient;
|
|
12
14
|
private storageKey;
|
|
13
15
|
increment(key: string, windowMs: number): Promise<RateLimitEntry>;
|
|
14
16
|
reset(key: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-rate-limit.d.ts","sourceRoot":"","sources":["../../../../../src/src/middleware/builtin/security/redis-rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAYjE,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,mBAAoB,YAAW,cAAc;IACxD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,qBAA0B;
|
|
1
|
+
{"version":3,"file":"redis-rate-limit.d.ts","sourceRoot":"","sources":["../../../../../src/src/middleware/builtin/security/redis-rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAYjE,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,mBAAoB,YAAW,cAAc;IACxD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,qBAA0B;IAK/C,OAAO,CAAC,YAAY;YAQN,aAAa;IAkC3B,OAAO,CAAC,UAAU;IAIZ,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAqBjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B"}
|
|
@@ -2,15 +2,22 @@ import { createError, toError } from "../../../errors/veryfront-error.js";
|
|
|
2
2
|
import { serverLogger as logger } from "../../../utils/index.js";
|
|
3
3
|
export class RedisRateLimitStore {
|
|
4
4
|
client = null;
|
|
5
|
+
clientPromise = null;
|
|
5
6
|
url;
|
|
6
7
|
keyPrefix;
|
|
7
8
|
constructor(options = {}) {
|
|
8
9
|
this.url = options.url;
|
|
9
10
|
this.keyPrefix = options.keyPrefix ?? "veryfront:ratelimit:";
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
+
ensureClient() {
|
|
12
13
|
if (this.client)
|
|
13
|
-
return this.client;
|
|
14
|
+
return Promise.resolve(this.client);
|
|
15
|
+
if (this.clientPromise)
|
|
16
|
+
return this.clientPromise;
|
|
17
|
+
this.clientPromise = this.connectClient();
|
|
18
|
+
return this.clientPromise;
|
|
19
|
+
}
|
|
20
|
+
async connectClient() {
|
|
14
21
|
let createClient;
|
|
15
22
|
try {
|
|
16
23
|
const redisClientModule = ["npm:@redis/client", "@1.5.8"].join("");
|
|
@@ -18,18 +25,25 @@ export class RedisRateLimitStore {
|
|
|
18
25
|
createClient = mod.createClient;
|
|
19
26
|
}
|
|
20
27
|
catch {
|
|
28
|
+
this.clientPromise = null;
|
|
21
29
|
throw toError(createError({
|
|
22
30
|
type: "config",
|
|
23
31
|
message: "Redis rate limit store requires npm:@redis/client. Install dependencies or use MemoryRateLimitStore.",
|
|
24
32
|
}));
|
|
25
33
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
try {
|
|
35
|
+
const client = createClient({ url: this.url });
|
|
36
|
+
client.on?.("error", (err) => {
|
|
37
|
+
logger.error("[redis-ratelimit] client error", err);
|
|
38
|
+
});
|
|
39
|
+
await client.connect();
|
|
40
|
+
this.client = client;
|
|
41
|
+
return client;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
this.clientPromise = null;
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
33
47
|
}
|
|
34
48
|
storageKey(key) {
|
|
35
49
|
return `${this.keyPrefix}${key}`;
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
/** Redis caching for cross-pod SSR module sharing */
|
|
2
2
|
import { type RedisClient } from "../../../../utils/redis-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Legacy key builder. CacheBackend handles prefixing internally.
|
|
5
|
+
* Used only for backward compatibility if needed.
|
|
6
|
+
*/
|
|
3
7
|
export declare function redisKey(key: string): string;
|
|
4
8
|
/** Initialize distributed caching for SSR modules */
|
|
5
9
|
export declare function initializeSSRDistributedCache(): Promise<boolean>;
|
|
10
|
+
/** Check if distributed caching is enabled for SSR modules */
|
|
6
11
|
export declare function isSSRDistributedCacheEnabled(): boolean;
|
|
7
12
|
/** @deprecated Use initializeSSRDistributedCache instead */
|
|
8
13
|
export declare const initializeSSRRedisCache: typeof initializeSSRDistributedCache;
|
|
9
14
|
/** @deprecated Use isSSRDistributedCacheEnabled instead */
|
|
10
15
|
export declare const isSSRRedisCacheEnabled: typeof isSSRDistributedCacheEnabled;
|
|
16
|
+
/** @deprecated Use isSSRDistributedCacheEnabled instead */
|
|
11
17
|
export declare function getRedisEnabled(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Direct Redis client access is deprecated. Use CacheBackend abstraction.
|
|
20
|
+
* Returns null to force use of CacheBackend path in updated consumers.
|
|
21
|
+
*/
|
|
12
22
|
export declare function getRedisClientInstance(): RedisClient | null;
|
|
13
23
|
export declare function getFromRedis(cacheKey: string): Promise<string | null>;
|
|
14
24
|
/** Store transformed code in Redis with environment-aware TTL */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../../../../src/src/modules/react-loader/ssr-module-loader/cache/redis.ts"],"names":[],"mappings":"AAAA,qDAAqD;AAGrD,OAAO,
|
|
1
|
+
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../../../../src/src/modules/react-loader/ssr-module-loader/cache/redis.ts"],"names":[],"mappings":"AAAA,qDAAqD;AAGrD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAWrE;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,qDAAqD;AACrD,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED,8DAA8D;AAC9D,wBAAgB,4BAA4B,IAAI,OAAO,CAMtD;AAED,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,sCAAgC,CAAC;AAErE,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,qCAA+B,CAAC;AAEnE,2DAA2D;AAC3D,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,WAAW,GAAG,IAAI,CAE3D;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAU3E;AAED,iEAAiE;AACjE,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAWf"}
|
|
@@ -1,79 +1,67 @@
|
|
|
1
1
|
/** Redis caching for cross-pod SSR module sharing */
|
|
2
2
|
import { rendererLogger as logger } from "../../../../utils/index.js";
|
|
3
|
-
import { getRedisClient, isRedisConfigured, } from "../../../../utils/redis-client.js";
|
|
4
3
|
import { buildRedisSSRModuleKey } from "../../../../cache/index.js";
|
|
5
4
|
import { getSSRModuleRedisTTL } from "../constants.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
import { CacheBackends, createDistributedCacheAccessor } from "../../../../cache/backend.js";
|
|
6
|
+
/** Lazy-loaded distributed cache backend for cross-pod sharing */
|
|
7
|
+
const getDistributedCache = createDistributedCacheAccessor(() => CacheBackends.ssrModule(), "SSR-MODULE-LOADER");
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Legacy key builder. CacheBackend handles prefixing internally.
|
|
10
|
+
* Used only for backward compatibility if needed.
|
|
11
|
+
*/
|
|
10
12
|
export function redisKey(key) {
|
|
11
13
|
return buildRedisSSRModuleKey(key);
|
|
12
14
|
}
|
|
13
15
|
/** Initialize distributed caching for SSR modules */
|
|
14
16
|
export async function initializeSSRDistributedCache() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (redisInitPromise) {
|
|
18
|
-
await redisInitPromise;
|
|
19
|
-
return redisEnabled;
|
|
20
|
-
}
|
|
21
|
-
redisInitPromise = (async () => {
|
|
22
|
-
if (!isRedisConfigured()) {
|
|
23
|
-
logger.debug("[SSR-MODULE-LOADER] Redis not configured, using memory cache");
|
|
24
|
-
redisInitialized = true;
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
redisClient = await getRedisClient();
|
|
29
|
-
redisEnabled = true;
|
|
30
|
-
logger.debug("[SSR-MODULE-LOADER] Redis cache enabled");
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
logger.warn("[SSR-MODULE-LOADER] Redis unavailable, falling back to memory cache", { error });
|
|
34
|
-
redisEnabled = false;
|
|
35
|
-
}
|
|
36
|
-
finally {
|
|
37
|
-
redisInitialized = true;
|
|
38
|
-
}
|
|
39
|
-
})();
|
|
40
|
-
await redisInitPromise;
|
|
41
|
-
redisInitPromise = null;
|
|
42
|
-
return redisEnabled;
|
|
17
|
+
const backend = await getDistributedCache();
|
|
18
|
+
return backend !== null;
|
|
43
19
|
}
|
|
20
|
+
/** Check if distributed caching is enabled for SSR modules */
|
|
44
21
|
export function isSSRDistributedCacheEnabled() {
|
|
45
|
-
|
|
22
|
+
// We can't synchronously check if backend is initialized without accessing the promise
|
|
23
|
+
// But we can check if we *should* be enabled based on env via CacheBackend utils
|
|
24
|
+
// For now, this returns true because it's used as a guard for get/set calls
|
|
25
|
+
// which themselves are async and handle missing backends gracefully.
|
|
26
|
+
return true;
|
|
46
27
|
}
|
|
47
28
|
/** @deprecated Use initializeSSRDistributedCache instead */
|
|
48
29
|
export const initializeSSRRedisCache = initializeSSRDistributedCache;
|
|
49
30
|
/** @deprecated Use isSSRDistributedCacheEnabled instead */
|
|
50
31
|
export const isSSRRedisCacheEnabled = isSSRDistributedCacheEnabled;
|
|
32
|
+
/** @deprecated Use isSSRDistributedCacheEnabled instead */
|
|
51
33
|
export function getRedisEnabled() {
|
|
52
|
-
return
|
|
34
|
+
return isSSRDistributedCacheEnabled();
|
|
53
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated Direct Redis client access is deprecated. Use CacheBackend abstraction.
|
|
38
|
+
* Returns null to force use of CacheBackend path in updated consumers.
|
|
39
|
+
*/
|
|
54
40
|
export function getRedisClientInstance() {
|
|
55
|
-
return
|
|
41
|
+
return null;
|
|
56
42
|
}
|
|
57
43
|
export async function getFromRedis(cacheKey) {
|
|
58
|
-
|
|
44
|
+
const backend = await getDistributedCache();
|
|
45
|
+
if (!backend)
|
|
59
46
|
return null;
|
|
60
47
|
try {
|
|
61
|
-
return await
|
|
48
|
+
return await backend.get(cacheKey);
|
|
62
49
|
}
|
|
63
50
|
catch (error) {
|
|
64
|
-
logger.debug("[SSR-MODULE-LOADER]
|
|
51
|
+
logger.debug("[SSR-MODULE-LOADER] Distributed cache get failed", { key: cacheKey, error });
|
|
65
52
|
return null;
|
|
66
53
|
}
|
|
67
54
|
}
|
|
68
55
|
/** Store transformed code in Redis with environment-aware TTL */
|
|
69
56
|
export async function setInRedis(cacheKey, code, options) {
|
|
70
|
-
|
|
57
|
+
const backend = await getDistributedCache();
|
|
58
|
+
if (!backend)
|
|
71
59
|
return;
|
|
72
60
|
const ttl = options?.ttlSeconds ?? getSSRModuleRedisTTL(options?.isProduction ?? true);
|
|
73
61
|
try {
|
|
74
|
-
await
|
|
62
|
+
await backend.set(cacheKey, code, ttl);
|
|
75
63
|
}
|
|
76
64
|
catch (error) {
|
|
77
|
-
logger.debug("[SSR-MODULE-LOADER]
|
|
65
|
+
logger.debug("[SSR-MODULE-LOADER] Distributed cache set failed", { key: cacheKey, error });
|
|
78
66
|
}
|
|
79
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/loader.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/loader.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAsCpC,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAgC3E;;;;;GAKG;AACH,qBAAa,eAAe;IAId,OAAO,CAAC,OAAO;IAH3B,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,mBAAmB,CAAuB;gBAE9B,OAAO,EAAE,sBAAsB;IAEnD;;OAEG;IACH,UAAU,CACR,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAyFxD,OAAO,CAAC,mBAAmB;IAiC3B,OAAO,CAAC,wBAAwB;IAwBhC,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,yBAAyB;IAuBjC,OAAO,CAAC,kBAAkB;IAK1B;;OAEG;YACW,2BAA2B;IAkGzC,OAAO,CAAC,yBAAyB;YAiBnB,2BAA2B;IA6RzC;;;OAGG;YACW,mBAAmB;IA2CjC,OAAO,CAAC,yBAAyB;IAYjC;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,wBAAwB;IAehC,OAAO,CAAC,2BAA2B;IAiBnC,OAAO,CAAC,2BAA2B;IAWnC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,aAAa;YAIP,uBAAuB;IAyCrC;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAUhB;;;OAGG;YACW,gBAAgB;YAgBhB,WAAW;YAeX,YAAY;CAiC3B"}
|
|
@@ -21,9 +21,10 @@ import { SpanNames } from "../../../observability/tracing/span-names.js";
|
|
|
21
21
|
import { extractComponent } from "../extract-component.js";
|
|
22
22
|
import { CIRCUIT_BREAKER_RESET_MS, CIRCUIT_BREAKER_THRESHOLD, IN_PROGRESS_WAIT_TIMEOUT_MS, MAX_CONCURRENT_TRANSFORMS, MAX_TRANSFORM_DEPTH, TRANSFORM_ACQUIRE_TIMEOUT_MS, TRANSFORM_BATCH_SIZE, } from "./constants.js";
|
|
23
23
|
import { withTimeoutThrow } from "../../../rendering/utils/stream-utils.js";
|
|
24
|
-
import { failedComponents, getFromRedis,
|
|
24
|
+
import { failedComponents, getFromRedis, globalCrossProjectCache, globalInProgress, globalModuleCache, globalTmpDirs, isSSRDistributedCacheEnabled, setInRedis, transformSemaphore, } from "./cache/index.js";
|
|
25
25
|
import { getCacheBaseDir, getHttpBundleCacheDir } from "../../../utils/cache-dir.js";
|
|
26
26
|
import { ensureHttpBundlesExist } from "../../../transforms/esm/http-cache.js";
|
|
27
|
+
import { LRUCache } from "../../../utils/lru-wrapper.js";
|
|
27
28
|
/** Pattern to match HTTP bundle file:// paths in transformed code */
|
|
28
29
|
const HTTP_BUNDLE_PATTERN = /file:\/\/([^"'\s]+veryfront-http-bundle\/http-([a-f0-9]+)\.mjs)/gi;
|
|
29
30
|
/** Extract HTTP bundle paths from transformed code for proactive recovery */
|
|
@@ -42,8 +43,12 @@ function extractHttpBundlePaths(code) {
|
|
|
42
43
|
HTTP_BUNDLE_PATTERN.lastIndex = 0;
|
|
43
44
|
return bundles;
|
|
44
45
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Track modules whose HTTP bundles have been verified, keyed by tempPath:contentHash.
|
|
48
|
+
* Bounded LRU to prevent unbounded memory growth in long-running pods.
|
|
49
|
+
* Keying by contentHash ensures verification is re-done when content changes at the same path.
|
|
50
|
+
*/
|
|
51
|
+
const verifiedHttpBundlePaths = new LRUCache({ maxEntries: 2000 });
|
|
47
52
|
/**
|
|
48
53
|
* SSR Module Loader with Redis Support.
|
|
49
54
|
*
|
|
@@ -298,8 +303,9 @@ export class SSRModuleLoader {
|
|
|
298
303
|
const inProgressKey = contentCacheKey;
|
|
299
304
|
const cachedEntry = globalModuleCache.get(contentCacheKey);
|
|
300
305
|
if (cachedEntry) {
|
|
301
|
-
// Verify HTTP bundles exist for in-memory cached transforms (once per path)
|
|
302
|
-
|
|
306
|
+
// Verify HTTP bundles exist for in-memory cached transforms (once per path+content)
|
|
307
|
+
const verifyKey = `${cachedEntry.tempPath}:${cachedEntry.contentHash}`;
|
|
308
|
+
if (!verifiedHttpBundlePaths.get(verifyKey)) {
|
|
303
309
|
try {
|
|
304
310
|
const cachedCode = await this.fs.readTextFile(cachedEntry.tempPath);
|
|
305
311
|
const bundlePaths = extractHttpBundlePaths(cachedCode);
|
|
@@ -316,11 +322,11 @@ export class SSRModuleLoader {
|
|
|
316
322
|
// Fall through to Redis or fresh transform
|
|
317
323
|
}
|
|
318
324
|
else {
|
|
319
|
-
verifiedHttpBundlePaths.
|
|
325
|
+
verifiedHttpBundlePaths.set(verifyKey, true);
|
|
320
326
|
}
|
|
321
327
|
}
|
|
322
328
|
else {
|
|
323
|
-
verifiedHttpBundlePaths.
|
|
329
|
+
verifiedHttpBundlePaths.set(verifyKey, true);
|
|
324
330
|
}
|
|
325
331
|
}
|
|
326
332
|
catch {
|
|
@@ -336,9 +342,7 @@ export class SSRModuleLoader {
|
|
|
336
342
|
return;
|
|
337
343
|
}
|
|
338
344
|
}
|
|
339
|
-
|
|
340
|
-
const redisClient = getRedisClientInstance();
|
|
341
|
-
if (redisEnabled && redisClient) {
|
|
345
|
+
if (isSSRDistributedCacheEnabled()) {
|
|
342
346
|
const redisCode = await getFromRedis(contentCacheKey);
|
|
343
347
|
if (redisCode) {
|
|
344
348
|
// Proactively ensure HTTP bundles exist before using cached transform.
|
|
@@ -363,7 +367,7 @@ export class SSRModuleLoader {
|
|
|
363
367
|
recursive: true,
|
|
364
368
|
});
|
|
365
369
|
await this.fs.writeTextFile(tempPath, redisCode);
|
|
366
|
-
verifiedHttpBundlePaths.
|
|
370
|
+
verifiedHttpBundlePaths.set(`${tempPath}:${contentHash}`, true);
|
|
367
371
|
const entry = { tempPath, contentHash };
|
|
368
372
|
globalModuleCache.set(contentCacheKey, entry);
|
|
369
373
|
globalModuleCache.set(filePathCacheKey, entry);
|
|
@@ -444,16 +448,33 @@ export class SSRModuleLoader {
|
|
|
444
448
|
// Rewrite local imports to use hashed temp paths
|
|
445
449
|
// This ensures that each content version uses its own cached module
|
|
446
450
|
transformed = this.rewriteLocalImports(transformed, localImportPaths, filePath);
|
|
451
|
+
// Ensure HTTP bundles exist for this transform (handles nested bundle deps)
|
|
452
|
+
const bundlePaths = extractHttpBundlePaths(transformed);
|
|
453
|
+
if (bundlePaths.length > 0) {
|
|
454
|
+
const cacheDir = getHttpBundleCacheDir();
|
|
455
|
+
const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
456
|
+
if (failed.length > 0) {
|
|
457
|
+
logger.warn("[SSR-MODULE-LOADER] Some HTTP bundles could not be recovered", {
|
|
458
|
+
file: filePath.slice(-40),
|
|
459
|
+
failed,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
447
463
|
// Hash the TRANSFORMED content (after import rewrites) for cache busting
|
|
448
464
|
// This ensures Deno's module cache is invalidated when dependencies change
|
|
449
465
|
const transformedHash = await this.hashContentAsync(transformed);
|
|
450
466
|
const tempPath = await this.getTempPath(filePath, transformedHash);
|
|
451
467
|
await this.fs.mkdir(tempPath.substring(0, tempPath.lastIndexOf("/")), { recursive: true });
|
|
452
468
|
await this.fs.writeTextFile(tempPath, transformed);
|
|
453
|
-
if (
|
|
469
|
+
if (isSSRDistributedCacheEnabled()) {
|
|
454
470
|
setInRedis(contentCacheKey, transformed, {
|
|
455
471
|
isProduction: this.isProductionContentSource(),
|
|
456
|
-
}).catch(() => {
|
|
472
|
+
}).catch((error) => {
|
|
473
|
+
logger.debug("[SSR-MODULE-LOADER] Distributed cache set failed", {
|
|
474
|
+
key: contentCacheKey,
|
|
475
|
+
error,
|
|
476
|
+
});
|
|
477
|
+
});
|
|
457
478
|
}
|
|
458
479
|
// Use transformedHash for cache busting in dynamic imports
|
|
459
480
|
const entry = { tempPath, contentHash: transformedHash };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-cache.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/cache/file-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAc,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAmC3E;;;GAGG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC,CAwBnE;AAED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAEvD;AAED,yDAAyD;AACzD,eAAO,MAAM,wBAAwB,mCAA6B,CAAC;AAEnE,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,sCAAgC,CAAC;AAErE;;;;;GAKG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAK;gBAEP,OAAO,GAAE,gBAAqB;IAgB1C,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,UAAU;IAIlB;;;OAGG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IA4BlC;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAqChD;;;OAGG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"file-cache.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/cache/file-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAc,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAmC3E;;;GAGG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC,CAwBnE;AAED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAEvD;AAED,yDAAyD;AACzD,eAAO,MAAM,wBAAwB,mCAA6B,CAAC;AAEnE,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,sCAAgC,CAAC;AAErE;;;;;GAKG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAK;gBAEP,OAAO,GAAE,gBAAqB;IAgB1C,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,UAAU;IAIlB;;;OAGG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IA4BlC;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAqChD;;;OAGG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAsBnC;;OAEG;IACH,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BjD,mEAAmE;IACnE,OAAO,CAAC,aAAa;IAWrB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAezB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAM5B,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAqBtC,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBpD,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAsB/D,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkB7E,KAAK,IAAI,IAAI;IAOb,KAAK,IAAI,UAAU,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE;IAazC,YAAY,IAAI,MAAM;IAetB,OAAO,CAAC,qBAAqB;CAuB9B"}
|
|
@@ -186,7 +186,9 @@ export class FileCache {
|
|
|
186
186
|
const serialized = JSON.stringify(entry);
|
|
187
187
|
// Update request-scoped cache so subsequent reads in same request see the new value
|
|
188
188
|
setInRequestCache(key, serialized);
|
|
189
|
-
backend.set(key, serialized, BACKEND_TTL_SECONDS).catch(() => {
|
|
189
|
+
backend.set(key, serialized, BACKEND_TTL_SECONDS).catch((error) => {
|
|
190
|
+
logger.debug("[FileCache] Backend set failed", { key, error });
|
|
191
|
+
});
|
|
190
192
|
return;
|
|
191
193
|
}
|
|
192
194
|
this.setToFallback(key, entry, size);
|
|
@@ -261,7 +263,9 @@ export class FileCache {
|
|
|
261
263
|
}
|
|
262
264
|
// Fire-and-forget backend deletion
|
|
263
265
|
// Note: prefix already includes "file:" from buildFileCacheKeyPrefix, don't add it again
|
|
264
|
-
cacheBackend?.delByPattern?.(`${prefix}*`).catch(() => {
|
|
266
|
+
cacheBackend?.delByPattern?.(`${prefix}*`).catch((error) => {
|
|
267
|
+
logger.debug("[FileCache] Backend invalidation failed", { prefix, error });
|
|
268
|
+
});
|
|
265
269
|
return count;
|
|
266
270
|
}
|
|
267
271
|
deleteByPrefixAsync(prefix) {
|
|
@@ -289,7 +293,9 @@ export class FileCache {
|
|
|
289
293
|
}
|
|
290
294
|
// Fire-and-forget backend deletion
|
|
291
295
|
// Note: prefix already includes "file:" from buildFileCacheKeyPrefix, don't add it again
|
|
292
|
-
cacheBackend?.delByPattern?.(`${prefix}*:${suffix}`).catch(() => {
|
|
296
|
+
cacheBackend?.delByPattern?.(`${prefix}*:${suffix}`).catch((error) => {
|
|
297
|
+
logger.debug("[FileCache] Backend invalidation failed", { prefix, suffix, error });
|
|
298
|
+
});
|
|
293
299
|
return count;
|
|
294
300
|
}
|
|
295
301
|
deleteByPrefixAndSuffixAsync(prefix, suffix) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-invalidation.d.ts","sourceRoot":"","sources":["../../../../src/src/server/context/cache-invalidation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cache-invalidation.d.ts","sourceRoot":"","sources":["../../../../src/src/server/context/cache-invalidation.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,WAAW,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACvC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,EAAE,EACvB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAiGf"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { serverLogger as logger } from "../../utils/index.js";
|
|
2
2
|
import { clearModulePathCache, invalidateModulePaths, } from "../../transforms/mdx/esm-module-loader/index.js";
|
|
3
|
+
import { clearModuleCacheForProject } from "../../cache/module-cache.js";
|
|
3
4
|
import { clearSSRModuleCache, clearSSRModuleCacheForProject, } from "../../modules/react-loader/ssr-module-loader/index.js";
|
|
4
5
|
import { clearRendererCacheForProject, clearRendererCaches } from "../../rendering/renderer.js";
|
|
5
6
|
import { clearRouterDetectionCache } from "../../rendering/router-detection.js";
|
|
@@ -38,6 +39,9 @@ export async function invalidateProjectCaches(projectSlug, changedPaths, options
|
|
|
38
39
|
});
|
|
39
40
|
if (projectId) {
|
|
40
41
|
clearSSRModuleCacheForProject(projectId);
|
|
42
|
+
// Also clear the pod-level module cache (used by RenderPipeline)
|
|
43
|
+
// This was previously missed, causing stale renders despite SSR module cache clearing
|
|
44
|
+
clearModuleCacheForProject(projectId);
|
|
41
45
|
}
|
|
42
46
|
else {
|
|
43
47
|
clearSSRModuleCache();
|
|
@@ -321,6 +321,8 @@ async function handleListFiles(req, ctx) {
|
|
|
321
321
|
if (!projectDir)
|
|
322
322
|
return errorResponse("No project directory configured", 500);
|
|
323
323
|
const relativePath = new URL(req.url).searchParams.get("path") || "";
|
|
324
|
+
if (relativePath.includes(".."))
|
|
325
|
+
return errorResponse("Invalid path", 400);
|
|
324
326
|
const fullPath = relativePath ? `${projectDir}/${relativePath}` : projectDir;
|
|
325
327
|
try {
|
|
326
328
|
const files = [];
|
|
@@ -352,6 +354,8 @@ async function handleReadFileContent(req, ctx) {
|
|
|
352
354
|
const relativePath = new URL(req.url).searchParams.get("path") || "";
|
|
353
355
|
if (!relativePath)
|
|
354
356
|
return errorResponse("path parameter is required", 400);
|
|
357
|
+
if (relativePath.includes(".."))
|
|
358
|
+
return errorResponse("Invalid path", 400);
|
|
355
359
|
try {
|
|
356
360
|
const content = await adapter.fs.readFile(`${projectDir}/${relativePath}`);
|
|
357
361
|
const extension = relativePath.split(".").pop() || "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/dev/projects/ui-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,8BAA8B,CAAC;AA6ExD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"ui-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/dev/projects/ui-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,8BAA8B,CAAC;AA6ExD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA8CvF"}
|
|
@@ -62,6 +62,12 @@ export function handleProjectsUI(req) {
|
|
|
62
62
|
return Promise.resolve(null);
|
|
63
63
|
return withSpan("server.dev.projectsUI.handle", async () => {
|
|
64
64
|
const relativePath = pathname.replace("/_projects/ui/", "").replace(/\.js$/, "");
|
|
65
|
+
if (relativePath.includes("..")) {
|
|
66
|
+
return new dntShim.Response("Invalid path", {
|
|
67
|
+
status: 400,
|
|
68
|
+
headers: { "Content-Type": "text/plain" },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
65
71
|
const uiDir = getUiDirectory();
|
|
66
72
|
const module = await readUiSource(uiDir, relativePath);
|
|
67
73
|
if (!module) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/http-cache.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAmBzE,KAAK,YAAY,GAAG;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,eAAe,CAAC;IAC3B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"http-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/http-cache.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAmBzE,KAAK,YAAY,GAAG;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,eAAe,CAAC;IAC3B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAyUF;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOvF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8D9F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,EAAE,CAAC,CAiInB"}
|