rwsdk 0.2.0-alpha.9 → 0.3.0
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/lib/constants.d.mts +6 -1
- package/dist/lib/constants.mjs +6 -1
- package/dist/lib/smokeTests/browser.mjs +5 -21
- package/dist/lib/smokeTests/codeUpdates.d.mts +1 -1
- package/dist/lib/smokeTests/codeUpdates.mjs +41 -5
- package/dist/lib/smokeTests/development.d.mts +1 -1
- package/dist/lib/smokeTests/development.mjs +4 -10
- package/dist/lib/smokeTests/release.d.mts +1 -1
- package/dist/lib/smokeTests/release.mjs +4 -9
- package/dist/lib/smokeTests/runSmokeTests.mjs +2 -2
- package/dist/lib/smokeTests/templates/SmokeTest.template.js +3 -2
- package/dist/lib/testUtils/stubEnvVars.d.mts +2 -0
- package/dist/lib/testUtils/stubEnvVars.mjs +11 -0
- package/dist/runtime/client/client.d.ts +10 -0
- package/dist/runtime/{client.js → client/client.js} +13 -10
- package/dist/runtime/{clientNavigation.test.js → client/navigation.test.js} +1 -1
- package/dist/runtime/client/setWebpackRequire.d.ts +1 -0
- package/dist/runtime/client/setWebpackRequire.js +2 -0
- package/dist/runtime/{client.d.ts → client/types.d.ts} +4 -10
- package/dist/runtime/client/types.js +1 -0
- package/dist/runtime/entries/client.d.ts +2 -2
- package/dist/runtime/entries/client.js +2 -2
- package/dist/runtime/imports/client.d.ts +3 -3
- package/dist/runtime/imports/client.js +11 -15
- package/dist/runtime/imports/ssr.d.ts +3 -3
- package/dist/runtime/imports/ssr.js +3 -3
- package/dist/runtime/imports/worker.d.ts +3 -3
- package/dist/runtime/imports/worker.js +5 -4
- package/dist/runtime/lib/manifest.d.ts +11 -2
- package/dist/runtime/lib/manifest.js +1 -1
- package/dist/runtime/lib/memoizeOnId.d.ts +1 -0
- package/dist/runtime/lib/memoizeOnId.js +11 -0
- package/dist/runtime/lib/realtime/client.d.ts +1 -1
- package/dist/runtime/lib/realtime/client.js +1 -1
- package/dist/runtime/lib/router.d.ts +3 -3
- package/dist/runtime/lib/router.js +77 -33
- package/dist/runtime/register/ssr.d.ts +1 -1
- package/dist/runtime/register/ssr.js +4 -3
- package/dist/runtime/render/preloads.d.ts +6 -0
- package/dist/runtime/render/preloads.js +40 -0
- package/dist/runtime/render/renderRscThenableToHtmlStream.js +2 -1
- package/dist/runtime/render/stylesheets.js +1 -1
- package/dist/runtime/requestInfo/types.d.ts +3 -1
- package/dist/runtime/requestInfo/worker.js +9 -1
- package/dist/runtime/worker.d.ts +0 -3
- package/dist/runtime/worker.js +2 -11
- package/dist/scripts/debug-sync.mjs +142 -39
- package/dist/scripts/smoke-test.mjs +0 -10
- package/dist/scripts/worker-run.mjs +8 -3
- package/dist/vite/buildApp.d.mts +15 -0
- package/dist/vite/buildApp.mjs +53 -0
- package/dist/vite/configPlugin.d.mts +4 -2
- package/dist/vite/configPlugin.mjs +69 -62
- package/dist/vite/createDirectiveLookupPlugin.d.mts +0 -6
- package/dist/vite/createDirectiveLookupPlugin.mjs +61 -145
- package/dist/vite/directiveModulesDevPlugin.d.mts +8 -0
- package/dist/vite/directiveModulesDevPlugin.mjs +62 -0
- package/dist/vite/directivesFilteringPlugin.d.mts +6 -0
- package/dist/vite/directivesFilteringPlugin.mjs +31 -0
- package/dist/vite/directivesPlugin.mjs +28 -42
- package/dist/vite/getViteEsbuild.d.mts +1 -0
- package/dist/vite/getViteEsbuild.mjs +12 -0
- package/dist/vite/hasOwnReactVitePlugin.d.mts +3 -0
- package/dist/vite/hasOwnReactVitePlugin.mjs +14 -0
- package/dist/vite/injectVitePreamblePlugin.d.mts +3 -2
- package/dist/vite/injectVitePreamblePlugin.mjs +4 -2
- package/dist/vite/linkerPlugin.d.mts +4 -0
- package/dist/vite/linkerPlugin.mjs +41 -0
- package/dist/vite/manifestPlugin.d.mts +2 -2
- package/dist/vite/manifestPlugin.mjs +12 -37
- package/dist/vite/miniflareHMRPlugin.mjs +17 -2
- package/dist/vite/moveStaticAssetsPlugin.mjs +2 -1
- package/dist/vite/prismaPlugin.mjs +1 -1
- package/dist/vite/reactConditionsResolverPlugin.d.mts +3 -4
- package/dist/vite/reactConditionsResolverPlugin.mjs +74 -56
- package/dist/vite/redwoodPlugin.d.mts +1 -1
- package/dist/vite/redwoodPlugin.mjs +36 -12
- package/dist/vite/runDirectivesScan.d.mts +7 -0
- package/dist/vite/runDirectivesScan.mjs +152 -0
- package/dist/vite/ssrBridgePlugin.mjs +13 -14
- package/dist/vite/transformClientComponents.d.mts +0 -1
- package/dist/vite/transformClientComponents.mjs +1 -9
- package/dist/vite/transformJsxScriptTagsPlugin.d.mts +4 -3
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +151 -158
- package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +393 -136
- package/dist/vite/transformServerFunctions.d.mts +1 -1
- package/dist/vite/transformServerFunctions.mjs +11 -12
- package/package.json +28 -4
- /package/dist/runtime/{imports → client}/ClientOnly.d.ts +0 -0
- /package/dist/runtime/{imports → client}/ClientOnly.js +0 -0
- /package/dist/runtime/{clientNavigation.d.ts → client/navigation.d.ts} +0 -0
- /package/dist/runtime/{clientNavigation.js → client/navigation.js} +0 -0
- /package/dist/runtime/{clientNavigation.test.d.ts → client/navigation.test.d.ts} +0 -0
|
@@ -71,53 +71,97 @@ export function defineRoutes(routes) {
|
|
|
71
71
|
if (path !== "/" && !path.endsWith("/")) {
|
|
72
72
|
path = path + "/";
|
|
73
73
|
}
|
|
74
|
-
//
|
|
75
|
-
|
|
74
|
+
// Flow below; helpers are declared after the main flow for readability
|
|
75
|
+
// 1) Global middlewares: run any middleware functions encountered (Response short-circuits, element renders)
|
|
76
|
+
// 2) Route matching: skip non-matching route definitions; stop at first match
|
|
77
|
+
// 3) Route-specific middlewares: run middlewares attached to the matched route
|
|
78
|
+
// 4) Final component: render the matched route's final component (layouts apply only here)
|
|
76
79
|
for (const route of flattenedRoutes) {
|
|
80
|
+
// 1) Global middlewares (encountered before a match)
|
|
77
81
|
if (typeof route === "function") {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
const result = await route(getRequestInfo());
|
|
83
|
+
const handled = await handleMiddlewareResult(result);
|
|
84
|
+
if (handled) {
|
|
85
|
+
return handled;
|
|
81
86
|
}
|
|
82
87
|
continue;
|
|
83
88
|
}
|
|
89
|
+
// 2) Route matching (skip if not matched)
|
|
84
90
|
const params = matchPath(route.path, path);
|
|
85
|
-
if (params) {
|
|
86
|
-
|
|
87
|
-
break;
|
|
91
|
+
if (!params) {
|
|
92
|
+
continue;
|
|
88
93
|
}
|
|
94
|
+
// Found a match: 3) route middlewares, then 4) final component, then stop
|
|
95
|
+
return await runWithRequestInfoOverrides({ params }, async () => {
|
|
96
|
+
const { routeMiddlewares, componentHandler } = parseHandlers(route.handler);
|
|
97
|
+
// 3) Route-specific middlewares
|
|
98
|
+
const mwHandled = await handleRouteMiddlewares(routeMiddlewares);
|
|
99
|
+
if (mwHandled) {
|
|
100
|
+
return mwHandled;
|
|
101
|
+
}
|
|
102
|
+
// 4) Final component (always last item)
|
|
103
|
+
return await handleRouteComponent(componentHandler, route.layouts || []);
|
|
104
|
+
});
|
|
89
105
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return runWithRequestInfoOverrides({ params }, async () => {
|
|
106
|
+
// No route matched and no middleware handled the request
|
|
107
|
+
// todo(peterp, 2025-01-28): Allow the user to define their own "not found" route.
|
|
108
|
+
return new Response("Not Found", { status: 404 });
|
|
109
|
+
// --- Helpers ---
|
|
110
|
+
function parseHandlers(handler) {
|
|
96
111
|
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
const routeMiddlewares = handlers.slice(0, Math.max(handlers.length - 1, 0));
|
|
113
|
+
const componentHandler = handlers[handlers.length - 1];
|
|
114
|
+
return {
|
|
115
|
+
routeMiddlewares: routeMiddlewares,
|
|
116
|
+
componentHandler,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function renderElement(element) {
|
|
120
|
+
const requestInfo = getRequestInfo();
|
|
121
|
+
const Element = () => element;
|
|
122
|
+
return renderPage(requestInfo, Element, onError);
|
|
123
|
+
}
|
|
124
|
+
async function handleMiddlewareResult(result) {
|
|
125
|
+
if (result instanceof Response) {
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
if (result && React.isValidElement(result)) {
|
|
129
|
+
return await renderElement(result);
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
// Note: We no longer have separate global pass or match-only pass;
|
|
134
|
+
// the outer single pass above handles both behaviors correctly.
|
|
135
|
+
async function handleRouteMiddlewares(mws) {
|
|
136
|
+
for (const mw of mws) {
|
|
137
|
+
const result = await mw(getRequestInfo());
|
|
138
|
+
const handled = await handleMiddlewareResult(result);
|
|
139
|
+
if (handled)
|
|
140
|
+
return handled;
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
async function handleRouteComponent(component, layouts) {
|
|
145
|
+
if (isRouteComponent(component)) {
|
|
146
|
+
const requestInfo = getRequestInfo();
|
|
147
|
+
const WrappedComponent = wrapWithLayouts(wrapHandlerToThrowResponses(component), layouts, requestInfo);
|
|
148
|
+
if (!isClientReference(component)) {
|
|
149
|
+
// context(justinvdm, 31 Jul 2025): We now know we're dealing with a page route,
|
|
150
|
+
// so we create a deferred so that we can signal when we're done determining whether
|
|
151
|
+
// we're returning a response or a react element
|
|
152
|
+
requestInfo.rw.pageRouteResolved = Promise.withResolvers();
|
|
114
153
|
}
|
|
154
|
+
return await renderPage(requestInfo, WrappedComponent, onError);
|
|
115
155
|
}
|
|
116
|
-
//
|
|
156
|
+
// If the last handler is not a component, handle as middleware result (no layouts)
|
|
157
|
+
const tailResult = await component(getRequestInfo());
|
|
158
|
+
const handledTail = await handleMiddlewareResult(tailResult);
|
|
159
|
+
if (handledTail)
|
|
160
|
+
return handledTail;
|
|
117
161
|
return new Response("Response not returned from route handler", {
|
|
118
162
|
status: 500,
|
|
119
163
|
});
|
|
120
|
-
}
|
|
164
|
+
}
|
|
121
165
|
},
|
|
122
166
|
};
|
|
123
167
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const loadServerModule: (
|
|
1
|
+
export declare const loadServerModule: (id: string) => Promise<any>;
|
|
2
2
|
export declare const getServerModuleExport: (id: string) => Promise<any>;
|
|
3
3
|
export declare const createServerReference: (id: string, name: string) => any;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import memoize from "lodash/memoize";
|
|
2
1
|
import { createServerReference as baseCreateServerReference } from "react-server-dom-webpack/client.edge";
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { memoizeOnId } from "../lib/memoizeOnId";
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { useServerLookup } from "virtual:use-server-lookup.js";
|
|
5
|
+
export const loadServerModule = memoizeOnId(async (id) => {
|
|
5
6
|
const moduleFn = useServerLookup[id];
|
|
6
7
|
if (!moduleFn) {
|
|
7
8
|
throw new Error(`(worker) No module found for '${id}' in module lookup for "use server" directive`);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RequestInfo } from "../requestInfo/types.js";
|
|
2
|
+
import type { Manifest, ManifestChunk } from "../lib/manifest.js";
|
|
3
|
+
export declare function findScriptForModule(id: string, manifest: Manifest): ManifestChunk | undefined;
|
|
4
|
+
export declare const Preloads: ({ requestInfo }: {
|
|
5
|
+
requestInfo: RequestInfo;
|
|
6
|
+
}) => import("react/jsx-runtime.js").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { use } from "react";
|
|
3
|
+
import { getManifest } from "../lib/manifest.js";
|
|
4
|
+
export function findScriptForModule(id, manifest) {
|
|
5
|
+
const visited = new Set();
|
|
6
|
+
function find(id) {
|
|
7
|
+
if (visited.has(id)) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
visited.add(id);
|
|
11
|
+
const manifestEntry = manifest[id];
|
|
12
|
+
if (!manifestEntry) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (manifestEntry.isEntry || manifestEntry.isDynamicEntry) {
|
|
16
|
+
return manifestEntry;
|
|
17
|
+
}
|
|
18
|
+
if (manifestEntry.imports) {
|
|
19
|
+
for (const dep of manifestEntry.imports) {
|
|
20
|
+
const entry = find(dep);
|
|
21
|
+
if (entry) {
|
|
22
|
+
return entry;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
return find(id);
|
|
29
|
+
}
|
|
30
|
+
export const Preloads = ({ requestInfo }) => {
|
|
31
|
+
const manifest = use(getManifest());
|
|
32
|
+
const allScripts = new Set();
|
|
33
|
+
for (const scriptId of requestInfo.rw.scriptsToBeLoaded) {
|
|
34
|
+
const script = findScriptForModule(scriptId, manifest);
|
|
35
|
+
if (script) {
|
|
36
|
+
allScripts.add(script.file);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return (_jsx(_Fragment, { children: Array.from(allScripts).map((href) => (_jsx("link", { rel: "modulepreload", href: href }, href))) }));
|
|
40
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { use } from "react";
|
|
3
3
|
import { renderToReadableStream } from "react-dom/server.edge";
|
|
4
|
+
import { Preloads } from "./preloads.js";
|
|
4
5
|
import { Stylesheets } from "./stylesheets.js";
|
|
5
6
|
export const renderRscThenableToHtmlStream = async ({ thenable, Document, requestInfo, shouldSSR, onError, }) => {
|
|
6
7
|
const Component = () => {
|
|
7
8
|
const RscApp = () => {
|
|
8
9
|
const node = use(thenable).node;
|
|
9
|
-
return (_jsxs(_Fragment, { children: [_jsx(Stylesheets, { requestInfo: requestInfo }), _jsx("div", { id: "hydrate-root", children: node })] }));
|
|
10
|
+
return (_jsxs(_Fragment, { children: [_jsx(Stylesheets, { requestInfo: requestInfo }), _jsx(Preloads, { requestInfo: requestInfo }), _jsx("div", { id: "hydrate-root", children: node })] }));
|
|
10
11
|
};
|
|
11
12
|
// todo(justinvdm, 18 Jun 2025): We can build on this later to allow users
|
|
12
13
|
// surface context. e.g:
|
|
@@ -23,7 +23,7 @@ const findCssForModule = (scriptId, manifest) => {
|
|
|
23
23
|
return Array.from(css);
|
|
24
24
|
};
|
|
25
25
|
export const Stylesheets = ({ requestInfo }) => {
|
|
26
|
-
const manifest = use(getManifest(
|
|
26
|
+
const manifest = use(getManifest());
|
|
27
27
|
const allStylesheets = new Set();
|
|
28
28
|
for (const scriptId of requestInfo.rw.scriptsToBeLoaded) {
|
|
29
29
|
const css = findCssForModule(scriptId, manifest);
|
|
@@ -2,7 +2,15 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
2
2
|
const requestInfoDeferred = Promise.withResolvers();
|
|
3
3
|
const requestInfoStore = new AsyncLocalStorage();
|
|
4
4
|
const requestInfoBase = {};
|
|
5
|
-
const REQUEST_INFO_KEYS = [
|
|
5
|
+
const REQUEST_INFO_KEYS = [
|
|
6
|
+
"request",
|
|
7
|
+
"params",
|
|
8
|
+
"ctx",
|
|
9
|
+
"headers",
|
|
10
|
+
"rw",
|
|
11
|
+
"cf",
|
|
12
|
+
"response",
|
|
13
|
+
];
|
|
6
14
|
REQUEST_INFO_KEYS.forEach((key) => {
|
|
7
15
|
Object.defineProperty(requestInfoBase, key, {
|
|
8
16
|
enumerable: true,
|
package/dist/runtime/worker.d.ts
CHANGED
|
@@ -10,9 +10,6 @@ declare global {
|
|
|
10
10
|
export declare const defineApp: <T extends RequestInfo = RequestInfo<any, DefaultAppContext>>(routes: Route<T>[]) => {
|
|
11
11
|
fetch: (request: Request, env: Env, cf: ExecutionContext) => Promise<Response>;
|
|
12
12
|
};
|
|
13
|
-
export declare const SmokeTestWrapper: React.FC<{
|
|
14
|
-
children: React.ReactNode;
|
|
15
|
-
}>;
|
|
16
13
|
export declare const DefaultDocument: React.FC<{
|
|
17
14
|
children: React.ReactNode;
|
|
18
15
|
}>;
|
package/dist/runtime/worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { transformRscToHtmlStream } from "./render/transformRscToHtmlStream";
|
|
3
3
|
import { renderToRscStream } from "./render/renderToRscStream";
|
|
4
4
|
import { rscActionHandler } from "./register/worker";
|
|
@@ -33,7 +33,6 @@ export const defineApp = (routes) => {
|
|
|
33
33
|
const url = new URL(request.url);
|
|
34
34
|
const isRSCRequest = url.searchParams.has("__rsc") ||
|
|
35
35
|
request.headers.get("accept")?.includes("text/x-component");
|
|
36
|
-
const isSmokeTest = url.searchParams.has("__smoke_test");
|
|
37
36
|
const userHeaders = new Headers();
|
|
38
37
|
const rw = {
|
|
39
38
|
Document: DefaultDocument,
|
|
@@ -67,9 +66,6 @@ export const defineApp = (routes) => {
|
|
|
67
66
|
else {
|
|
68
67
|
pageElement = _jsx(Page, { ...requestInfo });
|
|
69
68
|
}
|
|
70
|
-
if (isSmokeTest) {
|
|
71
|
-
pageElement = _jsx(SmokeTestWrapper, { children: pageElement });
|
|
72
|
-
}
|
|
73
69
|
return pageElement;
|
|
74
70
|
};
|
|
75
71
|
const renderPage = async (requestInfo, Page, onError) => {
|
|
@@ -169,17 +165,12 @@ export const defineApp = (routes) => {
|
|
|
169
165
|
if (e instanceof Response) {
|
|
170
166
|
return e;
|
|
171
167
|
}
|
|
172
|
-
console.error("
|
|
168
|
+
console.error("rwsdk: Received an unhandled error:\n\n%s", e);
|
|
173
169
|
throw e;
|
|
174
170
|
}
|
|
175
171
|
},
|
|
176
172
|
};
|
|
177
173
|
};
|
|
178
|
-
export const SmokeTestWrapper = async ({ children }) => {
|
|
179
|
-
const smokeTestInfo = await Object.values(await import.meta.glob("/src/app/components/__SmokeTest.tsx"))[0]();
|
|
180
|
-
const SmokeTestInfo = smokeTestInfo.SmokeTestInfo;
|
|
181
|
-
return (_jsxs(_Fragment, { children: [_jsx(SmokeTestInfo, {}), children] }));
|
|
182
|
-
};
|
|
183
174
|
export const DefaultDocument = ({ children, }) => (_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" })] }), _jsx("body", { children: _jsx("div", { id: "root", children: children }) })] }));
|
|
184
175
|
const isClientReference = (Component) => {
|
|
185
176
|
return Object.prototype.hasOwnProperty.call(Component, "$$isClientReference");
|
|
@@ -7,6 +7,9 @@ import chokidar from "chokidar";
|
|
|
7
7
|
import { lock } from "proper-lockfile";
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const getPackageManagerInfo = (targetDir) => {
|
|
10
|
+
if (existsSync(path.join(targetDir, "bun.lock"))) {
|
|
11
|
+
return { name: "bun", lockFile: "bun.lock", command: "add" };
|
|
12
|
+
}
|
|
10
13
|
const pnpmResult = {
|
|
11
14
|
name: "pnpm",
|
|
12
15
|
lockFile: "pnpm-lock.yaml",
|
|
@@ -24,15 +27,48 @@ const getPackageManagerInfo = (targetDir) => {
|
|
|
24
27
|
}
|
|
25
28
|
return pnpmResult;
|
|
26
29
|
};
|
|
30
|
+
const cleanupViteEntries = async (targetDir) => {
|
|
31
|
+
const nodeModulesDir = path.join(targetDir, "node_modules");
|
|
32
|
+
if (!existsSync(nodeModulesDir)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const entries = await fs.readdir(nodeModulesDir);
|
|
37
|
+
const viteEntries = entries.filter((entry) => entry.startsWith(".vite"));
|
|
38
|
+
for (const entry of viteEntries) {
|
|
39
|
+
const entryPath = path.join(nodeModulesDir, entry);
|
|
40
|
+
try {
|
|
41
|
+
const stat = await fs.lstat(entryPath);
|
|
42
|
+
if (!stat.isSymbolicLink()) {
|
|
43
|
+
console.log(`Removing vite cache entry: ${entry}`);
|
|
44
|
+
await fs.rm(entryPath, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(`Skipping symlinked vite cache entry: ${entry}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// If we can't stat it, try to remove it
|
|
52
|
+
console.log(`Removing vite cache entry: ${entry}`);
|
|
53
|
+
await fs.rm(entryPath, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.log(`Failed to cleanup vite cache entries: ${error}`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
27
61
|
const performFullSync = async (sdkDir, targetDir) => {
|
|
28
|
-
const sdkPackageJsonPath = path.join(sdkDir, "package.json");
|
|
29
|
-
let originalSdkPackageJson = null;
|
|
30
62
|
let tarballPath = "";
|
|
31
63
|
let tarballName = "";
|
|
64
|
+
// Clean up vite cache
|
|
65
|
+
await cleanupViteEntries(targetDir);
|
|
32
66
|
try {
|
|
33
67
|
console.log("📦 Packing SDK...");
|
|
34
|
-
const packResult = await $({ cwd: sdkDir }) `npm pack`;
|
|
35
|
-
|
|
68
|
+
const packResult = await $({ cwd: sdkDir }) `npm pack --json`;
|
|
69
|
+
const json = JSON.parse(packResult.stdout || "[]");
|
|
70
|
+
const packInfo = Array.isArray(json) ? json[0] : undefined;
|
|
71
|
+
tarballName = (packInfo && (packInfo.filename || packInfo.name)) || "";
|
|
36
72
|
if (!tarballName) {
|
|
37
73
|
console.error("❌ Failed to get tarball name from npm pack.");
|
|
38
74
|
return;
|
|
@@ -49,6 +85,29 @@ const performFullSync = async (sdkDir, targetDir) => {
|
|
|
49
85
|
.readFile(lockfilePath, "utf-8")
|
|
50
86
|
.catch(() => null);
|
|
51
87
|
try {
|
|
88
|
+
// For bun, we need to remove the existing dependency from package.json
|
|
89
|
+
// before adding the tarball to avoid a dependency loop error.
|
|
90
|
+
if (pm.name === "bun" && originalPackageJson) {
|
|
91
|
+
try {
|
|
92
|
+
const targetPackageJson = JSON.parse(originalPackageJson);
|
|
93
|
+
let modified = false;
|
|
94
|
+
if (targetPackageJson.dependencies?.rwsdk) {
|
|
95
|
+
delete targetPackageJson.dependencies.rwsdk;
|
|
96
|
+
modified = true;
|
|
97
|
+
}
|
|
98
|
+
if (targetPackageJson.devDependencies?.rwsdk) {
|
|
99
|
+
delete targetPackageJson.devDependencies.rwsdk;
|
|
100
|
+
modified = true;
|
|
101
|
+
}
|
|
102
|
+
if (modified) {
|
|
103
|
+
console.log("Temporarily removing rwsdk from target package.json to prevent dependency loop with bun.");
|
|
104
|
+
await fs.writeFile(packageJsonPath, JSON.stringify(targetPackageJson, null, 2));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
console.warn("Could not modify target package.json, proceeding anyway.");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
52
111
|
const cmd = pm.name;
|
|
53
112
|
const args = [pm.command];
|
|
54
113
|
if (pm.name === "yarn") {
|
|
@@ -74,10 +133,6 @@ const performFullSync = async (sdkDir, targetDir) => {
|
|
|
74
133
|
}
|
|
75
134
|
}
|
|
76
135
|
finally {
|
|
77
|
-
if (originalSdkPackageJson) {
|
|
78
|
-
console.log("Restoring package.json...");
|
|
79
|
-
await fs.writeFile(sdkPackageJsonPath, originalSdkPackageJson);
|
|
80
|
-
}
|
|
81
136
|
if (tarballPath) {
|
|
82
137
|
console.log("Removing tarball...");
|
|
83
138
|
await fs.unlink(tarballPath).catch(() => {
|
|
@@ -86,19 +141,63 @@ const performFullSync = async (sdkDir, targetDir) => {
|
|
|
86
141
|
}
|
|
87
142
|
}
|
|
88
143
|
};
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
144
|
+
const syncFilesWithRsyncOrFs = async (sdkDir, destDir, filesEntries) => {
|
|
145
|
+
const sources = filesEntries.map((p) => path.join(sdkDir, p));
|
|
146
|
+
// Always include package.json in sync
|
|
147
|
+
const pkgJsonPath = path.join(sdkDir, "package.json");
|
|
148
|
+
sources.push(pkgJsonPath);
|
|
149
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
150
|
+
// Try rsync across all sources in one shot
|
|
151
|
+
try {
|
|
152
|
+
if (sources.length > 0) {
|
|
153
|
+
const rsyncArgs = [
|
|
154
|
+
"-a",
|
|
155
|
+
"--delete",
|
|
156
|
+
"--omit-dir-times",
|
|
157
|
+
"--no-perms",
|
|
158
|
+
"--no-owner",
|
|
159
|
+
"--no-group",
|
|
160
|
+
...sources,
|
|
161
|
+
destDir + path.sep,
|
|
162
|
+
];
|
|
163
|
+
await $({ stdio: "inherit" })("rsync", rsyncArgs);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// fall through to fs fallback
|
|
169
|
+
}
|
|
170
|
+
console.log("Rsync failed, falling back to fs");
|
|
171
|
+
// Fallback: destructive copy using Node fs to mirror content
|
|
172
|
+
await fs.rm(destDir, { recursive: true, force: true });
|
|
173
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
174
|
+
for (const src of sources) {
|
|
175
|
+
const rel = path.relative(sdkDir, src);
|
|
176
|
+
const dst = path.join(destDir, rel);
|
|
177
|
+
await fs.mkdir(path.dirname(dst), { recursive: true });
|
|
178
|
+
try {
|
|
179
|
+
const stat = await fs.lstat(src);
|
|
180
|
+
if (stat.isDirectory()) {
|
|
181
|
+
await fs.cp(src, dst, { recursive: true, force: true });
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
await fs.copyFile(src, dst);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
await fs.cp(src, dst, { recursive: true, force: true }).catch(() => { });
|
|
98
189
|
}
|
|
99
190
|
}
|
|
100
|
-
|
|
101
|
-
|
|
191
|
+
};
|
|
192
|
+
const performFastSync = async (sdkDir, targetDir) => {
|
|
193
|
+
console.log("⚡️ No dependency changes, performing fast sync...");
|
|
194
|
+
// Clean up vite cache
|
|
195
|
+
await cleanupViteEntries(targetDir);
|
|
196
|
+
const nodeModulesPkgDir = path.join(targetDir, "node_modules", "rwsdk");
|
|
197
|
+
// Copy directories/files declared in package.json#files (plus package.json)
|
|
198
|
+
const filesToSync = JSON.parse(await fs.readFile(path.join(sdkDir, "package.json"), "utf-8"))
|
|
199
|
+
.files || [];
|
|
200
|
+
await syncFilesWithRsyncOrFs(sdkDir, nodeModulesPkgDir, filesToSync);
|
|
102
201
|
};
|
|
103
202
|
const areDependenciesEqual = (deps1, deps2) => {
|
|
104
203
|
// Simple string comparison for this use case is sufficient
|
|
@@ -120,13 +219,8 @@ const performSync = async (sdkDir, targetDir) => {
|
|
|
120
219
|
if (existsSync(installedSdkPackageJsonPath)) {
|
|
121
220
|
const sdkPackageJsonContent = await fs.readFile(sdkPackageJsonPath, "utf-8");
|
|
122
221
|
const installedSdkPackageJsonContent = await fs.readFile(installedSdkPackageJsonPath, "utf-8");
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (areDependenciesEqual(sdkPkg.dependencies, installedPkg.dependencies) &&
|
|
126
|
-
areDependenciesEqual(sdkPkg.devDependencies, installedPkg.devDependencies) &&
|
|
127
|
-
areDependenciesEqual(sdkPkg.peerDependencies, installedPkg.peerDependencies)) {
|
|
128
|
-
packageJsonChanged = false;
|
|
129
|
-
}
|
|
222
|
+
packageJsonChanged =
|
|
223
|
+
sdkPackageJsonContent !== installedSdkPackageJsonContent;
|
|
130
224
|
}
|
|
131
225
|
if (packageJsonChanged) {
|
|
132
226
|
console.log("📦 package.json changed, performing full sync...");
|
|
@@ -198,20 +292,19 @@ export const debugSync = async (opts) => {
|
|
|
198
292
|
cwd: sdkDir,
|
|
199
293
|
});
|
|
200
294
|
let syncing = false;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (syncing || filePath.endsWith(".tgz")) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (expectingFileChanges && process.env.RWSDK_FORCE_FULL_SYNC) {
|
|
209
|
-
expectingFileChanges = false;
|
|
295
|
+
let pendingResync = false;
|
|
296
|
+
const triggerResync = async (reason) => {
|
|
297
|
+
if (syncing) {
|
|
298
|
+
pendingResync = true;
|
|
210
299
|
return;
|
|
211
300
|
}
|
|
212
301
|
syncing = true;
|
|
213
|
-
|
|
214
|
-
|
|
302
|
+
if (reason) {
|
|
303
|
+
console.log(`\nDetected change, re-syncing... (file: ${reason})`);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
console.log(`\nDetected change, re-syncing...`);
|
|
307
|
+
}
|
|
215
308
|
if (childProc && !childProc.killed) {
|
|
216
309
|
console.log("Stopping running process...");
|
|
217
310
|
childProc.kill();
|
|
@@ -220,7 +313,6 @@ export const debugSync = async (opts) => {
|
|
|
220
313
|
});
|
|
221
314
|
}
|
|
222
315
|
try {
|
|
223
|
-
watcher.unwatch(filesToWatch);
|
|
224
316
|
await performSync(sdkDir, targetDir);
|
|
225
317
|
runWatchedCommand();
|
|
226
318
|
}
|
|
@@ -230,8 +322,19 @@ export const debugSync = async (opts) => {
|
|
|
230
322
|
}
|
|
231
323
|
finally {
|
|
232
324
|
syncing = false;
|
|
233
|
-
watcher.add(filesToWatch);
|
|
234
325
|
}
|
|
326
|
+
if (pendingResync) {
|
|
327
|
+
pendingResync = false;
|
|
328
|
+
// Coalesce any rapid additional events into a single follow-up sync
|
|
329
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
330
|
+
return triggerResync();
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
watcher.on("all", async (_event, filePath) => {
|
|
334
|
+
if (filePath.endsWith(".tgz")) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
await triggerResync(filePath);
|
|
235
338
|
});
|
|
236
339
|
const cleanup = async () => {
|
|
237
340
|
console.log("\nCleaning up...");
|
|
@@ -17,7 +17,6 @@ if (fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
|
17
17
|
const ciFlag = args.includes("--ci");
|
|
18
18
|
// Set initial default values (sync will be determined below)
|
|
19
19
|
const options = {
|
|
20
|
-
customPath: "/", // Default path
|
|
21
20
|
skipDev: false,
|
|
22
21
|
skipRelease: false,
|
|
23
22
|
skipClient: false,
|
|
@@ -88,7 +87,6 @@ Smoke Test Usage:
|
|
|
88
87
|
node smoke-test.mjs [options]
|
|
89
88
|
|
|
90
89
|
Options:
|
|
91
|
-
--url=PATH Custom URL path to test (e.g., "/login")
|
|
92
90
|
--skip-dev Skip testing the local development server
|
|
93
91
|
--skip-release Skip testing the release/production deployment
|
|
94
92
|
--skip-client Skip client-side tests, only run server-side checks
|
|
@@ -114,14 +112,6 @@ Options:
|
|
|
114
112
|
else if (arg.startsWith("--artifact-dir=")) {
|
|
115
113
|
options.artifactDir = arg.substring(15);
|
|
116
114
|
}
|
|
117
|
-
else if (arg.startsWith("--url=")) {
|
|
118
|
-
options.customPath = arg.substring(6);
|
|
119
|
-
}
|
|
120
|
-
else if (!arg.startsWith("--")) {
|
|
121
|
-
// For backwards compatibility, any non-flag argument is treated as the custom path
|
|
122
|
-
console.log(`Setting URL path to "${arg}" (use --url= format in the future)`);
|
|
123
|
-
options.customPath = arg;
|
|
124
|
-
}
|
|
125
115
|
else {
|
|
126
116
|
// Throw error for unknown options instead of just warning
|
|
127
117
|
log("Unknown option: %s", arg);
|
|
@@ -16,13 +16,18 @@ export const runWorkerScript = async (relativeScriptPath) => {
|
|
|
16
16
|
console.error("Error: Script path is required");
|
|
17
17
|
console.log("\nUsage:");
|
|
18
18
|
console.log(" npm run worker:run <script-path>");
|
|
19
|
-
console.log("\
|
|
20
|
-
console.log("
|
|
19
|
+
console.log("\nOptions:");
|
|
20
|
+
console.log(" RWSDK_WRANGLER_CONFIG Environment variable for config path");
|
|
21
|
+
console.log("\nExamples:");
|
|
22
|
+
console.log(" npm run worker:run src/scripts/seed.ts");
|
|
23
|
+
console.log(" RWSDK_WRANGLER_CONFIG=custom.toml npm run worker:run src/scripts/seed.ts\n");
|
|
21
24
|
process.exit(1);
|
|
22
25
|
}
|
|
23
26
|
const scriptPath = resolve(process.cwd(), relativeScriptPath);
|
|
24
27
|
debug("Running worker script: %s", scriptPath);
|
|
25
|
-
const workerConfigPath =
|
|
28
|
+
const workerConfigPath = process.env.RWSDK_WRANGLER_CONFIG
|
|
29
|
+
? resolve(process.cwd(), process.env.RWSDK_WRANGLER_CONFIG)
|
|
30
|
+
: await findWranglerConfig(process.cwd());
|
|
26
31
|
debug("Using wrangler config: %s", workerConfigPath);
|
|
27
32
|
const workerConfig = unstable_readConfig({
|
|
28
33
|
config: workerConfigPath,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ViteBuilder } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* The build orchestrator is responsible for running the multi-phase build
|
|
4
|
+
* process for production. It is designed to solve the circular dependency
|
|
5
|
+
* between the worker, client, and SSR builds.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/architecture/productionBuildProcess.md
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildApp({ builder, clientEntryPoints, clientFiles, serverFiles, projectRootDir, }: {
|
|
10
|
+
builder: ViteBuilder;
|
|
11
|
+
clientEntryPoints: Set<string>;
|
|
12
|
+
clientFiles: Set<string>;
|
|
13
|
+
serverFiles: Set<string>;
|
|
14
|
+
projectRootDir: string;
|
|
15
|
+
}): Promise<void>;
|