@bleedingdev/modern-js-ultramodern-create 3.9.0-ultramodern.13 → 3.9.0-ultramodern.15
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/cjs/ultramodern-workspace/patch-inventory.cjs +0 -9
- package/dist/esm/ultramodern-workspace/patch-inventory.js +0 -9
- package/dist/esm-node/ultramodern-workspace/patch-inventory.js +0 -9
- package/package.json +9 -9
- package/release-cohort.json +48 -48
- package/templates/workspace-scripts/proof-workerd-ssr.mjs +355 -220
|
@@ -99,15 +99,6 @@ const __rspack_default_export = [
|
|
|
99
99
|
repository: true,
|
|
100
100
|
workspace: null,
|
|
101
101
|
reason: 'Build router params without string code generation in CSP/Worker bundles.'
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
packageName: 'zod',
|
|
105
|
-
version: '4.6.5',
|
|
106
|
-
path: 'patches/zod@4.6.5.patch',
|
|
107
|
-
sha256: '7640b49bb7addea5feff95947618c322ddb94b0bc834b4eb3ad349cff6fbf73b',
|
|
108
|
-
repository: true,
|
|
109
|
-
workspace: null,
|
|
110
|
-
reason: 'Disable code-generation capability probing in CSP/Worker bundles.'
|
|
111
102
|
}
|
|
112
103
|
];
|
|
113
104
|
__webpack_require__.d(__webpack_exports__, {}, {
|
|
@@ -70,15 +70,6 @@ const patch_inventory = [
|
|
|
70
70
|
repository: true,
|
|
71
71
|
workspace: null,
|
|
72
72
|
reason: 'Build router params without string code generation in CSP/Worker bundles.'
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
packageName: 'zod',
|
|
76
|
-
version: '4.6.5',
|
|
77
|
-
path: 'patches/zod@4.6.5.patch',
|
|
78
|
-
sha256: '7640b49bb7addea5feff95947618c322ddb94b0bc834b4eb3ad349cff6fbf73b',
|
|
79
|
-
repository: true,
|
|
80
|
-
workspace: null,
|
|
81
|
-
reason: 'Disable code-generation capability probing in CSP/Worker bundles.'
|
|
82
73
|
}
|
|
83
74
|
];
|
|
84
75
|
export default patch_inventory;
|
|
@@ -71,15 +71,6 @@ const patch_inventory = [
|
|
|
71
71
|
repository: true,
|
|
72
72
|
workspace: null,
|
|
73
73
|
reason: 'Build router params without string code generation in CSP/Worker bundles.'
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
packageName: 'zod',
|
|
77
|
-
version: '4.6.5',
|
|
78
|
-
path: 'patches/zod@4.6.5.patch',
|
|
79
|
-
sha256: '7640b49bb7addea5feff95947618c322ddb94b0bc834b4eb3ad349cff6fbf73b',
|
|
80
|
-
repository: true,
|
|
81
|
-
workspace: null,
|
|
82
|
-
reason: 'Disable code-generation capability probing in CSP/Worker bundles.'
|
|
83
74
|
}
|
|
84
75
|
];
|
|
85
76
|
export default patch_inventory;
|
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"engines": {
|
|
27
27
|
"node": ">=26.7.0"
|
|
28
28
|
},
|
|
29
|
-
"version": "3.9.0-ultramodern.
|
|
29
|
+
"version": "3.9.0-ultramodern.15",
|
|
30
30
|
"types": "./dist/types/index.d.ts",
|
|
31
31
|
"main": "./dist/esm-node/index.js",
|
|
32
32
|
"bin": {
|
|
@@ -83,11 +83,11 @@
|
|
|
83
83
|
],
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@babel/parser": "^8.0.6",
|
|
86
|
-
"@modern-js/app-tools-extensions": "npm:@bleedingdev/modern-js-app-tools-extensions@3.9.0-ultramodern.
|
|
87
|
-
"@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.
|
|
86
|
+
"@modern-js/app-tools-extensions": "npm:@bleedingdev/modern-js-app-tools-extensions@3.9.0-ultramodern.15",
|
|
87
|
+
"@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.15",
|
|
88
88
|
"@modern-js/codesmith": "2.6.9",
|
|
89
|
-
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.9.0-ultramodern.
|
|
90
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.
|
|
89
|
+
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.9.0-ultramodern.15",
|
|
90
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.15",
|
|
91
91
|
"esbuild": "^0.28.2",
|
|
92
92
|
"miniflare": "5.20260921.0-alpha",
|
|
93
93
|
"oxfmt": "0.66.0",
|
|
@@ -95,9 +95,9 @@
|
|
|
95
95
|
"ultracite": "7.12.0"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
|
-
"@modern-js/i18n-integration": "npm:@bleedingdev/modern-js-i18n-integration@3.9.0-ultramodern.
|
|
99
|
-
"@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.0-ultramodern.
|
|
100
|
-
"@modern-js/plugin-bff-build-extensions": "npm:@bleedingdev/modern-js-plugin-bff-build-extensions@3.9.0-ultramodern.
|
|
98
|
+
"@modern-js/i18n-integration": "npm:@bleedingdev/modern-js-i18n-integration@3.9.0-ultramodern.15",
|
|
99
|
+
"@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.0-ultramodern.15",
|
|
100
|
+
"@modern-js/plugin-bff-build-extensions": "npm:@bleedingdev/modern-js-plugin-bff-build-extensions@3.9.0-ultramodern.15",
|
|
101
101
|
"@rslib/core": "1.0.2",
|
|
102
102
|
"@scripts/rstest-config": "2.66.0",
|
|
103
103
|
"@types/node": "^26.6.2",
|
|
@@ -118,6 +118,6 @@
|
|
|
118
118
|
"type-check": "tsgo --noEmit -p tsconfig.json"
|
|
119
119
|
},
|
|
120
120
|
"ultramodern": {
|
|
121
|
-
"frameworkVersion": "3.9.0-ultramodern.
|
|
121
|
+
"frameworkVersion": "3.9.0-ultramodern.15"
|
|
122
122
|
}
|
|
123
123
|
}
|
package/release-cohort.json
CHANGED
|
@@ -51,242 +51,242 @@
|
|
|
51
51
|
{
|
|
52
52
|
"sourceName": "@modern-js/adapter-rstest",
|
|
53
53
|
"targetName": "@bleedingdev/modern-js-adapter-rstest",
|
|
54
|
-
"version": "3.9.0-ultramodern.
|
|
54
|
+
"version": "3.9.0-ultramodern.15"
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"sourceName": "@modern-js/app-tools",
|
|
58
58
|
"targetName": "@bleedingdev/modern-js-app-tools",
|
|
59
|
-
"version": "3.9.0-ultramodern.
|
|
59
|
+
"version": "3.9.0-ultramodern.15"
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"sourceName": "@modern-js/app-tools-extensions",
|
|
63
63
|
"targetName": "@bleedingdev/modern-js-app-tools-extensions",
|
|
64
|
-
"version": "3.9.0-ultramodern.
|
|
64
|
+
"version": "3.9.0-ultramodern.15"
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"sourceName": "@modern-js/backend-federation-contracts",
|
|
68
68
|
"targetName": "@bleedingdev/modern-js-backend-federation-contracts",
|
|
69
|
-
"version": "3.9.0-ultramodern.
|
|
69
|
+
"version": "3.9.0-ultramodern.15"
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"sourceName": "@modern-js/bff-core",
|
|
73
73
|
"targetName": "@bleedingdev/modern-js-bff-core",
|
|
74
|
-
"version": "3.9.0-ultramodern.
|
|
74
|
+
"version": "3.9.0-ultramodern.15"
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
"sourceName": "@modern-js/bff-effect",
|
|
78
78
|
"targetName": "@bleedingdev/modern-js-bff-effect",
|
|
79
|
-
"version": "3.9.0-ultramodern.
|
|
79
|
+
"version": "3.9.0-ultramodern.15"
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
"sourceName": "@modern-js/bff-runtime",
|
|
83
83
|
"targetName": "@bleedingdev/modern-js-bff-runtime",
|
|
84
|
-
"version": "3.9.0-ultramodern.
|
|
84
|
+
"version": "3.9.0-ultramodern.15"
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"sourceName": "@modern-js/boundary-debugger",
|
|
88
88
|
"targetName": "@bleedingdev/modern-js-boundary-debugger",
|
|
89
|
-
"version": "3.9.0-ultramodern.
|
|
89
|
+
"version": "3.9.0-ultramodern.15"
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
"sourceName": "@modern-js/builder",
|
|
93
93
|
"targetName": "@bleedingdev/modern-js-builder",
|
|
94
|
-
"version": "3.9.0-ultramodern.
|
|
94
|
+
"version": "3.9.0-ultramodern.15"
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
"sourceName": "@modern-js/code-tools",
|
|
98
98
|
"targetName": "@bleedingdev/modern-js-code-tools",
|
|
99
|
-
"version": "3.9.0-ultramodern.
|
|
99
|
+
"version": "3.9.0-ultramodern.15"
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
"sourceName": "@modern-js/create-request",
|
|
103
103
|
"targetName": "@bleedingdev/modern-js-create-request",
|
|
104
|
-
"version": "3.9.0-ultramodern.
|
|
104
|
+
"version": "3.9.0-ultramodern.15"
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
"sourceName": "@modern-js/federation-runtime",
|
|
108
108
|
"targetName": "@bleedingdev/modern-js-federation-runtime",
|
|
109
|
-
"version": "3.9.0-ultramodern.
|
|
109
|
+
"version": "3.9.0-ultramodern.15"
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
"sourceName": "@modern-js/i18n-integration",
|
|
113
113
|
"targetName": "@bleedingdev/modern-js-i18n-integration",
|
|
114
|
-
"version": "3.9.0-ultramodern.
|
|
114
|
+
"version": "3.9.0-ultramodern.15"
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
"sourceName": "@modern-js/i18n-runtime-extensions",
|
|
118
118
|
"targetName": "@bleedingdev/modern-js-i18n-runtime-extensions",
|
|
119
|
-
"version": "3.9.0-ultramodern.
|
|
119
|
+
"version": "3.9.0-ultramodern.15"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"sourceName": "@modern-js/i18n-utils",
|
|
123
123
|
"targetName": "@bleedingdev/modern-js-i18n-utils",
|
|
124
|
-
"version": "3.9.0-ultramodern.
|
|
124
|
+
"version": "3.9.0-ultramodern.15"
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"sourceName": "@modern-js/image",
|
|
128
128
|
"targetName": "@bleedingdev/modern-js-image",
|
|
129
|
-
"version": "3.9.0-ultramodern.
|
|
129
|
+
"version": "3.9.0-ultramodern.15"
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
"sourceName": "@modern-js/main-doc",
|
|
133
133
|
"targetName": "@bleedingdev/modern-js-main-doc",
|
|
134
|
-
"version": "3.9.0-ultramodern.
|
|
134
|
+
"version": "3.9.0-ultramodern.15"
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
"sourceName": "@modern-js/plugin",
|
|
138
138
|
"targetName": "@bleedingdev/modern-js-plugin",
|
|
139
|
-
"version": "3.9.0-ultramodern.
|
|
139
|
+
"version": "3.9.0-ultramodern.15"
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
"sourceName": "@modern-js/plugin-bff",
|
|
143
143
|
"targetName": "@bleedingdev/modern-js-plugin-bff",
|
|
144
|
-
"version": "3.9.0-ultramodern.
|
|
144
|
+
"version": "3.9.0-ultramodern.15"
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
"sourceName": "@modern-js/plugin-bff-build-extensions",
|
|
148
148
|
"targetName": "@bleedingdev/modern-js-plugin-bff-build-extensions",
|
|
149
|
-
"version": "3.9.0-ultramodern.
|
|
149
|
+
"version": "3.9.0-ultramodern.15"
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
152
|
"sourceName": "@modern-js/plugin-bff-extensions",
|
|
153
153
|
"targetName": "@bleedingdev/modern-js-plugin-bff-extensions",
|
|
154
|
-
"version": "3.9.0-ultramodern.
|
|
154
|
+
"version": "3.9.0-ultramodern.15"
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
"sourceName": "@modern-js/plugin-data-loader",
|
|
158
158
|
"targetName": "@bleedingdev/modern-js-plugin-data-loader",
|
|
159
|
-
"version": "3.9.0-ultramodern.
|
|
159
|
+
"version": "3.9.0-ultramodern.15"
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
"sourceName": "@modern-js/plugin-i18n",
|
|
163
163
|
"targetName": "@bleedingdev/modern-js-plugin-i18n",
|
|
164
|
-
"version": "3.9.0-ultramodern.
|
|
164
|
+
"version": "3.9.0-ultramodern.15"
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
"sourceName": "@modern-js/plugin-polyfill",
|
|
168
168
|
"targetName": "@bleedingdev/modern-js-plugin-polyfill",
|
|
169
|
-
"version": "3.9.0-ultramodern.
|
|
169
|
+
"version": "3.9.0-ultramodern.15"
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
"sourceName": "@modern-js/plugin-ssg",
|
|
173
173
|
"targetName": "@bleedingdev/modern-js-plugin-ssg",
|
|
174
|
-
"version": "3.9.0-ultramodern.
|
|
174
|
+
"version": "3.9.0-ultramodern.15"
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
"sourceName": "@modern-js/plugin-styled-components",
|
|
178
178
|
"targetName": "@bleedingdev/modern-js-plugin-styled-components",
|
|
179
|
-
"version": "3.9.0-ultramodern.
|
|
179
|
+
"version": "3.9.0-ultramodern.15"
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
"sourceName": "@modern-js/plugin-tanstack",
|
|
183
183
|
"targetName": "@bleedingdev/modern-js-plugin-tanstack",
|
|
184
|
-
"version": "3.9.0-ultramodern.
|
|
184
|
+
"version": "3.9.0-ultramodern.15"
|
|
185
185
|
},
|
|
186
186
|
{
|
|
187
187
|
"sourceName": "@modern-js/prod-server",
|
|
188
188
|
"targetName": "@bleedingdev/modern-js-prod-server",
|
|
189
|
-
"version": "3.9.0-ultramodern.
|
|
189
|
+
"version": "3.9.0-ultramodern.15"
|
|
190
190
|
},
|
|
191
191
|
{
|
|
192
192
|
"sourceName": "@modern-js/render",
|
|
193
193
|
"targetName": "@bleedingdev/modern-js-render",
|
|
194
|
-
"version": "3.9.0-ultramodern.
|
|
194
|
+
"version": "3.9.0-ultramodern.15"
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
"sourceName": "@modern-js/runtime",
|
|
198
198
|
"targetName": "@bleedingdev/modern-js-runtime",
|
|
199
|
-
"version": "3.9.0-ultramodern.
|
|
199
|
+
"version": "3.9.0-ultramodern.15"
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
"sourceName": "@modern-js/runtime-extensions",
|
|
203
203
|
"targetName": "@bleedingdev/modern-js-runtime-extensions",
|
|
204
|
-
"version": "3.9.0-ultramodern.
|
|
204
|
+
"version": "3.9.0-ultramodern.15"
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
207
|
"sourceName": "@modern-js/runtime-renderer-extensions",
|
|
208
208
|
"targetName": "@bleedingdev/modern-js-runtime-renderer-extensions",
|
|
209
|
-
"version": "3.9.0-ultramodern.
|
|
209
|
+
"version": "3.9.0-ultramodern.15"
|
|
210
210
|
},
|
|
211
211
|
{
|
|
212
212
|
"sourceName": "@modern-js/runtime-utils",
|
|
213
213
|
"targetName": "@bleedingdev/modern-js-runtime-utils",
|
|
214
|
-
"version": "3.9.0-ultramodern.
|
|
214
|
+
"version": "3.9.0-ultramodern.15"
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
"sourceName": "@modern-js/sandpack-react",
|
|
218
218
|
"targetName": "@bleedingdev/modern-js-sandpack-react",
|
|
219
|
-
"version": "3.9.0-ultramodern.
|
|
219
|
+
"version": "3.9.0-ultramodern.15"
|
|
220
220
|
},
|
|
221
221
|
{
|
|
222
222
|
"sourceName": "@modern-js/server",
|
|
223
223
|
"targetName": "@bleedingdev/modern-js-server",
|
|
224
|
-
"version": "3.9.0-ultramodern.
|
|
224
|
+
"version": "3.9.0-ultramodern.15"
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
"sourceName": "@modern-js/server-core",
|
|
228
228
|
"targetName": "@bleedingdev/modern-js-server-core",
|
|
229
|
-
"version": "3.9.0-ultramodern.
|
|
229
|
+
"version": "3.9.0-ultramodern.15"
|
|
230
230
|
},
|
|
231
231
|
{
|
|
232
232
|
"sourceName": "@modern-js/server-runtime",
|
|
233
233
|
"targetName": "@bleedingdev/modern-js-server-runtime",
|
|
234
|
-
"version": "3.9.0-ultramodern.
|
|
234
|
+
"version": "3.9.0-ultramodern.15"
|
|
235
235
|
},
|
|
236
236
|
{
|
|
237
237
|
"sourceName": "@modern-js/server-runtime-extensions",
|
|
238
238
|
"targetName": "@bleedingdev/modern-js-server-runtime-extensions",
|
|
239
|
-
"version": "3.9.0-ultramodern.
|
|
239
|
+
"version": "3.9.0-ultramodern.15"
|
|
240
240
|
},
|
|
241
241
|
{
|
|
242
242
|
"sourceName": "@modern-js/server-utils",
|
|
243
243
|
"targetName": "@bleedingdev/modern-js-server-utils",
|
|
244
|
-
"version": "3.9.0-ultramodern.
|
|
244
|
+
"version": "3.9.0-ultramodern.15"
|
|
245
245
|
},
|
|
246
246
|
{
|
|
247
247
|
"sourceName": "@modern-js/surface-resolution",
|
|
248
248
|
"targetName": "@bleedingdev/modern-js-surface-resolution",
|
|
249
|
-
"version": "3.9.0-ultramodern.
|
|
249
|
+
"version": "3.9.0-ultramodern.15"
|
|
250
250
|
},
|
|
251
251
|
{
|
|
252
252
|
"sourceName": "@modern-js/tsconfig",
|
|
253
253
|
"targetName": "@bleedingdev/modern-js-tsconfig",
|
|
254
|
-
"version": "3.9.0-ultramodern.
|
|
254
|
+
"version": "3.9.0-ultramodern.15"
|
|
255
255
|
},
|
|
256
256
|
{
|
|
257
257
|
"sourceName": "@modern-js/types",
|
|
258
258
|
"targetName": "@bleedingdev/modern-js-types",
|
|
259
|
-
"version": "3.9.0-ultramodern.
|
|
259
|
+
"version": "3.9.0-ultramodern.15"
|
|
260
260
|
},
|
|
261
261
|
{
|
|
262
262
|
"sourceName": "@modern-js/ultramodern-app-tools",
|
|
263
263
|
"targetName": "@bleedingdev/modern-js-ultramodern-app-tools",
|
|
264
|
-
"version": "3.9.0-ultramodern.
|
|
264
|
+
"version": "3.9.0-ultramodern.15"
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
"sourceName": "@modern-js/ultramodern-create",
|
|
268
268
|
"targetName": "@bleedingdev/modern-js-ultramodern-create",
|
|
269
|
-
"version": "3.9.0-ultramodern.
|
|
269
|
+
"version": "3.9.0-ultramodern.15"
|
|
270
270
|
},
|
|
271
271
|
{
|
|
272
272
|
"sourceName": "@modern-js/ultramodern-sandpack-profile",
|
|
273
273
|
"targetName": "@bleedingdev/modern-js-ultramodern-sandpack-profile",
|
|
274
|
-
"version": "3.9.0-ultramodern.
|
|
274
|
+
"version": "3.9.0-ultramodern.15"
|
|
275
275
|
},
|
|
276
276
|
{
|
|
277
277
|
"sourceName": "@modern-js/utils",
|
|
278
278
|
"targetName": "@bleedingdev/modern-js-utils",
|
|
279
|
-
"version": "3.9.0-ultramodern.
|
|
279
|
+
"version": "3.9.0-ultramodern.15"
|
|
280
280
|
}
|
|
281
281
|
],
|
|
282
282
|
"release": {
|
|
283
283
|
"tag": "latest",
|
|
284
|
-
"version": "3.9.0-ultramodern.
|
|
284
|
+
"version": "3.9.0-ultramodern.15"
|
|
285
285
|
},
|
|
286
286
|
"schema": "bleedingdev.ultramodern.release-cohort",
|
|
287
287
|
"schemaVersion": 1,
|
|
288
288
|
"source": {
|
|
289
|
-
"commit": "
|
|
289
|
+
"commit": "e4818436b36242ae6701671f9ce14e55e69c28c0",
|
|
290
290
|
"repository": "BleedingDev/ultramodern.js"
|
|
291
291
|
}
|
|
292
292
|
}
|
|
@@ -3,14 +3,12 @@ import crypto from "node:crypto";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import http from "node:http";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { parseEnv } from "node:util";
|
|
6
7
|
import { convertV4MiniflareOptions, Log, LogLevel, Miniflare } from "miniflare";
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
workspaceRoot,
|
|
12
|
-
".codex/reports/cloudflare-workerd-ssr/composition-proof.json",
|
|
13
|
-
);
|
|
9
|
+
const reportRelativePath = ".codex/reports/cloudflare-workerd-ssr/composition-proof.json";
|
|
10
|
+
// Mirrors the public website quality gate default for server-rendered HTML.
|
|
11
|
+
const defaultSsrHtmlMaxBytes = 250_000;
|
|
14
12
|
|
|
15
13
|
const assert = (condition, message) => {
|
|
16
14
|
if (!condition) {
|
|
@@ -120,86 +118,164 @@ const bindExecutedModule = (app, envelope, module) => {
|
|
|
120
118
|
};
|
|
121
119
|
};
|
|
122
120
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
121
|
+
const normalizeRoutes = (routes) => [
|
|
122
|
+
...new Set(
|
|
123
|
+
(Array.isArray(routes) ? routes : []).filter(
|
|
124
|
+
(route) => typeof route === "string" && route.startsWith("/"),
|
|
125
|
+
),
|
|
126
|
+
),
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
// The generator's distributed SSR contract: every expose except the page
|
|
130
|
+
// route is a fragment a host can server-render (see distributedSsrExposes).
|
|
131
|
+
const declaresDistributedSsrExpose = (rawApp) =>
|
|
132
|
+
Array.isArray(rawApp?.moduleFederation?.exposes) &&
|
|
133
|
+
rawApp.moduleFederation.exposes.some(
|
|
134
|
+
(expose) => typeof expose === "string" && expose !== "./Route",
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Derive what the proof must execute from topology/reference-topology.json.
|
|
139
|
+
*
|
|
140
|
+
* - `ssrRoutes`: every route the app itself must server-render on its own
|
|
141
|
+
* Worker (`cloudflare.routes.ssr` plus `cloudflare.distributedSsrProofRoutes`).
|
|
142
|
+
* - `serverRenderedRoutes`: shell routes that must compose distributed SSR
|
|
143
|
+
* boundaries. `cloudflare.distributedSsrProofRoutes` declares them; without
|
|
144
|
+
* it the shell's `cloudflare.routes.ssr` is the composition route. An empty
|
|
145
|
+
* array declares a shell whose MicroVerticals load on the client only.
|
|
146
|
+
* - `serverRenderedRemoteIds`: shell `verticalRefs` whose topology declares a
|
|
147
|
+
* distributed SSR expose, i.e. the boundaries the shell must server-render.
|
|
148
|
+
*/
|
|
149
|
+
export const planWorkerdSsrProof = (topology) => {
|
|
143
150
|
assert(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
);
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
151
|
+
topology?.schemaVersion === 1 && topology.shell && Array.isArray(topology.verticals),
|
|
152
|
+
"Invalid topology/reference-topology.json",
|
|
153
|
+
);
|
|
154
|
+
const rawApps = [topology.shell, ...topology.verticals, ...(topology.shells ?? [])];
|
|
155
|
+
const rawAppsById = new Map(rawApps.map((rawApp) => [String(rawApp.id), rawApp]));
|
|
156
|
+
const plans = rawApps.map((rawApp) => {
|
|
157
|
+
const id = String(rawApp.id);
|
|
158
|
+
const kind = rawApp.kind === "vertical" ? "vertical" : "shell";
|
|
159
|
+
const cloudflare = rawApp.cloudflare ?? {};
|
|
160
|
+
const declaredServerRenderedRoutes = cloudflare.distributedSsrProofRoutes;
|
|
161
|
+
const rawVerticalRefs = rawApp.verticalRefs ?? rawApp.moduleFederation?.verticalRefs;
|
|
162
|
+
const verticalRefs = Array.isArray(rawVerticalRefs)
|
|
163
|
+
? rawVerticalRefs.filter((ref) => typeof ref === "string")
|
|
164
|
+
: [];
|
|
165
|
+
for (const ref of verticalRefs) {
|
|
166
|
+
assert(rawAppsById.has(ref), `${id} references missing MicroVertical ${ref}`);
|
|
167
|
+
}
|
|
168
|
+
const serverRenderedRemoteIds =
|
|
169
|
+
kind === "shell"
|
|
170
|
+
? verticalRefs.filter((ref) => declaresDistributedSsrExpose(rawAppsById.get(ref)))
|
|
171
|
+
: [];
|
|
172
|
+
assert(
|
|
173
|
+
declaredServerRenderedRoutes === undefined ||
|
|
174
|
+
(Array.isArray(declaredServerRenderedRoutes) &&
|
|
175
|
+
normalizeRoutes(declaredServerRenderedRoutes).length ===
|
|
176
|
+
declaredServerRenderedRoutes.length),
|
|
177
|
+
`${id} cloudflare.distributedSsrProofRoutes must list unique absolute routes`,
|
|
178
|
+
);
|
|
179
|
+
const serverRenderedRoutes =
|
|
180
|
+
kind !== "shell"
|
|
181
|
+
? []
|
|
182
|
+
: Array.isArray(declaredServerRenderedRoutes)
|
|
183
|
+
? normalizeRoutes(declaredServerRenderedRoutes)
|
|
184
|
+
: normalizeRoutes([cloudflare.routes?.ssr]);
|
|
185
|
+
const ssrRoutes = normalizeRoutes([
|
|
186
|
+
...(Array.isArray(declaredServerRenderedRoutes) ? declaredServerRenderedRoutes : []),
|
|
187
|
+
cloudflare.routes?.ssr,
|
|
188
|
+
]);
|
|
189
|
+
const apiOnly = rawApp.surfaceProfile === "api-only";
|
|
190
|
+
assert(
|
|
191
|
+
apiOnly || normalizeRoutes([cloudflare.routes?.ssr]).length === 1,
|
|
192
|
+
`${id} declares no cloudflare.routes.ssr to prove on its own Worker`,
|
|
193
|
+
);
|
|
194
|
+
return {
|
|
195
|
+
apiOnly,
|
|
196
|
+
id,
|
|
197
|
+
kind,
|
|
198
|
+
serverRenderedRemoteIds:
|
|
199
|
+
serverRenderedRoutes.length > 0 ? serverRenderedRemoteIds : [],
|
|
200
|
+
serverRenderedRoutes,
|
|
201
|
+
ssrRoutes,
|
|
202
|
+
verticalRefs,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
166
205
|
assert(
|
|
167
|
-
|
|
168
|
-
|
|
206
|
+
plans.some((plan) => plan.kind === "shell"),
|
|
207
|
+
"Workerd SSR proof requires at least one shell",
|
|
169
208
|
);
|
|
209
|
+
assert(
|
|
210
|
+
plans.some((plan) => plan.kind === "vertical"),
|
|
211
|
+
"Workerd SSR proof requires at least one MicroVertical or headless API",
|
|
212
|
+
);
|
|
213
|
+
return { plans, rawApps };
|
|
214
|
+
};
|
|
170
215
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
path: appPath,
|
|
177
|
-
mfName: typeof moduleFederation.name === "string" ? moduleFederation.name : String(rawApp.id),
|
|
178
|
-
verticalRefs: Array.isArray(rawApp.verticalRefs ?? moduleFederation.verticalRefs)
|
|
179
|
-
? (rawApp.verticalRefs ?? moduleFederation.verticalRefs).filter((ref) => typeof ref === "string")
|
|
180
|
-
: [],
|
|
181
|
-
apiPrefix:
|
|
182
|
-
typeof rawApp.api?.bff?.prefix === "string"
|
|
183
|
-
? rawApp.api.bff.prefix.replace(/\/+$/u, "")
|
|
184
|
-
: undefined,
|
|
185
|
-
apiProtocol: rawApp.api?.protocol ?? "rest",
|
|
186
|
-
rpcRoute: rawApp.api?.protocol === "rpc" ? rawApp.cloudflare?.routes?.rpc : undefined,
|
|
187
|
-
proofRoutes: proofRoutes.length > 0 ? proofRoutes : defaultProofRoutes,
|
|
188
|
-
jsonSmokeChecks: Array.isArray(rawApp.cloudflare?.jsonSmokeChecks)
|
|
189
|
-
? rawApp.cloudflare.jsonSmokeChecks
|
|
190
|
-
: [],
|
|
191
|
-
...executedEnvelope,
|
|
192
|
-
outputRoot,
|
|
193
|
-
port,
|
|
194
|
-
wrangler,
|
|
195
|
-
};
|
|
196
|
-
});
|
|
216
|
+
const readDevVars = (outputRoot) => {
|
|
217
|
+
// Wrangler's local-secret convention: `.dev.vars` beside the executed config.
|
|
218
|
+
const devVarsPath = path.join(outputRoot, ".dev.vars");
|
|
219
|
+
return fs.existsSync(devVarsPath) ? parseEnv(fs.readFileSync(devVarsPath, "utf8")) : {};
|
|
220
|
+
};
|
|
197
221
|
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
222
|
+
const loadApps = (workspaceRoot, topology, overlay) => {
|
|
223
|
+
const { plans, rawApps } = planWorkerdSsrProof(topology);
|
|
224
|
+
return rawApps.map((rawApp, index) => {
|
|
225
|
+
const plan = plans[index];
|
|
226
|
+
const hasApiSurface = plan.kind === "vertical" && rawApp.surfaceProfile !== "ui-only";
|
|
227
|
+
const appPath = rawApp.path;
|
|
228
|
+
assert(typeof appPath === "string" && appPath.length > 0, `${plan.id} topology path is missing`);
|
|
229
|
+
const outputRoot = path.join(workspaceRoot, appPath, ".output");
|
|
230
|
+
const wranglerPath = path.join(outputRoot, "wrangler.json");
|
|
231
|
+
assert(
|
|
232
|
+
fs.existsSync(wranglerPath),
|
|
233
|
+
`${plan.id} Cloudflare output is missing; run pnpm cloudflare:build first`,
|
|
234
|
+
);
|
|
235
|
+
const wrangler = readJson(wranglerPath);
|
|
236
|
+
const executedEnvelope = hasApiSurface
|
|
237
|
+
? readExecutionEnvelope(plan.id, outputRoot, rawApp.deliveryUnit?.unitId)
|
|
238
|
+
: {};
|
|
239
|
+
const domain = rawApp.domain ?? rawApp.id;
|
|
240
|
+
const portEnv = rawApp.portEnv ??
|
|
241
|
+
(plan.kind === "vertical"
|
|
242
|
+
? `VERTICAL_${String(domain).replace(/[^a-zA-Z0-9]/gu, "_").toUpperCase()}_PORT`
|
|
243
|
+
: undefined);
|
|
244
|
+
assert(typeof portEnv === "string" && portEnv.length > 0, `${plan.id} has no declared port environment name`);
|
|
245
|
+
const configuredPort = process.env[portEnv];
|
|
246
|
+
const port = configuredPort === undefined
|
|
247
|
+
? Number(overlay.ports?.[plan.id])
|
|
248
|
+
: Number(configuredPort);
|
|
249
|
+
assert(
|
|
250
|
+
Number.isInteger(port) && port > 0 && port <= 65535,
|
|
251
|
+
`${plan.id} has an invalid local proof port from ${portEnv}`,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
...plan,
|
|
256
|
+
hasApiSurface,
|
|
257
|
+
path: appPath,
|
|
258
|
+
apiPrefix:
|
|
259
|
+
typeof rawApp.api?.bff?.prefix === "string"
|
|
260
|
+
? rawApp.api.bff.prefix.replace(/\/+$/u, "")
|
|
261
|
+
: undefined,
|
|
262
|
+
apiProtocol: rawApp.api?.protocol ?? "rest",
|
|
263
|
+
rpcRoute: rawApp.api?.protocol === "rpc" ? rawApp.cloudflare?.routes?.rpc : undefined,
|
|
264
|
+
ssrHtmlMaxBytes: Number(
|
|
265
|
+
rawApp.cloudflare?.qualityGates?.budgets?.ssrHtmlMaxBytes ??
|
|
266
|
+
defaultSsrHtmlMaxBytes,
|
|
267
|
+
),
|
|
268
|
+
jsonSmokeChecks: Array.isArray(rawApp.cloudflare?.jsonSmokeChecks)
|
|
269
|
+
? rawApp.cloudflare.jsonSmokeChecks
|
|
270
|
+
: [],
|
|
271
|
+
...executedEnvelope,
|
|
272
|
+
devVars: readDevVars(outputRoot),
|
|
273
|
+
outputRoot,
|
|
274
|
+
port,
|
|
275
|
+
wrangler,
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
};
|
|
203
279
|
|
|
204
280
|
const workerName = (app) => {
|
|
205
281
|
assert(
|
|
@@ -209,7 +285,7 @@ const workerName = (app) => {
|
|
|
209
285
|
return app.wrangler.name;
|
|
210
286
|
};
|
|
211
287
|
|
|
212
|
-
const createWorkerOptions = (app, extra = {}) => {
|
|
288
|
+
const createWorkerOptions = (app, workspaceRoot, extra = {}) => {
|
|
213
289
|
const main = typeof app.wrangler.main === "string" ? app.wrangler.main : "server/index.mjs";
|
|
214
290
|
const assets =
|
|
215
291
|
app.wrangler.assets && typeof app.wrangler.assets === "object" ? app.wrangler.assets : {};
|
|
@@ -279,6 +355,7 @@ const createWorkerOptions = (app, extra = {}) => {
|
|
|
279
355
|
modulesRoot: app.outputRoot,
|
|
280
356
|
compatibilityDate: app.wrangler.compatibility_date,
|
|
281
357
|
compatibilityFlags: app.wrangler.compatibility_flags,
|
|
358
|
+
bindings: { ...app.wrangler.vars, ...app.devVars },
|
|
282
359
|
assets: {
|
|
283
360
|
binding: typeof assets.binding === "string" ? assets.binding : "ASSETS",
|
|
284
361
|
directory: path.resolve(app.outputRoot, directory),
|
|
@@ -387,7 +464,7 @@ const resolveApiSmokeChecks = (app, shell) => {
|
|
|
387
464
|
return [...uniqueChecks.values()];
|
|
388
465
|
};
|
|
389
466
|
|
|
390
|
-
const runApiProofs = async (miniflare, shell, executionByAppId) => {
|
|
467
|
+
const runApiProofs = async (apps, miniflare, shell, executionByAppId) => {
|
|
391
468
|
const results = [];
|
|
392
469
|
for (const app of apps.filter((candidate) => candidate.hasApiSurface)) {
|
|
393
470
|
const jsonSmokeChecks = resolveApiSmokeChecks(app, shell);
|
|
@@ -477,6 +554,7 @@ const closeServer = (server) =>
|
|
|
477
554
|
});
|
|
478
555
|
|
|
479
556
|
const startWorkerdTargetServers = async (
|
|
557
|
+
apps,
|
|
480
558
|
miniflare,
|
|
481
559
|
failedServices,
|
|
482
560
|
workerConfigurations,
|
|
@@ -490,18 +568,9 @@ const startWorkerdTargetServers = async (
|
|
|
490
568
|
Number.isInteger(app.port) && app.port > 0,
|
|
491
569
|
`${app.id} requires a configured local port for all-workerd browser proof`,
|
|
492
570
|
);
|
|
493
|
-
// Miniflare assigns one shared assets storage service inside
|
|
494
|
-
// a multi-worker instance. Keep the shell composition runtime intact,
|
|
495
|
-
// but give every directly browsed MicroVertical its own runtime so its
|
|
496
|
-
// static assets and MF manifest cannot resolve from a sibling Worker.
|
|
497
571
|
const runtime =
|
|
498
572
|
app.kind === "vertical"
|
|
499
|
-
?
|
|
500
|
-
convertV4MiniflareOptions({
|
|
501
|
-
log: new Log(LogLevel.ERROR),
|
|
502
|
-
workers: [workerConfigurations[index]],
|
|
503
|
-
}),
|
|
504
|
-
)
|
|
573
|
+
? createIsolatedRuntime(workerConfigurations[index])
|
|
505
574
|
: miniflare;
|
|
506
575
|
if (runtime !== miniflare) {
|
|
507
576
|
isolatedVerticalRuntimes.push(runtime);
|
|
@@ -649,9 +718,22 @@ const decodeDistributedSsrFragmentRequest = (request) => {
|
|
|
649
718
|
};
|
|
650
719
|
};
|
|
651
720
|
|
|
721
|
+
// Miniflare assigns one shared assets storage service inside a multi-worker
|
|
722
|
+
// instance. Keep the shell composition runtime intact, but give every directly
|
|
723
|
+
// requested MicroVertical its own runtime so its static assets and MF manifest
|
|
724
|
+
// cannot resolve from a sibling Worker. Its service bindings still resolve
|
|
725
|
+
// through the shared composition runtime.
|
|
726
|
+
const createIsolatedRuntime = (workerOptions) =>
|
|
727
|
+
new Miniflare(
|
|
728
|
+
convertV4MiniflareOptions({
|
|
729
|
+
log: new Log(LogLevel.ERROR),
|
|
730
|
+
workers: [workerOptions],
|
|
731
|
+
}),
|
|
732
|
+
);
|
|
733
|
+
|
|
652
734
|
const createServiceBindings = (
|
|
653
735
|
caller,
|
|
654
|
-
{ apiBindingRequests, failedServices, fragmentBindingRequests },
|
|
736
|
+
{ apiBindingRequests, composition, failedServices, fragmentBindingRequests },
|
|
655
737
|
) => {
|
|
656
738
|
const services = Array.isArray(caller.wrangler.services) ? caller.wrangler.services : [];
|
|
657
739
|
return Object.fromEntries(
|
|
@@ -662,7 +744,7 @@ const createServiceBindings = (
|
|
|
662
744
|
);
|
|
663
745
|
return [
|
|
664
746
|
service.binding,
|
|
665
|
-
async (request
|
|
747
|
+
async (request) => {
|
|
666
748
|
if (failedServices.has(service.service)) {
|
|
667
749
|
throw new Error(
|
|
668
750
|
`Injected unavailable service binding ${service.binding} -> ${service.service}`,
|
|
@@ -699,7 +781,7 @@ const createServiceBindings = (
|
|
|
699
781
|
};
|
|
700
782
|
apiBindingRequests.push(apiBindingRequest);
|
|
701
783
|
}
|
|
702
|
-
const target = await miniflare.getWorker(service.service);
|
|
784
|
+
const target = await composition.miniflare.getWorker(service.service);
|
|
703
785
|
const response = await target.fetch(request);
|
|
704
786
|
if (apiBindingRequest !== undefined) {
|
|
705
787
|
apiBindingRequest.response = {
|
|
@@ -714,11 +796,96 @@ const createServiceBindings = (
|
|
|
714
796
|
);
|
|
715
797
|
};
|
|
716
798
|
|
|
717
|
-
const
|
|
718
|
-
const
|
|
719
|
-
|
|
799
|
+
const proveRoute = async (app, route, fetchRoute, apps, recorders) => {
|
|
800
|
+
const apiBindingRequestStart = recorders.apiBindingRequests.length;
|
|
801
|
+
const fragmentBindingRequestStart = recorders.fragmentBindingRequests.length;
|
|
802
|
+
const outboundRequestStart = recorders.outboundRequests.length;
|
|
803
|
+
const response = await fetchRoute(`https://${workerName(app)}.invalid${route}`, {
|
|
804
|
+
headers: { accept: "text/html" },
|
|
805
|
+
});
|
|
806
|
+
const html = await response.text();
|
|
807
|
+
const htmlBytes = Buffer.byteLength(html);
|
|
808
|
+
const routeOutboundRequests = recorders.outboundRequests.slice(outboundRequestStart);
|
|
809
|
+
assert(
|
|
810
|
+
response.status === 200,
|
|
811
|
+
`${app.id} returned HTTP ${response.status} for ${route} in workerd; outbound requests: ${JSON.stringify(routeOutboundRequests)}; response: ${html.slice(0, 500)}`,
|
|
812
|
+
);
|
|
813
|
+
assert(
|
|
814
|
+
response.headers.get("content-type")?.includes("text/html") === true,
|
|
815
|
+
`${app.id} did not return HTML for ${route} in workerd`,
|
|
816
|
+
);
|
|
817
|
+
// Runaway server rendering (for example a worker re-rendering its own
|
|
818
|
+
// route data requests) can still end in an oversized document.
|
|
819
|
+
assert(
|
|
820
|
+
htmlBytes <= app.ssrHtmlMaxBytes,
|
|
821
|
+
`${app.id} rendered ${htmlBytes} bytes for ${route} in workerd, over the ${app.ssrHtmlMaxBytes} byte ssrHtmlMaxBytes budget`,
|
|
822
|
+
);
|
|
823
|
+
assert(
|
|
824
|
+
!html.includes('data-modern-distributed-ssr-status="degraded"'),
|
|
825
|
+
`${app.id} rendered a degraded MicroVertical fallback for ${route} in workerd`,
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
const boundaries = collectDistributedBoundaries(html);
|
|
829
|
+
const routeApiBindingRequests = recorders.apiBindingRequests.slice(apiBindingRequestStart);
|
|
830
|
+
const routeFragmentBindingRequests = recorders.fragmentBindingRequests.slice(
|
|
831
|
+
fragmentBindingRequestStart,
|
|
832
|
+
);
|
|
833
|
+
for (const boundary of boundaries) {
|
|
834
|
+
assert(
|
|
835
|
+
boundary.status === "ready",
|
|
836
|
+
`${app.id} did not mark ${boundary.key} as ready for ${route}`,
|
|
837
|
+
);
|
|
838
|
+
assert(
|
|
839
|
+
typeof boundary.buildMarker === "string" && boundary.buildMarker.length > 0,
|
|
840
|
+
`${app.id} ${boundary.key} is missing immutable build provenance`,
|
|
841
|
+
);
|
|
842
|
+
assert(
|
|
843
|
+
/^[a-f\d]{64}$/u.test(boundary.digest ?? ""),
|
|
844
|
+
`${app.id} ${boundary.key} is missing a verified SHA-256 digest`,
|
|
845
|
+
);
|
|
846
|
+
const remote = apps.find((candidate) => candidate.id === boundary.remote);
|
|
847
|
+
assert(remote, `${app.id} rendered unknown remote ${boundary.remote}`);
|
|
848
|
+
const requests = routeFragmentBindingRequests.filter(
|
|
849
|
+
(request) =>
|
|
850
|
+
request.service === workerName(remote) &&
|
|
851
|
+
request.remote === boundary.remote &&
|
|
852
|
+
request.expose === boundary.expose,
|
|
853
|
+
);
|
|
854
|
+
const renderedCount = boundaries.filter(
|
|
855
|
+
(candidate) => candidate.key === boundary.key,
|
|
856
|
+
).length;
|
|
857
|
+
assert(
|
|
858
|
+
requests.length === renderedCount &&
|
|
859
|
+
requests.every((request) => request.pathname.includes("/_mf/fragment/")),
|
|
860
|
+
`${app.id} must compose each ${boundary.key} occurrence through its remote service binding`,
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const stylesheetHrefs = collectStylesheetHrefs(html);
|
|
865
|
+
assert(
|
|
866
|
+
new Set(stylesheetHrefs).size === stylesheetHrefs.length,
|
|
867
|
+
`${app.id} rendered duplicate distributed SSR stylesheets for ${route}`,
|
|
868
|
+
);
|
|
869
|
+
assert(
|
|
870
|
+
!routeOutboundRequests.some(({ url }) => /(?:remoteEntry|\.m?js(?:\?|$))/u.test(url)),
|
|
871
|
+
`${app.id} attempted to fetch remote JavaScript during ${route} server rendering`,
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
return {
|
|
875
|
+
worker: workerName(app),
|
|
876
|
+
route,
|
|
877
|
+
status: response.status,
|
|
878
|
+
htmlBytes,
|
|
879
|
+
boundaries,
|
|
880
|
+
fragmentBindingRequests: routeFragmentBindingRequests,
|
|
881
|
+
apiBindingRequests: routeApiBindingRequests,
|
|
882
|
+
outboundRequests: routeOutboundRequests,
|
|
883
|
+
stylesheetHrefs,
|
|
884
|
+
degradedBoundaryCount: count(html, 'data-modern-distributed-ssr-status="degraded"'),
|
|
885
|
+
};
|
|
886
|
+
};
|
|
720
887
|
|
|
721
|
-
const writeReport = () => {
|
|
888
|
+
const writeReport = (reportPath, report) => {
|
|
722
889
|
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
723
890
|
fs.writeFileSync(
|
|
724
891
|
reportPath,
|
|
@@ -726,10 +893,8 @@ const writeReport = () => {
|
|
|
726
893
|
{
|
|
727
894
|
schemaVersion: 3,
|
|
728
895
|
runtime: "workerd",
|
|
729
|
-
routes: [...new Set(proofs.map((proof) => proof.route))],
|
|
730
|
-
|
|
731
|
-
apiProofs,
|
|
732
|
-
proofs,
|
|
896
|
+
routes: [...new Set(report.proofs.map((proof) => proof.route))],
|
|
897
|
+
...report,
|
|
733
898
|
},
|
|
734
899
|
null,
|
|
735
900
|
2,
|
|
@@ -737,42 +902,39 @@ const writeReport = () => {
|
|
|
737
902
|
);
|
|
738
903
|
};
|
|
739
904
|
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
expectedRemotes.length > 0 || headlessApiVerticals.length > 0,
|
|
749
|
-
`${shell.id} has no UI MicroVerticals or headless APIs to prove`,
|
|
905
|
+
const main = async () => {
|
|
906
|
+
const workspaceRoot = path.resolve(process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd());
|
|
907
|
+
const reportPath = path.join(workspaceRoot, reportRelativePath);
|
|
908
|
+
const keepWorkerd = process.env.ULTRAMODERN_KEEP_WORKERD === "1";
|
|
909
|
+
const apps = loadApps(
|
|
910
|
+
workspaceRoot,
|
|
911
|
+
readJson(path.join(workspaceRoot, "topology/reference-topology.json")),
|
|
912
|
+
readJson(path.join(workspaceRoot, "topology/local-overlays/development.json")),
|
|
750
913
|
);
|
|
914
|
+
const shells = apps.filter((app) => app.kind === "shell");
|
|
915
|
+
assert(!keepWorkerd || shells.length === 1, "Browser workerd proof requires exactly one shell");
|
|
751
916
|
|
|
752
|
-
const
|
|
917
|
+
const recorders = {
|
|
918
|
+
apiBindingRequests: [],
|
|
919
|
+
fragmentBindingRequests: [],
|
|
920
|
+
outboundRequests: [],
|
|
921
|
+
};
|
|
753
922
|
const failedServices = new Set();
|
|
754
|
-
const
|
|
755
|
-
const outboundRequests = [];
|
|
923
|
+
const composition = {};
|
|
756
924
|
const workerConfigurations = apps.map((app) =>
|
|
757
|
-
createWorkerOptions(app, {
|
|
758
|
-
serviceBindings: createServiceBindings(app, {
|
|
759
|
-
apiBindingRequests,
|
|
760
|
-
failedServices,
|
|
761
|
-
fragmentBindingRequests,
|
|
762
|
-
}),
|
|
925
|
+
createWorkerOptions(app, workspaceRoot, {
|
|
926
|
+
serviceBindings: createServiceBindings(app, { ...recorders, composition, failedServices }),
|
|
763
927
|
async outboundService(request) {
|
|
764
928
|
const requestUrl = new URL(request.url);
|
|
765
|
-
outboundRequests.push({ callerId: app.id, url: requestUrl.href });
|
|
929
|
+
recorders.outboundRequests.push({ callerId: app.id, url: requestUrl.href });
|
|
766
930
|
return new Response("External network disabled by SSR proof", {
|
|
767
931
|
status: 502,
|
|
768
932
|
});
|
|
769
933
|
},
|
|
770
934
|
}),
|
|
771
935
|
);
|
|
772
|
-
executions = workerConfigurations.map((configuration) => configuration.executionEvidence);
|
|
773
|
-
const executionByAppId = new Map(
|
|
774
|
-
apps.map((app, index) => [app.id, executions[index]]),
|
|
775
|
-
);
|
|
936
|
+
const executions = workerConfigurations.map((configuration) => configuration.executionEvidence);
|
|
937
|
+
const executionByAppId = new Map(apps.map((app, index) => [app.id, executions[index]]));
|
|
776
938
|
const workers = workerConfigurations.map(
|
|
777
939
|
({ executionEvidence: _executionEvidence, ...configuration }) => configuration,
|
|
778
940
|
);
|
|
@@ -782,113 +944,83 @@ for (const shell of shells) {
|
|
|
782
944
|
workers,
|
|
783
945
|
}),
|
|
784
946
|
);
|
|
785
|
-
|
|
947
|
+
composition.miniflare = miniflare;
|
|
948
|
+
const report = { executions, apiProofs: [], proofs: [], verticalProofs: [] };
|
|
786
949
|
|
|
787
950
|
try {
|
|
788
|
-
for (const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
`${shell.id} returned HTTP ${response.status} for ${route} in workerd`,
|
|
800
|
-
);
|
|
801
|
-
assert(
|
|
802
|
-
!html.includes('data-modern-distributed-ssr-status="degraded"'),
|
|
803
|
-
`${shell.id} rendered a degraded MicroVertical fallback for ${route} in workerd`,
|
|
804
|
-
);
|
|
805
|
-
|
|
806
|
-
const boundaries = collectDistributedBoundaries(html);
|
|
807
|
-
assert(
|
|
808
|
-
expectedRemotes.length > 0 ? boundaries.length > 0 : boundaries.length === 0,
|
|
809
|
-
expectedRemotes.length > 0
|
|
810
|
-
? `${shell.id} rendered no distributed SSR boundaries for ${route}`
|
|
811
|
-
: `${shell.id} rendered unexpected distributed SSR boundaries for ${route}`,
|
|
812
|
-
);
|
|
813
|
-
const routeApiBindingRequests = apiBindingRequests.slice(
|
|
814
|
-
apiBindingRequestStart,
|
|
815
|
-
);
|
|
816
|
-
const routeFragmentBindingRequests = fragmentBindingRequests.slice(
|
|
817
|
-
fragmentBindingRequestStart,
|
|
818
|
-
);
|
|
819
|
-
const routeOutboundRequests = outboundRequests.slice(outboundRequestStart);
|
|
820
|
-
for (const boundary of boundaries) {
|
|
821
|
-
renderedRemoteIds.add(boundary.remote);
|
|
822
|
-
assert(
|
|
823
|
-
boundary.status === "ready",
|
|
824
|
-
`${shell.id} did not mark ${boundary.key} as ready for ${route}`,
|
|
825
|
-
);
|
|
826
|
-
assert(
|
|
827
|
-
typeof boundary.buildMarker === "string" && boundary.buildMarker.length > 0,
|
|
828
|
-
`${shell.id} ${boundary.key} is missing immutable build provenance`,
|
|
951
|
+
for (const shell of shells) {
|
|
952
|
+
// Shell routes compose declared server-rendered boundaries through the
|
|
953
|
+
// shell's entry Worker exactly as a browser request would.
|
|
954
|
+
const renderedRemoteIds = new Set();
|
|
955
|
+
for (const route of shell.ssrRoutes) {
|
|
956
|
+
const proof = await proveRoute(
|
|
957
|
+
shell,
|
|
958
|
+
route,
|
|
959
|
+
(url, init) => miniflare.dispatchFetch(url, init),
|
|
960
|
+
apps,
|
|
961
|
+
recorders,
|
|
829
962
|
);
|
|
830
963
|
assert(
|
|
831
|
-
|
|
832
|
-
`${shell.id}
|
|
964
|
+
shell.serverRenderedRoutes.length > 0 || proof.boundaries.length === 0,
|
|
965
|
+
`${shell.id} declares client composition but rendered distributed SSR boundaries for ${route}`,
|
|
833
966
|
);
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
967
|
+
if (shell.serverRenderedRoutes.includes(route)) {
|
|
968
|
+
assert(
|
|
969
|
+
shell.serverRenderedRemoteIds.length === 0 || proof.boundaries.length > 0,
|
|
970
|
+
`${shell.id} rendered no distributed SSR boundaries for ${route}`,
|
|
971
|
+
);
|
|
972
|
+
for (const boundary of proof.boundaries) {
|
|
973
|
+
renderedRemoteIds.add(boundary.remote);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
report.proofs.push({ shellId: shell.id, ...proof });
|
|
977
|
+
}
|
|
978
|
+
for (const remoteId of shell.serverRenderedRemoteIds) {
|
|
845
979
|
assert(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
`${shell.id} must compose each ${boundary.key} occurrence through its remote service binding`,
|
|
980
|
+
renderedRemoteIds.has(remoteId),
|
|
981
|
+
`${shell.id} proof routes are missing independently rendered ${remoteId} content`,
|
|
849
982
|
);
|
|
850
983
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
assert(
|
|
854
|
-
new Set(stylesheetHrefs).size === stylesheetHrefs.length,
|
|
855
|
-
`${shell.id} rendered duplicate distributed SSR stylesheets for ${route}`,
|
|
984
|
+
report.apiProofs.push(
|
|
985
|
+
...(await runApiProofs(apps, miniflare, shell, executionByAppId)),
|
|
856
986
|
);
|
|
857
|
-
assert(
|
|
858
|
-
!routeOutboundRequests.some(({ url }) => /(?:remoteEntry|\.m?js(?:\?|$))/u.test(url)),
|
|
859
|
-
`${shell.id} attempted to fetch remote JavaScript during ${route} server composition`,
|
|
860
|
-
);
|
|
861
|
-
|
|
862
|
-
proofs.push({
|
|
863
|
-
shellId: shell.id,
|
|
864
|
-
worker: workerName(shell),
|
|
865
|
-
route,
|
|
866
|
-
status: response.status,
|
|
867
|
-
boundaries,
|
|
868
|
-
fragmentBindingRequests: routeFragmentBindingRequests,
|
|
869
|
-
apiBindingRequests: routeApiBindingRequests,
|
|
870
|
-
outboundRequests: routeOutboundRequests,
|
|
871
|
-
stylesheetHrefs,
|
|
872
|
-
degradedBoundaryCount: count(html, 'data-modern-distributed-ssr-status="degraded"'),
|
|
873
|
-
});
|
|
874
987
|
}
|
|
875
988
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
989
|
+
// Every UI MicroVertical proves its own Worker at its declared SSR routes,
|
|
990
|
+
// whether or not a shell also composes it on the server.
|
|
991
|
+
for (const [index, vertical] of apps.entries()) {
|
|
992
|
+
if (vertical.kind !== "vertical" || vertical.ssrRoutes.length === 0) {
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
const runtime = createIsolatedRuntime(workers[index]);
|
|
996
|
+
try {
|
|
997
|
+
for (const route of vertical.ssrRoutes) {
|
|
998
|
+
report.verticalProofs.push({
|
|
999
|
+
appId: vertical.id,
|
|
1000
|
+
...(await proveRoute(
|
|
1001
|
+
vertical,
|
|
1002
|
+
route,
|
|
1003
|
+
(url, init) => runtime.dispatchFetch(url, init),
|
|
1004
|
+
apps,
|
|
1005
|
+
recorders,
|
|
1006
|
+
)),
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
} finally {
|
|
1010
|
+
await runtime.dispose();
|
|
1011
|
+
}
|
|
881
1012
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1013
|
+
|
|
1014
|
+
writeReport(reportPath, report);
|
|
1015
|
+
if (keepWorkerd) {
|
|
885
1016
|
const targetServers = await startWorkerdTargetServers(
|
|
1017
|
+
apps,
|
|
886
1018
|
miniflare,
|
|
887
1019
|
failedServices,
|
|
888
1020
|
workers,
|
|
889
1021
|
);
|
|
890
1022
|
console.log(`WORKERD_TARGET_URLS=${JSON.stringify(targetServers.targetUrls)}`);
|
|
891
|
-
console.log(`WORKERD_URL=${targetServers.targetUrls[
|
|
1023
|
+
console.log(`WORKERD_URL=${targetServers.targetUrls[shells[0].id]}`);
|
|
892
1024
|
try {
|
|
893
1025
|
await new Promise((resolve) => {
|
|
894
1026
|
process.once("SIGINT", resolve);
|
|
@@ -901,7 +1033,10 @@ for (const shell of shells) {
|
|
|
901
1033
|
} finally {
|
|
902
1034
|
await miniflare.dispose();
|
|
903
1035
|
}
|
|
904
|
-
}
|
|
905
1036
|
|
|
906
|
-
|
|
907
|
-
|
|
1037
|
+
console.log(`Workerd SSR composition proof passed for ${shells.length} shell(s): ${reportPath}`);
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
if (import.meta.main) {
|
|
1041
|
+
await main();
|
|
1042
|
+
}
|