@bleedingdev/modern-js-create 3.2.0-ultramodern.102 → 3.2.0-ultramodern.104

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.
@@ -1,3 +1,118 @@
1
- declare const i18n: any;
2
- declare const localeKeys: any;
1
+ import { I18n } from '@modern-js/i18n-utils';
2
+ declare const i18n: I18n;
3
+ declare const localeKeys: {
4
+ prompt: {
5
+ projectName: string;
6
+ };
7
+ error: {
8
+ projectNameEmpty: string;
9
+ directoryExists: string;
10
+ invalidRouter: string;
11
+ invalidBffRuntime: string;
12
+ createFailed: string;
13
+ };
14
+ message: {
15
+ welcome: string;
16
+ success: string;
17
+ nextSteps: string;
18
+ step1: string;
19
+ step2: string;
20
+ step3: string;
21
+ };
22
+ help: {
23
+ title: string;
24
+ description: string;
25
+ usage: string;
26
+ usageExample: string;
27
+ options: string;
28
+ optionHelp: string;
29
+ optionVersion: string;
30
+ optionLang: string;
31
+ optionRouter: string;
32
+ optionBff: string;
33
+ optionBffRuntime: string;
34
+ optionTailwind: string;
35
+ optionWorkspace: string;
36
+ optionUltramodernWorkspace: string;
37
+ optionUltramodernPackageSource: string;
38
+ optionUltramodernPackageScope: string;
39
+ optionUltramodernPackageNamePrefix: string;
40
+ optionVertical: string;
41
+ optionSub: string;
42
+ examples: string;
43
+ example1: string;
44
+ example2: string;
45
+ example3: string;
46
+ example4: string;
47
+ example5: string;
48
+ example6: string;
49
+ example7: string;
50
+ example8: string;
51
+ example9: string;
52
+ example10: string;
53
+ example11: string;
54
+ example12: string;
55
+ moreInfo: string;
56
+ };
57
+ version: {
58
+ message: string;
59
+ };
60
+ } | {
61
+ prompt: {
62
+ projectName: string;
63
+ };
64
+ error: {
65
+ projectNameEmpty: string;
66
+ directoryExists: string;
67
+ invalidRouter: string;
68
+ invalidBffRuntime: string;
69
+ createFailed: string;
70
+ };
71
+ message: {
72
+ welcome: string;
73
+ success: string;
74
+ nextSteps: string;
75
+ step1: string;
76
+ step2: string;
77
+ step3: string;
78
+ };
79
+ help: {
80
+ title: string;
81
+ description: string;
82
+ usage: string;
83
+ usageExample: string;
84
+ options: string;
85
+ optionHelp: string;
86
+ optionVersion: string;
87
+ optionLang: string;
88
+ optionRouter: string;
89
+ optionBff: string;
90
+ optionBffRuntime: string;
91
+ optionTailwind: string;
92
+ optionWorkspace: string;
93
+ optionUltramodernWorkspace: string;
94
+ optionUltramodernPackageSource: string;
95
+ optionUltramodernPackageScope: string;
96
+ optionUltramodernPackageNamePrefix: string;
97
+ optionVertical: string;
98
+ optionSub: string;
99
+ examples: string;
100
+ example1: string;
101
+ example2: string;
102
+ example3: string;
103
+ example4: string;
104
+ example5: string;
105
+ example6: string;
106
+ example7: string;
107
+ example8: string;
108
+ example9: string;
109
+ example10: string;
110
+ example11: string;
111
+ example12: string;
112
+ moreInfo: string;
113
+ };
114
+ version: {
115
+ message: string;
116
+ };
117
+ };
3
118
  export { i18n, localeKeys };
@@ -0,0 +1,28 @@
1
+ export declare const WORKSPACE_PACKAGE_VERSION = "workspace:*";
2
+ export declare const BLEEDINGDEV_CREATE_PACKAGE = "@bleedingdev/modern-js-create";
3
+ export declare const BLEEDINGDEV_PACKAGE_SCOPE = "bleedingdev";
4
+ export declare const BLEEDINGDEV_PACKAGE_NAME_PREFIX = "modern-js-";
5
+ export declare const BLEEDINGDEV_FRAMEWORK_VERSION_ENV = "MODERN_CREATE_ULTRAMODERN_FRAMEWORK_VERSION";
6
+ export declare const ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES: readonly ['@modern-js/runtime', '@modern-js/app-tools', '@modern-js/tsconfig', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/plugin-bff', '@modern-js/adapter-rstest'];
7
+ export declare const ULTRAMODERN_WORKSPACE_MODERN_PACKAGES: readonly ['@modern-js/app-tools', '@modern-js/plugin-bff', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/runtime'];
8
+ export type UltramodernPackageSourceStrategy = 'workspace' | 'install';
9
+ export type ResolvedUltramodernPackageSource = {
10
+ strategy: UltramodernPackageSourceStrategy;
11
+ modernPackageVersion: string;
12
+ registry?: string;
13
+ aliasScope?: string;
14
+ aliasPackageNamePrefix?: string;
15
+ };
16
+ export type UltramodernModernPackagesMetadata = {
17
+ packages: string[];
18
+ specifier: string;
19
+ registry?: string;
20
+ aliases?: Record<string, string>;
21
+ };
22
+ export declare function modernPackageVersion(packageSource: ResolvedUltramodernPackageSource): string;
23
+ export declare function modernAliasPackageName(packageName: string, packageSource: ResolvedUltramodernPackageSource): string;
24
+ export declare function modernPackageSpecifier(packageName: string, packageSource: ResolvedUltramodernPackageSource): string;
25
+ export declare function modernPackageAliases(packageNames: readonly string[], packageSource: ResolvedUltramodernPackageSource): Record<string, string> | undefined;
26
+ export declare function createModernPackagesMetadata(packageNames: readonly string[], packageSource: ResolvedUltramodernPackageSource, options?: {
27
+ includeAliases?: boolean;
28
+ }): UltramodernModernPackagesMetadata;
@@ -1,4 +1,4 @@
1
- type UltramodernPackageSourceStrategy = 'workspace' | 'install';
1
+ import { type UltramodernPackageSourceStrategy } from './ultramodern-package-source';
2
2
  export type UltramodernWorkspaceOptions = {
3
3
  targetDir: string;
4
4
  packageName: string;
@@ -28,4 +28,3 @@ export declare const ultramodernWorkspaceVersions: {
28
28
  tailwind: string;
29
29
  tailwindPostcss: string;
30
30
  };
31
- export {};
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.2.0-ultramodern.102",
24
+ "version": "3.2.0-ultramodern.104",
25
25
  "types": "./dist/types/index.d.ts",
26
26
  "main": "./dist/index.js",
27
27
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "@types/node": "^25.9.1",
42
42
  "@typescript/native-preview": "7.0.0-dev.20260527.2",
43
43
  "tsx": "^4.22.3",
44
- "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.102"
44
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.104"
45
45
  },
46
46
  "publishConfig": {
47
47
  "registry": "https://registry.npmjs.org/",
@@ -54,6 +54,6 @@
54
54
  "start": "node ./dist/index.js"
55
55
  },
56
56
  "ultramodern": {
57
- "frameworkVersion": "3.2.0-ultramodern.102"
57
+ "frameworkVersion": "3.2.0-ultramodern.104"
58
58
  }
59
59
  }
@@ -49,6 +49,7 @@ The default app is intentionally monolith-friendly:
49
49
  | --- | --- |
50
50
  | App routes | Locale-prefixed pages under `src/routes/[lang]` |
51
51
  | Copy | English and Czech resources in `config/public/locales` |
52
+ | Web defaults | Local favicon/logo assets, localized metadata, semantic starter markup |
52
53
  | Styling | App-local CSS, with Tailwind files only when selected |
53
54
  | Server logic | Optional BFF entrypoints under `api/` |
54
55
  | Tests | Rstest smoke coverage in `tests/` |
@@ -66,6 +67,11 @@ real routes, actions, and API calls. Put user-visible text in
66
67
  `config/public/locales/<lang>/translation.json`, then render it through
67
68
  `react-i18next` or `@modern-js/plugin-i18n/runtime`.
68
69
 
70
+ The starter keeps favicon and logo assets local in `config/favicon.svg` and
71
+ `config/public/assets/ultramodern-logo.svg`. Replace those files when your app
72
+ has product branding. The localized page title and description live in the same
73
+ translation resources as the visible UI copy.
74
+
69
75
  Tune the preset in `modern.config.ts`. Production builds require
70
76
  `MODERN_PUBLIC_SITE_URL` so canonical and `hreflang` URLs use your deployed
71
77
  origin. The local fallback is `http://localhost:8080`.
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="UltraModern.js">
2
+ <rect width="64" height="64" rx="12" fill="#111827"/>
3
+ <path fill="#35d399" d="M14 17h9v21c0 5 3 8 9 8s9-3 9-8V17h9v22c0 11-8 18-18 18s-18-7-18-18V17Z"/>
4
+ <path fill="#ffffff" d="M28 17h8v25h-8z"/>
5
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" role="img" aria-label="UltraModern.js logo">
2
+ <rect width="160" height="160" rx="28" fill="#111827"/>
3
+ <path fill="#35d399" d="M42 36h22v56c0 22 14 34 36 34s36-12 36-34V36h22v57c0 35-24 57-58 57S42 128 42 93V36Z"/>
4
+ <path fill="#ffffff" d="M76 36h20v74H76z"/>
5
+ <path fill="#8be8ff" d="M112 36h18v74h-18z"/>
6
+ </svg>
@@ -33,7 +33,12 @@
33
33
  "switcher": "Jazyk"
34
34
  },
35
35
  "logoAlt": "Logo UltraModern.js",
36
+ "meta": {
37
+ "description": "Lokalizovany starter aplikace UltraModern.js se silnymi vychozimi hodnotami pro komplexni produkty.",
38
+ "title": "UltraModern.js Starter"
39
+ },
36
40
  "name": "Jednoduchy starter aplikace",
41
+ "skipLink": "Preskocit na obsah",
37
42
  "title": "UltraModern.js Starter"
38
43
  }
39
44
  }
@@ -33,7 +33,12 @@
33
33
  "switcher": "Language"
34
34
  },
35
35
  "logoAlt": "UltraModern.js Logo",
36
+ "meta": {
37
+ "description": "A localized UltraModern.js app starter with strong defaults for complex products.",
38
+ "title": "UltraModern.js Starter"
39
+ },
36
40
  "name": "Simple app starter",
41
+ "skipLink": "Skip to content",
37
42
  "title": "UltraModern.js Starter"
38
43
  }
39
44
  }
@@ -39,7 +39,13 @@ export default defineConfig(
39
39
  {{/if}} runtimeFramework: '{{bffRuntime}}',
40
40
  },
41
41
 
42
- {{/if}} plugins: [
42
+ {{/if}} html: {
43
+ meta: {
44
+ viewport: 'width=device-width, initial-scale=1.0, viewport-fit=cover',
45
+ },
46
+ title: 'UltraModern.js Starter',
47
+ },
48
+ plugins: [
43
49
  appTools(),
44
50
  i18nPlugin({
45
51
  localeDetection: {
@@ -64,8 +70,8 @@ export default defineConfig(
64
70
  enableBffRequestId,
65
71
  enableModuleFederationSSR,
66
72
  enableTelemetryExporters,
67
- telemetryFailLoudStartup,
68
73
  ...(typeof otlpEndpoint === 'string' ? { otlpEndpoint } : {}),
74
+ telemetryFailLoudStartup,
69
75
  ...(typeof victoriaMetricsEndpoint === 'string' ? { victoriaMetricsEndpoint } : {}),
70
76
  },
71
77
  ),
@@ -7,6 +7,97 @@ const ignoredDirectories = new Set(['.modern', '.modernjs', 'dist', 'node_module
7
7
  const visibleAttributePattern =
8
8
  /\s(?:aria-label|alt|placeholder|title)=["']([^"']*[A-Za-z][^"']*)["']/gu;
9
9
  const jsxTextPattern = />([^<>{}]*[A-Za-z][^<>{}]*)</gu;
10
+ const jsxIntrinsicTags = new Set([
11
+ 'a',
12
+ 'abbr',
13
+ 'address',
14
+ 'area',
15
+ 'article',
16
+ 'aside',
17
+ 'audio',
18
+ 'b',
19
+ 'blockquote',
20
+ 'body',
21
+ 'br',
22
+ 'button',
23
+ 'canvas',
24
+ 'caption',
25
+ 'cite',
26
+ 'code',
27
+ 'col',
28
+ 'colgroup',
29
+ 'data',
30
+ 'datalist',
31
+ 'dd',
32
+ 'del',
33
+ 'details',
34
+ 'dfn',
35
+ 'dialog',
36
+ 'div',
37
+ 'dl',
38
+ 'dt',
39
+ 'em',
40
+ 'fieldset',
41
+ 'figcaption',
42
+ 'figure',
43
+ 'footer',
44
+ 'form',
45
+ 'h1',
46
+ 'h2',
47
+ 'h3',
48
+ 'h4',
49
+ 'h5',
50
+ 'h6',
51
+ 'head',
52
+ 'header',
53
+ 'hr',
54
+ 'html',
55
+ 'i',
56
+ 'iframe',
57
+ 'img',
58
+ 'input',
59
+ 'label',
60
+ 'legend',
61
+ 'li',
62
+ 'link',
63
+ 'main',
64
+ 'mark',
65
+ 'menu',
66
+ 'meta',
67
+ 'meter',
68
+ 'nav',
69
+ 'ol',
70
+ 'option',
71
+ 'p',
72
+ 'picture',
73
+ 'pre',
74
+ 'progress',
75
+ 'q',
76
+ 'script',
77
+ 'section',
78
+ 'select',
79
+ 'small',
80
+ 'source',
81
+ 'span',
82
+ 'strong',
83
+ 'style',
84
+ 'summary',
85
+ 'svg',
86
+ 'table',
87
+ 'tbody',
88
+ 'td',
89
+ 'template',
90
+ 'textarea',
91
+ 'tfoot',
92
+ 'th',
93
+ 'thead',
94
+ 'time',
95
+ 'title',
96
+ 'tr',
97
+ 'u',
98
+ 'ul',
99
+ 'video',
100
+ ]);
10
101
 
11
102
  const collectFiles = (directory) => {
12
103
  if (!fs.existsSync(directory)) {
@@ -37,6 +128,24 @@ const isCodeElementText = (content, index) => {
37
128
  }
38
129
  return /^<code(?:\s|>)/u.test(content.slice(tagStart, index));
39
130
  };
131
+ const isJsxTagEnd = (content, index) => {
132
+ const tagStart = content.lastIndexOf('<', index);
133
+ if (tagStart === -1 || content.slice(tagStart + 1, index).includes('<')) {
134
+ return false;
135
+ }
136
+ const match = content
137
+ .slice(tagStart, index + 1)
138
+ .match(/^<\/?\s*([A-Za-z][\w:.-]*)\b[^<>]*>$/u);
139
+ if (!match) {
140
+ return false;
141
+ }
142
+ const [, tagName] = match;
143
+ return (
144
+ /^[A-Z]/u.test(tagName) ||
145
+ tagName.includes('-') ||
146
+ jsxIntrinsicTags.has(tagName)
147
+ );
148
+ };
40
149
  const isIgnoredLine = (content, index) => {
41
150
  const lineStart = content.lastIndexOf('\n', index) + 1;
42
151
  const lineEnd = content.indexOf('\n', index);
@@ -54,20 +163,23 @@ const violations = [];
54
163
  for (const filePath of scanRoots.flatMap(collectFiles)) {
55
164
  const content = fs.readFileSync(filePath, 'utf-8');
56
165
  for (const match of content.matchAll(visibleAttributePattern)) {
166
+ const [, visibleText] = match;
57
167
  if (!isIgnoredLine(content, match.index ?? 0)) {
58
168
  violations.push({
59
169
  filePath,
60
170
  line: lineNumberForIndex(content, match.index ?? 0),
61
- text: match[1].trim(),
171
+ text: visibleText.trim(),
62
172
  });
63
173
  }
64
174
  }
65
175
 
66
176
  for (const match of content.matchAll(jsxTextPattern)) {
67
- const text = match[1].replaceAll(/\s+/gu, ' ').trim();
177
+ const [, jsxText] = match;
178
+ const text = jsxText.replaceAll(/\s+/gu, ' ').trim();
68
179
  if (
69
180
  text &&
70
181
  !isIgnoredLine(content, match.index ?? 0) &&
182
+ isJsxTagEnd(content, match.index ?? 0) &&
71
183
  !isCodeElementText(content, match.index ?? 0)
72
184
  ) {
73
185
  violations.push({
@@ -8,6 +8,9 @@ const packageSourcePath = path.resolve(
8
8
  process.cwd(),
9
9
  '.modernjs/ultramodern-package-source.json',
10
10
  );
11
+ const readText = (relativePath) =>
12
+ fs.readFileSync(path.resolve(process.cwd(), relativePath), 'utf-8');
13
+ const readJson = (relativePath) => JSON.parse(readText(relativePath));
11
14
  const readPnpmConfig = (key) => {
12
15
  const env = Object.fromEntries(
13
16
  Object.entries(process.env).filter(
@@ -109,6 +112,8 @@ const requiredPaths = [
109
112
  {{/if}}
110
113
  'config/public/locales/en/translation.json',
111
114
  'config/public/locales/cs/translation.json',
115
+ 'config/favicon.svg',
116
+ 'config/public/assets/ultramodern-logo.svg',
112
117
  'src/modern-app-env.d.ts',
113
118
  'src/routes/index.css',
114
119
  'src/routes/layout.tsx',
@@ -241,6 +246,11 @@ const packageJson = JSON.parse(
241
246
  fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
242
247
  );
243
248
  const packageSource = JSON.parse(fs.readFileSync(packageSourcePath, 'utf-8'));
249
+ const routePage = readText('src/routes/[lang]/page.tsx');
250
+ const routeCss = readText('src/routes/index.css');
251
+ const modernConfig = readText('modern.config.ts');
252
+ const enLocale = readJson('config/public/locales/en/translation.json');
253
+ const csLocale = readJson('config/public/locales/cs/translation.json');
244
254
  const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
245
255
  if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
246
256
  console.error('package.json contains unresolved template markers');
@@ -250,6 +260,89 @@ if (JSON.stringify(packageSource).includes(unresolvedTemplateMarker)) {
250
260
  console.error('package source metadata contains unresolved template markers');
251
261
  process.exit(1);
252
262
  }
263
+
264
+ for (const [fileName, text] of [
265
+ ['src/routes/[lang]/page.tsx', routePage],
266
+ ['src/routes/index.css', routeCss],
267
+ ['modern.config.ts', modernConfig],
268
+ ]) {
269
+ if (text.includes('lf3-static.bytednsdoc.com')) {
270
+ console.error(`${fileName} must not depend on remote starter assets`);
271
+ process.exit(1);
272
+ }
273
+ }
274
+
275
+ for (const requiredSnippet of [
276
+ '<Helmet',
277
+ 'htmlAttributes={{',
278
+ 'dir: languageDirections[currentLanguage]',
279
+ 'lang: currentLanguage',
280
+ '<title>{pageTitle}</title>',
281
+ '<meta name="description" content={pageDescription} />',
282
+ '<main id="starter-main" className="starter-main">',
283
+ '<h1 id="starter-heading" className="title">',
284
+ 'src="/assets/ultramodern-logo.svg"',
285
+ 'height={96}',
286
+ 'width={96}',
287
+ '<span aria-hidden="true" className="arrow-right" />',
288
+ ]) {
289
+ if (!routePage.includes(requiredSnippet)) {
290
+ console.error(`Generated route page must retain starter correctness snippet: ${requiredSnippet}`);
291
+ process.exit(1);
292
+ }
293
+ }
294
+
295
+ for (const forbiddenSnippet of ['src="https://', '<div className="title">']) {
296
+ if (routePage.includes(forbiddenSnippet)) {
297
+ console.error(`Generated route page must not contain ${forbiddenSnippet}`);
298
+ process.exit(1);
299
+ }
300
+ }
301
+
302
+ for (const requiredSnippet of [
303
+ 'width=device-width, initial-scale=1.0, viewport-fit=cover',
304
+ "title: 'UltraModern.js Starter'",
305
+ ]) {
306
+ if (!modernConfig.includes(requiredSnippet)) {
307
+ console.error(`modern.config.ts must retain ${requiredSnippet}`);
308
+ process.exit(1);
309
+ }
310
+ }
311
+ if (modernConfig.includes('user-scalable=no') || modernConfig.includes('maximum-scale')) {
312
+ console.error('modern.config.ts must not disable user zoom');
313
+ process.exit(1);
314
+ }
315
+
316
+ for (const requiredSnippet of [
317
+ 'min-block-size: 100dvh',
318
+ 'grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr))',
319
+ ':focus-visible',
320
+ '@media (prefers-reduced-motion: reduce)',
321
+ '.skip-link',
322
+ ]) {
323
+ if (!routeCss.includes(requiredSnippet)) {
324
+ console.error(`Starter CSS must retain ${requiredSnippet}`);
325
+ process.exit(1);
326
+ }
327
+ }
328
+ if (routeCss.includes('width: 1100px') || /\.card:focus(?!-visible)/u.test(routeCss)) {
329
+ console.error('Starter CSS must not reintroduce fixed grid width or focus transform styling');
330
+ process.exit(1);
331
+ }
332
+
333
+ for (const [localeName, locale] of [
334
+ ['en', enLocale],
335
+ ['cs', csLocale],
336
+ ]) {
337
+ if (
338
+ typeof locale.home?.meta?.title !== 'string' ||
339
+ typeof locale.home?.meta?.description !== 'string' ||
340
+ typeof locale.home?.skipLink !== 'string'
341
+ ) {
342
+ console.error(`${localeName} locale must include starter metadata and skip-link copy`);
343
+ process.exit(1);
344
+ }
345
+ }
253
346
  {{#unless isSubproject}}
254
347
  const skillsLock = JSON.parse(
255
348
  fs.readFileSync(path.resolve(process.cwd(), '.agents/skills-lock.json'), 'utf-8'),
@@ -388,6 +481,11 @@ if (
388
481
  process.exit(1);
389
482
  }
390
483
 
484
+ if (packageJson.modernjs?.packageSource?.strategy !== packageSource.strategy) {
485
+ console.error('package.json package source strategy must match package source metadata');
486
+ process.exit(1);
487
+ }
488
+
391
489
  if (packageSource.schemaVersion !== 1) {
392
490
  console.error('Package source metadata must use schemaVersion 1');
393
491
  process.exit(1);
@@ -403,21 +501,33 @@ if (packageSource.strategy !== 'workspace' && packageSource.strategy !== 'instal
403
501
  process.exit(1);
404
502
  }
405
503
 
406
- const expectedModernPackages = [
407
- '@modern-js/runtime',
408
- '@modern-js/app-tools',
409
- '@modern-js/tsconfig',
410
- '@modern-js/plugin-i18n',
411
- '@modern-js/plugin-tanstack',
412
- '@modern-js/plugin-bff',
413
- '@modern-js/adapter-rstest',
414
- ];
504
+ const declaredModernPackages = packageSource.modernPackages?.packages;
505
+ if (!Array.isArray(declaredModernPackages) || declaredModernPackages.length === 0) {
506
+ console.error('Package source metadata must declare the generated Modern package cohort');
507
+ process.exit(1);
508
+ }
415
509
 
416
- for (const packageName of expectedModernPackages) {
417
- if (!packageSource.modernPackages?.packages?.includes(packageName)) {
418
- console.error(`Package source metadata must include ${packageName}`);
419
- process.exit(1);
420
- }
510
+ const invalidModernPackages = declaredModernPackages.filter(
511
+ (packageName) => typeof packageName !== 'string' || !packageName.startsWith('@modern-js/'),
512
+ );
513
+ if (invalidModernPackages.length > 0) {
514
+ console.error(`Package source metadata contains invalid Modern packages: ${invalidModernPackages.join(', ')}`);
515
+ process.exit(1);
516
+ }
517
+
518
+ const packageJsonModernDependencies = [
519
+ ...new Set(
520
+ ['dependencies', 'devDependencies']
521
+ .flatMap((section) => Object.keys(packageJson[section] ?? {}))
522
+ .filter((packageName) => packageName.startsWith('@modern-js/')),
523
+ ),
524
+ ];
525
+ const missingMetadataPackages = packageJsonModernDependencies.filter(
526
+ (packageName) => !declaredModernPackages.includes(packageName),
527
+ );
528
+ if (missingMetadataPackages.length > 0) {
529
+ console.error(`Package source metadata must include package.json Modern dependencies: ${missingMetadataPackages.join(', ')}`);
530
+ process.exit(1);
421
531
  }
422
532
 
423
533
  const expectedModernSpecifier = packageSource.modernPackages?.specifier;
@@ -442,32 +552,15 @@ const expectedModernDependency = (packageName) => {
442
552
  : expectedModernSpecifier;
443
553
  };
444
554
 
445
- for (const packageName of [
446
- '@modern-js/runtime',
447
- '@modern-js/plugin-i18n',
448
- '@modern-js/plugin-tanstack',
449
- ]) {
450
- if (
451
- packageJson.dependencies?.[packageName] &&
452
- packageJson.dependencies[packageName] !== expectedModernDependency(packageName)
453
- ) {
454
- console.error(`Dependency ${packageName} must match package source metadata`);
455
- process.exit(1);
456
- }
457
- }
458
-
459
- for (const packageName of [
460
- '@modern-js/app-tools',
461
- '@modern-js/adapter-rstest',
462
- '@modern-js/tsconfig',
463
- '@modern-js/plugin-bff',
464
- ]) {
465
- if (
466
- packageJson.devDependencies?.[packageName] &&
467
- packageJson.devDependencies[packageName] !== expectedModernDependency(packageName)
468
- ) {
469
- console.error(`Dev dependency ${packageName} must match package source metadata`);
470
- process.exit(1);
555
+ for (const section of ['dependencies', 'devDependencies']) {
556
+ for (const packageName of declaredModernPackages) {
557
+ if (
558
+ packageJson[section]?.[packageName] &&
559
+ packageJson[section][packageName] !== expectedModernDependency(packageName)
560
+ ) {
561
+ console.error(`${section}.${packageName} must match package source metadata`);
562
+ process.exit(1);
563
+ }
471
564
  }
472
565
  }
473
566