@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
@@ -2,10 +2,8 @@ import "node:module";
2
2
  import node_fs from "node:fs";
3
3
  import node_path from "node:path";
4
4
  import { normalizeUltramodernBridgeConfig } from "../ultramodern-workspace/bridge-config.js";
5
- import { GENERATED_CONTRACT_PATH, ULTRAMODERN_CONFIG_PATH, createNeutralOwnership, shellApp } from "../ultramodern-workspace/descriptors.js";
5
+ import { ULTRAMODERN_CONFIG_PATH, createNeutralOwnership, shellApp } from "../ultramodern-workspace/descriptors.js";
6
6
  import { toKebabCase } from "../ultramodern-workspace/naming.js";
7
- const PACKAGE_SOURCE_METADATA_PATH = '.modernjs/ultramodern-package-source.json';
8
- const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
9
7
  function readJsonObject(filePath) {
10
8
  const value = JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
11
9
  if (null === value || 'object' != typeof value || Array.isArray(value)) throw new Error(`UltraModern config must contain a JSON object: ${filePath}`);
@@ -14,45 +12,18 @@ function readJsonObject(filePath) {
14
12
  function readOptionalJsonObject(filePath) {
15
13
  return node_fs.existsSync(filePath) ? readJsonObject(filePath) : {};
16
14
  }
17
- function packageScopeFromRoot(workspaceRoot) {
18
- const rootPackage = readOptionalJsonObject(node_path.join(workspaceRoot, 'package.json'));
19
- return 'string' == typeof rootPackage.name && rootPackage.name.length > 0 ? rootPackage.name : node_path.basename(workspaceRoot);
15
+ function packageScopeFromRoot(workspaceRoot1) {
16
+ const rootPackage = readOptionalJsonObject(node_path.join(workspaceRoot1, 'package.json'));
17
+ return 'string' == typeof rootPackage.name && rootPackage.name.length > 0 ? rootPackage.name : node_path.basename(workspaceRoot1);
20
18
  }
21
- function packageSourceFromMetadata(workspaceRoot) {
22
- const metadataPath = node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH);
23
- if (!node_fs.existsSync(metadataPath)) return;
24
- const metadata = readJsonObject(metadataPath);
25
- const aliases = metadata.modernPackages?.aliases ?? {};
26
- const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
27
- const firstPackage = Object.keys(aliases)[0];
28
- const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
29
- const unscopedName = firstPackage?.split('/').at(-1) ?? '';
30
- const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
31
- const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
32
- return {
33
- strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
34
- modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : 'workspace:*',
35
- registry: 'string' == typeof metadata.modernPackages?.registry ? metadata.modernPackages.registry : void 0,
36
- aliasScope,
37
- aliasPackageNamePrefix
38
- };
39
- }
40
- function readOverlayPorts(workspaceRoot) {
41
- const overlayPath = node_path.join(workspaceRoot, DEVELOPMENT_OVERLAY_PATH);
42
- if (!node_fs.existsSync(overlayPath)) return {};
43
- const overlay = readJsonObject(overlayPath);
44
- const ports = overlay.ports;
45
- if (null === ports || 'object' != typeof ports || Array.isArray(ports)) return {};
46
- return Object.fromEntries(Object.entries(ports).filter((entry)=>'number' == typeof entry[1]));
47
- }
48
- function normalizeCompactConfig(workspaceRoot, sourcePath, config) {
19
+ function normalizeCompactConfig(sourcePath, config) {
49
20
  const packageSource = config.packageSource && 'object' == typeof config.packageSource ? {
50
21
  strategy: 'install' === config.packageSource.strategy ? 'install' : 'workspace',
51
22
  modernPackageVersion: 'string' == typeof config.packageSource.modernPackageVersion ? config.packageSource.modernPackageVersion : 'workspace:*',
52
23
  registry: 'string' == typeof config.packageSource.registry ? config.packageSource.registry : void 0,
53
24
  aliasScope: 'string' == typeof config.packageSource.aliasScope ? config.packageSource.aliasScope : void 0,
54
25
  aliasPackageNamePrefix: 'string' == typeof config.packageSource.aliasPackageNamePrefix ? config.packageSource.aliasPackageNamePrefix : void 0
55
- } : packageSourceFromMetadata(workspaceRoot);
26
+ } : void 0;
56
27
  return {
57
28
  schemaVersion: 'number' == typeof config.schemaVersion ? config.schemaVersion : 1,
58
29
  profile: 'string' == typeof config.profile ? config.profile : void 0,
@@ -98,63 +69,10 @@ function normalizeCompactConfig(workspaceRoot, sourcePath, config) {
98
69
  }
99
70
  };
100
71
  }
101
- function adaptLegacyContract(workspaceRoot, sourcePath, contract) {
102
- const ports = readOverlayPorts(workspaceRoot);
103
- const apps = Array.isArray(contract.apps) ? contract.apps : [];
104
- const shell = apps.find((app)=>app?.id === shellApp.id);
105
- return {
106
- schemaVersion: 'number' == typeof contract.schemaVersion ? contract.schemaVersion : 1,
107
- profile: 'string' == typeof contract.profile ? contract.profile : void 0,
108
- source: 'legacy',
109
- sourcePath,
110
- workspace: {
111
- packageScope: packageScopeFromRoot(workspaceRoot)
112
- },
113
- packageSource: packageSourceFromMetadata(workspaceRoot),
114
- features: {
115
- tailwind: shell?.styling?.tailwind !== false
116
- },
117
- topology: {
118
- apps: apps.map((app)=>{
119
- const id = String(app.id);
120
- const appPath = 'string' == typeof app.path ? app.path : id === shellApp.id ? shellApp.directory : `verticals/${toKebabCase(id)}`;
121
- const domain = 'string' == typeof app.i18n?.namespace && 'shell' !== app.i18n.namespace ? app.i18n.namespace : appPath.split('/').at(-1);
122
- return {
123
- id,
124
- kind: 'vertical' === app.kind ? 'vertical' : 'shell',
125
- path: appPath,
126
- package: 'string' == typeof app.package ? app.package : void 0,
127
- packageSuffix: 'string' == typeof app.package ? app.package.split('/').at(-1) : appPath.split('/').at(-1),
128
- displayName: id === shellApp.id ? shellApp.displayName : void 0,
129
- domain,
130
- port: ports[id],
131
- moduleFederation: app.moduleFederation && 'object' == typeof app.moduleFederation ? {
132
- role: 'vertical' === app.kind ? 'remote' : 'host',
133
- name: 'string' == typeof app.moduleFederation.name ? app.moduleFederation.name : void 0,
134
- exposes: Array.isArray(app.moduleFederation.exposes) ? app.moduleFederation.exposes.filter((expose)=>'string' == typeof expose) : void 0,
135
- verticalRefs: Array.isArray(app.moduleFederation.verticalRefs) ? app.moduleFederation.verticalRefs.filter((ref)=>'string' == typeof ref) : void 0,
136
- hostOnly: 'vertical' !== app.kind
137
- } : void 0,
138
- effectApi: app.effect && 'object' == typeof app.effect ? {
139
- stem: 'string' == typeof app.effect.prefix ? app.effect.prefix.split('/').filter(Boolean).at(-1) ?? domain ?? id : domain ?? id,
140
- prefix: 'string' == typeof app.effect.prefix ? app.effect.prefix : `/${domain ?? id}-api`,
141
- consumedBy: [
142
- shellApp.id,
143
- id
144
- ]
145
- } : void 0
146
- };
147
- })
148
- },
149
- legacyContract: contract
150
- };
151
- }
152
- function readUltramodernConfig(workspaceRoot = process.cwd()) {
153
- const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
154
- if (node_fs.existsSync(compactPath)) return normalizeCompactConfig(workspaceRoot, compactPath, readJsonObject(compactPath));
155
- const legacyPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
156
- if (node_fs.existsSync(legacyPath)) return adaptLegacyContract(workspaceRoot, legacyPath, readJsonObject(legacyPath));
157
- throw new Error(`Missing UltraModern config. Expected ${ULTRAMODERN_CONFIG_PATH} or ${GENERATED_CONTRACT_PATH}.`);
72
+ function readUltramodernConfig(workspaceRoot1 = process.cwd()) {
73
+ const compactPath = node_path.join(workspaceRoot1, ULTRAMODERN_CONFIG_PATH);
74
+ if (node_fs.existsSync(compactPath)) return normalizeCompactConfig(compactPath, readJsonObject(compactPath));
75
+ throw new Error(`Missing UltraModern config. Expected ${ULTRAMODERN_CONFIG_PATH}.`);
158
76
  }
159
77
  function workspaceAppsFromToolingConfig(config) {
160
78
  return config.topology.apps.map((app)=>{
@@ -6,7 +6,7 @@ import { WORKSPACE_PACKAGE_VERSION } from "../ultramodern-package-source.js";
6
6
  import { readUltramodernConfig } from "../ultramodern-tooling/config.js";
7
7
  import { createAppEnvDts, createAppRuntimeConfig, createShellFrameComponent } from "./app-files.js";
8
8
  import { createShellPage, createShellRemoteComponents } from "./demo-components.js";
9
- import { GENERATED_CONTRACT_PATH, ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, remoteDependencyAlias, shellApp, zephyrRemoteDependency } from "./descriptors.js";
9
+ import { ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, remoteDependencyAlias, shellApp, zephyrRemoteDependency } from "./descriptors.js";
10
10
  import { createShellEffectClient, effectApiTopologyMetadata } from "./effect-api.js";
11
11
  import { formatGeneratedWorkspaceFiles, readJsonFile, writeFileReplacing, writeJsonFile } from "./fs-io.js";
12
12
  import { createFileSnapshot, createGenerationResult, diffFileSnapshots } from "./generation-result.js";
@@ -25,7 +25,6 @@ const FIRST_VERTICAL_PORT = 4101;
25
25
  const TOPOLOGY_PATH = 'topology/reference-topology.json';
26
26
  const OWNERSHIP_PATH = 'topology/ownership.json';
27
27
  const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
28
- const PACKAGE_SOURCE_METADATA_PATH = '.modernjs/ultramodern-package-source.json';
29
28
  function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
30
29
  if (packageSource) return resolvePackageSource({
31
30
  targetDir: workspaceRoot,
@@ -38,37 +37,12 @@ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
38
37
  const compactConfig = readUltramodernConfig(workspaceRoot);
39
38
  if (compactConfig.packageSource) return compactConfig.packageSource;
40
39
  }
41
- const metadataPath = node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH);
42
- if (!node_fs.existsSync(metadataPath)) return resolvePackageSource({
43
- targetDir: workspaceRoot,
44
- packageName: node_path.basename(workspaceRoot),
45
- modernVersion
46
- });
47
- const metadata = readJsonFile(metadataPath);
48
- const aliases = metadata.modernPackages?.aliases ?? {};
49
- const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
50
- const firstPackage = Object.keys(aliases)[0];
51
- const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
52
- const unscopedName = firstPackage?.split('/').at(-1) ?? '';
53
- const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
54
- const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
55
- return {
56
- strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
57
- modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : modernVersion,
58
- registry: metadata.modernPackages?.registry,
59
- aliasScope,
60
- aliasPackageNamePrefix
61
- };
40
+ throw new Error(`Missing UltraModern workspace file: ${compactPath}`);
62
41
  }
63
42
  function existingTailwindEnabled(workspaceRoot) {
64
43
  const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
65
44
  if (node_fs.existsSync(compactPath)) return readUltramodernConfig(workspaceRoot).features.tailwind;
66
- const contractPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
67
- if (!node_fs.existsSync(contractPath)) return true;
68
- const contract = readJsonFile(contractPath);
69
- const apps = isRecord(contract) && Array.isArray(contract.apps) ? contract.apps : [];
70
- const shell = apps.find((app)=>isRecord(app) && app.id === shellApp.id);
71
- return shell?.styling && isRecord(shell.styling) ? false !== shell.styling.tailwind : true;
45
+ throw new Error(`Missing UltraModern workspace file: ${compactPath}`);
72
46
  }
73
47
  function existingBridgeConfig(workspaceRoot) {
74
48
  const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
@@ -114,7 +88,7 @@ function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwin
114
88
  writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/[lang]/page.tsx`, createShellPage(remotes));
115
89
  writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/vertical-components.tsx`, createShellRemoteComponents(scope, remotes));
116
90
  writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/shell-frame.tsx`, createShellFrameComponent());
117
- writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/effect/vertical-clients.ts`, createShellEffectClient(scope, remotes));
91
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/api/vertical-clients.ts`, createShellEffectClient(scope, remotes));
118
92
  }
119
93
  function addShellZephyrDependency(workspaceRoot, scope, remote) {
120
94
  const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
@@ -170,10 +144,11 @@ function verticalsFromTopology(topology, ports) {
170
144
  return (topology.verticals ?? []).map((vertical)=>{
171
145
  const domain = vertical.domain ?? String(vertical.id);
172
146
  const packageSuffix = vertical.package?.split('/').at(-1) ?? domain;
173
- const effectApi = vertical.api?.effect ? {
174
- stem: 'string' == typeof vertical.api.effect.basePath ? vertical.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
175
- prefix: vertical.api.effect.bff?.prefix ?? `/${domain}-api`,
176
- consumedBy: Array.isArray(vertical.api.effect.consumedBy) ? vertical.api.effect.consumedBy : [
147
+ const apiTopology = vertical.api;
148
+ const effectApi = apiTopology?.runtime === 'effect' ? {
149
+ stem: 'string' == typeof apiTopology.basePath ? apiTopology.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
150
+ prefix: apiTopology.bff?.prefix ?? `/${domain}-api`,
151
+ consumedBy: Array.isArray(apiTopology.consumedBy) ? apiTopology.consumedBy : [
177
152
  shellApp.id,
178
153
  vertical.id
179
154
  ]
@@ -267,9 +242,7 @@ function readRequiredJsonObject(filePath) {
267
242
  }
268
243
  function readRequiredWorkspaceConfig(workspaceRoot) {
269
244
  const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
270
- if (node_fs.existsSync(compactPath)) return void readRequiredJsonObject(compactPath);
271
- readRequiredJsonObject(node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH));
272
- readRequiredJsonObject(node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH));
245
+ readRequiredJsonObject(compactPath);
273
246
  }
274
247
  function assertOptionalJsonObject(value, label, filePath) {
275
248
  if (void 0 !== value && !isRecord(value)) throw new Error(`${label} in ${filePath} must be a JSON object`);
@@ -290,8 +263,8 @@ function validateWorkspaceAppDescriptors(apps) {
290
263
  if ('number' != typeof app.port || !Number.isFinite(app.port) || app.port <= 0) throw new Error(`Invalid development port for ${appLabel}`);
291
264
  assertNonEmptyString(app.mfName, `Module Federation name for ${appLabel}`);
292
265
  if (app.effectApi) {
293
- assertNonEmptyString(app.effectApi.prefix, `Effect API prefix for ${appLabel}`);
294
- if (!app.effectApi.prefix.startsWith('/')) throw new Error(`Effect API prefix for ${appLabel} must start with "/"`);
266
+ assertNonEmptyString(app.effectApi.prefix, `API prefix for ${appLabel}`);
267
+ if (!app.effectApi.prefix.startsWith('/')) throw new Error(`API prefix for ${appLabel} must start with "/"`);
295
268
  }
296
269
  }
297
270
  }
@@ -301,7 +274,7 @@ function validateUniqueWorkspaceAppDescriptors(apps) {
301
274
  assertUniqueAppField(apps, 'output path', (app)=>normalizePath(app.directory));
302
275
  assertUniqueAppField(apps, 'Module Federation name', (app)=>app.mfName);
303
276
  assertUniqueAppField(apps, 'development port', (app)=>String(app.port));
304
- assertUniqueAppField(apps, 'Effect API prefix', (app)=>app.effectApi?.prefix);
277
+ assertUniqueAppField(apps, 'API prefix', (app)=>app.effectApi?.prefix);
305
278
  assertUniqueAppField(apps, 'manifest environment name', (app)=>'vertical' === app.kind ? createRemoteManifestEnv(app) : void 0);
306
279
  }
307
280
  function assertUniqueAppField(apps, label, readValue) {
@@ -390,7 +363,7 @@ function createDryRunJsonMutations(preflight, manifestUrl) {
390
363
  {
391
364
  path: DEVELOPMENT_OVERLAY_PATH,
392
365
  pointer: `/apis/${vertical.id}`,
393
- description: `Add local Effect API URL for ${vertical.id}`,
366
+ description: `Add local API URL for ${vertical.id}`,
394
367
  value: `http://localhost:${vertical.port}${effectApiPrefix(vertical)}`
395
368
  }
396
369
  ] : [];
@@ -61,6 +61,7 @@ function createTopology(scope, remotes = []) {
61
61
  script: "scripts/validate-ultramodern-workspace.mjs",
62
62
  commands: [
63
63
  'pnpm i18n:boundaries',
64
+ 'pnpm api:check',
64
65
  'pnpm contract:check'
65
66
  ]
66
67
  }
@@ -254,6 +255,7 @@ function createUltramodernConfig(scope, modernVersion, packageSource, apps = [
254
255
  publicSurface: "scripts/generate-public-surface-assets.mjs",
255
256
  cloudflareProof: "scripts/proof-cloudflare-version.mjs",
256
257
  performanceReadiness: "scripts/ultramodern-performance-readiness.mjs",
258
+ apiBoundaries: "scripts/check-ultramodern-api-boundaries.mjs",
257
259
  skills: "scripts/bootstrap-agent-skills.mjs"
258
260
  }
259
261
  }
@@ -210,36 +210,36 @@ ${showcaseItems}
210
210
  }
211
211
  function createRemotePage(app) {
212
212
  const tw = createTw(tailwindPrefixForApp(app));
213
- const listEffectItems = `list${toPascalCase(effectApiStem(app))}`;
214
- const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
213
+ const listApiItems = `list${toPascalCase(effectApiStem(app))}`;
214
+ const apiImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
215
215
  import { Link } from '@modern-js/plugin-tanstack/runtime';
216
216
  import { useEffect, useState } from 'react';
217
217
  import {
218
218
  Effect,
219
- ${listEffectItems},
219
+ ${listApiItems},
220
220
  runEffectRequest,
221
- } from '../../effect/${effectApiStem(app)}-client';
221
+ } from '../../api/${effectApiStem(app)}-client';
222
222
  import { UltramodernRouteHead } from '../ultramodern-route-head';
223
223
  import { ultramodernUiMarker } from '../../ultramodern-build';
224
224
  ` : "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";
225
- const effectBffState = appHasEffectApi(app) ? ` const [effectApiStatus, setEffectApiStatus] = useState('pending');
225
+ const apiState = appHasEffectApi(app) ? ` const [apiStatus, setApiStatus] = useState('pending');
226
226
 
227
227
  useEffect(() => {
228
228
  let cancelled = false;
229
229
  void runEffectRequest(
230
- ${listEffectItems}({ limit: 1 }).pipe(
230
+ ${listApiItems}({ limit: 1 }).pipe(
231
231
  Effect.match({
232
232
  onFailure: () => {
233
233
  if (cancelled) {
234
234
  return;
235
235
  }
236
- setEffectApiStatus('unavailable');
236
+ setApiStatus('unavailable');
237
237
  },
238
238
  onSuccess: data => {
239
239
  if (cancelled) {
240
240
  return;
241
241
  }
242
- setEffectApiStatus(data.items.at(0)?.title ?? 'empty');
242
+ setApiStatus(data.items.at(0)?.title ?? 'empty');
243
243
  },
244
244
  }),
245
245
  ),
@@ -251,12 +251,12 @@ import { ultramodernUiMarker } from '../../ultramodern-build';
251
251
  }, []);
252
252
 
253
253
  ` : '';
254
- const effectBffMarkup = appHasEffectApi(app) ? ` <p data-testid="effect-bff-status">{effectApiStatus}</p>
254
+ const apiMarkup = appHasEffectApi(app) ? ` <p data-testid="api-status">{apiStatus}</p>
255
255
  ` : '';
256
- return `${effectBffImport}
256
+ return `${apiImport}
257
257
  export default function ${toPascalCase(app.id)}Home() {
258
258
  const { language, supportedLanguages, t } = useModernI18n();
259
- ${effectBffState} return (
259
+ ${apiState} return (
260
260
  <main className="${tw('min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8')}">
261
261
  <UltramodernRouteHead />
262
262
  <nav aria-label={t('${app.domain}.language.switcher')} className="${tw('flex gap-3')}">
@@ -277,7 +277,7 @@ ${effectBffState} return (
277
277
  <p className="${tw('sr-only')}" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
278
278
  {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
279
279
  </p>
280
- ${effectBffMarkup} </main>
280
+ ${apiMarkup} </main>
281
281
  );
282
282
  }
283
283
  `;
@@ -1,6 +1,5 @@
1
1
  import "node:module";
2
2
  import { packageName, toCamelCase, toEnvSegment, toKebabCase, toPascalCase } from "./naming.js";
3
- const GENERATED_CONTRACT_PATH = '.modernjs/ultramodern-generated-contract.json';
4
3
  const ULTRAMODERN_CONFIG_PATH = '.modernjs/ultramodern.json';
5
4
  const shellApp = {
6
5
  id: 'shell-super-app',
@@ -132,4 +131,4 @@ function createCloudflarePublicUrlEnv(app) {
132
131
  function appI18nNamespace(app) {
133
132
  return 'shell' === app.kind ? 'shell' : app.domain ?? app.id;
134
133
  }
135
- export { GENERATED_CONTRACT_PATH, ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, effectApiStem, remoteDependencyAlias, resolveRemoteRefs, sharedPackages, shellApp, verticalEffectApps, zephyrRemoteDependency };
134
+ export { ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, effectApiStem, remoteDependencyAlias, resolveRemoteRefs, sharedPackages, shellApp, verticalEffectApps, zephyrRemoteDependency };