@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.
- package/README.md +9 -3
- package/dist/index.js +709 -128
- package/dist/types/locale/index.d.ts +117 -2
- package/dist/types/ultramodern-package-source.d.ts +28 -0
- package/dist/types/ultramodern-workspace.d.ts +1 -2
- package/package.json +3 -3
- package/template/README.md +6 -0
- package/template/config/favicon.svg +5 -0
- package/template/config/public/assets/ultramodern-logo.svg +6 -0
- package/template/config/public/locales/cs/translation.json +5 -0
- package/template/config/public/locales/en/translation.json +5 -0
- package/template/modern.config.ts.handlebars +8 -2
- package/template/scripts/check-i18n-strings.mjs +114 -2
- package/template/scripts/validate-ultramodern.mjs.handlebars +133 -40
- package/template/src/routes/[lang]/page.tsx.handlebars +39 -41
- package/template/src/routes/index.css.handlebars +192 -55
- package/template/tests/ultramodern.contract.test.ts.handlebars +71 -8
- package/template/tsconfig.json +1 -1
- package/template-workspace/README.md.handlebars +12 -3
|
@@ -13,6 +13,10 @@ import { useTranslation } from 'react-i18next';
|
|
|
13
13
|
const fallbackLanguage = 'en';
|
|
14
14
|
const supportedLanguages = ['en', 'cs'] as const;
|
|
15
15
|
type SupportedLanguage = (typeof supportedLanguages)[number];
|
|
16
|
+
const languageDirections: Record<SupportedLanguage, 'ltr'> = {
|
|
17
|
+
cs: 'ltr',
|
|
18
|
+
en: 'ltr',
|
|
19
|
+
};
|
|
16
20
|
|
|
17
21
|
const isSupportedLanguage = (value: string): value is SupportedLanguage =>
|
|
18
22
|
supportedLanguages.includes(value as SupportedLanguage);
|
|
@@ -52,6 +56,8 @@ const Index = () => {
|
|
|
52
56
|
const { language } = useModernI18n();
|
|
53
57
|
const location = useLocation();
|
|
54
58
|
const currentLanguage = isSupportedLanguage(language) ? language : fallbackLanguage;
|
|
59
|
+
const pageTitle = t('home.meta.title');
|
|
60
|
+
const pageDescription = t('home.meta.description');
|
|
55
61
|
const canonicalPath = localizedPath(location.pathname, currentLanguage);
|
|
56
62
|
const suffix = locationSuffix(location);
|
|
57
63
|
const languageOptions = supportedLanguages.map((code) => ({
|
|
@@ -81,12 +87,9 @@ const Index = () => {
|
|
|
81
87
|
{{/if}}
|
|
82
88
|
return (
|
|
83
89
|
<div className="container-box">
|
|
84
|
-
<Helmet>
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
type="image/x-icon"
|
|
88
|
-
href="https://lf3-static.bytednsdoc.com/obj/eden-cn/uhbfnupenuhf/favicon.ico"
|
|
89
|
-
/>
|
|
90
|
+
<Helmet htmlAttributes={{ dir: languageDirections[currentLanguage], lang: currentLanguage }}>
|
|
91
|
+
<title>{pageTitle}</title>
|
|
92
|
+
<meta name="description" content={pageDescription} />
|
|
90
93
|
<link rel="canonical" href={absoluteUrl(canonicalPath)} />
|
|
91
94
|
{supportedLanguages.map((code) => (
|
|
92
95
|
<link
|
|
@@ -102,7 +105,10 @@ const Index = () => {
|
|
|
102
105
|
rel="alternate"
|
|
103
106
|
/>
|
|
104
107
|
</Helmet>
|
|
105
|
-
<main>
|
|
108
|
+
<a className="skip-link" href="#starter-main">
|
|
109
|
+
{t('home.skipLink')}
|
|
110
|
+
</a>
|
|
111
|
+
<header className="starter-header">
|
|
106
112
|
<nav className="language-switcher" aria-label={t('home.language.switcher')}>
|
|
107
113
|
{languageOptions.map((option) => (
|
|
108
114
|
<a
|
|
@@ -114,24 +120,32 @@ const Index = () => {
|
|
|
114
120
|
</a>
|
|
115
121
|
))}
|
|
116
122
|
</nav>
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
</header>
|
|
124
|
+
<main id="starter-main" className="starter-main">
|
|
125
|
+
<section className="hero" aria-labelledby="starter-heading">
|
|
119
126
|
<img
|
|
120
127
|
alt={t('home.logoAlt')}
|
|
121
128
|
className="logo"
|
|
122
|
-
|
|
129
|
+
height={96}
|
|
130
|
+
src="/assets/ultramodern-logo.svg"
|
|
131
|
+
width={96}
|
|
123
132
|
/>
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
<div className="hero-copy">
|
|
134
|
+
<p className="name">{t('home.name')}</p>
|
|
135
|
+
<h1 id="starter-heading" className="title">
|
|
136
|
+
{t('home.title')}
|
|
137
|
+
</h1>
|
|
138
|
+
<p className="description{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
|
|
139
|
+
{t('home.description.intro')}{' '}
|
|
140
|
+
<code className="code">presetUltramodern(...)</code>{' '}
|
|
141
|
+
{t('home.description.afterPreset')}{' '}
|
|
142
|
+
<code className="code">modern.config.ts</code>{' '}
|
|
143
|
+
{t('home.description.afterConfig')}{' '}
|
|
144
|
+
<code className="code">pnpm run ultramodern:check</code>{' '}
|
|
145
|
+
{t('home.description.end')}
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
148
|
+
</section>
|
|
135
149
|
{{#if useEffectBff}}
|
|
136
150
|
<p className="description effect-message{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
|
|
137
151
|
{t('home.bff.response')} <code className="code">{effectMessage}</code>
|
|
@@ -146,11 +160,7 @@ const Index = () => {
|
|
|
146
160
|
>
|
|
147
161
|
<h2>
|
|
148
162
|
{t('home.cards.guide.title')}
|
|
149
|
-
<
|
|
150
|
-
alt=""
|
|
151
|
-
className="arrow-right"
|
|
152
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
|
|
153
|
-
/>
|
|
163
|
+
<span aria-hidden="true" className="arrow-right" />
|
|
154
164
|
</h2>
|
|
155
165
|
<p>{t('home.cards.guide.body')}</p>
|
|
156
166
|
</a>
|
|
@@ -162,11 +172,7 @@ const Index = () => {
|
|
|
162
172
|
>
|
|
163
173
|
<h2>
|
|
164
174
|
{t('home.cards.config.title')}
|
|
165
|
-
<
|
|
166
|
-
alt=""
|
|
167
|
-
className="arrow-right"
|
|
168
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
|
|
169
|
-
/>
|
|
175
|
+
<span aria-hidden="true" className="arrow-right" />
|
|
170
176
|
</h2>
|
|
171
177
|
<p>{t('home.cards.config.body')}</p>
|
|
172
178
|
</a>
|
|
@@ -178,11 +184,7 @@ const Index = () => {
|
|
|
178
184
|
>
|
|
179
185
|
<h2>
|
|
180
186
|
{t('home.cards.gates.title')}
|
|
181
|
-
<
|
|
182
|
-
alt=""
|
|
183
|
-
className="arrow-right"
|
|
184
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
|
|
185
|
-
/>
|
|
187
|
+
<span aria-hidden="true" className="arrow-right" />
|
|
186
188
|
</h2>
|
|
187
189
|
<p>{t('home.cards.gates.body')}</p>
|
|
188
190
|
</a>
|
|
@@ -194,11 +196,7 @@ const Index = () => {
|
|
|
194
196
|
>
|
|
195
197
|
<h2>
|
|
196
198
|
{t('home.cards.bff.title')}
|
|
197
|
-
<
|
|
198
|
-
alt=""
|
|
199
|
-
className="arrow-right"
|
|
200
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
|
|
201
|
-
/>
|
|
199
|
+
<span aria-hidden="true" className="arrow-right" />
|
|
202
200
|
</h2>
|
|
203
201
|
<p>{t('home.cards.bff.body')}</p>
|
|
204
202
|
</a>
|
|
@@ -1,129 +1,266 @@
|
|
|
1
1
|
{{#if enableTailwind}}@import 'tailwindcss';
|
|
2
2
|
|
|
3
|
-
{{/if}}
|
|
3
|
+
{{/if}}:root {
|
|
4
|
+
color-scheme: light;
|
|
5
|
+
--starter-bg: #f3f6f4;
|
|
6
|
+
--starter-surface: #ffffff;
|
|
7
|
+
--starter-text: #14201b;
|
|
8
|
+
--starter-muted: #52625b;
|
|
9
|
+
--starter-border: #cfdad4;
|
|
10
|
+
--starter-accent: #087f5b;
|
|
11
|
+
--starter-accent-strong: #075f48;
|
|
12
|
+
--starter-focus: #0b7285;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
html {
|
|
16
|
+
scroll-behavior: smooth;
|
|
17
|
+
}
|
|
18
|
+
|
|
4
19
|
body {
|
|
5
|
-
padding: 0;
|
|
6
20
|
margin: 0;
|
|
7
21
|
font-family:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
22
|
+
Inter,
|
|
23
|
+
ui-sans-serif,
|
|
24
|
+
system-ui,
|
|
25
|
+
-apple-system,
|
|
26
|
+
BlinkMacSystemFont,
|
|
27
|
+
Segoe UI,
|
|
11
28
|
Arial,
|
|
12
29
|
sans-serif;
|
|
13
|
-
|
|
30
|
+
color: var(--starter-text);
|
|
31
|
+
background: linear-gradient(180deg, #ffffff 0%, var(--starter-bg) 100%);
|
|
14
32
|
}
|
|
15
33
|
|
|
16
34
|
p {
|
|
17
35
|
margin: 0;
|
|
18
36
|
}
|
|
19
37
|
|
|
20
|
-
|
|
38
|
+
.container-box,
|
|
39
|
+
.container-box *,
|
|
40
|
+
.container-box *::before,
|
|
41
|
+
.container-box *::after {
|
|
21
42
|
-webkit-font-smoothing: antialiased;
|
|
22
43
|
-moz-osx-font-smoothing: grayscale;
|
|
23
44
|
box-sizing: border-box;
|
|
24
45
|
}
|
|
25
46
|
|
|
26
47
|
.container-box {
|
|
27
|
-
min-
|
|
28
|
-
|
|
48
|
+
min-block-size: 100dvh;
|
|
49
|
+
inline-size: 100%;
|
|
29
50
|
display: flex;
|
|
30
51
|
flex-direction: column;
|
|
31
|
-
|
|
32
|
-
align-items: center;
|
|
33
|
-
padding-top: 10px;
|
|
52
|
+
padding: 1.25rem;
|
|
34
53
|
}
|
|
35
54
|
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
.skip-link {
|
|
56
|
+
position: fixed;
|
|
57
|
+
inset-block-start: 1rem;
|
|
58
|
+
inset-inline-start: 1rem;
|
|
59
|
+
z-index: 1;
|
|
60
|
+
padding: 0.65rem 0.9rem;
|
|
61
|
+
color: #ffffff;
|
|
62
|
+
background: var(--starter-accent-strong);
|
|
63
|
+
border-radius: 6px;
|
|
64
|
+
transform: translateY(-150%);
|
|
65
|
+
transition: transform 0.15s ease;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.skip-link:focus-visible {
|
|
69
|
+
transform: translateY(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.starter-header {
|
|
73
|
+
inline-size: min(100%, 72rem);
|
|
74
|
+
margin-inline: auto;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.language-switcher {
|
|
38
78
|
display: flex;
|
|
39
|
-
flex-
|
|
40
|
-
|
|
79
|
+
flex-wrap: wrap;
|
|
80
|
+
gap: 0.5rem;
|
|
81
|
+
justify-content: flex-end;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.language-switcher a {
|
|
85
|
+
min-block-size: 2.25rem;
|
|
86
|
+
padding: 0.55rem 0.8rem;
|
|
87
|
+
color: var(--starter-muted);
|
|
88
|
+
text-decoration: none;
|
|
89
|
+
border: 1px solid transparent;
|
|
90
|
+
border-radius: 999px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.language-switcher a:hover {
|
|
94
|
+
color: var(--starter-text);
|
|
95
|
+
border-color: var(--starter-border);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.language-switcher a[aria-current='page'] {
|
|
99
|
+
color: var(--starter-accent-strong);
|
|
100
|
+
background: rgb(8 127 91 / 10%);
|
|
101
|
+
border-color: rgb(8 127 91 / 24%);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.starter-main {
|
|
105
|
+
flex: 1;
|
|
106
|
+
inline-size: min(100%, 72rem);
|
|
107
|
+
margin-inline: auto;
|
|
108
|
+
padding-block: 4rem 2rem;
|
|
109
|
+
display: grid;
|
|
110
|
+
gap: 3rem;
|
|
111
|
+
align-content: center;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.hero {
|
|
115
|
+
display: grid;
|
|
116
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
117
|
+
gap: 1.5rem;
|
|
41
118
|
align-items: center;
|
|
42
119
|
}
|
|
43
120
|
|
|
44
121
|
.title {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
font-
|
|
49
|
-
|
|
122
|
+
margin: 0.35rem 0 0;
|
|
123
|
+
font-size: 3.5rem;
|
|
124
|
+
line-height: 1;
|
|
125
|
+
font-weight: 750;
|
|
126
|
+
letter-spacing: 0;
|
|
127
|
+
text-wrap: balance;
|
|
50
128
|
}
|
|
51
129
|
|
|
52
130
|
.logo {
|
|
53
|
-
|
|
54
|
-
|
|
131
|
+
inline-size: 6rem;
|
|
132
|
+
block-size: 6rem;
|
|
133
|
+
border-radius: 1.25rem;
|
|
134
|
+
filter: drop-shadow(0 1rem 1.5rem rgb(17 24 39 / 18%));
|
|
55
135
|
}
|
|
56
136
|
|
|
57
137
|
.name {
|
|
58
|
-
color:
|
|
138
|
+
color: var(--starter-accent-strong);
|
|
139
|
+
font-size: 0.95rem;
|
|
140
|
+
font-weight: 700;
|
|
141
|
+
line-height: 1.4;
|
|
59
142
|
}
|
|
60
143
|
|
|
61
144
|
.description {
|
|
62
|
-
text-align: center;
|
|
63
145
|
line-height: 1.5;
|
|
64
|
-
font-size: 1.
|
|
65
|
-
color:
|
|
66
|
-
|
|
146
|
+
font-size: 1.15rem;
|
|
147
|
+
color: var(--starter-muted);
|
|
148
|
+
max-inline-size: 54rem;
|
|
149
|
+
margin-block-start: 1rem;
|
|
150
|
+
text-wrap: pretty;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.effect-message {
|
|
154
|
+
padding: 1rem;
|
|
155
|
+
background: var(--starter-surface);
|
|
156
|
+
border: 1px solid var(--starter-border);
|
|
157
|
+
border-radius: 8px;
|
|
67
158
|
}
|
|
68
159
|
|
|
69
160
|
.code {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
161
|
+
display: inline-block;
|
|
162
|
+
padding: 0.15rem 0.35rem;
|
|
163
|
+
color: var(--starter-accent-strong);
|
|
164
|
+
background: rgb(8 127 91 / 10%);
|
|
165
|
+
border-radius: 6px;
|
|
166
|
+
font-size: 0.95em;
|
|
74
167
|
font-family:
|
|
168
|
+
ui-monospace,
|
|
169
|
+
SFMono-Regular,
|
|
75
170
|
Menlo,
|
|
76
171
|
Monaco,
|
|
77
|
-
Lucida Console,
|
|
78
|
-
Liberation Mono,
|
|
79
|
-
DejaVu Sans Mono,
|
|
80
|
-
Bitstream Vera Sans Mono,
|
|
81
|
-
Courier New,
|
|
82
172
|
monospace;
|
|
83
173
|
}
|
|
84
174
|
|
|
85
175
|
.container-box .grid {
|
|
86
|
-
display:
|
|
176
|
+
display: grid;
|
|
177
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
|
|
87
178
|
align-items: center;
|
|
88
|
-
|
|
89
|
-
width: 1100px;
|
|
90
|
-
margin-top: 3rem;
|
|
179
|
+
gap: 1rem;
|
|
91
180
|
}
|
|
92
181
|
|
|
93
182
|
.card {
|
|
94
|
-
padding: 1.5rem;
|
|
95
183
|
display: flex;
|
|
96
184
|
flex-direction: column;
|
|
97
|
-
|
|
98
|
-
|
|
185
|
+
gap: 0.75rem;
|
|
186
|
+
min-block-size: 9rem;
|
|
187
|
+
padding: 1.25rem;
|
|
99
188
|
color: inherit;
|
|
100
189
|
text-decoration: none;
|
|
101
|
-
|
|
102
|
-
|
|
190
|
+
background: var(--starter-surface);
|
|
191
|
+
border: 1px solid var(--starter-border);
|
|
192
|
+
border-radius: 8px;
|
|
193
|
+
transition:
|
|
194
|
+
border-color 0.15s ease,
|
|
195
|
+
transform 0.15s ease;
|
|
103
196
|
}
|
|
104
197
|
|
|
105
|
-
.card:hover
|
|
106
|
-
|
|
107
|
-
transform:
|
|
198
|
+
.card:hover {
|
|
199
|
+
border-color: rgb(8 127 91 / 45%);
|
|
200
|
+
transform: translateY(-0.125rem);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.skip-link:focus-visible,
|
|
204
|
+
.language-switcher a:focus-visible,
|
|
205
|
+
.card:focus-visible {
|
|
206
|
+
outline: 3px solid var(--starter-focus);
|
|
207
|
+
outline-offset: 3px;
|
|
108
208
|
}
|
|
109
209
|
|
|
110
210
|
.card h2 {
|
|
111
211
|
display: flex;
|
|
112
212
|
align-items: center;
|
|
113
|
-
|
|
213
|
+
gap: 0.55rem;
|
|
214
|
+
font-size: 1.25rem;
|
|
215
|
+
line-height: 1.2;
|
|
114
216
|
margin: 0;
|
|
115
217
|
padding: 0;
|
|
116
218
|
}
|
|
117
219
|
|
|
118
220
|
.card p {
|
|
119
|
-
opacity: 0.
|
|
221
|
+
opacity: 0.7;
|
|
120
222
|
font-size: 0.9rem;
|
|
121
223
|
line-height: 1.5;
|
|
122
|
-
margin-top: 1rem;
|
|
123
224
|
}
|
|
124
225
|
|
|
125
226
|
.arrow-right {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
227
|
+
flex: none;
|
|
228
|
+
inline-size: 0.65rem;
|
|
229
|
+
block-size: 0.65rem;
|
|
230
|
+
border-block-start: 2px solid currentColor;
|
|
231
|
+
border-inline-end: 2px solid currentColor;
|
|
232
|
+
transform: rotate(45deg);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@media (max-width: 44rem) {
|
|
236
|
+
.container-box {
|
|
237
|
+
padding: 1rem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.starter-main {
|
|
241
|
+
padding-block: 2rem 1rem;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.hero {
|
|
245
|
+
grid-template-columns: 1fr;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.title {
|
|
249
|
+
font-size: 2.5rem;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@media (prefers-reduced-motion: reduce) {
|
|
254
|
+
html {
|
|
255
|
+
scroll-behavior: auto;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.skip-link,
|
|
259
|
+
.card {
|
|
260
|
+
transition: none;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.card:hover {
|
|
264
|
+
transform: none;
|
|
265
|
+
}
|
|
129
266
|
}
|
|
@@ -25,6 +25,63 @@ describe('generated UltraModern contract', () => {
|
|
|
25
25
|
{{/unless}}
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
test('keeps UltraModern starter web correctness defaults', () => {
|
|
29
|
+
const routePage = readText('src/routes/[lang]/page.tsx');
|
|
30
|
+
const routeCss = readText('src/routes/index.css');
|
|
31
|
+
const modernConfig = readText('modern.config.ts');
|
|
32
|
+
const enLocale = readJson<{
|
|
33
|
+
home?: {
|
|
34
|
+
meta?: {
|
|
35
|
+
description?: string;
|
|
36
|
+
title?: string;
|
|
37
|
+
};
|
|
38
|
+
skipLink?: string;
|
|
39
|
+
};
|
|
40
|
+
}>('config/public/locales/en/translation.json');
|
|
41
|
+
|
|
42
|
+
expect(fs.existsSync(path.join(root, 'config/favicon.svg'))).toBe(true);
|
|
43
|
+
expect(
|
|
44
|
+
fs.existsSync(
|
|
45
|
+
path.join(root, 'config/public/assets/ultramodern-logo.svg'),
|
|
46
|
+
),
|
|
47
|
+
).toBe(true);
|
|
48
|
+
expect(routePage).not.toContain('lf3-static.bytednsdoc.com');
|
|
49
|
+
expect(routePage).toContain('<Helmet');
|
|
50
|
+
expect(routePage).toContain('htmlAttributes={{');
|
|
51
|
+
expect(routePage).toContain('dir: languageDirections[currentLanguage]');
|
|
52
|
+
expect(routePage).toContain('lang: currentLanguage');
|
|
53
|
+
expect(routePage).toContain('<title>{pageTitle}</title>');
|
|
54
|
+
expect(routePage).toContain(
|
|
55
|
+
'<meta name="description" content={pageDescription} />',
|
|
56
|
+
);
|
|
57
|
+
expect(routePage).toContain(
|
|
58
|
+
'<main id="starter-main" className="starter-main">',
|
|
59
|
+
);
|
|
60
|
+
expect(routePage).toContain('<h1 id="starter-heading" className="title">');
|
|
61
|
+
expect(routePage).toContain('src="/assets/ultramodern-logo.svg"');
|
|
62
|
+
expect(routePage).toContain('height={96}');
|
|
63
|
+
expect(routePage).toContain('width={96}');
|
|
64
|
+
expect(routePage).toContain(
|
|
65
|
+
'<span aria-hidden="true" className="arrow-right" />',
|
|
66
|
+
);
|
|
67
|
+
expect(routePage).not.toContain('<div className="title">');
|
|
68
|
+
expect(modernConfig).toContain(
|
|
69
|
+
'width=device-width, initial-scale=1.0, viewport-fit=cover',
|
|
70
|
+
);
|
|
71
|
+
expect(modernConfig).not.toContain('user-scalable=no');
|
|
72
|
+
expect(modernConfig).not.toContain('maximum-scale');
|
|
73
|
+
expect(routeCss).toContain('min-block-size: 100dvh');
|
|
74
|
+
expect(routeCss).toContain(
|
|
75
|
+
'grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr))',
|
|
76
|
+
);
|
|
77
|
+
expect(routeCss).toContain(':focus-visible');
|
|
78
|
+
expect(routeCss).toContain('@media (prefers-reduced-motion: reduce)');
|
|
79
|
+
expect(routeCss).not.toContain('width: 1100px');
|
|
80
|
+
expect(enLocale.home?.meta?.title).toBe('UltraModern.js Starter');
|
|
81
|
+
expect(enLocale.home?.meta?.description).toBeTruthy();
|
|
82
|
+
expect(enLocale.home?.skipLink).toBeTruthy();
|
|
83
|
+
});
|
|
84
|
+
|
|
28
85
|
test('retains package-source metadata for generated Modern.js packages', () => {
|
|
29
86
|
const packageJson = readJson<{
|
|
30
87
|
dependencies?: Record<string, string>;
|
|
@@ -32,6 +89,7 @@ describe('generated UltraModern contract', () => {
|
|
|
32
89
|
modernjs?: {
|
|
33
90
|
packageSource?: {
|
|
34
91
|
config?: string;
|
|
92
|
+
strategy?: string;
|
|
35
93
|
};
|
|
36
94
|
preset?: string;
|
|
37
95
|
};
|
|
@@ -48,15 +106,20 @@ describe('generated UltraModern contract', () => {
|
|
|
48
106
|
expect(packageJson.modernjs?.packageSource?.config).toBe(
|
|
49
107
|
'./.modernjs/ultramodern-package-source.json',
|
|
50
108
|
);
|
|
51
|
-
expect(packageSource
|
|
52
|
-
|
|
53
|
-
'@modern-js/runtime',
|
|
54
|
-
);
|
|
55
|
-
expect(packageSource.modernPackages?.packages).toContain(
|
|
56
|
-
'@modern-js/app-tools',
|
|
109
|
+
expect(packageJson.modernjs?.packageSource?.strategy).toBe(
|
|
110
|
+
packageSource.strategy,
|
|
57
111
|
);
|
|
58
|
-
expect(packageSource.
|
|
59
|
-
|
|
112
|
+
expect(packageSource.strategy).toMatch(/^(workspace|install)$/u);
|
|
113
|
+
const generatedModernDependencies = [
|
|
114
|
+
...new Set(
|
|
115
|
+
(['dependencies', 'devDependencies'] as const).flatMap(section =>
|
|
116
|
+
Object.keys(packageJson[section] ?? {}),
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
].filter(packageName => packageName.startsWith('@modern-js/'));
|
|
120
|
+
expect(packageSource.modernPackages?.packages?.length).toBeGreaterThan(0);
|
|
121
|
+
expect(packageSource.modernPackages?.packages).toEqual(
|
|
122
|
+
expect.arrayContaining(generatedModernDependencies),
|
|
60
123
|
);
|
|
61
124
|
expect(packageSource.modernPackages?.specifier).toBeTruthy();
|
|
62
125
|
expect(
|
package/template/tsconfig.json
CHANGED
|
@@ -19,9 +19,18 @@ pnpm dlx @bleedingdev/modern-js-create payments --vertical
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Each added vertical owns its UI/routes, browser-safe Module Federation exposes,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
stay out of browser exposes.
|
|
22
|
+
private-first route metadata, localized URLs, public-route opt-ins, CSS prefix,
|
|
23
|
+
Effect BFF handlers, local API contract, and typed client surface. Server
|
|
24
|
+
handlers and Effect client/contract modules stay out of browser exposes.
|
|
25
|
+
|
|
26
|
+
## Private-First Public Surfaces
|
|
27
|
+
|
|
28
|
+
Generated app routes are private and non-indexable by default. Private app,
|
|
29
|
+
auth, tenant, dashboard, and internal routes publish no discovery output unless
|
|
30
|
+
route metadata explicitly marks them `public && indexable`. The default scaffold
|
|
31
|
+
therefore emits only a disallowing `robots.txt`; sitemap, web manifest,
|
|
32
|
+
`llms.txt`, API catalog, security.txt, and JSON-LD output stay omitted until a
|
|
33
|
+
safe public route or public docs/help/product surface exists.
|
|
25
34
|
|
|
26
35
|
Run the scaffold validator before adding business code and after every
|
|
27
36
|
`--vertical` mutation:
|