@bleedingdev/modern-js-create 3.2.0-ultramodern.2 → 3.2.0-ultramodern.21
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 +12 -0
- package/dist/index.js +676 -198
- package/package.json +5 -2
- package/template/.agents/skills-lock.json +34 -0
- package/template/.github/renovate.json +53 -0
- package/template/.github/workflows/ultramodern-gates.yml.handlebars +26 -4
- package/template/AGENTS.md +27 -0
- package/template/README.md +7 -3
- package/template/api/effect/index.ts.handlebars +7 -45
- package/template/config/public/locales/cs/translation.json +39 -0
- package/template/config/public/locales/en/translation.json +39 -0
- package/template/modern.config.ts.handlebars +44 -23
- package/template/oxfmt.config.ts +8 -0
- package/template/oxlint.config.ts +12 -0
- package/template/package.json.handlebars +56 -27
- package/template/pnpm-workspace.yaml +24 -0
- package/template/rstest.config.mts +7 -0
- package/template/scripts/bootstrap-agent-skills.mjs +95 -0
- package/template/scripts/check-i18n-strings.mjs +83 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +350 -16
- package/template/shared/effect/api.ts.handlebars +1 -2
- package/template/src/modern-app-env.d.ts +2 -0
- package/template/src/modern.runtime.ts.handlebars +17 -3
- package/template/src/routes/[lang]/page.tsx.handlebars +211 -0
- package/template/src/routes/index.css.handlebars +14 -3
- package/template/src/routes/layout.tsx.handlebars +1 -1
- package/template/tests/tsconfig.json +7 -0
- package/template/tests/ultramodern.contract.test.ts +67 -0
- package/template/tsconfig.json +106 -2
- package/template-workspace/.agents/agent-reference-repos.json +24 -0
- package/template-workspace/.agents/rstackjs-agent-skills-LICENSE +21 -0
- package/template-workspace/.agents/skills/rsbuild-best-practices/SKILL.md +57 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/SKILL.md +96 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/command-map.md +113 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/common-analysis-patterns.md +190 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-common.md +88 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-rspack.md +138 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-webpack.md +71 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor.md +39 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/rsdoctor-data-types.md +103 -0
- package/template-workspace/.agents/skills/rslib-best-practices/SKILL.md +58 -0
- package/template-workspace/.agents/skills/rslib-modern-package/SKILL.md +173 -0
- package/template-workspace/.agents/skills/rspack-best-practices/SKILL.md +70 -0
- package/template-workspace/.agents/skills/rspack-tracing/SKILL.md +75 -0
- package/template-workspace/.agents/skills/rspack-tracing/references/bottlenecks.md +47 -0
- package/template-workspace/.agents/skills/rspack-tracing/references/tracing-guide.md +38 -0
- package/template-workspace/.agents/skills/rspack-tracing/scripts/analyze_trace.js +184 -0
- package/template-workspace/.agents/skills/rstest-best-practices/SKILL.md +133 -0
- package/template-workspace/.agents/skills-lock.json +95 -0
- package/template-workspace/.github/renovate.json +29 -0
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +52 -0
- package/template-workspace/.gitignore.handlebars +5 -0
- package/template-workspace/AGENTS.md +61 -0
- package/template-workspace/README.md.handlebars +11 -1
- package/template-workspace/oxfmt.config.ts +16 -0
- package/template-workspace/oxlint.config.ts +19 -0
- package/template-workspace/pnpm-workspace.yaml +24 -6
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +95 -0
- package/template-workspace/scripts/check-i18n-strings.mjs +83 -0
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +364 -0
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +405 -59
- package/template/biome.json +0 -41
- package/template/src/routes/page.tsx.handlebars +0 -119
|
@@ -4,52 +4,99 @@ import path from 'node:path';
|
|
|
4
4
|
const root = process.cwd();
|
|
5
5
|
const packageScope = '{{packageScope}}';
|
|
6
6
|
const tanstackVersion = '1.170.1';
|
|
7
|
+
const rstackAgentSkillsCommit = '61c948b42512e223bad44b83af4080eba48b2677';
|
|
7
8
|
const modernPackages = [
|
|
8
9
|
'@modern-js/app-tools',
|
|
9
10
|
'@modern-js/plugin-bff',
|
|
11
|
+
'@modern-js/plugin-i18n',
|
|
10
12
|
'@modern-js/plugin-tanstack',
|
|
11
13
|
'@modern-js/runtime',
|
|
12
14
|
];
|
|
15
|
+
const baselineAgentSkills = [
|
|
16
|
+
'rsbuild-best-practices',
|
|
17
|
+
'rspack-best-practices',
|
|
18
|
+
'rspack-tracing',
|
|
19
|
+
'rsdoctor-analysis',
|
|
20
|
+
'rslib-best-practices',
|
|
21
|
+
'rslib-modern-package',
|
|
22
|
+
'rstest-best-practices',
|
|
23
|
+
];
|
|
24
|
+
const privateAgentSkills = ['plan-graph', 'dag', 'subagent-graph', 'helm', 'debugger-mode'];
|
|
13
25
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function readJson(relativePath) {
|
|
19
|
-
return JSON.parse(readText(relativePath));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function assert(condition, message) {
|
|
26
|
+
const readText = (relativePath) => fs.readFileSync(path.join(root, relativePath), 'utf-8');
|
|
27
|
+
const readJson = (relativePath) => JSON.parse(readText(relativePath));
|
|
28
|
+
const assert = (condition, message) => {
|
|
23
29
|
if (!condition) {
|
|
24
30
|
throw new Error(message);
|
|
25
31
|
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function assertExists(relativePath) {
|
|
32
|
+
};
|
|
33
|
+
const assertExists = (relativePath) => {
|
|
29
34
|
assert(fs.existsSync(path.join(root, relativePath)), `Missing ${relativePath}`);
|
|
30
|
-
}
|
|
35
|
+
};
|
|
31
36
|
|
|
32
37
|
const requiredPaths = [
|
|
38
|
+
'AGENTS.md',
|
|
39
|
+
'.gitignore',
|
|
33
40
|
'package.json',
|
|
34
41
|
'pnpm-workspace.yaml',
|
|
35
42
|
'tsconfig.base.json',
|
|
43
|
+
'oxlint.config.ts',
|
|
44
|
+
'oxfmt.config.ts',
|
|
45
|
+
'.github/renovate.json',
|
|
46
|
+
'.github/workflows/ultramodern-workspace-gates.yml',
|
|
47
|
+
'.agents/skills-lock.json',
|
|
48
|
+
'.agents/agent-reference-repos.json',
|
|
49
|
+
'.agents/rstackjs-agent-skills-LICENSE',
|
|
50
|
+
'.agents/skills/rsbuild-best-practices/SKILL.md',
|
|
51
|
+
'.agents/skills/rspack-best-practices/SKILL.md',
|
|
52
|
+
'.agents/skills/rspack-tracing/SKILL.md',
|
|
53
|
+
'.agents/skills/rspack-tracing/references/tracing-guide.md',
|
|
54
|
+
'.agents/skills/rspack-tracing/scripts/analyze_trace.js',
|
|
55
|
+
'.agents/skills/rsdoctor-analysis/SKILL.md',
|
|
56
|
+
'.agents/skills/rsdoctor-analysis/references/rsdoctor-data-types.md',
|
|
57
|
+
'.agents/skills/rslib-best-practices/SKILL.md',
|
|
58
|
+
'.agents/skills/rslib-modern-package/SKILL.md',
|
|
59
|
+
'.agents/skills/rstest-best-practices/SKILL.md',
|
|
36
60
|
'topology/reference-topology.json',
|
|
37
61
|
'topology/ownership.json',
|
|
38
62
|
'topology/local-overlays/development.json',
|
|
39
63
|
'.modernjs/ultramodern-workspace-template-manifest.json',
|
|
40
64
|
'.modernjs/ultramodern-package-source.json',
|
|
65
|
+
'scripts/bootstrap-agent-skills.mjs',
|
|
66
|
+
'scripts/setup-agent-reference-repos.mjs',
|
|
67
|
+
'scripts/check-i18n-strings.mjs',
|
|
41
68
|
'apps/shell-super-app/package.json',
|
|
69
|
+
'apps/shell-super-app/config/public/locales/en/translation.json',
|
|
70
|
+
'apps/shell-super-app/config/public/locales/cs/translation.json',
|
|
42
71
|
'apps/shell-super-app/modern.config.ts',
|
|
43
72
|
'apps/shell-super-app/module-federation.config.ts',
|
|
73
|
+
'apps/shell-super-app/src/modern-app-env.d.ts',
|
|
74
|
+
'apps/shell-super-app/src/modern.runtime.ts',
|
|
75
|
+
'apps/shell-super-app/src/routes/[lang]/page.tsx',
|
|
44
76
|
'apps/remotes/remote-commerce/package.json',
|
|
77
|
+
'apps/remotes/remote-commerce/config/public/locales/en/translation.json',
|
|
78
|
+
'apps/remotes/remote-commerce/config/public/locales/cs/translation.json',
|
|
45
79
|
'apps/remotes/remote-commerce/modern.config.ts',
|
|
46
80
|
'apps/remotes/remote-commerce/module-federation.config.ts',
|
|
81
|
+
'apps/remotes/remote-commerce/src/modern-app-env.d.ts',
|
|
82
|
+
'apps/remotes/remote-commerce/src/modern.runtime.ts',
|
|
83
|
+
'apps/remotes/remote-commerce/src/routes/[lang]/page.tsx',
|
|
47
84
|
'apps/remotes/remote-identity/package.json',
|
|
85
|
+
'apps/remotes/remote-identity/config/public/locales/en/translation.json',
|
|
86
|
+
'apps/remotes/remote-identity/config/public/locales/cs/translation.json',
|
|
48
87
|
'apps/remotes/remote-identity/modern.config.ts',
|
|
49
88
|
'apps/remotes/remote-identity/module-federation.config.ts',
|
|
89
|
+
'apps/remotes/remote-identity/src/modern-app-env.d.ts',
|
|
90
|
+
'apps/remotes/remote-identity/src/modern.runtime.ts',
|
|
91
|
+
'apps/remotes/remote-identity/src/routes/[lang]/page.tsx',
|
|
50
92
|
'apps/remotes/remote-design-system/package.json',
|
|
93
|
+
'apps/remotes/remote-design-system/config/public/locales/en/translation.json',
|
|
94
|
+
'apps/remotes/remote-design-system/config/public/locales/cs/translation.json',
|
|
51
95
|
'apps/remotes/remote-design-system/modern.config.ts',
|
|
52
96
|
'apps/remotes/remote-design-system/module-federation.config.ts',
|
|
97
|
+
'apps/remotes/remote-design-system/src/modern-app-env.d.ts',
|
|
98
|
+
'apps/remotes/remote-design-system/src/modern.runtime.ts',
|
|
99
|
+
'apps/remotes/remote-design-system/src/routes/[lang]/page.tsx',
|
|
53
100
|
'services/service-recommendations-effect/package.json',
|
|
54
101
|
'services/service-recommendations-effect/modern.config.ts',
|
|
55
102
|
'services/service-recommendations-effect/api/effect/index.ts',
|
|
@@ -63,14 +110,30 @@ for (const requiredPath of requiredPaths) {
|
|
|
63
110
|
assertExists(requiredPath);
|
|
64
111
|
}
|
|
65
112
|
|
|
113
|
+
for (const appDirectory of [
|
|
114
|
+
'apps/shell-super-app',
|
|
115
|
+
'apps/remotes/remote-commerce',
|
|
116
|
+
'apps/remotes/remote-identity',
|
|
117
|
+
'apps/remotes/remote-design-system',
|
|
118
|
+
]) {
|
|
119
|
+
assert(
|
|
120
|
+
!fs.existsSync(path.join(root, appDirectory, 'src/routes/page.tsx')),
|
|
121
|
+
`${appDirectory} must use src/routes/[lang]/page.tsx`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
66
125
|
const rootPackage = readJson('package.json');
|
|
67
126
|
const packageSource = readJson('.modernjs/ultramodern-package-source.json');
|
|
127
|
+
const skillsLock = readJson('.agents/skills-lock.json');
|
|
128
|
+
const agentReferenceRepos = readJson('.agents/agent-reference-repos.json');
|
|
129
|
+
const pnpmWorkspace = readText('pnpm-workspace.yaml');
|
|
130
|
+
const workflowContent = readText('.github/workflows/ultramodern-workspace-gates.yml');
|
|
131
|
+
const renovateConfig = readJson('.github/renovate.json');
|
|
132
|
+
const deprecatedTanstackRuntime = '@modern-js/runtime/' + 'tanstack-router';
|
|
68
133
|
const expectedModernSpecifier =
|
|
69
|
-
packageSource.strategy === 'install'
|
|
70
|
-
? packageSource.modernPackages?.specifier
|
|
71
|
-
: 'workspace:*';
|
|
134
|
+
packageSource.strategy === 'install' ? packageSource.modernPackages?.specifier : 'workspace:*';
|
|
72
135
|
|
|
73
|
-
|
|
136
|
+
const expectedModernDependency = (packageName) => {
|
|
74
137
|
if (packageSource.strategy !== 'install') {
|
|
75
138
|
return 'workspace:*';
|
|
76
139
|
}
|
|
@@ -79,13 +142,74 @@ function expectedModernDependency(packageName) {
|
|
|
79
142
|
return aliasPackageName
|
|
80
143
|
? `npm:${aliasPackageName}@${expectedModernSpecifier}`
|
|
81
144
|
: expectedModernSpecifier;
|
|
82
|
-
}
|
|
145
|
+
};
|
|
83
146
|
|
|
84
147
|
assert(rootPackage.private === true, 'Root package must be private');
|
|
85
148
|
assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare presetUltramodern');
|
|
149
|
+
assert(rootPackage.packageManager === 'pnpm@11.1.2', 'Root must pin pnpm 11.1.2');
|
|
150
|
+
assert(rootPackage.engines?.pnpm === '>=11.0.0', 'Root must require pnpm >=11');
|
|
151
|
+
for (const requiredSnippet of [
|
|
152
|
+
'packages:\n - apps/*\n - apps/remotes/*\n - services/*\n - packages/*',
|
|
153
|
+
'minimumReleaseAge: 1440',
|
|
154
|
+
'minimumReleaseAgeStrict: true',
|
|
155
|
+
'minimumReleaseAgeIgnoreMissingTime: false',
|
|
156
|
+
"minimumReleaseAgeExclude:\n - '@modern-js/*'\n - '@bleedingdev/*'\n - '@effect/tsgo'\n - '@effect/tsgo-*'\n - '@typescript/native-preview'\n - '@typescript/native-preview-*'",
|
|
157
|
+
'trustPolicy: no-downgrade',
|
|
158
|
+
'trustPolicyIgnoreAfter: 1440',
|
|
159
|
+
'blockExoticSubdeps: true',
|
|
160
|
+
'engineStrict: true',
|
|
161
|
+
'pmOnFail: error',
|
|
162
|
+
'verifyDepsBeforeRun: error',
|
|
163
|
+
'strictDepBuilds: true',
|
|
164
|
+
"allowBuilds:\n '@swc/core': true\n core-js: true\n esbuild: true\n msgpackr-extract: true\n simple-git-hooks: true",
|
|
165
|
+
]) {
|
|
166
|
+
assert(
|
|
167
|
+
pnpmWorkspace.includes(requiredSnippet),
|
|
168
|
+
`pnpm-workspace.yaml must retain ${requiredSnippet.split('\n')[0]}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
assert(
|
|
172
|
+
!pnpmWorkspace.includes('onlyBuiltDependencies'),
|
|
173
|
+
'pnpm-workspace.yaml must use pnpm 11 allowBuilds instead of onlyBuiltDependencies',
|
|
174
|
+
);
|
|
175
|
+
for (const requiredSnippet of [
|
|
176
|
+
'permissions:\n contents: read',
|
|
177
|
+
'pull_request:',
|
|
178
|
+
'persist-credentials: false',
|
|
179
|
+
'pnpm install --frozen-lockfile',
|
|
180
|
+
'pnpm -r --filter "./apps/**" run build',
|
|
181
|
+
'MODERN_PUBLIC_SITE_URL: http://localhost:8080',
|
|
182
|
+
'timeout-minutes:',
|
|
183
|
+
'egress-policy: audit',
|
|
184
|
+
]) {
|
|
185
|
+
assert(
|
|
186
|
+
workflowContent.includes(requiredSnippet),
|
|
187
|
+
`Generated workspace workflow must retain ${requiredSnippet.split('\n')[0]}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
86
190
|
assert(
|
|
87
|
-
|
|
88
|
-
|
|
191
|
+
!workflowContent.includes('pull_request_target'),
|
|
192
|
+
'Generated workspace workflow must not use pull_request_target',
|
|
193
|
+
);
|
|
194
|
+
for (const match of workflowContent.matchAll(/^\s*uses:\s*([^@\s]+)@([^\s#]+)/gmu)) {
|
|
195
|
+
const [, actionName, actionRef] = match;
|
|
196
|
+
assert(
|
|
197
|
+
/^[a-f0-9]{40}$/u.test(actionRef),
|
|
198
|
+
`Generated workspace workflow must pin ${actionName}@${actionRef} to a commit SHA`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
assert(
|
|
202
|
+
renovateConfig.dependencyDashboard === true &&
|
|
203
|
+
renovateConfig.minimumReleaseAge === '1 day' &&
|
|
204
|
+
renovateConfig.extends?.includes('helpers:pinGitHubActionDigests') &&
|
|
205
|
+
renovateConfig.packageRules?.some(
|
|
206
|
+
(rule) =>
|
|
207
|
+
rule.dependencyDashboardApproval === true && rule.matchUpdateTypes?.includes('major'),
|
|
208
|
+
),
|
|
209
|
+
'Generated workspace Renovate config must retain dashboard, release-age, action pinning, and major-approval policy',
|
|
210
|
+
);
|
|
211
|
+
assert(
|
|
212
|
+
rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json',
|
|
89
213
|
'Root must point to the UltraModern package source metadata',
|
|
90
214
|
);
|
|
91
215
|
assert(
|
|
@@ -101,21 +225,122 @@ assert(
|
|
|
101
225
|
'Generated shared packages must keep workspace:* links',
|
|
102
226
|
);
|
|
103
227
|
assert(
|
|
104
|
-
modernPackages.every(packageName =>
|
|
228
|
+
modernPackages.every((packageName) =>
|
|
105
229
|
packageSource.modernPackages?.packages?.includes(packageName),
|
|
106
230
|
),
|
|
107
231
|
'Package source metadata must list all Modern runtime/tooling packages',
|
|
108
232
|
);
|
|
109
233
|
assert(
|
|
110
|
-
expectedModernSpecifier &&
|
|
111
|
-
packageSource.modernPackages?.specifier === expectedModernSpecifier,
|
|
234
|
+
expectedModernSpecifier && packageSource.modernPackages?.specifier === expectedModernSpecifier,
|
|
112
235
|
'Package source metadata must provide a Modern package specifier',
|
|
113
236
|
);
|
|
237
|
+
|
|
238
|
+
const requiredRootScripts = {
|
|
239
|
+
format: 'oxfmt .',
|
|
240
|
+
'format:check': 'oxfmt --check .',
|
|
241
|
+
'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
|
|
242
|
+
lint: 'oxlint .',
|
|
243
|
+
'lint:fix': 'oxlint . --fix',
|
|
244
|
+
'agents:refs:check': 'node ./scripts/setup-agent-reference-repos.mjs --check',
|
|
245
|
+
'agents:refs:install': 'node ./scripts/setup-agent-reference-repos.mjs',
|
|
246
|
+
postinstall: 'node ./scripts/setup-agent-reference-repos.mjs',
|
|
247
|
+
'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
|
|
248
|
+
'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
|
|
249
|
+
typecheck: `pnpm -r --filter "@${packageScope}/*" typecheck`,
|
|
250
|
+
'ultramodern:check': 'node ./scripts/validate-ultramodern-workspace.mjs',
|
|
251
|
+
};
|
|
252
|
+
for (const [scriptName, scriptCommand] of Object.entries(requiredRootScripts)) {
|
|
253
|
+
assert(rootPackage.scripts?.[scriptName] === scriptCommand, `Root must expose ${scriptName}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
for (const dependency of [
|
|
257
|
+
'@effect/tsgo',
|
|
258
|
+
'@typescript/native-preview',
|
|
259
|
+
'oxfmt',
|
|
260
|
+
'oxlint',
|
|
261
|
+
'ultracite',
|
|
262
|
+
]) {
|
|
263
|
+
assert(rootPackage.devDependencies?.[dependency], `Root must depend on ${dependency}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const agentsInstructions = readText('AGENTS.md');
|
|
267
|
+
assert(
|
|
268
|
+
agentsInstructions.includes('UltraModern Agent Contract') &&
|
|
269
|
+
agentsInstructions.includes('Required Skill Baseline'),
|
|
270
|
+
'Root AGENTS.md must document the UltraModern agent contract',
|
|
271
|
+
);
|
|
272
|
+
assert(
|
|
273
|
+
skillsLock.source?.repository === 'https://github.com/rstackjs/agent-skills',
|
|
274
|
+
'Agent skills lock must retain the Rstack skill source repository',
|
|
275
|
+
);
|
|
276
|
+
assert(
|
|
277
|
+
skillsLock.source?.commit === rstackAgentSkillsCommit,
|
|
278
|
+
'Agent skills lock must pin the expected Rstack skill commit',
|
|
279
|
+
);
|
|
280
|
+
assert(
|
|
281
|
+
skillsLock.installDir === '.agents/skills',
|
|
282
|
+
'Agent skills lock must use .agents/skills as installDir',
|
|
283
|
+
);
|
|
284
|
+
assert(
|
|
285
|
+
agentReferenceRepos.defaultEnabled === true,
|
|
286
|
+
'Agent reference repositories must be enabled by default',
|
|
287
|
+
);
|
|
288
|
+
assert(
|
|
289
|
+
agentReferenceRepos.strategy === 'git-subtree-squash',
|
|
290
|
+
'Agent reference repositories must use git subtree squash strategy',
|
|
291
|
+
);
|
|
292
|
+
assert(
|
|
293
|
+
agentReferenceRepos.repositories?.some(
|
|
294
|
+
(repo) =>
|
|
295
|
+
repo.id === 'effect' &&
|
|
296
|
+
repo.url === 'https://github.com/Effect-TS/effect.git' &&
|
|
297
|
+
repo.path === 'repos/effect',
|
|
298
|
+
),
|
|
299
|
+
'Agent reference repositories must include Effect',
|
|
300
|
+
);
|
|
301
|
+
assert(
|
|
302
|
+
agentReferenceRepos.repositories?.some(
|
|
303
|
+
(repo) =>
|
|
304
|
+
repo.id === 'ultramodern-js' &&
|
|
305
|
+
repo.url === 'https://github.com/BleedingDev/ultramodern.js.git' &&
|
|
306
|
+
repo.path === 'repos/ultramodern.js',
|
|
307
|
+
),
|
|
308
|
+
'Agent reference repositories must include UltraModern.js',
|
|
309
|
+
);
|
|
310
|
+
assert(
|
|
311
|
+
readText('scripts/setup-agent-reference-repos.mjs').includes(
|
|
312
|
+
'ULTRAMODERN_SKIP_AGENT_REPOS',
|
|
313
|
+
),
|
|
314
|
+
'Agent reference repository setup must expose an opt-out environment variable',
|
|
315
|
+
);
|
|
114
316
|
assert(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
'Root must expose the ultramodern:check script',
|
|
317
|
+
readText('scripts/setup-agent-reference-repos.mjs').includes('git-subtree-dir'),
|
|
318
|
+
'Agent reference repository setup must validate git subtree evidence',
|
|
118
319
|
);
|
|
320
|
+
for (const skillName of baselineAgentSkills) {
|
|
321
|
+
assert(
|
|
322
|
+
skillsLock.baseline?.some((skill) => skill.name === skillName),
|
|
323
|
+
`Agent skills lock must include ${skillName}`,
|
|
324
|
+
);
|
|
325
|
+
assert(
|
|
326
|
+
readText(`.agents/skills/${skillName}/SKILL.md`).includes(`name: ${skillName}`),
|
|
327
|
+
`${skillName} must contain matching skill metadata`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const privateSource = skillsLock.sources?.find(
|
|
332
|
+
(source) => source.repository === 'https://github.com/TechsioCZ/skills',
|
|
333
|
+
);
|
|
334
|
+
assert(
|
|
335
|
+
privateSource?.install === 'clone-if-authorized',
|
|
336
|
+
'Agent skills lock must configure TechsioCZ skills as clone-if-authorized',
|
|
337
|
+
);
|
|
338
|
+
for (const skillName of privateAgentSkills) {
|
|
339
|
+
assert(
|
|
340
|
+
privateSource.baseline?.some((skill) => skill.name === skillName),
|
|
341
|
+
`Agent skills lock must allowlist private skill ${skillName}`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
119
344
|
|
|
120
345
|
const appPackagePaths = [
|
|
121
346
|
'apps/shell-super-app/package.json',
|
|
@@ -126,31 +351,47 @@ const appPackagePaths = [
|
|
|
126
351
|
|
|
127
352
|
for (const packagePath of appPackagePaths) {
|
|
128
353
|
const packageJson = readJson(packagePath);
|
|
354
|
+
assert(
|
|
355
|
+
packageJson.dependencies?.['@modern-js/plugin-i18n'] ===
|
|
356
|
+
expectedModernDependency('@modern-js/plugin-i18n'),
|
|
357
|
+
`${packagePath} must use @modern-js/plugin-i18n through ${expectedModernDependency(
|
|
358
|
+
'@modern-js/plugin-i18n',
|
|
359
|
+
)}`,
|
|
360
|
+
);
|
|
129
361
|
assert(
|
|
130
362
|
packageJson.dependencies?.['@modern-js/plugin-tanstack'] ===
|
|
131
363
|
expectedModernDependency('@modern-js/plugin-tanstack'),
|
|
132
|
-
`${packagePath} must
|
|
364
|
+
`${packagePath} must use @modern-js/plugin-tanstack through ${expectedModernDependency(
|
|
365
|
+
'@modern-js/plugin-tanstack',
|
|
366
|
+
)}`,
|
|
133
367
|
);
|
|
134
368
|
assert(
|
|
135
369
|
packageJson.dependencies?.['@modern-js/runtime'] ===
|
|
136
370
|
expectedModernDependency('@modern-js/runtime'),
|
|
137
|
-
`${packagePath} must
|
|
371
|
+
`${packagePath} must use @modern-js/runtime through ${expectedModernDependency(
|
|
372
|
+
'@modern-js/runtime',
|
|
373
|
+
)}`,
|
|
138
374
|
);
|
|
139
375
|
assert(
|
|
140
376
|
packageJson.devDependencies?.['@modern-js/app-tools'] ===
|
|
141
377
|
expectedModernDependency('@modern-js/app-tools'),
|
|
142
|
-
`${packagePath} must
|
|
378
|
+
`${packagePath} must use @modern-js/app-tools through ${expectedModernDependency(
|
|
379
|
+
'@modern-js/app-tools',
|
|
380
|
+
)}`,
|
|
143
381
|
);
|
|
144
382
|
assert(
|
|
145
|
-
packageJson.dependencies?.[`@${packageScope}/shared-contracts`] ===
|
|
146
|
-
'workspace:*',
|
|
383
|
+
packageJson.dependencies?.[`@${packageScope}/shared-contracts`] === 'workspace:*',
|
|
147
384
|
`${packagePath} must link generated shared contracts through workspace:*`,
|
|
148
385
|
);
|
|
149
386
|
assert(
|
|
150
|
-
packageJson.dependencies?.[`@${packageScope}/shared-design-tokens`] ===
|
|
151
|
-
'workspace:*',
|
|
387
|
+
packageJson.dependencies?.[`@${packageScope}/shared-design-tokens`] === 'workspace:*',
|
|
152
388
|
`${packagePath} must link generated shared design tokens through workspace:*`,
|
|
153
389
|
);
|
|
390
|
+
assert(packageJson.dependencies?.i18next === '26.2.0', `${packagePath} must include i18next`);
|
|
391
|
+
assert(
|
|
392
|
+
packageJson.dependencies?.['react-i18next'] === '17.0.8',
|
|
393
|
+
`${packagePath} must include react-i18next`,
|
|
394
|
+
);
|
|
154
395
|
assert(
|
|
155
396
|
packageJson.dependencies?.['@tanstack/react-router'] === tanstackVersion,
|
|
156
397
|
`${packagePath} must use @tanstack/react-router ${tanstackVersion}`,
|
|
@@ -159,6 +400,10 @@ for (const packagePath of appPackagePaths) {
|
|
|
159
400
|
packageJson.dependencies?.['@module-federation/modern-js-v3'] === '2.4.0',
|
|
160
401
|
`${packagePath} must include the Module Federation plugin`,
|
|
161
402
|
);
|
|
403
|
+
assert(
|
|
404
|
+
packageJson.dependencies?.['zephyr-modernjs-plugin'] === '1.1.1',
|
|
405
|
+
`${packagePath} must include the official Zephyr Modern.js plugin`,
|
|
406
|
+
);
|
|
162
407
|
assert(
|
|
163
408
|
packageJson.modernjs?.preset === 'presetUltramodern',
|
|
164
409
|
`${packagePath} must keep presetUltramodern metadata`,
|
|
@@ -173,57 +418,133 @@ for (const configPath of [
|
|
|
173
418
|
]) {
|
|
174
419
|
const config = readText(configPath);
|
|
175
420
|
assert(config.includes('presetUltramodern('), `${configPath} must use presetUltramodern`);
|
|
421
|
+
assert(config.includes('i18nPlugin('), `${configPath} must enable plugin-i18n`);
|
|
422
|
+
assert(config.includes('localePathRedirect: true'), `${configPath} must prefix localized URLs`);
|
|
423
|
+
assert(config.includes('ULTRAMODERN_SITE_URL'), `${configPath} must expose site URL metadata`);
|
|
424
|
+
assert(
|
|
425
|
+
config.includes('MODERN_PUBLIC_SITE_URL must be set for production builds'),
|
|
426
|
+
`${configPath} must require MODERN_PUBLIC_SITE_URL for production builds`,
|
|
427
|
+
);
|
|
176
428
|
assert(config.includes('tanstackRouterPlugin()'), `${configPath} must enable plugin-tanstack`);
|
|
177
|
-
assert(
|
|
429
|
+
assert(
|
|
430
|
+
config.includes('moduleFederationPlugin()'),
|
|
431
|
+
`${configPath} must enable Module Federation`,
|
|
432
|
+
);
|
|
433
|
+
assert(
|
|
434
|
+
config.includes("from 'zephyr-modernjs-plugin'"),
|
|
435
|
+
`${configPath} must import the official Zephyr Modern.js plugin`,
|
|
436
|
+
);
|
|
437
|
+
assert(config.includes('withZephyr()'), `${configPath} must enable Zephyr through plugins`);
|
|
438
|
+
assert(
|
|
439
|
+
config.includes("bundler: 'rspack'"),
|
|
440
|
+
`${configPath} must keep appTools on the rspack bundler for Zephyr`,
|
|
441
|
+
);
|
|
442
|
+
assert(
|
|
443
|
+
config.includes("html: './'"),
|
|
444
|
+
`${configPath} must keep Modern.js output.distPath.html compatible with Zephyr`,
|
|
445
|
+
);
|
|
446
|
+
assert(
|
|
447
|
+
config.includes("outputStructure: 'flat'"),
|
|
448
|
+
`${configPath} must keep Modern.js HTML output flat for Zephyr`,
|
|
449
|
+
);
|
|
450
|
+
assert(
|
|
451
|
+
config.includes("mainEntryName: 'index'"),
|
|
452
|
+
`${configPath} must keep Modern.js source.mainEntryName compatible with Zephyr`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
for (const routePath of [
|
|
457
|
+
'apps/shell-super-app/src/routes/[lang]/page.tsx',
|
|
458
|
+
'apps/remotes/remote-commerce/src/routes/[lang]/page.tsx',
|
|
459
|
+
'apps/remotes/remote-identity/src/routes/[lang]/page.tsx',
|
|
460
|
+
'apps/remotes/remote-design-system/src/routes/[lang]/page.tsx',
|
|
461
|
+
]) {
|
|
462
|
+
const route = readText(routePath);
|
|
463
|
+
assert(route.includes('rel="canonical"'), `${routePath} must emit canonical metadata`);
|
|
464
|
+
assert(route.includes('rel="alternate"'), `${routePath} must emit alternate locale metadata`);
|
|
465
|
+
assert(route.includes('hrefLang="x-default"'), `${routePath} must emit x-default metadata`);
|
|
466
|
+
assert(route.includes('localizedPath('), `${routePath} must build localized URLs`);
|
|
467
|
+
assert(
|
|
468
|
+
route.includes('@modern-js/plugin-tanstack/runtime'),
|
|
469
|
+
`${routePath} must import TanStack runtime from @modern-js/plugin-tanstack/runtime`,
|
|
470
|
+
);
|
|
471
|
+
assert(
|
|
472
|
+
!route.includes(deprecatedTanstackRuntime),
|
|
473
|
+
`${routePath} must not import deprecated TanStack runtime path`,
|
|
474
|
+
);
|
|
178
475
|
}
|
|
179
476
|
|
|
180
477
|
const shellMf = readText('apps/shell-super-app/module-federation.config.ts');
|
|
181
478
|
assert(shellMf.includes("name: 'shellSuperApp'"), 'Shell MF config must name the shell');
|
|
182
|
-
assert(
|
|
183
|
-
|
|
184
|
-
|
|
479
|
+
assert(
|
|
480
|
+
shellMf.includes('remoteCommerce@http://localhost:3021/mf-manifest.json'),
|
|
481
|
+
'Shell must reference commerce remote',
|
|
482
|
+
);
|
|
483
|
+
assert(
|
|
484
|
+
shellMf.includes('remoteIdentity@http://localhost:3022/mf-manifest.json'),
|
|
485
|
+
'Shell must reference identity remote',
|
|
486
|
+
);
|
|
487
|
+
assert(
|
|
488
|
+
shellMf.includes('remoteDesignSystem@http://localhost:3023/mf-manifest.json'),
|
|
489
|
+
'Shell must reference design-system remote',
|
|
490
|
+
);
|
|
185
491
|
|
|
186
492
|
const commerceMf = readText('apps/remotes/remote-commerce/module-federation.config.ts');
|
|
187
493
|
assert(commerceMf.includes("name: 'remoteCommerce'"), 'Commerce remote MF name is missing');
|
|
188
|
-
assert(commerceMf.includes('
|
|
494
|
+
assert(commerceMf.includes("'./Widget'"), 'Commerce remote must expose a widget');
|
|
189
495
|
|
|
190
496
|
const designMf = readText('apps/remotes/remote-design-system/module-federation.config.ts');
|
|
191
497
|
assert(designMf.includes("name: 'remoteDesignSystem'"), 'Design-system remote MF name is missing');
|
|
192
|
-
assert(designMf.includes('
|
|
193
|
-
assert(designMf.includes('
|
|
498
|
+
assert(designMf.includes("'./Button'"), 'Design-system remote must expose Button');
|
|
499
|
+
assert(designMf.includes("'./tokens'"), 'Design-system remote must expose tokens');
|
|
194
500
|
|
|
195
501
|
const servicePackage = readJson('services/service-recommendations-effect/package.json');
|
|
196
502
|
assert(
|
|
197
503
|
servicePackage.dependencies?.['@modern-js/runtime'] ===
|
|
198
504
|
expectedModernDependency('@modern-js/runtime'),
|
|
199
|
-
`Effect service must use @modern-js/runtime through ${expectedModernDependency(
|
|
505
|
+
`Effect service must use @modern-js/runtime through ${expectedModernDependency(
|
|
506
|
+
'@modern-js/runtime',
|
|
507
|
+
)}`,
|
|
200
508
|
);
|
|
201
509
|
assert(
|
|
202
510
|
servicePackage.devDependencies?.['@modern-js/app-tools'] ===
|
|
203
511
|
expectedModernDependency('@modern-js/app-tools'),
|
|
204
|
-
`Effect service must use @modern-js/app-tools through ${expectedModernDependency(
|
|
512
|
+
`Effect service must use @modern-js/app-tools through ${expectedModernDependency(
|
|
513
|
+
'@modern-js/app-tools',
|
|
514
|
+
)}`,
|
|
205
515
|
);
|
|
206
516
|
assert(
|
|
207
517
|
servicePackage.devDependencies?.['@modern-js/plugin-bff'] ===
|
|
208
518
|
expectedModernDependency('@modern-js/plugin-bff'),
|
|
209
|
-
`Effect service must use @modern-js/plugin-bff through ${expectedModernDependency(
|
|
519
|
+
`Effect service must use @modern-js/plugin-bff through ${expectedModernDependency(
|
|
520
|
+
'@modern-js/plugin-bff',
|
|
521
|
+
)}`,
|
|
210
522
|
);
|
|
211
523
|
assert(
|
|
212
|
-
servicePackage.dependencies?.[`@${packageScope}/shared-effect-api`] ===
|
|
213
|
-
'workspace:*',
|
|
524
|
+
servicePackage.dependencies?.[`@${packageScope}/shared-effect-api`] === 'workspace:*',
|
|
214
525
|
'Effect service must link generated shared Effect API through workspace:*',
|
|
215
526
|
);
|
|
216
527
|
|
|
217
528
|
const serviceConfig = readText('services/service-recommendations-effect/modern.config.ts');
|
|
218
|
-
assert(
|
|
529
|
+
assert(
|
|
530
|
+
serviceConfig.includes("runtimeFramework: 'effect'"),
|
|
531
|
+
'Effect service must use Effect runtime',
|
|
532
|
+
);
|
|
219
533
|
assert(serviceConfig.includes('bffPlugin()'), 'Effect service must enable bffPlugin');
|
|
220
534
|
|
|
221
535
|
const serviceEntry = readText('services/service-recommendations-effect/api/effect/index.ts');
|
|
222
|
-
assert(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
assert(
|
|
536
|
+
assert(
|
|
537
|
+
serviceEntry.includes('defineEffectBff'),
|
|
538
|
+
'Effect service must expose defineEffectBff placeholder',
|
|
539
|
+
);
|
|
540
|
+
assert(
|
|
541
|
+
serviceEntry.includes('recommendationsEffectApi'),
|
|
542
|
+
'Effect service must use shared recommendations API',
|
|
543
|
+
);
|
|
544
|
+
assert(
|
|
545
|
+
readText('services/service-recommendations-effect/shared/effect/api.ts').includes('HttpApi.make'),
|
|
546
|
+
'Effect shared API placeholder must define HttpApi',
|
|
547
|
+
);
|
|
227
548
|
|
|
228
549
|
const topology = readJson('topology/reference-topology.json');
|
|
229
550
|
assert(topology.preset === 'presetUltramodern', 'Topology must reference presetUltramodern');
|
|
@@ -232,27 +553,26 @@ assert(topology.shell?.remoteRefs?.length === 3, 'Topology shell must reference
|
|
|
232
553
|
assert(topology.remotes?.length === 3, 'Topology must contain three remotes');
|
|
233
554
|
assert(
|
|
234
555
|
topology.remotes.some(
|
|
235
|
-
remote =>
|
|
236
|
-
remote.id === 'remote-design-system' &&
|
|
237
|
-
remote.kind === 'horizontal-design-system',
|
|
556
|
+
(remote) => remote.id === 'remote-design-system' && remote.kind === 'horizontal-design-system',
|
|
238
557
|
),
|
|
239
558
|
'Topology must contain the horizontal design-system remote',
|
|
240
559
|
);
|
|
241
|
-
assert(
|
|
560
|
+
assert(
|
|
561
|
+
topology.effectServices?.[0]?.runtime === 'effect',
|
|
562
|
+
'Topology must contain an Effect service',
|
|
563
|
+
);
|
|
242
564
|
assert(topology.sharedPackages?.length === 3, 'Topology must contain shared package placeholders');
|
|
243
565
|
|
|
244
566
|
const ownership = readJson('topology/ownership.json');
|
|
245
567
|
assert(
|
|
246
568
|
ownership.owners?.some(
|
|
247
|
-
owner =>
|
|
248
|
-
owner.id === 'remote-commerce' &&
|
|
249
|
-
owner.ownership?.team === 'commerce-experience',
|
|
569
|
+
(owner) => owner.id === 'remote-commerce' && owner.ownership?.team === 'commerce-experience',
|
|
250
570
|
),
|
|
251
571
|
'Ownership metadata must retain commerce owner',
|
|
252
572
|
);
|
|
253
573
|
assert(
|
|
254
574
|
ownership.owners?.some(
|
|
255
|
-
owner =>
|
|
575
|
+
(owner) =>
|
|
256
576
|
owner.id === 'service-recommendations-effect' &&
|
|
257
577
|
owner.package === `@${packageScope}/service-recommendations-effect`,
|
|
258
578
|
),
|
|
@@ -268,9 +588,35 @@ assert(
|
|
|
268
588
|
manifest.packageSource?.strategy === packageSource.strategy,
|
|
269
589
|
'Template manifest must retain the generated package source strategy',
|
|
270
590
|
);
|
|
591
|
+
assert(
|
|
592
|
+
manifest.agentSkills?.source?.commit === rstackAgentSkillsCommit,
|
|
593
|
+
'Template manifest must retain the Rstack agent skills commit',
|
|
594
|
+
);
|
|
595
|
+
assert(
|
|
596
|
+
baselineAgentSkills.every((skillName) => manifest.agentSkills?.baseline?.includes(skillName)),
|
|
597
|
+
'Template manifest must list every baseline agent skill',
|
|
598
|
+
);
|
|
599
|
+
assert(
|
|
600
|
+
manifest.agentSkills?.privateSource?.repository === 'https://github.com/TechsioCZ/skills',
|
|
601
|
+
'Template manifest must retain the private TechsioCZ skill source',
|
|
602
|
+
);
|
|
603
|
+
assert(
|
|
604
|
+
privateAgentSkills.every((skillName) =>
|
|
605
|
+
manifest.agentSkills?.privateSource?.baseline?.includes(skillName),
|
|
606
|
+
),
|
|
607
|
+
'Template manifest must list every private agent skill allowlist entry',
|
|
608
|
+
);
|
|
271
609
|
assert(
|
|
272
610
|
manifest.validation?.expectedCommands?.includes('pnpm run ultramodern:check'),
|
|
273
611
|
'Template manifest must document the validation command',
|
|
274
612
|
);
|
|
613
|
+
assert(
|
|
614
|
+
manifest.validation?.postMaterializationValidation?.includes('pnpm-11-policy-enforced'),
|
|
615
|
+
'Template manifest must document pnpm 11 policy validation',
|
|
616
|
+
);
|
|
617
|
+
assert(
|
|
618
|
+
manifest.validation?.postMaterializationValidation?.includes('github-workflow-security-enforced'),
|
|
619
|
+
'Template manifest must document generated workflow security validation',
|
|
620
|
+
);
|
|
275
621
|
|
|
276
622
|
console.log('UltraModern workspace scaffold validated');
|