rwsdk 1.0.0-beta.8 → 1.0.0-beta.9
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/e2e/index.d.mts +1 -0
- package/dist/lib/e2e/index.mjs +1 -0
- package/dist/lib/e2e/testHarness.d.mts +31 -3
- package/dist/lib/e2e/testHarness.mjs +179 -75
- package/dist/runtime/lib/db/createDb.d.ts +1 -2
- package/dist/runtime/lib/db/createDb.js +4 -0
- package/dist/runtime/script.d.ts +1 -3
- package/dist/runtime/script.js +1 -10
- package/dist/runtime/worker.js +23 -0
- package/dist/scripts/worker-run.d.mts +1 -1
- package/dist/scripts/worker-run.mjs +50 -113
- package/dist/vite/directiveModulesDevPlugin.mjs +1 -1
- package/dist/vite/runDirectivesScan.mjs +2 -1
- package/package.json +4 -3
package/dist/lib/e2e/index.d.mts
CHANGED
package/dist/lib/e2e/index.mjs
CHANGED
|
@@ -5,6 +5,7 @@ export type { Browser, Page } from "puppeteer-core";
|
|
|
5
5
|
export { DEPLOYMENT_CHECK_TIMEOUT, DEPLOYMENT_MIN_TRIES, DEPLOYMENT_TIMEOUT, DEV_SERVER_MIN_TRIES, DEV_SERVER_TIMEOUT, HYDRATION_TIMEOUT, INSTALL_DEPENDENCIES_RETRIES, PUPPETEER_TIMEOUT, SETUP_PLAYGROUND_ENV_TIMEOUT, SETUP_WAIT_TIMEOUT, TEST_MAX_RETRIES, TEST_MAX_RETRIES_PER_CODE, };
|
|
6
6
|
interface DevServerInstance {
|
|
7
7
|
url: string;
|
|
8
|
+
projectDir: string;
|
|
8
9
|
stopDev: () => Promise<void>;
|
|
9
10
|
}
|
|
10
11
|
interface DeploymentInstance {
|
|
@@ -43,17 +44,29 @@ export interface SetupPlaygroundEnvironmentOptions {
|
|
|
43
44
|
* and installs dependencies using a tarball of the SDK.
|
|
44
45
|
* This ensures that tests run in a clean, isolated environment.
|
|
45
46
|
*/
|
|
46
|
-
export declare function setupPlaygroundEnvironment(options
|
|
47
|
+
export declare function setupPlaygroundEnvironment(options: SetupPlaygroundEnvironmentOptions | string): void;
|
|
47
48
|
/**
|
|
48
49
|
* Creates a dev server instance using the shared playground environment.
|
|
49
50
|
* Automatically registers cleanup to run after the test.
|
|
50
51
|
*/
|
|
51
|
-
export declare function createDevServer(
|
|
52
|
+
export declare function createDevServer(): {
|
|
53
|
+
projectDir: string;
|
|
54
|
+
start: () => Promise<DevServerInstance>;
|
|
55
|
+
};
|
|
52
56
|
/**
|
|
53
57
|
* Creates a deployment instance using the shared playground environment.
|
|
54
58
|
* Automatically registers cleanup to run after the test.
|
|
55
59
|
*/
|
|
56
|
-
export declare function createDeployment(
|
|
60
|
+
export declare function createDeployment(): {
|
|
61
|
+
projectDir: string;
|
|
62
|
+
start: () => Promise<{
|
|
63
|
+
url: string;
|
|
64
|
+
workerName: string;
|
|
65
|
+
resourceUniqueKey: string;
|
|
66
|
+
projectDir: string;
|
|
67
|
+
cleanup: () => Promise<void>;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
57
70
|
/**
|
|
58
71
|
* Executes a test function with a retry mechanism for specific error codes.
|
|
59
72
|
* @param name - The name of the test, used for logging.
|
|
@@ -63,13 +76,25 @@ export declare function createDeployment(projectDir: string): Promise<Deployment
|
|
|
63
76
|
* called automatically on failure.
|
|
64
77
|
*/
|
|
65
78
|
export declare function runTestWithRetries(name: string, attemptFn: () => Promise<void>): Promise<void>;
|
|
79
|
+
type SDKRunner = (name: string, testLogic: (context: {
|
|
80
|
+
createDevServer: () => Promise<DevServerInstance>;
|
|
81
|
+
createDeployment: () => Promise<DeploymentInstance>;
|
|
82
|
+
browser: Browser;
|
|
83
|
+
page: Page;
|
|
84
|
+
projectDir: string;
|
|
85
|
+
}) => Promise<void>) => void;
|
|
66
86
|
declare function createTestRunner(testFn: (typeof test | typeof test.only)["concurrent"], envType: "dev" | "deploy"): (name: string, testLogic: (context: {
|
|
67
87
|
devServer?: DevServerInstance;
|
|
68
88
|
deployment?: DeploymentInstance;
|
|
69
89
|
browser: Browser;
|
|
70
90
|
page: Page;
|
|
71
91
|
url: string;
|
|
92
|
+
projectDir: string;
|
|
72
93
|
}) => Promise<void>) => void;
|
|
94
|
+
export declare const testSDK: SDKRunner & {
|
|
95
|
+
only: SDKRunner;
|
|
96
|
+
skip: typeof test.skip;
|
|
97
|
+
};
|
|
73
98
|
/**
|
|
74
99
|
* High-level test wrapper for dev server tests.
|
|
75
100
|
* Automatically skips if RWSDK_SKIP_DEV=1
|
|
@@ -83,6 +108,7 @@ export declare namespace testDev {
|
|
|
83
108
|
browser: Browser;
|
|
84
109
|
page: Page;
|
|
85
110
|
url: string;
|
|
111
|
+
projectDir: string;
|
|
86
112
|
}) => Promise<void>) => void;
|
|
87
113
|
}
|
|
88
114
|
/**
|
|
@@ -98,6 +124,7 @@ export declare namespace testDeploy {
|
|
|
98
124
|
browser: Browser;
|
|
99
125
|
page: Page;
|
|
100
126
|
url: string;
|
|
127
|
+
projectDir: string;
|
|
101
128
|
}) => Promise<void>) => void;
|
|
102
129
|
}
|
|
103
130
|
/**
|
|
@@ -110,6 +137,7 @@ export declare function testDevAndDeploy(name: string, testFn: (context: {
|
|
|
110
137
|
browser: Browser;
|
|
111
138
|
page: Page;
|
|
112
139
|
url: string;
|
|
140
|
+
projectDir: string;
|
|
113
141
|
}) => Promise<void>): void;
|
|
114
142
|
export declare namespace testDevAndDeploy {
|
|
115
143
|
var skip: (name: string, testFn?: any) => void;
|
|
@@ -20,6 +20,8 @@ let globalDevInstancePromise = null;
|
|
|
20
20
|
let globalDeploymentInstancePromise = null;
|
|
21
21
|
let globalDevInstance = null;
|
|
22
22
|
let globalDeploymentInstance = null;
|
|
23
|
+
const devInstances = [];
|
|
24
|
+
const deploymentInstances = [];
|
|
23
25
|
let hooksRegistered = false;
|
|
24
26
|
/**
|
|
25
27
|
* Registers global cleanup hooks automatically
|
|
@@ -30,11 +32,11 @@ function ensureHooksRegistered() {
|
|
|
30
32
|
// Register global afterAll to clean up the playground environment
|
|
31
33
|
afterAll(async () => {
|
|
32
34
|
const cleanupPromises = [];
|
|
33
|
-
|
|
34
|
-
cleanupPromises.push(
|
|
35
|
+
for (const instance of devInstances) {
|
|
36
|
+
cleanupPromises.push(instance.stopDev());
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
cleanupPromises.push(
|
|
38
|
+
for (const instance of deploymentInstances) {
|
|
39
|
+
cleanupPromises.push(instance.cleanup());
|
|
38
40
|
}
|
|
39
41
|
if (globalDevPlaygroundEnv) {
|
|
40
42
|
cleanupPromises.push(globalDevPlaygroundEnv.cleanup());
|
|
@@ -43,8 +45,8 @@ function ensureHooksRegistered() {
|
|
|
43
45
|
cleanupPromises.push(globalDeployPlaygroundEnv.cleanup());
|
|
44
46
|
}
|
|
45
47
|
await Promise.all(cleanupPromises);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
devInstances.length = 0;
|
|
49
|
+
deploymentInstances.length = 0;
|
|
48
50
|
globalDevPlaygroundEnv = null;
|
|
49
51
|
globalDeployPlaygroundEnv = null;
|
|
50
52
|
});
|
|
@@ -82,7 +84,7 @@ function getPlaygroundDirFromImportMeta(importMetaUrl) {
|
|
|
82
84
|
* and installs dependencies using a tarball of the SDK.
|
|
83
85
|
* This ensures that tests run in a clean, isolated environment.
|
|
84
86
|
*/
|
|
85
|
-
export function setupPlaygroundEnvironment(options
|
|
87
|
+
export function setupPlaygroundEnvironment(options) {
|
|
86
88
|
const { sourceProjectDir, monorepoRoot, dev = true, deploy = true, } = typeof options === "string" ? { sourceProjectDir: options } : options;
|
|
87
89
|
ensureHooksRegistered();
|
|
88
90
|
beforeAll(async () => {
|
|
@@ -109,7 +111,8 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
109
111
|
projectDir: devEnv.targetDir,
|
|
110
112
|
cleanup: devEnv.cleanup,
|
|
111
113
|
};
|
|
112
|
-
|
|
114
|
+
const devControl = createDevServer();
|
|
115
|
+
globalDevInstancePromise = devControl.start().then((instance) => {
|
|
113
116
|
globalDevInstance = instance;
|
|
114
117
|
return instance;
|
|
115
118
|
});
|
|
@@ -118,7 +121,7 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
118
121
|
globalDevInstancePromise.catch(() => { });
|
|
119
122
|
}
|
|
120
123
|
else {
|
|
121
|
-
|
|
124
|
+
globalDevPlaygroundEnv = null;
|
|
122
125
|
}
|
|
123
126
|
if (deploy) {
|
|
124
127
|
const deployEnv = await setupTarballEnvironment({
|
|
@@ -130,7 +133,10 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
130
133
|
projectDir: deployEnv.targetDir,
|
|
131
134
|
cleanup: deployEnv.cleanup,
|
|
132
135
|
};
|
|
133
|
-
|
|
136
|
+
const deployControl = createDeployment();
|
|
137
|
+
globalDeploymentInstancePromise = deployControl
|
|
138
|
+
.start()
|
|
139
|
+
.then((instance) => {
|
|
134
140
|
globalDeploymentInstance = instance;
|
|
135
141
|
return instance;
|
|
136
142
|
});
|
|
@@ -138,7 +144,7 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
138
144
|
globalDeploymentInstancePromise.catch(() => { });
|
|
139
145
|
}
|
|
140
146
|
else {
|
|
141
|
-
|
|
147
|
+
globalDeployPlaygroundEnv = null;
|
|
142
148
|
}
|
|
143
149
|
}, SETUP_PLAYGROUND_ENV_TIMEOUT);
|
|
144
150
|
}
|
|
@@ -146,82 +152,106 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
146
152
|
* Creates a dev server instance using the shared playground environment.
|
|
147
153
|
* Automatically registers cleanup to run after the test.
|
|
148
154
|
*/
|
|
149
|
-
export
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
export function createDevServer() {
|
|
156
|
+
ensureHooksRegistered();
|
|
157
|
+
if (!globalDevPlaygroundEnv) {
|
|
158
|
+
throw new Error("Dev playground environment not initialized. Enable `dev: true` in setupPlaygroundEnvironment.");
|
|
152
159
|
}
|
|
160
|
+
const { projectDir } = globalDevPlaygroundEnv;
|
|
153
161
|
const packageManager = process.env.PACKAGE_MANAGER || "pnpm";
|
|
154
|
-
|
|
155
|
-
timeout: DEV_SERVER_TIMEOUT,
|
|
156
|
-
minTries: DEV_SERVER_MIN_TRIES,
|
|
157
|
-
onRetry: (error, tries) => {
|
|
158
|
-
console.log(`Retrying dev server creation (attempt ${tries})... Error: ${error.message}`);
|
|
159
|
-
},
|
|
160
|
-
});
|
|
162
|
+
let instance = null;
|
|
161
163
|
return {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
projectDir,
|
|
165
|
+
start: async () => {
|
|
166
|
+
if (instance)
|
|
167
|
+
return instance;
|
|
168
|
+
if (SKIP_DEV_SERVER_TESTS) {
|
|
169
|
+
throw new Error("Dev server tests are skipped via RWSDK_SKIP_DEV=1");
|
|
170
|
+
}
|
|
171
|
+
const devResult = await pollValue(() => runDevServer(packageManager, projectDir), {
|
|
172
|
+
timeout: DEV_SERVER_TIMEOUT,
|
|
173
|
+
minTries: DEV_SERVER_MIN_TRIES,
|
|
174
|
+
onRetry: (error, tries) => {
|
|
175
|
+
console.log(`Retrying dev server creation (attempt ${tries})... Error: ${error.message}`);
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
instance = {
|
|
179
|
+
url: devResult.url,
|
|
180
|
+
projectDir,
|
|
181
|
+
stopDev: devResult.stopDev,
|
|
182
|
+
};
|
|
183
|
+
devInstances.push(instance);
|
|
184
|
+
return instance;
|
|
185
|
+
},
|
|
164
186
|
};
|
|
165
187
|
}
|
|
166
188
|
/**
|
|
167
189
|
* Creates a deployment instance using the shared playground environment.
|
|
168
190
|
* Automatically registers cleanup to run after the test.
|
|
169
191
|
*/
|
|
170
|
-
export
|
|
171
|
-
|
|
172
|
-
|
|
192
|
+
export function createDeployment() {
|
|
193
|
+
ensureHooksRegistered();
|
|
194
|
+
if (!globalDeployPlaygroundEnv) {
|
|
195
|
+
throw new Error("Deploy playground environment not initialized. Enable `deploy: true` in setupPlaygroundEnvironment.");
|
|
173
196
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Poll the URL to ensure it's live before proceeding
|
|
184
|
-
await poll(async () => {
|
|
185
|
-
try {
|
|
186
|
-
const response = await fetch(deployResult.url);
|
|
187
|
-
// We consider any response (even 4xx or 5xx) as success,
|
|
188
|
-
// as it means the worker is routable.
|
|
189
|
-
return response.status > 0;
|
|
190
|
-
}
|
|
191
|
-
catch (e) {
|
|
192
|
-
return false;
|
|
197
|
+
const { projectDir } = globalDeployPlaygroundEnv;
|
|
198
|
+
let instance = null;
|
|
199
|
+
return {
|
|
200
|
+
projectDir,
|
|
201
|
+
start: async () => {
|
|
202
|
+
if (instance)
|
|
203
|
+
return instance;
|
|
204
|
+
if (SKIP_DEPLOYMENT_TESTS) {
|
|
205
|
+
throw new Error("Deployment tests are skipped via RWSDK_SKIP_DEPLOY=1");
|
|
193
206
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
const newInstance = await pollValue(async () => {
|
|
208
|
+
const dirName = basename(projectDir);
|
|
209
|
+
const match = dirName.match(/-e2e-test-([a-f0-9]+)$/);
|
|
210
|
+
const resourceUniqueKey = match
|
|
211
|
+
? match[1]
|
|
212
|
+
: Math.random().toString(36).substring(2, 15);
|
|
213
|
+
const deployResult = await runRelease(projectDir, projectDir, resourceUniqueKey);
|
|
214
|
+
await poll(async () => {
|
|
215
|
+
try {
|
|
216
|
+
const response = await fetch(deployResult.url);
|
|
217
|
+
return response.status > 0;
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}, {
|
|
223
|
+
timeout: DEPLOYMENT_CHECK_TIMEOUT,
|
|
224
|
+
});
|
|
225
|
+
const cleanup = async () => {
|
|
226
|
+
const performCleanup = async () => {
|
|
227
|
+
if (isRelatedToTest(deployResult.workerName, resourceUniqueKey)) {
|
|
228
|
+
await deleteWorker(deployResult.workerName, projectDir, resourceUniqueKey);
|
|
229
|
+
}
|
|
230
|
+
await deleteD1Database(resourceUniqueKey, projectDir, resourceUniqueKey);
|
|
231
|
+
};
|
|
232
|
+
performCleanup().catch((error) => {
|
|
233
|
+
console.warn(`Warning: Background deployment cleanup failed: ${error.message}`);
|
|
234
|
+
});
|
|
235
|
+
return Promise.resolve();
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
url: deployResult.url,
|
|
239
|
+
workerName: deployResult.workerName,
|
|
240
|
+
resourceUniqueKey,
|
|
241
|
+
projectDir: projectDir,
|
|
242
|
+
cleanup,
|
|
243
|
+
};
|
|
244
|
+
}, {
|
|
245
|
+
timeout: DEPLOYMENT_TIMEOUT,
|
|
246
|
+
minTries: DEPLOYMENT_MIN_TRIES,
|
|
247
|
+
onRetry: (error, tries) => {
|
|
248
|
+
console.log(`Retrying deployment creation (attempt ${tries})... Error: ${error.message}`);
|
|
249
|
+
},
|
|
208
250
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
url: deployResult.url,
|
|
213
|
-
workerName: deployResult.workerName,
|
|
214
|
-
resourceUniqueKey,
|
|
215
|
-
projectDir: projectDir,
|
|
216
|
-
cleanup,
|
|
217
|
-
};
|
|
218
|
-
}, {
|
|
219
|
-
timeout: DEPLOYMENT_TIMEOUT,
|
|
220
|
-
minTries: DEPLOYMENT_MIN_TRIES,
|
|
221
|
-
onRetry: (error, tries) => {
|
|
222
|
-
console.log(`Retrying deployment creation (attempt ${tries})... Error: ${error.message}`);
|
|
251
|
+
deploymentInstances.push(newInstance);
|
|
252
|
+
return newInstance;
|
|
223
253
|
},
|
|
224
|
-
}
|
|
254
|
+
};
|
|
225
255
|
}
|
|
226
256
|
/**
|
|
227
257
|
* Executes a test function with a retry mechanism for specific error codes.
|
|
@@ -270,7 +300,7 @@ function createTestRunner(testFn, envType) {
|
|
|
270
300
|
return (name, testLogic) => {
|
|
271
301
|
if ((envType === "dev" && SKIP_DEV_SERVER_TESTS) ||
|
|
272
302
|
(envType === "deploy" && SKIP_DEPLOYMENT_TESTS)) {
|
|
273
|
-
test.skip(name
|
|
303
|
+
test.skip(`${name} (${envType})`, () => { });
|
|
274
304
|
return;
|
|
275
305
|
}
|
|
276
306
|
describe.concurrent(name, () => {
|
|
@@ -332,12 +362,86 @@ function createTestRunner(testFn, envType) {
|
|
|
332
362
|
browser: browser,
|
|
333
363
|
page: page,
|
|
334
364
|
url: instance.url,
|
|
365
|
+
projectDir: instance
|
|
366
|
+
.projectDir,
|
|
335
367
|
});
|
|
336
368
|
});
|
|
337
369
|
});
|
|
338
370
|
});
|
|
339
371
|
};
|
|
340
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Creates a low-level test runner that provides utilities for creating
|
|
375
|
+
* tests that need to perform setup actions before the server starts.
|
|
376
|
+
*/
|
|
377
|
+
function createSDKTestRunner() {
|
|
378
|
+
const internalRunner = (testFn) => {
|
|
379
|
+
return (name, testLogic) => {
|
|
380
|
+
describe.concurrent(name, () => {
|
|
381
|
+
let page;
|
|
382
|
+
let browser;
|
|
383
|
+
beforeAll(async () => {
|
|
384
|
+
const tempDir = path.join(os.tmpdir(), "rwsdk-e2e-tests");
|
|
385
|
+
const wsEndpointFile = path.join(tempDir, "wsEndpoint");
|
|
386
|
+
try {
|
|
387
|
+
const wsEndpoint = await fs.readFile(wsEndpointFile, "utf-8");
|
|
388
|
+
browser = await puppeteer.connect({
|
|
389
|
+
browserWSEndpoint: wsEndpoint,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
console.warn("Failed to connect to existing browser instance. " +
|
|
394
|
+
"This might happen if you are running a single test file. " +
|
|
395
|
+
"Launching a new browser instance instead.");
|
|
396
|
+
browser = await launchBrowser();
|
|
397
|
+
}
|
|
398
|
+
}, SETUP_WAIT_TIMEOUT);
|
|
399
|
+
afterAll(async () => {
|
|
400
|
+
if (browser) {
|
|
401
|
+
await browser.disconnect();
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
beforeEach(async () => {
|
|
405
|
+
if (!globalDevPlaygroundEnv && !globalDeployPlaygroundEnv) {
|
|
406
|
+
throw new Error("Test environment not initialized. Call setupPlaygroundEnvironment() in your test file.");
|
|
407
|
+
}
|
|
408
|
+
page = await browser.newPage();
|
|
409
|
+
page.setDefaultTimeout(PUPPETEER_TIMEOUT);
|
|
410
|
+
}, SETUP_WAIT_TIMEOUT);
|
|
411
|
+
afterEach(async () => {
|
|
412
|
+
if (page) {
|
|
413
|
+
try {
|
|
414
|
+
await page.close();
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
console.warn(`Suppressing error during page.close() in test "${name}":`, error);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
testFn(">", async () => {
|
|
422
|
+
if (!browser) {
|
|
423
|
+
throw new Error("Test environment not ready.");
|
|
424
|
+
}
|
|
425
|
+
await runTestWithRetries(name, async () => {
|
|
426
|
+
await testLogic({
|
|
427
|
+
browser: browser,
|
|
428
|
+
page: page,
|
|
429
|
+
projectDir: globalDevPlaygroundEnv?.projectDir ||
|
|
430
|
+
globalDeployPlaygroundEnv?.projectDir ||
|
|
431
|
+
"",
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
};
|
|
437
|
+
};
|
|
438
|
+
const main = internalRunner(test);
|
|
439
|
+
return Object.assign(main, {
|
|
440
|
+
only: internalRunner(test.only),
|
|
441
|
+
skip: test.skip,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
export const testSDK = createSDKTestRunner();
|
|
341
445
|
/**
|
|
342
446
|
* High-level test wrapper for dev server tests.
|
|
343
447
|
* Automatically skips if RWSDK_SKIP_DEV=1
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { Kysely } from "kysely";
|
|
2
|
-
|
|
3
|
-
export declare function createDb<T>(durableObjectBinding: DurableObjectNamespace<SqliteDurableObject>, name?: string): Kysely<T>;
|
|
2
|
+
export declare function createDb<DatabaseType>(durableObjectBinding: DurableObjectNamespace<any>, name?: string): Kysely<DatabaseType>;
|
|
@@ -5,6 +5,10 @@ export function createDb(durableObjectBinding, name = "main") {
|
|
|
5
5
|
dialect: new DOWorkerDialect({
|
|
6
6
|
kyselyExecuteQuery: (...args) => {
|
|
7
7
|
const durableObjectId = durableObjectBinding.idFromName(name);
|
|
8
|
+
// context(justinvdm, 2 Oct 2025): First prize would be a type parameter
|
|
9
|
+
// for the durable object and then use it for `durableObjectBinding`'s
|
|
10
|
+
// type, rather than casting like this. However, that would prevent
|
|
11
|
+
// users from being able to do createDb<InferredDbType> then though.
|
|
8
12
|
const stub = durableObjectBinding.get(durableObjectId);
|
|
9
13
|
stub.initialize();
|
|
10
14
|
return stub.kyselyExecuteQuery(...args);
|
package/dist/runtime/script.d.ts
CHANGED
package/dist/runtime/script.js
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
1
|
import { env } from "cloudflare:workers";
|
|
2
|
-
|
|
3
|
-
export const defineScript = (fn) => {
|
|
4
|
-
const app = defineApp([
|
|
5
|
-
async () => {
|
|
6
|
-
await fn({ env: env });
|
|
7
|
-
return new Response("Done!");
|
|
8
|
-
},
|
|
9
|
-
]);
|
|
10
|
-
return app;
|
|
11
|
-
};
|
|
2
|
+
export const defineScript = (fn) => () => fn({ env: env });
|
package/dist/runtime/worker.js
CHANGED
|
@@ -23,6 +23,29 @@ export const defineApp = (routes) => {
|
|
|
23
23
|
url.pathname = url.pathname.slice("/assets/".length);
|
|
24
24
|
return env.ASSETS.fetch(new Request(url.toString(), request));
|
|
25
25
|
}
|
|
26
|
+
else if (import.meta.env.VITE_IS_DEV_SERVER &&
|
|
27
|
+
new URL(request.url).pathname === "/__worker-run") {
|
|
28
|
+
const url = new URL(request.url);
|
|
29
|
+
const scriptPath = url.searchParams.get("script");
|
|
30
|
+
if (!scriptPath) {
|
|
31
|
+
return new Response("Missing 'script' query parameter", {
|
|
32
|
+
status: 400,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const scriptModule = await import(/* @vite-ignore */ scriptPath);
|
|
37
|
+
if (scriptModule.default) {
|
|
38
|
+
await scriptModule.default(request, env, cf);
|
|
39
|
+
}
|
|
40
|
+
return new Response("Script executed successfully");
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
console.error(`Error executing script: ${scriptPath}\n\n${e.stack}`);
|
|
44
|
+
return new Response(`Error executing script: ${e.message}`, {
|
|
45
|
+
status: 500,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
26
49
|
else if (import.meta.env.VITE_IS_DEV_SERVER &&
|
|
27
50
|
request.url.includes("/__vite_preamble__")) {
|
|
28
51
|
return new Response('import RefreshRuntime from "/@react-refresh"; RefreshRuntime.injectIntoGlobalHook(window); window.$RefreshReg$ = () => {}; window.$RefreshSig$ = () => (type) => type; window.__vite_plugin_react_preamble_installed__ = true;', {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
@@ -1,131 +1,68 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { redwood } from "../vite/index.mjs";
|
|
11
|
-
const debug = baseDebug("rwsdk:worker-run");
|
|
12
|
-
export const runWorkerScript = async (relativeScriptPath) => {
|
|
1
|
+
import dbg from "debug";
|
|
2
|
+
import getPort from "get-port";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import * as vite from "vite";
|
|
5
|
+
import { createLogger } from "vite";
|
|
6
|
+
const debug = dbg("rwsdk:worker-run");
|
|
7
|
+
const main = async () => {
|
|
8
|
+
process.env.RWSDK_WORKER_RUN = "1";
|
|
9
|
+
const relativeScriptPath = process.argv[2];
|
|
13
10
|
if (!relativeScriptPath) {
|
|
14
11
|
console.error("Error: Script path is required");
|
|
15
12
|
console.log("\nUsage:");
|
|
16
|
-
console.log("
|
|
17
|
-
console.log("\nOptions:");
|
|
18
|
-
console.log(" RWSDK_WRANGLER_CONFIG Environment variable for config path");
|
|
13
|
+
console.log(" rwsdk worker-run <script-path>");
|
|
19
14
|
console.log("\nExamples:");
|
|
20
|
-
console.log("
|
|
21
|
-
console.log(" RWSDK_WRANGLER_CONFIG=custom.toml npm run worker:run src/scripts/seed.ts\n");
|
|
15
|
+
console.log(" rwsdk worker-run src/scripts/seed.ts\n");
|
|
22
16
|
process.exit(1);
|
|
23
17
|
}
|
|
24
|
-
const scriptPath = resolve(process.cwd(), relativeScriptPath);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const workerConfig = unstable_readConfig({
|
|
31
|
-
config: workerConfigPath,
|
|
32
|
-
env: "dev",
|
|
33
|
-
});
|
|
34
|
-
const durableObjectsToExport = workerConfig.durable_objects?.bindings
|
|
35
|
-
.filter((binding) => !binding.script_name)
|
|
36
|
-
.map((binding) => binding.class_name) ?? [];
|
|
37
|
-
const workerEntryRelativePath = workerConfig.main;
|
|
38
|
-
const workerEntryPath = workerEntryRelativePath ?? path.join(process.cwd(), "src/worker.tsx");
|
|
39
|
-
const durableObjectExports = [];
|
|
40
|
-
if (durableObjectsToExport.length > 0) {
|
|
41
|
-
const resolver = enhancedResolve.create.sync({
|
|
42
|
-
extensions: [".mts", ".ts", ".tsx", ".mjs", ".js", ".jsx", ".json"],
|
|
43
|
-
});
|
|
44
|
-
const workerEntryContents = await readFile(workerEntryPath, "utf-8");
|
|
45
|
-
const workerEntryAst = parse(Lang.Tsx, workerEntryContents);
|
|
46
|
-
const exportDeclarations = [
|
|
47
|
-
...workerEntryAst.root().findAll('export { $$$EXPORTS } from "$MODULE"'),
|
|
48
|
-
...workerEntryAst.root().findAll("export { $$$EXPORTS } from '$MODULE'"),
|
|
49
|
-
...workerEntryAst.root().findAll("export { $$$EXPORTS } from '$MODULE'"),
|
|
50
|
-
];
|
|
51
|
-
for (const exportDeclaration of exportDeclarations) {
|
|
52
|
-
const moduleMatch = exportDeclaration.getMatch("MODULE");
|
|
53
|
-
const exportsMatch = exportDeclaration.getMultipleMatches("EXPORTS");
|
|
54
|
-
if (!moduleMatch || exportsMatch.length === 0) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const modulePath = moduleMatch.text();
|
|
58
|
-
const specifiers = exportsMatch.map((m) => m.text().trim());
|
|
59
|
-
for (const specifier of specifiers) {
|
|
60
|
-
if (durableObjectsToExport.includes(specifier)) {
|
|
61
|
-
const resolvedPath = resolver(path.dirname(workerEntryPath), modulePath);
|
|
62
|
-
durableObjectExports.push(`export { ${specifier} } from "${resolvedPath}";`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
18
|
+
const scriptPath = path.resolve(process.cwd(), relativeScriptPath);
|
|
19
|
+
const port = await getPort();
|
|
20
|
+
let server;
|
|
21
|
+
const cleanup = async () => {
|
|
22
|
+
if (server) {
|
|
23
|
+
await server.close();
|
|
65
24
|
}
|
|
66
|
-
|
|
67
|
-
const tmpDir = await tmp.dir({
|
|
68
|
-
prefix: "rw-worker-run-",
|
|
69
|
-
unsafeCleanup: true,
|
|
70
|
-
});
|
|
71
|
-
const relativeTmpWorkerEntryPath = "worker.tsx";
|
|
72
|
-
const tmpWorkerPath = path.join(tmpDir.path, "wrangler.json");
|
|
73
|
-
const tmpWorkerEntryPath = path.join(tmpDir.path, relativeTmpWorkerEntryPath);
|
|
74
|
-
const scriptWorkerConfig = {
|
|
75
|
-
...workerConfig,
|
|
76
|
-
configPath: tmpWorkerPath,
|
|
77
|
-
userConfigPath: tmpWorkerPath,
|
|
78
|
-
main: relativeTmpWorkerEntryPath,
|
|
25
|
+
process.exit();
|
|
79
26
|
};
|
|
27
|
+
process.on("SIGINT", cleanup);
|
|
28
|
+
process.on("SIGTERM", cleanup);
|
|
80
29
|
try {
|
|
81
|
-
await
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
configFile: false,
|
|
91
|
-
plugins: [
|
|
92
|
-
redwood({
|
|
93
|
-
configPath: tmpWorkerPath,
|
|
94
|
-
includeCloudflarePlugin: true,
|
|
95
|
-
entry: {
|
|
96
|
-
worker: tmpWorkerEntryPath,
|
|
97
|
-
},
|
|
98
|
-
}),
|
|
99
|
-
],
|
|
30
|
+
server = await vite.createServer({
|
|
31
|
+
logLevel: "silent",
|
|
32
|
+
build: {
|
|
33
|
+
outDir: ".rwsdk",
|
|
34
|
+
},
|
|
35
|
+
customLogger: createLogger("info", {
|
|
36
|
+
prefix: "[rwsdk]",
|
|
37
|
+
allowClearScreen: true,
|
|
38
|
+
}),
|
|
100
39
|
server: {
|
|
101
|
-
port
|
|
40
|
+
port,
|
|
41
|
+
host: "localhost",
|
|
102
42
|
},
|
|
103
43
|
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
44
|
+
await server.listen();
|
|
45
|
+
const url = `http://localhost:${port}/__worker-run?script=${scriptPath}`;
|
|
46
|
+
debug("Fetching %s", url);
|
|
47
|
+
const response = await fetch(url);
|
|
48
|
+
debug("Response from worker: %s", response);
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const errorText = await response.text();
|
|
51
|
+
console.error(`Error: worker-run script failed with status ${response.status}.`);
|
|
52
|
+
if (errorText) {
|
|
53
|
+
console.error("Response:", errorText);
|
|
111
54
|
}
|
|
112
|
-
|
|
113
|
-
await fetch(`http://localhost:${address.port}/`);
|
|
114
|
-
debug("Worker fetched successfully");
|
|
115
|
-
}
|
|
116
|
-
finally {
|
|
117
|
-
debug("Closing server...");
|
|
118
|
-
server.close();
|
|
119
|
-
debug("Server closed");
|
|
55
|
+
process.exit(1);
|
|
120
56
|
}
|
|
57
|
+
const responseText = await response.text();
|
|
58
|
+
debug("Response from worker: %s", responseText);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.error("rwsdk: Error running script:\n\n%s", e.message);
|
|
62
|
+
process.exit(1);
|
|
121
63
|
}
|
|
122
64
|
finally {
|
|
123
|
-
|
|
124
|
-
debug("Temporary files cleaned up");
|
|
65
|
+
await cleanup();
|
|
125
66
|
}
|
|
126
|
-
// todo(justinvdm, 01 Apr 2025): Investigate what handles are remaining open
|
|
127
|
-
process.exit(0);
|
|
128
67
|
};
|
|
129
|
-
|
|
130
|
-
runWorkerScript(process.argv[2]);
|
|
131
|
-
}
|
|
68
|
+
main();
|
|
@@ -38,7 +38,7 @@ export const directiveModulesDevPlugin = ({ clientFiles, serverFiles, projectRoo
|
|
|
38
38
|
return {
|
|
39
39
|
name: "rwsdk:directive-modules-dev",
|
|
40
40
|
configureServer(server) {
|
|
41
|
-
if (!process.env.VITE_IS_DEV_SERVER
|
|
41
|
+
if (!process.env.VITE_IS_DEV_SERVER) {
|
|
42
42
|
resolveScanPromise();
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
@@ -254,7 +254,8 @@ export const runDirectivesScan = async ({ rootConfig, environments, clientFiles,
|
|
|
254
254
|
}
|
|
255
255
|
};
|
|
256
256
|
const deferredLog = (message) => {
|
|
257
|
+
const doLog = process.env.RWSDK_WORKER_RUN ? log : console.log;
|
|
257
258
|
setTimeout(() => {
|
|
258
|
-
|
|
259
|
+
doLog(message);
|
|
259
260
|
}, 500);
|
|
260
261
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rwsdk",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.9",
|
|
4
4
|
"description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -145,11 +145,13 @@
|
|
|
145
145
|
"@vitejs/plugin-react": "~5.0.0",
|
|
146
146
|
"chokidar": "~4.0.0",
|
|
147
147
|
"debug": "~4.4.0",
|
|
148
|
+
"decompress": "~4.2.1",
|
|
148
149
|
"enhanced-resolve": "~5.18.1",
|
|
149
150
|
"eventsource-parser": "~3.0.0",
|
|
150
151
|
"execa": "~9.6.0",
|
|
151
152
|
"find-up": "~8.0.0",
|
|
152
153
|
"fs-extra": "~11.3.0",
|
|
154
|
+
"get-port": "^7.1.0",
|
|
153
155
|
"glob": "~11.0.1",
|
|
154
156
|
"ignore": "~7.0.4",
|
|
155
157
|
"jsonc-parser": "~3.3.1",
|
|
@@ -166,8 +168,7 @@
|
|
|
166
168
|
"ts-morph": "~27.0.0",
|
|
167
169
|
"unique-names-generator": "~4.7.1",
|
|
168
170
|
"vibe-rules": "~0.3.0",
|
|
169
|
-
"vite-tsconfig-paths": "~5.1.4"
|
|
170
|
-
"decompress": "~4.2.1"
|
|
171
|
+
"vite-tsconfig-paths": "~5.1.4"
|
|
171
172
|
},
|
|
172
173
|
"peerDependencies": {
|
|
173
174
|
"@cloudflare/vite-plugin": "^1.12.4",
|