@ripple-ts/vite-plugin 0.3.10 → 0.3.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/CHANGELOG.md +7 -0
- package/package.json +3 -3
- package/src/index.js +119 -10
- package/src/load-config.js +63 -6
- package/types/index.d.ts +33 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Vite plugin for Ripple",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.11",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/index.js",
|
|
9
9
|
"main": "src/index.js",
|
|
@@ -32,13 +32,13 @@
|
|
|
32
32
|
"url": "https://github.com/Ripple-TS/ripple/issues"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@ripple-ts/adapter": "0.3.
|
|
35
|
+
"@ripple-ts/adapter": "0.3.11"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^24.3.0",
|
|
39
39
|
"type-fest": "^5.4.4",
|
|
40
40
|
"vite": "^8.0.0",
|
|
41
|
-
"ripple": "0.3.
|
|
41
|
+
"ripple": "0.3.11"
|
|
42
42
|
},
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
package/src/index.js
CHANGED
|
@@ -27,9 +27,19 @@ import { patch_global_fetch, is_rpc_request, handle_rpc_request } from '@ripple-
|
|
|
27
27
|
|
|
28
28
|
// Re-export route classes
|
|
29
29
|
export { RenderRoute, ServerRoute } from './routes.js';
|
|
30
|
+
export {
|
|
31
|
+
getRippleConfigPath,
|
|
32
|
+
loadRippleConfig,
|
|
33
|
+
resolveRippleConfig,
|
|
34
|
+
rippleConfigExists,
|
|
35
|
+
} from './load-config.js';
|
|
30
36
|
|
|
31
37
|
const VITE_FS_PREFIX = '/@fs/';
|
|
32
38
|
const IS_WINDOWS = process.platform === 'win32';
|
|
39
|
+
const VIRTUAL_HYDRATE_ID = 'virtual:ripple-hydrate';
|
|
40
|
+
const RESOLVED_VIRTUAL_HYDRATE_ID = '\0virtual:ripple-hydrate';
|
|
41
|
+
const VIRTUAL_COMPAT_ID = 'virtual:ripple-compat';
|
|
42
|
+
const RESOLVED_VIRTUAL_COMPAT_ID = '\0virtual:ripple-compat';
|
|
33
43
|
|
|
34
44
|
// Dev server always runs in Node — use node:async_hooks as default runtime
|
|
35
45
|
// If the user provides adapter.runtime in their config, that will be used instead.
|
|
@@ -67,6 +77,60 @@ function getDevAsyncContext(config) {
|
|
|
67
77
|
return devAsyncContext;
|
|
68
78
|
}
|
|
69
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @param {ResolvedRippleConfig | null} config
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
function create_compat_virtual_module(config) {
|
|
85
|
+
const compat_entries = Object.entries(config?.compat ?? {});
|
|
86
|
+
|
|
87
|
+
if (compat_entries.length === 0) {
|
|
88
|
+
return `const compat = undefined;
|
|
89
|
+
globalThis.__RIPPLE_COMPAT__ = compat;
|
|
90
|
+
export { compat };
|
|
91
|
+
export default compat;
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const imports = [];
|
|
96
|
+
const properties = [];
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < compat_entries.length; i++) {
|
|
99
|
+
const [kind, entry] = compat_entries[i];
|
|
100
|
+
const local_name = `__ripple_compat_factory_${i}`;
|
|
101
|
+
|
|
102
|
+
if (entry.factory) {
|
|
103
|
+
imports.push(
|
|
104
|
+
`import { ${entry.factory} as ${local_name} } from ${JSON.stringify(entry.from)};`,
|
|
105
|
+
);
|
|
106
|
+
} else {
|
|
107
|
+
imports.push(`import ${local_name} from ${JSON.stringify(entry.from)};`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
properties.push(` ${JSON.stringify(kind)}: ${local_name}(),`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return `${imports.join('\n')}
|
|
114
|
+
|
|
115
|
+
const compat = {
|
|
116
|
+
${properties.join('\n')}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
globalThis.__RIPPLE_COMPAT__ = compat;
|
|
120
|
+
|
|
121
|
+
export { compat };
|
|
122
|
+
export default compat;
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {ResolvedRippleConfig | null} config
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
function has_route_config(config) {
|
|
131
|
+
return (config?.router.routes.length ?? 0) > 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
70
134
|
/**
|
|
71
135
|
* @param {string} filename
|
|
72
136
|
* @param {ResolvedConfig['root']} root
|
|
@@ -327,6 +391,18 @@ export function ripple(inlineOptions = {}) {
|
|
|
327
391
|
/** @type {Set<string>} File paths (relative to root) of .ripple modules with #server blocks */
|
|
328
392
|
const serverBlockModules = new Set();
|
|
329
393
|
|
|
394
|
+
/**
|
|
395
|
+
* @returns {Promise<ResolvedRippleConfig | null>}
|
|
396
|
+
*/
|
|
397
|
+
async function get_current_ripple_config() {
|
|
398
|
+
if (loadedRippleConfig) return loadedRippleConfig;
|
|
399
|
+
if (rippleConfig) return rippleConfig;
|
|
400
|
+
if (!root || !rippleConfigExists(root)) return null;
|
|
401
|
+
|
|
402
|
+
loadedRippleConfig = await loadRippleConfig(root);
|
|
403
|
+
return loadedRippleConfig;
|
|
404
|
+
}
|
|
405
|
+
|
|
330
406
|
/** @type {Plugin[]} */
|
|
331
407
|
const plugins = [
|
|
332
408
|
{
|
|
@@ -344,6 +420,12 @@ export function ripple(inlineOptions = {}) {
|
|
|
344
420
|
const projectRoot = userConfig.root || process.cwd();
|
|
345
421
|
|
|
346
422
|
if (rippleConfigExists(projectRoot)) {
|
|
423
|
+
loadedRippleConfig = await loadRippleConfig(projectRoot);
|
|
424
|
+
|
|
425
|
+
if (!has_route_config(loadedRippleConfig)) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
|
|
347
429
|
const htmlInput = path.join(projectRoot, 'index.html');
|
|
348
430
|
if (!fs.existsSync(htmlInput)) {
|
|
349
431
|
throw new Error(
|
|
@@ -356,11 +438,9 @@ export function ripple(inlineOptions = {}) {
|
|
|
356
438
|
'[@ripple-ts/vite-plugin] Detected ripple.config.ts — configuring client build',
|
|
357
439
|
);
|
|
358
440
|
|
|
359
|
-
//
|
|
441
|
+
// The config was loaded above so build options (e.g. minify) can
|
|
360
442
|
// influence the client build config returned from this hook.
|
|
361
|
-
// The loaded config is cached and reused by
|
|
362
|
-
// buildStart and closeBundle.
|
|
363
|
-
loadedRippleConfig = await loadRippleConfig(projectRoot);
|
|
443
|
+
// The loaded config is cached and reused by buildStart/closeBundle.
|
|
364
444
|
|
|
365
445
|
const outDir = loadedRippleConfig.build.outDir;
|
|
366
446
|
|
|
@@ -408,9 +488,11 @@ export function ripple(inlineOptions = {}) {
|
|
|
408
488
|
}
|
|
409
489
|
|
|
410
490
|
if (excludeRippleExternalModules) {
|
|
491
|
+
/** @type {string[]} */
|
|
492
|
+
const excluded = userConfig.optimizeDeps?.exclude || [];
|
|
411
493
|
return {
|
|
412
494
|
optimizeDeps: {
|
|
413
|
-
exclude:
|
|
495
|
+
exclude: excluded,
|
|
414
496
|
},
|
|
415
497
|
};
|
|
416
498
|
}
|
|
@@ -421,6 +503,7 @@ export function ripple(inlineOptions = {}) {
|
|
|
421
503
|
detectedPackages.forEach((pkg) => {
|
|
422
504
|
ripplePackages.add(pkg);
|
|
423
505
|
});
|
|
506
|
+
/** @type {string[]} */
|
|
424
507
|
const existingExclude = userConfig.optimizeDeps?.exclude || [];
|
|
425
508
|
console.log('[@ripple-ts/vite-plugin] Scan complete. Found:', detectedPackages);
|
|
426
509
|
console.log(
|
|
@@ -428,7 +511,9 @@ export function ripple(inlineOptions = {}) {
|
|
|
428
511
|
existingExclude,
|
|
429
512
|
);
|
|
430
513
|
// Merge with existing exclude list
|
|
431
|
-
const
|
|
514
|
+
const ripple_package_list = /** @type {string[]} */ (Array.from(ripplePackages));
|
|
515
|
+
/** @type {string[]} */
|
|
516
|
+
const allExclude = [...new Set([...existingExclude, ...ripple_package_list])];
|
|
432
517
|
|
|
433
518
|
console.log(`[@ripple-ts/vite-plugin] Merged 'optimizeDeps.exclude':`, allExclude);
|
|
434
519
|
console.log(
|
|
@@ -464,6 +549,8 @@ export function ripple(inlineOptions = {}) {
|
|
|
464
549
|
loadedRippleConfig = await loadRippleConfig(root);
|
|
465
550
|
}
|
|
466
551
|
|
|
552
|
+
if (!has_route_config(loadedRippleConfig)) return;
|
|
553
|
+
|
|
467
554
|
renderRouteEntries = loadedRippleConfig.router.routes
|
|
468
555
|
.filter((/** @type {Route} */ r) => r.type === 'render')
|
|
469
556
|
.map((/** @type {RenderRoute} */ r) => r.entry);
|
|
@@ -488,6 +575,10 @@ export function ripple(inlineOptions = {}) {
|
|
|
488
575
|
try {
|
|
489
576
|
rippleConfig = await loadRippleConfig(root, { vite });
|
|
490
577
|
|
|
578
|
+
if (!has_route_config(rippleConfig)) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
491
582
|
// Create router from config
|
|
492
583
|
router = createRouter(rippleConfig.router.routes);
|
|
493
584
|
console.log(
|
|
@@ -687,7 +778,7 @@ export function ripple(inlineOptions = {}) {
|
|
|
687
778
|
transformIndexHtml: {
|
|
688
779
|
order: 'pre',
|
|
689
780
|
handler(html) {
|
|
690
|
-
if (!isBuild || isSSRBuild) return html;
|
|
781
|
+
if (!isBuild || isSSRBuild || !has_route_config(loadedRippleConfig)) return html;
|
|
691
782
|
|
|
692
783
|
// Inject the hydration client entry script before </body>
|
|
693
784
|
const hydrationScript = `<script type="module" src="virtual:ripple-hydrate"></script>`;
|
|
@@ -708,6 +799,8 @@ export function ripple(inlineOptions = {}) {
|
|
|
708
799
|
loadedRippleConfig = await loadRippleConfig(root);
|
|
709
800
|
}
|
|
710
801
|
|
|
802
|
+
if (!has_route_config(loadedRippleConfig)) return;
|
|
803
|
+
|
|
711
804
|
console.log('[@ripple-ts/vite-plugin] Client build done. Starting server build...');
|
|
712
805
|
|
|
713
806
|
// Re-resolve with adapter validation for production builds.
|
|
@@ -897,8 +990,12 @@ export function ripple(inlineOptions = {}) {
|
|
|
897
990
|
|
|
898
991
|
async resolveId(id, importer, options) {
|
|
899
992
|
// Handle virtual hydrate module
|
|
900
|
-
if (id ===
|
|
901
|
-
return
|
|
993
|
+
if (id === VIRTUAL_HYDRATE_ID) {
|
|
994
|
+
return RESOLVED_VIRTUAL_HYDRATE_ID;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (id === VIRTUAL_COMPAT_ID) {
|
|
998
|
+
return RESOLVED_VIRTUAL_COMPAT_ID;
|
|
902
999
|
}
|
|
903
1000
|
|
|
904
1001
|
// Skip non-package imports (relative/absolute paths)
|
|
@@ -941,8 +1038,13 @@ export function ripple(inlineOptions = {}) {
|
|
|
941
1038
|
},
|
|
942
1039
|
|
|
943
1040
|
async load(id, opts) {
|
|
1041
|
+
if (id === RESOLVED_VIRTUAL_COMPAT_ID) {
|
|
1042
|
+
const compat_config = await get_current_ripple_config();
|
|
1043
|
+
return create_compat_virtual_module(compat_config);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
944
1046
|
// Handle virtual hydrate module
|
|
945
|
-
if (id ===
|
|
1047
|
+
if (id === RESOLVED_VIRTUAL_HYDRATE_ID) {
|
|
946
1048
|
if (isBuild && renderRouteEntries.length > 0) {
|
|
947
1049
|
// Production: generate static import map so Vite bundles page components
|
|
948
1050
|
const importMapLines = renderRouteEntries
|
|
@@ -955,6 +1057,7 @@ export function ripple(inlineOptions = {}) {
|
|
|
955
1057
|
// main bundle awaits page module import → page module awaits main bundle's
|
|
956
1058
|
// TLA to complete → circular wait.
|
|
957
1059
|
return `
|
|
1060
|
+
import ${JSON.stringify(VIRTUAL_COMPAT_ID)};
|
|
958
1061
|
import { hydrate, mount } from 'ripple';
|
|
959
1062
|
|
|
960
1063
|
const routeModules = {
|
|
@@ -1004,6 +1107,7 @@ ${importMapLines}
|
|
|
1004
1107
|
// Dev mode: use async IIFE to avoid top-level await deadlock
|
|
1005
1108
|
// (same reason as production — page modules import from the main bundle)
|
|
1006
1109
|
return `
|
|
1110
|
+
import ${JSON.stringify(VIRTUAL_COMPAT_ID)};
|
|
1007
1111
|
import { hydrate, mount } from 'ripple';
|
|
1008
1112
|
|
|
1009
1113
|
(async () => {
|
|
@@ -1052,11 +1156,16 @@ import { hydrate, mount } from 'ripple';
|
|
|
1052
1156
|
const ssr = opts?.ssr === true || this.environment.config.consumer === 'server';
|
|
1053
1157
|
|
|
1054
1158
|
const is_dev = config?.command === 'serve';
|
|
1159
|
+
const current_ripple_config = await get_current_ripple_config();
|
|
1055
1160
|
|
|
1056
1161
|
const { js, css } = await compile(code, filename, {
|
|
1057
1162
|
mode: ssr ? 'server' : 'client',
|
|
1058
1163
|
dev: is_dev,
|
|
1059
1164
|
hmr: is_dev && !ssr,
|
|
1165
|
+
compat_kinds:
|
|
1166
|
+
current_ripple_config === null
|
|
1167
|
+
? undefined
|
|
1168
|
+
: Object.keys(current_ripple_config.compat),
|
|
1060
1169
|
});
|
|
1061
1170
|
|
|
1062
1171
|
// Track modules with #server blocks for RPC (client build only)
|
package/src/load-config.js
CHANGED
|
@@ -15,12 +15,67 @@
|
|
|
15
15
|
* and the generated production server entry.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
/** @import { RippleConfigOptions, ResolvedRippleConfig } from '@ripple-ts/vite-plugin' */
|
|
18
|
+
/** @import { CompatFactoryConfig, RippleConfigOptions, ResolvedRippleConfig } from '@ripple-ts/vite-plugin' */
|
|
19
19
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import fs from 'node:fs';
|
|
22
22
|
import { DEFAULT_OUTDIR } from './constants.js';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @param {unknown} entry
|
|
26
|
+
* @returns {entry is CompatFactoryConfig}
|
|
27
|
+
*/
|
|
28
|
+
function is_compat_descriptor(entry) {
|
|
29
|
+
return !!entry && typeof entry === 'object' && 'from' in entry;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {unknown} entry
|
|
34
|
+
* @returns {entry is { __ripple_compat__: CompatFactoryConfig }}
|
|
35
|
+
*/
|
|
36
|
+
function is_compat_branded_entry(entry) {
|
|
37
|
+
return (
|
|
38
|
+
!!entry &&
|
|
39
|
+
(typeof entry === 'function' || typeof entry === 'object') &&
|
|
40
|
+
'__ripple_compat__' in entry &&
|
|
41
|
+
is_compat_descriptor(entry.__ripple_compat__)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} kind
|
|
47
|
+
* @param {unknown} entry
|
|
48
|
+
* @returns {CompatFactoryConfig}
|
|
49
|
+
*/
|
|
50
|
+
function normalize_compat_entry(kind, entry) {
|
|
51
|
+
if (is_compat_branded_entry(entry)) {
|
|
52
|
+
entry = entry.__ripple_compat__;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!is_compat_descriptor(entry)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`[@ripple-ts/vite-plugin] ripple.config.ts compat.${kind} must be either a compat descriptor, a compat factory, or an invoked compat entry.`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof entry.from !== 'string' || entry.from.length === 0) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`[@ripple-ts/vite-plugin] ripple.config.ts compat.${kind}.from must be a non-empty string.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (entry.factory !== undefined && typeof entry.factory !== 'string') {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`[@ripple-ts/vite-plugin] ripple.config.ts compat.${kind}.factory must be a string when provided.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
from: entry.from,
|
|
75
|
+
...(entry.factory ? { factory: entry.factory } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
24
79
|
/**
|
|
25
80
|
* Validate a raw ripple config and apply all defaults.
|
|
26
81
|
*
|
|
@@ -46,10 +101,6 @@ export function resolveRippleConfig(raw, options = {}) {
|
|
|
46
101
|
);
|
|
47
102
|
}
|
|
48
103
|
|
|
49
|
-
if (!raw.router?.routes) {
|
|
50
|
-
throw new Error('[@ripple-ts/vite-plugin] ripple.config.ts must define `router.routes`.');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
104
|
if (requireAdapter) {
|
|
54
105
|
if (!raw.adapter) {
|
|
55
106
|
throw new Error(
|
|
@@ -77,9 +128,15 @@ export function resolveRippleConfig(raw, options = {}) {
|
|
|
77
128
|
},
|
|
78
129
|
adapter: raw.adapter,
|
|
79
130
|
router: {
|
|
80
|
-
routes: raw.router
|
|
131
|
+
routes: raw.router?.routes ?? [],
|
|
81
132
|
},
|
|
82
133
|
middlewares: raw.middlewares ?? [],
|
|
134
|
+
compat: Object.fromEntries(
|
|
135
|
+
Object.entries(raw.compat ?? {}).map(([kind, entry]) => [
|
|
136
|
+
kind,
|
|
137
|
+
normalize_compat_entry(kind, entry),
|
|
138
|
+
]),
|
|
139
|
+
),
|
|
83
140
|
platform: {
|
|
84
141
|
env: raw.platform?.env ?? {},
|
|
85
142
|
},
|
package/types/index.d.ts
CHANGED
|
@@ -99,6 +99,26 @@ declare module '@ripple-ts/vite-plugin' {
|
|
|
99
99
|
excludeRippleExternalModules?: boolean;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
export interface CompatFactoryConfig {
|
|
103
|
+
/** Module specifier that exports the compat factory */
|
|
104
|
+
from: string;
|
|
105
|
+
/** Named export to call. Omit to use the module's default export. */
|
|
106
|
+
factory?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CompatFactory<T = unknown> {
|
|
110
|
+
(): T;
|
|
111
|
+
__ripple_compat__: CompatFactoryConfig;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface CompatEntryValue {
|
|
115
|
+
__ripple_compat__: CompatFactoryConfig;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type CompatConfigEntry = CompatFactoryConfig | CompatFactory | CompatEntryValue;
|
|
119
|
+
|
|
120
|
+
export type CompatConfig = Record<string, CompatConfigEntry>;
|
|
121
|
+
|
|
102
122
|
export interface RippleConfigOptions {
|
|
103
123
|
build?: {
|
|
104
124
|
/** Output directory for the production build. @default 'dist' */
|
|
@@ -119,11 +139,21 @@ declare module '@ripple-ts/vite-plugin' {
|
|
|
119
139
|
*/
|
|
120
140
|
runtime: RuntimePrimitives;
|
|
121
141
|
};
|
|
122
|
-
router
|
|
142
|
+
router?: {
|
|
123
143
|
routes: Route[];
|
|
124
144
|
};
|
|
125
145
|
/** Global middlewares applied to all routes */
|
|
126
146
|
middlewares?: Middleware[];
|
|
147
|
+
/**
|
|
148
|
+
* Client-side TSX compat integrations keyed by kind, e.g. `react` for `<tsx:react>`.
|
|
149
|
+
*
|
|
150
|
+
* You can either pass a descriptor object or import a compat factory directly,
|
|
151
|
+
* as long as that factory export carries Ripple compat metadata.
|
|
152
|
+
*
|
|
153
|
+
* These are compiled into a browser-side compat registry by the Vite plugin,
|
|
154
|
+
* allowing `mount()` / `hydrate()` to pick them up automatically.
|
|
155
|
+
*/
|
|
156
|
+
compat?: CompatConfig;
|
|
127
157
|
platform?: {
|
|
128
158
|
env: Record<string, string>;
|
|
129
159
|
};
|
|
@@ -163,6 +193,8 @@ declare module '@ripple-ts/vite-plugin' {
|
|
|
163
193
|
};
|
|
164
194
|
/** @default [] */
|
|
165
195
|
middlewares: Middleware[];
|
|
196
|
+
/** @default {} */
|
|
197
|
+
compat: Record<string, CompatFactoryConfig>;
|
|
166
198
|
platform: {
|
|
167
199
|
/** @default {} */
|
|
168
200
|
env: Record<string, string>;
|