@bleedingdev/modern-js-create 3.2.0-ultramodern.120 → 3.2.0-ultramodern.122

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 (118) hide show
  1. package/README.md +35 -12
  2. package/dist/cjs/create-package-root.cjs +7 -9
  3. package/dist/cjs/index.cjs +74 -44
  4. package/dist/cjs/locale/en.cjs +6 -7
  5. package/dist/cjs/locale/zh.cjs +6 -7
  6. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +337 -0
  7. package/dist/cjs/ultramodern-workspace/app-files.cjs +223 -0
  8. package/dist/cjs/ultramodern-workspace/contracts.cjs +836 -0
  9. package/dist/cjs/ultramodern-workspace/demo-components.cjs +422 -0
  10. package/dist/cjs/ultramodern-workspace/descriptors.cjs +222 -0
  11. package/dist/cjs/ultramodern-workspace/effect-api.cjs +952 -0
  12. package/dist/cjs/ultramodern-workspace/fs-io.cjs +191 -0
  13. package/dist/cjs/ultramodern-workspace/index.cjs +48 -0
  14. package/dist/cjs/ultramodern-workspace/locales.cjs +173 -0
  15. package/dist/cjs/ultramodern-workspace/module-federation.cjs +487 -0
  16. package/dist/cjs/ultramodern-workspace/naming.cjs +161 -0
  17. package/dist/cjs/ultramodern-workspace/package-json.cjs +406 -0
  18. package/dist/cjs/ultramodern-workspace/package-source.cjs +59 -0
  19. package/dist/cjs/ultramodern-workspace/policy.cjs +248 -0
  20. package/dist/cjs/ultramodern-workspace/public-surface.cjs +268 -0
  21. package/dist/cjs/ultramodern-workspace/routes.cjs +375 -0
  22. package/dist/cjs/ultramodern-workspace/types.cjs +61 -0
  23. package/dist/cjs/ultramodern-workspace/versions.cjs +153 -0
  24. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +153 -0
  25. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +175 -0
  26. package/dist/esm/create-package-root.js +7 -9
  27. package/dist/esm/index.js +72 -42
  28. package/dist/esm/locale/en.js +6 -7
  29. package/dist/esm/locale/zh.js +6 -7
  30. package/dist/esm/ultramodern-workspace/add-vertical.js +252 -0
  31. package/dist/esm/ultramodern-workspace/app-files.js +149 -0
  32. package/dist/esm/ultramodern-workspace/contracts.js +741 -0
  33. package/dist/esm/ultramodern-workspace/demo-components.js +363 -0
  34. package/dist/esm/ultramodern-workspace/descriptors.js +133 -0
  35. package/dist/esm/ultramodern-workspace/effect-api.js +854 -0
  36. package/dist/esm/ultramodern-workspace/fs-io.js +90 -0
  37. package/dist/esm/ultramodern-workspace/index.js +3 -0
  38. package/dist/esm/ultramodern-workspace/locales.js +122 -0
  39. package/dist/esm/ultramodern-workspace/module-federation.js +415 -0
  40. package/dist/esm/ultramodern-workspace/naming.js +71 -0
  41. package/dist/esm/ultramodern-workspace/package-json.js +338 -0
  42. package/dist/esm/ultramodern-workspace/package-source.js +21 -0
  43. package/dist/esm/ultramodern-workspace/policy.js +183 -0
  44. package/dist/esm/ultramodern-workspace/public-surface.js +183 -0
  45. package/dist/esm/ultramodern-workspace/routes.js +280 -0
  46. package/dist/esm/ultramodern-workspace/types.js +16 -0
  47. package/dist/esm/ultramodern-workspace/versions.js +34 -0
  48. package/dist/esm/ultramodern-workspace/workspace-scripts.js +91 -0
  49. package/dist/esm/ultramodern-workspace/write-workspace.js +121 -0
  50. package/dist/esm-node/create-package-root.js +7 -9
  51. package/dist/esm-node/index.js +72 -42
  52. package/dist/esm-node/locale/en.js +6 -7
  53. package/dist/esm-node/locale/zh.js +6 -7
  54. package/dist/esm-node/ultramodern-workspace/add-vertical.js +253 -0
  55. package/dist/esm-node/ultramodern-workspace/app-files.js +150 -0
  56. package/dist/esm-node/ultramodern-workspace/contracts.js +742 -0
  57. package/dist/esm-node/ultramodern-workspace/demo-components.js +364 -0
  58. package/dist/esm-node/ultramodern-workspace/descriptors.js +134 -0
  59. package/dist/esm-node/ultramodern-workspace/effect-api.js +855 -0
  60. package/dist/esm-node/ultramodern-workspace/fs-io.js +91 -0
  61. package/dist/esm-node/ultramodern-workspace/index.js +4 -0
  62. package/dist/esm-node/ultramodern-workspace/locales.js +123 -0
  63. package/dist/esm-node/ultramodern-workspace/module-federation.js +416 -0
  64. package/dist/esm-node/ultramodern-workspace/naming.js +72 -0
  65. package/dist/esm-node/ultramodern-workspace/package-json.js +339 -0
  66. package/dist/esm-node/ultramodern-workspace/package-source.js +22 -0
  67. package/dist/esm-node/ultramodern-workspace/policy.js +184 -0
  68. package/dist/esm-node/ultramodern-workspace/public-surface.js +184 -0
  69. package/dist/esm-node/ultramodern-workspace/routes.js +281 -0
  70. package/dist/esm-node/ultramodern-workspace/types.js +17 -0
  71. package/dist/esm-node/ultramodern-workspace/versions.js +35 -0
  72. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +92 -0
  73. package/dist/esm-node/ultramodern-workspace/write-workspace.js +122 -0
  74. package/dist/types/locale/en.d.ts +4 -5
  75. package/dist/types/locale/index.d.ts +8 -10
  76. package/dist/types/locale/zh.d.ts +4 -5
  77. package/dist/types/ultramodern-workspace/add-vertical.d.ts +19 -0
  78. package/dist/types/ultramodern-workspace/app-files.d.ts +14 -0
  79. package/dist/types/ultramodern-workspace/contracts.d.ts +21 -0
  80. package/dist/types/ultramodern-workspace/demo-components.d.ts +9 -0
  81. package/dist/types/ultramodern-workspace/descriptors.d.ts +39 -0
  82. package/dist/types/ultramodern-workspace/effect-api.d.ts +73 -0
  83. package/dist/types/ultramodern-workspace/fs-io.d.ts +18 -0
  84. package/dist/types/ultramodern-workspace/index.d.ts +4 -0
  85. package/dist/types/ultramodern-workspace/locales.d.ts +183 -0
  86. package/dist/types/ultramodern-workspace/module-federation.d.ts +16 -0
  87. package/dist/types/ultramodern-workspace/naming.d.ts +16 -0
  88. package/dist/types/ultramodern-workspace/package-json.d.ts +12 -0
  89. package/dist/types/ultramodern-workspace/package-source.d.ts +2 -0
  90. package/dist/types/ultramodern-workspace/policy.d.ts +60 -0
  91. package/dist/types/ultramodern-workspace/public-surface.d.ts +37 -0
  92. package/dist/types/ultramodern-workspace/routes.d.ts +25 -0
  93. package/dist/types/ultramodern-workspace/types.d.ts +95 -0
  94. package/dist/types/ultramodern-workspace/versions.d.ts +38 -0
  95. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +10 -0
  96. package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -0
  97. package/package.json +4 -3
  98. package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +1 -4
  99. package/template-workspace/.mise.toml.handlebars +1 -0
  100. package/template-workspace/{AGENTS.md → AGENTS.md.handlebars} +12 -7
  101. package/template-workspace/README.md.handlebars +40 -24
  102. package/template-workspace/{pnpm-workspace.yaml → pnpm-workspace.yaml.handlebars} +2 -2
  103. package/template-workspace/scripts/bootstrap-agent-skills.mjs +31 -51
  104. package/templates/app/shell-frame.tsx +49 -0
  105. package/templates/app/ultramodern-route-head.tsx.handlebars +142 -0
  106. package/templates/packages/shared-contracts-index.ts +466 -0
  107. package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +69 -0
  108. package/templates/workspace-scripts/check-ultramodern-i18n-boundaries.mjs +9 -0
  109. package/templates/workspace-scripts/generate-public-surface-assets.mjs +529 -0
  110. package/templates/workspace-scripts/proof-cloudflare-version.mjs +125 -0
  111. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +851 -0
  112. package/templates/workspace-scripts/ultramodern-performance-readiness.config.mjs +7 -0
  113. package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +223 -0
  114. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +656 -0
  115. package/dist/cjs/ultramodern-workspace.cjs +0 -6797
  116. package/dist/esm/ultramodern-workspace.js +0 -6738
  117. package/dist/esm-node/ultramodern-workspace.js +0 -6739
  118. package/dist/types/ultramodern-workspace.d.ts +0 -29
@@ -37,45 +37,14 @@ const commandExists = command => {
37
37
  }
38
38
  };
39
39
 
40
- const runShell = script =>
41
- run('sh', ['-lc', script], {
42
- stdio: 'inherit',
43
- });
44
-
45
- const installGit = () => {
40
+ const requireGit = () => {
46
41
  if (commandExists('git')) {
47
42
  return;
48
43
  }
49
44
 
50
- if (commandExists('brew')) {
51
- run('brew', ['install', 'git'], { stdio: 'inherit' });
52
- } else if (process.platform === 'linux' && commandExists('apt-get')) {
53
- const sudo =
54
- typeof process.getuid === 'function' && process.getuid() === 0
55
- ? ''
56
- : 'sudo ';
57
- runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
58
- } else if (process.platform === 'linux' && commandExists('dnf')) {
59
- const sudo =
60
- typeof process.getuid === 'function' && process.getuid() === 0
61
- ? ''
62
- : 'sudo ';
63
- runShell(`${sudo}dnf install -y git`);
64
- } else if (process.platform === 'linux' && commandExists('yum')) {
65
- const sudo =
66
- typeof process.getuid === 'function' && process.getuid() === 0
67
- ? ''
68
- : 'sudo ';
69
- runShell(`${sudo}yum install -y git`);
70
- } else if (process.platform === 'linux' && commandExists('apk')) {
71
- runShell('apk add --no-cache git');
72
- }
73
-
74
- if (!commandExists('git')) {
75
- throw new Error(
76
- 'Git is required for UltraModern setup. Install git and run pnpm skills:install again.',
77
- );
78
- }
45
+ throw new Error(
46
+ 'Git is required to install agent skills. Install git yourself (for example "brew install git" or "sudo apt-get install git") and run pnpm skills:install again. This script never installs system packages on your behalf.',
47
+ );
79
48
  };
80
49
 
81
50
  const isInsideGitWorkTree = () => {
@@ -183,13 +152,15 @@ const requiredCloneSources = sources.filter(
183
152
  const optionalCloneSources = sources.filter(
184
153
  source => source.install === 'clone-if-authorized',
185
154
  );
186
- const requiredSkills = [
187
- ...(lock.baseline ?? []),
188
- ...requiredCloneSources.flatMap(source => source.baseline ?? []),
189
- ].filter(
190
- (skill, index, skills) =>
191
- skills.findIndex(candidate => candidate.name === skill.name) === index,
155
+ const cloneSourceSkillNames = new Set(
156
+ [...requiredCloneSources, ...optionalCloneSources].flatMap(source =>
157
+ (source.baseline ?? []).map(skill => skill.name),
158
+ ),
192
159
  );
160
+ const vendoredRequiredSkills = (lock.baseline ?? []).filter(
161
+ skill => !cloneSourceSkillNames.has(skill.name),
162
+ );
163
+ const cloneOptIn = truthy(process.env.ULTRAMODERN_AGENT_SKILLS);
193
164
 
194
165
  if (skipRequested) {
195
166
  const reason = 'agent skills bootstrap skipped by environment';
@@ -203,12 +174,15 @@ if (skipRequested) {
203
174
  }
204
175
 
205
176
  if (checkOnly) {
206
- const missingRequired = requiredSkills
177
+ const missingVendored = vendoredRequiredSkills
207
178
  .map(skill => skill.name)
208
179
  .filter(
209
180
  skillName => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
210
181
  );
211
- const missingOptional = optionalCloneSources.flatMap(source =>
182
+ const missingCloneInstalled = [
183
+ ...requiredCloneSources,
184
+ ...optionalCloneSources,
185
+ ].flatMap(source =>
212
186
  (source.baseline ?? [])
213
187
  .map(skill => skill.name)
214
188
  .filter(
@@ -217,27 +191,33 @@ if (checkOnly) {
217
191
  ),
218
192
  );
219
193
 
220
- if (missingRequired.length > 0) {
194
+ if (missingVendored.length > 0) {
221
195
  console.error(
222
- `Required agent skills not installed: ${missingRequired.join(', ')}. Run pnpm skills:install.`,
196
+ `Required agent skills not installed: ${missingVendored.join(', ')}. Run pnpm skills:install.`,
223
197
  );
224
198
  process.exit(1);
225
199
  }
226
200
 
227
- if (missingOptional.length > 0) {
201
+ if (missingCloneInstalled.length > 0) {
228
202
  console.warn(
229
- `Private skills not installed: ${missingOptional.join(', ')}. Run pnpm skills:install if you have access.`,
203
+ `Clone-installed agent skills not present: ${missingCloneInstalled.join(', ')}. Run pnpm skills:install to fetch them.`,
230
204
  );
231
205
  } else {
232
- console.log('Required and private agent skills are installed.');
233
- process.exit(0);
206
+ console.log('All pinned agent skills are installed.');
234
207
  }
235
- console.log('Required agent skills are installed.');
208
+ process.exit(0);
209
+ }
210
+
211
+ if (postinstall && !cloneOptIn) {
212
+ console.log(
213
+ 'Skipping agent skill repository clones during postinstall. Run pnpm skills:install (or set ULTRAMODERN_AGENT_SKILLS=1 before installing) to fetch them.',
214
+ );
215
+ installLefthook();
236
216
  process.exit(0);
237
217
  }
238
218
 
239
219
  fs.mkdirSync(installDir, { recursive: true });
240
- installGit();
220
+ requireGit();
241
221
  initializeGitRepository();
242
222
 
243
223
  for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
@@ -0,0 +1,49 @@
1
+ import { useLocalizedLocation, useModernI18n } from '@modern-js/plugin-i18n/runtime';
2
+ import type { ReactNode } from 'react';
3
+ import { Header, StatusBadge } from './vertical-components';
4
+
5
+ interface ShellFrameProps {
6
+ children: ReactNode;
7
+ }
8
+
9
+ export default function ShellFrame({ children }: ShellFrameProps) {
10
+ const { i18nInstance, language } = useModernI18n();
11
+ const t = i18nInstance['t'].bind(i18nInstance);
12
+ const { alternates } = useLocalizedLocation();
13
+
14
+ return (
15
+ <main className="shell:min-h-screen shell:bg-um-canvas shell:px-4 shell:py-5 shell:text-um-foreground shell:sm:px-6 shell:lg:px-12">
16
+ <div className="shell:mx-auto shell:flex shell:min-h-20 shell:max-w-7xl shell:flex-col shell:items-start shell:gap-3 shell:bg-white/90 shell:px-4 shell:py-3 shell:shadow-xl shell:shadow-stone-900/10 shell:sm:px-6 shell:md:flex-row shell:md:flex-wrap shell:md:items-center shell:md:justify-between">
17
+ <Header />
18
+ <div className="shell:flex shell:min-w-0 shell:flex-wrap shell:items-center shell:gap-2 shell:md:ml-auto">
19
+ <label className="shell:sr-only" htmlFor="ultramodern-language">
20
+ {t('shell.language.switcher')}
21
+ </label>
22
+ <select
23
+ aria-label={t('shell.language.switcher')}
24
+ className="shell:h-10 shell:w-10 shell:cursor-pointer shell:appearance-none shell:border-0 shell:bg-transparent shell:p-0 shell:text-center shell:text-3xl shell:font-black shell:leading-none shell:text-stone-950 shell:shadow-none shell:[appearance:none] shell:[text-align-last:center] shell:focus-visible:rounded-md shell:focus-visible:outline-3 shell:focus-visible:outline-offset-2 shell:focus-visible:outline-emerald-700/40 shell:[&::-ms-expand]:hidden shell:[&::picker-icon]:hidden shell:[&_option]:text-xl"
25
+ id="ultramodern-language"
26
+ name="language"
27
+ onChange={event => {
28
+ const nextLanguage = event.currentTarget.value;
29
+ const targetHref = alternates[nextLanguage];
30
+ if (targetHref !== undefined) {
31
+ window.location.assign(targetHref);
32
+ }
33
+ }}
34
+ value={language}
35
+ >
36
+ <option aria-label={t('shell.language.en')} value="en">
37
+ 🇬🇧
38
+ </option>
39
+ <option aria-label={t('shell.language.cs')} value="cs">
40
+ 🇨🇿
41
+ </option>
42
+ </select>
43
+ <StatusBadge />
44
+ </div>
45
+ </div>
46
+ {children}
47
+ </main>
48
+ );
49
+ }
@@ -0,0 +1,142 @@
1
+ import { useLocalizedLocation, useModernI18n } from '@modern-js/plugin-i18n/runtime';
2
+ import { Helmet } from '@modern-js/runtime/head';
3
+ import {
4
+ ultramodernRouteMetadata,
5
+ } from './ultramodern-route-metadata';
6
+ import type { RouteJsonLd } from './ultramodern-jsonld';
7
+
8
+ const appName = {{appDisplayNameJson}};
9
+ const fallbackLanguage = 'en';
10
+ const supportedLanguages = ['en', 'cs'] as const;
11
+ type SupportedLanguage = (typeof supportedLanguages)[number];
12
+ type RouteMetadata = (typeof ultramodernRouteMetadata)[number] & {
13
+ readonly jsonLd?: RouteJsonLd;
14
+ };
15
+
16
+ const routeMetadata = ultramodernRouteMetadata as readonly RouteMetadata[];
17
+
18
+ const isSupportedLanguage = (value: string): value is SupportedLanguage =>
19
+ supportedLanguages.includes(value as SupportedLanguage);
20
+
21
+ const normalisePath = (pathname: string) => {
22
+ const normalised = pathname.replaceAll(/\/+/gu, '/').replace(/\/+$/u, '');
23
+ return normalised.length > 0 ? normalised : '/';
24
+ };
25
+
26
+ const stripLanguagePrefix = (pathname: string) => {
27
+ const segments = normalisePath(pathname).split('/').filter(Boolean);
28
+ if (segments.length > 0 && isSupportedLanguage(segments[0] ?? '')) {
29
+ segments.shift();
30
+ }
31
+ return `/${segments.join('/')}`;
32
+ };
33
+
34
+ const escapeRegExp = (value: string) =>
35
+ value.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&');
36
+
37
+ const paramName = (segment: string) => segment.slice(1).replace(/\?$/u, '');
38
+
39
+ const matchPattern = (pathname: string, pattern: string) => {
40
+ const names: string[] = [];
41
+ const source = normalisePath(pattern)
42
+ .split('/')
43
+ .filter(Boolean)
44
+ .map(segment => {
45
+ if (segment.startsWith(':')) {
46
+ names.push(paramName(segment));
47
+ return segment.endsWith('?') ? '(?:/([^/]+))?' : '/([^/]+)';
48
+ }
49
+ return `/${escapeRegExp(segment)}`;
50
+ })
51
+ .join('');
52
+ const match = new RegExp(`^${source || '/'}$`, 'u').exec(
53
+ normalisePath(pathname),
54
+ );
55
+
56
+ if (match === null) {
57
+ return;
58
+ }
59
+
60
+ const params: Record<string, string> = {};
61
+ for (const [index, name] of names.entries()) {
62
+ params[name] = decodeURIComponent(match[index + 1] ?? '');
63
+ }
64
+ return params;
65
+ };
66
+
67
+ const resolveRouteMetadata = (pathname: string) => {
68
+ const pathWithoutLanguage = stripLanguagePrefix(pathname);
69
+
70
+ for (const route of routeMetadata) {
71
+ const canonicalParams = matchPattern(pathWithoutLanguage, route.canonicalPath);
72
+ if (canonicalParams !== undefined) {
73
+ return route;
74
+ }
75
+
76
+ for (const language of supportedLanguages) {
77
+ const params = matchPattern(pathWithoutLanguage, route.localisedPaths[language]);
78
+ if (params !== undefined) {
79
+ return route;
80
+ }
81
+ }
82
+ }
83
+
84
+ return routeMetadata[0];
85
+ };
86
+
87
+ const absoluteUrl = (pathname: string) => {
88
+ const origin = ULTRAMODERN_SITE_URL.replace(/\/+$/u, '');
89
+ return `${origin}${pathname}`;
90
+ };
91
+
92
+ const sanitiseJsonLd = (value: RouteJsonLd) =>
93
+ JSON.stringify(value).replaceAll('<', '\\u003c');
94
+
95
+ export const UltramodernRouteHead = () => {
96
+ const { i18nInstance } = useModernI18n();
97
+ const t = i18nInstance['t'].bind(i18nInstance);
98
+ const { canonical, alternates } = useLocalizedLocation();
99
+ const route = resolveRouteMetadata(canonical);
100
+ const title = route ? t(route.titleKey) : appName;
101
+ const description = route ? t(route.descriptionKey) : appName;
102
+ const canonicalUrl = absoluteUrl(alternates[fallbackLanguage] ?? `/${fallbackLanguage}`);
103
+ const indexable = route?.public === true && route?.indexable === true;
104
+ const jsonLd = indexable ? route?.jsonLd : undefined;
105
+
106
+ return (
107
+ <Helmet htmlAttributes={{ lang: i18nInstance.language ?? fallbackLanguage }}>
108
+ <title>{title}</title>
109
+ <meta content={description} name="description" />
110
+ <meta content={indexable ? 'index, follow' : 'noindex, nofollow'} name="robots" />
111
+ {indexable && (
112
+ <>
113
+ <link rel="canonical" href={canonicalUrl} />
114
+ {supportedLanguages.map(code => (
115
+ <link
116
+ href={absoluteUrl(alternates[code] ?? `/${code}`)}
117
+ hrefLang={code}
118
+ key={code}
119
+ rel="alternate"
120
+ />
121
+ ))}
122
+ <link
123
+ href={absoluteUrl(alternates[fallbackLanguage] ?? `/${fallbackLanguage}`)}
124
+ hrefLang="x-default"
125
+ rel="alternate"
126
+ />
127
+ <meta content={title} property="og:title" />
128
+ <meta content={description} property="og:description" />
129
+ <meta content={canonicalUrl} property="og:url" />
130
+ <meta content="website" property="og:type" />
131
+ <meta content={i18nInstance.language ?? fallbackLanguage} property="og:locale" />
132
+ <meta content="summary_large_image" name="twitter:card" />
133
+ <meta content={title} name="twitter:title" />
134
+ <meta content={description} name="twitter:description" />
135
+ {jsonLd && (
136
+ <script type="application/ld+json">{sanitiseJsonLd(jsonLd)}</script>
137
+ )}
138
+ </>
139
+ )}
140
+ </Helmet>
141
+ );
142
+ };