@bleedingdev/modern-js-create 3.2.0-ultramodern.10 → 3.2.0-ultramodern.12
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/dist/index.js +249 -146
- package/package.json +3 -3
- package/template/AGENTS.md +5 -0
- 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 +8 -1
- package/template/package.json.handlebars +8 -3
- package/template/scripts/check-i18n-strings.mjs +83 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +12 -0
- package/template/src/modern.runtime.ts.handlebars +17 -1
- package/template/src/routes/page.tsx.handlebars +45 -26
- package/template-workspace/AGENTS.md +6 -1
- package/template-workspace/oxfmt.config.ts +12 -3
- package/template-workspace/oxlint.config.ts +11 -4
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +17 -28
- package/template-workspace/scripts/check-i18n-strings.mjs +83 -0
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +121 -91
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const root = process.cwd();
|
|
5
|
+
const scanRoots = ['apps'].map((scanRoot) => path.join(root, scanRoot));
|
|
6
|
+
const ignoredDirectories = new Set(['.modern', '.modernjs', 'dist', 'node_modules']);
|
|
7
|
+
const visibleAttributePattern =
|
|
8
|
+
/\s(?:aria-label|alt|placeholder|title)=["']([^"']*[A-Za-z][^"']*)["']/gu;
|
|
9
|
+
const jsxTextPattern = />([^<>{}]*[A-Za-z][^<>{}]*)</gu;
|
|
10
|
+
|
|
11
|
+
const collectFiles = (directory) => {
|
|
12
|
+
if (!fs.existsSync(directory)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const files = [];
|
|
17
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
if (!ignoredDirectories.has(entry.name)) {
|
|
20
|
+
files.push(...collectFiles(path.join(directory, entry.name)));
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (entry.isFile() && /\.(jsx|tsx)$/u.test(entry.name) && !entry.name.endsWith('.d.ts')) {
|
|
26
|
+
files.push(path.join(directory, entry.name));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return files;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const lineNumberForIndex = (content, index) => content.slice(0, index).split('\n').length;
|
|
33
|
+
const isIgnoredLine = (content, index) => {
|
|
34
|
+
const lineStart = content.lastIndexOf('\n', index) + 1;
|
|
35
|
+
const lineEnd = content.indexOf('\n', index);
|
|
36
|
+
const currentLineEnd = lineEnd === -1 ? content.length : lineEnd;
|
|
37
|
+
const previousLineStart = content.lastIndexOf('\n', Math.max(0, lineStart - 2)) + 1;
|
|
38
|
+
const nextLineEnd = content.indexOf('\n', currentLineEnd + 1);
|
|
39
|
+
const context = content.slice(
|
|
40
|
+
previousLineStart,
|
|
41
|
+
nextLineEnd === -1 ? content.length : nextLineEnd,
|
|
42
|
+
);
|
|
43
|
+
return /i18n-ignore/u.test(context);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const violations = [];
|
|
47
|
+
for (const filePath of scanRoots.flatMap(collectFiles)) {
|
|
48
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
49
|
+
for (const match of content.matchAll(visibleAttributePattern)) {
|
|
50
|
+
if (!isIgnoredLine(content, match.index ?? 0)) {
|
|
51
|
+
violations.push({
|
|
52
|
+
filePath,
|
|
53
|
+
line: lineNumberForIndex(content, match.index ?? 0),
|
|
54
|
+
text: match[1].trim(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const match of content.matchAll(jsxTextPattern)) {
|
|
60
|
+
const text = match[1].replaceAll(/\s+/gu, ' ').trim();
|
|
61
|
+
if (text && !isIgnoredLine(content, match.index ?? 0)) {
|
|
62
|
+
violations.push({
|
|
63
|
+
filePath,
|
|
64
|
+
line: lineNumberForIndex(content, match.index ?? 0),
|
|
65
|
+
text,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (violations.length > 0) {
|
|
72
|
+
console.error('Hardcoded user-visible JSX strings found. Move copy to locale JSON files.');
|
|
73
|
+
for (const violation of violations) {
|
|
74
|
+
console.error(
|
|
75
|
+
`${path.relative(root, violation.filePath)}:${violation.line} ${JSON.stringify(
|
|
76
|
+
violation.text,
|
|
77
|
+
)}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log('No hardcoded user-visible JSX strings found.');
|
|
@@ -4,11 +4,11 @@ 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 =
|
|
8
|
-
'61c948b42512e223bad44b83af4080eba48b2677';
|
|
7
|
+
const rstackAgentSkillsCommit = '61c948b42512e223bad44b83af4080eba48b2677';
|
|
9
8
|
const modernPackages = [
|
|
10
9
|
'@modern-js/app-tools',
|
|
11
10
|
'@modern-js/plugin-bff',
|
|
11
|
+
'@modern-js/plugin-i18n',
|
|
12
12
|
'@modern-js/plugin-tanstack',
|
|
13
13
|
'@modern-js/runtime',
|
|
14
14
|
];
|
|
@@ -21,31 +21,18 @@ const baselineAgentSkills = [
|
|
|
21
21
|
'rslib-modern-package',
|
|
22
22
|
'rstest-best-practices',
|
|
23
23
|
];
|
|
24
|
-
const privateAgentSkills = [
|
|
25
|
-
'plan-graph',
|
|
26
|
-
'dag',
|
|
27
|
-
'subagent-graph',
|
|
28
|
-
'helm',
|
|
29
|
-
'debugger-mode',
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
function readText(relativePath) {
|
|
33
|
-
return fs.readFileSync(path.join(root, relativePath), 'utf-8');
|
|
34
|
-
}
|
|
24
|
+
const privateAgentSkills = ['plan-graph', 'dag', 'subagent-graph', 'helm', 'debugger-mode'];
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
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) => {
|
|
41
29
|
if (!condition) {
|
|
42
30
|
throw new Error(message);
|
|
43
31
|
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function assertExists(relativePath) {
|
|
32
|
+
};
|
|
33
|
+
const assertExists = (relativePath) => {
|
|
47
34
|
assert(fs.existsSync(path.join(root, relativePath)), `Missing ${relativePath}`);
|
|
48
|
-
}
|
|
35
|
+
};
|
|
49
36
|
|
|
50
37
|
const requiredPaths = [
|
|
51
38
|
'AGENTS.md',
|
|
@@ -72,18 +59,31 @@ const requiredPaths = [
|
|
|
72
59
|
'.modernjs/ultramodern-workspace-template-manifest.json',
|
|
73
60
|
'.modernjs/ultramodern-package-source.json',
|
|
74
61
|
'scripts/bootstrap-agent-skills.mjs',
|
|
62
|
+
'scripts/check-i18n-strings.mjs',
|
|
75
63
|
'apps/shell-super-app/package.json',
|
|
64
|
+
'apps/shell-super-app/config/public/locales/en/translation.json',
|
|
65
|
+
'apps/shell-super-app/config/public/locales/cs/translation.json',
|
|
76
66
|
'apps/shell-super-app/modern.config.ts',
|
|
77
67
|
'apps/shell-super-app/module-federation.config.ts',
|
|
68
|
+
'apps/shell-super-app/src/modern.runtime.ts',
|
|
78
69
|
'apps/remotes/remote-commerce/package.json',
|
|
70
|
+
'apps/remotes/remote-commerce/config/public/locales/en/translation.json',
|
|
71
|
+
'apps/remotes/remote-commerce/config/public/locales/cs/translation.json',
|
|
79
72
|
'apps/remotes/remote-commerce/modern.config.ts',
|
|
80
73
|
'apps/remotes/remote-commerce/module-federation.config.ts',
|
|
74
|
+
'apps/remotes/remote-commerce/src/modern.runtime.ts',
|
|
81
75
|
'apps/remotes/remote-identity/package.json',
|
|
76
|
+
'apps/remotes/remote-identity/config/public/locales/en/translation.json',
|
|
77
|
+
'apps/remotes/remote-identity/config/public/locales/cs/translation.json',
|
|
82
78
|
'apps/remotes/remote-identity/modern.config.ts',
|
|
83
79
|
'apps/remotes/remote-identity/module-federation.config.ts',
|
|
80
|
+
'apps/remotes/remote-identity/src/modern.runtime.ts',
|
|
84
81
|
'apps/remotes/remote-design-system/package.json',
|
|
82
|
+
'apps/remotes/remote-design-system/config/public/locales/en/translation.json',
|
|
83
|
+
'apps/remotes/remote-design-system/config/public/locales/cs/translation.json',
|
|
85
84
|
'apps/remotes/remote-design-system/modern.config.ts',
|
|
86
85
|
'apps/remotes/remote-design-system/module-federation.config.ts',
|
|
86
|
+
'apps/remotes/remote-design-system/src/modern.runtime.ts',
|
|
87
87
|
'services/service-recommendations-effect/package.json',
|
|
88
88
|
'services/service-recommendations-effect/modern.config.ts',
|
|
89
89
|
'services/service-recommendations-effect/api/effect/index.ts',
|
|
@@ -101,11 +101,9 @@ const rootPackage = readJson('package.json');
|
|
|
101
101
|
const packageSource = readJson('.modernjs/ultramodern-package-source.json');
|
|
102
102
|
const skillsLock = readJson('.agents/skills-lock.json');
|
|
103
103
|
const expectedModernSpecifier =
|
|
104
|
-
packageSource.strategy === 'install'
|
|
105
|
-
? packageSource.modernPackages?.specifier
|
|
106
|
-
: 'workspace:*';
|
|
104
|
+
packageSource.strategy === 'install' ? packageSource.modernPackages?.specifier : 'workspace:*';
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
const expectedModernDependency = (packageName) => {
|
|
109
107
|
if (packageSource.strategy !== 'install') {
|
|
110
108
|
return 'workspace:*';
|
|
111
109
|
}
|
|
@@ -114,15 +112,14 @@ function expectedModernDependency(packageName) {
|
|
|
114
112
|
return aliasPackageName
|
|
115
113
|
? `npm:${aliasPackageName}@${expectedModernSpecifier}`
|
|
116
114
|
: expectedModernSpecifier;
|
|
117
|
-
}
|
|
115
|
+
};
|
|
118
116
|
|
|
119
117
|
assert(rootPackage.private === true, 'Root package must be private');
|
|
120
118
|
assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare presetUltramodern');
|
|
121
119
|
assert(rootPackage.packageManager === 'pnpm@11.1.2', 'Root must pin pnpm 11.1.2');
|
|
122
120
|
assert(rootPackage.engines?.pnpm === '>=11.0.0', 'Root must require pnpm >=11');
|
|
123
121
|
assert(
|
|
124
|
-
rootPackage.modernjs?.packageSource?.config ===
|
|
125
|
-
'./.modernjs/ultramodern-package-source.json',
|
|
122
|
+
rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json',
|
|
126
123
|
'Root must point to the UltraModern package source metadata',
|
|
127
124
|
);
|
|
128
125
|
assert(
|
|
@@ -138,46 +135,39 @@ assert(
|
|
|
138
135
|
'Generated shared packages must keep workspace:* links',
|
|
139
136
|
);
|
|
140
137
|
assert(
|
|
141
|
-
modernPackages.every(packageName =>
|
|
138
|
+
modernPackages.every((packageName) =>
|
|
142
139
|
packageSource.modernPackages?.packages?.includes(packageName),
|
|
143
140
|
),
|
|
144
141
|
'Package source metadata must list all Modern runtime/tooling packages',
|
|
145
142
|
);
|
|
146
143
|
assert(
|
|
147
|
-
expectedModernSpecifier &&
|
|
148
|
-
packageSource.modernPackages?.specifier === expectedModernSpecifier,
|
|
144
|
+
expectedModernSpecifier && packageSource.modernPackages?.specifier === expectedModernSpecifier,
|
|
149
145
|
'Package source metadata must provide a Modern package specifier',
|
|
150
146
|
);
|
|
151
|
-
|
|
152
|
-
rootPackage.scripts?.['ultramodern:check'] ===
|
|
153
|
-
'node ./scripts/validate-ultramodern-workspace.mjs',
|
|
154
|
-
'Root must expose the ultramodern:check script',
|
|
155
|
-
);
|
|
147
|
+
|
|
156
148
|
const requiredRootScripts = {
|
|
157
149
|
format: 'oxfmt .',
|
|
158
150
|
'format:check': 'oxfmt --check .',
|
|
151
|
+
'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
|
|
159
152
|
lint: 'oxlint .',
|
|
160
153
|
'lint:fix': 'oxlint . --fix',
|
|
161
|
-
'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
|
|
162
154
|
'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
|
|
155
|
+
'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
|
|
156
|
+
typecheck: `pnpm -r --filter "@${packageScope}/*" typecheck`,
|
|
157
|
+
'ultramodern:check': 'node ./scripts/validate-ultramodern-workspace.mjs',
|
|
163
158
|
};
|
|
164
159
|
for (const [scriptName, scriptCommand] of Object.entries(requiredRootScripts)) {
|
|
165
|
-
assert(
|
|
166
|
-
rootPackage.scripts?.[scriptName] === scriptCommand,
|
|
167
|
-
`Root must expose ${scriptName}`,
|
|
168
|
-
);
|
|
160
|
+
assert(rootPackage.scripts?.[scriptName] === scriptCommand, `Root must expose ${scriptName}`);
|
|
169
161
|
}
|
|
162
|
+
|
|
170
163
|
for (const dependency of [
|
|
171
164
|
'@effect/tsgo',
|
|
172
165
|
'@typescript/native-preview',
|
|
173
|
-
'oxlint',
|
|
174
166
|
'oxfmt',
|
|
167
|
+
'oxlint',
|
|
175
168
|
'ultracite',
|
|
176
169
|
]) {
|
|
177
|
-
assert(
|
|
178
|
-
rootPackage.devDependencies?.[dependency],
|
|
179
|
-
`Root must depend on ${dependency}`,
|
|
180
|
-
);
|
|
170
|
+
assert(rootPackage.devDependencies?.[dependency], `Root must depend on ${dependency}`);
|
|
181
171
|
}
|
|
182
172
|
|
|
183
173
|
const agentsInstructions = readText('AGENTS.md');
|
|
@@ -200,17 +190,17 @@ assert(
|
|
|
200
190
|
);
|
|
201
191
|
for (const skillName of baselineAgentSkills) {
|
|
202
192
|
assert(
|
|
203
|
-
skillsLock.baseline?.some(skill => skill.name === skillName),
|
|
193
|
+
skillsLock.baseline?.some((skill) => skill.name === skillName),
|
|
204
194
|
`Agent skills lock must include ${skillName}`,
|
|
205
195
|
);
|
|
206
|
-
const skillContent = readText(`.agents/skills/${skillName}/SKILL.md`);
|
|
207
196
|
assert(
|
|
208
|
-
|
|
197
|
+
readText(`.agents/skills/${skillName}/SKILL.md`).includes(`name: ${skillName}`),
|
|
209
198
|
`${skillName} must contain matching skill metadata`,
|
|
210
199
|
);
|
|
211
200
|
}
|
|
201
|
+
|
|
212
202
|
const privateSource = skillsLock.sources?.find(
|
|
213
|
-
source => source.repository === 'https://github.com/TechsioCZ/skills',
|
|
203
|
+
(source) => source.repository === 'https://github.com/TechsioCZ/skills',
|
|
214
204
|
);
|
|
215
205
|
assert(
|
|
216
206
|
privateSource?.install === 'clone-if-authorized',
|
|
@@ -218,7 +208,7 @@ assert(
|
|
|
218
208
|
);
|
|
219
209
|
for (const skillName of privateAgentSkills) {
|
|
220
210
|
assert(
|
|
221
|
-
privateSource.baseline?.some(skill => skill.name === skillName),
|
|
211
|
+
privateSource.baseline?.some((skill) => skill.name === skillName),
|
|
222
212
|
`Agent skills lock must allowlist private skill ${skillName}`,
|
|
223
213
|
);
|
|
224
214
|
}
|
|
@@ -232,31 +222,47 @@ const appPackagePaths = [
|
|
|
232
222
|
|
|
233
223
|
for (const packagePath of appPackagePaths) {
|
|
234
224
|
const packageJson = readJson(packagePath);
|
|
225
|
+
assert(
|
|
226
|
+
packageJson.dependencies?.['@modern-js/plugin-i18n'] ===
|
|
227
|
+
expectedModernDependency('@modern-js/plugin-i18n'),
|
|
228
|
+
`${packagePath} must use @modern-js/plugin-i18n through ${expectedModernDependency(
|
|
229
|
+
'@modern-js/plugin-i18n',
|
|
230
|
+
)}`,
|
|
231
|
+
);
|
|
235
232
|
assert(
|
|
236
233
|
packageJson.dependencies?.['@modern-js/plugin-tanstack'] ===
|
|
237
234
|
expectedModernDependency('@modern-js/plugin-tanstack'),
|
|
238
|
-
`${packagePath} must
|
|
235
|
+
`${packagePath} must use @modern-js/plugin-tanstack through ${expectedModernDependency(
|
|
236
|
+
'@modern-js/plugin-tanstack',
|
|
237
|
+
)}`,
|
|
239
238
|
);
|
|
240
239
|
assert(
|
|
241
240
|
packageJson.dependencies?.['@modern-js/runtime'] ===
|
|
242
241
|
expectedModernDependency('@modern-js/runtime'),
|
|
243
|
-
`${packagePath} must
|
|
242
|
+
`${packagePath} must use @modern-js/runtime through ${expectedModernDependency(
|
|
243
|
+
'@modern-js/runtime',
|
|
244
|
+
)}`,
|
|
244
245
|
);
|
|
245
246
|
assert(
|
|
246
247
|
packageJson.devDependencies?.['@modern-js/app-tools'] ===
|
|
247
248
|
expectedModernDependency('@modern-js/app-tools'),
|
|
248
|
-
`${packagePath} must
|
|
249
|
+
`${packagePath} must use @modern-js/app-tools through ${expectedModernDependency(
|
|
250
|
+
'@modern-js/app-tools',
|
|
251
|
+
)}`,
|
|
249
252
|
);
|
|
250
253
|
assert(
|
|
251
|
-
packageJson.dependencies?.[`@${packageScope}/shared-contracts`] ===
|
|
252
|
-
'workspace:*',
|
|
254
|
+
packageJson.dependencies?.[`@${packageScope}/shared-contracts`] === 'workspace:*',
|
|
253
255
|
`${packagePath} must link generated shared contracts through workspace:*`,
|
|
254
256
|
);
|
|
255
257
|
assert(
|
|
256
|
-
packageJson.dependencies?.[`@${packageScope}/shared-design-tokens`] ===
|
|
257
|
-
'workspace:*',
|
|
258
|
+
packageJson.dependencies?.[`@${packageScope}/shared-design-tokens`] === 'workspace:*',
|
|
258
259
|
`${packagePath} must link generated shared design tokens through workspace:*`,
|
|
259
260
|
);
|
|
261
|
+
assert(packageJson.dependencies?.i18next === '26.2.0', `${packagePath} must include i18next`);
|
|
262
|
+
assert(
|
|
263
|
+
packageJson.dependencies?.['react-i18next'] === '17.0.8',
|
|
264
|
+
`${packagePath} must include react-i18next`,
|
|
265
|
+
);
|
|
260
266
|
assert(
|
|
261
267
|
packageJson.dependencies?.['@tanstack/react-router'] === tanstackVersion,
|
|
262
268
|
`${packagePath} must use @tanstack/react-router ${tanstackVersion}`,
|
|
@@ -279,57 +285,85 @@ for (const configPath of [
|
|
|
279
285
|
]) {
|
|
280
286
|
const config = readText(configPath);
|
|
281
287
|
assert(config.includes('presetUltramodern('), `${configPath} must use presetUltramodern`);
|
|
288
|
+
assert(config.includes('i18nPlugin('), `${configPath} must enable plugin-i18n`);
|
|
282
289
|
assert(config.includes('tanstackRouterPlugin()'), `${configPath} must enable plugin-tanstack`);
|
|
283
|
-
assert(
|
|
290
|
+
assert(
|
|
291
|
+
config.includes('moduleFederationPlugin()'),
|
|
292
|
+
`${configPath} must enable Module Federation`,
|
|
293
|
+
);
|
|
284
294
|
}
|
|
285
295
|
|
|
286
296
|
const shellMf = readText('apps/shell-super-app/module-federation.config.ts');
|
|
287
297
|
assert(shellMf.includes("name: 'shellSuperApp'"), 'Shell MF config must name the shell');
|
|
288
|
-
assert(
|
|
289
|
-
|
|
290
|
-
|
|
298
|
+
assert(
|
|
299
|
+
shellMf.includes('remoteCommerce@http://localhost:3021/mf-manifest.json'),
|
|
300
|
+
'Shell must reference commerce remote',
|
|
301
|
+
);
|
|
302
|
+
assert(
|
|
303
|
+
shellMf.includes('remoteIdentity@http://localhost:3022/mf-manifest.json'),
|
|
304
|
+
'Shell must reference identity remote',
|
|
305
|
+
);
|
|
306
|
+
assert(
|
|
307
|
+
shellMf.includes('remoteDesignSystem@http://localhost:3023/mf-manifest.json'),
|
|
308
|
+
'Shell must reference design-system remote',
|
|
309
|
+
);
|
|
291
310
|
|
|
292
311
|
const commerceMf = readText('apps/remotes/remote-commerce/module-federation.config.ts');
|
|
293
312
|
assert(commerceMf.includes("name: 'remoteCommerce'"), 'Commerce remote MF name is missing');
|
|
294
|
-
assert(commerceMf.includes('
|
|
313
|
+
assert(commerceMf.includes("'./Widget'"), 'Commerce remote must expose a widget');
|
|
295
314
|
|
|
296
315
|
const designMf = readText('apps/remotes/remote-design-system/module-federation.config.ts');
|
|
297
316
|
assert(designMf.includes("name: 'remoteDesignSystem'"), 'Design-system remote MF name is missing');
|
|
298
|
-
assert(designMf.includes('
|
|
299
|
-
assert(designMf.includes('
|
|
317
|
+
assert(designMf.includes("'./Button'"), 'Design-system remote must expose Button');
|
|
318
|
+
assert(designMf.includes("'./tokens'"), 'Design-system remote must expose tokens');
|
|
300
319
|
|
|
301
320
|
const servicePackage = readJson('services/service-recommendations-effect/package.json');
|
|
302
321
|
assert(
|
|
303
322
|
servicePackage.dependencies?.['@modern-js/runtime'] ===
|
|
304
323
|
expectedModernDependency('@modern-js/runtime'),
|
|
305
|
-
`Effect service must use @modern-js/runtime through ${expectedModernDependency(
|
|
324
|
+
`Effect service must use @modern-js/runtime through ${expectedModernDependency(
|
|
325
|
+
'@modern-js/runtime',
|
|
326
|
+
)}`,
|
|
306
327
|
);
|
|
307
328
|
assert(
|
|
308
329
|
servicePackage.devDependencies?.['@modern-js/app-tools'] ===
|
|
309
330
|
expectedModernDependency('@modern-js/app-tools'),
|
|
310
|
-
`Effect service must use @modern-js/app-tools through ${expectedModernDependency(
|
|
331
|
+
`Effect service must use @modern-js/app-tools through ${expectedModernDependency(
|
|
332
|
+
'@modern-js/app-tools',
|
|
333
|
+
)}`,
|
|
311
334
|
);
|
|
312
335
|
assert(
|
|
313
336
|
servicePackage.devDependencies?.['@modern-js/plugin-bff'] ===
|
|
314
337
|
expectedModernDependency('@modern-js/plugin-bff'),
|
|
315
|
-
`Effect service must use @modern-js/plugin-bff through ${expectedModernDependency(
|
|
338
|
+
`Effect service must use @modern-js/plugin-bff through ${expectedModernDependency(
|
|
339
|
+
'@modern-js/plugin-bff',
|
|
340
|
+
)}`,
|
|
316
341
|
);
|
|
317
342
|
assert(
|
|
318
|
-
servicePackage.dependencies?.[`@${packageScope}/shared-effect-api`] ===
|
|
319
|
-
'workspace:*',
|
|
343
|
+
servicePackage.dependencies?.[`@${packageScope}/shared-effect-api`] === 'workspace:*',
|
|
320
344
|
'Effect service must link generated shared Effect API through workspace:*',
|
|
321
345
|
);
|
|
322
346
|
|
|
323
347
|
const serviceConfig = readText('services/service-recommendations-effect/modern.config.ts');
|
|
324
|
-
assert(
|
|
348
|
+
assert(
|
|
349
|
+
serviceConfig.includes("runtimeFramework: 'effect'"),
|
|
350
|
+
'Effect service must use Effect runtime',
|
|
351
|
+
);
|
|
325
352
|
assert(serviceConfig.includes('bffPlugin()'), 'Effect service must enable bffPlugin');
|
|
326
353
|
|
|
327
354
|
const serviceEntry = readText('services/service-recommendations-effect/api/effect/index.ts');
|
|
328
|
-
assert(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
assert(
|
|
355
|
+
assert(
|
|
356
|
+
serviceEntry.includes('defineEffectBff'),
|
|
357
|
+
'Effect service must expose defineEffectBff placeholder',
|
|
358
|
+
);
|
|
359
|
+
assert(
|
|
360
|
+
serviceEntry.includes('recommendationsEffectApi'),
|
|
361
|
+
'Effect service must use shared recommendations API',
|
|
362
|
+
);
|
|
363
|
+
assert(
|
|
364
|
+
readText('services/service-recommendations-effect/shared/effect/api.ts').includes('HttpApi.make'),
|
|
365
|
+
'Effect shared API placeholder must define HttpApi',
|
|
366
|
+
);
|
|
333
367
|
|
|
334
368
|
const topology = readJson('topology/reference-topology.json');
|
|
335
369
|
assert(topology.preset === 'presetUltramodern', 'Topology must reference presetUltramodern');
|
|
@@ -338,27 +372,26 @@ assert(topology.shell?.remoteRefs?.length === 3, 'Topology shell must reference
|
|
|
338
372
|
assert(topology.remotes?.length === 3, 'Topology must contain three remotes');
|
|
339
373
|
assert(
|
|
340
374
|
topology.remotes.some(
|
|
341
|
-
remote =>
|
|
342
|
-
remote.id === 'remote-design-system' &&
|
|
343
|
-
remote.kind === 'horizontal-design-system',
|
|
375
|
+
(remote) => remote.id === 'remote-design-system' && remote.kind === 'horizontal-design-system',
|
|
344
376
|
),
|
|
345
377
|
'Topology must contain the horizontal design-system remote',
|
|
346
378
|
);
|
|
347
|
-
assert(
|
|
379
|
+
assert(
|
|
380
|
+
topology.effectServices?.[0]?.runtime === 'effect',
|
|
381
|
+
'Topology must contain an Effect service',
|
|
382
|
+
);
|
|
348
383
|
assert(topology.sharedPackages?.length === 3, 'Topology must contain shared package placeholders');
|
|
349
384
|
|
|
350
385
|
const ownership = readJson('topology/ownership.json');
|
|
351
386
|
assert(
|
|
352
387
|
ownership.owners?.some(
|
|
353
|
-
owner =>
|
|
354
|
-
owner.id === 'remote-commerce' &&
|
|
355
|
-
owner.ownership?.team === 'commerce-experience',
|
|
388
|
+
(owner) => owner.id === 'remote-commerce' && owner.ownership?.team === 'commerce-experience',
|
|
356
389
|
),
|
|
357
390
|
'Ownership metadata must retain commerce owner',
|
|
358
391
|
);
|
|
359
392
|
assert(
|
|
360
393
|
ownership.owners?.some(
|
|
361
|
-
owner =>
|
|
394
|
+
(owner) =>
|
|
362
395
|
owner.id === 'service-recommendations-effect' &&
|
|
363
396
|
owner.package === `@${packageScope}/service-recommendations-effect`,
|
|
364
397
|
),
|
|
@@ -379,18 +412,15 @@ assert(
|
|
|
379
412
|
'Template manifest must retain the Rstack agent skills commit',
|
|
380
413
|
);
|
|
381
414
|
assert(
|
|
382
|
-
baselineAgentSkills.every(skillName =>
|
|
383
|
-
manifest.agentSkills?.baseline?.includes(skillName),
|
|
384
|
-
),
|
|
415
|
+
baselineAgentSkills.every((skillName) => manifest.agentSkills?.baseline?.includes(skillName)),
|
|
385
416
|
'Template manifest must list every baseline agent skill',
|
|
386
417
|
);
|
|
387
418
|
assert(
|
|
388
|
-
manifest.agentSkills?.privateSource?.repository ===
|
|
389
|
-
'https://github.com/TechsioCZ/skills',
|
|
419
|
+
manifest.agentSkills?.privateSource?.repository === 'https://github.com/TechsioCZ/skills',
|
|
390
420
|
'Template manifest must retain the private TechsioCZ skill source',
|
|
391
421
|
);
|
|
392
422
|
assert(
|
|
393
|
-
privateAgentSkills.every(skillName =>
|
|
423
|
+
privateAgentSkills.every((skillName) =>
|
|
394
424
|
manifest.agentSkills?.privateSource?.baseline?.includes(skillName),
|
|
395
425
|
),
|
|
396
426
|
'Template manifest must list every private agent skill allowlist entry',
|