@bleedingdev/modern-js-create 3.2.0-ultramodern.2 → 3.2.0-ultramodern.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +23 -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 +6 -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 +217 -0
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +397 -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
|
+
}
|
|
190
|
+
assert(
|
|
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
|
+
);
|
|
86
211
|
assert(
|
|
87
|
-
rootPackage.modernjs?.packageSource?.config ===
|
|
88
|
-
'./.modernjs/ultramodern-package-source.json',
|
|
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,114 @@ 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
|
+
);
|
|
114
272
|
assert(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
'Root must expose the ultramodern:check script',
|
|
273
|
+
skillsLock.source?.repository === 'https://github.com/rstackjs/agent-skills',
|
|
274
|
+
'Agent skills lock must retain the Rstack skill source repository',
|
|
118
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.repositories?.some(
|
|
290
|
+
(repo) =>
|
|
291
|
+
repo.id === 'effect' &&
|
|
292
|
+
repo.url === 'https://github.com/Effect-TS/effect.git' &&
|
|
293
|
+
repo.path === 'repos/effect',
|
|
294
|
+
),
|
|
295
|
+
'Agent reference repositories must include Effect',
|
|
296
|
+
);
|
|
297
|
+
assert(
|
|
298
|
+
agentReferenceRepos.repositories?.some(
|
|
299
|
+
(repo) =>
|
|
300
|
+
repo.id === 'ultramodern-js' &&
|
|
301
|
+
repo.url === 'https://github.com/BleedingDev/ultramodern.js.git' &&
|
|
302
|
+
repo.path === 'repos/ultramodern.js',
|
|
303
|
+
),
|
|
304
|
+
'Agent reference repositories must include UltraModern.js',
|
|
305
|
+
);
|
|
306
|
+
assert(
|
|
307
|
+
readText('scripts/setup-agent-reference-repos.mjs').includes(
|
|
308
|
+
'ULTRAMODERN_SKIP_AGENT_REPOS',
|
|
309
|
+
),
|
|
310
|
+
'Agent reference repository setup must expose an opt-out environment variable',
|
|
311
|
+
);
|
|
312
|
+
for (const skillName of baselineAgentSkills) {
|
|
313
|
+
assert(
|
|
314
|
+
skillsLock.baseline?.some((skill) => skill.name === skillName),
|
|
315
|
+
`Agent skills lock must include ${skillName}`,
|
|
316
|
+
);
|
|
317
|
+
assert(
|
|
318
|
+
readText(`.agents/skills/${skillName}/SKILL.md`).includes(`name: ${skillName}`),
|
|
319
|
+
`${skillName} must contain matching skill metadata`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const privateSource = skillsLock.sources?.find(
|
|
324
|
+
(source) => source.repository === 'https://github.com/TechsioCZ/skills',
|
|
325
|
+
);
|
|
326
|
+
assert(
|
|
327
|
+
privateSource?.install === 'clone-if-authorized',
|
|
328
|
+
'Agent skills lock must configure TechsioCZ skills as clone-if-authorized',
|
|
329
|
+
);
|
|
330
|
+
for (const skillName of privateAgentSkills) {
|
|
331
|
+
assert(
|
|
332
|
+
privateSource.baseline?.some((skill) => skill.name === skillName),
|
|
333
|
+
`Agent skills lock must allowlist private skill ${skillName}`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
119
336
|
|
|
120
337
|
const appPackagePaths = [
|
|
121
338
|
'apps/shell-super-app/package.json',
|
|
@@ -126,31 +343,47 @@ const appPackagePaths = [
|
|
|
126
343
|
|
|
127
344
|
for (const packagePath of appPackagePaths) {
|
|
128
345
|
const packageJson = readJson(packagePath);
|
|
346
|
+
assert(
|
|
347
|
+
packageJson.dependencies?.['@modern-js/plugin-i18n'] ===
|
|
348
|
+
expectedModernDependency('@modern-js/plugin-i18n'),
|
|
349
|
+
`${packagePath} must use @modern-js/plugin-i18n through ${expectedModernDependency(
|
|
350
|
+
'@modern-js/plugin-i18n',
|
|
351
|
+
)}`,
|
|
352
|
+
);
|
|
129
353
|
assert(
|
|
130
354
|
packageJson.dependencies?.['@modern-js/plugin-tanstack'] ===
|
|
131
355
|
expectedModernDependency('@modern-js/plugin-tanstack'),
|
|
132
|
-
`${packagePath} must
|
|
356
|
+
`${packagePath} must use @modern-js/plugin-tanstack through ${expectedModernDependency(
|
|
357
|
+
'@modern-js/plugin-tanstack',
|
|
358
|
+
)}`,
|
|
133
359
|
);
|
|
134
360
|
assert(
|
|
135
361
|
packageJson.dependencies?.['@modern-js/runtime'] ===
|
|
136
362
|
expectedModernDependency('@modern-js/runtime'),
|
|
137
|
-
`${packagePath} must
|
|
363
|
+
`${packagePath} must use @modern-js/runtime through ${expectedModernDependency(
|
|
364
|
+
'@modern-js/runtime',
|
|
365
|
+
)}`,
|
|
138
366
|
);
|
|
139
367
|
assert(
|
|
140
368
|
packageJson.devDependencies?.['@modern-js/app-tools'] ===
|
|
141
369
|
expectedModernDependency('@modern-js/app-tools'),
|
|
142
|
-
`${packagePath} must
|
|
370
|
+
`${packagePath} must use @modern-js/app-tools through ${expectedModernDependency(
|
|
371
|
+
'@modern-js/app-tools',
|
|
372
|
+
)}`,
|
|
143
373
|
);
|
|
144
374
|
assert(
|
|
145
|
-
packageJson.dependencies?.[`@${packageScope}/shared-contracts`] ===
|
|
146
|
-
'workspace:*',
|
|
375
|
+
packageJson.dependencies?.[`@${packageScope}/shared-contracts`] === 'workspace:*',
|
|
147
376
|
`${packagePath} must link generated shared contracts through workspace:*`,
|
|
148
377
|
);
|
|
149
378
|
assert(
|
|
150
|
-
packageJson.dependencies?.[`@${packageScope}/shared-design-tokens`] ===
|
|
151
|
-
'workspace:*',
|
|
379
|
+
packageJson.dependencies?.[`@${packageScope}/shared-design-tokens`] === 'workspace:*',
|
|
152
380
|
`${packagePath} must link generated shared design tokens through workspace:*`,
|
|
153
381
|
);
|
|
382
|
+
assert(packageJson.dependencies?.i18next === '26.2.0', `${packagePath} must include i18next`);
|
|
383
|
+
assert(
|
|
384
|
+
packageJson.dependencies?.['react-i18next'] === '17.0.8',
|
|
385
|
+
`${packagePath} must include react-i18next`,
|
|
386
|
+
);
|
|
154
387
|
assert(
|
|
155
388
|
packageJson.dependencies?.['@tanstack/react-router'] === tanstackVersion,
|
|
156
389
|
`${packagePath} must use @tanstack/react-router ${tanstackVersion}`,
|
|
@@ -159,6 +392,10 @@ for (const packagePath of appPackagePaths) {
|
|
|
159
392
|
packageJson.dependencies?.['@module-federation/modern-js-v3'] === '2.4.0',
|
|
160
393
|
`${packagePath} must include the Module Federation plugin`,
|
|
161
394
|
);
|
|
395
|
+
assert(
|
|
396
|
+
packageJson.dependencies?.['zephyr-modernjs-plugin'] === '1.1.1',
|
|
397
|
+
`${packagePath} must include the official Zephyr Modern.js plugin`,
|
|
398
|
+
);
|
|
162
399
|
assert(
|
|
163
400
|
packageJson.modernjs?.preset === 'presetUltramodern',
|
|
164
401
|
`${packagePath} must keep presetUltramodern metadata`,
|
|
@@ -173,57 +410,133 @@ for (const configPath of [
|
|
|
173
410
|
]) {
|
|
174
411
|
const config = readText(configPath);
|
|
175
412
|
assert(config.includes('presetUltramodern('), `${configPath} must use presetUltramodern`);
|
|
413
|
+
assert(config.includes('i18nPlugin('), `${configPath} must enable plugin-i18n`);
|
|
414
|
+
assert(config.includes('localePathRedirect: true'), `${configPath} must prefix localized URLs`);
|
|
415
|
+
assert(config.includes('ULTRAMODERN_SITE_URL'), `${configPath} must expose site URL metadata`);
|
|
416
|
+
assert(
|
|
417
|
+
config.includes('MODERN_PUBLIC_SITE_URL must be set for production builds'),
|
|
418
|
+
`${configPath} must require MODERN_PUBLIC_SITE_URL for production builds`,
|
|
419
|
+
);
|
|
176
420
|
assert(config.includes('tanstackRouterPlugin()'), `${configPath} must enable plugin-tanstack`);
|
|
177
|
-
assert(
|
|
421
|
+
assert(
|
|
422
|
+
config.includes('moduleFederationPlugin()'),
|
|
423
|
+
`${configPath} must enable Module Federation`,
|
|
424
|
+
);
|
|
425
|
+
assert(
|
|
426
|
+
config.includes("from 'zephyr-modernjs-plugin'"),
|
|
427
|
+
`${configPath} must import the official Zephyr Modern.js plugin`,
|
|
428
|
+
);
|
|
429
|
+
assert(config.includes('withZephyr()'), `${configPath} must enable Zephyr through plugins`);
|
|
430
|
+
assert(
|
|
431
|
+
config.includes("bundler: 'rspack'"),
|
|
432
|
+
`${configPath} must keep appTools on the rspack bundler for Zephyr`,
|
|
433
|
+
);
|
|
434
|
+
assert(
|
|
435
|
+
config.includes("html: './'"),
|
|
436
|
+
`${configPath} must keep Modern.js output.distPath.html compatible with Zephyr`,
|
|
437
|
+
);
|
|
438
|
+
assert(
|
|
439
|
+
config.includes("outputStructure: 'flat'"),
|
|
440
|
+
`${configPath} must keep Modern.js HTML output flat for Zephyr`,
|
|
441
|
+
);
|
|
442
|
+
assert(
|
|
443
|
+
config.includes("mainEntryName: 'index'"),
|
|
444
|
+
`${configPath} must keep Modern.js source.mainEntryName compatible with Zephyr`,
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
for (const routePath of [
|
|
449
|
+
'apps/shell-super-app/src/routes/[lang]/page.tsx',
|
|
450
|
+
'apps/remotes/remote-commerce/src/routes/[lang]/page.tsx',
|
|
451
|
+
'apps/remotes/remote-identity/src/routes/[lang]/page.tsx',
|
|
452
|
+
'apps/remotes/remote-design-system/src/routes/[lang]/page.tsx',
|
|
453
|
+
]) {
|
|
454
|
+
const route = readText(routePath);
|
|
455
|
+
assert(route.includes('rel="canonical"'), `${routePath} must emit canonical metadata`);
|
|
456
|
+
assert(route.includes('rel="alternate"'), `${routePath} must emit alternate locale metadata`);
|
|
457
|
+
assert(route.includes('hrefLang="x-default"'), `${routePath} must emit x-default metadata`);
|
|
458
|
+
assert(route.includes('localizedPath('), `${routePath} must build localized URLs`);
|
|
459
|
+
assert(
|
|
460
|
+
route.includes('@modern-js/plugin-tanstack/runtime'),
|
|
461
|
+
`${routePath} must import TanStack runtime from @modern-js/plugin-tanstack/runtime`,
|
|
462
|
+
);
|
|
463
|
+
assert(
|
|
464
|
+
!route.includes(deprecatedTanstackRuntime),
|
|
465
|
+
`${routePath} must not import deprecated TanStack runtime path`,
|
|
466
|
+
);
|
|
178
467
|
}
|
|
179
468
|
|
|
180
469
|
const shellMf = readText('apps/shell-super-app/module-federation.config.ts');
|
|
181
470
|
assert(shellMf.includes("name: 'shellSuperApp'"), 'Shell MF config must name the shell');
|
|
182
|
-
assert(
|
|
183
|
-
|
|
184
|
-
|
|
471
|
+
assert(
|
|
472
|
+
shellMf.includes('remoteCommerce@http://localhost:3021/mf-manifest.json'),
|
|
473
|
+
'Shell must reference commerce remote',
|
|
474
|
+
);
|
|
475
|
+
assert(
|
|
476
|
+
shellMf.includes('remoteIdentity@http://localhost:3022/mf-manifest.json'),
|
|
477
|
+
'Shell must reference identity remote',
|
|
478
|
+
);
|
|
479
|
+
assert(
|
|
480
|
+
shellMf.includes('remoteDesignSystem@http://localhost:3023/mf-manifest.json'),
|
|
481
|
+
'Shell must reference design-system remote',
|
|
482
|
+
);
|
|
185
483
|
|
|
186
484
|
const commerceMf = readText('apps/remotes/remote-commerce/module-federation.config.ts');
|
|
187
485
|
assert(commerceMf.includes("name: 'remoteCommerce'"), 'Commerce remote MF name is missing');
|
|
188
|
-
assert(commerceMf.includes('
|
|
486
|
+
assert(commerceMf.includes("'./Widget'"), 'Commerce remote must expose a widget');
|
|
189
487
|
|
|
190
488
|
const designMf = readText('apps/remotes/remote-design-system/module-federation.config.ts');
|
|
191
489
|
assert(designMf.includes("name: 'remoteDesignSystem'"), 'Design-system remote MF name is missing');
|
|
192
|
-
assert(designMf.includes('
|
|
193
|
-
assert(designMf.includes('
|
|
490
|
+
assert(designMf.includes("'./Button'"), 'Design-system remote must expose Button');
|
|
491
|
+
assert(designMf.includes("'./tokens'"), 'Design-system remote must expose tokens');
|
|
194
492
|
|
|
195
493
|
const servicePackage = readJson('services/service-recommendations-effect/package.json');
|
|
196
494
|
assert(
|
|
197
495
|
servicePackage.dependencies?.['@modern-js/runtime'] ===
|
|
198
496
|
expectedModernDependency('@modern-js/runtime'),
|
|
199
|
-
`Effect service must use @modern-js/runtime through ${expectedModernDependency(
|
|
497
|
+
`Effect service must use @modern-js/runtime through ${expectedModernDependency(
|
|
498
|
+
'@modern-js/runtime',
|
|
499
|
+
)}`,
|
|
200
500
|
);
|
|
201
501
|
assert(
|
|
202
502
|
servicePackage.devDependencies?.['@modern-js/app-tools'] ===
|
|
203
503
|
expectedModernDependency('@modern-js/app-tools'),
|
|
204
|
-
`Effect service must use @modern-js/app-tools through ${expectedModernDependency(
|
|
504
|
+
`Effect service must use @modern-js/app-tools through ${expectedModernDependency(
|
|
505
|
+
'@modern-js/app-tools',
|
|
506
|
+
)}`,
|
|
205
507
|
);
|
|
206
508
|
assert(
|
|
207
509
|
servicePackage.devDependencies?.['@modern-js/plugin-bff'] ===
|
|
208
510
|
expectedModernDependency('@modern-js/plugin-bff'),
|
|
209
|
-
`Effect service must use @modern-js/plugin-bff through ${expectedModernDependency(
|
|
511
|
+
`Effect service must use @modern-js/plugin-bff through ${expectedModernDependency(
|
|
512
|
+
'@modern-js/plugin-bff',
|
|
513
|
+
)}`,
|
|
210
514
|
);
|
|
211
515
|
assert(
|
|
212
|
-
servicePackage.dependencies?.[`@${packageScope}/shared-effect-api`] ===
|
|
213
|
-
'workspace:*',
|
|
516
|
+
servicePackage.dependencies?.[`@${packageScope}/shared-effect-api`] === 'workspace:*',
|
|
214
517
|
'Effect service must link generated shared Effect API through workspace:*',
|
|
215
518
|
);
|
|
216
519
|
|
|
217
520
|
const serviceConfig = readText('services/service-recommendations-effect/modern.config.ts');
|
|
218
|
-
assert(
|
|
521
|
+
assert(
|
|
522
|
+
serviceConfig.includes("runtimeFramework: 'effect'"),
|
|
523
|
+
'Effect service must use Effect runtime',
|
|
524
|
+
);
|
|
219
525
|
assert(serviceConfig.includes('bffPlugin()'), 'Effect service must enable bffPlugin');
|
|
220
526
|
|
|
221
527
|
const serviceEntry = readText('services/service-recommendations-effect/api/effect/index.ts');
|
|
222
|
-
assert(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
assert(
|
|
528
|
+
assert(
|
|
529
|
+
serviceEntry.includes('defineEffectBff'),
|
|
530
|
+
'Effect service must expose defineEffectBff placeholder',
|
|
531
|
+
);
|
|
532
|
+
assert(
|
|
533
|
+
serviceEntry.includes('recommendationsEffectApi'),
|
|
534
|
+
'Effect service must use shared recommendations API',
|
|
535
|
+
);
|
|
536
|
+
assert(
|
|
537
|
+
readText('services/service-recommendations-effect/shared/effect/api.ts').includes('HttpApi.make'),
|
|
538
|
+
'Effect shared API placeholder must define HttpApi',
|
|
539
|
+
);
|
|
227
540
|
|
|
228
541
|
const topology = readJson('topology/reference-topology.json');
|
|
229
542
|
assert(topology.preset === 'presetUltramodern', 'Topology must reference presetUltramodern');
|
|
@@ -232,27 +545,26 @@ assert(topology.shell?.remoteRefs?.length === 3, 'Topology shell must reference
|
|
|
232
545
|
assert(topology.remotes?.length === 3, 'Topology must contain three remotes');
|
|
233
546
|
assert(
|
|
234
547
|
topology.remotes.some(
|
|
235
|
-
remote =>
|
|
236
|
-
remote.id === 'remote-design-system' &&
|
|
237
|
-
remote.kind === 'horizontal-design-system',
|
|
548
|
+
(remote) => remote.id === 'remote-design-system' && remote.kind === 'horizontal-design-system',
|
|
238
549
|
),
|
|
239
550
|
'Topology must contain the horizontal design-system remote',
|
|
240
551
|
);
|
|
241
|
-
assert(
|
|
552
|
+
assert(
|
|
553
|
+
topology.effectServices?.[0]?.runtime === 'effect',
|
|
554
|
+
'Topology must contain an Effect service',
|
|
555
|
+
);
|
|
242
556
|
assert(topology.sharedPackages?.length === 3, 'Topology must contain shared package placeholders');
|
|
243
557
|
|
|
244
558
|
const ownership = readJson('topology/ownership.json');
|
|
245
559
|
assert(
|
|
246
560
|
ownership.owners?.some(
|
|
247
|
-
owner =>
|
|
248
|
-
owner.id === 'remote-commerce' &&
|
|
249
|
-
owner.ownership?.team === 'commerce-experience',
|
|
561
|
+
(owner) => owner.id === 'remote-commerce' && owner.ownership?.team === 'commerce-experience',
|
|
250
562
|
),
|
|
251
563
|
'Ownership metadata must retain commerce owner',
|
|
252
564
|
);
|
|
253
565
|
assert(
|
|
254
566
|
ownership.owners?.some(
|
|
255
|
-
owner =>
|
|
567
|
+
(owner) =>
|
|
256
568
|
owner.id === 'service-recommendations-effect' &&
|
|
257
569
|
owner.package === `@${packageScope}/service-recommendations-effect`,
|
|
258
570
|
),
|
|
@@ -268,9 +580,35 @@ assert(
|
|
|
268
580
|
manifest.packageSource?.strategy === packageSource.strategy,
|
|
269
581
|
'Template manifest must retain the generated package source strategy',
|
|
270
582
|
);
|
|
583
|
+
assert(
|
|
584
|
+
manifest.agentSkills?.source?.commit === rstackAgentSkillsCommit,
|
|
585
|
+
'Template manifest must retain the Rstack agent skills commit',
|
|
586
|
+
);
|
|
587
|
+
assert(
|
|
588
|
+
baselineAgentSkills.every((skillName) => manifest.agentSkills?.baseline?.includes(skillName)),
|
|
589
|
+
'Template manifest must list every baseline agent skill',
|
|
590
|
+
);
|
|
591
|
+
assert(
|
|
592
|
+
manifest.agentSkills?.privateSource?.repository === 'https://github.com/TechsioCZ/skills',
|
|
593
|
+
'Template manifest must retain the private TechsioCZ skill source',
|
|
594
|
+
);
|
|
595
|
+
assert(
|
|
596
|
+
privateAgentSkills.every((skillName) =>
|
|
597
|
+
manifest.agentSkills?.privateSource?.baseline?.includes(skillName),
|
|
598
|
+
),
|
|
599
|
+
'Template manifest must list every private agent skill allowlist entry',
|
|
600
|
+
);
|
|
271
601
|
assert(
|
|
272
602
|
manifest.validation?.expectedCommands?.includes('pnpm run ultramodern:check'),
|
|
273
603
|
'Template manifest must document the validation command',
|
|
274
604
|
);
|
|
605
|
+
assert(
|
|
606
|
+
manifest.validation?.postMaterializationValidation?.includes('pnpm-11-policy-enforced'),
|
|
607
|
+
'Template manifest must document pnpm 11 policy validation',
|
|
608
|
+
);
|
|
609
|
+
assert(
|
|
610
|
+
manifest.validation?.postMaterializationValidation?.includes('github-workflow-security-enforced'),
|
|
611
|
+
'Template manifest must document generated workflow security validation',
|
|
612
|
+
);
|
|
275
613
|
|
|
276
614
|
console.log('UltraModern workspace scaffold validated');
|
package/template/biome.json
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"root": false,
|
|
3
|
-
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
4
|
-
"vcs": {
|
|
5
|
-
"enabled": true,
|
|
6
|
-
"defaultBranch": "main",
|
|
7
|
-
"clientKind": "git",
|
|
8
|
-
"useIgnoreFile": true
|
|
9
|
-
},
|
|
10
|
-
"formatter": {
|
|
11
|
-
"enabled": true,
|
|
12
|
-
"indentStyle": "space"
|
|
13
|
-
},
|
|
14
|
-
"javascript": {
|
|
15
|
-
"formatter": {
|
|
16
|
-
"quoteStyle": "single",
|
|
17
|
-
"arrowParentheses": "asNeeded",
|
|
18
|
-
"jsxQuoteStyle": "double",
|
|
19
|
-
"lineWidth": 80
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"linter": {
|
|
23
|
-
"enabled": true,
|
|
24
|
-
"rules": {
|
|
25
|
-
"recommended": true,
|
|
26
|
-
"suspicious": {
|
|
27
|
-
"noDuplicateFontNames": "off"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
32
|
-
"files": {
|
|
33
|
-
"ignoreUnknown": true,
|
|
34
|
-
"includes": [
|
|
35
|
-
"**",
|
|
36
|
-
"!**/.vscode/**/*",
|
|
37
|
-
"!**/node_modules/**/*",
|
|
38
|
-
"!**/dist/**/*"
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
}
|