@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,466 @@
1
+ export type UltramodernPublicSitemapChangeFrequency =
2
+ | 'always'
3
+ | 'hourly'
4
+ | 'daily'
5
+ | 'weekly'
6
+ | 'monthly'
7
+ | 'yearly'
8
+ | 'never';
9
+
10
+ export interface UltramodernPublicSitemapEntry {
11
+ /**
12
+ * Params used to expand every localized route pattern, for example
13
+ * { slug: 'platform-story' } for /talks/:slug.
14
+ */
15
+ params: Record<string, string | number | boolean>;
16
+ /**
17
+ * Per-locale overrides when translated URLs use translated params.
18
+ */
19
+ localeParams?: Partial<Record<'en' | 'cs', Record<string, string | number | boolean>>>;
20
+ draft?: boolean;
21
+ indexable?: boolean;
22
+ lastModified?: string;
23
+ changeFrequency?: UltramodernPublicSitemapChangeFrequency;
24
+ priority?: number;
25
+ }
26
+
27
+ export type UltramodernPerformanceReadinessSignalId =
28
+ | 'bfcache'
29
+ | 'core-web-vitals-rum'
30
+ | 'duplicate-prefetch-warmup'
31
+ | 'cache-policy-sanity'
32
+ | 'save-data-behavior'
33
+ | 'cloudflare-ssr-cache-hints';
34
+
35
+ export interface UltramodernPerformanceReadinessDiagnosticsConfig {
36
+ /**
37
+ * Default-on. Set to false only for an explicit local or CI fast path.
38
+ */
39
+ enabled?: boolean;
40
+ /**
41
+ * Diagnostics may fail objective generated/framework invariants, or never
42
+ * fail and only emit the deterministic report.
43
+ */
44
+ failOn?: 'framework-invariant' | 'never';
45
+ reportPath?: string;
46
+ signals?: Partial<
47
+ Record<
48
+ UltramodernPerformanceReadinessSignalId,
49
+ {
50
+ enabled?: boolean;
51
+ }
52
+ >
53
+ >;
54
+ }
55
+
56
+ export const ultramodernWorkspaceContract = {
57
+ ownership: 'topology/ownership.json',
58
+ performanceReadiness: {
59
+ defaultOn: true,
60
+ optOut: 'scripts/ultramodern-performance-readiness.config.mjs#enabled=false',
61
+ report:
62
+ '.codex/reports/performance-readiness/ultramodern-performance-readiness.json',
63
+ signals: [
64
+ 'bfcache',
65
+ 'core-web-vitals-rum',
66
+ 'duplicate-prefetch-warmup',
67
+ 'cache-policy-sanity',
68
+ 'save-data-behavior',
69
+ 'cloudflare-ssr-cache-hints',
70
+ ],
71
+ },
72
+ preset: 'presetUltramodern',
73
+ topology: 'topology/reference-topology.json',
74
+ } as const;
75
+
76
+ export const tractorEventNames = {
77
+ checkoutAddToCart: 'checkout:add-to-cart',
78
+ checkoutCartUpdated: 'checkout:cart-updated',
79
+ checkoutClearCart: 'checkout:clear-cart',
80
+ checkoutRemoveFromCart: 'checkout:remove-from-cart',
81
+ exploreSelectedShop: 'explore:selected-shop',
82
+ mfNavigate: 'mf:navigate',
83
+ } as const;
84
+
85
+ export type TractorEventName =
86
+ (typeof tractorEventNames)[keyof typeof tractorEventNames];
87
+
88
+ export interface CheckoutAddToCartPayload {
89
+ sku: string;
90
+ quantity: number;
91
+ name?: string;
92
+ shopId?: string;
93
+ unitPriceCents?: number;
94
+ }
95
+
96
+ export interface CheckoutCartLinePayload {
97
+ sku: string;
98
+ quantity: number;
99
+ name?: string;
100
+ unitPriceCents?: number;
101
+ }
102
+
103
+ export interface CheckoutCartUpdatedPayload {
104
+ lines: readonly CheckoutCartLinePayload[];
105
+ totalQuantity: number;
106
+ subtotalCents?: number;
107
+ }
108
+
109
+ export interface CheckoutRemoveFromCartPayload {
110
+ sku: string;
111
+ }
112
+
113
+ export interface CheckoutClearCartPayload {
114
+ reason?: string;
115
+ }
116
+
117
+ export interface ExploreSelectedShopPayload {
118
+ shopId: string;
119
+ name?: string;
120
+ }
121
+
122
+ export interface MfNavigatePayload {
123
+ to: string;
124
+ replace?: boolean;
125
+ state?: Record<string, unknown>;
126
+ }
127
+
128
+ export interface TractorEventPayloadMap {
129
+ 'checkout:add-to-cart': CheckoutAddToCartPayload;
130
+ 'checkout:cart-updated': CheckoutCartUpdatedPayload;
131
+ 'checkout:clear-cart': CheckoutClearCartPayload;
132
+ 'checkout:remove-from-cart': CheckoutRemoveFromCartPayload;
133
+ 'explore:selected-shop': ExploreSelectedShopPayload;
134
+ 'mf:navigate': MfNavigatePayload;
135
+ }
136
+
137
+ export class TractorEventValidationError extends Error {
138
+ readonly eventName: TractorEventName;
139
+ readonly payload: unknown;
140
+
141
+ constructor(eventName: TractorEventName, payload: unknown) {
142
+ super(`Invalid payload for Tractor event "${eventName}"`);
143
+ this.name = 'TractorEventValidationError';
144
+ this.eventName = eventName;
145
+ this.payload = payload;
146
+ }
147
+ }
148
+
149
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
150
+ typeof value === 'object' && value !== null && !Array.isArray(value);
151
+
152
+ const isNonEmptyString = (value: unknown): value is string =>
153
+ typeof value === 'string' && value.trim().length > 0;
154
+
155
+ const isPositiveInteger = (value: unknown): value is number =>
156
+ Number.isInteger(value) && value > 0;
157
+
158
+ const isNonNegativeInteger = (value: unknown): value is number =>
159
+ Number.isInteger(value) && value >= 0;
160
+
161
+ const hasOptionalString = (value: Record<string, unknown>, key: string) =>
162
+ value[key] === undefined || isNonEmptyString(value[key]);
163
+
164
+ const hasOptionalBoolean = (value: Record<string, unknown>, key: string) =>
165
+ value[key] === undefined || typeof value[key] === 'boolean';
166
+
167
+ const hasOptionalRecord = (value: Record<string, unknown>, key: string) =>
168
+ value[key] === undefined || isRecord(value[key]);
169
+
170
+ const hasOptionalNonNegativeInteger = (
171
+ value: Record<string, unknown>,
172
+ key: string,
173
+ ) => value[key] === undefined || isNonNegativeInteger(value[key]);
174
+
175
+ export const isCheckoutAddToCartPayload = (
176
+ payload: unknown,
177
+ ): payload is CheckoutAddToCartPayload =>
178
+ isRecord(payload) &&
179
+ isNonEmptyString(payload['sku']) &&
180
+ isPositiveInteger(payload['quantity']) &&
181
+ hasOptionalString(payload, 'name') &&
182
+ hasOptionalString(payload, 'shopId') &&
183
+ hasOptionalNonNegativeInteger(payload, 'unitPriceCents');
184
+
185
+ export const isCheckoutCartLinePayload = (
186
+ payload: unknown,
187
+ ): payload is CheckoutCartLinePayload =>
188
+ isRecord(payload) &&
189
+ isNonEmptyString(payload['sku']) &&
190
+ isPositiveInteger(payload['quantity']) &&
191
+ hasOptionalString(payload, 'name') &&
192
+ hasOptionalNonNegativeInteger(payload, 'unitPriceCents');
193
+
194
+ export const isCheckoutCartUpdatedPayload = (
195
+ payload: unknown,
196
+ ): payload is CheckoutCartUpdatedPayload =>
197
+ isRecord(payload) &&
198
+ Array.isArray(payload['lines']) &&
199
+ payload['lines'].every(isCheckoutCartLinePayload) &&
200
+ isNonNegativeInteger(payload['totalQuantity']) &&
201
+ hasOptionalNonNegativeInteger(payload, 'subtotalCents');
202
+
203
+ export const isCheckoutRemoveFromCartPayload = (
204
+ payload: unknown,
205
+ ): payload is CheckoutRemoveFromCartPayload =>
206
+ isRecord(payload) && isNonEmptyString(payload['sku']);
207
+
208
+ export const isCheckoutClearCartPayload = (
209
+ payload: unknown,
210
+ ): payload is CheckoutClearCartPayload =>
211
+ isRecord(payload) && hasOptionalString(payload, 'reason');
212
+
213
+ export const isExploreSelectedShopPayload = (
214
+ payload: unknown,
215
+ ): payload is ExploreSelectedShopPayload =>
216
+ isRecord(payload) &&
217
+ isNonEmptyString(payload['shopId']) &&
218
+ hasOptionalString(payload, 'name');
219
+
220
+ export const isMfNavigatePayload = (
221
+ payload: unknown,
222
+ ): payload is MfNavigatePayload =>
223
+ isRecord(payload) &&
224
+ isNonEmptyString(payload['to']) &&
225
+ hasOptionalBoolean(payload, 'replace') &&
226
+ hasOptionalRecord(payload, 'state');
227
+
228
+ const tractorEventValidators = {
229
+ [tractorEventNames.checkoutAddToCart]: isCheckoutAddToCartPayload,
230
+ [tractorEventNames.checkoutCartUpdated]: isCheckoutCartUpdatedPayload,
231
+ [tractorEventNames.checkoutClearCart]: isCheckoutClearCartPayload,
232
+ [tractorEventNames.checkoutRemoveFromCart]: isCheckoutRemoveFromCartPayload,
233
+ [tractorEventNames.exploreSelectedShop]: isExploreSelectedShopPayload,
234
+ [tractorEventNames.mfNavigate]: isMfNavigatePayload,
235
+ } satisfies {
236
+ [Name in TractorEventName]: (
237
+ payload: unknown,
238
+ ) => payload is TractorEventPayloadMap[Name];
239
+ };
240
+
241
+ export const isTractorEventPayload = <Name extends TractorEventName>(
242
+ eventName: Name,
243
+ payload: unknown,
244
+ ): payload is TractorEventPayloadMap[Name] =>
245
+ tractorEventValidators[eventName](payload);
246
+
247
+ export const assertTractorEventPayload = <Name extends TractorEventName>(
248
+ eventName: Name,
249
+ payload: unknown,
250
+ ): TractorEventPayloadMap[Name] => {
251
+ if (!isTractorEventPayload(eventName, payload)) {
252
+ throw new TractorEventValidationError(eventName, payload);
253
+ }
254
+
255
+ return payload;
256
+ };
257
+
258
+ export const createTractorEvent = <Name extends TractorEventName>(
259
+ eventName: Name,
260
+ payload: TractorEventPayloadMap[Name],
261
+ ): CustomEvent<TractorEventPayloadMap[Name]> =>
262
+ new CustomEvent(eventName, {
263
+ bubbles: true,
264
+ composed: true,
265
+ detail: assertTractorEventPayload(eventName, payload),
266
+ });
267
+
268
+ export const dispatchTractorEvent = <Name extends TractorEventName>(
269
+ target: EventTarget,
270
+ eventName: Name,
271
+ payload: TractorEventPayloadMap[Name],
272
+ ) => target.dispatchEvent(createTractorEvent(eventName, payload));
273
+
274
+ export const onTractorEvent = <Name extends TractorEventName>(
275
+ target: EventTarget,
276
+ eventName: Name,
277
+ handler: (
278
+ payload: TractorEventPayloadMap[Name],
279
+ event: CustomEvent<TractorEventPayloadMap[Name]>,
280
+ ) => void,
281
+ ) => {
282
+ const listener = (event: Event) => {
283
+ if (!('detail' in event)) {
284
+ throw new TractorEventValidationError(eventName, undefined);
285
+ }
286
+
287
+ const customEvent = event as CustomEvent<unknown>;
288
+ handler(
289
+ assertTractorEventPayload(eventName, customEvent.detail),
290
+ customEvent as CustomEvent<TractorEventPayloadMap[Name]>,
291
+ );
292
+ };
293
+
294
+ target.addEventListener(eventName, listener);
295
+
296
+ return () => {
297
+ target.removeEventListener(eventName, listener);
298
+ };
299
+ };
300
+
301
+ const normalizeCheckoutLine = (
302
+ line: CheckoutCartLinePayload,
303
+ ): CheckoutCartLinePayload => {
304
+ if (!isCheckoutCartLinePayload(line)) {
305
+ throw new TractorEventValidationError('checkout:cart-updated', line);
306
+ }
307
+
308
+ return {
309
+ quantity: line.quantity,
310
+ sku: line.sku,
311
+ ...(line.name === undefined ? {} : { name: line.name }),
312
+ ...(line.unitPriceCents === undefined
313
+ ? {}
314
+ : { unitPriceCents: line.unitPriceCents }),
315
+ };
316
+ };
317
+
318
+ export const createCheckoutCartSnapshot = (
319
+ lines: readonly CheckoutCartLinePayload[],
320
+ ): CheckoutCartUpdatedPayload => {
321
+ const normalizedLines = lines.map(line => normalizeCheckoutLine(line));
322
+ const subtotalCents = normalizedLines.reduce(
323
+ (total, line) => total + (line.unitPriceCents ?? 0) * line.quantity,
324
+ 0,
325
+ );
326
+
327
+ return {
328
+ lines: normalizedLines,
329
+ totalQuantity: normalizedLines.reduce(
330
+ (total, line) => total + line.quantity,
331
+ 0,
332
+ ),
333
+ ...(subtotalCents === 0 ? {} : { subtotalCents }),
334
+ };
335
+ };
336
+
337
+ export const applyCheckoutCartEvent = (
338
+ cart: CheckoutCartUpdatedPayload,
339
+ eventName:
340
+ | 'checkout:add-to-cart'
341
+ | 'checkout:remove-from-cart'
342
+ | 'checkout:clear-cart',
343
+ payload:
344
+ | CheckoutAddToCartPayload
345
+ | CheckoutRemoveFromCartPayload
346
+ | CheckoutClearCartPayload,
347
+ ): CheckoutCartUpdatedPayload => {
348
+ if (eventName === 'checkout:clear-cart') {
349
+ assertTractorEventPayload(eventName, payload);
350
+ return createCheckoutCartSnapshot([]);
351
+ }
352
+
353
+ if (eventName === 'checkout:remove-from-cart') {
354
+ const removePayload = assertTractorEventPayload(eventName, payload);
355
+ return createCheckoutCartSnapshot(
356
+ cart.lines.filter(line => line.sku !== removePayload.sku),
357
+ );
358
+ }
359
+
360
+ const addPayload = assertTractorEventPayload(eventName, payload);
361
+ const lines = cart.lines.map(normalizeCheckoutLine);
362
+ const existingIndex = lines.findIndex(line => line.sku === addPayload.sku);
363
+ const nextLine = normalizeCheckoutLine({
364
+ quantity: addPayload.quantity,
365
+ sku: addPayload.sku,
366
+ ...(addPayload.name === undefined ? {} : { name: addPayload.name }),
367
+ ...(addPayload.unitPriceCents === undefined
368
+ ? {}
369
+ : { unitPriceCents: addPayload.unitPriceCents }),
370
+ });
371
+
372
+ if (existingIndex === -1) {
373
+ return createCheckoutCartSnapshot([...lines, nextLine]);
374
+ }
375
+
376
+ const existing = lines[existingIndex];
377
+ return createCheckoutCartSnapshot(
378
+ lines.map((line, index) =>
379
+ index === existingIndex
380
+ ? normalizeCheckoutLine({
381
+ ...line,
382
+ ...nextLine,
383
+ quantity: existing.quantity + addPayload.quantity,
384
+ })
385
+ : line,
386
+ ),
387
+ );
388
+ };
389
+
390
+ export const dispatchCheckoutAddToCart = (
391
+ target: EventTarget,
392
+ payload: CheckoutAddToCartPayload,
393
+ ) => dispatchTractorEvent(target, 'checkout:add-to-cart', payload);
394
+
395
+ export const dispatchCheckoutCartUpdated = (
396
+ target: EventTarget,
397
+ payload: CheckoutCartUpdatedPayload,
398
+ ) => dispatchTractorEvent(target, 'checkout:cart-updated', payload);
399
+
400
+ export const dispatchCheckoutRemoveFromCart = (
401
+ target: EventTarget,
402
+ payload: CheckoutRemoveFromCartPayload,
403
+ ) => dispatchTractorEvent(target, 'checkout:remove-from-cart', payload);
404
+
405
+ export const dispatchCheckoutClearCart = (
406
+ target: EventTarget,
407
+ payload: CheckoutClearCartPayload = {},
408
+ ) => dispatchTractorEvent(target, 'checkout:clear-cart', payload);
409
+
410
+ export const dispatchExploreSelectedShop = (
411
+ target: EventTarget,
412
+ payload: ExploreSelectedShopPayload,
413
+ ) => dispatchTractorEvent(target, 'explore:selected-shop', payload);
414
+
415
+ export const dispatchMfNavigate = (
416
+ target: EventTarget,
417
+ payload: MfNavigatePayload,
418
+ ) => dispatchTractorEvent(target, 'mf:navigate', payload);
419
+
420
+ export const onCheckoutAddToCart = (
421
+ target: EventTarget,
422
+ handler: (
423
+ payload: CheckoutAddToCartPayload,
424
+ event: CustomEvent<CheckoutAddToCartPayload>,
425
+ ) => void,
426
+ ) => onTractorEvent(target, 'checkout:add-to-cart', handler);
427
+
428
+ export const onCheckoutCartUpdated = (
429
+ target: EventTarget,
430
+ handler: (
431
+ payload: CheckoutCartUpdatedPayload,
432
+ event: CustomEvent<CheckoutCartUpdatedPayload>,
433
+ ) => void,
434
+ ) => onTractorEvent(target, 'checkout:cart-updated', handler);
435
+
436
+ export const onCheckoutRemoveFromCart = (
437
+ target: EventTarget,
438
+ handler: (
439
+ payload: CheckoutRemoveFromCartPayload,
440
+ event: CustomEvent<CheckoutRemoveFromCartPayload>,
441
+ ) => void,
442
+ ) => onTractorEvent(target, 'checkout:remove-from-cart', handler);
443
+
444
+ export const onCheckoutClearCart = (
445
+ target: EventTarget,
446
+ handler: (
447
+ payload: CheckoutClearCartPayload,
448
+ event: CustomEvent<CheckoutClearCartPayload>,
449
+ ) => void,
450
+ ) => onTractorEvent(target, 'checkout:clear-cart', handler);
451
+
452
+ export const onExploreSelectedShop = (
453
+ target: EventTarget,
454
+ handler: (
455
+ payload: ExploreSelectedShopPayload,
456
+ event: CustomEvent<ExploreSelectedShopPayload>,
457
+ ) => void,
458
+ ) => onTractorEvent(target, 'explore:selected-shop', handler);
459
+
460
+ export const onMfNavigate = (
461
+ target: EventTarget,
462
+ handler: (
463
+ payload: MfNavigatePayload,
464
+ event: CustomEvent<MfNavigatePayload>,
465
+ ) => void,
466
+ ) => onTractorEvent(target, 'mf:navigate', handler);
@@ -0,0 +1,69 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const root = process.cwd();
5
+ const generatedContractPath = path.join(
6
+ root,
7
+ '.modernjs/ultramodern-generated-contract.json',
8
+ );
9
+ const generatedContract = fs.existsSync(generatedContractPath)
10
+ ? JSON.parse(fs.readFileSync(generatedContractPath, 'utf-8'))
11
+ : undefined;
12
+ const defaultAppDirs = {{defaultAppDirsJson}};
13
+
14
+ const args = process.argv.slice(2);
15
+ if (args.includes('--help') || args.includes('-h')) {
16
+ process.stdout.write(`Usage:
17
+ node scripts/assert-mf-types.mjs [app-dir...]
18
+
19
+ Checks that every Module Federation remote with exposed modules emitted a non-empty dist/@mf-types.zip archive and uses the workspace TypeScript compiler.
20
+ `);
21
+ process.exit(0);
22
+ }
23
+
24
+ const candidateDirs = args;
25
+ const appDirs = candidateDirs.length
26
+ ? candidateDirs
27
+ : fs.existsSync(path.join(root, 'module-federation.config.ts'))
28
+ ? ['.']
29
+ : defaultAppDirs;
30
+
31
+ for (const appDir of appDirs) {
32
+ const configPath = path.join(root, appDir, 'module-federation.config.ts');
33
+ if (!fs.existsSync(configPath)) {
34
+ throw new Error(
35
+ `Missing Module Federation config: ${path.relative(root, configPath)}`,
36
+ );
37
+ }
38
+
39
+ const contractEntry = generatedContract?.apps?.find(
40
+ app => app.path === appDir.replace(/\\/g, '/'),
41
+ );
42
+ if (
43
+ contractEntry &&
44
+ contractEntry.moduleFederation?.dts?.compilerInstance !==
45
+ 'tsgo'
46
+ ) {
47
+ throw new Error(
48
+ `Module Federation DTS must use the workspace TypeScript compiler: ${appDir}`,
49
+ );
50
+ }
51
+
52
+ if (contractEntry && contractEntry.moduleFederation?.exposes?.length === 0) {
53
+ continue;
54
+ }
55
+
56
+ const typesArchivePath = path.join(root, appDir, 'dist/@mf-types.zip');
57
+ if (!fs.existsSync(typesArchivePath)) {
58
+ throw new Error(
59
+ `Missing Module Federation DTS archive: ${path.relative(root, typesArchivePath)}`,
60
+ );
61
+ }
62
+
63
+ const stats = fs.statSync(typesArchivePath);
64
+ if (stats.size === 0) {
65
+ throw new Error(
66
+ `Empty Module Federation DTS archive: ${path.relative(root, typesArchivePath)}`,
67
+ );
68
+ }
69
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { runWorkspaceSourceCheck } from '@modern-js/code-tools';
4
+
5
+ const root = path.resolve(import.meta.dirname, '..');
6
+ process.exitCode = runWorkspaceSourceCheck({
7
+ cwd: root,
8
+ sourceRoots: ['apps', 'verticals'],
9
+ });