@bleedingdev/modern-js-create 3.4.0-ultramodern.9 → 3.5.0-ultramodern.0
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 +25 -23
- 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 +87 -75
- package/dist/cjs/ultramodern-workspace/{effect-api.cjs → api.cjs} +207 -208
- 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 +127 -657
- package/dist/cjs/ultramodern-workspace/demo-components.cjs +12 -12
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +25 -25
- package/dist/cjs/ultramodern-workspace/fs-io.cjs +117 -0
- package/dist/cjs/ultramodern-workspace/generation-result.cjs +5 -5
- 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 +33 -12
- package/dist/cjs/ultramodern-workspace/overlays.cjs +2 -2
- package/dist/cjs/ultramodern-workspace/package-json.cjs +57 -41
- package/dist/cjs/ultramodern-workspace/policy.cjs +4 -4
- package/dist/cjs/ultramodern-workspace/public-api.cjs +4 -0
- package/dist/cjs/ultramodern-workspace/public-surface.cjs +2 -2
- package/dist/cjs/ultramodern-workspace/versions.cjs +5 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +119 -50
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +62 -21
- 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 +88 -79
- package/dist/esm/ultramodern-workspace/{effect-api.js → api.js} +146 -147
- 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 +129 -611
- package/dist/esm/ultramodern-workspace/demo-components.js +13 -13
- package/dist/esm/ultramodern-workspace/descriptors.js +11 -11
- package/dist/esm/ultramodern-workspace/fs-io.js +116 -2
- package/dist/esm/ultramodern-workspace/generation-result.js +6 -6
- 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 +34 -13
- package/dist/esm/ultramodern-workspace/overlays.js +2 -2
- package/dist/esm/ultramodern-workspace/package-json.js +56 -40
- package/dist/esm/ultramodern-workspace/policy.js +5 -5
- package/dist/esm/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm/ultramodern-workspace/public-surface.js +2 -2
- package/dist/esm/ultramodern-workspace/versions.js +3 -1
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +107 -34
- package/dist/esm/ultramodern-workspace/write-workspace.js +64 -26
- 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 +88 -79
- package/dist/esm-node/ultramodern-workspace/{effect-api.js → api.js} +146 -147
- 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 +129 -611
- package/dist/esm-node/ultramodern-workspace/demo-components.js +13 -13
- package/dist/esm-node/ultramodern-workspace/descriptors.js +11 -11
- package/dist/esm-node/ultramodern-workspace/fs-io.js +116 -2
- package/dist/esm-node/ultramodern-workspace/generation-result.js +6 -6
- 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 +34 -13
- package/dist/esm-node/ultramodern-workspace/overlays.js +2 -2
- package/dist/esm-node/ultramodern-workspace/package-json.js +56 -40
- package/dist/esm-node/ultramodern-workspace/policy.js +5 -5
- package/dist/esm-node/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm-node/ultramodern-workspace/public-surface.js +2 -2
- package/dist/esm-node/ultramodern-workspace/versions.js +3 -1
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +107 -34
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +64 -26
- 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/api.d.ts +73 -0
- 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 +10 -10
- 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 +5 -4
- package/dist/types/ultramodern-workspace/public-api.d.ts +2 -0
- package/dist/types/ultramodern-workspace/types.d.ts +9 -7
- 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/.github/workflows/ultramodern-workspace-gates.yml.handlebars +2 -0
- 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 +4 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +77 -67
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +51 -8
- package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +344 -0
- package/templates/workspace-scripts/{check-ultramodern-i18n-boundaries.mjs → check-ultramodern-i18n-boundaries.mts} +1 -1
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +246 -9
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +277 -9
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +35 -10
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +165 -2
- package/templates/workspace-scripts/ultramodern-typecheck.mjs +4 -2
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +840 -84
- package/dist/types/ultramodern-workspace/effect-api.d.ts +0 -73
- /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,17 +1,15 @@
|
|
|
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
|
-
|
|
10
|
-
);
|
|
11
|
-
const contractPath = path.join(
|
|
12
|
-
workspaceRoot,
|
|
13
|
-
'.modernjs/ultramodern-generated-contract.json',
|
|
9
|
+
process.env.ULTRAMODERN_WORKSPACE_ROOT ??
|
|
10
|
+
path.join(path.dirname(fileURLToPath(import.meta.url)), '..'),
|
|
14
11
|
);
|
|
12
|
+
const compactConfigPath = path.join(workspaceRoot, '.modernjs/ultramodern.json');
|
|
15
13
|
const defaultOut = path.join(
|
|
16
14
|
workspaceRoot,
|
|
17
15
|
'.codex/reports/cloudflare-version-proof/public-url-proof.json',
|
|
@@ -21,6 +19,276 @@ function readJson(filePath) {
|
|
|
21
19
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
22
20
|
}
|
|
23
21
|
|
|
22
|
+
function toKebabCase(value) {
|
|
23
|
+
return String(value)
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/([a-z0-9])([A-Z])/gu, '$1-$2')
|
|
26
|
+
.replace(/[^a-zA-Z0-9._-]+/gu, '-')
|
|
27
|
+
.replace(/[._]+/gu, '-')
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/-+/gu, '-')
|
|
30
|
+
.replace(/^-+|-+$/gu, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toPascalCase(value) {
|
|
34
|
+
return toKebabCase(value)
|
|
35
|
+
.split('-')
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
38
|
+
.join('');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function toEnvSegment(value) {
|
|
42
|
+
return toKebabCase(value).replace(/-/gu, '_').toUpperCase();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeRelativePath(value) {
|
|
46
|
+
return String(value ?? '').replace(/\\/gu, '/').replace(/^\.\/+/u, '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function appNamespace(app) {
|
|
50
|
+
return app.kind === 'shell' ? 'shell' : (app.domain ?? app.id);
|
|
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
|
+
const api =
|
|
77
|
+
rawApp.api && typeof rawApp.api === 'object'
|
|
78
|
+
? {
|
|
79
|
+
stem:
|
|
80
|
+
typeof rawApp.api.stem === 'string'
|
|
81
|
+
? rawApp.api.stem
|
|
82
|
+
: domain ?? id,
|
|
83
|
+
prefix:
|
|
84
|
+
typeof rawApp.api.prefix === 'string'
|
|
85
|
+
? rawApp.api.prefix
|
|
86
|
+
: `/${domain ?? id}-api`,
|
|
87
|
+
}
|
|
88
|
+
: undefined;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
id,
|
|
92
|
+
kind,
|
|
93
|
+
path: appPath,
|
|
94
|
+
packageSuffix,
|
|
95
|
+
domain,
|
|
96
|
+
mfName:
|
|
97
|
+
typeof moduleFederation.name === 'string'
|
|
98
|
+
? moduleFederation.name
|
|
99
|
+
: kind === 'shell'
|
|
100
|
+
? 'shellSuperApp'
|
|
101
|
+
: `vertical${toPascalCase(domain ?? id)}`,
|
|
102
|
+
api,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createBuildMarker(scope, app) {
|
|
107
|
+
return crypto
|
|
108
|
+
.createHash('sha256')
|
|
109
|
+
.update(`${scope}:${app.packageSuffix}:${app.id}:0.1.0`)
|
|
110
|
+
.digest('hex')
|
|
111
|
+
.slice(0, 16);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createCloudflareSecurity() {
|
|
115
|
+
return {
|
|
116
|
+
enabled: true,
|
|
117
|
+
headers: {
|
|
118
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
119
|
+
contentTypeOptions: 'nosniff',
|
|
120
|
+
permissionsPolicy:
|
|
121
|
+
'camera=(), geolocation=(), microphone=(), payment=(), usb=()',
|
|
122
|
+
},
|
|
123
|
+
contentSecurityPolicy: {
|
|
124
|
+
mode: 'report-only',
|
|
125
|
+
directives: {
|
|
126
|
+
'base-uri': [`'self'`],
|
|
127
|
+
'connect-src': [`'self'`, 'https:', 'http:', 'wss:', 'ws:'],
|
|
128
|
+
'default-src': [`'self'`],
|
|
129
|
+
'font-src': [`'self'`, 'data:', 'https:', 'http:'],
|
|
130
|
+
'form-action': [`'self'`],
|
|
131
|
+
'frame-ancestors': [`'self'`],
|
|
132
|
+
'img-src': [`'self'`, 'data:', 'blob:', 'https:', 'http:'],
|
|
133
|
+
'manifest-src': [`'self'`, 'https:', 'http:'],
|
|
134
|
+
'object-src': [`'none'`],
|
|
135
|
+
'script-src': [
|
|
136
|
+
`'self'`,
|
|
137
|
+
`'unsafe-inline'`,
|
|
138
|
+
`'unsafe-eval'`,
|
|
139
|
+
'https:',
|
|
140
|
+
'http:',
|
|
141
|
+
'blob:',
|
|
142
|
+
],
|
|
143
|
+
'style-src': [`'self'`, `'unsafe-inline'`, 'https:', 'http:'],
|
|
144
|
+
'worker-src': [`'self'`, 'blob:'],
|
|
145
|
+
},
|
|
146
|
+
reason:
|
|
147
|
+
'Report-only by default so Cloudflare Module Federation SSR can prove remote script, style, and connect compatibility before enforcement.',
|
|
148
|
+
},
|
|
149
|
+
noindex: {
|
|
150
|
+
workersDev: true,
|
|
151
|
+
localhost: true,
|
|
152
|
+
previewHostnames: [],
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function createQualityGates() {
|
|
158
|
+
return {
|
|
159
|
+
publicRoutes: {
|
|
160
|
+
requireSitemapWhenPresent: true,
|
|
161
|
+
requireRobotsSitemapConsistency: true,
|
|
162
|
+
requireWebManifestWhenPresent: true,
|
|
163
|
+
},
|
|
164
|
+
statusCodes: {
|
|
165
|
+
notFoundRoute: '/__ultramodern-smoke-missing/nope',
|
|
166
|
+
unknownRouteStatus: 404,
|
|
167
|
+
},
|
|
168
|
+
indexing: {
|
|
169
|
+
previewNoindex: true,
|
|
170
|
+
productionPublicRoutesIndexable: true,
|
|
171
|
+
},
|
|
172
|
+
assets: {
|
|
173
|
+
cssPreloadRequired: true,
|
|
174
|
+
cssResponseRequired: true,
|
|
175
|
+
cacheControlRequiredForCss: true,
|
|
176
|
+
sourcemapsPubliclyReferenced: false,
|
|
177
|
+
},
|
|
178
|
+
budgets: {
|
|
179
|
+
ssrHtmlMaxBytes: 250_000,
|
|
180
|
+
mfManifestMaxBytes: 500_000,
|
|
181
|
+
localeJsonMaxBytes: 100_000,
|
|
182
|
+
sitemapXmlMaxBytes: 500_000,
|
|
183
|
+
cssAssetMaxBytes: 750_000,
|
|
184
|
+
},
|
|
185
|
+
csp: {
|
|
186
|
+
finalMode: 'report-only-dogfood',
|
|
187
|
+
decision:
|
|
188
|
+
'Report-only remains the generated final mode until public smoke proof records MF SSR script/style/connect compatibility for the deployed surface.',
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function createPublicHead() {
|
|
194
|
+
return {
|
|
195
|
+
alternates: {
|
|
196
|
+
hreflang: ['en', 'cs'],
|
|
197
|
+
xDefault: 'en',
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function createPublicSurface() {
|
|
203
|
+
return {
|
|
204
|
+
publicRoutes: [],
|
|
205
|
+
routeEntries: [],
|
|
206
|
+
contentSources: [],
|
|
207
|
+
concreteUrlPaths: [],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function createCloudflareRoutes(app) {
|
|
212
|
+
return {
|
|
213
|
+
ssr: '/en',
|
|
214
|
+
mfManifest: '/mf-manifest.json',
|
|
215
|
+
locale: `/locales/en/${appNamespace(app)}.json`,
|
|
216
|
+
...(app.api
|
|
217
|
+
? {
|
|
218
|
+
apiReadiness: `${app.api.prefix}/${app.api.stem}/readiness`,
|
|
219
|
+
}
|
|
220
|
+
: {}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function createContractApp(config, app) {
|
|
225
|
+
const packageScope =
|
|
226
|
+
typeof config.workspace?.packageScope === 'string'
|
|
227
|
+
? config.workspace.packageScope
|
|
228
|
+
: path.basename(workspaceRoot);
|
|
229
|
+
const compatibilityDate =
|
|
230
|
+
typeof config.deploy?.worker?.compatibilityDate === 'string'
|
|
231
|
+
? config.deploy.worker.compatibilityDate
|
|
232
|
+
: '2026-06-02';
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
id: app.id,
|
|
236
|
+
deploy: {
|
|
237
|
+
cloudflare: {
|
|
238
|
+
workerName: `${toKebabCase(packageScope)}-${app.packageSuffix}`.slice(
|
|
239
|
+
0,
|
|
240
|
+
63,
|
|
241
|
+
),
|
|
242
|
+
publicUrlEnv: `ULTRAMODERN_PUBLIC_URL_${toEnvSegment(app.id)}`,
|
|
243
|
+
compatibilityDate,
|
|
244
|
+
compatibilityFlags: ['nodejs_compat', 'global_fetch_strictly_public'],
|
|
245
|
+
routes: createCloudflareRoutes(app),
|
|
246
|
+
security: createCloudflareSecurity(),
|
|
247
|
+
qualityGates: createQualityGates(),
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
i18n: {
|
|
251
|
+
namespace: appNamespace(app),
|
|
252
|
+
},
|
|
253
|
+
marker: {
|
|
254
|
+
appId: app.id,
|
|
255
|
+
build: createBuildMarker(packageScope, app),
|
|
256
|
+
},
|
|
257
|
+
routes: {
|
|
258
|
+
publicHead: createPublicHead(),
|
|
259
|
+
publicSurface: createPublicSurface(),
|
|
260
|
+
},
|
|
261
|
+
styling: {
|
|
262
|
+
federation: {
|
|
263
|
+
rootSelector: `[data-app-id="${app.id}"]`,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function synthesizeContractFromCompactConfig(config) {
|
|
270
|
+
const apps = Array.isArray(config.topology?.apps)
|
|
271
|
+
? config.topology.apps.map(normalizeCompactApp)
|
|
272
|
+
: [];
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
sourcePath: compactConfigPath,
|
|
276
|
+
apps: apps.map(app => createContractApp(config, app)),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function readGeneratedContractView() {
|
|
281
|
+
if (fs.existsSync(compactConfigPath)) {
|
|
282
|
+
return synthesizeContractFromCompactConfig(readJson(compactConfigPath));
|
|
283
|
+
}
|
|
284
|
+
throw new Error(
|
|
285
|
+
`Missing UltraModern config. Expected ${path.relative(
|
|
286
|
+
workspaceRoot,
|
|
287
|
+
compactConfigPath,
|
|
288
|
+
)}.`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
24
292
|
function parseArgs(argv) {
|
|
25
293
|
const parsed = {
|
|
26
294
|
appId: undefined,
|
|
@@ -50,7 +318,7 @@ function parseArgs(argv) {
|
|
|
50
318
|
|
|
51
319
|
function printHelp() {
|
|
52
320
|
process.stdout.write(`Usage:
|
|
53
|
-
node scripts/proof-cloudflare-version.
|
|
321
|
+
node scripts/proof-cloudflare-version.mts [--app workspace] [--out evidence.json] [--require-public-urls]
|
|
54
322
|
|
|
55
323
|
Set each app's public URL using the contract env key, for example:
|
|
56
324
|
ULTRAMODERN_PUBLIC_URL_WORKSPACE=https://workspace.example.workers.dev
|
|
@@ -70,7 +338,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
70
338
|
return 0;
|
|
71
339
|
}
|
|
72
340
|
|
|
73
|
-
const contract =
|
|
341
|
+
const contract = readGeneratedContractView();
|
|
74
342
|
const apps = args.appId
|
|
75
343
|
? contract.apps.filter(app => app.id === args.appId)
|
|
76
344
|
: contract.apps;
|
|
@@ -101,7 +369,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
101
369
|
schemaVersion: 1,
|
|
102
370
|
generatedAt: new Date().toISOString(),
|
|
103
371
|
status: results.length > 0 ? 'pass' : 'skipped',
|
|
104
|
-
contractPath,
|
|
372
|
+
contractPath: contract.sourcePath ?? compactConfigPath,
|
|
105
373
|
results,
|
|
106
374
|
skipped,
|
|
107
375
|
};
|
|
@@ -6,6 +6,20 @@ function normalizeUrlWithTrailingSlash(url) {
|
|
|
6
6
|
return url.endsWith('/') ? url : `${url}/`;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function resolveModuleFederationPublicPath(publicPath, manifestUrl) {
|
|
10
|
+
if (typeof publicPath !== 'string' || publicPath.trim().length === 0) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return normalizeUrlWithTrailingSlash(
|
|
16
|
+
new URL(publicPath.trim(), manifestUrl).toString(),
|
|
17
|
+
);
|
|
18
|
+
} catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
async function fetchText(url) {
|
|
10
24
|
const response = await fetch(url);
|
|
11
25
|
return {
|
|
@@ -464,7 +478,8 @@ async function validateSsrHead(evidence, app, publicUrl, ssrRoute, ssr) {
|
|
|
464
478
|
async function validateNotFound(evidence, app, publicUrl) {
|
|
465
479
|
const qualityGates = app.deploy?.cloudflare?.qualityGates ?? {};
|
|
466
480
|
const notFoundRoute =
|
|
467
|
-
qualityGates.statusCodes?.notFoundRoute ??
|
|
481
|
+
qualityGates.statusCodes?.notFoundRoute ??
|
|
482
|
+
'/__ultramodern-smoke-missing/nope';
|
|
468
483
|
const expectedStatus = qualityGates.statusCodes?.unknownRouteStatus ?? 404;
|
|
469
484
|
const response = await fetchText(joinUrl(publicUrl, notFoundRoute));
|
|
470
485
|
evidence.assertions.push({
|
|
@@ -721,7 +736,8 @@ async function validateModuleFederationManifestEvidence(evidence, app, publicUrl
|
|
|
721
736
|
const budgets = qualityGates.budgets ?? {};
|
|
722
737
|
|
|
723
738
|
const manifestRoute = routes.mfManifest ?? '/mf-manifest.json';
|
|
724
|
-
const
|
|
739
|
+
const manifestUrl = joinUrl(publicUrl, manifestRoute);
|
|
740
|
+
const manifest = await fetchText(manifestUrl);
|
|
725
741
|
const manifestJson = parseMaybeJson(manifest.body);
|
|
726
742
|
evidence.assertions.push({
|
|
727
743
|
type: 'mf-manifest',
|
|
@@ -754,17 +770,26 @@ async function validateModuleFederationManifestEvidence(evidence, app, publicUrl
|
|
|
754
770
|
manifest.accessControlAllowOrigin === '*',
|
|
755
771
|
`${app.id} MF manifest is missing Cloudflare CORS headers`,
|
|
756
772
|
);
|
|
757
|
-
const expectedPublicPath = normalizeUrlWithTrailingSlash(
|
|
773
|
+
const expectedPublicPath = normalizeUrlWithTrailingSlash(
|
|
774
|
+
new URL('.', manifestUrl).toString(),
|
|
775
|
+
);
|
|
758
776
|
const manifestPublicPath = manifestJson?.metaData?.publicPath;
|
|
777
|
+
const resolvedPublicPath = resolveModuleFederationPublicPath(
|
|
778
|
+
manifestPublicPath,
|
|
779
|
+
manifestUrl,
|
|
780
|
+
);
|
|
759
781
|
evidence.assertions.push({
|
|
760
782
|
type: 'mf-manifest-public-path',
|
|
761
783
|
expected: expectedPublicPath,
|
|
762
784
|
actual: manifestPublicPath,
|
|
763
|
-
|
|
785
|
+
resolved: resolvedPublicPath,
|
|
786
|
+
status: resolvedPublicPath === expectedPublicPath ? 'pass' : 'fail',
|
|
764
787
|
});
|
|
765
788
|
assert(
|
|
766
|
-
|
|
767
|
-
`${app.id} MF manifest publicPath must resolve remote assets from ${expectedPublicPath}
|
|
789
|
+
resolvedPublicPath === expectedPublicPath,
|
|
790
|
+
`${app.id} MF manifest publicPath must resolve remote assets from ${expectedPublicPath}; got ${String(
|
|
791
|
+
manifestPublicPath,
|
|
792
|
+
)}`,
|
|
768
793
|
);
|
|
769
794
|
}
|
|
770
795
|
|
|
@@ -815,13 +840,13 @@ async function validateI18nEvidence(evidence, app, publicUrl, routes) {
|
|
|
815
840
|
}
|
|
816
841
|
|
|
817
842
|
async function validateReadinessEvidence(evidence, app, publicUrl, routes) {
|
|
818
|
-
if (routes.
|
|
819
|
-
const readiness = await fetchText(joinUrl(publicUrl, routes.
|
|
843
|
+
if (routes.apiReadiness) {
|
|
844
|
+
const readiness = await fetchText(joinUrl(publicUrl, routes.apiReadiness));
|
|
820
845
|
const readinessJson = parseMaybeJson(readiness.body);
|
|
821
846
|
const apiMarker = markerFromJson(readinessJson);
|
|
822
847
|
evidence.assertions.push({
|
|
823
848
|
type: 'api-marker',
|
|
824
|
-
route: routes.
|
|
849
|
+
route: routes.apiReadiness,
|
|
825
850
|
expected: app.marker?.build,
|
|
826
851
|
actual: apiMarker,
|
|
827
852
|
status: readiness.ok && apiMarker === app.marker?.build ? 'pass' : 'fail',
|
|
@@ -848,4 +873,4 @@ async function validateApp(app, publicUrl) {
|
|
|
848
873
|
return evidence;
|
|
849
874
|
}
|
|
850
875
|
|
|
851
|
-
export { validateApp };
|
|
876
|
+
export { resolveModuleFederationPublicPath, validateApp };
|
|
@@ -6,6 +6,7 @@ const root = process.cwd();
|
|
|
6
6
|
const defaultReportPath =
|
|
7
7
|
'.codex/reports/performance-readiness/ultramodern-performance-readiness.json';
|
|
8
8
|
const configPath = 'scripts/ultramodern-performance-readiness.config.mjs';
|
|
9
|
+
const compactConfigPath = '.modernjs/ultramodern.json';
|
|
9
10
|
const optOutEnv = 'ULTRAMODERN_PERFORMANCE_READINESS_DIAGNOSTICS';
|
|
10
11
|
const signalIds = [
|
|
11
12
|
'bfcache',
|
|
@@ -60,6 +61,168 @@ const createSignal = (id, status, evidence) => ({
|
|
|
60
61
|
|
|
61
62
|
const unique = values => new Set(values).size === values.length;
|
|
62
63
|
|
|
64
|
+
const toKebabCase = value =>
|
|
65
|
+
String(value)
|
|
66
|
+
.trim()
|
|
67
|
+
.replace(/([a-z0-9])([A-Z])/gu, '$1-$2')
|
|
68
|
+
.replace(/[^a-zA-Z0-9._-]+/gu, '-')
|
|
69
|
+
.replace(/[._]+/gu, '-')
|
|
70
|
+
.toLowerCase()
|
|
71
|
+
.replace(/-+/gu, '-')
|
|
72
|
+
.replace(/^-+|-+$/gu, '');
|
|
73
|
+
|
|
74
|
+
const toPascalCase = value =>
|
|
75
|
+
toKebabCase(value)
|
|
76
|
+
.split('-')
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
79
|
+
.join('');
|
|
80
|
+
|
|
81
|
+
const normalizeRelativePath = value =>
|
|
82
|
+
String(value ?? '').replace(/\\/gu, '/').replace(/^\.\/+/u, '');
|
|
83
|
+
|
|
84
|
+
const appNamespace = app => (app.kind === 'shell' ? 'shell' : (app.domain ?? app.id));
|
|
85
|
+
|
|
86
|
+
const normalizeCompactApp = rawApp => {
|
|
87
|
+
const id = String(rawApp.id);
|
|
88
|
+
const kind = rawApp.kind === 'vertical' ? 'vertical' : 'shell';
|
|
89
|
+
const appPath =
|
|
90
|
+
typeof rawApp.path === 'string'
|
|
91
|
+
? normalizeRelativePath(rawApp.path)
|
|
92
|
+
: kind === 'shell'
|
|
93
|
+
? 'apps/shell-super-app'
|
|
94
|
+
: `verticals/${toKebabCase(id)}`;
|
|
95
|
+
const packageSuffix =
|
|
96
|
+
typeof rawApp.packageSuffix === 'string'
|
|
97
|
+
? rawApp.packageSuffix
|
|
98
|
+
: appPath.split('/').at(-1) ?? id;
|
|
99
|
+
const domain =
|
|
100
|
+
typeof rawApp.domain === 'string'
|
|
101
|
+
? rawApp.domain
|
|
102
|
+
: kind === 'vertical'
|
|
103
|
+
? packageSuffix
|
|
104
|
+
: undefined;
|
|
105
|
+
const moduleFederation =
|
|
106
|
+
rawApp.moduleFederation && typeof rawApp.moduleFederation === 'object'
|
|
107
|
+
? rawApp.moduleFederation
|
|
108
|
+
: {};
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
id,
|
|
112
|
+
kind,
|
|
113
|
+
path: appPath,
|
|
114
|
+
packageSuffix,
|
|
115
|
+
domain,
|
|
116
|
+
port:
|
|
117
|
+
typeof rawApp.port === 'number'
|
|
118
|
+
? rawApp.port
|
|
119
|
+
: kind === 'shell'
|
|
120
|
+
? 3020
|
|
121
|
+
: 3030,
|
|
122
|
+
mfName:
|
|
123
|
+
typeof moduleFederation.name === 'string'
|
|
124
|
+
? moduleFederation.name
|
|
125
|
+
: kind === 'shell'
|
|
126
|
+
? 'shellSuperApp'
|
|
127
|
+
: `vertical${toPascalCase(domain ?? id)}`,
|
|
128
|
+
moduleFederation: {
|
|
129
|
+
remotes: [],
|
|
130
|
+
verticalRefs: Array.isArray(moduleFederation.verticalRefs)
|
|
131
|
+
? moduleFederation.verticalRefs.filter(ref => typeof ref === 'string')
|
|
132
|
+
: [],
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const createRemoteContracts = (app, apps) =>
|
|
138
|
+
(app.moduleFederation?.verticalRefs ?? [])
|
|
139
|
+
.map(ref => apps.find(candidate => candidate.id === ref))
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.map(remote => ({
|
|
142
|
+
id: remote.id,
|
|
143
|
+
name: remote.mfName,
|
|
144
|
+
manifestUrl: `http://localhost:${remote.port ?? 3030}/mf-manifest.json`,
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
const createPerformanceReadinessContract = () => ({
|
|
148
|
+
schemaVersion: 1,
|
|
149
|
+
default: 'enabled',
|
|
150
|
+
mode: 'diagnostic',
|
|
151
|
+
scope: 'ultramodern-generated-and-framework-owned',
|
|
152
|
+
report: {
|
|
153
|
+
script: 'scripts/ultramodern-performance-readiness.mts',
|
|
154
|
+
config: 'scripts/ultramodern-performance-readiness.config.mjs',
|
|
155
|
+
defaultPath: defaultReportPath,
|
|
156
|
+
deterministic: true,
|
|
157
|
+
},
|
|
158
|
+
signals: signalIds.map(id => ({ id })),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const createContractApp = (config, app, apps) => {
|
|
162
|
+
const compatibilityDate =
|
|
163
|
+
typeof config.deploy?.worker?.compatibilityDate === 'string'
|
|
164
|
+
? config.deploy.worker.compatibilityDate
|
|
165
|
+
: '2026-06-02';
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
id: app.id,
|
|
169
|
+
path: app.path,
|
|
170
|
+
config: {
|
|
171
|
+
plugins: [
|
|
172
|
+
'appTools',
|
|
173
|
+
'tanstackRouterPlugin',
|
|
174
|
+
'i18nPlugin',
|
|
175
|
+
...(app.kind === 'vertical' ? ['bffPlugin'] : []),
|
|
176
|
+
'moduleFederationPlugin',
|
|
177
|
+
'zephyrRspackPlugin',
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
deploy: {
|
|
181
|
+
cloudflare: {
|
|
182
|
+
compatibilityDate,
|
|
183
|
+
compatibilityFlags: ['nodejs_compat', 'global_fetch_strictly_public'],
|
|
184
|
+
routes: {
|
|
185
|
+
ssr: '/en',
|
|
186
|
+
mfManifest: '/mf-manifest.json',
|
|
187
|
+
locale: `/locales/en/${appNamespace(app)}.json`,
|
|
188
|
+
},
|
|
189
|
+
qualityGates: {
|
|
190
|
+
assets: {
|
|
191
|
+
cacheControlRequiredForCss: true,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
moduleFederation: {
|
|
197
|
+
remotes: createRemoteContracts(app, apps),
|
|
198
|
+
},
|
|
199
|
+
routes: {
|
|
200
|
+
localisedUrls: {},
|
|
201
|
+
publicSurface: {
|
|
202
|
+
artifactLifecycle: 'build-and-deploy-output',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const readGeneratedContractView = () => {
|
|
209
|
+
if (exists(compactConfigPath)) {
|
|
210
|
+
const compactConfig = readJson(compactConfigPath);
|
|
211
|
+
const apps = Array.isArray(compactConfig.topology?.apps)
|
|
212
|
+
? compactConfig.topology.apps.map(normalizeCompactApp)
|
|
213
|
+
: [];
|
|
214
|
+
return {
|
|
215
|
+
sourcePath: compactConfigPath,
|
|
216
|
+
performanceReadiness: createPerformanceReadinessContract(),
|
|
217
|
+
apps: apps.map(app => createContractApp(compactConfig, app, apps)),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Missing UltraModern config. Expected ${compactConfigPath}.`,
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
63
226
|
const appGeneratedFiles = app => [
|
|
64
227
|
`${app.path}/modern.config.ts`,
|
|
65
228
|
`${app.path}/module-federation.config.ts`,
|
|
@@ -181,14 +344,14 @@ const main = async () => {
|
|
|
181
344
|
return;
|
|
182
345
|
}
|
|
183
346
|
|
|
184
|
-
const contract =
|
|
347
|
+
const contract = readGeneratedContractView();
|
|
185
348
|
assert(
|
|
186
349
|
contract.performanceReadiness?.default === 'enabled',
|
|
187
350
|
'Generated contract must keep performance readiness diagnostics default-on',
|
|
188
351
|
);
|
|
189
352
|
assert(
|
|
190
353
|
contract.performanceReadiness?.report?.script ===
|
|
191
|
-
'scripts/ultramodern-performance-readiness.
|
|
354
|
+
'scripts/ultramodern-performance-readiness.mts',
|
|
192
355
|
'Generated contract must point at the performance readiness diagnostics script',
|
|
193
356
|
);
|
|
194
357
|
const contractSignalIds =
|
|
@@ -9,11 +9,13 @@ import {
|
|
|
9
9
|
mkdirSync,
|
|
10
10
|
} from 'node:fs';
|
|
11
11
|
import os from 'node:os';
|
|
12
|
-
import { dirname, join } from 'node:path';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
|
|
15
15
|
const args = process.argv.slice(2);
|
|
16
|
-
const workspaceRoot =
|
|
16
|
+
const workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT
|
|
17
|
+
? resolve(process.env.ULTRAMODERN_WORKSPACE_ROOT)
|
|
18
|
+
: join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
19
|
const cpuCount = Math.max(
|
|
18
20
|
1,
|
|
19
21
|
typeof os.availableParallelism === 'function'
|