@lightspeed/crane 1.4.2 → 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 +33 -0
  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 +3 -2
  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 +6 -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 +347 -39
  39. package/template/preview/shared/mock.ts +43 -41
  40. package/template/preview/shared/preview.ts +205 -126
  41. package/template/preview/shared/utils.ts +208 -62
  42. package/template/preview/ssr-server.ts +429 -0
  43. package/template/preview/vite.config.js +64 -65
  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,28 +1,79 @@
1
- import { getContentAndDesign } from './preview';
2
-
3
- // Hardcoded URLs and token
4
- const TILES_URL = '';
5
- const CUSTOM_CONTENT_URL = '';
6
- const BEARER_TOKEN = '';
7
-
8
- export async function loadModule(path: string): Promise<any> {
9
- // Add cache busting parameter with timestamp
10
- const cacheBuster = `?t=${Date.now()}`;
11
- const pathWithCacheBuster = path + cacheBuster;
12
- return import(/* @vite-ignore */ pathWithCacheBuster);
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
+ }
13
36
  }
14
37
 
15
38
  /**
16
- * Fetches tiles from the hardcoded URL using hardcoded bearer token
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
17
59
  * @returns The tiles response object or null if failed
18
60
  */
19
- export async function fetchTiles(): Promise<any> {
61
+ export async function fetchTiles(authToken?: string, tilesUrl?: string): Promise<any> {
20
62
  try {
21
- const response = await fetch(TILES_URL, {
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, {
22
75
  method: 'GET',
23
- headers: {
24
- 'Authorization': BEARER_TOKEN
25
- }
76
+ headers,
26
77
  });
27
78
 
28
79
  if (!response.ok) {
@@ -30,9 +81,7 @@ export async function fetchTiles(): Promise<any> {
30
81
  return null;
31
82
  }
32
83
 
33
- const tilesResponse = await response.json();
34
- return tilesResponse;
35
-
84
+ return await response.json();
36
85
  } catch (error) {
37
86
  console.error('Error fetching tiles:', error);
38
87
  return null;
@@ -53,7 +102,7 @@ export function updateTilesSection(
53
102
  sectionName: string,
54
103
  showcaseId: string,
55
104
  content: any,
56
- design: any
105
+ design: any,
57
106
  ): any {
58
107
  const targetPattern = `_${sectionName}_${showcaseId}`;
59
108
 
@@ -75,64 +124,156 @@ export function updateTilesSection(
75
124
  }
76
125
 
77
126
  /**
78
- * Fetches tiles and returns the ID of a section matching the pattern
127
+ * Fetches tiles and returns all IDs of sections matching the pattern
79
128
  * @param sectionName - The section name to match (e.g., 'example-section')
80
129
  * @param showcaseId - The showcase ID to match
81
- * @returns The section ID or null if not found
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
82
133
  */
83
- export async function getSectionId(sectionName: string, showcaseId: string): Promise<string | null> {
134
+ export async function getSectionIds(sectionName: string, showcaseId: string, authToken?: string, tilesUrl?: string): Promise<string[] | null> {
84
135
  try {
85
- const tilesResponse = await fetchTiles();
136
+ const tilesResponse = await fetchTiles(authToken, tilesUrl);
86
137
  if (!tilesResponse || !tilesResponse.tiles) {
87
138
  return null;
88
139
  }
89
140
 
90
141
  const targetPattern = `_${sectionName}_${showcaseId}`;
91
142
 
92
- const targetItem = tilesResponse.tiles.find((item: any) => {
143
+ const targetItems = tilesResponse.tiles.filter((item: any) => {
93
144
  const itemId = item.sourceId;
94
145
  return itemId?.includes(targetPattern);
95
146
  });
96
147
 
97
- return targetItem ? targetItem.id : null;
98
-
148
+ return targetItems.length > 0 ? targetItems.map((item: any) => item.id) : null;
99
149
  } catch (error) {
100
- console.error('Error getting section ID:', error);
150
+ console.error('Error getting section IDs:', error);
101
151
  return null;
102
152
  }
103
153
  }
104
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
+ }
105
180
 
106
181
  /**
107
- * Fetches custom content and updates the matching section with content and design from getContentAndDesign
108
- * @param sectionName - The section name to match (e.g., 'example-section')
109
- * @param showcaseId - The showcase ID to match
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'])
110
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
111
252
  * @returns The updated custom content response or null if failed
112
253
  */
113
254
  export async function updateCustomContent(
114
- sectionName: string,
115
- showcaseId: string,
116
- distPath: string
255
+ sectionNames: string[],
256
+ distPath: string,
257
+ authToken?: string,
258
+ customContentUrl?: string,
259
+ tilesUrl?: string,
117
260
  ): Promise<any> {
118
261
  try {
119
- // First, get the section ID from tiles
120
- const sectionId = await getSectionId(sectionName, showcaseId);
121
-
122
- if (!sectionId) {
123
- console.error('Section ID not found for:', sectionName, showcaseId);
262
+ if (!customContentUrl || !tilesUrl) {
263
+ console.error('No custom content or tile URL provided');
124
264
  return null;
125
265
  }
126
266
 
127
- // Fetch content and design using getContentAndDesign from preview.ts
128
- const { content, design } = await getContentAndDesign(sectionName, showcaseId, distPath);
129
-
130
267
  // Fetch custom content
131
- const response = await fetch(CUSTOM_CONTENT_URL, {
268
+ const headers: Record<string, string> = {};
269
+
270
+ if (authToken) {
271
+ headers['Authorization'] = authToken;
272
+ }
273
+
274
+ const response = await fetch(customContentUrl, {
132
275
  method: 'GET',
133
- headers: {
134
- 'Authorization': BEARER_TOKEN
135
- }
276
+ headers,
136
277
  });
137
278
 
138
279
  if (!response.ok) {
@@ -140,29 +281,34 @@ export async function updateCustomContent(
140
281
  return null;
141
282
  }
142
283
 
143
- const customContentResponse = await response.json();
284
+ const customContentResponse = await response.json() as Record<string, unknown>;
144
285
 
145
- if (!customContentResponse.customContent || !customContentResponse.customContent.sections) {
286
+ const customContent = customContentResponse.customContent as Record<string, unknown>;
287
+ if (!customContent || !customContent.sections) {
146
288
  console.error('Invalid custom content response structure');
147
289
  return null;
148
290
  }
149
291
 
150
- // Find the section by ID
151
- const targetSection = customContentResponse.customContent.sections.find((section: any) => {
152
- return section.state.data.tileId === sectionId;
153
- });
292
+ const customContentSections = customContent.sections as Array<Record<string, unknown>>;
154
293
 
155
- if (!targetSection) {
156
- console.error('Section not found in custom content with ID:', sectionId);
157
- return null;
158
- }
294
+ // Process all sections and showcases in parallel
295
+ const updatePromises = sectionNames.flatMap((sectionName) => {
296
+ const showcaseIds = getShowcaseIds(sectionName, distPath);
159
297
 
160
- // Update content and design
161
- targetSection.state.data.content = JSON.stringify(content);
162
- targetSection.state.data.design = JSON.stringify(design);
298
+ return showcaseIds.map(showcaseId =>
299
+ updateSectionShowcase(
300
+ sectionName,
301
+ showcaseId,
302
+ distPath,
303
+ customContentSections,
304
+ authToken,
305
+ tilesUrl,
306
+ ),
307
+ );
308
+ });
163
309
 
310
+ await Promise.all(updatePromises);
164
311
  return customContentResponse;
165
-
166
312
  } catch (error) {
167
313
  console.error('Error updating custom content:', error);
168
314
  return null;