@lightspeed/crane 1.4.1 → 2.0.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 (116) hide show
  1. package/CHANGELOG.md +51 -4
  2. package/UPGRADE.md +19 -0
  3. package/dist/app.d.mts +1 -1028
  4. package/dist/app.d.ts +1 -1028
  5. package/dist/app.mjs +1 -1
  6. package/dist/cli.mjs +20 -7
  7. package/package.json +6 -3
  8. package/template/footers/example-footer/ExampleFooter.vue +1 -1
  9. package/template/footers/example-footer/client.ts +1 -1
  10. package/template/footers/example-footer/component/LegalLinks.vue +1 -1
  11. package/template/footers/example-footer/component/MadeWith.vue +1 -1
  12. package/template/footers/example-footer/component/ReportAbuse.vue +1 -1
  13. package/template/footers/example-footer/entity/color.ts +2 -2
  14. package/template/footers/example-footer/server.ts +1 -1
  15. package/template/headers/example-header/client.ts +1 -1
  16. package/template/headers/example-header/component/Account.vue +1 -1
  17. package/template/headers/example-header/component/Cart.vue +1 -1
  18. package/template/headers/example-header/component/CategoriesDropdown.vue +1 -1
  19. package/template/headers/example-header/component/Logo.vue +1 -1
  20. package/template/headers/example-header/component/NavigationMenu.vue +1 -1
  21. package/template/headers/example-header/component/SearchForm.vue +1 -1
  22. package/template/headers/example-header/server.ts +1 -1
  23. package/template/index.d.ts +1 -1
  24. package/template/layouts/catalog/example-catalog/Main.vue +1 -1
  25. package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/client.ts +1 -1
  26. package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/server.ts +1 -1
  27. package/template/layouts/category/example-category/Main.vue +1 -1
  28. package/template/layouts/category/example-category/settings/content.ts +1 -1
  29. package/template/layouts/category/example-category/settings/design.ts +1 -1
  30. package/template/layouts/product/example-product/Main.vue +1 -1
  31. package/template/layouts/product/example-product/settings/content.ts +1 -1
  32. package/template/layouts/product/example-product/settings/design.ts +1 -1
  33. package/template/package.json +7 -3
  34. package/template/page-templates/example-template/pages/catalog.ts +1 -1
  35. package/template/page-templates/example-template/pages/category.ts +1 -1
  36. package/template/page-templates/example-template/pages/product.ts +1 -1
  37. package/template/preview/sections/preview.html +1 -1
  38. package/template/preview/shared/api-routes.ts +443 -0
  39. package/template/preview/shared/mock.ts +43 -41
  40. package/template/preview/shared/preview.ts +213 -101
  41. package/template/preview/shared/utils.ts +315 -2
  42. package/template/preview/ssr-server.ts +429 -0
  43. package/template/preview/vite.config.js +76 -34
  44. package/template/reference/sections/about-us/AboutUs.vue +1 -1
  45. package/template/reference/sections/about-us/client.ts +1 -1
  46. package/template/reference/sections/about-us/component/Image.vue +1 -1
  47. package/template/reference/sections/about-us/component/Stats.vue +2 -2
  48. package/template/reference/sections/about-us/component/Title.vue +1 -1
  49. package/template/reference/sections/about-us/server.ts +1 -1
  50. package/template/reference/sections/about-us/util/visibility-provider.ts +1 -1
  51. package/template/reference/sections/featured-products/FeaturedProducts.vue +65 -0
  52. package/template/reference/sections/featured-products/assets/arrow.svg +3 -0
  53. package/template/reference/sections/featured-products/assets/custom_section_showcase_1_preview.png +0 -0
  54. package/template/reference/sections/featured-products/client.ts +5 -0
  55. package/template/reference/sections/featured-products/component/ProductItem.vue +71 -0
  56. package/template/reference/sections/featured-products/component/Title.vue +31 -0
  57. package/template/reference/sections/featured-products/entity/color.ts +4 -0
  58. package/template/reference/sections/featured-products/server.ts +5 -0
  59. package/template/reference/sections/featured-products/settings/content.ts +14 -0
  60. package/template/reference/sections/featured-products/settings/design.ts +33 -0
  61. package/template/reference/sections/featured-products/settings/translations.ts +24 -0
  62. package/template/reference/sections/featured-products/showcases/1.ts +28 -0
  63. package/template/reference/sections/featured-products/showcases/translations.ts +16 -0
  64. package/template/reference/sections/featured-products/type.ts +5 -0
  65. package/template/reference/sections/intro-slider/IntroSlider.vue +1 -1
  66. package/template/reference/sections/intro-slider/client.ts +1 -1
  67. package/template/reference/sections/intro-slider/component/Slider.vue +8 -2
  68. package/template/reference/sections/intro-slider/component/Title.vue +1 -1
  69. package/template/reference/sections/intro-slider/entity/color.ts +2 -2
  70. package/template/reference/sections/intro-slider/server.ts +1 -1
  71. package/template/reference/sections/tag-lines/TagLines.vue +1 -1
  72. package/template/reference/sections/tag-lines/client.ts +1 -1
  73. package/template/reference/sections/tag-lines/component/SectionImage.vue +1 -1
  74. package/template/reference/sections/tag-lines/component/Title.vue +1 -1
  75. package/template/reference/sections/tag-lines/composables/highlighted-text-image-list.ts +2 -2
  76. package/template/reference/sections/tag-lines/server.ts +1 -1
  77. package/template/reference/sections/trending-categories/TrendingCategories.vue +70 -0
  78. package/template/reference/sections/trending-categories/assets/arrow.svg +3 -0
  79. package/template/reference/sections/trending-categories/assets/custom_section_showcase_1_preview.png +0 -0
  80. package/template/reference/sections/trending-categories/client.ts +5 -0
  81. package/template/reference/sections/trending-categories/component/CategoryItem.vue +62 -0
  82. package/template/reference/sections/trending-categories/component/Title.vue +32 -0
  83. package/template/reference/sections/trending-categories/entity/color.ts +4 -0
  84. package/template/reference/sections/trending-categories/server.ts +5 -0
  85. package/template/reference/sections/trending-categories/settings/content.ts +14 -0
  86. package/template/reference/sections/trending-categories/settings/design.ts +33 -0
  87. package/template/reference/sections/trending-categories/settings/translations.ts +24 -0
  88. package/template/reference/sections/trending-categories/showcases/1.ts +36 -0
  89. package/template/reference/sections/trending-categories/showcases/translations.ts +22 -0
  90. package/template/reference/sections/trending-categories/type.ts +5 -0
  91. package/template/reference/shared/components/Button.vue +1 -1
  92. package/template/reference/templates/reference-template-apparel/pages/catalog.ts +1 -1
  93. package/template/reference/templates/reference-template-apparel/pages/category.ts +1 -1
  94. package/template/reference/templates/reference-template-apparel/pages/home.ts +10 -0
  95. package/template/reference/templates/reference-template-apparel/pages/product.ts +1 -1
  96. package/template/reference/templates/reference-template-bike/pages/catalog.ts +1 -1
  97. package/template/reference/templates/reference-template-bike/pages/category.ts +1 -1
  98. package/template/reference/templates/reference-template-bike/pages/home.ts +10 -0
  99. package/template/reference/templates/reference-template-bike/pages/product.ts +1 -1
  100. package/template/sections/example-section/ExampleSection.vue +8 -1
  101. package/template/sections/example-section/client.ts +1 -1
  102. package/template/sections/example-section/component/button/Button.vue +1 -1
  103. package/template/sections/example-section/component/image/Image.vue +1 -1
  104. package/template/sections/example-section/component/image/ImagesGrid.vue +1 -1
  105. package/template/sections/example-section/component/selectbox/Selectbox.vue +1 -1
  106. package/template/sections/example-section/component/title/Title.vue +1 -1
  107. package/template/sections/example-section/component/toggle/Toggle.vue +1 -1
  108. package/template/sections/example-section/entity/color.ts +2 -2
  109. package/template/sections/example-section/server.ts +1 -1
  110. package/template/sections/example-section/settings/translations.ts +1 -1
  111. package/template/sections/example-section/showcases/translations.ts +13 -13
  112. package/template/shared/components/LanguageSelector.vue +1 -1
  113. package/template/shared/translation.ts +16 -0
  114. package/template/shared/utils.ts +3 -1
  115. package/template/tsconfig.json +1 -0
  116. package/types.d.ts +6 -457
@@ -1,3 +1,316 @@
1
- export async function loadModule(path: string): Promise<any> {
2
- return import(/* @vite-ignore */ path);
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { getShowcaseState } from './preview';
3
+ import * as path from 'path';
4
+ import * as fs from 'fs';
5
+
6
+ /**
7
+ * Renders a server.js module by directly calling the SSR server.
8
+ * Returns HTML directly from the SSR server.
9
+ */
10
+ export async function renderServerModule(
11
+ modulePath: string,
12
+ context?: unknown,
13
+ data?: unknown,
14
+ ): Promise<string> {
15
+ try {
16
+ // Get SSR server port from environment variable
17
+ const ssrPort = process.env.PREVIEW_SSR_PORT || 3001;
18
+ const ssrUrl = `http://localhost:${ssrPort}/load-server-module`;
19
+
20
+ const response = await fetch(ssrUrl, {
21
+ method: 'POST',
22
+ headers: { 'Content-Type': 'application/json' },
23
+ body: JSON.stringify({ modulePath, context, data }),
24
+ });
25
+
26
+ if (!response.ok) {
27
+ throw new Error(`Failed to render server module: ${response.statusText}`);
28
+ }
29
+
30
+ const result = await response.json() as { html: string };
31
+ return result.html;
32
+ } catch (error) {
33
+ console.error('Error rendering server module via SSR server:', error);
34
+ throw error;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Adds cache-bust parameter to URL.
40
+ */
41
+ function addCacheBust(url: string): string {
42
+ const separator = url.includes('?') ? '&' : '?';
43
+ return `${url}${separator}t=${Date.now()}`;
44
+ }
45
+
46
+ /**
47
+ * Loads a module with proper SSR setup for server.js files.
48
+ * For other modules, uses dynamic import with cache-bust.
49
+ * Note: server.js modules are NOT loaded here - they are rendered via renderServerModule
50
+ */
51
+ export async function loadModule(modulePath: string): Promise<unknown> {
52
+ return import(/* @vite-ignore */ addCacheBust(modulePath));
53
+ }
54
+
55
+ /**
56
+ * Fetches tiles from the provided URL using bearer token from parameter
57
+ * @param authToken - Bearer token from request header (e.g., "Bearer xxx")
58
+ * @param tilesUrl - The original tiles URL from the Chrome extension
59
+ * @returns The tiles response object or null if failed
60
+ */
61
+ export async function fetchTiles(authToken?: string, tilesUrl?: string): Promise<any> {
62
+ try {
63
+ if (!tilesUrl) {
64
+ console.error('No tiles URL provided');
65
+ return null;
66
+ }
67
+
68
+ const headers: Record<string, string> = {};
69
+
70
+ if (authToken) {
71
+ headers['Authorization'] = authToken;
72
+ }
73
+
74
+ const response = await fetch(tilesUrl, {
75
+ method: 'GET',
76
+ headers,
77
+ });
78
+
79
+ if (!response.ok) {
80
+ console.error('Failed to fetch tiles:', response.status, response.statusText);
81
+ return null;
82
+ }
83
+
84
+ return await response.json();
85
+ } catch (error) {
86
+ console.error('Error fetching tiles:', error);
87
+ return null;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Updates the matching section in tiles with new content and design
93
+ * @param tilesResponse - The tiles response object
94
+ * @param sectionName - The section name to match (e.g., 'example-section')
95
+ * @param showcaseId - The showcase ID to match
96
+ * @param content - The new content to replace
97
+ * @param design - The new design to replace
98
+ * @returns The updated tiles response object
99
+ */
100
+ export function updateTilesSection(
101
+ tilesResponse: any,
102
+ sectionName: string,
103
+ showcaseId: string,
104
+ content: any,
105
+ design: any,
106
+ ): any {
107
+ const targetPattern = `_${sectionName}_${showcaseId}`;
108
+
109
+ const targetItem = tilesResponse.tiles.find((item: any) => {
110
+ const itemId = item.sourceId;
111
+ if (itemId && typeof itemId === 'string') {
112
+ return itemId.includes(targetPattern);
113
+ }
114
+ return false;
115
+ });
116
+
117
+ if (targetItem) {
118
+ // Replace only content and design, preserve all other properties including tils
119
+ targetItem.content = content;
120
+ targetItem.design = design;
121
+ }
122
+
123
+ return tilesResponse;
124
+ }
125
+
126
+ /**
127
+ * Fetches tiles and returns all IDs of sections matching the pattern
128
+ * @param sectionName - The section name to match (e.g., 'example-section')
129
+ * @param showcaseId - The showcase ID to match
130
+ * @param authToken - Bearer token from request header (e.g., "Bearer xxx")
131
+ * @param tilesUrl - The original tiles URL from the Chrome extension
132
+ * @returns Array of section IDs or null if not found
133
+ */
134
+ export async function getSectionIds(sectionName: string, showcaseId: string, authToken?: string, tilesUrl?: string): Promise<string[] | null> {
135
+ try {
136
+ const tilesResponse = await fetchTiles(authToken, tilesUrl);
137
+ if (!tilesResponse || !tilesResponse.tiles) {
138
+ return null;
139
+ }
140
+
141
+ const targetPattern = `_${sectionName}_${showcaseId}`;
142
+
143
+ const targetItems = tilesResponse.tiles.filter((item: any) => {
144
+ const itemId = item.sourceId;
145
+ return itemId?.includes(targetPattern);
146
+ });
147
+
148
+ return targetItems.length > 0 ? targetItems.map((item: any) => item.id) : null;
149
+ } catch (error) {
150
+ console.error('Error getting section IDs:', error);
151
+ return null;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Gets all showcase IDs for a given section
157
+ * @param sectionName - The section name (e.g., 'example-section')
158
+ * @param distPath - The dist path
159
+ * @returns Array of showcase IDs
160
+ */
161
+ export function getShowcaseIds(sectionName: string, distPath: string): string[] {
162
+ try {
163
+ const showcasesPath = path.join(distPath, 'sections', sectionName, 'js', 'showcases');
164
+
165
+ if (!fs.existsSync(showcasesPath)) {
166
+ console.error(`Showcases directory not found for section: ${sectionName}`);
167
+ return [];
168
+ }
169
+
170
+ const showcaseIds = fs.readdirSync(showcasesPath)
171
+ .filter((file: string) => file.endsWith('.mjs') && file !== 'translations.mjs')
172
+ .map((file: string) => file.replace('.mjs', ''));
173
+
174
+ return showcaseIds;
175
+ } catch (error) {
176
+ console.error(`Error getting showcase IDs for section ${sectionName}:`, error);
177
+ return [];
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Updates all matching section showcases in custom content
183
+ * @param sectionName - The section name
184
+ * @param showcaseId - The showcase ID
185
+ * @param distPath - The dist path
186
+ * @param customContentSections - The sections array from custom content
187
+ * @param authToken - Bearer token
188
+ * @param tilesUrl - The tiles URL
189
+ * @returns True if at least one section was updated successfully, false otherwise
190
+ */
191
+ async function updateSectionShowcase(
192
+ sectionName: string,
193
+ showcaseId: string,
194
+ distPath: string,
195
+ customContentSections: Array<Record<string, unknown>>,
196
+ authToken?: string,
197
+ tilesUrl?: string,
198
+ ): Promise<boolean> {
199
+ try {
200
+ // Get all section IDs from tiles
201
+ const sectionIds = await getSectionIds(sectionName, showcaseId, authToken, tilesUrl);
202
+
203
+ if (!sectionIds || sectionIds.length === 0) {
204
+ return false;
205
+ }
206
+
207
+ // Process each section ID
208
+ for (const sectionId of sectionIds) {
209
+ // Find the section by ID in custom content
210
+ const targetSection = customContentSections.find((section) => {
211
+ const state = section.state as Record<string, unknown>;
212
+ const data = state.data as Record<string, unknown>;
213
+ return data.tileId === sectionId;
214
+ });
215
+
216
+ if (!targetSection) {
217
+ console.error(`Section not found in custom content with ID: ${sectionId}`);
218
+ continue;
219
+ }
220
+
221
+ // Fetch content, design, and html using getShowcaseState
222
+ const state = targetSection.state as Record<string, unknown>;
223
+ // eslint-disable-next-line no-await-in-loop
224
+ const { content, design, html } = await getShowcaseState(
225
+ sectionName,
226
+ showcaseId,
227
+ distPath,
228
+ state.context as Record<string, unknown>,
229
+ );
230
+
231
+ // Update content, design, and html
232
+ const data = state.data as Record<string, unknown>;
233
+ data.content = JSON.stringify(content);
234
+ data.design = JSON.stringify(design);
235
+ targetSection.html = html;
236
+ }
237
+
238
+ return true;
239
+ } catch (error) {
240
+ console.error(`Error updating ${sectionName} showcase ${showcaseId}:`, error);
241
+ return false;
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Fetches custom content and updates all specified sections with their showcases
247
+ * @param sectionNames - Array of section names to update (e.g., ['example-section', 'test-section'])
248
+ * @param distPath - The dist path for getContentAndDesign
249
+ * @param authToken - Bearer token from request header (e.g., "Bearer xxx")
250
+ * @param customContentUrl - The custom content URL
251
+ * @param tilesUrl - The tiles URL to fetch section IDs
252
+ * @returns The updated custom content response or null if failed
253
+ */
254
+ export async function updateCustomContent(
255
+ sectionNames: string[],
256
+ distPath: string,
257
+ authToken?: string,
258
+ customContentUrl?: string,
259
+ tilesUrl?: string,
260
+ ): Promise<any> {
261
+ try {
262
+ if (!customContentUrl || !tilesUrl) {
263
+ console.error('No custom content or tile URL provided');
264
+ return null;
265
+ }
266
+
267
+ // Fetch custom content
268
+ const headers: Record<string, string> = {};
269
+
270
+ if (authToken) {
271
+ headers['Authorization'] = authToken;
272
+ }
273
+
274
+ const response = await fetch(customContentUrl, {
275
+ method: 'GET',
276
+ headers,
277
+ });
278
+
279
+ if (!response.ok) {
280
+ console.error('Failed to fetch custom content:', response.status, response.statusText);
281
+ return null;
282
+ }
283
+
284
+ const customContentResponse = await response.json() as Record<string, unknown>;
285
+
286
+ const customContent = customContentResponse.customContent as Record<string, unknown>;
287
+ if (!customContent || !customContent.sections) {
288
+ console.error('Invalid custom content response structure');
289
+ return null;
290
+ }
291
+
292
+ const customContentSections = customContent.sections as Array<Record<string, unknown>>;
293
+
294
+ // Process all sections and showcases in parallel
295
+ const updatePromises = sectionNames.flatMap((sectionName) => {
296
+ const showcaseIds = getShowcaseIds(sectionName, distPath);
297
+
298
+ return showcaseIds.map(showcaseId =>
299
+ updateSectionShowcase(
300
+ sectionName,
301
+ showcaseId,
302
+ distPath,
303
+ customContentSections,
304
+ authToken,
305
+ tilesUrl,
306
+ ),
307
+ );
308
+ });
309
+
310
+ await Promise.all(updatePromises);
311
+ return customContentResponse;
312
+ } catch (error) {
313
+ console.error('Error updating custom content:', error);
314
+ return null;
315
+ }
3
316
  }