@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.21 → 3.9.0-ultramodern.23

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.
@@ -84,6 +84,18 @@ export interface CloudflareWorkerServiceBindingConfig {
84
84
  */
85
85
  fragments?: CloudflareWorkerServiceBindingFragmentConfig[];
86
86
  }
87
+ export interface CloudflareWorkerVpcServiceConfig {
88
+ /** Worker binding name exposed on the module worker `env` object. */
89
+ binding: string;
90
+ /** Workers VPC service id (`wrangler vpc service create`). */
91
+ serviceId: string;
92
+ /**
93
+ * Optional application path prefix that Modern.js should dispatch to this
94
+ * private origin with `env[binding].fetch(request)`, exactly like a
95
+ * prefixed Worker service binding.
96
+ */
97
+ prefix?: string;
98
+ }
87
99
  export interface CloudflareWorkerServiceBindingFragmentConfig {
88
100
  /** Stable remote id used by the shell's composition contract. */
89
101
  remote: string;
@@ -174,19 +186,64 @@ export interface CloudflareWorkerSecurityConfig {
174
186
  };
175
187
  reason?: string;
176
188
  }
189
+ export interface CloudflareWorkerDeployConfig {
190
+ name?: string;
191
+ /**
192
+ * Cloudflare Workers compatibility date for generated wrangler config.
193
+ * Use YYYY-MM-DD. Defaults to the date validated against the bundled
194
+ * Wrangler version used by UltraModern generated workspaces.
195
+ */
196
+ compatibilityDate?: string;
197
+ ssr?: boolean;
198
+ security?: CloudflareWorkerSecurityConfig;
199
+ /**
200
+ * Raw Wrangler-compatible config merged into `.output/wrangler.json`.
201
+ * Framework-owned worker invariants still win for `main`, the assets
202
+ * binding/directory/run mode, and required compatibility flags.
203
+ */
204
+ wrangler?: Record<string, JsonValue>;
205
+ /**
206
+ * Additional app-root files or directories to stage under `.output`.
207
+ * Use this for provider resources such as migrations or generated config.
208
+ */
209
+ artifacts?: CloudflareWorkerArtifactConfig[];
210
+ /**
211
+ * Additional app-root files or directories to serve as Cloudflare Worker
212
+ * Static Assets under `.output/public`. Use `to: '.'` to copy a
213
+ * source directory's contents into the public asset root.
214
+ */
215
+ publicAssets?: CloudflareWorkerPublicAssetConfig[];
216
+ /**
217
+ * First-class Cloudflare D1 bindings. Modern.js writes these to
218
+ * `wrangler.json` as `d1_databases` and stages configured migration
219
+ * directories into `.output`.
220
+ */
221
+ d1Databases?: CloudflareWorkerD1DatabaseConfig[];
222
+ /**
223
+ * First-class Cloudflare service bindings. Modern.js writes these to
224
+ * `wrangler.json` as `services`; when a binding also has `prefix`,
225
+ * the generated Worker dispatches matching requests through
226
+ * `env[binding].fetch(request)`.
227
+ */
228
+ services?: CloudflareWorkerServiceBindingConfig[];
229
+ /**
230
+ * First-class Workers VPC service bindings to private origins reached
231
+ * through a Cloudflare Tunnel. Modern.js writes these to `wrangler.json`
232
+ * as `vpc_services`; a binding with `prefix` joins the prefix dispatch of
233
+ * `services`.
234
+ */
235
+ vpcServices?: CloudflareWorkerVpcServiceConfig[];
236
+ /**
237
+ * Dist output paths that must not be copied into Cloudflare public assets.
238
+ * Entries are slash-normalized path prefixes relative to the app dist root.
239
+ * Top-level `api` and `shared` dist directories are excluded when matching
240
+ * source directories exist in the app root because they conventionally
241
+ * contain server-only implementation code.
242
+ */
243
+ publicAssetExcludes?: string[];
244
+ }
177
245
  export interface CloudflareDeployConfig {
178
- worker?: {
179
- name?: string;
180
- compatibilityDate?: string;
181
- ssr?: boolean;
182
- security?: CloudflareWorkerSecurityConfig;
183
- wrangler?: Record<string, JsonValue>;
184
- artifacts?: CloudflareWorkerArtifactConfig[];
185
- publicAssets?: CloudflareWorkerPublicAssetConfig[];
186
- d1Databases?: CloudflareWorkerD1DatabaseConfig[];
187
- services?: CloudflareWorkerServiceBindingConfig[];
188
- publicAssetExcludes?: string[];
189
- };
246
+ worker?: CloudflareWorkerDeployConfig;
190
247
  }
191
248
  export interface NodeDeployConfig {
192
249
  node?: {
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "directory": "packages/solutions/app-tools-extensions"
12
12
  },
13
13
  "license": "MIT",
14
- "version": "3.9.0-ultramodern.21",
14
+ "version": "3.9.0-ultramodern.23",
15
15
  "files": [
16
16
  "dist",
17
17
  "src"
@@ -153,16 +153,16 @@
153
153
  "@babel/parser": "^8.0.6",
154
154
  "@babel/traverse": "^8.0.6",
155
155
  "@loadable/component": "5.16.7",
156
- "@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.21",
157
- "@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.9.0-ultramodern.21",
158
- "@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.9.0-ultramodern.21",
159
- "@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.0-ultramodern.21",
160
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.21",
161
- "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.21",
162
- "@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.21",
163
- "@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.21",
164
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.21",
165
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.21",
156
+ "@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.23",
157
+ "@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.9.0-ultramodern.23",
158
+ "@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.9.0-ultramodern.23",
159
+ "@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.0-ultramodern.23",
160
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.23",
161
+ "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.23",
162
+ "@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.23",
163
+ "@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.23",
164
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.23",
165
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.23",
166
166
  "@swc/helpers": "^0.5.23",
167
167
  "@vercel/nft": "^1.11.0",
168
168
  "compression-webpack-plugin": "^12.0.0",
@@ -179,10 +179,10 @@
179
179
  }
180
180
  },
181
181
  "devDependencies": {
182
- "@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.21",
183
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.21",
184
- "@modern-js/plugin-bff-extensions": "npm:@bleedingdev/modern-js-plugin-bff-extensions@3.9.0-ultramodern.21",
185
- "@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.9.0-ultramodern.21",
182
+ "@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.23",
183
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.23",
184
+ "@modern-js/plugin-bff-extensions": "npm:@bleedingdev/modern-js-plugin-bff-extensions@3.9.0-ultramodern.23",
185
+ "@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.9.0-ultramodern.23",
186
186
  "@module-federation/modern-js-v3": "2.9.2",
187
187
  "@module-federation/runtime-tools": "2.9.2",
188
188
  "@rsbuild/core": "2.2.10",
@@ -1,4 +1,3 @@
1
- import { execFileSync } from 'node:child_process';
2
1
  import { createHash } from 'node:crypto';
3
2
  import {
4
3
  accessSync,
@@ -14,37 +13,97 @@ import {
14
13
  } from 'node:fs';
15
14
  import { createRequire } from 'node:module';
16
15
  import { tmpdir } from 'node:os';
17
- import { basename, dirname, join, resolve } from 'node:path';
16
+ import { basename, dirname, join } from 'node:path';
18
17
  import type { URL } from 'node:url';
19
18
 
20
19
  const EFFECT_TSGO_PACKAGE = '@effect/tsgo';
21
- const EFFECT_TSGO_BIN = 'effect-tsgo';
20
+ /** Native TypeScript packages, in the order Effect TS-Go discovers them. */
21
+ const NATIVE_TYPESCRIPT_PACKAGES = ['typescript', '@typescript/native'];
22
22
  const EFFECT_TSGO_RESOLUTION_ERROR =
23
23
  'Unable to resolve the Effect TS-Go compiler. Install "@effect/tsgo" and a native TypeScript backend for this build config, or set EFFECT_TSGO_BIN.';
24
24
  const executableEffectTsgoCompilers = new Map<string, string>();
25
25
 
26
26
  type PackageJson = {
27
- bin?: Record<string, string> | string;
27
+ version?: unknown;
28
28
  };
29
29
 
30
- function resolveEffectTsgoCli(from: string | URL): string {
30
+ type EffectTsgoUpstreamManifest = {
31
+ components?: {
32
+ typescript?: Record<string, unknown>;
33
+ };
34
+ };
35
+
36
+ function readJson<T>(fileName: string): T {
37
+ return JSON.parse(readFileSync(fileName, 'utf-8')) as T;
38
+ }
39
+
40
+ function resolvePackageVersion(
41
+ packageRequire: NodeJS.Require,
42
+ packageName: string,
43
+ ): { packageJsonPath: string; version: string } | undefined {
44
+ let packageJsonPath: string;
45
+ try {
46
+ packageJsonPath = packageRequire.resolve(`${packageName}/package.json`);
47
+ } catch {
48
+ return undefined;
49
+ }
50
+ const { version } = readJson<PackageJson>(packageJsonPath);
51
+ return typeof version === 'string' ? { packageJsonPath, version } : undefined;
52
+ }
53
+
54
+ /**
55
+ * Resolves only the TypeScript component packaged by Effect TS-Go.
56
+ *
57
+ * `effect-tsgo get-exe-path` discovers every integration before selecting the
58
+ * TypeScript one, so an installed Oxlint makes it fail on Linux musl. The
59
+ * TypeScript artifact itself is platform-neutral across libc: the
60
+ * `@effect/tsgo-<platform>-<arch>` package lists it in `lib/upstream.json`
61
+ * under `components.typescript` and ships it at
62
+ * `artifacts/typescript/<version>/tsc`, keyed by the installed
63
+ * `@typescript/typescript-<platform>-<arch>` version.
64
+ */
65
+ function resolveEffectTsgoTypeScriptArtifact(from: string | URL): string {
31
66
  const projectRequire = createRequire(from);
32
- const packageJsonPath = projectRequire.resolve(
67
+ const effectTsgoPackageJsonPath = projectRequire.resolve(
33
68
  `${EFFECT_TSGO_PACKAGE}/package.json`,
34
69
  );
35
- const packageJson = JSON.parse(
36
- readFileSync(packageJsonPath, 'utf-8'),
37
- ) as PackageJson;
38
- const bin =
39
- typeof packageJson.bin === 'string'
40
- ? packageJson.bin
41
- : packageJson.bin?.[EFFECT_TSGO_BIN];
42
-
43
- if (!bin) {
44
- throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
70
+ const platformTarget = `${process.platform}-${process.arch}`;
71
+ const effectTsgoPlatformPackageJsonPath = createRequire(
72
+ effectTsgoPackageJsonPath,
73
+ ).resolve(`${EFFECT_TSGO_PACKAGE}-${platformTarget}/package.json`);
74
+ const upstream = readJson<EffectTsgoUpstreamManifest>(
75
+ join(dirname(effectTsgoPlatformPackageJsonPath), 'lib', 'upstream.json'),
76
+ );
77
+ const packagedTypeScriptVersions = upstream.components?.typescript ?? {};
78
+
79
+ for (const packageName of NATIVE_TYPESCRIPT_PACKAGES) {
80
+ const typescript = resolvePackageVersion(projectRequire, packageName);
81
+ const major = typescript && /^\s*(\d+)/u.exec(typescript.version);
82
+ if (!typescript || !major || Number(major[1]) < 7) {
83
+ continue;
84
+ }
85
+
86
+ const typescriptPlatform = resolvePackageVersion(
87
+ createRequire(typescript.packageJsonPath),
88
+ `@typescript/typescript-${platformTarget}`,
89
+ );
90
+ if (
91
+ !typescriptPlatform ||
92
+ !Object.hasOwn(packagedTypeScriptVersions, typescriptPlatform.version)
93
+ ) {
94
+ break;
95
+ }
96
+
97
+ return join(
98
+ dirname(effectTsgoPlatformPackageJsonPath),
99
+ 'artifacts',
100
+ 'typescript',
101
+ typescriptPlatform.version,
102
+ process.platform === 'win32' ? 'tsc.exe' : 'tsc',
103
+ );
45
104
  }
46
105
 
47
- return resolve(dirname(packageJsonPath), bin);
106
+ throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
48
107
  }
49
108
 
50
109
  function resolveExecutableEffectTsgoCompiler(compilerPath: string): string {
@@ -143,17 +202,11 @@ export function resolveEffectTsgoCompiler(
143
202
  }
144
203
 
145
204
  try {
146
- const compiler = execFileSync(
147
- process.execPath,
148
- [resolveEffectTsgoCli(options.from), 'get-exe-path'],
149
- { encoding: 'utf-8' },
150
- ).trim();
151
-
152
- if (compiler) {
153
- return resolveExecutableEffectTsgoCompiler(compiler);
154
- }
205
+ return resolveExecutableEffectTsgoCompiler(
206
+ resolveEffectTsgoTypeScriptArtifact(options.from),
207
+ );
155
208
  } catch {
156
- // Use one stable error for package, platform-binary, and CLI failures.
209
+ // Use one stable error for package, platform-package, and artifact failures.
157
210
  }
158
211
 
159
212
  throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
@@ -28,6 +28,8 @@ import { isRecord, normalizeRelativePath } from './utils';
28
28
  import {
29
29
  createWorkerManifestServiceBindings,
30
30
  createWorkerServiceBindings,
31
+ createWorkerVpcServiceBindings,
32
+ getWorkerEffectBffPrefix,
31
33
  } from './wrangler-config';
32
34
 
33
35
  const createMissingEffectBffWorkerError = (
@@ -157,20 +159,31 @@ export const createWorkerManifest = async (
157
159
  }),
158
160
  );
159
161
 
160
- const bffPrefix = modernConfig.bff?.prefix;
161
- const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
162
162
  const isEffectApi =
163
163
  Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
164
+ const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
164
165
  const effectApiWorkerExists = await fse.pathExists(
165
166
  path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY),
166
167
  );
168
+ const typedServiceBindings = createWorkerServiceBindings(
169
+ modernConfig,
170
+ undefined,
171
+ );
167
172
  const serviceBindings = createWorkerManifestServiceBindings(
168
- createWorkerServiceBindings(modernConfig, undefined),
173
+ typedServiceBindings,
174
+ // The wrangler config checks VPC names against every Worker binding; the
175
+ // manifest only needs the framework-owned assets binding reserved.
176
+ createWorkerVpcServiceBindings(
177
+ modernConfig,
178
+ undefined,
179
+ typedServiceBindings,
180
+ new Set([ASSETS_BINDING]),
181
+ ),
169
182
  );
170
183
  const moduleFederation =
171
184
  await createModuleFederationWorkerManifest(outputDirectory);
172
185
 
173
- if (isEffectApi && primaryBffPrefix && !effectApiWorkerExists) {
186
+ if (effectBffPrefix !== undefined && !effectApiWorkerExists) {
174
187
  throw createMissingEffectBffWorkerError(
175
188
  outputDirectory,
176
189
  BFF_EFFECT_WORKER_ENTRY,
@@ -212,11 +225,11 @@ export const createWorkerManifest = async (
212
225
  ...(deliveryUnitStamp ? { deliveryUnit: deliveryUnitStamp } : {}),
213
226
  i18n: createI18nWorkerManifest(routeSpec, appContext),
214
227
  bff:
215
- isEffectApi && primaryBffPrefix && effectApiWorkerExists
228
+ effectBffPrefix !== undefined && effectApiWorkerExists
216
229
  ? {
217
230
  dispatcherExport: BFF_EFFECT_WORKER_DISPATCHER_EXPORT,
218
231
  runtimeFramework: 'effect',
219
- prefix: primaryBffPrefix,
232
+ prefix: effectBffPrefix,
220
233
  worker: BFF_EFFECT_WORKER_ENTRY,
221
234
  effect: effectBffManifest,
222
235
  }