@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.
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/locale/en.cjs +2 -2
- package/dist/cjs/locale/zh.cjs +2 -2
- package/dist/cjs/ultramodern-tooling/config.cjs +9 -91
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +13 -40
- package/dist/cjs/ultramodern-workspace/contracts.cjs +2 -0
- package/dist/cjs/ultramodern-workspace/demo-components.cjs +12 -12
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +0 -4
- package/dist/cjs/ultramodern-workspace/effect-api.cjs +74 -75
- package/dist/cjs/ultramodern-workspace/mf-validation.cjs +1 -2
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +3 -2
- package/dist/cjs/ultramodern-workspace/package-json.cjs +6 -5
- package/dist/cjs/ultramodern-workspace/policy.cjs +1 -1
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +7 -0
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +4 -4
- package/dist/esm/index.js +1 -1
- package/dist/esm/locale/en.js +2 -2
- package/dist/esm/locale/zh.js +2 -2
- package/dist/esm/ultramodern-tooling/config.js +10 -92
- package/dist/esm/ultramodern-workspace/add-vertical.js +14 -41
- package/dist/esm/ultramodern-workspace/contracts.js +2 -0
- package/dist/esm/ultramodern-workspace/demo-components.js +12 -12
- package/dist/esm/ultramodern-workspace/descriptors.js +1 -2
- package/dist/esm/ultramodern-workspace/effect-api.js +74 -75
- package/dist/esm/ultramodern-workspace/mf-validation.js +1 -2
- package/dist/esm/ultramodern-workspace/module-federation.js +3 -2
- package/dist/esm/ultramodern-workspace/package-json.js +6 -5
- package/dist/esm/ultramodern-workspace/policy.js +1 -1
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +5 -1
- package/dist/esm/ultramodern-workspace/write-workspace.js +4 -4
- package/dist/esm-node/index.js +1 -1
- package/dist/esm-node/locale/en.js +2 -2
- package/dist/esm-node/locale/zh.js +2 -2
- package/dist/esm-node/ultramodern-tooling/config.js +10 -92
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +14 -41
- package/dist/esm-node/ultramodern-workspace/contracts.js +2 -0
- package/dist/esm-node/ultramodern-workspace/demo-components.js +12 -12
- package/dist/esm-node/ultramodern-workspace/descriptors.js +1 -2
- package/dist/esm-node/ultramodern-workspace/effect-api.js +74 -75
- package/dist/esm-node/ultramodern-workspace/mf-validation.js +1 -2
- package/dist/esm-node/ultramodern-workspace/module-federation.js +3 -2
- package/dist/esm-node/ultramodern-workspace/package-json.js +6 -5
- package/dist/esm-node/ultramodern-workspace/policy.js +1 -1
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +5 -1
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +4 -4
- package/dist/types/ultramodern-tooling/config.d.ts +1 -2
- package/dist/types/ultramodern-workspace/descriptors.d.ts +0 -1
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +1 -0
- package/package.json +3 -3
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +1 -11
- package/templates/workspace-scripts/check-ultramodern-api-boundaries.mjs +339 -0
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +14 -12
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +3 -13
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +3 -3
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +1 -9
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +82 -86
package/dist/esm/locale/zh.js
CHANGED
|
@@ -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、\
|
|
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
|
|
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 {
|
|
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(
|
|
17
|
-
const rootPackage = readOptionalJsonObject(node_path.join(
|
|
18
|
-
return 'string' == typeof rootPackage.name && rootPackage.name.length > 0 ? rootPackage.name : node_path.basename(
|
|
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
|
|
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
|
-
} :
|
|
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
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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, `
|
|
293
|
-
if (!app.effectApi.prefix.startsWith('/')) throw new Error(`
|
|
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, '
|
|
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
|
|
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
|
|
213
|
-
const
|
|
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
|
-
${
|
|
218
|
+
${listApiItems},
|
|
219
219
|
runEffectRequest,
|
|
220
|
-
} from '../../
|
|
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
|
|
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
|
-
${
|
|
229
|
+
${listApiItems}({ limit: 1 }).pipe(
|
|
230
230
|
Effect.match({
|
|
231
231
|
onFailure: () => {
|
|
232
232
|
if (cancelled) {
|
|
233
233
|
return;
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
setApiStatus('unavailable');
|
|
236
236
|
},
|
|
237
237
|
onSuccess: data => {
|
|
238
238
|
if (cancelled) {
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
|
-
|
|
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
|
|
253
|
+
const apiMarkup = appHasEffectApi(app) ? ` <p data-testid="api-status">{apiStatus}</p>
|
|
254
254
|
` : '';
|
|
255
|
-
return `${
|
|
255
|
+
return `${apiImport}
|
|
256
256
|
export default function ${toPascalCase(app.id)}Home() {
|
|
257
257
|
const { language, supportedLanguages, t } = useModernI18n();
|
|
258
|
-
${
|
|
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
|
-
${
|
|
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 {
|
|
133
|
+
export { ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, effectApiStem, remoteDependencyAlias, resolveRemoteRefs, sharedPackages, shellApp, verticalEffectApps, zephyrRemoteDependency };
|