@bleedingdev/modern-js-app-tools-extensions 3.8.3-ultramodern.6 → 3.9.0-ultramodern.10
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/backend-federation-build/codegen.js +256 -0
- package/dist/cjs/backend-federation-build/config.js +189 -0
- package/dist/cjs/backend-federation-build/emit.js +150 -0
- package/dist/cjs/backend-federation-build/index.js +53 -0
- package/dist/cjs/build-config/build-environment.js +268 -0
- package/dist/cjs/build-config/precompress/plugin.js +124 -0
- package/dist/cjs/build-config/precompress/types.js +18 -0
- package/dist/cjs/build-config/public.js +46 -0
- package/dist/cjs/cloudflare/constants.js +1 -1
- package/dist/cjs/cloudflare/delivery-unit.js +7 -16
- package/dist/cjs/cloudflare-output-contract.js +37 -1
- package/dist/cjs/cloudflare-output-verifier/identity.js +6 -6
- package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +4 -1
- package/dist/cjs/config.js +29 -1
- package/dist/cjs/deploy-output/npmAliases.js +226 -0
- package/dist/cjs/deploy-output/plugin.js +76 -0
- package/dist/cjs/deploy-output/target.js +48 -0
- package/dist/cjs/native-type-checker.js +193 -0
- package/dist/cjs/policy-defaults.js +101 -0
- package/dist/cjs/release-envelope/framework-output.js +77 -32
- package/dist/cjs/release-envelope/plugin.js +5 -3
- package/dist/cjs/release-identity.js +3 -6
- package/dist/cjs/runtime-package-resolution.js +118 -0
- package/dist/esm/backend-federation-build/codegen.mjs +205 -0
- package/dist/esm/backend-federation-build/config.mjs +116 -0
- package/dist/esm/backend-federation-build/emit.mjs +99 -0
- package/dist/esm/backend-federation-build/index.mjs +1 -0
- package/dist/esm/build-config/build-environment.mjs +224 -0
- package/dist/esm/build-config/precompress/plugin.mjs +76 -0
- package/dist/esm/build-config/precompress/types.mjs +0 -0
- package/dist/esm/build-config/public.mjs +1 -0
- package/dist/esm/cloudflare/constants.mjs +1 -1
- package/dist/esm/cloudflare/delivery-unit.mjs +1 -10
- package/dist/esm/cloudflare-output-contract.mjs +35 -2
- package/dist/esm/cloudflare-output-verifier/identity.mjs +1 -1
- package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +5 -2
- package/dist/esm/config.mjs +1 -0
- package/dist/esm/deploy-output/npmAliases.mjs +174 -0
- package/dist/esm/deploy-output/plugin.mjs +28 -0
- package/dist/esm/deploy-output/target.mjs +10 -0
- package/dist/esm/native-type-checker.mjs +142 -0
- package/dist/esm/policy-defaults.mjs +45 -0
- package/dist/esm/release-envelope/framework-output.mjs +51 -6
- package/dist/esm/release-envelope/plugin.mjs +5 -3
- package/dist/esm/release-identity.mjs +3 -6
- package/dist/esm/runtime-package-resolution.mjs +60 -0
- package/dist/esm-node/backend-federation-build/codegen.mjs +206 -0
- package/dist/esm-node/backend-federation-build/config.mjs +117 -0
- package/dist/esm-node/backend-federation-build/emit.mjs +100 -0
- package/dist/esm-node/backend-federation-build/index.mjs +2 -0
- package/dist/esm-node/build-config/build-environment.mjs +225 -0
- package/dist/esm-node/build-config/precompress/plugin.mjs +77 -0
- package/dist/esm-node/build-config/precompress/types.mjs +1 -0
- package/dist/esm-node/build-config/public.mjs +2 -0
- package/dist/esm-node/cloudflare/constants.mjs +1 -1
- package/dist/esm-node/cloudflare/delivery-unit.mjs +1 -10
- package/dist/esm-node/cloudflare-output-contract.mjs +35 -2
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +1 -1
- package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +5 -2
- package/dist/esm-node/config.mjs +1 -0
- package/dist/esm-node/deploy-output/npmAliases.mjs +175 -0
- package/dist/esm-node/deploy-output/plugin.mjs +31 -0
- package/dist/esm-node/deploy-output/target.mjs +11 -0
- package/dist/esm-node/native-type-checker.mjs +143 -0
- package/dist/esm-node/policy-defaults.mjs +46 -0
- package/dist/esm-node/release-envelope/framework-output.mjs +51 -6
- package/dist/esm-node/release-envelope/plugin.mjs +5 -3
- package/dist/esm-node/release-identity.mjs +3 -6
- package/dist/esm-node/runtime-package-resolution.mjs +61 -0
- package/dist/types/backend-federation-build/codegen.d.ts +86 -0
- package/dist/types/backend-federation-build/config.d.ts +102 -0
- package/dist/types/backend-federation-build/emit.d.ts +21 -0
- package/dist/types/backend-federation-build/index.d.ts +2 -0
- package/dist/types/build-config/build-environment.d.ts +22 -0
- package/dist/types/build-config/precompress/plugin.d.ts +4 -0
- package/dist/types/build-config/precompress/types.d.ts +19 -0
- package/dist/types/build-config/public.d.ts +2 -0
- package/dist/types/cloudflare/artifacts.d.ts +1 -1
- package/dist/types/cloudflare/constants.d.ts +2 -2
- package/dist/types/cloudflare/delivery-unit.d.ts +2 -2
- package/dist/types/cloudflare/i18n-worker.d.ts +1 -1
- package/dist/types/cloudflare/index.d.ts +1 -1
- package/dist/types/cloudflare/security-policies.d.ts +1 -1
- package/dist/types/cloudflare/types.d.ts +1 -1
- package/dist/types/cloudflare/utils.d.ts +1 -1
- package/dist/types/cloudflare/worker-manifest.d.ts +4 -4
- package/dist/types/cloudflare/wrangler-config.d.ts +2 -2
- package/dist/types/cloudflare-output-contract.d.ts +2 -1
- package/dist/types/cloudflare-output-plan.d.ts +1 -1
- package/dist/types/cloudflare-output-verifier/identity.d.ts +2 -2
- package/dist/types/cloudflare-output-verifier/index.d.ts +5 -5
- package/dist/types/cloudflare-output-verifier/manifest.d.ts +1 -1
- package/dist/types/cloudflare-output-verifier/mutation-policy.d.ts +1 -1
- package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/deploy-output/npmAliases.d.ts +14 -0
- package/dist/types/deploy-output/plugin.d.ts +15 -0
- package/dist/types/deploy-output/target.d.ts +6 -0
- package/dist/types/index.d.ts +10 -10
- package/dist/types/native-type-checker.d.ts +15 -0
- package/dist/types/policy-defaults.d.ts +46 -0
- package/dist/types/precompress/LICENSE +20 -0
- package/dist/types/precompress/index.d.ts +76 -0
- package/dist/types/release-envelope/canonical.d.ts +1 -1
- package/dist/types/release-envelope/framework-output.d.ts +3 -2
- package/dist/types/release-envelope/index.d.ts +3 -3
- package/dist/types/runtime-package-resolution.d.ts +28 -0
- package/package.json +72 -14
- package/src/backend-federation-build/codegen.ts +245 -0
- package/src/backend-federation-build/config.ts +356 -0
- package/src/backend-federation-build/emit.ts +202 -0
- package/src/backend-federation-build/index.ts +2 -0
- package/src/build-config/build-environment.ts +404 -0
- package/src/build-config/precompress/plugin.ts +140 -0
- package/src/build-config/precompress/types.ts +26 -0
- package/src/build-config/public.ts +6 -0
- package/src/cloudflare/constants.ts +1 -1
- package/src/cloudflare/delivery-unit.ts +3 -20
- package/src/cloudflare/worker-manifest.ts +1 -0
- package/src/cloudflare-builder.ts +1 -0
- package/src/cloudflare-output-contract.ts +37 -0
- package/src/cloudflare-output-verifier/identity.ts +1 -1
- package/src/cloudflare-output-verifier/worker-bundles.ts +5 -3
- package/src/config.ts +7 -0
- package/src/deploy-output/npmAliases.ts +333 -0
- package/src/deploy-output/plugin.ts +49 -0
- package/src/deploy-output/target.ts +23 -0
- package/src/native-type-checker.ts +183 -0
- package/src/policy-defaults.ts +127 -0
- package/src/release-envelope/framework-output.ts +135 -4
- package/src/release-envelope/plugin.ts +15 -3
- package/src/release-identity.ts +3 -2
- package/src/runtime-package-resolution.ts +115 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { constants as zlibConstants } from 'node:zlib';
|
|
2
|
+
import type { RsbuildPlugin } from '@rsbuild/core';
|
|
3
|
+
import CompressionPlugin from 'compression-webpack-plugin';
|
|
4
|
+
import type { PrecompressConfig } from './types';
|
|
5
|
+
|
|
6
|
+
export type { PrecompressCodecOptions, PrecompressConfig } from './types';
|
|
7
|
+
|
|
8
|
+
type CompressionPluginOptions = NonNullable<
|
|
9
|
+
ConstructorParameters<typeof CompressionPlugin>[0]
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PRECOMPRESS_TEST = /\.(js|mjs|cjs|css|html|svg|json|map)$/i;
|
|
13
|
+
const DEFAULT_PRECOMPRESS_THRESHOLD = 10 * 1024;
|
|
14
|
+
const DEFAULT_PRECOMPRESS_MIN_RATIO = 0.8;
|
|
15
|
+
|
|
16
|
+
const createDefaultGzipOptions = (): CompressionPluginOptions => ({
|
|
17
|
+
algorithm: 'gzip',
|
|
18
|
+
filename: '[path][base].gz',
|
|
19
|
+
test: DEFAULT_PRECOMPRESS_TEST,
|
|
20
|
+
threshold: DEFAULT_PRECOMPRESS_THRESHOLD,
|
|
21
|
+
minRatio: DEFAULT_PRECOMPRESS_MIN_RATIO,
|
|
22
|
+
compressionOptions: {
|
|
23
|
+
level: 6,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const createDefaultBrotliOptions = (): CompressionPluginOptions => ({
|
|
28
|
+
algorithm: 'brotliCompress',
|
|
29
|
+
filename: '[path][base].br',
|
|
30
|
+
test: DEFAULT_PRECOMPRESS_TEST,
|
|
31
|
+
threshold: DEFAULT_PRECOMPRESS_THRESHOLD,
|
|
32
|
+
minRatio: DEFAULT_PRECOMPRESS_MIN_RATIO,
|
|
33
|
+
compressionOptions: {
|
|
34
|
+
params: {
|
|
35
|
+
[zlibConstants.BROTLI_PARAM_QUALITY]: 9,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const mergeCompressionOptions = (
|
|
41
|
+
base: CompressionPluginOptions,
|
|
42
|
+
next: Partial<CompressionPluginOptions>,
|
|
43
|
+
): CompressionPluginOptions => {
|
|
44
|
+
const merged = {
|
|
45
|
+
...base,
|
|
46
|
+
...next,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (base.compressionOptions || next.compressionOptions) {
|
|
50
|
+
const baseCompressionOptions = base.compressionOptions as
|
|
51
|
+
| Record<string, unknown>
|
|
52
|
+
| undefined;
|
|
53
|
+
const nextCompressionOptions = next.compressionOptions as
|
|
54
|
+
| Record<string, unknown>
|
|
55
|
+
| undefined;
|
|
56
|
+
const mergedCompressionOptions = {
|
|
57
|
+
...baseCompressionOptions,
|
|
58
|
+
...nextCompressionOptions,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (baseCompressionOptions?.params || nextCompressionOptions?.params) {
|
|
62
|
+
mergedCompressionOptions.params = {
|
|
63
|
+
...(baseCompressionOptions?.params as Record<string, unknown>),
|
|
64
|
+
...(nextCompressionOptions?.params as Record<string, unknown>),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
merged.compressionOptions = mergedCompressionOptions;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return merged;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const resolveCodecOptions = (
|
|
75
|
+
codecConfig: PrecompressConfig['gzip'] | PrecompressConfig['brotli'],
|
|
76
|
+
createDefaultOptions: () => CompressionPluginOptions,
|
|
77
|
+
): CompressionPluginOptions | null => {
|
|
78
|
+
if (codecConfig === false) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (codecConfig === undefined || codecConfig === true) {
|
|
83
|
+
return createDefaultOptions();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return mergeCompressionOptions(createDefaultOptions(), codecConfig);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const resolvePrecompressConfig = (
|
|
90
|
+
precompress: boolean | PrecompressConfig | undefined,
|
|
91
|
+
): PrecompressConfig | false => {
|
|
92
|
+
if (precompress === false || precompress === undefined) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (precompress === true) {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return precompress;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const builderPluginAdapterPrecompress = (
|
|
104
|
+
precompress: boolean | PrecompressConfig | undefined,
|
|
105
|
+
): RsbuildPlugin => ({
|
|
106
|
+
name: 'builder-plugin-adapter-modern-precompress',
|
|
107
|
+
|
|
108
|
+
setup(api) {
|
|
109
|
+
api.modifyBundlerChain((chain, { isProd, target }) => {
|
|
110
|
+
if (!isProd || target !== 'web') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const precompressConfig = resolvePrecompressConfig(precompress);
|
|
115
|
+
if (precompressConfig === false) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const gzipOptions = resolveCodecOptions(
|
|
120
|
+
precompressConfig.gzip,
|
|
121
|
+
createDefaultGzipOptions,
|
|
122
|
+
);
|
|
123
|
+
if (gzipOptions) {
|
|
124
|
+
chain
|
|
125
|
+
.plugin('modern-precompress-gzip')
|
|
126
|
+
.use(CompressionPlugin, [gzipOptions]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const brotliOptions = resolveCodecOptions(
|
|
130
|
+
precompressConfig.brotli,
|
|
131
|
+
createDefaultBrotliOptions,
|
|
132
|
+
);
|
|
133
|
+
if (brotliOptions) {
|
|
134
|
+
chain
|
|
135
|
+
.plugin('modern-precompress-brotli')
|
|
136
|
+
.use(CompressionPlugin, [brotliOptions]);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type CompressionPlugin from 'compression-webpack-plugin';
|
|
2
|
+
|
|
3
|
+
type CompressionPluginOptions = NonNullable<
|
|
4
|
+
ConstructorParameters<typeof CompressionPlugin>[0]
|
|
5
|
+
>;
|
|
6
|
+
|
|
7
|
+
export type PrecompressCodecOptions =
|
|
8
|
+
| boolean
|
|
9
|
+
| Partial<CompressionPluginOptions>;
|
|
10
|
+
|
|
11
|
+
export interface PrecompressConfig {
|
|
12
|
+
/**
|
|
13
|
+
* Configure gzip precompression options.
|
|
14
|
+
* `true` means using default options.
|
|
15
|
+
* `false` means disabling gzip precompression.
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
gzip?: PrecompressCodecOptions;
|
|
19
|
+
/**
|
|
20
|
+
* Configure brotli precompression options.
|
|
21
|
+
* `true` means using default options.
|
|
22
|
+
* `false` means disabling brotli precompression.
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
brotli?: PrecompressCodecOptions;
|
|
26
|
+
}
|
|
@@ -34,7 +34,7 @@ export const BFF_EFFECT_WORKER_ENTRY = `${WORKER_BUNDLE_DIRECTORY}/__modern_bff_
|
|
|
34
34
|
export const BFF_EFFECT_WORKER_DISPATCHER_EXPORT =
|
|
35
35
|
'__modern_create_effect_bff_dispatcher';
|
|
36
36
|
export const EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE =
|
|
37
|
-
'Ensure the Effect API entry exists at api/index.ts or bff.effect.entry, and import Cloudflare edge handlers from @modern-js/
|
|
37
|
+
'Ensure the Effect API entry exists at api/index.ts or bff.effect.entry, and import Cloudflare edge handlers from @modern-js/bff-effect/effect-edge instead of lambda/Hono server helpers.';
|
|
38
38
|
export const DEFAULT_COMPATIBILITY_DATE = '2026-06-02';
|
|
39
39
|
export const COMPATIBILITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
|
|
40
40
|
export const REQUIRED_COMPATIBILITY_FLAGS =
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { fs as fse } from '@modern-js/utils';
|
|
3
2
|
import {
|
|
4
3
|
type DeliveryUnitIdentity,
|
|
5
4
|
isUltramodernBuildArtifact,
|
|
6
5
|
nonEmptyString,
|
|
7
6
|
toDeliveryUnitIdentity,
|
|
8
7
|
ULTRAMODERN_BUILD_ARTIFACT_PATH,
|
|
9
|
-
|
|
10
|
-
} from '@modern-js/utils
|
|
8
|
+
} from '@modern-js/backend-federation-contracts';
|
|
9
|
+
import { fs as fse } from '@modern-js/utils';
|
|
11
10
|
import { resolveUltramodernReleaseIdentity } from '../release-identity';
|
|
12
11
|
import { isRecord } from './utils';
|
|
13
12
|
|
|
@@ -148,7 +147,7 @@ export const resolveTopologyDeliveryUnit = async (
|
|
|
148
147
|
|
|
149
148
|
/**
|
|
150
149
|
* Resolve the delivery-unit identity actually bundled into the worker by
|
|
151
|
-
*
|
|
150
|
+
* reading the generated `shared/ultramodern-build.json` artifact. This is the
|
|
152
151
|
* worker snapshot / declared surface source that gets stamped into the manifest.
|
|
153
152
|
*/
|
|
154
153
|
export const resolveWorkerDeliveryUnitStamp = async (
|
|
@@ -158,10 +157,6 @@ export const resolveWorkerDeliveryUnitStamp = async (
|
|
|
158
157
|
appDirectory,
|
|
159
158
|
ULTRAMODERN_BUILD_ARTIFACT_PATH,
|
|
160
159
|
);
|
|
161
|
-
const buildModulePath = path.join(
|
|
162
|
-
appDirectory,
|
|
163
|
-
ULTRAMODERN_BUILD_MODULE_PATH,
|
|
164
|
-
);
|
|
165
160
|
let identity: DeliveryUnitIdentity | undefined;
|
|
166
161
|
|
|
167
162
|
if (await fse.pathExists(buildArtifactPath)) {
|
|
@@ -170,18 +165,6 @@ export const resolveWorkerDeliveryUnitStamp = async (
|
|
|
170
165
|
return undefined;
|
|
171
166
|
}
|
|
172
167
|
identity = toDeliveryUnitIdentity(artifact.deliveryUnit);
|
|
173
|
-
} else if (await fse.pathExists(buildModulePath)) {
|
|
174
|
-
console.warn(
|
|
175
|
-
`[cloudflare] ${buildArtifactPath} missing; falling back to legacy regex parsing of ${buildModulePath}. Regenerate the workspace to emit ultramodern-build.json.`,
|
|
176
|
-
);
|
|
177
|
-
const source = await fse.readFile(buildModulePath, 'utf8');
|
|
178
|
-
identity = toDeliveryUnitIdentity({
|
|
179
|
-
buildMarker: source.match(/\bbuild:\s*['"]([^'"]+)['"]/u)?.[1],
|
|
180
|
-
unitId: source.match(/\bunitId:\s*['"]([^'"]+)['"]/u)?.[1],
|
|
181
|
-
sourceRevision: source.match(
|
|
182
|
-
/\bsourceRevision:\s*['"]([^'"]+)['"]/u,
|
|
183
|
-
)?.[1],
|
|
184
|
-
});
|
|
185
168
|
}
|
|
186
169
|
|
|
187
170
|
if (!identity) {
|
|
@@ -3,6 +3,7 @@ import { createRequire } from 'node:module';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { SERVICE_WORKER_ENVIRONMENT_NAME } from '@modern-js/builder';
|
|
5
5
|
import type { BffUserConfig } from '@modern-js/server-core';
|
|
6
|
+
import type {} from '@modern-js/server-runtime-extensions/server-config';
|
|
6
7
|
import { isProd } from '@modern-js/utils';
|
|
7
8
|
import type {
|
|
8
9
|
EnvironmentConfig,
|
|
@@ -10,19 +10,56 @@ export const CLOUDFLARE_RUNTIME_TYPE = 'cloudflare-module-worker';
|
|
|
10
10
|
export const CLOUDFLARE_OUTPUT_PACKAGE_TYPE = 'module';
|
|
11
11
|
export const CLOUDFLARE_WORKER_PACKAGE_TYPE = 'commonjs';
|
|
12
12
|
export const CLOUDFLARE_WORKER_BUNDLE_FORMAT = 'commonjs';
|
|
13
|
+
// Import availability for the default 2026-06-02 nodejs_compat target.
|
|
14
|
+
// Some modules are runtime-provided stubs whose unsupported operations throw.
|
|
13
15
|
export const CLOUDFLARE_WORKER_NODE_BUILTINS = [
|
|
16
|
+
'assert',
|
|
17
|
+
'assert/strict',
|
|
14
18
|
'async_hooks',
|
|
15
19
|
'buffer',
|
|
20
|
+
'child_process',
|
|
16
21
|
'crypto',
|
|
22
|
+
'dgram',
|
|
23
|
+
'dns',
|
|
24
|
+
'domain',
|
|
17
25
|
'events',
|
|
26
|
+
'fs',
|
|
18
27
|
'fs/promises',
|
|
28
|
+
'http',
|
|
29
|
+
'http2',
|
|
30
|
+
'inspector',
|
|
19
31
|
'module',
|
|
32
|
+
'net',
|
|
33
|
+
'os',
|
|
20
34
|
'path',
|
|
21
35
|
'process',
|
|
36
|
+
'readline',
|
|
37
|
+
'readline/promises',
|
|
38
|
+
'repl',
|
|
39
|
+
'sqlite',
|
|
22
40
|
'stream',
|
|
41
|
+
'stream/consumers',
|
|
42
|
+
'stream/promises',
|
|
43
|
+
'stream/web',
|
|
23
44
|
'string_decoder',
|
|
45
|
+
'timers',
|
|
46
|
+
'timers/promises',
|
|
47
|
+
'tls',
|
|
48
|
+
'trace_events',
|
|
49
|
+
'tty',
|
|
24
50
|
'url',
|
|
25
51
|
'util',
|
|
52
|
+
'util/types',
|
|
53
|
+
'v8',
|
|
54
|
+
'vm',
|
|
55
|
+
'wasi',
|
|
56
|
+
'worker_threads',
|
|
57
|
+
'zlib',
|
|
58
|
+
] as const;
|
|
59
|
+
// Platform imports available with the default 2026-06-02 nodejs_compat target.
|
|
60
|
+
// Import availability does not imply support for every Node API operation.
|
|
61
|
+
export const CLOUDFLARE_WORKER_PLATFORM_MODULES = [
|
|
62
|
+
'cloudflare:sockets',
|
|
26
63
|
] as const;
|
|
27
64
|
export const CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS = [
|
|
28
65
|
'nodejs_compat',
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
DELIVERY_UNIT_IDENTITY_FIELDS,
|
|
3
3
|
type DeliveryUnitIdentity,
|
|
4
4
|
deliveryUnitIdentityFieldValue,
|
|
5
|
-
} from '@modern-js/
|
|
5
|
+
} from '@modern-js/backend-federation-contracts';
|
|
6
6
|
import type { CloudflareOutputVerifierIssue, JsonObject } from './issues';
|
|
7
7
|
import { addIssue, assertEqual } from './issues';
|
|
8
8
|
|
|
@@ -7,6 +7,7 @@ import traverse, { type NodePath } from '@babel/traverse';
|
|
|
7
7
|
import {
|
|
8
8
|
CLOUDFLARE_WORKER_BUNDLE_DIRECTORY,
|
|
9
9
|
CLOUDFLARE_WORKER_NODE_BUILTINS,
|
|
10
|
+
CLOUDFLARE_WORKER_PLATFORM_MODULES,
|
|
10
11
|
} from '../cloudflare-output-contract';
|
|
11
12
|
import type { CloudflareOutputVerifierIssue, JsonObject } from './issues';
|
|
12
13
|
import { addIssue } from './issues';
|
|
@@ -436,9 +437,10 @@ const getPackageName = (specifier: string) =>
|
|
|
436
437
|
? specifier.split('/').slice(0, 2).join('/')
|
|
437
438
|
: specifier.split('/')[0];
|
|
438
439
|
|
|
439
|
-
const CLOUDFLARE_WORKER_NODE_BUILTIN_SPECIFIERS = new Set(
|
|
440
|
-
CLOUDFLARE_WORKER_NODE_BUILTINS.map(builtin => `node:${builtin}`),
|
|
441
|
-
|
|
440
|
+
const CLOUDFLARE_WORKER_NODE_BUILTIN_SPECIFIERS = new Set([
|
|
441
|
+
...CLOUDFLARE_WORKER_NODE_BUILTINS.map(builtin => `node:${builtin}`),
|
|
442
|
+
...CLOUDFLARE_WORKER_PLATFORM_MODULES,
|
|
443
|
+
]);
|
|
442
444
|
|
|
443
445
|
const isInsideDirectory = (directory: string, filePath: string) => {
|
|
444
446
|
const relative = path.relative(directory, filePath);
|
package/src/config.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export type { ResolveEffectTsgoCompilerOptions } from './build-config/public';
|
|
2
|
+
export {
|
|
3
|
+
getBuildConfigEnvironment,
|
|
4
|
+
resolveEffectTsgoCompiler,
|
|
5
|
+
withBuildConfigEnvironment,
|
|
6
|
+
} from './build-config/public';
|
|
7
|
+
|
|
1
8
|
export type DeployTarget =
|
|
2
9
|
| 'node'
|
|
3
10
|
| 'vercel'
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
type PackageJson = {
|
|
5
|
+
name?: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
dependencies?: Record<string, string>;
|
|
8
|
+
optionalDependencies?: Record<string, string>;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type PackageRecord = {
|
|
13
|
+
directory: string;
|
|
14
|
+
packageJson: PackageJson;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type NpmAlias = {
|
|
18
|
+
aliasName: string;
|
|
19
|
+
targetName: string;
|
|
20
|
+
targetVersion: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const dependencyKeys = ['dependencies', 'optionalDependencies'] as const;
|
|
24
|
+
const exactVersionPattern =
|
|
25
|
+
/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
26
|
+
|
|
27
|
+
async function readPackageJson(filePath: string): Promise<PackageJson> {
|
|
28
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8')) as PackageJson;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseNpmAlias(
|
|
32
|
+
aliasName: string,
|
|
33
|
+
specifier: string,
|
|
34
|
+
): NpmAlias | undefined {
|
|
35
|
+
if (!specifier.startsWith('npm:')) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const target = specifier.slice('npm:'.length);
|
|
40
|
+
const versionSeparator = target.lastIndexOf('@');
|
|
41
|
+
if (versionSeparator <= 0 || versionSeparator === target.length - 1) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Invalid npm alias specifier for ${aliasName}: ${specifier}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
aliasName,
|
|
49
|
+
targetName: target.slice(0, versionSeparator),
|
|
50
|
+
targetVersion: target.slice(versionSeparator + 1),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function aliasesFromPackageJson(packageJson: PackageJson): NpmAlias[] {
|
|
55
|
+
const aliases: NpmAlias[] = [];
|
|
56
|
+
for (const dependencyKey of dependencyKeys) {
|
|
57
|
+
for (const [aliasName, specifier] of Object.entries(
|
|
58
|
+
packageJson[dependencyKey] ?? {},
|
|
59
|
+
)) {
|
|
60
|
+
const alias = parseNpmAlias(aliasName, specifier);
|
|
61
|
+
if (alias) {
|
|
62
|
+
aliases.push(alias);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return aliases;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function collectPackageRecords(
|
|
70
|
+
nodeModulesDirectory: string,
|
|
71
|
+
): Promise<PackageRecord[]> {
|
|
72
|
+
const records: PackageRecord[] = [];
|
|
73
|
+
const visitedNodeModules = new Set<string>();
|
|
74
|
+
|
|
75
|
+
const visitPackage = async (directory: string) => {
|
|
76
|
+
const packageJsonPath = path.join(directory, 'package.json');
|
|
77
|
+
try {
|
|
78
|
+
const packageJson = await readPackageJson(packageJsonPath);
|
|
79
|
+
if (packageJson.name && packageJson.version) {
|
|
80
|
+
records.push({ directory, packageJson });
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
await visitNodeModules(path.join(directory, 'node_modules'));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const visitScope = async (scopeDirectory: string) => {
|
|
91
|
+
const entries = await fs.readdir(scopeDirectory, { withFileTypes: true });
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
await visitPackage(path.join(scopeDirectory, entry.name));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const visitNdepeStore = async (storeDirectory: string) => {
|
|
100
|
+
const entries = await fs.readdir(storeDirectory, { withFileTypes: true });
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
if (entry.isDirectory()) {
|
|
103
|
+
const entryPath = path.join(storeDirectory, entry.name);
|
|
104
|
+
if (entry.name.startsWith('@')) {
|
|
105
|
+
// ndepe preserves the slash in a scoped package name, so a
|
|
106
|
+
// multi-version package is stored as
|
|
107
|
+
// .ndepe/@scope/package@version/node_modules/@scope/package.
|
|
108
|
+
await visitScope(entryPath);
|
|
109
|
+
} else {
|
|
110
|
+
await visitNodeModules(path.join(entryPath, 'node_modules'));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const visitNodeModules = async (directory: string): Promise<void> => {
|
|
117
|
+
const resolvedDirectory = path.resolve(directory);
|
|
118
|
+
if (visitedNodeModules.has(resolvedDirectory)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
visitedNodeModules.add(resolvedDirectory);
|
|
122
|
+
|
|
123
|
+
let entries;
|
|
124
|
+
try {
|
|
125
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (!entry.isDirectory()) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const entryPath = path.join(directory, entry.name);
|
|
138
|
+
if (entry.name === '.ndepe') {
|
|
139
|
+
await visitNdepeStore(entryPath);
|
|
140
|
+
} else if (entry.name.startsWith('@')) {
|
|
141
|
+
await visitScope(entryPath);
|
|
142
|
+
} else {
|
|
143
|
+
await visitPackage(entryPath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
await visitNodeModules(nodeModulesDirectory);
|
|
149
|
+
return records;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function resolveAliasTarget(
|
|
153
|
+
ownerDirectory: string,
|
|
154
|
+
alias: NpmAlias,
|
|
155
|
+
outputDirectory: string,
|
|
156
|
+
): Promise<PackageRecord | undefined> {
|
|
157
|
+
let directory = ownerDirectory;
|
|
158
|
+
const outputRoot = path.resolve(outputDirectory);
|
|
159
|
+
const resolvedOutputRoot = await fs.realpath(outputRoot);
|
|
160
|
+
while (
|
|
161
|
+
directory === outputRoot ||
|
|
162
|
+
directory.startsWith(`${outputRoot}${path.sep}`)
|
|
163
|
+
) {
|
|
164
|
+
const candidate = path.join(directory, 'node_modules', alias.targetName);
|
|
165
|
+
try {
|
|
166
|
+
const resolvedCandidate = await fs.realpath(candidate);
|
|
167
|
+
if (
|
|
168
|
+
resolvedCandidate !== resolvedOutputRoot &&
|
|
169
|
+
!resolvedCandidate.startsWith(`${resolvedOutputRoot}${path.sep}`)
|
|
170
|
+
) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Cannot preserve npm alias ${alias.aliasName}: emitted target ${candidate} resolves outside deployment output to ${resolvedCandidate}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const packageJson = await readPackageJson(
|
|
176
|
+
path.join(candidate, 'package.json'),
|
|
177
|
+
);
|
|
178
|
+
if (packageJson.name !== alias.targetName || !packageJson.version) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`npm alias ${alias.aliasName} resolved ${candidate} to unexpected package ${String(packageJson.name)}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (
|
|
184
|
+
exactVersionPattern.test(alias.targetVersion) &&
|
|
185
|
+
packageJson.version !== alias.targetVersion
|
|
186
|
+
) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
// Keep the lexical path inside .output. realpath can cross an operating
|
|
190
|
+
// system alias such as macOS /var -> /private/var, which would turn the
|
|
191
|
+
// deployment link into a non-relocatable path outside the artifact.
|
|
192
|
+
return { directory: candidate, packageJson };
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (directory === outputRoot) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
directory = path.dirname(directory);
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function ensureAliasLink(
|
|
207
|
+
ownerDirectory: string,
|
|
208
|
+
alias: NpmAlias,
|
|
209
|
+
target: PackageRecord,
|
|
210
|
+
) {
|
|
211
|
+
const aliasPath = path.join(ownerDirectory, 'node_modules', alias.aliasName);
|
|
212
|
+
await fs.mkdir(path.dirname(aliasPath), { recursive: true });
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const [existing, expected] = await Promise.all([
|
|
216
|
+
fs.realpath(aliasPath),
|
|
217
|
+
fs.realpath(target.directory),
|
|
218
|
+
]);
|
|
219
|
+
if (existing === expected) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Cannot preserve npm alias ${alias.aliasName}: ${aliasPath} already resolves to ${existing}, expected ${expected}`,
|
|
224
|
+
);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const linkTarget = path.relative(path.dirname(aliasPath), target.directory);
|
|
232
|
+
await fs.symlink(linkTarget, aliasPath, 'dir');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function aliasSpecifier(alias: NpmAlias) {
|
|
236
|
+
return `npm:${alias.targetName}@${alias.targetVersion}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function readPackageIdentity(
|
|
240
|
+
entryPath: string,
|
|
241
|
+
): Promise<{ name: string; version: string }> {
|
|
242
|
+
let directory = path.dirname(entryPath);
|
|
243
|
+
const root = path.parse(directory).root;
|
|
244
|
+
while (directory !== root) {
|
|
245
|
+
try {
|
|
246
|
+
const packageJson = await readPackageJson(
|
|
247
|
+
path.join(directory, 'package.json'),
|
|
248
|
+
);
|
|
249
|
+
if (packageJson.name && packageJson.version) {
|
|
250
|
+
return { name: packageJson.name, version: packageJson.version };
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
directory = path.dirname(directory);
|
|
258
|
+
}
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Cannot find owning package for deployment entry ${entryPath}`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function preserveNpmAliases({
|
|
265
|
+
appDirectory,
|
|
266
|
+
outputDirectory,
|
|
267
|
+
implicitAliases = [],
|
|
268
|
+
}: {
|
|
269
|
+
appDirectory: string;
|
|
270
|
+
outputDirectory: string;
|
|
271
|
+
implicitAliases?: NpmAlias[];
|
|
272
|
+
}) {
|
|
273
|
+
const records = await collectPackageRecords(
|
|
274
|
+
path.join(outputDirectory, 'node_modules'),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const appPackageJson = await readPackageJson(
|
|
278
|
+
path.join(appDirectory, 'package.json'),
|
|
279
|
+
);
|
|
280
|
+
const rootAliases = [
|
|
281
|
+
...aliasesFromPackageJson(appPackageJson),
|
|
282
|
+
...implicitAliases,
|
|
283
|
+
].filter(alias => alias.aliasName !== alias.targetName);
|
|
284
|
+
|
|
285
|
+
const emittedRootAliases: NpmAlias[] = [];
|
|
286
|
+
for (const alias of rootAliases) {
|
|
287
|
+
const target = await resolveAliasTarget(
|
|
288
|
+
outputDirectory,
|
|
289
|
+
alias,
|
|
290
|
+
outputDirectory,
|
|
291
|
+
);
|
|
292
|
+
if (!target) {
|
|
293
|
+
if (implicitAliases.includes(alias)) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Cannot preserve implicit npm alias ${alias.aliasName}: emitted target ${alias.targetName}@${alias.targetVersion} is missing`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
await ensureAliasLink(outputDirectory, alias, target);
|
|
301
|
+
emittedRootAliases.push(alias);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (const record of records) {
|
|
305
|
+
for (const alias of aliasesFromPackageJson(record.packageJson).filter(
|
|
306
|
+
candidate => candidate.aliasName !== candidate.targetName,
|
|
307
|
+
)) {
|
|
308
|
+
const target = await resolveAliasTarget(
|
|
309
|
+
record.directory,
|
|
310
|
+
alias,
|
|
311
|
+
outputDirectory,
|
|
312
|
+
);
|
|
313
|
+
if (!target) {
|
|
314
|
+
// ndepe emits only runtime-traced packages. A package manifest can
|
|
315
|
+
// still declare type-only or otherwise unused aliases whose targets
|
|
316
|
+
// correctly are not part of this deployment output.
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
await ensureAliasLink(record.directory, alias, target);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const outputPackageJsonPath = path.join(outputDirectory, 'package.json');
|
|
324
|
+
const outputPackageJson = await readPackageJson(outputPackageJsonPath);
|
|
325
|
+
outputPackageJson.dependencies ??= {};
|
|
326
|
+
for (const alias of emittedRootAliases) {
|
|
327
|
+
outputPackageJson.dependencies[alias.aliasName] = aliasSpecifier(alias);
|
|
328
|
+
}
|
|
329
|
+
await fs.writeFile(
|
|
330
|
+
outputPackageJsonPath,
|
|
331
|
+
`${JSON.stringify(outputPackageJson, null, 2)}\n`,
|
|
332
|
+
);
|
|
333
|
+
}
|