@bleedingdev/modern-js-create 3.4.0-ultramodern.2 → 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/README.md +13 -11
- package/dist/cjs/index.cjs +37 -4
- package/dist/cjs/locale/en.cjs +12 -2
- package/dist/cjs/locale/zh.cjs +12 -2
- package/dist/cjs/ultramodern-tooling/commands.cjs +185 -0
- package/dist/cjs/ultramodern-tooling/config.cjs +172 -0
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +79 -61
- package/dist/cjs/ultramodern-workspace/bridge-config.cjs +327 -0
- package/dist/cjs/ultramodern-workspace/codesmith.cjs +1 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +121 -648
- package/dist/cjs/ultramodern-workspace/demo-components.cjs +26 -33
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +4 -4
- package/dist/cjs/ultramodern-workspace/effect-api.cjs +230 -100
- package/dist/cjs/ultramodern-workspace/fs-io.cjs +117 -0
- package/dist/cjs/ultramodern-workspace/generation-result.cjs +1 -1
- package/dist/cjs/ultramodern-workspace/index.cjs +4 -0
- package/dist/cjs/ultramodern-workspace/mf-validation.cjs +571 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +51 -20
- package/dist/cjs/ultramodern-workspace/package-json.cjs +62 -19
- package/dist/cjs/ultramodern-workspace/policy.cjs +2 -2
- package/dist/cjs/ultramodern-workspace/public-api.cjs +4 -0
- package/dist/cjs/ultramodern-workspace/versions.cjs +5 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +80 -43
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +63 -20
- package/dist/esm/index.js +37 -4
- package/dist/esm/locale/en.js +12 -2
- package/dist/esm/locale/zh.js +12 -2
- package/dist/esm/ultramodern-tooling/commands.js +132 -0
- package/dist/esm/ultramodern-tooling/config.js +120 -0
- package/dist/esm/ultramodern-workspace/add-vertical.js +81 -66
- package/dist/esm/ultramodern-workspace/bridge-config.js +270 -0
- package/dist/esm/ultramodern-workspace/codesmith.js +1 -0
- package/dist/esm/ultramodern-workspace/contracts.js +124 -603
- package/dist/esm/ultramodern-workspace/demo-components.js +26 -33
- package/dist/esm/ultramodern-workspace/descriptors.js +2 -2
- package/dist/esm/ultramodern-workspace/effect-api.js +230 -100
- package/dist/esm/ultramodern-workspace/fs-io.js +116 -2
- package/dist/esm/ultramodern-workspace/generation-result.js +2 -2
- package/dist/esm/ultramodern-workspace/index.js +1 -0
- package/dist/esm/ultramodern-workspace/mf-validation.js +516 -0
- package/dist/esm/ultramodern-workspace/module-federation.js +49 -21
- package/dist/esm/ultramodern-workspace/package-json.js +60 -20
- package/dist/esm/ultramodern-workspace/policy.js +2 -2
- package/dist/esm/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm/ultramodern-workspace/versions.js +3 -1
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +78 -26
- package/dist/esm/ultramodern-workspace/write-workspace.js +67 -27
- package/dist/esm-node/index.js +37 -4
- package/dist/esm-node/locale/en.js +12 -2
- package/dist/esm-node/locale/zh.js +12 -2
- package/dist/esm-node/ultramodern-tooling/commands.js +133 -0
- package/dist/esm-node/ultramodern-tooling/config.js +121 -0
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +81 -66
- package/dist/esm-node/ultramodern-workspace/bridge-config.js +271 -0
- package/dist/esm-node/ultramodern-workspace/codesmith.js +1 -0
- package/dist/esm-node/ultramodern-workspace/contracts.js +124 -603
- package/dist/esm-node/ultramodern-workspace/demo-components.js +26 -33
- package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -2
- package/dist/esm-node/ultramodern-workspace/effect-api.js +230 -100
- package/dist/esm-node/ultramodern-workspace/fs-io.js +116 -2
- package/dist/esm-node/ultramodern-workspace/generation-result.js +2 -2
- package/dist/esm-node/ultramodern-workspace/index.js +1 -0
- package/dist/esm-node/ultramodern-workspace/mf-validation.js +517 -0
- package/dist/esm-node/ultramodern-workspace/module-federation.js +49 -21
- package/dist/esm-node/ultramodern-workspace/package-json.js +60 -20
- package/dist/esm-node/ultramodern-workspace/policy.js +2 -2
- package/dist/esm-node/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm-node/ultramodern-workspace/versions.js +3 -1
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +78 -26
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +67 -27
- package/dist/types/locale/en.d.ts +10 -0
- package/dist/types/locale/index.d.ts +20 -0
- package/dist/types/locale/zh.d.ts +10 -0
- package/dist/types/ultramodern-tooling/commands.d.ts +1 -0
- package/dist/types/ultramodern-tooling/config.d.ts +43 -0
- package/dist/types/ultramodern-workspace/add-vertical.d.ts +5 -2
- package/dist/types/ultramodern-workspace/bridge-config.d.ts +64 -0
- package/dist/types/ultramodern-workspace/codesmith.d.ts +1 -0
- package/dist/types/ultramodern-workspace/contracts.d.ts +2 -17
- package/dist/types/ultramodern-workspace/descriptors.d.ts +1 -1
- package/dist/types/ultramodern-workspace/fs-io.d.ts +1 -0
- package/dist/types/ultramodern-workspace/index.d.ts +2 -0
- package/dist/types/ultramodern-workspace/mf-validation.d.ts +27 -0
- package/dist/types/ultramodern-workspace/module-federation.d.ts +1 -0
- package/dist/types/ultramodern-workspace/package-json.d.ts +5 -3
- package/dist/types/ultramodern-workspace/public-api.d.ts +2 -0
- package/dist/types/ultramodern-workspace/types.d.ts +2 -0
- package/dist/types/ultramodern-workspace/versions.d.ts +2 -0
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +2 -7
- package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -2
- package/package.json +5 -3
- package/template-workspace/{.agents → .codex}/skills-lock.json +12 -12
- package/template-workspace/.gitignore.handlebars +3 -0
- package/template-workspace/AGENTS.md.handlebars +11 -5
- package/template-workspace/README.md.handlebars +15 -12
- package/template-workspace/oxfmt.config.ts +1 -0
- package/template-workspace/oxlint.config.ts +1 -0
- package/template-workspace/patches/@tanstack__router-core@1.171.13.patch +51 -0
- package/template-workspace/pnpm-workspace.yaml.handlebars +12 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +96 -69
- package/templates/app/shell-frame.tsx +1 -2
- package/templates/app/ultramodern-route-head.tsx.handlebars +5 -6
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +59 -7
- package/templates/workspace-scripts/check-ultramodern-api-boundaries.mjs +339 -0
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +245 -8
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +276 -8
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +35 -10
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +164 -1
- package/templates/workspace-scripts/ultramodern-typecheck.mjs +4 -2
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +949 -63
- /package/template-workspace/{.agents → .codex}/rstackjs-agent-skills-LICENSE +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsbuild-best-practices/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/command-map.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/common-analysis-patterns.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-common.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-rspack.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-webpack.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/rsdoctor-data-types.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rslib-best-practices/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rslib-modern-package/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-best-practices/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/references/bottlenecks.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/references/tracing-guide.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/scripts/analyze_trace.js +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rstest-best-practices/SKILL.md +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
|
|
@@ -14,6 +15,21 @@ const expectedCloudflareBuildScript = {{expectedCloudflareBuildScriptJson}};
|
|
|
14
15
|
const expectedCloudflareDeployScript = {{expectedCloudflareDeployScriptJson}};
|
|
15
16
|
const expectedCloudflareSecurity = {{expectedCloudflareSecurityJson}};
|
|
16
17
|
const publicSurfaceManagedSourceAssetPaths = {{publicSurfaceManagedSourceAssetPathsJson}};
|
|
18
|
+
const compactConfigPath = '.modernjs/ultramodern.json';
|
|
19
|
+
const retiredMetadataPaths = [
|
|
20
|
+
'.modernjs/ultramodern-generated-contract.json',
|
|
21
|
+
'.modernjs/ultramodern-package-source.json',
|
|
22
|
+
'.modernjs/ultramodern-workspace-template-manifest.json',
|
|
23
|
+
];
|
|
24
|
+
const modernPackageCohort = [
|
|
25
|
+
'@modern-js/create',
|
|
26
|
+
'@modern-js/code-tools',
|
|
27
|
+
'@modern-js/app-tools',
|
|
28
|
+
'@modern-js/plugin-bff',
|
|
29
|
+
'@modern-js/plugin-i18n',
|
|
30
|
+
'@modern-js/plugin-tanstack',
|
|
31
|
+
'@modern-js/runtime',
|
|
32
|
+
];
|
|
17
33
|
const expectedModernPackageSpecifier = packageName => {
|
|
18
34
|
if (packageSource.strategy === 'workspace') {
|
|
19
35
|
return 'workspace:*';
|
|
@@ -70,6 +86,688 @@ const assertArray = (value, contract, fixArea) => {
|
|
|
70
86
|
};
|
|
71
87
|
const findById = (entries, id) =>
|
|
72
88
|
Array.isArray(entries) ? entries.find(entry => entry?.id === id) : undefined;
|
|
89
|
+
const generatedContractLabel = compactConfigPath;
|
|
90
|
+
const toKebabCase = value =>
|
|
91
|
+
String(value)
|
|
92
|
+
.trim()
|
|
93
|
+
.replace(/([a-z0-9])([A-Z])/gu, '$1-$2')
|
|
94
|
+
.replace(/[^a-zA-Z0-9._-]+/gu, '-')
|
|
95
|
+
.replace(/[._]+/gu, '-')
|
|
96
|
+
.toLowerCase()
|
|
97
|
+
.replace(/-+/gu, '-')
|
|
98
|
+
.replace(/^-+|-+$/gu, '');
|
|
99
|
+
const toPascalCase = value =>
|
|
100
|
+
toKebabCase(value)
|
|
101
|
+
.split('-')
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
104
|
+
.join('');
|
|
105
|
+
const toCamelCase = value => {
|
|
106
|
+
const pascal = toPascalCase(value);
|
|
107
|
+
return `${pascal.charAt(0).toLowerCase()}${pascal.slice(1)}`;
|
|
108
|
+
};
|
|
109
|
+
const toEnvSegment = value => toKebabCase(value).replace(/-/gu, '_').toUpperCase();
|
|
110
|
+
const packageNameFor = (scope, suffix) => `@${scope}/${suffix}`;
|
|
111
|
+
const normalizeRelativePath = value =>
|
|
112
|
+
String(value ?? '').replace(/\\/gu, '/').replace(/^\.\/+/u, '');
|
|
113
|
+
const appNamespace = app => (app.kind === 'shell' ? 'shell' : (app.domain ?? app.id));
|
|
114
|
+
const tailwindPrefixDigitWords = [
|
|
115
|
+
'zero',
|
|
116
|
+
'one',
|
|
117
|
+
'two',
|
|
118
|
+
'three',
|
|
119
|
+
'four',
|
|
120
|
+
'five',
|
|
121
|
+
'six',
|
|
122
|
+
'seven',
|
|
123
|
+
'eight',
|
|
124
|
+
'nine',
|
|
125
|
+
];
|
|
126
|
+
const tailwindPrefixFor = app =>
|
|
127
|
+
app.kind === 'shell'
|
|
128
|
+
? 'shell'
|
|
129
|
+
: String(app.domain ?? app.id)
|
|
130
|
+
.toLowerCase()
|
|
131
|
+
.replace(/[^a-z0-9]/gu, '')
|
|
132
|
+
.replace(/[0-9]/gu, digit => tailwindPrefixDigitWords[Number(digit)]);
|
|
133
|
+
const buildMarkerFor = app =>
|
|
134
|
+
crypto
|
|
135
|
+
.createHash('sha256')
|
|
136
|
+
.update(`${packageScope}:${app.packageSuffix}:${app.id}:0.1.0`)
|
|
137
|
+
.digest('hex')
|
|
138
|
+
.slice(0, 16);
|
|
139
|
+
const normalizeCompactApp = rawApp => {
|
|
140
|
+
const id = String(rawApp.id);
|
|
141
|
+
const kind = rawApp.kind === 'vertical' ? 'vertical' : 'shell';
|
|
142
|
+
const appPath =
|
|
143
|
+
typeof rawApp.path === 'string'
|
|
144
|
+
? normalizeRelativePath(rawApp.path)
|
|
145
|
+
: kind === 'shell'
|
|
146
|
+
? 'apps/shell-super-app'
|
|
147
|
+
: `verticals/${toKebabCase(id)}`;
|
|
148
|
+
const packageSuffix =
|
|
149
|
+
typeof rawApp.packageSuffix === 'string'
|
|
150
|
+
? rawApp.packageSuffix
|
|
151
|
+
: appPath.split('/').at(-1) ?? id;
|
|
152
|
+
const domain =
|
|
153
|
+
typeof rawApp.domain === 'string'
|
|
154
|
+
? rawApp.domain
|
|
155
|
+
: kind === 'vertical'
|
|
156
|
+
? packageSuffix
|
|
157
|
+
: undefined;
|
|
158
|
+
const moduleFederation =
|
|
159
|
+
rawApp.moduleFederation && typeof rawApp.moduleFederation === 'object'
|
|
160
|
+
? rawApp.moduleFederation
|
|
161
|
+
: {};
|
|
162
|
+
const effectApi =
|
|
163
|
+
rawApp.effectApi && typeof rawApp.effectApi === 'object'
|
|
164
|
+
? {
|
|
165
|
+
stem:
|
|
166
|
+
typeof rawApp.effectApi.stem === 'string'
|
|
167
|
+
? rawApp.effectApi.stem
|
|
168
|
+
: domain ?? id,
|
|
169
|
+
prefix:
|
|
170
|
+
typeof rawApp.effectApi.prefix === 'string'
|
|
171
|
+
? rawApp.effectApi.prefix
|
|
172
|
+
: `/${domain ?? id}-api`,
|
|
173
|
+
consumedBy: Array.isArray(rawApp.effectApi.consumedBy)
|
|
174
|
+
? rawApp.effectApi.consumedBy.filter(
|
|
175
|
+
consumer => typeof consumer === 'string',
|
|
176
|
+
)
|
|
177
|
+
: ['shell-super-app', id],
|
|
178
|
+
}
|
|
179
|
+
: undefined;
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
id,
|
|
183
|
+
kind,
|
|
184
|
+
path: appPath,
|
|
185
|
+
package: typeof rawApp.package === 'string' ? rawApp.package : undefined,
|
|
186
|
+
packageSuffix,
|
|
187
|
+
domain,
|
|
188
|
+
port:
|
|
189
|
+
typeof rawApp.port === 'number'
|
|
190
|
+
? rawApp.port
|
|
191
|
+
: kind === 'shell'
|
|
192
|
+
? 3020
|
|
193
|
+
: 3030,
|
|
194
|
+
portEnv:
|
|
195
|
+
typeof rawApp.portEnv === 'string'
|
|
196
|
+
? rawApp.portEnv
|
|
197
|
+
: kind === 'shell'
|
|
198
|
+
? 'SHELL_SUPER_APP_PORT'
|
|
199
|
+
: `VERTICAL_${toEnvSegment(domain ?? id)}_PORT`,
|
|
200
|
+
mfName:
|
|
201
|
+
typeof moduleFederation.name === 'string'
|
|
202
|
+
? moduleFederation.name
|
|
203
|
+
: kind === 'shell'
|
|
204
|
+
? 'shellSuperApp'
|
|
205
|
+
: `vertical${toPascalCase(domain ?? id)}`,
|
|
206
|
+
exposes: Array.isArray(moduleFederation.exposes)
|
|
207
|
+
? moduleFederation.exposes.filter(expose => typeof expose === 'string')
|
|
208
|
+
: [],
|
|
209
|
+
moduleFederationSsr: moduleFederation.ssr !== false,
|
|
210
|
+
verticalRefs: Array.isArray(moduleFederation.verticalRefs)
|
|
211
|
+
? moduleFederation.verticalRefs.filter(ref => typeof ref === 'string')
|
|
212
|
+
: [],
|
|
213
|
+
effectApi,
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
const compactAppsFromConfig = config =>
|
|
217
|
+
Array.isArray(config.topology?.apps)
|
|
218
|
+
? config.topology.apps.map(normalizeCompactApp)
|
|
219
|
+
: [];
|
|
220
|
+
const remoteDependencyAliasFor = app => toCamelCase(app.domain ?? app.id.replace(/^remote-/u, ''));
|
|
221
|
+
const remoteContractsFor = (app, apps) =>
|
|
222
|
+
(app.verticalRefs ?? [])
|
|
223
|
+
.map(ref => apps.find(candidate => candidate.id === ref))
|
|
224
|
+
.filter(Boolean)
|
|
225
|
+
.map(remote => ({
|
|
226
|
+
id: remote.id,
|
|
227
|
+
alias: remoteDependencyAliasFor(remote),
|
|
228
|
+
name: remote.mfName,
|
|
229
|
+
manifestEnv: `VERTICAL_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`,
|
|
230
|
+
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`,
|
|
231
|
+
}));
|
|
232
|
+
const createRouteOwnedEntries = app => {
|
|
233
|
+
const namespace = appNamespace(app);
|
|
234
|
+
const base = {
|
|
235
|
+
descriptionKey: `${namespace}.seo.description`,
|
|
236
|
+
mfBoundaryId: app.mfName,
|
|
237
|
+
namespace,
|
|
238
|
+
ownerAppId: app.id,
|
|
239
|
+
public: false,
|
|
240
|
+
indexable: false,
|
|
241
|
+
publicSurface: 'private-app-screen',
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
return [
|
|
245
|
+
{
|
|
246
|
+
...base,
|
|
247
|
+
canonicalPath: '/',
|
|
248
|
+
id: app.kind === 'shell' ? 'shell-home' : `${app.id}-home`,
|
|
249
|
+
localisedPaths: {
|
|
250
|
+
cs: '/',
|
|
251
|
+
en: '/',
|
|
252
|
+
},
|
|
253
|
+
titleKey: app.kind === 'shell' ? 'shell.title' : `${namespace}.title`,
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
};
|
|
257
|
+
const createPublicRoutes = app =>
|
|
258
|
+
createRouteOwnedEntries(app)
|
|
259
|
+
.filter(route => route.public && route.indexable)
|
|
260
|
+
.map(route => ({
|
|
261
|
+
canonicalPath: route.canonicalPath,
|
|
262
|
+
id: route.id,
|
|
263
|
+
localisedPaths: route.localisedPaths,
|
|
264
|
+
namespace: route.namespace,
|
|
265
|
+
ownerAppId: route.ownerAppId,
|
|
266
|
+
descriptionKey: route.descriptionKey,
|
|
267
|
+
titleKey: route.titleKey,
|
|
268
|
+
}));
|
|
269
|
+
const createLocalisedUrls = app =>
|
|
270
|
+
Object.fromEntries(
|
|
271
|
+
createRouteOwnedEntries(app).flatMap(route => {
|
|
272
|
+
if (route.canonicalPath === '/') {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
return Array.from(
|
|
276
|
+
new Set([route.canonicalPath, ...Object.values(route.localisedPaths)]),
|
|
277
|
+
).map(pathname => [pathname, route.localisedPaths]);
|
|
278
|
+
}),
|
|
279
|
+
);
|
|
280
|
+
const createPublicSurface = app => {
|
|
281
|
+
const publicRoutes = createPublicRoutes(app);
|
|
282
|
+
return {
|
|
283
|
+
authoring: 'colocated-route-meta',
|
|
284
|
+
artifactLifecycle: 'build-and-deploy-output',
|
|
285
|
+
generatedManifest: './src/routes/ultramodern-route-metadata',
|
|
286
|
+
source: 'route-owned-public-routes',
|
|
287
|
+
metadataExport: './src/routes/ultramodern-route-metadata',
|
|
288
|
+
generator: 'scripts/generate-public-surface-assets.mjs',
|
|
289
|
+
outputRoot: 'dist/public',
|
|
290
|
+
cloudflareOutputRoot: '.output/public',
|
|
291
|
+
privateRoutePolicy: 'omit-from-generated-public-surface',
|
|
292
|
+
files:
|
|
293
|
+
publicRoutes.length > 0
|
|
294
|
+
? ['robots.txt', 'sitemap.xml', 'site.webmanifest']
|
|
295
|
+
: ['robots.txt'],
|
|
296
|
+
omittedByDefault: ['api-catalog.json', 'llms.txt', 'security.txt'],
|
|
297
|
+
languages: ['en', 'cs'],
|
|
298
|
+
contentExpansion: {
|
|
299
|
+
authoring: 'route-owned-esm-provider',
|
|
300
|
+
defaultProviderFile: 'route.sitemap.mjs',
|
|
301
|
+
entryExport: 'default-or-entries',
|
|
302
|
+
paramsSource: 'params-or-localeParams',
|
|
303
|
+
draftPolicy: 'omit-draft-by-default',
|
|
304
|
+
indexablePolicy: 'omit-indexable-false',
|
|
305
|
+
lifecycle: 'executed-during-public-surface-generation',
|
|
306
|
+
},
|
|
307
|
+
contentSources: [],
|
|
308
|
+
publicRoutes,
|
|
309
|
+
routeEntries: [],
|
|
310
|
+
concreteUrlPaths: [],
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
const createPublicHead = () => ({
|
|
314
|
+
authoring: 'colocated-route-meta',
|
|
315
|
+
generator: './src/routes/ultramodern-route-head',
|
|
316
|
+
renderer: '@modern-js/runtime/head Helmet',
|
|
317
|
+
ssr: true,
|
|
318
|
+
title: {
|
|
319
|
+
required: true,
|
|
320
|
+
source: 'route.titleKey',
|
|
321
|
+
},
|
|
322
|
+
description: {
|
|
323
|
+
required: true,
|
|
324
|
+
source: 'route.descriptionKey',
|
|
325
|
+
},
|
|
326
|
+
canonical: {
|
|
327
|
+
publicIndexableOnly: true,
|
|
328
|
+
source: 'localized canonical route URL',
|
|
329
|
+
},
|
|
330
|
+
alternates: {
|
|
331
|
+
hreflang: ['en', 'cs'],
|
|
332
|
+
xDefault: 'en',
|
|
333
|
+
},
|
|
334
|
+
openGraph: {
|
|
335
|
+
publicIndexableOnly: true,
|
|
336
|
+
required: ['og:title', 'og:description', 'og:url', 'og:type'],
|
|
337
|
+
},
|
|
338
|
+
twitter: {
|
|
339
|
+
publicIndexableOnly: true,
|
|
340
|
+
required: ['twitter:card', 'twitter:title', 'twitter:description'],
|
|
341
|
+
},
|
|
342
|
+
structuredData: {
|
|
343
|
+
publicIndexableOnly: true,
|
|
344
|
+
optional: true,
|
|
345
|
+
source: 'route.jsonLd',
|
|
346
|
+
inference: false,
|
|
347
|
+
helperModule: './src/routes/ultramodern-jsonld',
|
|
348
|
+
helperTypes: [
|
|
349
|
+
'WebPage',
|
|
350
|
+
'WebApplication',
|
|
351
|
+
'SoftwareApplication',
|
|
352
|
+
'BreadcrumbList',
|
|
353
|
+
'FAQPage',
|
|
354
|
+
'Organization',
|
|
355
|
+
],
|
|
356
|
+
sanitizesHtmlOpenBracket: true,
|
|
357
|
+
},
|
|
358
|
+
privateRouteRobots: 'noindex, nofollow',
|
|
359
|
+
});
|
|
360
|
+
const createQualityGates = () => ({
|
|
361
|
+
publicRoutes: {
|
|
362
|
+
requireSitemapWhenPresent: true,
|
|
363
|
+
requireRobotsSitemapConsistency: true,
|
|
364
|
+
requireWebManifestWhenPresent: true,
|
|
365
|
+
},
|
|
366
|
+
statusCodes: {
|
|
367
|
+
notFoundRoute: '/__ultramodern-smoke-missing/nope',
|
|
368
|
+
unknownRouteStatus: 404,
|
|
369
|
+
},
|
|
370
|
+
indexing: {
|
|
371
|
+
previewNoindex: true,
|
|
372
|
+
productionPublicRoutesIndexable: true,
|
|
373
|
+
},
|
|
374
|
+
assets: {
|
|
375
|
+
cssPreloadRequired: true,
|
|
376
|
+
cssResponseRequired: true,
|
|
377
|
+
cacheControlRequiredForCss: true,
|
|
378
|
+
sourcemapsPubliclyReferenced: false,
|
|
379
|
+
},
|
|
380
|
+
budgets: {
|
|
381
|
+
ssrHtmlMaxBytes: 250_000,
|
|
382
|
+
mfManifestMaxBytes: 500_000,
|
|
383
|
+
localeJsonMaxBytes: 100_000,
|
|
384
|
+
sitemapXmlMaxBytes: 500_000,
|
|
385
|
+
cssAssetMaxBytes: 750_000,
|
|
386
|
+
},
|
|
387
|
+
csp: {
|
|
388
|
+
finalMode: 'report-only-dogfood',
|
|
389
|
+
decision:
|
|
390
|
+
'Report-only remains the generated final mode until public smoke proof records MF SSR script/style/connect compatibility for the deployed surface.',
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
const createCloudflareRoutes = app => ({
|
|
394
|
+
ssr: '/en',
|
|
395
|
+
mfManifest: '/mf-manifest.json',
|
|
396
|
+
locale: `/locales/en/${appNamespace(app)}.json`,
|
|
397
|
+
...(app.effectApi
|
|
398
|
+
? {
|
|
399
|
+
apiReadiness: `${app.effectApi.prefix}/${app.effectApi.stem}/readiness`,
|
|
400
|
+
}
|
|
401
|
+
: {}),
|
|
402
|
+
});
|
|
403
|
+
const createCloudflareDeploy = app => ({
|
|
404
|
+
target: 'cloudflare',
|
|
405
|
+
workerName: expectedWorkerName(app.packageSuffix),
|
|
406
|
+
publicUrlEnv: `ULTRAMODERN_PUBLIC_URL_${toEnvSegment(app.id)}`,
|
|
407
|
+
compatibilityDate: '{{cloudflareCompatibilityDate}}',
|
|
408
|
+
compatibilityFlags: ['nodejs_compat', 'global_fetch_strictly_public'],
|
|
409
|
+
assetsBinding: 'ASSETS',
|
|
410
|
+
routes: createCloudflareRoutes(app),
|
|
411
|
+
security: expectedCloudflareSecurity,
|
|
412
|
+
qualityGates: createQualityGates(),
|
|
413
|
+
evidence: {
|
|
414
|
+
proofScript: 'scripts/proof-cloudflare-version.mjs',
|
|
415
|
+
reportDefault:
|
|
416
|
+
'.codex/reports/cloudflare-version-proof/public-url-proof.json',
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
const createEffectReadiness = app => ({
|
|
420
|
+
endpoint: `/${app.effectApi.stem}/readiness`,
|
|
421
|
+
marker: {
|
|
422
|
+
ui: 'ultramodernUiMarker',
|
|
423
|
+
api: 'ultramodernApiMarker',
|
|
424
|
+
skew: 'none',
|
|
425
|
+
},
|
|
426
|
+
checks: ['moduleFederation', 'ssr', 'translations', 'api'],
|
|
427
|
+
});
|
|
428
|
+
const createEffectRequestContext = () => ({
|
|
429
|
+
propagatedHeaders: [
|
|
430
|
+
'accept-language',
|
|
431
|
+
'authorization',
|
|
432
|
+
'traceparent',
|
|
433
|
+
'x-correlation-id',
|
|
434
|
+
'x-tenant-id',
|
|
435
|
+
'x-ultramodern-env',
|
|
436
|
+
'x-vertical-version-id',
|
|
437
|
+
],
|
|
438
|
+
source: 'shell-to-vertical-api-client',
|
|
439
|
+
});
|
|
440
|
+
const createEffectDomainOperations = app => {
|
|
441
|
+
const stem = app.effectApi.stem;
|
|
442
|
+
const group = toCamelCase(stem);
|
|
443
|
+
const basePath = `/${stem}`;
|
|
444
|
+
return {
|
|
445
|
+
workspaceFeed: {
|
|
446
|
+
client: `list${toPascalCase(stem)}`,
|
|
447
|
+
method: 'GET',
|
|
448
|
+
path: basePath,
|
|
449
|
+
resource: 'workspace-items',
|
|
450
|
+
owner: app.id,
|
|
451
|
+
},
|
|
452
|
+
workspaceDetail: {
|
|
453
|
+
client: `get${toPascalCase(stem)}`,
|
|
454
|
+
method: 'GET',
|
|
455
|
+
path: `${basePath}/:id`,
|
|
456
|
+
resource: 'workspace-item',
|
|
457
|
+
owner: app.id,
|
|
458
|
+
},
|
|
459
|
+
workspaceCreate: {
|
|
460
|
+
client: `create${toPascalCase(stem)}`,
|
|
461
|
+
method: 'POST',
|
|
462
|
+
path: basePath,
|
|
463
|
+
resource: group,
|
|
464
|
+
owner: app.id,
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
};
|
|
468
|
+
const createEffectOperationContract = app => ({
|
|
469
|
+
group: toCamelCase(app.effectApi.stem),
|
|
470
|
+
operations: {
|
|
471
|
+
readiness: {
|
|
472
|
+
method: 'GET',
|
|
473
|
+
path: `/${app.effectApi.stem}/readiness`,
|
|
474
|
+
source: 'generated-client',
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
const createAppConfigContract = app => ({
|
|
479
|
+
preset: 'presetUltramodern',
|
|
480
|
+
plugins: [
|
|
481
|
+
'appTools',
|
|
482
|
+
'tanstackRouterPlugin',
|
|
483
|
+
'i18nPlugin',
|
|
484
|
+
...(app.effectApi ? ['bffPlugin'] : []),
|
|
485
|
+
'moduleFederationPlugin',
|
|
486
|
+
'zephyrRspackPlugin',
|
|
487
|
+
],
|
|
488
|
+
dev: {
|
|
489
|
+
assetPrefix: '/',
|
|
490
|
+
},
|
|
491
|
+
output: {
|
|
492
|
+
assetPrefix: {
|
|
493
|
+
envFallbackOrder: ['MODERN_ASSET_PREFIX', 'ULTRAMODERN_ASSET_PREFIX'],
|
|
494
|
+
default: app.kind === 'shell' ? '/' : 'app-public-origin',
|
|
495
|
+
},
|
|
496
|
+
disableTsChecker: false,
|
|
497
|
+
},
|
|
498
|
+
performance: {
|
|
499
|
+
readinessDiagnostics: {
|
|
500
|
+
default: 'enabled',
|
|
501
|
+
optOut: {
|
|
502
|
+
env: 'ULTRAMODERN_PERFORMANCE_READINESS_DIAGNOSTICS=false',
|
|
503
|
+
config: 'scripts/ultramodern-performance-readiness.config.mjs',
|
|
504
|
+
},
|
|
505
|
+
failOn: 'framework-invariant',
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
rspack: {
|
|
509
|
+
output: {
|
|
510
|
+
uniqueName: app.mfName,
|
|
511
|
+
chunkLoadingGlobal: expectedChunkLoadingGlobal(app.mfName),
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
source: {
|
|
515
|
+
siteUrl: {
|
|
516
|
+
envFallbackOrder: [
|
|
517
|
+
'MODERN_PUBLIC_SITE_URL',
|
|
518
|
+
`ULTRAMODERN_PUBLIC_URL_${toEnvSegment(app.id)}`,
|
|
519
|
+
'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
|
|
520
|
+
app.portEnv,
|
|
521
|
+
],
|
|
522
|
+
defaultLocalhostPort: app.port,
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
const cssDedupe = () => ({
|
|
527
|
+
strategy: 'shared-token-package-plus-css-content-hash',
|
|
528
|
+
sharedPackage: packageNameFor(packageScope, 'shared-design-tokens'),
|
|
529
|
+
sharedLayers: ['ultramodern-shared-tokens'],
|
|
530
|
+
runtimeLoad: 'once-per-content-hash',
|
|
531
|
+
duplicateBaseStylesAllowed: false,
|
|
532
|
+
});
|
|
533
|
+
const createStylingContract = app => {
|
|
534
|
+
const sharedTokenPackage = packageNameFor(packageScope, 'shared-design-tokens');
|
|
535
|
+
const ownedLayers =
|
|
536
|
+
app.kind === 'shell'
|
|
537
|
+
? ['ultramodern-shell-base', 'ultramodern-shell-overlay']
|
|
538
|
+
: [`ultramodern-vertical-${app.domain ?? app.id}`];
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
tailwind: tailwindEnabled,
|
|
542
|
+
federation: {
|
|
543
|
+
owner: {
|
|
544
|
+
id: app.id,
|
|
545
|
+
package: app.package ?? packageNameFor(packageScope, app.packageSuffix),
|
|
546
|
+
},
|
|
547
|
+
role: app.kind === 'shell' ? 'shell-base-overlay' : 'vertical-css',
|
|
548
|
+
rootSelector: `[data-app-id="${app.id}"]`,
|
|
549
|
+
classPrefix: `${tailwindPrefixFor(app)}:`,
|
|
550
|
+
layers: {
|
|
551
|
+
shared: ['ultramodern-shared-tokens'],
|
|
552
|
+
owned: ownedLayers,
|
|
553
|
+
},
|
|
554
|
+
entrypoints: {
|
|
555
|
+
layoutImport: 'src/routes/layout.tsx',
|
|
556
|
+
css: ['src/routes/index.css'],
|
|
557
|
+
...(app.kind === 'shell'
|
|
558
|
+
? {}
|
|
559
|
+
: { federationEntry: 'src/federation-entry.tsx' }),
|
|
560
|
+
},
|
|
561
|
+
assets: {
|
|
562
|
+
shared: [`${sharedTokenPackage}/tokens.css`],
|
|
563
|
+
owned: ['src/routes/index.css'],
|
|
564
|
+
},
|
|
565
|
+
dedupe: cssDedupe(),
|
|
566
|
+
ssr: {
|
|
567
|
+
cloudflare: true,
|
|
568
|
+
firstPaintRequired: true,
|
|
569
|
+
verticalCss:
|
|
570
|
+
app.kind === 'shell'
|
|
571
|
+
? 'host-preloads-shell-and-shared-css'
|
|
572
|
+
: 'federated-manifest-owned-css',
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
};
|
|
576
|
+
};
|
|
577
|
+
const createAppContract = (app, apps) => ({
|
|
578
|
+
id: app.id,
|
|
579
|
+
package: app.package ?? packageNameFor(packageScope, app.packageSuffix),
|
|
580
|
+
path: app.path,
|
|
581
|
+
kind: app.kind,
|
|
582
|
+
config: createAppConfigContract(app),
|
|
583
|
+
styling: createStylingContract(app),
|
|
584
|
+
deploy: {
|
|
585
|
+
target: 'cloudflare',
|
|
586
|
+
cloudflare: createCloudflareDeploy(app),
|
|
587
|
+
worker: {
|
|
588
|
+
compatibilityDate: '{{cloudflareCompatibilityDate}}',
|
|
589
|
+
name: expectedWorkerName(app.packageSuffix),
|
|
590
|
+
security: expectedCloudflareSecurity,
|
|
591
|
+
ssr: true,
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
ssr: app.moduleFederationSsr
|
|
595
|
+
? {
|
|
596
|
+
mode: 'string',
|
|
597
|
+
moduleFederationAppSSR: true,
|
|
598
|
+
}
|
|
599
|
+
: undefined,
|
|
600
|
+
i18n: {
|
|
601
|
+
languages: ['en', 'cs'],
|
|
602
|
+
namespace: appNamespace(app),
|
|
603
|
+
localisedUrls: createLocalisedUrls(app),
|
|
604
|
+
},
|
|
605
|
+
routes: {
|
|
606
|
+
source: 'route-owned',
|
|
607
|
+
metadataAuthoring: 'colocated-route-meta',
|
|
608
|
+
generatedManifest: true,
|
|
609
|
+
metadataExport: './src/routes/ultramodern-route-metadata',
|
|
610
|
+
localisedUrls: createLocalisedUrls(app),
|
|
611
|
+
owned: createRouteOwnedEntries(app),
|
|
612
|
+
publicRoutes: createPublicRoutes(app),
|
|
613
|
+
privateByDefault: true,
|
|
614
|
+
publicnessDefault: 'private-app-screen',
|
|
615
|
+
publicHead: createPublicHead(),
|
|
616
|
+
publicSurface: createPublicSurface(app),
|
|
617
|
+
},
|
|
618
|
+
moduleFederation: {
|
|
619
|
+
name: app.mfName,
|
|
620
|
+
...(app.verticalRefs?.length
|
|
621
|
+
? {
|
|
622
|
+
verticalRefs: app.verticalRefs,
|
|
623
|
+
remotes: remoteContractsFor(app, apps),
|
|
624
|
+
}
|
|
625
|
+
: {}),
|
|
626
|
+
exposes: app.exposes,
|
|
627
|
+
dts: {
|
|
628
|
+
compilerInstance: 'tsgo',
|
|
629
|
+
displayErrorInTerminal: true,
|
|
630
|
+
tsConfigPath: './tsconfig.mf-types.json',
|
|
631
|
+
},
|
|
632
|
+
browserSafeExposesOnly: true,
|
|
633
|
+
},
|
|
634
|
+
marker: {
|
|
635
|
+
appId: app.id,
|
|
636
|
+
packageName: app.package ?? packageNameFor(packageScope, app.packageSuffix),
|
|
637
|
+
version: '0.1.0',
|
|
638
|
+
build: buildMarkerFor(app),
|
|
639
|
+
deployProfile: 'cloudflare-ssr-mf-effect-v1',
|
|
640
|
+
uiSurface: 'ui',
|
|
641
|
+
...(app.effectApi ? { apiSurface: 'api' } : {}),
|
|
642
|
+
},
|
|
643
|
+
...(app.effectApi
|
|
644
|
+
? {
|
|
645
|
+
api: {
|
|
646
|
+
runtime: 'effect',
|
|
647
|
+
import: '@modern-js/plugin-bff/effect-edge',
|
|
648
|
+
prefix: app.effectApi.prefix,
|
|
649
|
+
openapi: '/openapi.json',
|
|
650
|
+
strictEffectApproach: true,
|
|
651
|
+
workerEntry: 'worker/__modern_bff_effect.js',
|
|
652
|
+
contract: './api',
|
|
653
|
+
client: './api/client',
|
|
654
|
+
readiness: createEffectReadiness(app),
|
|
655
|
+
requestContext: createEffectRequestContext(),
|
|
656
|
+
domainOperations: createEffectDomainOperations(app),
|
|
657
|
+
...createEffectOperationContract(app),
|
|
658
|
+
},
|
|
659
|
+
}
|
|
660
|
+
: {}),
|
|
661
|
+
});
|
|
662
|
+
const createCssFederationContract = () => ({
|
|
663
|
+
sharedDesignTokens: {
|
|
664
|
+
owner: {
|
|
665
|
+
id: 'shared-design-tokens',
|
|
666
|
+
package: packageNameFor(packageScope, 'shared-design-tokens'),
|
|
667
|
+
},
|
|
668
|
+
role: 'shared-design-tokens',
|
|
669
|
+
rootSelector: ':root',
|
|
670
|
+
classPrefix: '--um-',
|
|
671
|
+
layers: {
|
|
672
|
+
owned: ['ultramodern-shared-tokens'],
|
|
673
|
+
},
|
|
674
|
+
entrypoints: {
|
|
675
|
+
css: ['packages/shared-design-tokens/src/tokens.css'],
|
|
676
|
+
},
|
|
677
|
+
assets: {
|
|
678
|
+
exports: ['./tokens.css'],
|
|
679
|
+
},
|
|
680
|
+
dedupe: cssDedupe(),
|
|
681
|
+
ssr: {
|
|
682
|
+
firstPaintRequired: true,
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
const createPerformanceReadinessContract = () => ({
|
|
687
|
+
default: 'enabled',
|
|
688
|
+
mode: 'diagnostic',
|
|
689
|
+
scope: 'ultramodern-generated-and-framework-owned',
|
|
690
|
+
report: {
|
|
691
|
+
script: 'scripts/ultramodern-performance-readiness.mjs',
|
|
692
|
+
config: 'scripts/ultramodern-performance-readiness.config.mjs',
|
|
693
|
+
deterministic: true,
|
|
694
|
+
},
|
|
695
|
+
optOut: {
|
|
696
|
+
env: 'ULTRAMODERN_PERFORMANCE_READINESS_DIAGNOSTICS=false',
|
|
697
|
+
},
|
|
698
|
+
signals: [
|
|
699
|
+
'bfcache',
|
|
700
|
+
'core-web-vitals-rum',
|
|
701
|
+
'duplicate-prefetch-warmup',
|
|
702
|
+
'cache-policy-sanity',
|
|
703
|
+
'save-data-behavior',
|
|
704
|
+
'cloudflare-ssr-cache-hints',
|
|
705
|
+
].map(id => ({ id })),
|
|
706
|
+
});
|
|
707
|
+
const createModernPackageAliases = packageSourceConfig => {
|
|
708
|
+
if (typeof packageSourceConfig?.aliasScope !== 'string') {
|
|
709
|
+
return undefined;
|
|
710
|
+
}
|
|
711
|
+
const scope = packageSourceConfig.aliasScope.replace(/^@/u, '');
|
|
712
|
+
const prefix =
|
|
713
|
+
typeof packageSourceConfig.aliasPackageNamePrefix === 'string'
|
|
714
|
+
? packageSourceConfig.aliasPackageNamePrefix
|
|
715
|
+
: '';
|
|
716
|
+
return Object.fromEntries(
|
|
717
|
+
modernPackageCohort.map(packageName => [
|
|
718
|
+
packageName,
|
|
719
|
+
`@${scope}/${prefix}${packageName.split('/').at(-1)}`,
|
|
720
|
+
]),
|
|
721
|
+
);
|
|
722
|
+
};
|
|
723
|
+
const synthesizePackageSourceFromCompact = config => {
|
|
724
|
+
const source = config.packageSource ?? {};
|
|
725
|
+
const strategy = source.strategy === 'install' ? 'install' : 'workspace';
|
|
726
|
+
const specifier =
|
|
727
|
+
strategy === 'install' && typeof source.modernPackageVersion === 'string'
|
|
728
|
+
? source.modernPackageVersion
|
|
729
|
+
: 'workspace:*';
|
|
730
|
+
const aliases = createModernPackageAliases(source);
|
|
731
|
+
return {
|
|
732
|
+
schemaVersion: 1,
|
|
733
|
+
strategy,
|
|
734
|
+
modernPackages: {
|
|
735
|
+
packages: modernPackageCohort,
|
|
736
|
+
specifier,
|
|
737
|
+
...(typeof source.registry === 'string'
|
|
738
|
+
? { registry: source.registry }
|
|
739
|
+
: {}),
|
|
740
|
+
...(aliases ? { aliases } : {}),
|
|
741
|
+
},
|
|
742
|
+
generatedWorkspacePackages: {
|
|
743
|
+
packages: [
|
|
744
|
+
packageNameFor(packageScope, 'shared-contracts'),
|
|
745
|
+
packageNameFor(packageScope, 'shared-design-tokens'),
|
|
746
|
+
],
|
|
747
|
+
specifier: 'workspace:*',
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
};
|
|
751
|
+
const synthesizeGeneratedContractFromCompact = config => {
|
|
752
|
+
const apps = compactAppsFromConfig(config);
|
|
753
|
+
return {
|
|
754
|
+
schemaVersion: 1,
|
|
755
|
+
profile: config.profile ?? 'cloudflare-ssr-mf-effect-v1',
|
|
756
|
+
node: {
|
|
757
|
+
version: config.workspace?.node?.version ?? expectedNodeVersion,
|
|
758
|
+
engineRange: '>=26',
|
|
759
|
+
},
|
|
760
|
+
performanceReadiness: createPerformanceReadinessContract(),
|
|
761
|
+
cssFederation: createCssFederationContract(),
|
|
762
|
+
apps: apps.map(app => createAppContract(app, apps)),
|
|
763
|
+
};
|
|
764
|
+
};
|
|
765
|
+
const readPackageSourceView = config => {
|
|
766
|
+
return synthesizePackageSourceFromCompact(config);
|
|
767
|
+
};
|
|
768
|
+
const readGeneratedContractView = config => {
|
|
769
|
+
return synthesizeGeneratedContractFromCompact(config);
|
|
770
|
+
};
|
|
73
771
|
const expectedManifestUrl = vertical =>
|
|
74
772
|
`http://localhost:${vertical.port}/mf-manifest.json`;
|
|
75
773
|
const expectedApiUrl = vertical =>
|
|
@@ -92,11 +790,12 @@ const expectedRemoteSubsetsForRefs = refs =>
|
|
|
92
790
|
const requiredMicroVerticalPaths = vertical => [
|
|
93
791
|
`${vertical.path}/package.json`,
|
|
94
792
|
`${vertical.path}/tsconfig.json`,
|
|
793
|
+
`${vertical.path}/tsconfig.mf-types.json`,
|
|
95
794
|
`${vertical.path}/modern.config.ts`,
|
|
96
795
|
`${vertical.path}/module-federation.config.ts`,
|
|
97
|
-
`${vertical.path}/api/
|
|
98
|
-
`${vertical.path}/shared/
|
|
99
|
-
`${vertical.path}/src/
|
|
796
|
+
`${vertical.path}/api/index.ts`,
|
|
797
|
+
`${vertical.path}/shared/api.ts`,
|
|
798
|
+
`${vertical.path}/src/api/${vertical.stem}-client.ts`,
|
|
100
799
|
`${vertical.path}/src/modern-app-env.d.ts`,
|
|
101
800
|
`${vertical.path}/src/modern.runtime.ts`,
|
|
102
801
|
`${vertical.path}/src/federation-entry.tsx`,
|
|
@@ -172,11 +871,11 @@ const assertMicroVerticalContractGraph = ({
|
|
|
172
871
|
assertObject(
|
|
173
872
|
overlay.apis,
|
|
174
873
|
'topology/local-overlays/development.json apis',
|
|
175
|
-
'restore generated local
|
|
874
|
+
'restore generated local API overlays',
|
|
176
875
|
);
|
|
177
876
|
assertArray(
|
|
178
877
|
generatedContract.apps,
|
|
179
|
-
|
|
878
|
+
`${generatedContractLabel} apps`,
|
|
180
879
|
'regenerate the generated contract from the workspace topology',
|
|
181
880
|
);
|
|
182
881
|
|
|
@@ -201,26 +900,26 @@ const assertMicroVerticalContractGraph = ({
|
|
|
201
900
|
assertSameJson(
|
|
202
901
|
generatedContract.apps.map(app => app?.id),
|
|
203
902
|
expectedAppIds,
|
|
204
|
-
|
|
903
|
+
`${generatedContractLabel} apps`,
|
|
205
904
|
'regenerate the generated contract after topology changes',
|
|
206
905
|
);
|
|
207
906
|
|
|
208
907
|
const shellContract = findById(generatedContract.apps, 'shell-super-app');
|
|
209
908
|
assertObject(
|
|
210
909
|
shellContract,
|
|
211
|
-
|
|
910
|
+
`${generatedContractLabel} apps.shell-super-app`,
|
|
212
911
|
'regenerate the generated shell contract entry',
|
|
213
912
|
);
|
|
214
913
|
assertSameJson(
|
|
215
914
|
shellContract.moduleFederation?.verticalRefs ?? [],
|
|
216
915
|
expectedVerticalIds,
|
|
217
|
-
|
|
916
|
+
`${generatedContractLabel} shell moduleFederation.verticalRefs`,
|
|
218
917
|
'regenerate the generated shell Module Federation contract',
|
|
219
918
|
);
|
|
220
919
|
assertSameJson(
|
|
221
920
|
(shellContract.moduleFederation?.remotes ?? []).map(remoteContractSubset),
|
|
222
921
|
expectedShellRemotes,
|
|
223
|
-
|
|
922
|
+
`${generatedContractLabel} shell moduleFederation.remotes`,
|
|
224
923
|
'regenerate the generated shell Module Federation contract',
|
|
225
924
|
);
|
|
226
925
|
assertSameJson(
|
|
@@ -229,7 +928,7 @@ const assertMicroVerticalContractGraph = ({
|
|
|
229
928
|
mode: 'string',
|
|
230
929
|
moduleFederationAppSSR: true,
|
|
231
930
|
},
|
|
232
|
-
|
|
931
|
+
`${generatedContractLabel} shell SSR contract`,
|
|
233
932
|
'restore generated string SSR Module Federation settings',
|
|
234
933
|
);
|
|
235
934
|
|
|
@@ -265,9 +964,9 @@ const assertMicroVerticalContractGraph = ({
|
|
|
265
964
|
remoteContractSubset,
|
|
266
965
|
),
|
|
267
966
|
},
|
|
268
|
-
|
|
269
|
-
prefix: topologyEntry.api?.
|
|
270
|
-
serverEntry: topologyEntry.api?.
|
|
967
|
+
api: {
|
|
968
|
+
prefix: topologyEntry.api?.bff?.prefix,
|
|
969
|
+
serverEntry: topologyEntry.api?.serverEntry,
|
|
271
970
|
},
|
|
272
971
|
},
|
|
273
972
|
{
|
|
@@ -281,9 +980,9 @@ const assertMicroVerticalContractGraph = ({
|
|
|
281
980
|
verticalRefs: expectedRefs,
|
|
282
981
|
remotes: expectedRemoteSubsetsForRefs(expectedRefs),
|
|
283
982
|
},
|
|
284
|
-
|
|
983
|
+
api: {
|
|
285
984
|
prefix: vertical.apiPrefix,
|
|
286
|
-
serverEntry: `${vertical.path}/api/
|
|
985
|
+
serverEntry: `${vertical.path}/api/index.ts`,
|
|
287
986
|
},
|
|
288
987
|
},
|
|
289
988
|
`topology/reference-topology.json verticals.${vertical.id}`,
|
|
@@ -324,14 +1023,14 @@ const assertMicroVerticalContractGraph = ({
|
|
|
324
1023
|
overlay.apis[vertical.id],
|
|
325
1024
|
expectedApiUrl(vertical),
|
|
326
1025
|
`topology/local-overlays/development.json apis.${vertical.id}`,
|
|
327
|
-
'restore generated local
|
|
1026
|
+
'restore generated local API overlay',
|
|
328
1027
|
);
|
|
329
1028
|
|
|
330
1029
|
assertSameJson(
|
|
331
1030
|
shellPackage.dependencies?.[vertical.packageName],
|
|
332
1031
|
'workspace:*',
|
|
333
1032
|
`apps/shell-super-app/package.json dependencies.${vertical.packageName}`,
|
|
334
|
-
'restore shell dependency for the MicroVertical
|
|
1033
|
+
'restore shell dependency for the MicroVertical API consumer',
|
|
335
1034
|
);
|
|
336
1035
|
assertSameJson(
|
|
337
1036
|
shellPackage['zephyr:dependencies']?.[vertical.zephyrAlias],
|
|
@@ -342,7 +1041,7 @@ const assertMicroVerticalContractGraph = ({
|
|
|
342
1041
|
|
|
343
1042
|
assertObject(
|
|
344
1043
|
contractEntry,
|
|
345
|
-
|
|
1044
|
+
`${generatedContractLabel} apps.${vertical.id}`,
|
|
346
1045
|
'regenerate the generated MicroVertical contract entry',
|
|
347
1046
|
);
|
|
348
1047
|
assertSameJson(
|
|
@@ -358,10 +1057,10 @@ const assertMicroVerticalContractGraph = ({
|
|
|
358
1057
|
remoteContractSubset,
|
|
359
1058
|
),
|
|
360
1059
|
},
|
|
361
|
-
|
|
362
|
-
prefix: contractEntry.
|
|
363
|
-
contract: contractEntry.
|
|
364
|
-
client: contractEntry.
|
|
1060
|
+
api: {
|
|
1061
|
+
prefix: contractEntry.api?.prefix,
|
|
1062
|
+
contract: contractEntry.api?.contract,
|
|
1063
|
+
client: contractEntry.api?.client,
|
|
365
1064
|
},
|
|
366
1065
|
ssr: contractEntry.ssr,
|
|
367
1066
|
},
|
|
@@ -375,21 +1074,141 @@ const assertMicroVerticalContractGraph = ({
|
|
|
375
1074
|
verticalRefs: expectedRefs,
|
|
376
1075
|
remotes: expectedRemoteSubsetsForRefs(expectedRefs),
|
|
377
1076
|
},
|
|
378
|
-
|
|
1077
|
+
api: {
|
|
379
1078
|
prefix: vertical.apiPrefix,
|
|
380
|
-
contract: './
|
|
381
|
-
client: './
|
|
1079
|
+
contract: './api',
|
|
1080
|
+
client: './api/client',
|
|
382
1081
|
},
|
|
383
1082
|
ssr: {
|
|
384
1083
|
mode: 'string',
|
|
385
1084
|
moduleFederationAppSSR: true,
|
|
386
1085
|
},
|
|
387
1086
|
},
|
|
388
|
-
|
|
1087
|
+
`${generatedContractLabel} apps.${vertical.id}`,
|
|
389
1088
|
'regenerate the generated MicroVertical contract entry',
|
|
390
1089
|
);
|
|
391
1090
|
}
|
|
392
1091
|
};
|
|
1092
|
+
const toPosixPath = value => value.split(path.sep).join('/');
|
|
1093
|
+
const referenceFrom = (fromPath, toPath) => ({
|
|
1094
|
+
path: toPosixPath(path.relative(fromPath, toPath)),
|
|
1095
|
+
});
|
|
1096
|
+
const sharedPackagePaths = [
|
|
1097
|
+
'packages/shared-contracts',
|
|
1098
|
+
'packages/shared-design-tokens',
|
|
1099
|
+
];
|
|
1100
|
+
const tsgoCacheKey = packagePath => packagePath.replace(/[^a-zA-Z0-9._-]+/gu, '__');
|
|
1101
|
+
const assertProjectReferenceEmitConfig = (tsConfig, packagePath) => {
|
|
1102
|
+
const compilerOptions = tsConfig.compilerOptions ?? {};
|
|
1103
|
+
const relativeRoot = toPosixPath(path.relative(packagePath, '.')) || '.';
|
|
1104
|
+
assert(compilerOptions.composite === true, `${packagePath} must stay a composite TS-Go project`);
|
|
1105
|
+
assert(compilerOptions.declaration === true, `${packagePath} must emit declarations for TS-Go build mode`);
|
|
1106
|
+
assert(compilerOptions.declarationMap === false, `${packagePath} must not emit declaration maps during checks`);
|
|
1107
|
+
assert(compilerOptions.emitDeclarationOnly === true, `${packagePath} must only emit declarations during checks`);
|
|
1108
|
+
assert(compilerOptions.noEmit === false, `${packagePath} must override root noEmit for TS-Go build mode`);
|
|
1109
|
+
assert(
|
|
1110
|
+
compilerOptions.outDir === `${relativeRoot}/node_modules/.cache/tsgo/declarations/${tsgoCacheKey(packagePath)}`,
|
|
1111
|
+
`${packagePath} must emit TS-Go declarations into the generated cache`,
|
|
1112
|
+
);
|
|
1113
|
+
assert(
|
|
1114
|
+
compilerOptions.tsBuildInfoFile === `${relativeRoot}/node_modules/.cache/tsgo/${tsgoCacheKey(packagePath)}.tsbuildinfo`,
|
|
1115
|
+
`${packagePath} must keep TS-Go build info in the generated cache`,
|
|
1116
|
+
);
|
|
1117
|
+
};
|
|
1118
|
+
const assertTsConfigReferenceGraph = () => {
|
|
1119
|
+
const rootTsConfig = readJson('tsconfig.json');
|
|
1120
|
+
const shellTsConfig = readJson('apps/shell-super-app/tsconfig.json');
|
|
1121
|
+
const shellMfTypesTsConfig = readJson(
|
|
1122
|
+
'apps/shell-super-app/tsconfig.mf-types.json',
|
|
1123
|
+
);
|
|
1124
|
+
const expectedRootReferences = [
|
|
1125
|
+
...sharedPackagePaths,
|
|
1126
|
+
'apps/shell-super-app',
|
|
1127
|
+
...fullStackVerticals.map(vertical => vertical.path),
|
|
1128
|
+
].map(referencePath => ({ path: referencePath }));
|
|
1129
|
+
const expectedShellReferences = [
|
|
1130
|
+
...sharedPackagePaths,
|
|
1131
|
+
...fullStackVerticals.map(vertical => vertical.path),
|
|
1132
|
+
].map(referencePath =>
|
|
1133
|
+
referenceFrom('apps/shell-super-app', referencePath),
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
assertSameJson(
|
|
1137
|
+
rootTsConfig.files,
|
|
1138
|
+
[],
|
|
1139
|
+
'tsconfig.json files',
|
|
1140
|
+
'restore the generated root project-reference tsconfig',
|
|
1141
|
+
);
|
|
1142
|
+
assertSameJson(
|
|
1143
|
+
rootTsConfig.references ?? [],
|
|
1144
|
+
expectedRootReferences,
|
|
1145
|
+
'tsconfig.json references',
|
|
1146
|
+
'restore the generated root project-reference graph',
|
|
1147
|
+
);
|
|
1148
|
+
assertSameJson(
|
|
1149
|
+
shellTsConfig.references ?? [],
|
|
1150
|
+
expectedShellReferences,
|
|
1151
|
+
'apps/shell-super-app/tsconfig.json references',
|
|
1152
|
+
'restore the generated shell project-reference graph',
|
|
1153
|
+
);
|
|
1154
|
+
assertProjectReferenceEmitConfig(
|
|
1155
|
+
shellTsConfig,
|
|
1156
|
+
'apps/shell-super-app',
|
|
1157
|
+
);
|
|
1158
|
+
assertSameJson(
|
|
1159
|
+
shellMfTypesTsConfig,
|
|
1160
|
+
{
|
|
1161
|
+
extends: './tsconfig.json',
|
|
1162
|
+
include: ['src/modern-app-env.d.ts'],
|
|
1163
|
+
},
|
|
1164
|
+
'apps/shell-super-app/tsconfig.mf-types.json',
|
|
1165
|
+
'restore the generated shell Module Federation DTS boundary',
|
|
1166
|
+
);
|
|
1167
|
+
for (const sharedPackagePath of sharedPackagePaths) {
|
|
1168
|
+
assertProjectReferenceEmitConfig(
|
|
1169
|
+
readJson(`${sharedPackagePath}/tsconfig.json`),
|
|
1170
|
+
sharedPackagePath,
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
for (const vertical of fullStackVerticals) {
|
|
1175
|
+
const verticalTsConfig = readJson(`${vertical.path}/tsconfig.json`);
|
|
1176
|
+
const verticalMfTypesTsConfig = readJson(
|
|
1177
|
+
`${vertical.path}/tsconfig.mf-types.json`,
|
|
1178
|
+
);
|
|
1179
|
+
const expectedVerticalReferences = [
|
|
1180
|
+
...sharedPackagePaths,
|
|
1181
|
+
...(vertical.verticalRefs ?? [])
|
|
1182
|
+
.map(verticalRef =>
|
|
1183
|
+
fullStackVerticals.find(candidate => candidate.id === verticalRef),
|
|
1184
|
+
)
|
|
1185
|
+
.filter(Boolean)
|
|
1186
|
+
.map(referencedVertical => referencedVertical.path),
|
|
1187
|
+
].map(referencePath => referenceFrom(vertical.path, referencePath));
|
|
1188
|
+
assertSameJson(
|
|
1189
|
+
verticalTsConfig.references ?? [],
|
|
1190
|
+
expectedVerticalReferences,
|
|
1191
|
+
`${vertical.path}/tsconfig.json references`,
|
|
1192
|
+
'restore the generated MicroVertical project-reference graph',
|
|
1193
|
+
);
|
|
1194
|
+
assertProjectReferenceEmitConfig(verticalTsConfig, vertical.path);
|
|
1195
|
+
assertSameJson(
|
|
1196
|
+
verticalMfTypesTsConfig,
|
|
1197
|
+
{
|
|
1198
|
+
extends: './tsconfig.json',
|
|
1199
|
+
include: [
|
|
1200
|
+
'src/federation-entry.tsx',
|
|
1201
|
+
...vertical.componentPaths.map(componentPath =>
|
|
1202
|
+
componentPath.replace(`${vertical.path}/`, ''),
|
|
1203
|
+
),
|
|
1204
|
+
'src/modern-app-env.d.ts',
|
|
1205
|
+
],
|
|
1206
|
+
},
|
|
1207
|
+
`${vertical.path}/tsconfig.mf-types.json`,
|
|
1208
|
+
'restore the generated MicroVertical Module Federation DTS boundary',
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
393
1212
|
const packageJsonFiles = startDir => {
|
|
394
1213
|
const files = [];
|
|
395
1214
|
const queue = [startDir];
|
|
@@ -649,17 +1468,15 @@ const requiredPaths = [
|
|
|
649
1468
|
'oxfmt.config.ts',
|
|
650
1469
|
'.github/renovate.json',
|
|
651
1470
|
'.github/workflows/ultramodern-workspace-gates.yml',
|
|
652
|
-
'.agents/skills-lock.json',
|
|
653
1471
|
'.agents/agent-reference-repos.json',
|
|
654
|
-
'.
|
|
1472
|
+
'.codex/skills-lock.json',
|
|
1473
|
+
'.codex/rstackjs-agent-skills-LICENSE',
|
|
655
1474
|
'topology/reference-topology.json',
|
|
656
1475
|
'topology/ownership.json',
|
|
657
1476
|
'topology/local-overlays/development.json',
|
|
658
|
-
'.modernjs/ultramodern-workspace-template-manifest.json',
|
|
659
|
-
'.modernjs/ultramodern-package-source.json',
|
|
660
|
-
'.modernjs/ultramodern-generated-contract.json',
|
|
661
1477
|
'scripts/assert-mf-types.mjs',
|
|
662
1478
|
'scripts/bootstrap-agent-skills.mjs',
|
|
1479
|
+
'scripts/check-ultramodern-api-boundaries.mjs',
|
|
663
1480
|
'scripts/check-ultramodern-i18n-boundaries.mjs',
|
|
664
1481
|
'scripts/generate-public-surface-assets.mjs',
|
|
665
1482
|
'scripts/proof-cloudflare-version.mjs',
|
|
@@ -667,13 +1484,15 @@ const requiredPaths = [
|
|
|
667
1484
|
'scripts/ultramodern-performance-readiness.config.mjs',
|
|
668
1485
|
'scripts/ultramodern-performance-readiness.mjs',
|
|
669
1486
|
'scripts/ultramodern-typecheck.mjs',
|
|
1487
|
+
'scripts/validate-ultramodern-workspace.mjs',
|
|
670
1488
|
'apps/shell-super-app/package.json',
|
|
671
1489
|
'apps/shell-super-app/tsconfig.json',
|
|
1490
|
+
'apps/shell-super-app/tsconfig.mf-types.json',
|
|
672
1491
|
'apps/shell-super-app/modern.config.ts',
|
|
673
1492
|
'apps/shell-super-app/module-federation.config.ts',
|
|
674
1493
|
'apps/shell-super-app/src/modern-app-env.d.ts',
|
|
675
1494
|
'apps/shell-super-app/src/modern.runtime.ts',
|
|
676
|
-
'apps/shell-super-app/src/
|
|
1495
|
+
'apps/shell-super-app/src/api/vertical-clients.ts',
|
|
677
1496
|
'apps/shell-super-app/locales/en/translation.json',
|
|
678
1497
|
`apps/shell-super-app/locales/en/${shellNamespace}.json`,
|
|
679
1498
|
'apps/shell-super-app/locales/cs/translation.json',
|
|
@@ -708,6 +1527,8 @@ if (tailwindEnabled) {
|
|
|
708
1527
|
);
|
|
709
1528
|
}
|
|
710
1529
|
|
|
1530
|
+
requiredPaths.push(compactConfigPath);
|
|
1531
|
+
|
|
711
1532
|
for (const vertical of fullStackVerticals) {
|
|
712
1533
|
requiredMicroVerticalPaths(vertical).forEach(
|
|
713
1534
|
assertRequiredVerticalFile(vertical),
|
|
@@ -719,9 +1540,15 @@ for (const requiredPath of requiredPaths) {
|
|
|
719
1540
|
for (const oldRemotePath of oldRemotePaths) {
|
|
720
1541
|
assertNotExists(oldRemotePath);
|
|
721
1542
|
}
|
|
1543
|
+
for (const retiredMetadataPath of retiredMetadataPaths) {
|
|
1544
|
+
assertNotExists(retiredMetadataPath);
|
|
1545
|
+
}
|
|
722
1546
|
const rootPackage = readJson('package.json');
|
|
723
|
-
const
|
|
724
|
-
const
|
|
1547
|
+
const ultramodernConfig = readJson(compactConfigPath);
|
|
1548
|
+
const bridgeConfig =
|
|
1549
|
+
ultramodernConfig?.bridge?.enabled === true ? ultramodernConfig.bridge : undefined;
|
|
1550
|
+
const packageSource = readPackageSourceView(ultramodernConfig);
|
|
1551
|
+
const generatedContract = readGeneratedContractView(ultramodernConfig);
|
|
725
1552
|
const topology = readJson('topology/reference-topology.json');
|
|
726
1553
|
const ownership = readJson('topology/ownership.json');
|
|
727
1554
|
const overlay = readJson('topology/local-overlays/development.json');
|
|
@@ -734,6 +1561,7 @@ assertMicroVerticalContractGraph({
|
|
|
734
1561
|
overlay,
|
|
735
1562
|
shellPackage,
|
|
736
1563
|
});
|
|
1564
|
+
assertTsConfigReferenceGraph();
|
|
737
1565
|
|
|
738
1566
|
assert(rootPackage.private === true, 'Root package must be private');
|
|
739
1567
|
assert(typeof rootPackage.packageManager === 'string', 'Root must declare packageManager');
|
|
@@ -759,7 +1587,10 @@ assert(
|
|
|
759
1587
|
);
|
|
760
1588
|
assert(!workflowText.includes('FORCE_JAVASCRIPT_ACTIONS_TO_NODE24'), 'CI workflow must not carry the legacy Node 24 override');
|
|
761
1589
|
assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare presetUltramodern');
|
|
762
|
-
assert(
|
|
1590
|
+
assert(
|
|
1591
|
+
rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern.json',
|
|
1592
|
+
'Root must point at compact UltraModern config',
|
|
1593
|
+
);
|
|
763
1594
|
assert(rootPackage.modernjs?.packageSource?.strategy === packageSource.strategy, 'Root package source strategy must match metadata');
|
|
764
1595
|
assert(packageSource.strategy === 'workspace' || packageSource.strategy === 'install', 'Package source strategy must be workspace or install');
|
|
765
1596
|
assert(packageSource.strategy === 'install' || packageSource.modernPackages?.specifier === 'workspace:*', 'Workspace package source must be explicitly backed by workspace:*');
|
|
@@ -807,22 +1638,61 @@ assert(
|
|
|
807
1638
|
);
|
|
808
1639
|
assert(rootPackage.scripts?.['cloudflare:build'] === expectedCloudflareBuildScript, 'Root cloudflare:build script is incorrect');
|
|
809
1640
|
assert(!('ultramodern:check' in (rootPackage.scripts ?? {})), 'Root must not expose ultramodern:check');
|
|
810
|
-
|
|
1641
|
+
if (bridgeConfig) {
|
|
1642
|
+
assert(
|
|
1643
|
+
rootPackage.scripts?.typecheck ===
|
|
1644
|
+
'pnpm -r --filter "./apps/*" --filter "./verticals/*" --filter "./packages/*" run typecheck',
|
|
1645
|
+
'Bridge root typecheck must check generated package boundaries without building parent implementation sources',
|
|
1646
|
+
);
|
|
1647
|
+
assert(Array.isArray(bridgeConfig.workspacePackages), 'Bridge config must record workspace package patterns');
|
|
1648
|
+
for (const workspacePackage of bridgeConfig.workspacePackages) {
|
|
1649
|
+
assert(
|
|
1650
|
+
rootPackage.workspaces?.includes(workspacePackage.pattern),
|
|
1651
|
+
`Root workspaces must include bridge package pattern ${workspacePackage.pattern}`,
|
|
1652
|
+
);
|
|
1653
|
+
assert(
|
|
1654
|
+
readText('pnpm-workspace.yaml').includes(` - ${workspacePackage.pattern}`),
|
|
1655
|
+
`pnpm-workspace.yaml must include bridge package pattern ${workspacePackage.pattern}`,
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
for (const gate of bridgeConfig.gates ?? []) {
|
|
1659
|
+
const expectedGateScript = `${gate.cwd ? `cd ${gate.cwd} && ` : ''}${gate.command}`;
|
|
1660
|
+
assert(
|
|
1661
|
+
rootPackage.scripts?.[`bridge:${gate.name}`] === expectedGateScript,
|
|
1662
|
+
`Bridge gate script bridge:${gate.name} is incorrect`,
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
assert(rootPackage.scripts?.['bridge:check'], 'Bridge workspaces must expose bridge:check');
|
|
1666
|
+
} else {
|
|
1667
|
+
assert(rootPackage.scripts?.typecheck === 'node ./scripts/ultramodern-typecheck.mjs --build tsconfig.json', 'Root typecheck must run TS-Go build mode across project references');
|
|
1668
|
+
}
|
|
811
1669
|
assert(rootPackage.scripts?.['contract:check'] === 'node ./scripts/validate-ultramodern-workspace.mjs', 'Root must expose contract:check');
|
|
1670
|
+
assert(rootPackage.scripts?.['api:check'] === 'node ./scripts/check-ultramodern-api-boundaries.mjs', 'Root must expose api:check');
|
|
812
1671
|
assert(rootPackage.scripts?.['i18n:boundaries'] === 'node ./scripts/check-ultramodern-i18n-boundaries.mjs', 'Root must expose i18n:boundaries');
|
|
813
1672
|
assert(rootPackage.scripts?.['performance:readiness'] === 'node ./scripts/ultramodern-performance-readiness.mjs', 'Root must expose default-on performance readiness diagnostics');
|
|
814
|
-
assert(
|
|
1673
|
+
assert(
|
|
1674
|
+
bridgeConfig
|
|
1675
|
+
? rootPackage.scripts?.check?.includes('pnpm api:check') &&
|
|
1676
|
+
rootPackage.scripts.check.endsWith('&& pnpm performance:readiness && pnpm bridge:check')
|
|
1677
|
+
: rootPackage.scripts?.check?.includes('pnpm api:check') &&
|
|
1678
|
+
rootPackage.scripts.check.endsWith('&& pnpm performance:readiness'),
|
|
1679
|
+
'Root check must run default-on performance readiness diagnostics and bridge gates when configured',
|
|
1680
|
+
);
|
|
815
1681
|
const performanceReadinessConfig = readText('scripts/ultramodern-performance-readiness.config.mjs');
|
|
816
|
-
const
|
|
1682
|
+
const assertToolWrapper = (scriptPath, command) => {
|
|
1683
|
+
const source = readText(scriptPath);
|
|
1684
|
+
assert(source.includes('modern-js-create'), `${scriptPath} must delegate to modern-js-create`);
|
|
1685
|
+
assert(source.includes('ULTRAMODERN_CREATE_BIN'), `${scriptPath} must support local create-bin overrides for generated-workspace tests`);
|
|
1686
|
+
assert(source.includes("'ultramodern'") || source.includes('"ultramodern"'), `${scriptPath} must dispatch through the UltraModern tool surface`);
|
|
1687
|
+
assert(source.includes(command), `${scriptPath} must dispatch ${command}`);
|
|
1688
|
+
};
|
|
817
1689
|
assert(performanceReadinessConfig.includes('UltramodernPerformanceReadinessDiagnosticsConfig'), 'Performance readiness config must carry the typed opt-out surface');
|
|
818
1690
|
assert(performanceReadinessConfig.includes('enabled: true'), 'Performance readiness diagnostics must be default-on');
|
|
819
1691
|
assert(performanceReadinessConfig.includes("failOn: 'framework-invariant'"), 'Performance readiness diagnostics must only fail framework invariants by default');
|
|
820
|
-
|
|
821
|
-
|
|
1692
|
+
assertToolWrapper('scripts/validate-ultramodern-workspace.mjs', 'validate');
|
|
1693
|
+
assertToolWrapper('scripts/ultramodern-performance-readiness.mjs', 'performance-readiness');
|
|
822
1694
|
const i18nBoundaryScript = readText('scripts/check-ultramodern-i18n-boundaries.mjs');
|
|
823
|
-
|
|
824
|
-
assert(ultramodernTypecheckScript.includes("'--checkers'") && ultramodernTypecheckScript.includes("'--builders'"), 'Root typecheck script must drive TS-Go checker and builder parallelism');
|
|
825
|
-
assert(ultramodernTypecheckScript.includes('effect-tsgo') && ultramodernTypecheckScript.includes('get-exe-path'), 'Root typecheck script must resolve the @effect/tsgo native-preview binary');
|
|
1695
|
+
assertToolWrapper('scripts/ultramodern-typecheck.mjs', 'typecheck');
|
|
826
1696
|
assert(
|
|
827
1697
|
i18nBoundaryScript.includes("from '@modern-js/code-tools'") &&
|
|
828
1698
|
i18nBoundaryScript.includes('runWorkspaceSourceCheck'),
|
|
@@ -833,10 +1703,13 @@ assert(rootPackage.scripts?.['cloudflare:deploy'] === expectedCloudflareDeploySc
|
|
|
833
1703
|
assert(rootPackage.scripts?.['cloudflare:proof'] === 'node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json', 'Root must expose cloudflare:proof');
|
|
834
1704
|
assert(rootPackage.scripts?.['skills:install'] === 'node ./scripts/bootstrap-agent-skills.mjs', 'Root must expose skills:install');
|
|
835
1705
|
assert(rootPackage.scripts?.['skills:check'] === 'node ./scripts/bootstrap-agent-skills.mjs --check', 'Root must expose skills:check');
|
|
836
|
-
assert(rootPackage.scripts?.postinstall === "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs --postinstall", 'Root postinstall must
|
|
1706
|
+
assert(rootPackage.scripts?.postinstall === "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs --postinstall", 'Root postinstall must format, run the default-on Codex skills bootstrap, and leave reference repository installs explicit');
|
|
837
1707
|
assert(rootPackage.scripts?.['agents:refs:install'] === 'node ./scripts/setup-agent-reference-repos.mjs', 'Root must expose agents:refs:install as the explicit reference repo installer');
|
|
838
1708
|
const agentSkillsBootstrap = readText('scripts/bootstrap-agent-skills.mjs');
|
|
839
|
-
assert
|
|
1709
|
+
assertToolWrapper('scripts/assert-mf-types.mjs', 'mf-types');
|
|
1710
|
+
assertToolWrapper('scripts/generate-public-surface-assets.mjs', 'public-surface');
|
|
1711
|
+
assertToolWrapper('scripts/proof-cloudflare-version.mjs', 'cloudflare-proof');
|
|
1712
|
+
assertToolWrapper('scripts/bootstrap-agent-skills.mjs', 'skills');
|
|
840
1713
|
assert(!agentSkillsBootstrap.includes("run('brew'") && !agentSkillsBootstrap.includes('runShell('), 'Agent skills bootstrap must never invoke system package managers');
|
|
841
1714
|
const agentReferenceRepoSetup = readText('scripts/setup-agent-reference-repos.mjs');
|
|
842
1715
|
assert(agentReferenceRepoSetup.includes("['commit', '--no-verify', '-m', message]"), 'Agent reference repo installer commits must skip hooks during postinstall');
|
|
@@ -875,6 +1748,7 @@ assert(generatedContract.performanceReadiness?.optOut?.env === 'ULTRAMODERN_PERF
|
|
|
875
1748
|
assert(JSON.stringify(generatedContract.performanceReadiness?.signals?.map(signal => signal.id)) === JSON.stringify(expectedPerformanceReadinessSignals), 'Performance readiness signal ids are incorrect');
|
|
876
1749
|
|
|
877
1750
|
const shellModernConfig = readText('apps/shell-super-app/modern.config.ts');
|
|
1751
|
+
const shellModuleFederationConfig = readText('apps/shell-super-app/module-federation.config.ts');
|
|
878
1752
|
const shellRouteHead = readText('apps/shell-super-app/src/routes/ultramodern-route-head.tsx');
|
|
879
1753
|
const shellRouteMetadata = readText('apps/shell-super-app/src/routes/ultramodern-route-metadata.ts');
|
|
880
1754
|
assert(shellRouteMetadata.includes('@generated by @modern-js/create'), 'Shell route metadata compatibility manifest must be marked generated');
|
|
@@ -909,8 +1783,9 @@ assert(shellModernConfig.includes('name: cloudflareWorkerName'), 'Shell modern.c
|
|
|
909
1783
|
assert(shellModernConfig.includes('const assetPrefix ='), 'Shell modern.config.ts must derive a dedicated asset prefix');
|
|
910
1784
|
assert(shellModernConfig.includes("const configuredUltramodernAssetPrefix = envValue('ULTRAMODERN_ASSET_PREFIX')"), 'Shell asset prefix must support ULTRAMODERN_ASSET_PREFIX');
|
|
911
1785
|
assert(shellModernConfig.includes("const configuredModernAssetPrefix = envValue('MODERN_ASSET_PREFIX')"), 'Shell asset prefix must support MODERN_ASSET_PREFIX');
|
|
1786
|
+
assert(shellModernConfig.includes("const defaultAssetPrefix = '/'"), 'Shell asset prefix must default to origin-relative assets');
|
|
912
1787
|
const shellAssetPrefixExpression = extractAssetPrefixExpression(shellModernConfig);
|
|
913
|
-
assert(shellAssetPrefixExpression.includes("configuredModernAssetPrefix || configuredUltramodernAssetPrefix ||
|
|
1788
|
+
assert(shellAssetPrefixExpression.includes("configuredModernAssetPrefix || configuredUltramodernAssetPrefix || defaultAssetPrefix"), 'Shell asset prefix fallback order is incorrect');
|
|
914
1789
|
assert(!shellAssetPrefixExpression.includes('configuredSiteUrl') && !shellAssetPrefixExpression.includes('MODERN_PUBLIC_SITE_URL'), 'Shell asset prefix must not fall back to MODERN_PUBLIC_SITE_URL');
|
|
915
1790
|
assert(!shellAssetPrefixExpression.includes('configuredCloudflareUrl') && !shellAssetPrefixExpression.includes('ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP'), 'Shell asset prefix must not fall back to the per-app public URL');
|
|
916
1791
|
assert(!shellAssetPrefixExpression.includes('inferredCloudflareUrl') && !shellAssetPrefixExpression.includes('ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN'), 'Shell asset prefix must not infer workers.dev URLs');
|
|
@@ -926,6 +1801,9 @@ assert(shellContract?.config?.performance?.readinessDiagnostics?.optOut?.env ===
|
|
|
926
1801
|
assert(JSON.stringify(shellContract?.config?.source?.siteUrl?.envFallbackOrder) === JSON.stringify(['MODERN_PUBLIC_SITE_URL', 'ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP', 'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN', 'SHELL_SUPER_APP_PORT']), 'Shell site URL env fallback order is incorrect');
|
|
927
1802
|
assert(shellContract?.config?.rspack?.output?.uniqueName === 'shellSuperApp', 'Shell Rspack uniqueName is incorrect');
|
|
928
1803
|
assert(shellContract?.config?.rspack?.output?.chunkLoadingGlobal === expectedChunkLoadingGlobal('shellSuperApp'), 'Shell Rspack chunkLoadingGlobal is incorrect');
|
|
1804
|
+
assert(shellContract?.moduleFederation?.dts?.compilerInstance === 'tsgo', 'Shell must keep mandatory DTS compiler');
|
|
1805
|
+
assert(shellContract?.moduleFederation?.dts?.tsConfigPath === './tsconfig.mf-types.json', 'Shell must keep dedicated Module Federation DTS tsconfig');
|
|
1806
|
+
assert(shellModuleFederationConfig.includes("tsConfigPath: './tsconfig.mf-types.json'"), 'Shell Module Federation config must use the dedicated DTS tsconfig');
|
|
929
1807
|
assert(topology.shell?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell topology Cloudflare workerName is incorrect');
|
|
930
1808
|
assert(shellContract?.styling?.federation?.owner?.id === 'shell-super-app', 'Shell CSS federation owner is missing');
|
|
931
1809
|
assert(shellContract?.styling?.federation?.role === 'shell-base-overlay', 'Shell must own base and overlay CSS');
|
|
@@ -960,6 +1838,7 @@ assert(!('effectServices' in topology), 'Default APIs must be vertical-owned, no
|
|
|
960
1838
|
for (const vertical of fullStackVerticals) {
|
|
961
1839
|
const packageJson = readJson(`${vertical.path}/package.json`);
|
|
962
1840
|
const modernConfig = readText(`${vertical.path}/modern.config.ts`);
|
|
1841
|
+
const moduleFederationConfig = readText(`${vertical.path}/module-federation.config.ts`);
|
|
963
1842
|
const routeHead = readText(`${vertical.path}/src/routes/ultramodern-route-head.tsx`);
|
|
964
1843
|
const routeMetadata = readText(`${vertical.path}/src/routes/ultramodern-route-metadata.ts`);
|
|
965
1844
|
assert(routeMetadata.includes('@generated by @modern-js/create'), `${vertical.id} route metadata compatibility manifest must be marked generated`);
|
|
@@ -972,8 +1851,8 @@ for (const vertical of fullStackVerticals) {
|
|
|
972
1851
|
assert(packageJson.dependencies?.['@modern-js/plugin-i18n'] === expectedModernPackageSpecifier('@modern-js/plugin-i18n'), `${vertical.id} plugin-i18n dependency must match package source metadata`);
|
|
973
1852
|
assert(packageJson.dependencies?.['@modern-js/plugin-tanstack'] === expectedModernPackageSpecifier('@modern-js/plugin-tanstack'), `${vertical.id} plugin-tanstack dependency must match package source metadata`);
|
|
974
1853
|
assert(packageJson.dependencies?.['@modern-js/runtime'] === expectedModernPackageSpecifier('@modern-js/runtime'), `${vertical.id} runtime dependency must match package source metadata`);
|
|
975
|
-
assert(packageJson.exports?.['./
|
|
976
|
-
assert(packageJson.exports?.['./
|
|
1854
|
+
assert(packageJson.exports?.['./api/client'] === `./src/api/${vertical.stem}-client.ts`, `${vertical.id} must export its API client`);
|
|
1855
|
+
assert(packageJson.exports?.['./api'] === './shared/api.ts', `${vertical.id} must export its API contract`);
|
|
977
1856
|
const expectedVerticalZephyrDependencies = Object.fromEntries(
|
|
978
1857
|
fullStackVerticals
|
|
979
1858
|
.filter(candidate => vertical.verticalRefs.includes(candidate.id))
|
|
@@ -1004,38 +1883,44 @@ for (const vertical of fullStackVerticals) {
|
|
|
1004
1883
|
assert(modernConfig.includes('const assetPrefix ='), `${vertical.id} modern.config.ts must derive a dedicated asset prefix`);
|
|
1005
1884
|
assert(modernConfig.includes("const configuredUltramodernAssetPrefix = envValue('ULTRAMODERN_ASSET_PREFIX')"), `${vertical.id} asset prefix must support ULTRAMODERN_ASSET_PREFIX`);
|
|
1006
1885
|
assert(modernConfig.includes("const configuredModernAssetPrefix = envValue('MODERN_ASSET_PREFIX')"), `${vertical.id} asset prefix must support MODERN_ASSET_PREFIX`);
|
|
1886
|
+
assert(modernConfig.includes('const defaultRemoteAssetPrefix'), `${vertical.id} asset prefix must derive the remote asset origin`);
|
|
1887
|
+
assert(modernConfig.includes('const defaultAssetPrefix = defaultRemoteAssetPrefix'), `${vertical.id} asset prefix must default to its own remote origin`);
|
|
1007
1888
|
const verticalAssetPrefixExpression = extractAssetPrefixExpression(modernConfig);
|
|
1008
|
-
assert(verticalAssetPrefixExpression.includes("configuredModernAssetPrefix || configuredUltramodernAssetPrefix ||
|
|
1889
|
+
assert(verticalAssetPrefixExpression.includes("configuredModernAssetPrefix || configuredUltramodernAssetPrefix || defaultAssetPrefix"), `${vertical.id} asset prefix fallback order is incorrect`);
|
|
1009
1890
|
assert(!verticalAssetPrefixExpression.includes('configuredSiteUrl') && !verticalAssetPrefixExpression.includes('MODERN_PUBLIC_SITE_URL'), `${vertical.id} asset prefix must not fall back to MODERN_PUBLIC_SITE_URL`);
|
|
1010
|
-
assert(
|
|
1011
|
-
assert(
|
|
1891
|
+
assert(modernConfig.includes(`envValue('ULTRAMODERN_PUBLIC_URL_${vertical.id.replace(/-/g, '_').toUpperCase()}')`), `${vertical.id} asset prefix must read its per-app public URL`);
|
|
1892
|
+
assert(modernConfig.includes('inferredCloudflareUrl'), `${vertical.id} asset prefix must support workers.dev origin inference`);
|
|
1012
1893
|
assert(modernConfig.includes("assetPrefix: '/'"), `${vertical.id} modern.config.ts must keep dev assets origin-relative`);
|
|
1013
1894
|
assert(modernConfig.includes('assetPrefix,'), `${vertical.id} modern.config.ts must wire output.assetPrefix to the derived asset prefix`);
|
|
1014
1895
|
assert(contractEntry?.config?.dev?.assetPrefix === '/', `${vertical.id} dev asset prefix must stay origin-relative`);
|
|
1015
|
-
assert(contractEntry?.config?.output?.assetPrefix?.default === '
|
|
1896
|
+
assert(contractEntry?.config?.output?.assetPrefix?.default === 'app-public-origin', `${vertical.id} asset prefix must default to its app public origin`);
|
|
1016
1897
|
assert(JSON.stringify(contractEntry?.config?.output?.assetPrefix?.envFallbackOrder) === JSON.stringify(['MODERN_ASSET_PREFIX', 'ULTRAMODERN_ASSET_PREFIX']), `${vertical.id} asset prefix env fallback order is incorrect`);
|
|
1017
1898
|
assert(contractEntry?.config?.output?.disableTsChecker === false, `${vertical.id} must keep the framework TypeScript checker enabled`);
|
|
1018
1899
|
assert(contractEntry?.config?.performance?.readinessDiagnostics?.default === 'enabled', `${vertical.id} performance readiness diagnostics must be default-on`);
|
|
1019
1900
|
assert(contractEntry?.config?.performance?.readinessDiagnostics?.failOn === 'framework-invariant', `${vertical.id} performance readiness diagnostics must only fail framework invariants by default`);
|
|
1020
1901
|
assert(contractEntry?.config?.performance?.readinessDiagnostics?.optOut?.config === 'scripts/ultramodern-performance-readiness.config.mjs', `${vertical.id} performance readiness opt-out config is incorrect`);
|
|
1021
|
-
assert(contractEntry?.deploy?.cloudflare?.routes?.
|
|
1902
|
+
assert(contractEntry?.deploy?.cloudflare?.routes?.apiReadiness === `${vertical.apiPrefix}/${vertical.stem}/readiness`, `${vertical.id} Cloudflare proof readiness route is incorrect`);
|
|
1022
1903
|
assert(contractEntry?.config?.rspack?.output?.uniqueName === vertical.mfName, `${vertical.id} Rspack uniqueName is incorrect`);
|
|
1023
1904
|
assert(contractEntry?.config?.rspack?.output?.chunkLoadingGlobal === expectedChunkLoadingGlobal(vertical.mfName), `${vertical.id} Rspack chunkLoadingGlobal is incorrect`);
|
|
1024
1905
|
assert(contractEntry?.moduleFederation?.name === vertical.mfName, `${vertical.id} MF name is incorrect`);
|
|
1025
1906
|
assert(JSON.stringify(contractEntry?.moduleFederation?.exposes) === JSON.stringify(vertical.exposes), `${vertical.id} MF exposes are incorrect`);
|
|
1026
1907
|
assert(contractEntry?.moduleFederation?.dts?.compilerInstance === 'tsgo', `${vertical.id} must keep mandatory DTS compiler`);
|
|
1908
|
+
assert(contractEntry?.moduleFederation?.dts?.tsConfigPath === './tsconfig.mf-types.json', `${vertical.id} must keep dedicated Module Federation DTS tsconfig`);
|
|
1909
|
+
assert(moduleFederationConfig.includes("tsConfigPath: './tsconfig.mf-types.json'"), `${vertical.id} Module Federation config must use the dedicated DTS tsconfig`);
|
|
1027
1910
|
assert(JSON.stringify(contractEntry?.moduleFederation?.verticalRefs ?? []) === JSON.stringify(vertical.verticalRefs), `${vertical.id} MF verticalRefs are incorrect`);
|
|
1028
1911
|
assert(
|
|
1029
1912
|
JSON.stringify((contractEntry?.moduleFederation?.remotes ?? []).map(remote => remote.id)) ===
|
|
1030
1913
|
JSON.stringify(vertical.verticalRefs),
|
|
1031
1914
|
`${vertical.id} MF consumed verticals are incorrect`,
|
|
1032
1915
|
);
|
|
1033
|
-
assert(contractEntry?.
|
|
1034
|
-
assert(contractEntry?.
|
|
1035
|
-
assert(contractEntry?.
|
|
1036
|
-
assert(contractEntry?.
|
|
1037
|
-
assert(contractEntry?.
|
|
1038
|
-
assert(
|
|
1916
|
+
assert(contractEntry?.api?.prefix === vertical.apiPrefix, `${vertical.id} API prefix is incorrect`);
|
|
1917
|
+
assert(contractEntry?.api?.group === vertical.group, `${vertical.id} API group is incorrect`);
|
|
1918
|
+
assert(contractEntry?.api?.runtime === 'effect', `${vertical.id} API runtime must be Effect`);
|
|
1919
|
+
assert(contractEntry?.api?.strictEffectApproach === true, `${vertical.id} strictEffectApproach must be enabled`);
|
|
1920
|
+
assert(contractEntry?.api?.readiness?.endpoint === `/${vertical.stem}/readiness`, `${vertical.id} readiness endpoint is incorrect`);
|
|
1921
|
+
assert(contractEntry?.api?.operations?.readiness?.path === `/${vertical.stem}/readiness`, `${vertical.id} readiness operation is missing`);
|
|
1922
|
+
assert(contractEntry?.api?.requestContext?.propagatedHeaders?.includes('traceparent'), `${vertical.id} trace context propagation is missing`);
|
|
1923
|
+
assert(Object.keys(contractEntry?.api?.domainOperations ?? {}).length >= 3, `${vertical.id} domain operations are missing`);
|
|
1039
1924
|
assert(contractEntry?.i18n?.languages?.includes('en') && contractEntry?.i18n?.languages?.includes('cs'), `${vertical.id} must declare i18n languages`);
|
|
1040
1925
|
assert(contractEntry?.i18n?.namespace === vertical.namespace, `${vertical.id} i18n namespace is incorrect`);
|
|
1041
1926
|
assert(
|
|
@@ -1074,10 +1959,11 @@ for (const vertical of fullStackVerticals) {
|
|
|
1074
1959
|
assert(topologyEntry?.moduleFederation?.name === vertical.mfName, `${vertical.id} topology MF name is incorrect`);
|
|
1075
1960
|
assert(JSON.stringify(topologyEntry?.moduleFederation?.exposes) === JSON.stringify(vertical.exposes), `${vertical.id} topology exposes are incorrect`);
|
|
1076
1961
|
assert(JSON.stringify(topologyEntry?.moduleFederation?.verticalRefs ?? []) === JSON.stringify(vertical.verticalRefs), `${vertical.id} topology verticalRefs are incorrect`);
|
|
1077
|
-
assert(topologyEntry?.api?.
|
|
1078
|
-
assert(topologyEntry?.api?.
|
|
1079
|
-
assert(topologyEntry?.api?.
|
|
1080
|
-
assert(
|
|
1962
|
+
assert(topologyEntry?.api?.bff?.prefix === vertical.apiPrefix, `${vertical.id} topology API prefix is incorrect`);
|
|
1963
|
+
assert(topologyEntry?.api?.bff?.strictEffectApproach === true, `${vertical.id} topology strictEffectApproach is incorrect`);
|
|
1964
|
+
assert(topologyEntry?.api?.serverEntry === `${vertical.path}/api/index.ts`, `${vertical.id} topology server entry is incorrect`);
|
|
1965
|
+
assert(topologyEntry?.api?.readiness?.endpoint === `/${vertical.stem}/readiness`, `${vertical.id} topology readiness endpoint is incorrect`);
|
|
1966
|
+
assert(Object.keys(topologyEntry?.api?.domainOperations ?? {}).length >= 3, `${vertical.id} topology domain operations are missing`);
|
|
1081
1967
|
|
|
1082
1968
|
assert(ownership.owners?.some(owner => owner.id === vertical.id && owner.path === vertical.path), `${vertical.id} ownership entry is missing`);
|
|
1083
1969
|
assert(overlay.ports?.[vertical.id], `${vertical.id} development port is missing`);
|