@bleedingdev/modern-js-create 3.4.0-ultramodern.19 → 3.4.0-ultramodern.20

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.
Files changed (56) hide show
  1. package/dist/cjs/index.cjs +1 -1
  2. package/dist/cjs/locale/en.cjs +2 -2
  3. package/dist/cjs/locale/zh.cjs +2 -2
  4. package/dist/cjs/ultramodern-tooling/config.cjs +9 -91
  5. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +13 -40
  6. package/dist/cjs/ultramodern-workspace/contracts.cjs +2 -0
  7. package/dist/cjs/ultramodern-workspace/demo-components.cjs +12 -12
  8. package/dist/cjs/ultramodern-workspace/descriptors.cjs +0 -4
  9. package/dist/cjs/ultramodern-workspace/effect-api.cjs +74 -75
  10. package/dist/cjs/ultramodern-workspace/mf-validation.cjs +1 -2
  11. package/dist/cjs/ultramodern-workspace/module-federation.cjs +3 -2
  12. package/dist/cjs/ultramodern-workspace/package-json.cjs +6 -5
  13. package/dist/cjs/ultramodern-workspace/policy.cjs +1 -1
  14. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +7 -0
  15. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +4 -4
  16. package/dist/esm/index.js +1 -1
  17. package/dist/esm/locale/en.js +2 -2
  18. package/dist/esm/locale/zh.js +2 -2
  19. package/dist/esm/ultramodern-tooling/config.js +10 -92
  20. package/dist/esm/ultramodern-workspace/add-vertical.js +14 -41
  21. package/dist/esm/ultramodern-workspace/contracts.js +2 -0
  22. package/dist/esm/ultramodern-workspace/demo-components.js +12 -12
  23. package/dist/esm/ultramodern-workspace/descriptors.js +1 -2
  24. package/dist/esm/ultramodern-workspace/effect-api.js +74 -75
  25. package/dist/esm/ultramodern-workspace/mf-validation.js +1 -2
  26. package/dist/esm/ultramodern-workspace/module-federation.js +3 -2
  27. package/dist/esm/ultramodern-workspace/package-json.js +6 -5
  28. package/dist/esm/ultramodern-workspace/policy.js +1 -1
  29. package/dist/esm/ultramodern-workspace/workspace-scripts.js +5 -1
  30. package/dist/esm/ultramodern-workspace/write-workspace.js +4 -4
  31. package/dist/esm-node/index.js +1 -1
  32. package/dist/esm-node/locale/en.js +2 -2
  33. package/dist/esm-node/locale/zh.js +2 -2
  34. package/dist/esm-node/ultramodern-tooling/config.js +10 -92
  35. package/dist/esm-node/ultramodern-workspace/add-vertical.js +14 -41
  36. package/dist/esm-node/ultramodern-workspace/contracts.js +2 -0
  37. package/dist/esm-node/ultramodern-workspace/demo-components.js +12 -12
  38. package/dist/esm-node/ultramodern-workspace/descriptors.js +1 -2
  39. package/dist/esm-node/ultramodern-workspace/effect-api.js +74 -75
  40. package/dist/esm-node/ultramodern-workspace/mf-validation.js +1 -2
  41. package/dist/esm-node/ultramodern-workspace/module-federation.js +3 -2
  42. package/dist/esm-node/ultramodern-workspace/package-json.js +6 -5
  43. package/dist/esm-node/ultramodern-workspace/policy.js +1 -1
  44. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +5 -1
  45. package/dist/esm-node/ultramodern-workspace/write-workspace.js +4 -4
  46. package/dist/types/ultramodern-tooling/config.d.ts +1 -2
  47. package/dist/types/ultramodern-workspace/descriptors.d.ts +0 -1
  48. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +1 -0
  49. package/package.json +3 -3
  50. package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +1 -11
  51. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mjs +339 -0
  52. package/templates/workspace-scripts/generate-public-surface-assets.mjs +14 -12
  53. package/templates/workspace-scripts/proof-cloudflare-version.mjs +3 -13
  54. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +3 -3
  55. package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +1 -9
  56. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +82 -86
@@ -18,7 +18,7 @@ const ZH_LOCALE = {
18
18
  step1: 'cd {projectName}',
19
19
  step2: 'pnpm install',
20
20
  step3: 'pnpm dev',
21
- legacyModernJsWarning: "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n严重警告:你正在退出 ULTRAMODERN.JS 默认配置。\n免交互默认值是最佳 UltraModern.js 配置:\n结构化 SuperApp 工作区、presetUltramodern、TanStack Router、\nEffect BFF、Tailwind CSS v4,以及 BleedingDev 包版本队列。\n原始 Modern.js 初始化是危险的显式选择路径。\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
21
+ legacyModernJsWarning: "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n严重警告:你正在退出 ULTRAMODERN.JS 默认配置。\n免交互默认值是最佳 UltraModern.js 配置:\n结构化 SuperApp 工作区、presetUltramodern、TanStack Router、\n严格 Effect API 运行时、Tailwind CSS v4,以及 BleedingDev 包版本队列。\n原始 Modern.js 初始化是危险的显式选择路径。\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
22
22
  },
23
23
  help: {
24
24
  title: '🚀 UltraModern.js 项目创建工具',
@@ -30,7 +30,7 @@ const ZH_LOCALE = {
30
30
  optionVersion: ' -v, --version 显示版本信息',
31
31
  optionLang: ' -l, --lang 设置语言 (默认 en;zh 需显式选择)',
32
32
  optionTailwind: ' --no-tailwind 禁用默认 Tailwind CSS v4 工作区样式',
33
- optionBff: ' --bff 保留默认的 Effect BFF 脚手架(每个 MicroVertical 自带 Effect BFF)',
33
+ optionBff: ' --bff 保留默认的严格 Effect API 运行时(每个 MicroVertical 自带一个)',
34
34
  optionBffRuntime: ' --bff-runtime 选择 MicroVertical 脚手架的 BFF 运行时(支持: effect;默认: effect)',
35
35
  optionWorkspace: ' --workspace 对 @modern-js 依赖使用 workspace 协议(用于本地 monorepo 联调)',
36
36
  optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install;BleedingDev 默认使用 install alias)',
@@ -1,10 +1,8 @@
1
1
  import node_fs from "node:fs";
2
2
  import node_path from "node:path";
3
3
  import { normalizeUltramodernBridgeConfig } from "../ultramodern-workspace/bridge-config.js";
4
- import { GENERATED_CONTRACT_PATH, ULTRAMODERN_CONFIG_PATH, createNeutralOwnership, shellApp } from "../ultramodern-workspace/descriptors.js";
4
+ import { ULTRAMODERN_CONFIG_PATH, createNeutralOwnership, shellApp } from "../ultramodern-workspace/descriptors.js";
5
5
  import { toKebabCase } from "../ultramodern-workspace/naming.js";
6
- const PACKAGE_SOURCE_METADATA_PATH = '.modernjs/ultramodern-package-source.json';
7
- const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
8
6
  function readJsonObject(filePath) {
9
7
  const value = JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
10
8
  if (null === value || 'object' != typeof value || Array.isArray(value)) throw new Error(`UltraModern config must contain a JSON object: ${filePath}`);
@@ -13,45 +11,18 @@ function readJsonObject(filePath) {
13
11
  function readOptionalJsonObject(filePath) {
14
12
  return node_fs.existsSync(filePath) ? readJsonObject(filePath) : {};
15
13
  }
16
- function packageScopeFromRoot(workspaceRoot) {
17
- const rootPackage = readOptionalJsonObject(node_path.join(workspaceRoot, 'package.json'));
18
- return 'string' == typeof rootPackage.name && rootPackage.name.length > 0 ? rootPackage.name : node_path.basename(workspaceRoot);
14
+ function packageScopeFromRoot(workspaceRoot1) {
15
+ const rootPackage = readOptionalJsonObject(node_path.join(workspaceRoot1, 'package.json'));
16
+ return 'string' == typeof rootPackage.name && rootPackage.name.length > 0 ? rootPackage.name : node_path.basename(workspaceRoot1);
19
17
  }
20
- function packageSourceFromMetadata(workspaceRoot) {
21
- const metadataPath = node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH);
22
- if (!node_fs.existsSync(metadataPath)) return;
23
- const metadata = readJsonObject(metadataPath);
24
- const aliases = metadata.modernPackages?.aliases ?? {};
25
- const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
26
- const firstPackage = Object.keys(aliases)[0];
27
- const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
28
- const unscopedName = firstPackage?.split('/').at(-1) ?? '';
29
- const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
30
- const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
31
- return {
32
- strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
33
- modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : 'workspace:*',
34
- registry: 'string' == typeof metadata.modernPackages?.registry ? metadata.modernPackages.registry : void 0,
35
- aliasScope,
36
- aliasPackageNamePrefix
37
- };
38
- }
39
- function readOverlayPorts(workspaceRoot) {
40
- const overlayPath = node_path.join(workspaceRoot, DEVELOPMENT_OVERLAY_PATH);
41
- if (!node_fs.existsSync(overlayPath)) return {};
42
- const overlay = readJsonObject(overlayPath);
43
- const ports = overlay.ports;
44
- if (null === ports || 'object' != typeof ports || Array.isArray(ports)) return {};
45
- return Object.fromEntries(Object.entries(ports).filter((entry)=>'number' == typeof entry[1]));
46
- }
47
- function normalizeCompactConfig(workspaceRoot, sourcePath, config) {
18
+ function normalizeCompactConfig(sourcePath, config) {
48
19
  const packageSource = config.packageSource && 'object' == typeof config.packageSource ? {
49
20
  strategy: 'install' === config.packageSource.strategy ? 'install' : 'workspace',
50
21
  modernPackageVersion: 'string' == typeof config.packageSource.modernPackageVersion ? config.packageSource.modernPackageVersion : 'workspace:*',
51
22
  registry: 'string' == typeof config.packageSource.registry ? config.packageSource.registry : void 0,
52
23
  aliasScope: 'string' == typeof config.packageSource.aliasScope ? config.packageSource.aliasScope : void 0,
53
24
  aliasPackageNamePrefix: 'string' == typeof config.packageSource.aliasPackageNamePrefix ? config.packageSource.aliasPackageNamePrefix : void 0
54
- } : packageSourceFromMetadata(workspaceRoot);
25
+ } : void 0;
55
26
  return {
56
27
  schemaVersion: 'number' == typeof config.schemaVersion ? config.schemaVersion : 1,
57
28
  profile: 'string' == typeof config.profile ? config.profile : void 0,
@@ -97,63 +68,10 @@ function normalizeCompactConfig(workspaceRoot, sourcePath, config) {
97
68
  }
98
69
  };
99
70
  }
100
- function adaptLegacyContract(workspaceRoot, sourcePath, contract) {
101
- const ports = readOverlayPorts(workspaceRoot);
102
- const apps = Array.isArray(contract.apps) ? contract.apps : [];
103
- const shell = apps.find((app)=>app?.id === shellApp.id);
104
- return {
105
- schemaVersion: 'number' == typeof contract.schemaVersion ? contract.schemaVersion : 1,
106
- profile: 'string' == typeof contract.profile ? contract.profile : void 0,
107
- source: 'legacy',
108
- sourcePath,
109
- workspace: {
110
- packageScope: packageScopeFromRoot(workspaceRoot)
111
- },
112
- packageSource: packageSourceFromMetadata(workspaceRoot),
113
- features: {
114
- tailwind: shell?.styling?.tailwind !== false
115
- },
116
- topology: {
117
- apps: apps.map((app)=>{
118
- const id = String(app.id);
119
- const appPath = 'string' == typeof app.path ? app.path : id === shellApp.id ? shellApp.directory : `verticals/${toKebabCase(id)}`;
120
- const domain = 'string' == typeof app.i18n?.namespace && 'shell' !== app.i18n.namespace ? app.i18n.namespace : appPath.split('/').at(-1);
121
- return {
122
- id,
123
- kind: 'vertical' === app.kind ? 'vertical' : 'shell',
124
- path: appPath,
125
- package: 'string' == typeof app.package ? app.package : void 0,
126
- packageSuffix: 'string' == typeof app.package ? app.package.split('/').at(-1) : appPath.split('/').at(-1),
127
- displayName: id === shellApp.id ? shellApp.displayName : void 0,
128
- domain,
129
- port: ports[id],
130
- moduleFederation: app.moduleFederation && 'object' == typeof app.moduleFederation ? {
131
- role: 'vertical' === app.kind ? 'remote' : 'host',
132
- name: 'string' == typeof app.moduleFederation.name ? app.moduleFederation.name : void 0,
133
- exposes: Array.isArray(app.moduleFederation.exposes) ? app.moduleFederation.exposes.filter((expose)=>'string' == typeof expose) : void 0,
134
- verticalRefs: Array.isArray(app.moduleFederation.verticalRefs) ? app.moduleFederation.verticalRefs.filter((ref)=>'string' == typeof ref) : void 0,
135
- hostOnly: 'vertical' !== app.kind
136
- } : void 0,
137
- effectApi: app.effect && 'object' == typeof app.effect ? {
138
- stem: 'string' == typeof app.effect.prefix ? app.effect.prefix.split('/').filter(Boolean).at(-1) ?? domain ?? id : domain ?? id,
139
- prefix: 'string' == typeof app.effect.prefix ? app.effect.prefix : `/${domain ?? id}-api`,
140
- consumedBy: [
141
- shellApp.id,
142
- id
143
- ]
144
- } : void 0
145
- };
146
- })
147
- },
148
- legacyContract: contract
149
- };
150
- }
151
- function readUltramodernConfig(workspaceRoot = process.cwd()) {
152
- const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
153
- if (node_fs.existsSync(compactPath)) return normalizeCompactConfig(workspaceRoot, compactPath, readJsonObject(compactPath));
154
- const legacyPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
155
- if (node_fs.existsSync(legacyPath)) return adaptLegacyContract(workspaceRoot, legacyPath, readJsonObject(legacyPath));
156
- throw new Error(`Missing UltraModern config. Expected ${ULTRAMODERN_CONFIG_PATH} or ${GENERATED_CONTRACT_PATH}.`);
71
+ function readUltramodernConfig(workspaceRoot1 = process.cwd()) {
72
+ const compactPath = node_path.join(workspaceRoot1, ULTRAMODERN_CONFIG_PATH);
73
+ if (node_fs.existsSync(compactPath)) return normalizeCompactConfig(compactPath, readJsonObject(compactPath));
74
+ throw new Error(`Missing UltraModern config. Expected ${ULTRAMODERN_CONFIG_PATH}.`);
157
75
  }
158
76
  function workspaceAppsFromToolingConfig(config) {
159
77
  return config.topology.apps.map((app)=>{
@@ -5,7 +5,7 @@ import { WORKSPACE_PACKAGE_VERSION } from "../ultramodern-package-source.js";
5
5
  import { readUltramodernConfig } from "../ultramodern-tooling/config.js";
6
6
  import { createAppEnvDts, createAppRuntimeConfig, createShellFrameComponent } from "./app-files.js";
7
7
  import { createShellPage, createShellRemoteComponents } from "./demo-components.js";
8
- import { GENERATED_CONTRACT_PATH, ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, remoteDependencyAlias, shellApp, zephyrRemoteDependency } from "./descriptors.js";
8
+ import { ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, remoteDependencyAlias, shellApp, zephyrRemoteDependency } from "./descriptors.js";
9
9
  import { createShellEffectClient, effectApiTopologyMetadata } from "./effect-api.js";
10
10
  import { formatGeneratedWorkspaceFiles, readJsonFile, writeFileReplacing, writeJsonFile } from "./fs-io.js";
11
11
  import { createFileSnapshot, createGenerationResult, diffFileSnapshots } from "./generation-result.js";
@@ -24,7 +24,6 @@ const FIRST_VERTICAL_PORT = 4101;
24
24
  const TOPOLOGY_PATH = 'topology/reference-topology.json';
25
25
  const OWNERSHIP_PATH = 'topology/ownership.json';
26
26
  const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
27
- const PACKAGE_SOURCE_METADATA_PATH = '.modernjs/ultramodern-package-source.json';
28
27
  function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
29
28
  if (packageSource) return resolvePackageSource({
30
29
  targetDir: workspaceRoot,
@@ -37,37 +36,12 @@ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
37
36
  const compactConfig = readUltramodernConfig(workspaceRoot);
38
37
  if (compactConfig.packageSource) return compactConfig.packageSource;
39
38
  }
40
- const metadataPath = node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH);
41
- if (!node_fs.existsSync(metadataPath)) return resolvePackageSource({
42
- targetDir: workspaceRoot,
43
- packageName: node_path.basename(workspaceRoot),
44
- modernVersion
45
- });
46
- const metadata = readJsonFile(metadataPath);
47
- const aliases = metadata.modernPackages?.aliases ?? {};
48
- const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
49
- const firstPackage = Object.keys(aliases)[0];
50
- const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
51
- const unscopedName = firstPackage?.split('/').at(-1) ?? '';
52
- const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
53
- const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
54
- return {
55
- strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
56
- modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : modernVersion,
57
- registry: metadata.modernPackages?.registry,
58
- aliasScope,
59
- aliasPackageNamePrefix
60
- };
39
+ throw new Error(`Missing UltraModern workspace file: ${compactPath}`);
61
40
  }
62
41
  function existingTailwindEnabled(workspaceRoot) {
63
42
  const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
64
43
  if (node_fs.existsSync(compactPath)) return readUltramodernConfig(workspaceRoot).features.tailwind;
65
- const contractPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
66
- if (!node_fs.existsSync(contractPath)) return true;
67
- const contract = readJsonFile(contractPath);
68
- const apps = isRecord(contract) && Array.isArray(contract.apps) ? contract.apps : [];
69
- const shell = apps.find((app)=>isRecord(app) && app.id === shellApp.id);
70
- return shell?.styling && isRecord(shell.styling) ? false !== shell.styling.tailwind : true;
44
+ throw new Error(`Missing UltraModern workspace file: ${compactPath}`);
71
45
  }
72
46
  function existingBridgeConfig(workspaceRoot) {
73
47
  const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
@@ -113,7 +87,7 @@ function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwin
113
87
  writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/[lang]/page.tsx`, createShellPage(remotes));
114
88
  writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/vertical-components.tsx`, createShellRemoteComponents(scope, remotes));
115
89
  writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/shell-frame.tsx`, createShellFrameComponent());
116
- writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/effect/vertical-clients.ts`, createShellEffectClient(scope, remotes));
90
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/api/vertical-clients.ts`, createShellEffectClient(scope, remotes));
117
91
  }
118
92
  function addShellZephyrDependency(workspaceRoot, scope, remote) {
119
93
  const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
@@ -169,10 +143,11 @@ function verticalsFromTopology(topology, ports) {
169
143
  return (topology.verticals ?? []).map((vertical)=>{
170
144
  const domain = vertical.domain ?? String(vertical.id);
171
145
  const packageSuffix = vertical.package?.split('/').at(-1) ?? domain;
172
- const effectApi = vertical.api?.effect ? {
173
- stem: 'string' == typeof vertical.api.effect.basePath ? vertical.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
174
- prefix: vertical.api.effect.bff?.prefix ?? `/${domain}-api`,
175
- consumedBy: Array.isArray(vertical.api.effect.consumedBy) ? vertical.api.effect.consumedBy : [
146
+ const apiTopology = vertical.api;
147
+ const effectApi = apiTopology?.runtime === 'effect' ? {
148
+ stem: 'string' == typeof apiTopology.basePath ? apiTopology.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
149
+ prefix: apiTopology.bff?.prefix ?? `/${domain}-api`,
150
+ consumedBy: Array.isArray(apiTopology.consumedBy) ? apiTopology.consumedBy : [
176
151
  shellApp.id,
177
152
  vertical.id
178
153
  ]
@@ -266,9 +241,7 @@ function readRequiredJsonObject(filePath) {
266
241
  }
267
242
  function readRequiredWorkspaceConfig(workspaceRoot) {
268
243
  const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
269
- if (node_fs.existsSync(compactPath)) return void readRequiredJsonObject(compactPath);
270
- readRequiredJsonObject(node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH));
271
- readRequiredJsonObject(node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH));
244
+ readRequiredJsonObject(compactPath);
272
245
  }
273
246
  function assertOptionalJsonObject(value, label, filePath) {
274
247
  if (void 0 !== value && !isRecord(value)) throw new Error(`${label} in ${filePath} must be a JSON object`);
@@ -289,8 +262,8 @@ function validateWorkspaceAppDescriptors(apps) {
289
262
  if ('number' != typeof app.port || !Number.isFinite(app.port) || app.port <= 0) throw new Error(`Invalid development port for ${appLabel}`);
290
263
  assertNonEmptyString(app.mfName, `Module Federation name for ${appLabel}`);
291
264
  if (app.effectApi) {
292
- assertNonEmptyString(app.effectApi.prefix, `Effect API prefix for ${appLabel}`);
293
- if (!app.effectApi.prefix.startsWith('/')) throw new Error(`Effect API prefix for ${appLabel} must start with "/"`);
265
+ assertNonEmptyString(app.effectApi.prefix, `API prefix for ${appLabel}`);
266
+ if (!app.effectApi.prefix.startsWith('/')) throw new Error(`API prefix for ${appLabel} must start with "/"`);
294
267
  }
295
268
  }
296
269
  }
@@ -300,7 +273,7 @@ function validateUniqueWorkspaceAppDescriptors(apps) {
300
273
  assertUniqueAppField(apps, 'output path', (app)=>normalizePath(app.directory));
301
274
  assertUniqueAppField(apps, 'Module Federation name', (app)=>app.mfName);
302
275
  assertUniqueAppField(apps, 'development port', (app)=>String(app.port));
303
- assertUniqueAppField(apps, 'Effect API prefix', (app)=>app.effectApi?.prefix);
276
+ assertUniqueAppField(apps, 'API prefix', (app)=>app.effectApi?.prefix);
304
277
  assertUniqueAppField(apps, 'manifest environment name', (app)=>'vertical' === app.kind ? createRemoteManifestEnv(app) : void 0);
305
278
  }
306
279
  function assertUniqueAppField(apps, label, readValue) {
@@ -389,7 +362,7 @@ function createDryRunJsonMutations(preflight, manifestUrl) {
389
362
  {
390
363
  path: DEVELOPMENT_OVERLAY_PATH,
391
364
  pointer: `/apis/${vertical.id}`,
392
- description: `Add local Effect API URL for ${vertical.id}`,
365
+ description: `Add local API URL for ${vertical.id}`,
393
366
  value: `http://localhost:${vertical.port}${effectApiPrefix(vertical)}`
394
367
  }
395
368
  ] : [];
@@ -60,6 +60,7 @@ function createTopology(scope, remotes = []) {
60
60
  script: "scripts/validate-ultramodern-workspace.mjs",
61
61
  commands: [
62
62
  'pnpm i18n:boundaries',
63
+ 'pnpm api:check',
63
64
  'pnpm contract:check'
64
65
  ]
65
66
  }
@@ -253,6 +254,7 @@ function createUltramodernConfig(scope, modernVersion, packageSource, apps = [
253
254
  publicSurface: "scripts/generate-public-surface-assets.mjs",
254
255
  cloudflareProof: "scripts/proof-cloudflare-version.mjs",
255
256
  performanceReadiness: "scripts/ultramodern-performance-readiness.mjs",
257
+ apiBoundaries: "scripts/check-ultramodern-api-boundaries.mjs",
256
258
  skills: "scripts/bootstrap-agent-skills.mjs"
257
259
  }
258
260
  }
@@ -209,36 +209,36 @@ ${showcaseItems}
209
209
  }
210
210
  function createRemotePage(app) {
211
211
  const tw = createTw(tailwindPrefixForApp(app));
212
- const listEffectItems = `list${toPascalCase(effectApiStem(app))}`;
213
- const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
212
+ const listApiItems = `list${toPascalCase(effectApiStem(app))}`;
213
+ const apiImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
214
214
  import { Link } from '@modern-js/plugin-tanstack/runtime';
215
215
  import { useEffect, useState } from 'react';
216
216
  import {
217
217
  Effect,
218
- ${listEffectItems},
218
+ ${listApiItems},
219
219
  runEffectRequest,
220
- } from '../../effect/${effectApiStem(app)}-client';
220
+ } from '../../api/${effectApiStem(app)}-client';
221
221
  import { UltramodernRouteHead } from '../ultramodern-route-head';
222
222
  import { ultramodernUiMarker } from '../../ultramodern-build';
223
223
  ` : "import { useModernI18n } from '@modern-js/plugin-i18n/runtime';\nimport { Link } from '@modern-js/plugin-tanstack/runtime';\nimport { UltramodernRouteHead } from '../ultramodern-route-head';\nimport { ultramodernUiMarker } from '../../ultramodern-build';\n";
224
- const effectBffState = appHasEffectApi(app) ? ` const [effectApiStatus, setEffectApiStatus] = useState('pending');
224
+ const apiState = appHasEffectApi(app) ? ` const [apiStatus, setApiStatus] = useState('pending');
225
225
 
226
226
  useEffect(() => {
227
227
  let cancelled = false;
228
228
  void runEffectRequest(
229
- ${listEffectItems}({ limit: 1 }).pipe(
229
+ ${listApiItems}({ limit: 1 }).pipe(
230
230
  Effect.match({
231
231
  onFailure: () => {
232
232
  if (cancelled) {
233
233
  return;
234
234
  }
235
- setEffectApiStatus('unavailable');
235
+ setApiStatus('unavailable');
236
236
  },
237
237
  onSuccess: data => {
238
238
  if (cancelled) {
239
239
  return;
240
240
  }
241
- setEffectApiStatus(data.items.at(0)?.title ?? 'empty');
241
+ setApiStatus(data.items.at(0)?.title ?? 'empty');
242
242
  },
243
243
  }),
244
244
  ),
@@ -250,12 +250,12 @@ import { ultramodernUiMarker } from '../../ultramodern-build';
250
250
  }, []);
251
251
 
252
252
  ` : '';
253
- const effectBffMarkup = appHasEffectApi(app) ? ` <p data-testid="effect-bff-status">{effectApiStatus}</p>
253
+ const apiMarkup = appHasEffectApi(app) ? ` <p data-testid="api-status">{apiStatus}</p>
254
254
  ` : '';
255
- return `${effectBffImport}
255
+ return `${apiImport}
256
256
  export default function ${toPascalCase(app.id)}Home() {
257
257
  const { language, supportedLanguages, t } = useModernI18n();
258
- ${effectBffState} return (
258
+ ${apiState} return (
259
259
  <main className="${tw('min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8')}">
260
260
  <UltramodernRouteHead />
261
261
  <nav aria-label={t('${app.domain}.language.switcher')} className="${tw('flex gap-3')}">
@@ -276,7 +276,7 @@ ${effectBffState} return (
276
276
  <p className="${tw('sr-only')}" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
277
277
  {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
278
278
  </p>
279
- ${effectBffMarkup} </main>
279
+ ${apiMarkup} </main>
280
280
  );
281
281
  }
282
282
  `;
@@ -1,5 +1,4 @@
1
1
  import { packageName, toCamelCase, toEnvSegment, toKebabCase, toPascalCase } from "./naming.js";
2
- const GENERATED_CONTRACT_PATH = '.modernjs/ultramodern-generated-contract.json';
3
2
  const ULTRAMODERN_CONFIG_PATH = '.modernjs/ultramodern.json';
4
3
  const shellApp = {
5
4
  id: 'shell-super-app',
@@ -131,4 +130,4 @@ function createCloudflarePublicUrlEnv(app) {
131
130
  function appI18nNamespace(app) {
132
131
  return 'shell' === app.kind ? 'shell' : app.domain ?? app.id;
133
132
  }
134
- export { GENERATED_CONTRACT_PATH, ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, effectApiStem, remoteDependencyAlias, resolveRemoteRefs, sharedPackages, shellApp, verticalEffectApps, zephyrRemoteDependency };
133
+ export { ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, effectApiStem, remoteDependencyAlias, resolveRemoteRefs, sharedPackages, shellApp, verticalEffectApps, zephyrRemoteDependency };