@bleedingdev/modern-js-create 3.2.0-ultramodern.11 → 3.2.0-ultramodern.111

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.
Files changed (75) hide show
  1. package/README.md +158 -35
  2. package/bin/run.js +0 -0
  3. package/dist/cjs/index.cjs +1042 -0
  4. package/dist/cjs/locale/en.cjs +97 -0
  5. package/dist/cjs/locale/index.cjs +50 -0
  6. package/dist/cjs/locale/zh.cjs +97 -0
  7. package/dist/cjs/ultramodern-package-source.cjs +135 -0
  8. package/dist/cjs/ultramodern-workspace.cjs +5623 -0
  9. package/dist/esm/index.js +1004 -0
  10. package/dist/esm/locale/en.js +59 -0
  11. package/dist/esm/locale/index.js +9 -0
  12. package/dist/esm/locale/zh.js +59 -0
  13. package/dist/esm/ultramodern-package-source.js +63 -0
  14. package/dist/esm/ultramodern-workspace.js +5561 -0
  15. package/dist/esm-node/index.js +1005 -0
  16. package/dist/esm-node/locale/en.js +60 -0
  17. package/dist/esm-node/locale/index.js +10 -0
  18. package/dist/esm-node/locale/zh.js +60 -0
  19. package/dist/esm-node/ultramodern-package-source.js +64 -0
  20. package/dist/esm-node/ultramodern-workspace.js +5562 -0
  21. package/dist/types/locale/en.d.ts +3 -0
  22. package/dist/types/locale/index.d.ts +117 -2
  23. package/dist/types/locale/zh.d.ts +3 -0
  24. package/dist/types/ultramodern-package-source.d.ts +28 -0
  25. package/dist/types/ultramodern-workspace.d.ts +12 -2
  26. package/package.json +27 -11
  27. package/template/.codex/hooks.json +16 -0
  28. package/template/.github/renovate.json +53 -0
  29. package/template/.github/workflows/ultramodern-gates.yml.handlebars +34 -10
  30. package/template/.mise.toml.handlebars +2 -0
  31. package/template/AGENTS.md +9 -6
  32. package/template/README.md +66 -34
  33. package/template/api/effect/index.ts.handlebars +20 -9
  34. package/template/api/lambda/hello.ts.handlebars +5 -5
  35. package/template/config/favicon.svg +5 -0
  36. package/template/config/public/assets/ultramodern-logo.svg +6 -0
  37. package/template/config/public/locales/cs/translation.json +44 -0
  38. package/template/config/public/locales/en/translation.json +44 -0
  39. package/template/lefthook.yml +10 -0
  40. package/template/modern.config.ts.handlebars +35 -3
  41. package/template/oxfmt.config.ts +8 -1
  42. package/template/oxlint.config.ts +8 -1
  43. package/template/package.json.handlebars +37 -30
  44. package/template/pnpm-workspace.yaml +34 -0
  45. package/template/rstest.config.mts +5 -0
  46. package/template/scripts/bootstrap-agent-skills.mjs +148 -15
  47. package/template/scripts/check-i18n-strings.mjs +3 -0
  48. package/template/scripts/validate-ultramodern.mjs.handlebars +495 -3
  49. package/template/src/modern-app-env.d.ts +2 -0
  50. package/template/src/modern.runtime.ts.handlebars +17 -1
  51. package/template/src/routes/[lang]/page.tsx.handlebars +209 -0
  52. package/template/src/routes/index.css.handlebars +192 -55
  53. package/template/src/routes/layout.tsx.handlebars +2 -1
  54. package/template/tailwind.config.ts.handlebars +1 -1
  55. package/template/tests/tsconfig.json +7 -0
  56. package/template/tests/ultramodern.contract.test.ts.handlebars +163 -0
  57. package/template/tsconfig.json +2 -1
  58. package/template-workspace/.agents/agent-reference-repos.json +24 -0
  59. package/template-workspace/.agents/skills-lock.json +19 -0
  60. package/template-workspace/.codex/hooks.json +16 -0
  61. package/template-workspace/.github/renovate.json +29 -0
  62. package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +54 -0
  63. package/template-workspace/.gitignore.handlebars +5 -0
  64. package/template-workspace/.mise.toml.handlebars +2 -0
  65. package/template-workspace/AGENTS.md +36 -5
  66. package/template-workspace/README.md.handlebars +70 -11
  67. package/template-workspace/lefthook.yml +10 -0
  68. package/template-workspace/oxfmt.config.ts +1 -0
  69. package/template-workspace/oxlint.config.ts +1 -0
  70. package/template-workspace/pnpm-workspace.yaml +31 -8
  71. package/template-workspace/scripts/bootstrap-agent-skills.mjs +190 -21
  72. package/template-workspace/scripts/setup-agent-reference-repos.mjs +370 -0
  73. package/dist/index.js +0 -2474
  74. package/template/src/routes/page.tsx.handlebars +0 -136
  75. package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -405
@@ -0,0 +1,209 @@
1
+ import { Helmet } from '@modern-js/runtime/head';
2
+ import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3
+ import { useLocation } from '{{routerRuntimeImport}}';
4
+ {{#if useEffectBff}}import effectBff from '@api/effect/index';
5
+ import { Effect } from '@modern-js/plugin-bff/effect-client';
6
+ import { useEffect, useState } from 'react';
7
+ {{/if}}
8
+ import { useTranslation } from 'react-i18next';
9
+
10
+ {{#if useEffectBff}}type GreetingResponse = Awaited<ReturnType<typeof effectBff.client.greetings.hello>>;
11
+
12
+ {{/if}}
13
+ const fallbackLanguage = 'en';
14
+ const supportedLanguages = ['en', 'cs'] as const;
15
+ type SupportedLanguage = (typeof supportedLanguages)[number];
16
+ const languageDirections: Record<SupportedLanguage, 'ltr'> = {
17
+ cs: 'ltr',
18
+ en: 'ltr',
19
+ };
20
+
21
+ const isSupportedLanguage = (value: string): value is SupportedLanguage =>
22
+ supportedLanguages.includes(value as SupportedLanguage);
23
+
24
+ const stripLanguagePrefix = (pathname: string) => {
25
+ const segments = pathname.split('/').filter(Boolean);
26
+ if (segments.length > 0 && isSupportedLanguage(segments[0] ?? '')) {
27
+ segments.shift();
28
+ }
29
+ return `/${segments.join('/')}`;
30
+ };
31
+
32
+ const localizedPath = (pathname: string, language: SupportedLanguage) => {
33
+ const pathWithoutLanguage = stripLanguagePrefix(pathname);
34
+ return pathWithoutLanguage === '/' ? `/${language}` : `/${language}${pathWithoutLanguage}`;
35
+ };
36
+
37
+ const absoluteUrl = (pathname: string) => {
38
+ const origin = ULTRAMODERN_SITE_URL.replace(/\/+$/u, '');
39
+ return `${origin}${pathname}`;
40
+ };
41
+
42
+ const locationSuffix = (location: { hash?: unknown; search?: unknown; searchStr?: unknown }) => {
43
+ const { hash, search, searchStr } = location;
44
+ let locationSearch = '';
45
+ if (typeof searchStr === 'string') {
46
+ locationSearch = searchStr;
47
+ } else if (typeof search === 'string') {
48
+ locationSearch = search;
49
+ }
50
+ const locationHash = typeof hash === 'string' ? hash : '';
51
+ return `${locationSearch}${locationHash}`;
52
+ };
53
+
54
+ const Index = () => {
55
+ const { t } = useTranslation();
56
+ const { language } = useModernI18n();
57
+ const location = useLocation();
58
+ const currentLanguage = isSupportedLanguage(language) ? language : fallbackLanguage;
59
+ const pageTitle = t('home.meta.title');
60
+ const pageDescription = t('home.meta.description');
61
+ const canonicalPath = localizedPath(location.pathname, currentLanguage);
62
+ const suffix = locationSuffix(location);
63
+ const languageOptions = supportedLanguages.map((code) => ({
64
+ code,
65
+ href: `${localizedPath(location.pathname, code)}${suffix}`,
66
+ label: t(`home.language.${code}`),
67
+ }));
68
+ {{#if useEffectBff}} const [effectMessage, setEffectMessage] = useState('loading...');
69
+
70
+ useEffect(() => {
71
+ let mounted = true;
72
+ Effect.runFork(
73
+ Effect.promise(() => effectBff.client.greetings.hello({})).pipe(
74
+ Effect.tap((data: GreetingResponse) =>
75
+ Effect.sync(() => {
76
+ if (mounted) {
77
+ setEffectMessage(data.message);
78
+ }
79
+ }),
80
+ ),
81
+ ),
82
+ );
83
+ return () => {
84
+ mounted = false;
85
+ };
86
+ }, []);
87
+ {{/if}}
88
+ return (
89
+ <div className="container-box">
90
+ <Helmet htmlAttributes={{ dir: languageDirections[currentLanguage], lang: currentLanguage }}>
91
+ <title>{pageTitle}</title>
92
+ <meta name="description" content={pageDescription} />
93
+ <link rel="canonical" href={absoluteUrl(canonicalPath)} />
94
+ {supportedLanguages.map((code) => (
95
+ <link
96
+ href={absoluteUrl(localizedPath(location.pathname, code))}
97
+ hrefLang={code}
98
+ key={code}
99
+ rel="alternate"
100
+ />
101
+ ))}
102
+ <link
103
+ href={absoluteUrl(localizedPath(location.pathname, fallbackLanguage))}
104
+ hrefLang="x-default"
105
+ rel="alternate"
106
+ />
107
+ </Helmet>
108
+ <a className="skip-link" href="#starter-main">
109
+ {t('home.skipLink')}
110
+ </a>
111
+ <header className="starter-header">
112
+ <nav className="language-switcher" aria-label={t('home.language.switcher')}>
113
+ {languageOptions.map((option) => (
114
+ <a
115
+ aria-current={currentLanguage === option.code ? 'page' : undefined}
116
+ href={option.href}
117
+ key={option.code}
118
+ >
119
+ {option.label}
120
+ </a>
121
+ ))}
122
+ </nav>
123
+ </header>
124
+ <main id="starter-main" className="starter-main">
125
+ <section className="hero" aria-labelledby="starter-heading">
126
+ <img
127
+ alt={t('home.logoAlt')}
128
+ className="logo"
129
+ height={96}
130
+ src="/assets/ultramodern-logo.svg"
131
+ width={96}
132
+ />
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>
149
+ {{#if useEffectBff}}
150
+ <p className="description effect-message{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
151
+ {t('home.bff.response')} <code className="code">{effectMessage}</code>
152
+ </p>
153
+ {{/if}}
154
+ <div className="grid">
155
+ <a
156
+ href="https://bleedingdev.github.io/ultramodern.js/guides/get-started/ultramodern.html"
157
+ target="_blank"
158
+ rel="noopener noreferrer"
159
+ className="card"
160
+ >
161
+ <h2>
162
+ {t('home.cards.guide.title')}
163
+ <span aria-hidden="true" className="arrow-right" />
164
+ </h2>
165
+ <p>{t('home.cards.guide.body')}</p>
166
+ </a>
167
+ <a
168
+ href="https://bleedingdev.github.io/ultramodern.js/configure/app/usage.html"
169
+ target="_blank"
170
+ className="card"
171
+ rel="noreferrer"
172
+ >
173
+ <h2>
174
+ {t('home.cards.config.title')}
175
+ <span aria-hidden="true" className="arrow-right" />
176
+ </h2>
177
+ <p>{t('home.cards.config.body')}</p>
178
+ </a>
179
+ <a
180
+ href="https://github.com/BleedingDev/ultramodern.js/blob/main-ultramodern/packages/toolkit/create/template/.github/workflows/ultramodern-gates.yml.handlebars"
181
+ target="_blank"
182
+ className="card"
183
+ rel="noreferrer"
184
+ >
185
+ <h2>
186
+ {t('home.cards.gates.title')}
187
+ <span aria-hidden="true" className="arrow-right" />
188
+ </h2>
189
+ <p>{t('home.cards.gates.body')}</p>
190
+ </a>
191
+ <a
192
+ href="https://bleedingdev.github.io/ultramodern.js/configure/app/bff/effect.html"
193
+ target="_blank"
194
+ rel="noopener noreferrer"
195
+ className="card"
196
+ >
197
+ <h2>
198
+ {t('home.cards.bff.title')}
199
+ <span aria-hidden="true" className="arrow-right" />
200
+ </h2>
201
+ <p>{t('home.cards.bff.body')}</p>
202
+ </a>
203
+ </div>
204
+ </main>
205
+ </div>
206
+ );
207
+ };
208
+
209
+ export default Index;
@@ -1,129 +1,266 @@
1
1
  {{#if enableTailwind}}@import 'tailwindcss';
2
2
 
3
- {{/if}}html,
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
- PingFang SC,
9
- Hiragino Sans GB,
10
- Microsoft YaHei,
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
- background: linear-gradient(to bottom, transparent, #fff) #eceeef;
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-height: 100vh;
28
- max-width: 100%;
48
+ min-block-size: 100dvh;
49
+ inline-size: 100%;
29
50
  display: flex;
30
51
  flex-direction: column;
31
- justify-content: center;
32
- align-items: center;
33
- padding-top: 10px;
52
+ padding: 1.25rem;
34
53
  }
35
54
 
36
- main {
37
- flex: 1;
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-direction: column;
40
- justify-content: center;
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
- display: flex;
46
- margin: 4rem 0 4rem;
47
- align-items: center;
48
- font-size: 4rem;
49
- font-weight: 600;
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
- width: 6rem;
54
- margin: 7px 0 0 1rem;
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: #4ecaff;
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.3rem;
65
- color: #1b3a42;
66
- margin-bottom: 5rem;
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
- background: #fafafa;
71
- border-radius: 12px;
72
- padding: 0.6rem 0.9rem;
73
- font-size: 1.05rem;
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: flex;
176
+ display: grid;
177
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
87
178
  align-items: center;
88
- justify-content: center;
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
- justify-content: center;
98
- height: 100px;
185
+ gap: 0.75rem;
186
+ min-block-size: 9rem;
187
+ padding: 1.25rem;
99
188
  color: inherit;
100
189
  text-decoration: none;
101
- transition: 0.15s ease;
102
- width: 45%;
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
- .card:focus {
107
- transform: scale(1.05);
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
- font-size: 1.5rem;
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.6;
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
- width: 1.3rem;
127
- margin-left: 0.5rem;
128
- margin-top: 3px;
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
  }
@@ -1,4 +1,5 @@
1
- import { Outlet } from '@modern-js/runtime/{{routerImportPath}}';
1
+ import { Outlet } from '{{routerRuntimeImport}}';
2
+ import './index.css';
2
3
 
3
4
  export default function Layout() {
4
5
  return (
@@ -2,9 +2,9 @@
2
2
 
3
3
  export default {
4
4
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
5
+ plugins: [],
5
6
  theme: {
6
7
  extend: {},
7
8
  },
8
- plugins: [],
9
9
  } satisfies Config;
10
10
  {{/if}}
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["@rstest/core/globals"]
5
+ },
6
+ "include": ["./"]
7
+ }