@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
|
@@ -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 {
|
|
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(
|
|
18
|
-
const rootPackage = readOptionalJsonObject(node_path.join(
|
|
19
|
-
return 'string' == typeof rootPackage.name && rootPackage.name.length > 0 ? rootPackage.name : node_path.basename(
|
|
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
|
|
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
|
-
} :
|
|
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
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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, `
|
|
294
|
-
if (!app.effectApi.prefix.startsWith('/')) throw new Error(`
|
|
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, '
|
|
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
|
|
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
|
|
214
|
-
const
|
|
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
|
-
${
|
|
219
|
+
${listApiItems},
|
|
220
220
|
runEffectRequest,
|
|
221
|
-
} from '../../
|
|
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
|
|
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
|
-
${
|
|
230
|
+
${listApiItems}({ limit: 1 }).pipe(
|
|
231
231
|
Effect.match({
|
|
232
232
|
onFailure: () => {
|
|
233
233
|
if (cancelled) {
|
|
234
234
|
return;
|
|
235
235
|
}
|
|
236
|
-
|
|
236
|
+
setApiStatus('unavailable');
|
|
237
237
|
},
|
|
238
238
|
onSuccess: data => {
|
|
239
239
|
if (cancelled) {
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
242
|
-
|
|
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
|
|
254
|
+
const apiMarkup = appHasEffectApi(app) ? ` <p data-testid="api-status">{apiStatus}</p>
|
|
255
255
|
` : '';
|
|
256
|
-
return `${
|
|
256
|
+
return `${apiImport}
|
|
257
257
|
export default function ${toPascalCase(app.id)}Home() {
|
|
258
258
|
const { language, supportedLanguages, t } = useModernI18n();
|
|
259
|
-
${
|
|
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
|
-
${
|
|
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 {
|
|
134
|
+
export { ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createCloudflarePublicUrlEnv, createCloudflareWorkerName, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, effectApiStem, remoteDependencyAlias, resolveRemoteRefs, sharedPackages, shellApp, verticalEffectApps, zephyrRemoteDependency };
|