@bleedingdev/modern-js-app-tools-extensions 3.9.3-ultramodern.1 → 3.9.3-ultramodern.11
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/build-config/build-environment.js +7 -5
- package/dist/cjs/build-config/internal-effect-discovery.js +94 -6
- package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +24 -3
- package/dist/cjs/native-type-checker.js +66 -20
- package/dist/cjs/release-identity.js +1 -1
- package/dist/cjs/renderer-build-identity.js +13 -3
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +18 -1
- package/dist/esm/build-config/build-environment.mjs +7 -5
- package/dist/esm/build-config/internal-effect-discovery.mjs +90 -5
- package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +24 -3
- package/dist/esm/native-type-checker.mjs +68 -22
- package/dist/esm/release-identity.mjs +1 -1
- package/dist/esm/renderer-build-identity.mjs +13 -3
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +18 -1
- package/dist/esm-node/build-config/build-environment.mjs +7 -5
- package/dist/esm-node/build-config/internal-effect-discovery.mjs +90 -5
- package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +24 -3
- package/dist/esm-node/native-type-checker.mjs +68 -22
- package/dist/esm-node/release-identity.mjs +1 -1
- package/dist/esm-node/renderer-build-identity.mjs +13 -3
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +18 -1
- package/dist/types/build-config/build-environment.d.ts +3 -1
- package/dist/types/build-config/internal-effect-discovery.d.ts +3 -1
- package/dist/types/cloudflare/worker-manifest.d.ts +3 -2
- package/dist/types/config.d.ts +4 -0
- package/dist/types/native-type-checker.d.ts +4 -2
- package/dist/types/release-identity.d.ts +10 -0
- package/dist/types/renderer-build-identity.d.ts +12 -1
- package/package.json +27 -27
- package/src/build-config/build-environment.ts +11 -9
- package/src/build-config/internal-effect-discovery.ts +238 -19
- package/src/cloudflare/worker-manifest.ts +7 -3
- package/src/cloudflare-output-verifier/mutation-policy.ts +41 -3
- package/src/config.ts +4 -0
- package/src/native-type-checker.ts +87 -43
- package/src/release-identity.ts +11 -2
- package/src/renderer-build-identity.ts +51 -11
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +18 -1
|
@@ -19,7 +19,8 @@ export interface VerifyCloudflareOutputMutationPolicyOptions {
|
|
|
19
19
|
excludePaths?: string[];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const SOURCE_SCAN_FILE_PATTERN = /\.(?:[cm]?[jt]
|
|
22
|
+
const SOURCE_SCAN_FILE_PATTERN = /\.(?:[cm]?[jt]sx?)$/u;
|
|
23
|
+
const DECLARATION_FILE_PATTERN = /\.d\.[cm]?ts$/u;
|
|
23
24
|
const SOURCE_SCAN_IGNORED_DIRECTORY_NAMES = new Set([
|
|
24
25
|
'.codex',
|
|
25
26
|
'.fastcontext',
|
|
@@ -115,7 +116,11 @@ export const walkFiles = async (root: string): Promise<string[]> => {
|
|
|
115
116
|
continue;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
entry.name === 'package.json' ||
|
|
121
|
+
(SOURCE_SCAN_FILE_PATTERN.test(entry.name) &&
|
|
122
|
+
!DECLARATION_FILE_PATTERN.test(entry.name))
|
|
123
|
+
) {
|
|
119
124
|
files.push(entryPath);
|
|
120
125
|
}
|
|
121
126
|
}
|
|
@@ -151,8 +156,38 @@ export const verifyCloudflareOutputMutationPolicy = async (
|
|
|
151
156
|
}
|
|
152
157
|
throw error;
|
|
153
158
|
}
|
|
159
|
+
let executableSources = [source];
|
|
160
|
+
if (path.basename(file) === 'package.json') {
|
|
161
|
+
try {
|
|
162
|
+
const manifest: unknown = JSON.parse(source);
|
|
163
|
+
if (!isRecord(manifest)) {
|
|
164
|
+
throw new TypeError('Package metadata must be an object.');
|
|
165
|
+
}
|
|
166
|
+
const scripts = Object.hasOwn(manifest, 'scripts')
|
|
167
|
+
? manifest.scripts
|
|
168
|
+
: {};
|
|
169
|
+
if (!isRecord(scripts)) {
|
|
170
|
+
throw new TypeError('Package scripts must be an object.');
|
|
171
|
+
}
|
|
172
|
+
const commands = Object.values(scripts);
|
|
173
|
+
if (
|
|
174
|
+
!commands.every((value): value is string => typeof value === 'string')
|
|
175
|
+
) {
|
|
176
|
+
throw new TypeError('Package scripts must contain only strings.');
|
|
177
|
+
}
|
|
178
|
+
executableSources = commands;
|
|
179
|
+
} catch {
|
|
180
|
+
addIssue(issues, {
|
|
181
|
+
code: 'invalid-package',
|
|
182
|
+
message:
|
|
183
|
+
'Cannot verify executable package lifecycle scripts: invalid package metadata.',
|
|
184
|
+
path: file,
|
|
185
|
+
});
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
154
189
|
for (const forbidden of FORBIDDEN_MUTATION_PATTERNS) {
|
|
155
|
-
if (forbidden.pattern.test(
|
|
190
|
+
if (executableSources.some(value => forbidden.pattern.test(value))) {
|
|
156
191
|
addIssue(issues, {
|
|
157
192
|
code: forbidden.code,
|
|
158
193
|
message: forbidden.message,
|
|
@@ -167,3 +202,6 @@ export const verifyCloudflareOutputMutationPolicy = async (
|
|
|
167
202
|
issues,
|
|
168
203
|
};
|
|
169
204
|
};
|
|
205
|
+
|
|
206
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
207
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
package/src/config.ts
CHANGED
|
@@ -247,6 +247,10 @@ export interface CloudflareWorkerDeployConfig {
|
|
|
247
247
|
* `env[binding].fetch(request)`.
|
|
248
248
|
*/
|
|
249
249
|
services?: CloudflareWorkerServiceBindingConfig[];
|
|
250
|
+
/** Native Worker context profiles, registered before federated exposes load. */
|
|
251
|
+
federation?: {
|
|
252
|
+
contextProfiles?: readonly string[];
|
|
253
|
+
};
|
|
250
254
|
/**
|
|
251
255
|
* First-class Workers VPC service bindings to private origins reached
|
|
252
256
|
* through a Cloudflare Tunnel. Modern.js writes these to `wrangler.json`
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
1
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import {
|
|
4
4
|
existsSync,
|
|
5
5
|
readFileSync,
|
|
6
6
|
realpathSync,
|
|
7
|
-
statSync,
|
|
8
7
|
unlinkSync,
|
|
9
8
|
writeFileSync,
|
|
10
9
|
} from 'node:fs';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { promisify } from 'node:util';
|
|
13
13
|
import { refreshTsgoCheckerConfig } from '@modern-js/builder';
|
|
14
14
|
import type { Rspack, RspackChain } from '@rsbuild/core';
|
|
15
15
|
import { type ParseError, parse, printParseErrorCode } from 'jsonc-parser';
|
|
16
|
+
import { resolveEffectTsgoCompiler } from './build-config/build-environment';
|
|
16
17
|
|
|
17
18
|
const execute = promisify(execFile);
|
|
18
19
|
const name = 'UltramodernNativeTypeChecker';
|
|
@@ -20,44 +21,63 @@ const name = 'UltramodernNativeTypeChecker';
|
|
|
20
21
|
export interface NativeTypeCheckerCommand {
|
|
21
22
|
executable: string;
|
|
22
23
|
args: readonly string[];
|
|
24
|
+
/** The selected package's native config API. tsrs has no --showConfig CLI. */
|
|
25
|
+
configApi?: string;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
/**
|
|
28
|
+
/** Resolve the Effect-enabled native compiler from the application's origin. */
|
|
26
29
|
export function resolveNativeTypeCheckerCommand(
|
|
27
|
-
|
|
30
|
+
from: string,
|
|
28
31
|
): NativeTypeCheckerCommand {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
const launcher = realpathSync(path.resolve(directory, bin));
|
|
46
|
-
const relative = path.relative(directory, launcher);
|
|
47
|
-
if (
|
|
48
|
-
!relative ||
|
|
49
|
-
path.isAbsolute(relative) ||
|
|
50
|
-
relative === '..' ||
|
|
51
|
-
relative.startsWith(`..${path.sep}`) ||
|
|
52
|
-
!statSync(launcher).isFile()
|
|
53
|
-
) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
`${name} public tsc bin must belong to its selected package.`,
|
|
56
|
-
);
|
|
32
|
+
const executable = resolveEffectTsgoCompiler({ from });
|
|
33
|
+
const version = execFileSync(executable, ['--version'], {
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
env: {
|
|
36
|
+
...process.env,
|
|
37
|
+
TSRS_CHECKER_ASSIGNMENT: process.env.TSRS_CHECKER_ASSIGNMENT || 'go',
|
|
38
|
+
},
|
|
39
|
+
timeout: 10_000,
|
|
40
|
+
});
|
|
41
|
+
if (version.includes('(tsrs ')) {
|
|
42
|
+
return {
|
|
43
|
+
executable,
|
|
44
|
+
args: [],
|
|
45
|
+
configApi: createRequire(from).resolve('tsrs/sync'),
|
|
46
|
+
};
|
|
57
47
|
}
|
|
58
|
-
|
|
48
|
+
// TypeScript-Go supports --showConfig, including project references. Its
|
|
49
|
+
// older native config API lacks tsrs's diagnostics and reference fields.
|
|
50
|
+
return {
|
|
51
|
+
executable,
|
|
52
|
+
args: [],
|
|
53
|
+
};
|
|
59
54
|
}
|
|
60
55
|
|
|
56
|
+
// Use the real native parser, not a handwritten tsconfig/extends interpreter.
|
|
57
|
+
// Running the client in a child also applies assignment to its native API
|
|
58
|
+
// process without changing the build/dev server's environment.
|
|
59
|
+
const inspectNativeConfig = `
|
|
60
|
+
import { pathToFileURL } from 'node:url';
|
|
61
|
+
const [modulePath, compiler, configFile] = process.argv.slice(1);
|
|
62
|
+
const { API, formatDiagnostics } = await import(pathToFileURL(modulePath).href);
|
|
63
|
+
const api = new API({ tsserverPath: compiler });
|
|
64
|
+
try {
|
|
65
|
+
const config = api.parseConfigFile(configFile);
|
|
66
|
+
if (config.errors.length) {
|
|
67
|
+
process.stdout.write(formatDiagnostics(config.errors, api));
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
} else {
|
|
70
|
+
process.stdout.write(JSON.stringify({
|
|
71
|
+
compilerOptions: config.options,
|
|
72
|
+
files: config.fileNames,
|
|
73
|
+
references: config.projectReferences,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
api.close();
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
|
|
61
81
|
interface TypeCheckerConfig {
|
|
62
82
|
compilerOptions?: Record<string, unknown>;
|
|
63
83
|
references?: Array<{ path: string }>;
|
|
@@ -230,25 +250,51 @@ export class UltramodernNativeTypeChecker {
|
|
|
230
250
|
private async run(args: string[]): Promise<string> {
|
|
231
251
|
try {
|
|
232
252
|
const selected = this.options.compiler();
|
|
233
|
-
const command =
|
|
253
|
+
const command: NativeTypeCheckerCommand =
|
|
234
254
|
typeof selected === 'string'
|
|
235
255
|
? { executable: selected, args: [] }
|
|
236
256
|
: selected;
|
|
257
|
+
const inspect = args[0] === '--showConfig' && command.configApi;
|
|
237
258
|
const result = await execute(
|
|
238
|
-
command.executable,
|
|
239
|
-
|
|
259
|
+
inspect ? process.execPath : command.executable,
|
|
260
|
+
inspect
|
|
261
|
+
? [
|
|
262
|
+
'--input-type=module',
|
|
263
|
+
'--eval',
|
|
264
|
+
inspectNativeConfig,
|
|
265
|
+
command.configApi!,
|
|
266
|
+
command.executable,
|
|
267
|
+
args[args.indexOf('--project') + 1],
|
|
268
|
+
]
|
|
269
|
+
: [...command.args, ...args],
|
|
240
270
|
{
|
|
241
271
|
cwd: path.dirname(this.options.configFile),
|
|
272
|
+
// Apply to every command, including explicit paths and TSRS_BINARY.
|
|
273
|
+
// Keep caller selection; do not change the dev server's environment.
|
|
274
|
+
env: {
|
|
275
|
+
...process.env,
|
|
276
|
+
TSRS_CHECKER_ASSIGNMENT:
|
|
277
|
+
process.env.TSRS_CHECKER_ASSIGNMENT || 'go',
|
|
278
|
+
},
|
|
242
279
|
maxBuffer: 16 * 1024 * 1024,
|
|
280
|
+
...(inspect ? { timeout: 10_000 } : {}),
|
|
243
281
|
},
|
|
244
282
|
);
|
|
245
283
|
return result.stdout;
|
|
246
284
|
} catch (cause) {
|
|
247
|
-
const output = cause as {
|
|
285
|
+
const output = cause as {
|
|
286
|
+
stdout?: string;
|
|
287
|
+
stderr?: string;
|
|
288
|
+
code?: number | string;
|
|
289
|
+
signal?: NodeJS.Signals;
|
|
290
|
+
};
|
|
248
291
|
const diagnostics = `${output.stdout ?? ''}${output.stderr ?? ''}`;
|
|
249
|
-
throw
|
|
250
|
-
|
|
251
|
-
|
|
292
|
+
throw Object.assign(
|
|
293
|
+
new Error(
|
|
294
|
+
`${name} failed:\n${diagnostics || (cause instanceof Error ? cause.message : String(cause))}${missingJsxRuntimeHint(diagnostics)}`,
|
|
295
|
+
{ cause },
|
|
296
|
+
),
|
|
297
|
+
{ code: output.code, signal: output.signal },
|
|
252
298
|
);
|
|
253
299
|
}
|
|
254
300
|
}
|
|
@@ -333,9 +379,7 @@ export class UltramodernNativeTypeChecker {
|
|
|
333
379
|
}
|
|
334
380
|
} catch (cause) {
|
|
335
381
|
compilation.errors.push(
|
|
336
|
-
|
|
337
|
-
cause,
|
|
338
|
-
}),
|
|
382
|
+
cause instanceof Error ? cause : new Error(String(cause)),
|
|
339
383
|
);
|
|
340
384
|
}
|
|
341
385
|
});
|
package/src/release-identity.ts
CHANGED
|
@@ -51,8 +51,7 @@ export const createUltramodernReleaseBuildMarker = ({
|
|
|
51
51
|
.update(
|
|
52
52
|
`${BUILD_MARKER_NAMESPACE}:${unitId}:${generationBuildMarker}:${sourceRevision}`,
|
|
53
53
|
)
|
|
54
|
-
.digest('hex')
|
|
55
|
-
.slice(0, 16);
|
|
54
|
+
.digest('hex');
|
|
56
55
|
|
|
57
56
|
export const resolveUltramodernSourceRevision = (
|
|
58
57
|
workspaceRoot = process.cwd(),
|
|
@@ -119,6 +118,16 @@ export const resolveUltramodernSourceRevision = (
|
|
|
119
118
|
return revision;
|
|
120
119
|
};
|
|
121
120
|
|
|
121
|
+
/**
|
|
122
|
+
* The one release identity of a delivery unit, resolvable before its build.
|
|
123
|
+
*
|
|
124
|
+
* For a clean source revision the build marker is a 64-hex digest of the unit
|
|
125
|
+
* id, its generation marker and the revision. API bundles carry it, and a UI
|
|
126
|
+
* unit's renderer adopts it as its `buildId`, so contracts, release plans and
|
|
127
|
+
* Worker names derived from it before the build match the built
|
|
128
|
+
* `ultramodern-build.json`, `renderer-build.json`, chunk banners and envelope.
|
|
129
|
+
* A dirty or non-Git checkout yields the non-promotable `workspace` identity.
|
|
130
|
+
*/
|
|
122
131
|
export const resolveUltramodernReleaseIdentity = ({
|
|
123
132
|
generationBuildMarker,
|
|
124
133
|
sourceRevision: configuredSourceRevision,
|
|
@@ -25,7 +25,18 @@ export interface RendererBuildIdentityOptions {
|
|
|
25
25
|
mode: 'development' | 'production';
|
|
26
26
|
/** Delivery-unit app id; defaults to the application package name. */
|
|
27
27
|
appId?: string;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The declared delivery unit's release identity, resolved before the build
|
|
30
|
+
* by `resolveUltramodernReleaseIdentity`. A promotable production build
|
|
31
|
+
* adopts its buildMarker as the buildId, so consumers that resolve the unit
|
|
32
|
+
* identity before compiling see the marker the built artifacts carry.
|
|
33
|
+
*/
|
|
34
|
+
deliveryUnit?: {
|
|
35
|
+
readonly unitId: string;
|
|
36
|
+
readonly buildMarker: string;
|
|
37
|
+
readonly sourceRevision: string;
|
|
38
|
+
};
|
|
39
|
+
/** Externally authenticated source revision (CI) for an undeclared app. */
|
|
29
40
|
sourceRevision?: string;
|
|
30
41
|
/** Actual final entry ownership, supplied by the selected renderer composition. */
|
|
31
42
|
routerBindings: RendererRouterBindings;
|
|
@@ -49,6 +60,8 @@ const LOCKFILES = [
|
|
|
49
60
|
'bun.lockb',
|
|
50
61
|
];
|
|
51
62
|
|
|
63
|
+
const RELEASE_BUILD_MARKER_PATTERN = /^[a-f0-9]{64}$/u;
|
|
64
|
+
|
|
52
65
|
/** Stable per process: one dev session or dirty build keeps one buildId. */
|
|
53
66
|
const processNonce = randomUUID();
|
|
54
67
|
|
|
@@ -132,20 +145,47 @@ export function resolveRendererBuildIdentities(
|
|
|
132
145
|
throw new Error(
|
|
133
146
|
'Renderer application identity requires a delivery artifact appId or a package name.',
|
|
134
147
|
);
|
|
148
|
+
const { deliveryUnit } = options;
|
|
149
|
+
if (
|
|
150
|
+
deliveryUnit !== undefined &&
|
|
151
|
+
(typeof deliveryUnit.unitId !== 'string' ||
|
|
152
|
+
!deliveryUnit.unitId ||
|
|
153
|
+
deliveryUnit.unitId.trim() !== deliveryUnit.unitId)
|
|
154
|
+
)
|
|
155
|
+
throw new Error(
|
|
156
|
+
'Renderer delivery-unit identity requires a nonempty trimmed unitId.',
|
|
157
|
+
);
|
|
135
158
|
const profileKey = rendererProfileKey(options.profile);
|
|
136
159
|
const sourceRevision = resolveUltramodernSourceRevision(
|
|
137
160
|
projectRoot,
|
|
138
|
-
options.sourceRevision,
|
|
139
|
-
);
|
|
140
|
-
const buildId = sha256(
|
|
141
|
-
JSON.stringify([
|
|
142
|
-
profileKey,
|
|
143
|
-
lockfileHash(projectRoot),
|
|
144
|
-
options.mode === 'development' || sourceRevision === 'workspace'
|
|
145
|
-
? processNonce
|
|
146
|
-
: sourceRevision,
|
|
147
|
-
]),
|
|
161
|
+
deliveryUnit ? deliveryUnit.sourceRevision : options.sourceRevision,
|
|
148
162
|
);
|
|
163
|
+
const promotable =
|
|
164
|
+
options.mode === 'production' && sourceRevision !== 'workspace';
|
|
165
|
+
let buildId: string;
|
|
166
|
+
if (promotable && deliveryUnit) {
|
|
167
|
+
// One identity per delivery unit, known before compilation.
|
|
168
|
+
if (
|
|
169
|
+
deliveryUnit.sourceRevision !== sourceRevision ||
|
|
170
|
+
!RELEASE_BUILD_MARKER_PATTERN.test(deliveryUnit.buildMarker)
|
|
171
|
+
)
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Renderer delivery unit ${deliveryUnit.unitId} must carry the release identity of source revision ${sourceRevision}.`,
|
|
174
|
+
);
|
|
175
|
+
buildId = deliveryUnit.buildMarker;
|
|
176
|
+
} else {
|
|
177
|
+
// Undeclared apps and non-promotable builds (development, dirty trees)
|
|
178
|
+
// keep a per-app id; non-promotable ids are unique per process.
|
|
179
|
+
buildId = sha256(
|
|
180
|
+
JSON.stringify([
|
|
181
|
+
appId,
|
|
182
|
+
deliveryUnit?.unitId ?? null,
|
|
183
|
+
profileKey,
|
|
184
|
+
lockfileHash(projectRoot),
|
|
185
|
+
promotable ? sourceRevision : processNonce,
|
|
186
|
+
]),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
149
189
|
const identities = Object.fromEntries(
|
|
150
190
|
entries.map(entryName => [
|
|
151
191
|
entryName,
|
|
@@ -306,7 +306,24 @@ async function invokeNativeRouteWorker(route, identity, request, env, ctx) {
|
|
|
306
306
|
const resources =
|
|
307
307
|
nativeRenderer?.renderer === identity.renderer &&
|
|
308
308
|
Object.hasOwn(nativeRenderer.entries, route.entryName)
|
|
309
|
-
?
|
|
309
|
+
? {
|
|
310
|
+
...nativeRenderer.entries[route.entryName],
|
|
311
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederation')
|
|
312
|
+
? { workerFederation: nativeRenderer.workerFederation }
|
|
313
|
+
: {}),
|
|
314
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederationManifestUrl')
|
|
315
|
+
? {
|
|
316
|
+
workerFederationManifestUrl:
|
|
317
|
+
nativeRenderer.workerFederationManifestUrl,
|
|
318
|
+
}
|
|
319
|
+
: {}),
|
|
320
|
+
...(Object.hasOwn(nativeRenderer, 'workerFederationServices')
|
|
321
|
+
? {
|
|
322
|
+
workerFederationServices:
|
|
323
|
+
nativeRenderer.workerFederationServices,
|
|
324
|
+
}
|
|
325
|
+
: {}),
|
|
326
|
+
}
|
|
310
327
|
: undefined;
|
|
311
328
|
// Native renderers have no Flight transport. Reject before the bundle loads.
|
|
312
329
|
if (
|