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

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 +593 -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
@@ -0,0 +1,253 @@
1
+ import "node:module";
2
+ import node_fs from "node:fs";
3
+ import node_path from "node:path";
4
+ import { WORKSPACE_PACKAGE_VERSION } from "../ultramodern-package-source.js";
5
+ import { createAppEnvDts, createAppRuntimeConfig, createShellFrameComponent } from "./app-files.js";
6
+ import { createGeneratedContract } from "./contracts.js";
7
+ import { createShellPage, createShellRemoteComponents } from "./demo-components.js";
8
+ import { GENERATED_CONTRACT_PATH, appHasEffectApi, appI18nNamespace, createModuleFederationRemoteContracts, createNeutralOwnership, createShellHost, createVerticalDescriptor, effectApiPrefix, remoteDependencyAlias, shellApp, zephyrRemoteDependency } from "./descriptors.js";
9
+ import { createShellEffectClient, effectApiTopologyMetadata } from "./effect-api.js";
10
+ import { readJsonFile, writeFileReplacing, writeJsonFile } from "./fs-io.js";
11
+ import { createAppPublicLocaleMessages } from "./locales.js";
12
+ import { createShellModuleFederationConfig } from "./module-federation.js";
13
+ import { assertUniqueTailwindPrefixes, packageName, toEnvSegment, toKebabCase, toPackageScope, toPascalCase } from "./naming.js";
14
+ import { createAppPackage, createRootPackageJson } from "./package-json.js";
15
+ import { resolvePackageSource } from "./package-source.js";
16
+ import { createCloudflareDeployContract } from "./policy.js";
17
+ import { createPublicWebAppArtifacts, rewriteWorkspaceAssetsForApp } from "./public-surface.js";
18
+ import { isRecord } from "./types.js";
19
+ import { writeGeneratedWorkspaceScripts } from "./workspace-scripts.js";
20
+ import { writeApp } from "./write-workspace.js";
21
+ const FIRST_VERTICAL_PORT = 4101;
22
+ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
23
+ if (packageSource) return resolvePackageSource({
24
+ targetDir: workspaceRoot,
25
+ packageName: node_path.basename(workspaceRoot),
26
+ modernVersion,
27
+ packageSource
28
+ });
29
+ const metadataPath = node_path.join(workspaceRoot, '.modernjs/ultramodern-package-source.json');
30
+ if (!node_fs.existsSync(metadataPath)) return resolvePackageSource({
31
+ targetDir: workspaceRoot,
32
+ packageName: node_path.basename(workspaceRoot),
33
+ modernVersion
34
+ });
35
+ const metadata = readJsonFile(metadataPath);
36
+ const aliases = metadata.modernPackages?.aliases ?? {};
37
+ const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
38
+ const firstPackage = Object.keys(aliases)[0];
39
+ const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
40
+ const unscopedName = firstPackage?.split('/').at(-1) ?? '';
41
+ const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
42
+ const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
43
+ return {
44
+ strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
45
+ modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : modernVersion,
46
+ registry: metadata.modernPackages?.registry,
47
+ aliasScope,
48
+ aliasPackageNamePrefix
49
+ };
50
+ }
51
+ function existingTailwindEnabled(workspaceRoot) {
52
+ const contractPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
53
+ if (!node_fs.existsSync(contractPath)) return true;
54
+ const contract = readJsonFile(contractPath);
55
+ const apps = isRecord(contract) && Array.isArray(contract.apps) ? contract.apps : [];
56
+ const shell = apps.find((app)=>isRecord(app) && app.id === shellApp.id);
57
+ return shell?.styling && isRecord(shell.styling) ? false !== shell.styling.tailwind : true;
58
+ }
59
+ function assertValidVerticalName(name) {
60
+ const normalized = toKebabCase(name);
61
+ if (!normalized || normalized !== name) throw new Error(`Invalid Vertical name "${name}". Use lowercase kebab-case.`);
62
+ return normalized;
63
+ }
64
+ function nextAvailablePort(ports) {
65
+ const numericPorts = Object.values(ports).filter((value)=>'number' == typeof value && Number.isFinite(value));
66
+ return Math.max(FIRST_VERTICAL_PORT - 1, ...numericPorts) + 1;
67
+ }
68
+ function assertCanCreate(workspaceRoot, relativePath) {
69
+ if (node_fs.existsSync(node_path.join(workspaceRoot, relativePath))) throw new Error(`Refusing to overwrite existing path: ${relativePath}`);
70
+ }
71
+ function updateRootWorkspaceScripts(workspaceRoot, scope, packageSource, remotes) {
72
+ const packagePath = node_path.join(workspaceRoot, 'package.json');
73
+ const rootPackage = readJsonFile(packagePath);
74
+ const generatedRootPackage = createRootPackageJson(scope, packageSource, remotes);
75
+ rootPackage.scripts = generatedRootPackage.scripts;
76
+ writeJsonFile(packagePath, rootPackage);
77
+ }
78
+ function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwind, remotes) {
79
+ const shellHost = createShellHost(remotes);
80
+ const publicWeb = createPublicWebAppArtifacts(shellHost);
81
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes));
82
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern-app-env.d.ts`, createAppEnvDts(shellHost, remotes));
83
+ writeFileReplacing(workspaceRoot, publicWeb.jsonLdHelperFile.path, publicWeb.jsonLdHelperFile.content);
84
+ writeFileReplacing(workspaceRoot, publicWeb.routeMetadataFile.path, publicWeb.routeMetadataFile.content);
85
+ writeFileReplacing(workspaceRoot, publicWeb.routeHeadFile.path, publicWeb.routeHeadFile.content);
86
+ for (const generatedFile of publicWeb.routeMetaFiles)writeFileReplacing(workspaceRoot, generatedFile.path, generatedFile.content);
87
+ rewriteWorkspaceAssetsForApp(workspaceRoot, shellHost);
88
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(shellHost, scope, remotes));
89
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/translation.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
90
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/${appI18nNamespace(shellHost)}.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
91
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/cs/translation.json`), createAppPublicLocaleMessages(shellHost, 'cs', remotes));
92
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/cs/${appI18nNamespace(shellHost)}.json`), createAppPublicLocaleMessages(shellHost, 'cs', remotes));
93
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/module-federation.config.ts`, createShellModuleFederationConfig(scope, remotes));
94
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/[lang]/page.tsx`, createShellPage(remotes));
95
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/vertical-components.tsx`, createShellRemoteComponents(scope, remotes));
96
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/shell-frame.tsx`, createShellFrameComponent());
97
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/effect/vertical-clients.ts`, createShellEffectClient(scope, remotes));
98
+ }
99
+ function addShellZephyrDependency(workspaceRoot, scope, remote) {
100
+ const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
101
+ const shellPackage = readJsonFile(packagePath);
102
+ shellPackage['zephyr:dependencies'] ??= {};
103
+ shellPackage['zephyr:dependencies'][remoteDependencyAlias(remote)] = zephyrRemoteDependency(scope, remote);
104
+ writeJsonFile(packagePath, shellPackage);
105
+ }
106
+ function addShellWorkspaceDependency(workspaceRoot, scope, remote) {
107
+ if (!appHasEffectApi(remote)) return;
108
+ const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
109
+ const shellPackage = readJsonFile(packagePath);
110
+ shellPackage.dependencies ??= {};
111
+ shellPackage.dependencies[packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
112
+ writeJsonFile(packagePath, shellPackage);
113
+ }
114
+ function verticalTopologyEntry(scope, vertical, remotes = []) {
115
+ return {
116
+ id: vertical.id,
117
+ kind: vertical.kind,
118
+ domain: vertical.domain,
119
+ package: packageName(scope, vertical.packageSuffix),
120
+ path: vertical.directory,
121
+ moduleFederation: {
122
+ role: 'remote',
123
+ name: vertical.mfName,
124
+ manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`,
125
+ exposes: Object.keys(vertical.exposes ?? {}),
126
+ ...vertical.verticalRefs?.length ? {
127
+ verticalRefs: vertical.verticalRefs,
128
+ remotes: createModuleFederationRemoteContracts(vertical, remotes)
129
+ } : {},
130
+ ssr: true,
131
+ fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
132
+ sharedContractVersion: 'mf-ssr-contract-v1'
133
+ },
134
+ ...effectApiTopologyMetadata(vertical) ? {
135
+ api: effectApiTopologyMetadata(vertical)
136
+ } : {},
137
+ cloudflare: createCloudflareDeployContract(scope, vertical),
138
+ ownership: vertical.ownership
139
+ };
140
+ }
141
+ function ownershipEntry(scope, owner) {
142
+ return {
143
+ id: owner.id,
144
+ package: packageName(scope, owner.packageSuffix),
145
+ path: owner.directory,
146
+ ownership: owner.ownership
147
+ };
148
+ }
149
+ function verticalsFromTopology(topology, ports) {
150
+ return (topology.verticals ?? []).map((vertical)=>{
151
+ const domain = vertical.domain ?? String(vertical.id);
152
+ const packageSuffix = vertical.package?.split('/').at(-1) ?? domain;
153
+ const effectApi = vertical.api?.effect ? {
154
+ stem: 'string' == typeof vertical.api.effect.basePath ? vertical.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
155
+ prefix: vertical.api.effect.bff?.prefix ?? `/${domain}-api`,
156
+ consumedBy: Array.isArray(vertical.api.effect.consumedBy) ? vertical.api.effect.consumedBy : [
157
+ shellApp.id,
158
+ vertical.id
159
+ ]
160
+ } : void 0;
161
+ return {
162
+ id: vertical.id,
163
+ directory: 'string' == typeof vertical.path ? vertical.path : `verticals/${domain}`,
164
+ packageSuffix,
165
+ displayName: vertical.displayName ?? `${toPascalCase(domain)} Vertical`,
166
+ kind: 'vertical',
167
+ domain,
168
+ portEnv: `VERTICAL_${toEnvSegment(domain)}_PORT`,
169
+ port: 'number' == typeof ports[vertical.id] ? ports[vertical.id] : 0,
170
+ mfName: vertical.moduleFederation?.name ?? `vertical${toPascalCase(domain)}`,
171
+ ...Array.isArray(vertical.moduleFederation?.exposes) ? {
172
+ exposes: Object.fromEntries(vertical.moduleFederation.exposes.map((expose)=>[
173
+ expose,
174
+ './Route' === expose ? './src/federation-entry.tsx' : './Widget' === expose ? `./src/components/${domain}-widget.tsx` : ''
175
+ ]))
176
+ } : {},
177
+ ...Array.isArray(vertical.moduleFederation?.verticalRefs) ? {
178
+ verticalRefs: vertical.moduleFederation.verticalRefs
179
+ } : Array.isArray(vertical.moduleFederation?.remotes) ? {
180
+ verticalRefs: vertical.moduleFederation.remotes.map((entry)=>entry.id).filter((id)=>'string' == typeof id)
181
+ } : {},
182
+ ...effectApi ? {
183
+ effectApi
184
+ } : {},
185
+ ownership: vertical.ownership ?? createNeutralOwnership(vertical.id)
186
+ };
187
+ });
188
+ }
189
+ function addUltramodernVertical(options) {
190
+ const name = assertValidVerticalName(options.name);
191
+ const rootPackage = readJsonFile(node_path.join(options.workspaceRoot, 'package.json'));
192
+ const scope = toPackageScope(String(rootPackage.name ?? node_path.basename(options.workspaceRoot)));
193
+ const topologyPath = node_path.join(options.workspaceRoot, 'topology/reference-topology.json');
194
+ const ownershipPath = node_path.join(options.workspaceRoot, 'topology/ownership.json');
195
+ const overlayPath = node_path.join(options.workspaceRoot, 'topology/local-overlays/development.json');
196
+ for (const requiredPath of [
197
+ topologyPath,
198
+ ownershipPath,
199
+ overlayPath
200
+ ])if (!node_fs.existsSync(requiredPath)) throw new Error(`Missing UltraModern workspace file: ${requiredPath}`);
201
+ const topology = readJsonFile(topologyPath);
202
+ const ownership = readJsonFile(ownershipPath);
203
+ const overlay = readJsonFile(overlayPath);
204
+ overlay.ports ??= {};
205
+ const packageSource = existingPackageSource(options.workspaceRoot, options.modernVersion, options.packageSource);
206
+ const enableTailwind = options.enableTailwind ?? existingTailwindEnabled(options.workspaceRoot);
207
+ const port = nextAvailablePort(overlay.ports);
208
+ const vertical = createVerticalDescriptor(name, port);
209
+ assertCanCreate(options.workspaceRoot, vertical.directory);
210
+ if ((topology.verticals ?? []).some((entry)=>entry.id === vertical.id)) throw new Error(`Topology already contains ${vertical.id}`);
211
+ if (Object.values(overlay.ports).includes(vertical.port)) throw new Error(`Development port ${vertical.port} is already in use`);
212
+ writeApp(options.workspaceRoot, scope, vertical, packageSource, enableTailwind);
213
+ topology.shell ??= {};
214
+ topology.shell.verticalRefs ??= [];
215
+ topology.shell.verticalRefs.push(vertical.id);
216
+ topology.shell.moduleFederation ??= {};
217
+ topology.shell.moduleFederation.remotes ??= [];
218
+ topology.shell.moduleFederation.remotes.push({
219
+ id: vertical.id,
220
+ name: vertical.mfName,
221
+ manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`
222
+ });
223
+ topology.verticals ??= [];
224
+ topology.verticals.push(verticalTopologyEntry(scope, vertical));
225
+ ownership.owners ??= [];
226
+ ownership.owners.push(ownershipEntry(scope, vertical));
227
+ overlay.ports[vertical.id] = vertical.port;
228
+ overlay.manifests ??= {};
229
+ overlay.manifests[vertical.id] = `http://localhost:${vertical.port}/mf-manifest.json`;
230
+ overlay.apis ??= {};
231
+ overlay.apis[vertical.id] = `http://localhost:${vertical.port}${effectApiPrefix(vertical)}`;
232
+ writeJsonFile(topologyPath, topology);
233
+ writeJsonFile(ownershipPath, ownership);
234
+ writeJsonFile(overlayPath, overlay);
235
+ const updatedVerticals = verticalsFromTopology(topology, overlay.ports);
236
+ assertUniqueTailwindPrefixes([
237
+ shellApp,
238
+ ...updatedVerticals
239
+ ]);
240
+ writeJsonFile(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH), createGeneratedContract(scope, [
241
+ {
242
+ ...shellApp,
243
+ verticalRefs: updatedVerticals.map((vertical)=>vertical.id)
244
+ },
245
+ ...updatedVerticals
246
+ ], enableTailwind));
247
+ rewriteShellAppFiles(options.workspaceRoot, scope, packageSource, enableTailwind, updatedVerticals);
248
+ writeGeneratedWorkspaceScripts(options.workspaceRoot, scope, enableTailwind, updatedVerticals);
249
+ addShellZephyrDependency(options.workspaceRoot, scope, vertical);
250
+ addShellWorkspaceDependency(options.workspaceRoot, scope, vertical);
251
+ updateRootWorkspaceScripts(options.workspaceRoot, scope, packageSource, updatedVerticals);
252
+ }
253
+ export { addShellWorkspaceDependency, addShellZephyrDependency, addUltramodernVertical, assertCanCreate, assertValidVerticalName, existingPackageSource, existingTailwindEnabled, nextAvailablePort, ownershipEntry, rewriteShellAppFiles, updateRootWorkspaceScripts, verticalTopologyEntry, verticalsFromTopology };
@@ -0,0 +1,150 @@
1
+ import "node:module";
2
+ import { appI18nNamespace, remoteDependencyAlias, resolveRemoteRefs, shellApp } from "./descriptors.js";
3
+ import { readFileTemplate, renderFileTemplate } from "./fs-io.js";
4
+ import { packageName, tailwindPrefixForApp } from "./naming.js";
5
+ function createBoundaryDebugMetadata(scope, remotes = []) {
6
+ return {
7
+ appId: shellApp.id,
8
+ boundaries: [
9
+ shellApp,
10
+ ...remotes
11
+ ].map((app)=>({
12
+ appId: app.id,
13
+ label: app.displayName,
14
+ mfName: app.mfName,
15
+ ownerTeam: app.ownership.team,
16
+ packageName: packageName(scope, app.packageSuffix),
17
+ role: 'shell' === app.kind ? 'host' : 'vertical'
18
+ })),
19
+ schemaVersion: 1
20
+ };
21
+ }
22
+ function createAppEnvDts(app, remotes = []) {
23
+ const remoteModuleDeclarations = resolveRemoteRefs(app, remotes).flatMap((remote)=>Object.keys(remote.exposes ?? {}).filter((expose)=>'./Route' !== expose).map((expose)=>{
24
+ const moduleName = `${remoteDependencyAlias(remote)}/${expose.replace(/^\.\//u, '')}`;
25
+ return `declare module '${moduleName}' {
26
+ const Component: React.ComponentType<Record<string, never>>;
27
+ export default Component;
28
+ }
29
+ `;
30
+ })).join('\n');
31
+ const reactTypeReference = remoteModuleDeclarations ? "/// <reference types='react' />\n" : '';
32
+ const siteUrlDeclaration = 'declare const ULTRAMODERN_SITE_URL: string;';
33
+ return `${reactTypeReference}/// <reference types='@modern-js/app-tools/types' />
34
+
35
+ ${siteUrlDeclaration}
36
+ declare module '*.svg' {
37
+ const url: string;
38
+ export default url;
39
+ }
40
+ declare module '*.css';
41
+ ${remoteModuleDeclarations ? `\n${remoteModuleDeclarations}` : ''}`;
42
+ }
43
+ function createAppRuntimeConfig(app, scope, remotes = []) {
44
+ const pluginsConfig = 'shell' === app.kind ? ` plugins: [
45
+ ultramodernBoundaryDebuggerPlugin({
46
+ metadata: ${JSON.stringify(createBoundaryDebugMetadata(scope, remotes), null, 6).split('\n').join('\n ')},
47
+ }),
48
+ ],
49
+ ` : '';
50
+ return `import { defineRuntimeConfig } from '@modern-js/runtime';
51
+ ${'shell' === app.kind ? "import { ultramodernBoundaryDebuggerPlugin } from '@modern-js/runtime/boundary-debugger';\n" : ''}import { createInstance } from 'i18next';
52
+ import csResource from '../locales/cs/${appI18nNamespace(app)}.json';
53
+ import enResource from '../locales/en/${appI18nNamespace(app)}.json';
54
+ import { ultramodernRouteNamespace } from './routes/ultramodern-route-metadata';
55
+
56
+ type LocaleResource = string | { readonly [key: string]: LocaleResource };
57
+
58
+ const flattenLocaleResource = (
59
+ resource: LocaleResource,
60
+ prefix = '',
61
+ ): Record<string, string> => {
62
+ if (typeof resource === 'string') {
63
+ return prefix.length > 0 ? { [prefix]: resource } : {};
64
+ }
65
+
66
+ return Object.fromEntries(
67
+ Object.entries(resource).flatMap(([key, value]) => {
68
+ const nextKey = prefix.length > 0 ? \`\${prefix}.\${key}\` : key;
69
+ return typeof value === 'string'
70
+ ? [[nextKey, value]]
71
+ : Object.entries(flattenLocaleResource(value, nextKey));
72
+ }),
73
+ );
74
+ };
75
+
76
+ const i18nInstance = createInstance();
77
+ const resources = {
78
+ cs: { [ultramodernRouteNamespace]: flattenLocaleResource(csResource) },
79
+ en: { [ultramodernRouteNamespace]: flattenLocaleResource(enResource) },
80
+ } as const;
81
+
82
+ export default defineRuntimeConfig({
83
+ i18n: {
84
+ i18nInstance,
85
+ initOptions: {
86
+ defaultNS: ultramodernRouteNamespace,
87
+ fallbackLng: 'en',
88
+ interpolation: {
89
+ escapeValue: false,
90
+ },
91
+ ns: [ultramodernRouteNamespace, 'translation'],
92
+ resources,
93
+ supportedLngs: ['en', 'cs'],
94
+ },
95
+ },
96
+ ${pluginsConfig}
97
+ router: {
98
+ framework: 'tanstack',
99
+ },
100
+ });
101
+ `;
102
+ }
103
+ function createCssTokenImport(scope) {
104
+ return `@import '${packageName(scope, 'shared-design-tokens')}/tokens.css';\n`;
105
+ }
106
+ function createTailwindImport(prefix) {
107
+ return `@import 'tailwindcss' prefix(${prefix}) source(none);\n@source '..';\n`;
108
+ }
109
+ function createShellStyles(enableTailwind, scope) {
110
+ return `${enableTailwind ? createTailwindImport(tailwindPrefixForApp(shellApp)) : ''}${createCssTokenImport(scope)}`;
111
+ }
112
+ function createRemoteStyles(enableTailwind, scope, app) {
113
+ return `${enableTailwind ? createTailwindImport(tailwindPrefixForApp(app)) : ''}${createCssTokenImport(scope)}`;
114
+ }
115
+ function createAppStyles(enableTailwind, scope, app) {
116
+ return 'shell' === app.kind ? createShellStyles(enableTailwind, scope) : createRemoteStyles(enableTailwind, scope, app);
117
+ }
118
+ function createPostcssConfig() {
119
+ return `export default {
120
+ plugins: {
121
+ '@tailwindcss/postcss': {},
122
+ },
123
+ };
124
+ `;
125
+ }
126
+ function createTailwindConfig() {
127
+ return `import type { Config } from 'tailwindcss';
128
+
129
+ export default {} satisfies Config;
130
+ `;
131
+ }
132
+ function createSharedDesignTokensCss() {
133
+ return `@theme {
134
+ --color-um-accent: #2f8f68;
135
+ --color-um-canvas: #f1eadc;
136
+ --color-um-foreground: #133225;
137
+ --color-um-link: #166b4b;
138
+ --color-um-surface: #f6fbf7;
139
+ }
140
+ `;
141
+ }
142
+ function createRouteHeadModule(app) {
143
+ return renderFileTemplate('app/ultramodern-route-head.tsx', {
144
+ appDisplayNameJson: JSON.stringify(app.displayName)
145
+ });
146
+ }
147
+ function createShellFrameComponent() {
148
+ return readFileTemplate('app/shell-frame.tsx');
149
+ }
150
+ export { createAppEnvDts, createAppRuntimeConfig, createAppStyles, createBoundaryDebugMetadata, createCssTokenImport, createPostcssConfig, createRemoteStyles, createRouteHeadModule, createSharedDesignTokensCss, createShellFrameComponent, createShellStyles, createTailwindConfig, createTailwindImport };