@bleedingdev/modern-js-create 3.4.0-ultramodern.12 → 3.4.0-ultramodern.14
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 +36 -3
- package/dist/cjs/locale/en.cjs +10 -0
- package/dist/cjs/locale/zh.cjs +10 -0
- package/dist/cjs/ultramodern-tooling/commands.cjs +185 -0
- package/dist/cjs/ultramodern-tooling/config.cjs +248 -0
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +64 -25
- 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 +119 -653
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +4 -0
- 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 +572 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +14 -0
- package/dist/cjs/ultramodern-workspace/package-json.cjs +26 -12
- package/dist/cjs/ultramodern-workspace/public-api.cjs +4 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +74 -44
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +55 -15
- package/dist/esm/index.js +36 -3
- package/dist/esm/locale/en.js +10 -0
- package/dist/esm/locale/zh.js +10 -0
- package/dist/esm/ultramodern-tooling/commands.js +132 -0
- package/dist/esm/ultramodern-tooling/config.js +196 -0
- package/dist/esm/ultramodern-workspace/add-vertical.js +65 -29
- 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 +122 -608
- package/dist/esm/ultramodern-workspace/descriptors.js +2 -1
- 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 +517 -0
- package/dist/esm/ultramodern-workspace/module-federation.js +14 -0
- package/dist/esm/ultramodern-workspace/package-json.js +26 -12
- package/dist/esm/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +75 -27
- package/dist/esm/ultramodern-workspace/write-workspace.js +56 -19
- package/dist/esm-node/index.js +36 -3
- package/dist/esm-node/locale/en.js +10 -0
- package/dist/esm-node/locale/zh.js +10 -0
- package/dist/esm-node/ultramodern-tooling/commands.js +133 -0
- package/dist/esm-node/ultramodern-tooling/config.js +197 -0
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +65 -29
- 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 +122 -608
- package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -1
- 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 +518 -0
- package/dist/esm-node/ultramodern-workspace/module-federation.js +14 -0
- package/dist/esm-node/ultramodern-workspace/package-json.js +26 -12
- package/dist/esm-node/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +75 -27
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +56 -19
- 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 -0
- 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/package-json.d.ts +4 -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/workspace-scripts.d.ts +1 -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/scripts/bootstrap-agent-skills.mjs +77 -67
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +56 -3
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +239 -4
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +282 -4
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +172 -1
- package/templates/workspace-scripts/ultramodern-typecheck.mjs +4 -2
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +783 -26
- /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
|
@@ -4,18 +4,253 @@ import path from 'node:path';
|
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
|
|
6
6
|
const workspaceRoot = path.resolve(
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
process.env.ULTRAMODERN_WORKSPACE_ROOT ??
|
|
8
|
+
path.join(path.dirname(fileURLToPath(import.meta.url)), '..'),
|
|
9
9
|
);
|
|
10
10
|
const contractPath = path.join(
|
|
11
11
|
workspaceRoot,
|
|
12
12
|
'.modernjs/ultramodern-generated-contract.json',
|
|
13
13
|
);
|
|
14
|
+
const compactConfigPath = path.join(workspaceRoot, '.modernjs/ultramodern.json');
|
|
14
15
|
|
|
15
16
|
function readJson(filePath) {
|
|
16
17
|
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
function toKebabCase(value) {
|
|
21
|
+
return String(value)
|
|
22
|
+
.trim()
|
|
23
|
+
.replace(/([a-z0-9])([A-Z])/gu, '$1-$2')
|
|
24
|
+
.replace(/[^a-zA-Z0-9._-]+/gu, '-')
|
|
25
|
+
.replace(/[._]+/gu, '-')
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/-+/gu, '-')
|
|
28
|
+
.replace(/^-+|-+$/gu, '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function toEnvSegment(value) {
|
|
32
|
+
return toKebabCase(value).replace(/-/gu, '_').toUpperCase();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeRelativePath(value) {
|
|
36
|
+
return String(value ?? '').replace(/\\/gu, '/').replace(/^\.\/+/u, '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function appNamespace(app) {
|
|
40
|
+
return app.kind === 'shell' ? 'shell' : (app.domain ?? app.id);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function defaultPortEnv(app) {
|
|
44
|
+
return app.kind === 'shell'
|
|
45
|
+
? 'SHELL_SUPER_APP_PORT'
|
|
46
|
+
: `VERTICAL_${toEnvSegment(app.domain ?? app.id)}_PORT`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function defaultPort(app) {
|
|
50
|
+
return app.kind === 'shell' ? 3020 : 3030;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeCompactApp(rawApp) {
|
|
54
|
+
const id = String(rawApp.id);
|
|
55
|
+
const kind = rawApp.kind === 'vertical' ? 'vertical' : 'shell';
|
|
56
|
+
const appPath =
|
|
57
|
+
typeof rawApp.path === 'string'
|
|
58
|
+
? normalizeRelativePath(rawApp.path)
|
|
59
|
+
: kind === 'shell'
|
|
60
|
+
? 'apps/shell-super-app'
|
|
61
|
+
: `verticals/${toKebabCase(id)}`;
|
|
62
|
+
const packageSuffix =
|
|
63
|
+
typeof rawApp.packageSuffix === 'string'
|
|
64
|
+
? rawApp.packageSuffix
|
|
65
|
+
: appPath.split('/').at(-1) ?? id;
|
|
66
|
+
const domain =
|
|
67
|
+
typeof rawApp.domain === 'string'
|
|
68
|
+
? rawApp.domain
|
|
69
|
+
: kind === 'vertical'
|
|
70
|
+
? packageSuffix
|
|
71
|
+
: undefined;
|
|
72
|
+
const moduleFederation =
|
|
73
|
+
rawApp.moduleFederation && typeof rawApp.moduleFederation === 'object'
|
|
74
|
+
? rawApp.moduleFederation
|
|
75
|
+
: {};
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
id,
|
|
79
|
+
kind,
|
|
80
|
+
path: appPath,
|
|
81
|
+
packageSuffix,
|
|
82
|
+
domain,
|
|
83
|
+
port:
|
|
84
|
+
typeof rawApp.port === 'number'
|
|
85
|
+
? rawApp.port
|
|
86
|
+
: defaultPort({ id, kind, domain }),
|
|
87
|
+
portEnv:
|
|
88
|
+
typeof rawApp.portEnv === 'string'
|
|
89
|
+
? rawApp.portEnv
|
|
90
|
+
: defaultPortEnv({ id, kind, domain }),
|
|
91
|
+
mfName:
|
|
92
|
+
typeof moduleFederation.name === 'string'
|
|
93
|
+
? moduleFederation.name
|
|
94
|
+
: kind === 'shell'
|
|
95
|
+
? 'shellSuperApp'
|
|
96
|
+
: `vertical${toKebabCase(domain ?? id)
|
|
97
|
+
.split('-')
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
100
|
+
.join('')}`,
|
|
101
|
+
exposes: Array.isArray(moduleFederation.exposes)
|
|
102
|
+
? moduleFederation.exposes.filter(expose => typeof expose === 'string')
|
|
103
|
+
: [],
|
|
104
|
+
verticalRefs: Array.isArray(moduleFederation.verticalRefs)
|
|
105
|
+
? moduleFederation.verticalRefs.filter(ref => typeof ref === 'string')
|
|
106
|
+
: [],
|
|
107
|
+
marker: {
|
|
108
|
+
appId: id,
|
|
109
|
+
},
|
|
110
|
+
effectApi:
|
|
111
|
+
rawApp.effectApi && typeof rawApp.effectApi === 'object'
|
|
112
|
+
? {
|
|
113
|
+
stem:
|
|
114
|
+
typeof rawApp.effectApi.stem === 'string'
|
|
115
|
+
? rawApp.effectApi.stem
|
|
116
|
+
: domain ?? id,
|
|
117
|
+
prefix:
|
|
118
|
+
typeof rawApp.effectApi.prefix === 'string'
|
|
119
|
+
? rawApp.effectApi.prefix
|
|
120
|
+
: `/${domain ?? id}-api`,
|
|
121
|
+
}
|
|
122
|
+
: undefined,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function createRouteOwnedEntries(app) {
|
|
127
|
+
const namespace = appNamespace(app);
|
|
128
|
+
const base = {
|
|
129
|
+
descriptionKey: `${namespace}.seo.description`,
|
|
130
|
+
mfBoundaryId: app.mfName,
|
|
131
|
+
namespace,
|
|
132
|
+
ownerAppId: app.id,
|
|
133
|
+
public: false,
|
|
134
|
+
indexable: false,
|
|
135
|
+
publicSurface: 'private-app-screen',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return [
|
|
139
|
+
{
|
|
140
|
+
...base,
|
|
141
|
+
canonicalPath: '/',
|
|
142
|
+
id: app.kind === 'shell' ? 'shell-home' : `${app.id}-home`,
|
|
143
|
+
localisedPaths: {
|
|
144
|
+
cs: '/',
|
|
145
|
+
en: '/',
|
|
146
|
+
},
|
|
147
|
+
titleKey: app.kind === 'shell' ? 'shell.title' : `${namespace}.title`,
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function createPublicRoutes(app) {
|
|
153
|
+
return createRouteOwnedEntries(app)
|
|
154
|
+
.filter(route => route.public && route.indexable)
|
|
155
|
+
.map(route => ({
|
|
156
|
+
canonicalPath: route.canonicalPath,
|
|
157
|
+
id: route.id,
|
|
158
|
+
localisedPaths: route.localisedPaths,
|
|
159
|
+
namespace: route.namespace,
|
|
160
|
+
ownerAppId: route.ownerAppId,
|
|
161
|
+
descriptionKey: route.descriptionKey,
|
|
162
|
+
titleKey: route.titleKey,
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function createPublicSurface(app) {
|
|
167
|
+
const publicRoutes = createPublicRoutes(app);
|
|
168
|
+
return {
|
|
169
|
+
authoring: 'colocated-route-meta',
|
|
170
|
+
artifactLifecycle: 'build-and-deploy-output',
|
|
171
|
+
generatedManifest: './src/routes/ultramodern-route-metadata',
|
|
172
|
+
source: 'route-owned-public-routes',
|
|
173
|
+
metadataExport: './src/routes/ultramodern-route-metadata',
|
|
174
|
+
generator: 'scripts/generate-public-surface-assets.mjs',
|
|
175
|
+
outputRoot: 'dist/public',
|
|
176
|
+
cloudflareOutputRoot: '.output/public',
|
|
177
|
+
privateRoutePolicy: 'omit-from-generated-public-surface',
|
|
178
|
+
files:
|
|
179
|
+
publicRoutes.length > 0
|
|
180
|
+
? ['robots.txt', 'sitemap.xml', 'site.webmanifest']
|
|
181
|
+
: ['robots.txt'],
|
|
182
|
+
omittedByDefault: ['api-catalog.json', 'llms.txt', 'security.txt'],
|
|
183
|
+
languages: ['en', 'cs'],
|
|
184
|
+
contentExpansion: {
|
|
185
|
+
authoring: 'route-owned-esm-provider',
|
|
186
|
+
defaultProviderFile: 'route.sitemap.mjs',
|
|
187
|
+
entryExport: 'default-or-entries',
|
|
188
|
+
paramsSource: 'params-or-localeParams',
|
|
189
|
+
draftPolicy: 'omit-draft-by-default',
|
|
190
|
+
indexablePolicy: 'omit-indexable-false',
|
|
191
|
+
lifecycle: 'executed-during-public-surface-generation',
|
|
192
|
+
},
|
|
193
|
+
contentSources: [],
|
|
194
|
+
publicRoutes,
|
|
195
|
+
routeEntries: [],
|
|
196
|
+
concreteUrlPaths: [],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function createCloudflareDeploy(config, app) {
|
|
201
|
+
const packageScope =
|
|
202
|
+
typeof config.workspace?.packageScope === 'string'
|
|
203
|
+
? config.workspace.packageScope
|
|
204
|
+
: path.basename(workspaceRoot);
|
|
205
|
+
const compatibilityDate =
|
|
206
|
+
typeof config.deploy?.worker?.compatibilityDate === 'string'
|
|
207
|
+
? config.deploy.worker.compatibilityDate
|
|
208
|
+
: '2026-06-02';
|
|
209
|
+
return {
|
|
210
|
+
workerName: `${toKebabCase(packageScope)}-${app.packageSuffix}`.slice(0, 63),
|
|
211
|
+
publicUrlEnv: `ULTRAMODERN_PUBLIC_URL_${toEnvSegment(app.id)}`,
|
|
212
|
+
compatibilityDate,
|
|
213
|
+
compatibilityFlags: ['nodejs_compat', 'global_fetch_strictly_public'],
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function synthesizeContractFromCompactConfig(config) {
|
|
218
|
+
const apps = Array.isArray(config.topology?.apps)
|
|
219
|
+
? config.topology.apps.map(normalizeCompactApp)
|
|
220
|
+
: [];
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
sourcePath: compactConfigPath,
|
|
224
|
+
apps: apps.map(app => ({
|
|
225
|
+
...app,
|
|
226
|
+
deploy: {
|
|
227
|
+
cloudflare: createCloudflareDeploy(config, app),
|
|
228
|
+
},
|
|
229
|
+
routes: {
|
|
230
|
+
publicSurface: createPublicSurface(app),
|
|
231
|
+
},
|
|
232
|
+
})),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function readGeneratedContractView() {
|
|
237
|
+
if (fs.existsSync(compactConfigPath)) {
|
|
238
|
+
return synthesizeContractFromCompactConfig(readJson(compactConfigPath));
|
|
239
|
+
}
|
|
240
|
+
if (fs.existsSync(contractPath)) {
|
|
241
|
+
return {
|
|
242
|
+
sourcePath: contractPath,
|
|
243
|
+
...readJson(contractPath),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Missing UltraModern config. Expected ${path.relative(
|
|
248
|
+
workspaceRoot,
|
|
249
|
+
compactConfigPath,
|
|
250
|
+
)} or ${path.relative(workspaceRoot, contractPath)}.`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
19
254
|
function parseArgs(argv) {
|
|
20
255
|
const parsed = {
|
|
21
256
|
appId: undefined,
|
|
@@ -517,10 +752,10 @@ try {
|
|
|
517
752
|
printHelp();
|
|
518
753
|
process.exit(0);
|
|
519
754
|
}
|
|
520
|
-
const contract =
|
|
755
|
+
const contract = readGeneratedContractView();
|
|
521
756
|
const app = contract.apps?.find(candidate => candidate.id === args.appId);
|
|
522
757
|
if (!app) {
|
|
523
|
-
throw new Error(`Unknown app in
|
|
758
|
+
throw new Error(`Unknown app in UltraModern config: ${args.appId}`);
|
|
524
759
|
}
|
|
525
760
|
await generatePublicSurfaceAssets(app, args.target, args.requirePublicOrigin);
|
|
526
761
|
} catch (error) {
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import crypto from 'node:crypto';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { validateApp } from './ultramodern-cloudflare-proof.mjs';
|
|
6
7
|
|
|
7
8
|
const workspaceRoot = path.resolve(
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
process.env.ULTRAMODERN_WORKSPACE_ROOT ??
|
|
10
|
+
path.join(path.dirname(fileURLToPath(import.meta.url)), '..'),
|
|
10
11
|
);
|
|
11
12
|
const contractPath = path.join(
|
|
12
13
|
workspaceRoot,
|
|
13
14
|
'.modernjs/ultramodern-generated-contract.json',
|
|
14
15
|
);
|
|
16
|
+
const compactConfigPath = path.join(workspaceRoot, '.modernjs/ultramodern.json');
|
|
15
17
|
const defaultOut = path.join(
|
|
16
18
|
workspaceRoot,
|
|
17
19
|
'.codex/reports/cloudflare-version-proof/public-url-proof.json',
|
|
@@ -21,6 +23,282 @@ function readJson(filePath) {
|
|
|
21
23
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
function toKebabCase(value) {
|
|
27
|
+
return String(value)
|
|
28
|
+
.trim()
|
|
29
|
+
.replace(/([a-z0-9])([A-Z])/gu, '$1-$2')
|
|
30
|
+
.replace(/[^a-zA-Z0-9._-]+/gu, '-')
|
|
31
|
+
.replace(/[._]+/gu, '-')
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/-+/gu, '-')
|
|
34
|
+
.replace(/^-+|-+$/gu, '');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toPascalCase(value) {
|
|
38
|
+
return toKebabCase(value)
|
|
39
|
+
.split('-')
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
42
|
+
.join('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toEnvSegment(value) {
|
|
46
|
+
return toKebabCase(value).replace(/-/gu, '_').toUpperCase();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeRelativePath(value) {
|
|
50
|
+
return String(value ?? '').replace(/\\/gu, '/').replace(/^\.\/+/u, '');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function appNamespace(app) {
|
|
54
|
+
return app.kind === 'shell' ? 'shell' : (app.domain ?? app.id);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeCompactApp(rawApp) {
|
|
58
|
+
const id = String(rawApp.id);
|
|
59
|
+
const kind = rawApp.kind === 'vertical' ? 'vertical' : 'shell';
|
|
60
|
+
const appPath =
|
|
61
|
+
typeof rawApp.path === 'string'
|
|
62
|
+
? normalizeRelativePath(rawApp.path)
|
|
63
|
+
: kind === 'shell'
|
|
64
|
+
? 'apps/shell-super-app'
|
|
65
|
+
: `verticals/${toKebabCase(id)}`;
|
|
66
|
+
const packageSuffix =
|
|
67
|
+
typeof rawApp.packageSuffix === 'string'
|
|
68
|
+
? rawApp.packageSuffix
|
|
69
|
+
: appPath.split('/').at(-1) ?? id;
|
|
70
|
+
const domain =
|
|
71
|
+
typeof rawApp.domain === 'string'
|
|
72
|
+
? rawApp.domain
|
|
73
|
+
: kind === 'vertical'
|
|
74
|
+
? packageSuffix
|
|
75
|
+
: undefined;
|
|
76
|
+
const moduleFederation =
|
|
77
|
+
rawApp.moduleFederation && typeof rawApp.moduleFederation === 'object'
|
|
78
|
+
? rawApp.moduleFederation
|
|
79
|
+
: {};
|
|
80
|
+
const effectApi =
|
|
81
|
+
rawApp.effectApi && typeof rawApp.effectApi === 'object'
|
|
82
|
+
? {
|
|
83
|
+
stem:
|
|
84
|
+
typeof rawApp.effectApi.stem === 'string'
|
|
85
|
+
? rawApp.effectApi.stem
|
|
86
|
+
: domain ?? id,
|
|
87
|
+
prefix:
|
|
88
|
+
typeof rawApp.effectApi.prefix === 'string'
|
|
89
|
+
? rawApp.effectApi.prefix
|
|
90
|
+
: `/${domain ?? id}-api`,
|
|
91
|
+
}
|
|
92
|
+
: undefined;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
kind,
|
|
97
|
+
path: appPath,
|
|
98
|
+
packageSuffix,
|
|
99
|
+
domain,
|
|
100
|
+
mfName:
|
|
101
|
+
typeof moduleFederation.name === 'string'
|
|
102
|
+
? moduleFederation.name
|
|
103
|
+
: kind === 'shell'
|
|
104
|
+
? 'shellSuperApp'
|
|
105
|
+
: `vertical${toPascalCase(domain ?? id)}`,
|
|
106
|
+
effectApi,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function createBuildMarker(scope, app) {
|
|
111
|
+
return crypto
|
|
112
|
+
.createHash('sha256')
|
|
113
|
+
.update(`${scope}:${app.packageSuffix}:${app.id}:0.1.0`)
|
|
114
|
+
.digest('hex')
|
|
115
|
+
.slice(0, 16);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createCloudflareSecurity() {
|
|
119
|
+
return {
|
|
120
|
+
enabled: true,
|
|
121
|
+
headers: {
|
|
122
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
123
|
+
contentTypeOptions: 'nosniff',
|
|
124
|
+
permissionsPolicy:
|
|
125
|
+
'camera=(), geolocation=(), microphone=(), payment=(), usb=()',
|
|
126
|
+
},
|
|
127
|
+
contentSecurityPolicy: {
|
|
128
|
+
mode: 'report-only',
|
|
129
|
+
directives: {
|
|
130
|
+
'base-uri': [`'self'`],
|
|
131
|
+
'connect-src': [`'self'`, 'https:', 'http:', 'wss:', 'ws:'],
|
|
132
|
+
'default-src': [`'self'`],
|
|
133
|
+
'font-src': [`'self'`, 'data:', 'https:', 'http:'],
|
|
134
|
+
'form-action': [`'self'`],
|
|
135
|
+
'frame-ancestors': [`'self'`],
|
|
136
|
+
'img-src': [`'self'`, 'data:', 'blob:', 'https:', 'http:'],
|
|
137
|
+
'manifest-src': [`'self'`, 'https:', 'http:'],
|
|
138
|
+
'object-src': [`'none'`],
|
|
139
|
+
'script-src': [
|
|
140
|
+
`'self'`,
|
|
141
|
+
`'unsafe-inline'`,
|
|
142
|
+
`'unsafe-eval'`,
|
|
143
|
+
'https:',
|
|
144
|
+
'http:',
|
|
145
|
+
'blob:',
|
|
146
|
+
],
|
|
147
|
+
'style-src': [`'self'`, `'unsafe-inline'`, 'https:', 'http:'],
|
|
148
|
+
'worker-src': [`'self'`, 'blob:'],
|
|
149
|
+
},
|
|
150
|
+
reason:
|
|
151
|
+
'Report-only by default so Cloudflare Module Federation SSR can prove remote script, style, and connect compatibility before enforcement.',
|
|
152
|
+
},
|
|
153
|
+
noindex: {
|
|
154
|
+
workersDev: true,
|
|
155
|
+
localhost: true,
|
|
156
|
+
previewHostnames: [],
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createQualityGates() {
|
|
162
|
+
return {
|
|
163
|
+
publicRoutes: {
|
|
164
|
+
requireSitemapWhenPresent: true,
|
|
165
|
+
requireRobotsSitemapConsistency: true,
|
|
166
|
+
requireWebManifestWhenPresent: true,
|
|
167
|
+
},
|
|
168
|
+
statusCodes: {
|
|
169
|
+
notFoundRoute: '/__ultramodern-smoke-missing',
|
|
170
|
+
unknownRouteStatus: 404,
|
|
171
|
+
},
|
|
172
|
+
indexing: {
|
|
173
|
+
previewNoindex: true,
|
|
174
|
+
productionPublicRoutesIndexable: true,
|
|
175
|
+
},
|
|
176
|
+
assets: {
|
|
177
|
+
cssPreloadRequired: true,
|
|
178
|
+
cssResponseRequired: true,
|
|
179
|
+
cacheControlRequiredForCss: true,
|
|
180
|
+
sourcemapsPubliclyReferenced: false,
|
|
181
|
+
},
|
|
182
|
+
budgets: {
|
|
183
|
+
ssrHtmlMaxBytes: 250_000,
|
|
184
|
+
mfManifestMaxBytes: 500_000,
|
|
185
|
+
localeJsonMaxBytes: 100_000,
|
|
186
|
+
sitemapXmlMaxBytes: 500_000,
|
|
187
|
+
cssAssetMaxBytes: 750_000,
|
|
188
|
+
},
|
|
189
|
+
csp: {
|
|
190
|
+
finalMode: 'report-only-dogfood',
|
|
191
|
+
decision:
|
|
192
|
+
'Report-only remains the generated final mode until public smoke proof records MF SSR script/style/connect compatibility for the deployed surface.',
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function createPublicHead() {
|
|
198
|
+
return {
|
|
199
|
+
alternates: {
|
|
200
|
+
hreflang: ['en', 'cs'],
|
|
201
|
+
xDefault: 'en',
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function createPublicSurface() {
|
|
207
|
+
return {
|
|
208
|
+
publicRoutes: [],
|
|
209
|
+
routeEntries: [],
|
|
210
|
+
contentSources: [],
|
|
211
|
+
concreteUrlPaths: [],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function createCloudflareRoutes(app) {
|
|
216
|
+
return {
|
|
217
|
+
ssr: '/en',
|
|
218
|
+
mfManifest: '/mf-manifest.json',
|
|
219
|
+
locale: `/locales/en/${appNamespace(app)}.json`,
|
|
220
|
+
...(app.effectApi
|
|
221
|
+
? {
|
|
222
|
+
effectReadiness: `${app.effectApi.prefix}/effect/${app.effectApi.stem}/readiness`,
|
|
223
|
+
}
|
|
224
|
+
: {}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function createContractApp(config, app) {
|
|
229
|
+
const packageScope =
|
|
230
|
+
typeof config.workspace?.packageScope === 'string'
|
|
231
|
+
? config.workspace.packageScope
|
|
232
|
+
: path.basename(workspaceRoot);
|
|
233
|
+
const compatibilityDate =
|
|
234
|
+
typeof config.deploy?.worker?.compatibilityDate === 'string'
|
|
235
|
+
? config.deploy.worker.compatibilityDate
|
|
236
|
+
: '2026-06-02';
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
id: app.id,
|
|
240
|
+
deploy: {
|
|
241
|
+
cloudflare: {
|
|
242
|
+
workerName: `${toKebabCase(packageScope)}-${app.packageSuffix}`.slice(
|
|
243
|
+
0,
|
|
244
|
+
63,
|
|
245
|
+
),
|
|
246
|
+
publicUrlEnv: `ULTRAMODERN_PUBLIC_URL_${toEnvSegment(app.id)}`,
|
|
247
|
+
compatibilityDate,
|
|
248
|
+
compatibilityFlags: ['nodejs_compat', 'global_fetch_strictly_public'],
|
|
249
|
+
routes: createCloudflareRoutes(app),
|
|
250
|
+
security: createCloudflareSecurity(),
|
|
251
|
+
qualityGates: createQualityGates(),
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
i18n: {
|
|
255
|
+
namespace: appNamespace(app),
|
|
256
|
+
},
|
|
257
|
+
marker: {
|
|
258
|
+
appId: app.id,
|
|
259
|
+
build: createBuildMarker(packageScope, app),
|
|
260
|
+
},
|
|
261
|
+
routes: {
|
|
262
|
+
publicHead: createPublicHead(),
|
|
263
|
+
publicSurface: createPublicSurface(),
|
|
264
|
+
},
|
|
265
|
+
styling: {
|
|
266
|
+
federation: {
|
|
267
|
+
rootSelector: `[data-app-id="${app.id}"]`,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function synthesizeContractFromCompactConfig(config) {
|
|
274
|
+
const apps = Array.isArray(config.topology?.apps)
|
|
275
|
+
? config.topology.apps.map(normalizeCompactApp)
|
|
276
|
+
: [];
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
sourcePath: compactConfigPath,
|
|
280
|
+
apps: apps.map(app => createContractApp(config, app)),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function readGeneratedContractView() {
|
|
285
|
+
if (fs.existsSync(compactConfigPath)) {
|
|
286
|
+
return synthesizeContractFromCompactConfig(readJson(compactConfigPath));
|
|
287
|
+
}
|
|
288
|
+
if (fs.existsSync(contractPath)) {
|
|
289
|
+
return {
|
|
290
|
+
sourcePath: contractPath,
|
|
291
|
+
...readJson(contractPath),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Missing UltraModern config. Expected ${path.relative(
|
|
296
|
+
workspaceRoot,
|
|
297
|
+
compactConfigPath,
|
|
298
|
+
)} or ${path.relative(workspaceRoot, contractPath)}.`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
24
302
|
function parseArgs(argv) {
|
|
25
303
|
const parsed = {
|
|
26
304
|
appId: undefined,
|
|
@@ -70,7 +348,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
70
348
|
return 0;
|
|
71
349
|
}
|
|
72
350
|
|
|
73
|
-
const contract =
|
|
351
|
+
const contract = readGeneratedContractView();
|
|
74
352
|
const apps = args.appId
|
|
75
353
|
? contract.apps.filter(app => app.id === args.appId)
|
|
76
354
|
: contract.apps;
|
|
@@ -101,7 +379,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
101
379
|
schemaVersion: 1,
|
|
102
380
|
generatedAt: new Date().toISOString(),
|
|
103
381
|
status: results.length > 0 ? 'pass' : 'skipped',
|
|
104
|
-
contractPath,
|
|
382
|
+
contractPath: contract.sourcePath ?? contractPath,
|
|
105
383
|
results,
|
|
106
384
|
skipped,
|
|
107
385
|
};
|