@lightspeed/crane 1.4.2 → 2.0.1

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