@bleedingdev/modern-js-create 3.2.0-ultramodern.117 → 3.2.0-ultramodern.118

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 (62) hide show
  1. package/README.md +35 -125
  2. package/dist/cjs/create-package-root.cjs +1 -1
  3. package/dist/cjs/index.cjs +11 -602
  4. package/dist/cjs/locale/en.cjs +12 -19
  5. package/dist/cjs/locale/zh.cjs +12 -19
  6. package/dist/cjs/ultramodern-workspace.cjs +16 -19
  7. package/dist/esm/create-package-root.js +1 -1
  8. package/dist/esm/index.js +13 -603
  9. package/dist/esm/locale/en.js +12 -19
  10. package/dist/esm/locale/zh.js +12 -19
  11. package/dist/esm/ultramodern-workspace.js +17 -17
  12. package/dist/esm-node/create-package-root.js +1 -1
  13. package/dist/esm-node/index.js +13 -603
  14. package/dist/esm-node/locale/en.js +12 -19
  15. package/dist/esm-node/locale/zh.js +12 -19
  16. package/dist/esm-node/ultramodern-workspace.js +17 -17
  17. package/dist/types/locale/en.d.ts +0 -7
  18. package/dist/types/locale/index.d.ts +0 -14
  19. package/dist/types/locale/zh.d.ts +0 -7
  20. package/dist/types/ultramodern-workspace.d.ts +0 -1
  21. package/package.json +4 -5
  22. package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +22 -6
  23. package/template-workspace/AGENTS.md +7 -3
  24. package/template-workspace/README.md.handlebars +5 -1
  25. package/template-workspace/lefthook.yml +18 -4
  26. package/template/.agents/skills-lock.json +0 -34
  27. package/template/.browserslistrc +0 -4
  28. package/template/.codex/hooks.json +0 -16
  29. package/template/.github/renovate.json +0 -53
  30. package/template/.github/workflows/ultramodern-gates.yml.handlebars +0 -54
  31. package/template/.gitignore.handlebars +0 -30
  32. package/template/.mise.toml.handlebars +0 -2
  33. package/template/.nvmrc +0 -2
  34. package/template/AGENTS.md +0 -23
  35. package/template/README.md +0 -111
  36. package/template/api/effect/index.ts.handlebars +0 -34
  37. package/template/api/lambda/hello.ts.handlebars +0 -6
  38. package/template/config/favicon.svg +0 -5
  39. package/template/config/public/assets/ultramodern-logo.svg +0 -6
  40. package/template/config/public/locales/cs/translation.json +0 -44
  41. package/template/config/public/locales/en/translation.json +0 -44
  42. package/template/lefthook.yml +0 -10
  43. package/template/modern.config.ts.handlebars +0 -78
  44. package/template/oxfmt.config.ts +0 -15
  45. package/template/oxlint.config.ts +0 -19
  46. package/template/package.json.handlebars +0 -69
  47. package/template/pnpm-workspace.yaml +0 -34
  48. package/template/postcss.config.mjs.handlebars +0 -6
  49. package/template/rstest.config.mts +0 -5
  50. package/template/scripts/bootstrap-agent-skills.mjs +0 -228
  51. package/template/scripts/check-i18n-strings.mjs +0 -3
  52. package/template/scripts/validate-ultramodern.mjs.handlebars +0 -658
  53. package/template/shared/effect/api.ts.handlebars +0 -17
  54. package/template/src/modern-app-env.d.ts +0 -3
  55. package/template/src/modern.runtime.ts.handlebars +0 -23
  56. package/template/src/routes/[lang]/page.tsx.handlebars +0 -209
  57. package/template/src/routes/index.css.handlebars +0 -266
  58. package/template/src/routes/layout.tsx.handlebars +0 -10
  59. package/template/tailwind.config.ts.handlebars +0 -10
  60. package/template/tests/tsconfig.json +0 -7
  61. package/template/tests/ultramodern.contract.test.ts.handlebars +0 -163
  62. package/template/tsconfig.json +0 -121
@@ -1,209 +0,0 @@
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,266 +0,0 @@
1
- {{#if enableTailwind}}@import 'tailwindcss';
2
-
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
-
19
- body {
20
- margin: 0;
21
- font-family:
22
- Inter,
23
- ui-sans-serif,
24
- system-ui,
25
- -apple-system,
26
- BlinkMacSystemFont,
27
- Segoe UI,
28
- Arial,
29
- sans-serif;
30
- color: var(--starter-text);
31
- background: linear-gradient(180deg, #ffffff 0%, var(--starter-bg) 100%);
32
- }
33
-
34
- p {
35
- margin: 0;
36
- }
37
-
38
- .container-box,
39
- .container-box *,
40
- .container-box *::before,
41
- .container-box *::after {
42
- -webkit-font-smoothing: antialiased;
43
- -moz-osx-font-smoothing: grayscale;
44
- box-sizing: border-box;
45
- }
46
-
47
- .container-box {
48
- min-block-size: 100dvh;
49
- inline-size: 100%;
50
- display: flex;
51
- flex-direction: column;
52
- padding: 1.25rem;
53
- }
54
-
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 {
78
- display: flex;
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;
118
- align-items: center;
119
- }
120
-
121
- .title {
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;
128
- }
129
-
130
- .logo {
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%));
135
- }
136
-
137
- .name {
138
- color: var(--starter-accent-strong);
139
- font-size: 0.95rem;
140
- font-weight: 700;
141
- line-height: 1.4;
142
- }
143
-
144
- .description {
145
- line-height: 1.5;
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;
158
- }
159
-
160
- .code {
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;
167
- font-family:
168
- ui-monospace,
169
- SFMono-Regular,
170
- Menlo,
171
- Monaco,
172
- monospace;
173
- }
174
-
175
- .container-box .grid {
176
- display: grid;
177
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
178
- align-items: center;
179
- gap: 1rem;
180
- }
181
-
182
- .card {
183
- display: flex;
184
- flex-direction: column;
185
- gap: 0.75rem;
186
- min-block-size: 9rem;
187
- padding: 1.25rem;
188
- color: inherit;
189
- text-decoration: none;
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;
196
- }
197
-
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;
208
- }
209
-
210
- .card h2 {
211
- display: flex;
212
- align-items: center;
213
- gap: 0.55rem;
214
- font-size: 1.25rem;
215
- line-height: 1.2;
216
- margin: 0;
217
- padding: 0;
218
- }
219
-
220
- .card p {
221
- opacity: 0.7;
222
- font-size: 0.9rem;
223
- line-height: 1.5;
224
- }
225
-
226
- .arrow-right {
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
- }
266
- }
@@ -1,10 +0,0 @@
1
- import { Outlet } from '{{routerRuntimeImport}}';
2
- import './index.css';
3
-
4
- export default function Layout() {
5
- return (
6
- <div>
7
- <Outlet />
8
- </div>
9
- );
10
- }
@@ -1,10 +0,0 @@
1
- {{#if enableTailwind}}import type { Config } from 'tailwindcss';
2
-
3
- export default {
4
- content: ['./src/**/*.{js,ts,jsx,tsx}'],
5
- plugins: [],
6
- theme: {
7
- extend: {},
8
- },
9
- } satisfies Config;
10
- {{/if}}
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../tsconfig.json",
3
- "compilerOptions": {
4
- "types": ["@rstest/core/globals"]
5
- },
6
- "include": ["./"]
7
- }
@@ -1,163 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { describe, expect, test } from '@rstest/core';
4
-
5
- const root = process.cwd();
6
- const readText = (relativePath: string) =>
7
- fs.readFileSync(path.join(root, relativePath), 'utf-8');
8
- const readJson = <T>(relativePath: string): T =>
9
- JSON.parse(readText(relativePath)) as T;
10
-
11
- describe('generated UltraModern contract', () => {
12
- test('keeps localized route metadata and Rstest wiring', () => {
13
- expect(fs.existsSync(path.join(root, 'src/routes/[lang]/page.tsx'))).toBe(
14
- true,
15
- );
16
- expect(fs.existsSync(path.join(root, 'src/routes/page.tsx'))).toBe(false);
17
- expect(fs.existsSync(path.join(root, 'src/routes/layout.tsx'))).toBe(true);
18
- expect(fs.existsSync(path.join(root, 'oxlint.config.ts'))).toBe(true);
19
- expect(fs.existsSync(path.join(root, 'oxfmt.config.ts'))).toBe(true);
20
- {{#if enableTailwind}}
21
- expect(fs.existsSync(path.join(root, 'postcss.config.mjs'))).toBe(true);
22
- expect(fs.existsSync(path.join(root, 'tailwind.config.ts'))).toBe(true);
23
- {{/if}}
24
- {{#unless enableTailwind}}
25
- expect(fs.existsSync(path.join(root, 'postcss.config.mjs'))).toBe(false);
26
- expect(fs.existsSync(path.join(root, 'tailwind.config.ts'))).toBe(false);
27
- {{/unless}}
28
- });
29
-
30
- test('keeps UltraModern starter web correctness defaults', () => {
31
- const routePage = readText('src/routes/[lang]/page.tsx');
32
- const routeCss = readText('src/routes/index.css');
33
- const modernConfig = readText('modern.config.ts');
34
- const enLocale = readJson<{
35
- home?: {
36
- meta?: {
37
- description?: string;
38
- title?: string;
39
- };
40
- skipLink?: string;
41
- };
42
- }>('config/public/locales/en/translation.json');
43
-
44
- expect(fs.existsSync(path.join(root, 'config/favicon.svg'))).toBe(true);
45
- expect(
46
- fs.existsSync(
47
- path.join(root, 'config/public/assets/ultramodern-logo.svg'),
48
- ),
49
- ).toBe(true);
50
- expect(routePage).not.toContain('lf3-static.bytednsdoc.com');
51
- expect(routePage).toContain('<Helmet');
52
- expect(routePage).toContain('htmlAttributes={{');
53
- expect(routePage).toContain('dir: languageDirections[currentLanguage]');
54
- expect(routePage).toContain('lang: currentLanguage');
55
- expect(routePage).toContain('<title>{pageTitle}</title>');
56
- expect(routePage).toContain(
57
- '<meta name="description" content={pageDescription} />',
58
- );
59
- expect(routePage).toContain(
60
- '<main id="starter-main" className="starter-main">',
61
- );
62
- expect(routePage).toContain('<h1 id="starter-heading" className="title">');
63
- expect(routePage).toContain('src="/assets/ultramodern-logo.svg"');
64
- expect(routePage).toContain('height={96}');
65
- expect(routePage).toContain('width={96}');
66
- expect(routePage).toContain(
67
- '<span aria-hidden="true" className="arrow-right" />',
68
- );
69
- expect(routePage).not.toContain('<div className="title">');
70
- expect(modernConfig).toContain(
71
- 'width=device-width, initial-scale=1.0, viewport-fit=cover',
72
- );
73
- expect(modernConfig).not.toContain('user-scalable=no');
74
- expect(modernConfig).not.toContain('maximum-scale');
75
- expect(routeCss).toContain('min-block-size: 100dvh');
76
- expect(routeCss).toContain(
77
- 'grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr))',
78
- );
79
- expect(routeCss).toContain(':focus-visible');
80
- expect(routeCss).toContain('@media (prefers-reduced-motion: reduce)');
81
- expect(routeCss).not.toContain('width: 1100px');
82
- expect(enLocale.home?.meta?.title).toBe('UltraModern.js Starter');
83
- expect(enLocale.home?.meta?.description).toBeTruthy();
84
- expect(enLocale.home?.skipLink).toBeTruthy();
85
- });
86
-
87
- test('retains package-source metadata for generated Modern.js packages', () => {
88
- const packageJson = readJson<{
89
- dependencies?: Record<string, string>;
90
- devDependencies?: Record<string, string>;
91
- scripts?: Record<string, string>;
92
- modernjs?: {
93
- packageSource?: {
94
- config?: string;
95
- strategy?: string;
96
- };
97
- preset?: string;
98
- };
99
- }>('package.json');
100
- const packageSource = readJson<{
101
- modernPackages?: {
102
- packages?: string[];
103
- specifier?: string;
104
- };
105
- strategy?: string;
106
- }>('.modernjs/ultramodern-package-source.json');
107
-
108
- expect(packageJson.modernjs?.preset).toBe('presetUltramodern');
109
- expect(packageJson.modernjs?.packageSource?.config).toBe(
110
- './.modernjs/ultramodern-package-source.json',
111
- );
112
- expect(packageJson.modernjs?.packageSource?.strategy).toBe(
113
- packageSource.strategy,
114
- );
115
- expect(packageSource.strategy).toMatch(/^(workspace|install)$/u);
116
- const generatedModernDependencies = [
117
- ...new Set(
118
- (['dependencies', 'devDependencies'] as const).flatMap(section =>
119
- Object.keys(packageJson[section] ?? {}),
120
- ),
121
- ),
122
- ].filter(packageName => packageName.startsWith('@modern-js/'));
123
- expect(packageSource.modernPackages?.packages?.length).toBeGreaterThan(0);
124
- expect(packageSource.modernPackages?.packages).toEqual(
125
- expect.arrayContaining(generatedModernDependencies),
126
- );
127
- expect(packageSource.modernPackages?.specifier).toBeTruthy();
128
- expect(
129
- packageJson.devDependencies?.['@modern-js/adapter-rstest'],
130
- ).toBeTruthy();
131
- expect(
132
- packageJson.devDependencies?.['@modern-js/code-tools'],
133
- ).toBeTruthy();
134
- expect(
135
- packageJson.devDependencies?.['@modern-js/create'],
136
- ).toBeTruthy();
137
- expect(readText('scripts/check-i18n-strings.mjs')).toContain(
138
- "from '@modern-js/code-tools'",
139
- );
140
- expect(packageJson.scripts?.format).toBe('oxfmt .');
141
- expect(packageJson.scripts?.['format:check']).toBe('oxfmt --check .');
142
- expect(packageJson.scripts?.lint).toBe('oxlint .');
143
- expect(packageJson.scripts?.['lint:fix']).toBe('oxlint . --fix');
144
- expect(packageJson.scripts?.['ultramodern:check']).toContain(
145
- 'pnpm format:check && pnpm lint',
146
- );
147
- expect(packageJson.devDependencies?.oxfmt).toBe('0.53.0');
148
- expect(packageJson.devDependencies?.oxlint).toBe('1.68.0');
149
- expect(packageJson.devDependencies?.ultracite).toBe('7.8.1');
150
- {{#if enableTailwind}}
151
- expect(packageJson.devDependencies?.tailwindcss).toBe('^4.3.0');
152
- expect(packageJson.devDependencies?.['@tailwindcss/postcss']).toBe(
153
- '^4.3.0',
154
- );
155
- {{/if}}
156
- {{#unless enableTailwind}}
157
- expect(packageJson.devDependencies?.tailwindcss).toBeUndefined();
158
- expect(
159
- packageJson.devDependencies?.['@tailwindcss/postcss'],
160
- ).toBeUndefined();
161
- {{/unless}}
162
- });
163
- });