@bleedingdev/modern-js-create 3.2.0-ultramodern.98 → 3.4.0-ultramodern.0

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 (194) hide show
  1. package/README.md +281 -106
  2. package/bin/run.js +1 -0
  3. package/dist/cjs/create-package-root.cjs +63 -0
  4. package/dist/cjs/index.cjs +658 -0
  5. package/dist/cjs/locale/en.cjs +101 -0
  6. package/dist/cjs/locale/index.cjs +50 -0
  7. package/dist/cjs/locale/zh.cjs +101 -0
  8. package/dist/cjs/ultramodern-package-source.cjs +135 -0
  9. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +603 -0
  10. package/dist/cjs/ultramodern-workspace/app-files.cjs +223 -0
  11. package/dist/cjs/ultramodern-workspace/codesmith.cjs +134 -0
  12. package/dist/cjs/ultramodern-workspace/contracts.cjs +837 -0
  13. package/dist/cjs/ultramodern-workspace/demo-components.cjs +422 -0
  14. package/dist/cjs/ultramodern-workspace/descriptors.cjs +222 -0
  15. package/dist/cjs/ultramodern-workspace/effect-api.cjs +952 -0
  16. package/dist/cjs/ultramodern-workspace/fs-io.cjs +191 -0
  17. package/dist/cjs/ultramodern-workspace/generation-result.cjs +158 -0
  18. package/dist/cjs/ultramodern-workspace/index.cjs +51 -0
  19. package/dist/cjs/ultramodern-workspace/locales.cjs +173 -0
  20. package/dist/cjs/ultramodern-workspace/module-federation.cjs +487 -0
  21. package/dist/cjs/ultramodern-workspace/naming.cjs +161 -0
  22. package/dist/cjs/ultramodern-workspace/overlays.cjs +120 -0
  23. package/dist/cjs/ultramodern-workspace/package-json.cjs +406 -0
  24. package/dist/cjs/ultramodern-workspace/package-source.cjs +59 -0
  25. package/dist/cjs/ultramodern-workspace/policy.cjs +248 -0
  26. package/dist/cjs/ultramodern-workspace/public-api.cjs +47 -0
  27. package/dist/cjs/ultramodern-workspace/public-surface.cjs +268 -0
  28. package/dist/cjs/ultramodern-workspace/routes.cjs +375 -0
  29. package/dist/cjs/ultramodern-workspace/types.cjs +61 -0
  30. package/dist/cjs/ultramodern-workspace/versions.cjs +161 -0
  31. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +154 -0
  32. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +197 -0
  33. package/dist/esm/create-package-root.js +14 -0
  34. package/dist/esm/index.js +621 -0
  35. package/dist/esm/locale/en.js +63 -0
  36. package/dist/esm/locale/index.js +9 -0
  37. package/dist/esm/locale/zh.js +63 -0
  38. package/dist/esm/ultramodern-package-source.js +63 -0
  39. package/dist/esm/ultramodern-workspace/add-vertical.js +511 -0
  40. package/dist/esm/ultramodern-workspace/app-files.js +149 -0
  41. package/dist/esm/ultramodern-workspace/codesmith.js +86 -0
  42. package/dist/esm/ultramodern-workspace/contracts.js +742 -0
  43. package/dist/esm/ultramodern-workspace/demo-components.js +363 -0
  44. package/dist/esm/ultramodern-workspace/descriptors.js +133 -0
  45. package/dist/esm/ultramodern-workspace/effect-api.js +854 -0
  46. package/dist/esm/ultramodern-workspace/fs-io.js +90 -0
  47. package/dist/esm/ultramodern-workspace/generation-result.js +102 -0
  48. package/dist/esm/ultramodern-workspace/index.js +3 -0
  49. package/dist/esm/ultramodern-workspace/locales.js +122 -0
  50. package/dist/esm/ultramodern-workspace/module-federation.js +415 -0
  51. package/dist/esm/ultramodern-workspace/naming.js +71 -0
  52. package/dist/esm/ultramodern-workspace/overlays.js +79 -0
  53. package/dist/esm/ultramodern-workspace/package-json.js +338 -0
  54. package/dist/esm/ultramodern-workspace/package-source.js +21 -0
  55. package/dist/esm/ultramodern-workspace/policy.js +183 -0
  56. package/dist/esm/ultramodern-workspace/public-api.js +2 -0
  57. package/dist/esm/ultramodern-workspace/public-surface.js +183 -0
  58. package/dist/esm/ultramodern-workspace/routes.js +280 -0
  59. package/dist/esm/ultramodern-workspace/types.js +16 -0
  60. package/dist/esm/ultramodern-workspace/versions.js +36 -0
  61. package/dist/esm/ultramodern-workspace/workspace-scripts.js +92 -0
  62. package/dist/esm/ultramodern-workspace/write-workspace.js +143 -0
  63. package/dist/esm-node/create-package-root.js +15 -0
  64. package/dist/esm-node/index.js +622 -0
  65. package/dist/esm-node/locale/en.js +64 -0
  66. package/dist/esm-node/locale/index.js +10 -0
  67. package/dist/esm-node/locale/zh.js +64 -0
  68. package/dist/esm-node/ultramodern-package-source.js +64 -0
  69. package/dist/esm-node/ultramodern-workspace/add-vertical.js +512 -0
  70. package/dist/esm-node/ultramodern-workspace/app-files.js +150 -0
  71. package/dist/esm-node/ultramodern-workspace/codesmith.js +87 -0
  72. package/dist/esm-node/ultramodern-workspace/contracts.js +743 -0
  73. package/dist/esm-node/ultramodern-workspace/demo-components.js +364 -0
  74. package/dist/esm-node/ultramodern-workspace/descriptors.js +134 -0
  75. package/dist/esm-node/ultramodern-workspace/effect-api.js +855 -0
  76. package/dist/esm-node/ultramodern-workspace/fs-io.js +91 -0
  77. package/dist/esm-node/ultramodern-workspace/generation-result.js +103 -0
  78. package/dist/esm-node/ultramodern-workspace/index.js +4 -0
  79. package/dist/esm-node/ultramodern-workspace/locales.js +123 -0
  80. package/dist/esm-node/ultramodern-workspace/module-federation.js +416 -0
  81. package/dist/esm-node/ultramodern-workspace/naming.js +72 -0
  82. package/dist/esm-node/ultramodern-workspace/overlays.js +80 -0
  83. package/dist/esm-node/ultramodern-workspace/package-json.js +339 -0
  84. package/dist/esm-node/ultramodern-workspace/package-source.js +22 -0
  85. package/dist/esm-node/ultramodern-workspace/policy.js +184 -0
  86. package/dist/esm-node/ultramodern-workspace/public-api.js +3 -0
  87. package/dist/esm-node/ultramodern-workspace/public-surface.js +184 -0
  88. package/dist/esm-node/ultramodern-workspace/routes.js +281 -0
  89. package/dist/esm-node/ultramodern-workspace/types.js +17 -0
  90. package/dist/esm-node/ultramodern-workspace/versions.js +37 -0
  91. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +93 -0
  92. package/dist/esm-node/ultramodern-workspace/write-workspace.js +144 -0
  93. package/dist/types/create-package-root.d.ts +1 -0
  94. package/dist/types/locale/en.d.ts +12 -8
  95. package/dist/types/locale/index.d.ts +125 -2
  96. package/dist/types/locale/zh.d.ts +12 -8
  97. package/dist/types/ultramodern-package-source.d.ts +28 -0
  98. package/dist/types/ultramodern-workspace/add-vertical.d.ts +36 -0
  99. package/dist/types/ultramodern-workspace/app-files.d.ts +14 -0
  100. package/dist/types/ultramodern-workspace/codesmith.d.ts +53 -0
  101. package/dist/types/ultramodern-workspace/contracts.d.ts +21 -0
  102. package/dist/types/ultramodern-workspace/demo-components.d.ts +9 -0
  103. package/dist/types/ultramodern-workspace/descriptors.d.ts +39 -0
  104. package/dist/types/ultramodern-workspace/effect-api.d.ts +73 -0
  105. package/dist/types/ultramodern-workspace/fs-io.d.ts +18 -0
  106. package/dist/types/ultramodern-workspace/generation-result.d.ts +14 -0
  107. package/dist/types/ultramodern-workspace/index.d.ts +4 -0
  108. package/dist/types/ultramodern-workspace/locales.d.ts +183 -0
  109. package/dist/types/ultramodern-workspace/module-federation.d.ts +16 -0
  110. package/dist/types/ultramodern-workspace/naming.d.ts +16 -0
  111. package/dist/types/ultramodern-workspace/overlays.d.ts +6 -0
  112. package/dist/types/ultramodern-workspace/package-json.d.ts +12 -0
  113. package/dist/types/ultramodern-workspace/package-source.d.ts +2 -0
  114. package/dist/types/ultramodern-workspace/policy.d.ts +60 -0
  115. package/dist/types/ultramodern-workspace/public-api.d.ts +3 -0
  116. package/dist/types/ultramodern-workspace/public-surface.d.ts +37 -0
  117. package/dist/types/ultramodern-workspace/routes.d.ts +25 -0
  118. package/dist/types/ultramodern-workspace/types.d.ts +197 -0
  119. package/dist/types/ultramodern-workspace/versions.d.ts +40 -0
  120. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +10 -0
  121. package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -0
  122. package/package.json +58 -15
  123. package/template-workspace/.agents/agent-reference-repos.json +24 -0
  124. package/template-workspace/.agents/rstackjs-agent-skills-LICENSE +21 -0
  125. package/template-workspace/.agents/skills/rsbuild-best-practices/SKILL.md +57 -0
  126. package/template-workspace/.agents/skills/rsdoctor-analysis/SKILL.md +96 -0
  127. package/template-workspace/.agents/skills/rsdoctor-analysis/references/command-map.md +113 -0
  128. package/template-workspace/.agents/skills/rsdoctor-analysis/references/common-analysis-patterns.md +190 -0
  129. package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-common.md +88 -0
  130. package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-rspack.md +138 -0
  131. package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-webpack.md +71 -0
  132. package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor.md +39 -0
  133. package/template-workspace/.agents/skills/rsdoctor-analysis/references/rsdoctor-data-types.md +103 -0
  134. package/template-workspace/.agents/skills/rslib-best-practices/SKILL.md +58 -0
  135. package/template-workspace/.agents/skills/rslib-modern-package/SKILL.md +173 -0
  136. package/template-workspace/.agents/skills/rspack-best-practices/SKILL.md +70 -0
  137. package/template-workspace/.agents/skills/rspack-tracing/SKILL.md +75 -0
  138. package/template-workspace/.agents/skills/rspack-tracing/references/bottlenecks.md +47 -0
  139. package/template-workspace/.agents/skills/rspack-tracing/references/tracing-guide.md +38 -0
  140. package/template-workspace/.agents/skills/rspack-tracing/scripts/analyze_trace.js +184 -0
  141. package/template-workspace/.agents/skills/rstest-best-practices/SKILL.md +133 -0
  142. package/template-workspace/.agents/skills-lock.json +114 -0
  143. package/template-workspace/.codex/hooks.json +16 -0
  144. package/template-workspace/.github/renovate.json +29 -0
  145. package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +67 -0
  146. package/template-workspace/.gitignore.handlebars +5 -0
  147. package/template-workspace/.mise.toml.handlebars +3 -0
  148. package/template-workspace/{AGENTS.md → AGENTS.md.handlebars} +20 -9
  149. package/template-workspace/README.md.handlebars +85 -14
  150. package/template-workspace/lefthook.yml +18 -4
  151. package/template-workspace/pnpm-workspace.yaml.handlebars +45 -0
  152. package/template-workspace/scripts/bootstrap-agent-skills.mjs +69 -75
  153. package/template-workspace/scripts/setup-agent-reference-repos.mjs +8 -6
  154. package/templates/app/shell-frame.tsx +49 -0
  155. package/templates/app/ultramodern-route-head.tsx.handlebars +142 -0
  156. package/templates/packages/shared-contracts-index.ts +466 -0
  157. package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +69 -0
  158. package/templates/workspace-scripts/check-ultramodern-i18n-boundaries.mjs +9 -0
  159. package/templates/workspace-scripts/generate-public-surface-assets.mjs +529 -0
  160. package/templates/workspace-scripts/proof-cloudflare-version.mjs +125 -0
  161. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +851 -0
  162. package/templates/workspace-scripts/ultramodern-performance-readiness.config.mjs +7 -0
  163. package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +223 -0
  164. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +986 -0
  165. package/dist/index.js +0 -6625
  166. package/dist/types/ultramodern-workspace.d.ts +0 -31
  167. package/template/AGENTS.md +0 -23
  168. package/template/README.md +0 -105
  169. package/template/api/effect/index.ts.handlebars +0 -34
  170. package/template/api/lambda/hello.ts.handlebars +0 -6
  171. package/template/config/public/locales/cs/translation.json +0 -39
  172. package/template/config/public/locales/en/translation.json +0 -39
  173. package/template/lefthook.yml +0 -10
  174. package/template/modern.config.ts.handlebars +0 -71
  175. package/template/oxfmt.config.ts +0 -15
  176. package/template/oxlint.config.ts +0 -19
  177. package/template/package.json.handlebars +0 -66
  178. package/template/pnpm-workspace.yaml +0 -29
  179. package/template/postcss.config.mjs.handlebars +0 -6
  180. package/template/rstest.config.mts +0 -5
  181. package/template/scripts/bootstrap-agent-skills.mjs +0 -228
  182. package/template/scripts/check-i18n-strings.mjs +0 -94
  183. package/template/scripts/validate-ultramodern.mjs.handlebars +0 -538
  184. package/template/shared/effect/api.ts.handlebars +0 -17
  185. package/template/src/modern-app-env.d.ts +0 -3
  186. package/template/src/modern.runtime.ts.handlebars +0 -23
  187. package/template/src/routes/[lang]/page.tsx.handlebars +0 -211
  188. package/template/src/routes/index.css.handlebars +0 -129
  189. package/template/src/routes/layout.tsx.handlebars +0 -10
  190. package/template/tailwind.config.ts.handlebars +0 -10
  191. package/template/tests/tsconfig.json +0 -7
  192. package/template/tests/ultramodern.contract.test.ts.handlebars +0 -78
  193. package/template/tsconfig.json +0 -120
  194. package/template-workspace/pnpm-workspace.yaml +0 -35
@@ -7,6 +7,16 @@ const root = process.cwd();
7
7
  const lockPath = path.join(root, '.agents/skills-lock.json');
8
8
  const checkOnly = process.argv.includes('--check');
9
9
  const force = process.argv.includes('--force');
10
+ const postinstall = process.argv.includes('--postinstall');
11
+ const truthy = value => /^(1|true|yes|on)$/i.test(String(value ?? ''));
12
+ const falsy = value => /^(0|false|no|off)$/i.test(String(value ?? ''));
13
+ const skipRequested =
14
+ truthy(process.env.ULTRAMODERN_SKIP_AGENT_SKILLS) ||
15
+ falsy(process.env.ULTRAMODERN_AGENT_SKILLS);
16
+ const cloneTimeoutMs = Number.parseInt(
17
+ process.env.ULTRAMODERN_AGENT_SKILLS_CLONE_TIMEOUT_MS ?? '60000',
18
+ 10,
19
+ );
10
20
 
11
21
  const readJson = filePath => JSON.parse(fs.readFileSync(filePath, 'utf-8'));
12
22
 
@@ -15,6 +25,7 @@ const run = (command, args, options = {}) =>
15
25
  cwd: options.cwd ?? root,
16
26
  encoding: 'utf-8',
17
27
  stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
28
+ timeout: options.timeout,
18
29
  });
19
30
 
20
31
  const commandExists = command => {
@@ -26,45 +37,14 @@ const commandExists = command => {
26
37
  }
27
38
  };
28
39
 
29
- const runShell = script =>
30
- run('sh', ['-lc', script], {
31
- stdio: 'inherit',
32
- });
33
-
34
- const installGit = () => {
40
+ const requireGit = () => {
35
41
  if (commandExists('git')) {
36
42
  return;
37
43
  }
38
44
 
39
- if (commandExists('brew')) {
40
- run('brew', ['install', 'git'], { stdio: 'inherit' });
41
- } else if (process.platform === 'linux' && commandExists('apt-get')) {
42
- const sudo =
43
- typeof process.getuid === 'function' && process.getuid() === 0
44
- ? ''
45
- : 'sudo ';
46
- runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
47
- } else if (process.platform === 'linux' && commandExists('dnf')) {
48
- const sudo =
49
- typeof process.getuid === 'function' && process.getuid() === 0
50
- ? ''
51
- : 'sudo ';
52
- runShell(`${sudo}dnf install -y git`);
53
- } else if (process.platform === 'linux' && commandExists('yum')) {
54
- const sudo =
55
- typeof process.getuid === 'function' && process.getuid() === 0
56
- ? ''
57
- : 'sudo ';
58
- runShell(`${sudo}yum install -y git`);
59
- } else if (process.platform === 'linux' && commandExists('apk')) {
60
- runShell('apk add --no-cache git');
61
- }
62
-
63
- if (!commandExists('git')) {
64
- throw new Error(
65
- 'Git is required for UltraModern setup. Install git and run pnpm skills:install again.',
66
- );
67
- }
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
+ );
68
48
  };
69
49
 
70
50
  const isInsideGitWorkTree = () => {
@@ -106,12 +86,14 @@ const removeTree = dir =>
106
86
 
107
87
  const cloneSource = (source, targetDir) => {
108
88
  if (source.commit) {
109
- run('git', ['init', targetDir]);
89
+ run('git', ['init', targetDir], { timeout: 30000 });
110
90
  run('git', ['remote', 'add', 'origin', source.repository], {
111
91
  cwd: targetDir,
92
+ timeout: 30000,
112
93
  });
113
94
  run('git', ['fetch', '--depth', '1', '--quiet', 'origin', source.commit], {
114
95
  cwd: targetDir,
96
+ timeout: cloneTimeoutMs,
115
97
  });
116
98
  run(
117
99
  'git',
@@ -123,32 +105,24 @@ const cloneSource = (source, targetDir) => {
123
105
  '--quiet',
124
106
  'FETCH_HEAD',
125
107
  ],
126
- { cwd: targetDir },
108
+ { cwd: targetDir, timeout: 30000 },
127
109
  );
128
110
  return;
129
111
  }
130
112
 
131
113
  const repo = source.repository.replace(/^https:\/\/github.com\//u, '');
132
114
  try {
133
- run('gh', [
134
- 'repo',
135
- 'clone',
136
- repo,
137
- targetDir,
138
- '--',
139
- '--depth',
140
- '1',
141
- '--quiet',
142
- ]);
115
+ run(
116
+ 'gh',
117
+ ['repo', 'clone', repo, targetDir, '--', '--depth', '1', '--quiet'],
118
+ { timeout: cloneTimeoutMs },
119
+ );
143
120
  } catch {
144
- run('git', [
145
- 'clone',
146
- '--depth',
147
- '1',
148
- '--quiet',
149
- source.repository,
150
- targetDir,
151
- ]);
121
+ run(
122
+ 'git',
123
+ ['clone', '--depth', '1', '--quiet', source.repository, targetDir],
124
+ { timeout: cloneTimeoutMs },
125
+ );
152
126
  }
153
127
  };
154
128
 
@@ -178,21 +152,37 @@ const requiredCloneSources = sources.filter(
178
152
  const optionalCloneSources = sources.filter(
179
153
  source => source.install === 'clone-if-authorized',
180
154
  );
181
- const requiredSkills = [
182
- ...(lock.baseline ?? []),
183
- ...requiredCloneSources.flatMap(source => source.baseline ?? []),
184
- ].filter(
185
- (skill, index, skills) =>
186
- 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
+ ),
187
159
  );
160
+ const vendoredRequiredSkills = (lock.baseline ?? []).filter(
161
+ skill => !cloneSourceSkillNames.has(skill.name),
162
+ );
163
+ const cloneOptIn = truthy(process.env.ULTRAMODERN_AGENT_SKILLS);
164
+
165
+ if (skipRequested) {
166
+ const reason = 'agent skills bootstrap skipped by environment';
167
+ if (checkOnly) {
168
+ console.log(reason);
169
+ process.exit(0);
170
+ }
171
+ console.log(reason);
172
+ installLefthook();
173
+ process.exit(0);
174
+ }
188
175
 
189
176
  if (checkOnly) {
190
- const missingRequired = requiredSkills
177
+ const missingVendored = vendoredRequiredSkills
191
178
  .map(skill => skill.name)
192
179
  .filter(
193
180
  skillName => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
194
181
  );
195
- const missingOptional = optionalCloneSources.flatMap(source =>
182
+ const missingCloneInstalled = [
183
+ ...requiredCloneSources,
184
+ ...optionalCloneSources,
185
+ ].flatMap(source =>
196
186
  (source.baseline ?? [])
197
187
  .map(skill => skill.name)
198
188
  .filter(
@@ -201,27 +191,33 @@ if (checkOnly) {
201
191
  ),
202
192
  );
203
193
 
204
- if (missingRequired.length > 0) {
194
+ if (missingVendored.length > 0) {
205
195
  console.error(
206
- `Required agent skills not installed: ${missingRequired.join(', ')}. Run pnpm skills:install.`,
196
+ `Required agent skills not installed: ${missingVendored.join(', ')}. Run pnpm skills:install.`,
207
197
  );
208
198
  process.exit(1);
209
199
  }
210
200
 
211
- if (missingOptional.length > 0) {
201
+ if (missingCloneInstalled.length > 0) {
212
202
  console.warn(
213
- `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.`,
214
204
  );
215
205
  } else {
216
- console.log('Required and private agent skills are installed.');
217
- process.exit(0);
206
+ console.log('All pinned agent skills are installed.');
218
207
  }
219
- 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();
220
216
  process.exit(0);
221
217
  }
222
218
 
223
219
  fs.mkdirSync(installDir, { recursive: true });
224
- installGit();
220
+ requireGit();
225
221
  initializeGitRepository();
226
222
 
227
223
  for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
@@ -230,10 +226,8 @@ for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
230
226
  try {
231
227
  cloneSource(source, tempDir);
232
228
  } catch (error) {
233
- if (source.install === 'clone-if-authorized') {
234
- console.warn(
235
- `Skipping ${source.repository}; current developer may not have access.`,
236
- );
229
+ if (source.install === 'clone-if-authorized' || postinstall) {
230
+ console.warn(`Skipping ${source.repository}; ${error.message}`);
237
231
  continue;
238
232
  }
239
233
  throw error;
@@ -122,6 +122,12 @@ function porcelainStatus() {
122
122
  return run('git', ['status', '--porcelain'], { timeout: 30000 });
123
123
  }
124
124
 
125
+ function commitInstallerChanges(message) {
126
+ run('git', ['commit', '--no-verify', '-m', message], {
127
+ timeout: 120000,
128
+ });
129
+ }
130
+
125
131
  function ensureGitRepository() {
126
132
  if (!isGitWorkTree()) {
127
133
  if (checkOnly) {
@@ -139,9 +145,7 @@ function ensureGitRepository() {
139
145
  }
140
146
  log('creating initial workspace commit before adding reference subtrees');
141
147
  run('git', ['add', '-A'], { timeout: 30000 });
142
- run('git', ['commit', '-m', 'Initialize UltraModern workspace'], {
143
- timeout: 120000,
144
- });
148
+ commitInstallerChanges('Initialize UltraModern workspace');
145
149
  return true;
146
150
  }
147
151
 
@@ -311,9 +315,7 @@ function commitManifestIfChanged() {
311
315
  return;
312
316
  }
313
317
  run('git', ['add', manifestPath], { timeout: 30000 });
314
- run('git', ['commit', '-m', 'Record agent reference repo manifest'], {
315
- timeout: 120000,
316
- });
318
+ commitInstallerChanges('Record agent reference repo manifest');
317
319
  }
318
320
 
319
321
  function main() {
@@ -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
+ };