@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.
- package/CHANGELOG.md +51 -4
- package/UPGRADE.md +19 -0
- package/dist/app.d.mts +1 -1028
- package/dist/app.d.ts +1 -1028
- package/dist/app.mjs +1 -1
- package/dist/cli.mjs +20 -7
- package/package.json +6 -3
- package/template/footers/example-footer/ExampleFooter.vue +1 -1
- package/template/footers/example-footer/client.ts +1 -1
- package/template/footers/example-footer/component/LegalLinks.vue +1 -1
- package/template/footers/example-footer/component/MadeWith.vue +1 -1
- package/template/footers/example-footer/component/ReportAbuse.vue +1 -1
- package/template/footers/example-footer/entity/color.ts +2 -2
- package/template/footers/example-footer/server.ts +1 -1
- package/template/headers/example-header/client.ts +1 -1
- package/template/headers/example-header/component/Account.vue +1 -1
- package/template/headers/example-header/component/Cart.vue +1 -1
- package/template/headers/example-header/component/CategoriesDropdown.vue +1 -1
- package/template/headers/example-header/component/Logo.vue +1 -1
- package/template/headers/example-header/component/NavigationMenu.vue +1 -1
- package/template/headers/example-header/component/SearchForm.vue +1 -1
- package/template/headers/example-header/server.ts +1 -1
- package/template/index.d.ts +1 -1
- package/template/layouts/catalog/example-catalog/Main.vue +1 -1
- package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/client.ts +1 -1
- package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/server.ts +1 -1
- package/template/layouts/category/example-category/Main.vue +1 -1
- package/template/layouts/category/example-category/settings/content.ts +1 -1
- package/template/layouts/category/example-category/settings/design.ts +1 -1
- package/template/layouts/product/example-product/Main.vue +1 -1
- package/template/layouts/product/example-product/settings/content.ts +1 -1
- package/template/layouts/product/example-product/settings/design.ts +1 -1
- package/template/package.json +7 -3
- package/template/page-templates/example-template/pages/catalog.ts +1 -1
- package/template/page-templates/example-template/pages/category.ts +1 -1
- package/template/page-templates/example-template/pages/product.ts +1 -1
- package/template/preview/sections/preview.html +1 -1
- package/template/preview/shared/api-routes.ts +443 -0
- package/template/preview/shared/mock.ts +43 -41
- package/template/preview/shared/preview.ts +213 -101
- package/template/preview/shared/utils.ts +315 -2
- package/template/preview/ssr-server.ts +429 -0
- package/template/preview/vite.config.js +76 -34
- package/template/reference/sections/about-us/AboutUs.vue +1 -1
- package/template/reference/sections/about-us/client.ts +1 -1
- package/template/reference/sections/about-us/component/Image.vue +1 -1
- package/template/reference/sections/about-us/component/Stats.vue +2 -2
- package/template/reference/sections/about-us/component/Title.vue +1 -1
- package/template/reference/sections/about-us/server.ts +1 -1
- package/template/reference/sections/about-us/util/visibility-provider.ts +1 -1
- package/template/reference/sections/featured-products/FeaturedProducts.vue +65 -0
- package/template/reference/sections/featured-products/assets/arrow.svg +3 -0
- package/template/reference/sections/featured-products/assets/custom_section_showcase_1_preview.png +0 -0
- package/template/reference/sections/featured-products/client.ts +5 -0
- package/template/reference/sections/featured-products/component/ProductItem.vue +71 -0
- package/template/reference/sections/featured-products/component/Title.vue +31 -0
- package/template/reference/sections/featured-products/entity/color.ts +4 -0
- package/template/reference/sections/featured-products/server.ts +5 -0
- package/template/reference/sections/featured-products/settings/content.ts +14 -0
- package/template/reference/sections/featured-products/settings/design.ts +33 -0
- package/template/reference/sections/featured-products/settings/translations.ts +24 -0
- package/template/reference/sections/featured-products/showcases/1.ts +28 -0
- package/template/reference/sections/featured-products/showcases/translations.ts +16 -0
- package/template/reference/sections/featured-products/type.ts +5 -0
- package/template/reference/sections/intro-slider/IntroSlider.vue +1 -1
- package/template/reference/sections/intro-slider/client.ts +1 -1
- package/template/reference/sections/intro-slider/component/Slider.vue +8 -2
- package/template/reference/sections/intro-slider/component/Title.vue +1 -1
- package/template/reference/sections/intro-slider/entity/color.ts +2 -2
- package/template/reference/sections/intro-slider/server.ts +1 -1
- package/template/reference/sections/tag-lines/TagLines.vue +1 -1
- package/template/reference/sections/tag-lines/client.ts +1 -1
- package/template/reference/sections/tag-lines/component/SectionImage.vue +1 -1
- package/template/reference/sections/tag-lines/component/Title.vue +1 -1
- package/template/reference/sections/tag-lines/composables/highlighted-text-image-list.ts +2 -2
- package/template/reference/sections/tag-lines/server.ts +1 -1
- package/template/reference/sections/trending-categories/TrendingCategories.vue +70 -0
- package/template/reference/sections/trending-categories/assets/arrow.svg +3 -0
- package/template/reference/sections/trending-categories/assets/custom_section_showcase_1_preview.png +0 -0
- package/template/reference/sections/trending-categories/client.ts +5 -0
- package/template/reference/sections/trending-categories/component/CategoryItem.vue +62 -0
- package/template/reference/sections/trending-categories/component/Title.vue +32 -0
- package/template/reference/sections/trending-categories/entity/color.ts +4 -0
- package/template/reference/sections/trending-categories/server.ts +5 -0
- package/template/reference/sections/trending-categories/settings/content.ts +14 -0
- package/template/reference/sections/trending-categories/settings/design.ts +33 -0
- package/template/reference/sections/trending-categories/settings/translations.ts +24 -0
- package/template/reference/sections/trending-categories/showcases/1.ts +36 -0
- package/template/reference/sections/trending-categories/showcases/translations.ts +22 -0
- package/template/reference/sections/trending-categories/type.ts +5 -0
- package/template/reference/shared/components/Button.vue +1 -1
- package/template/reference/templates/reference-template-apparel/pages/catalog.ts +1 -1
- package/template/reference/templates/reference-template-apparel/pages/category.ts +1 -1
- package/template/reference/templates/reference-template-apparel/pages/home.ts +10 -0
- package/template/reference/templates/reference-template-apparel/pages/product.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/catalog.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/category.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/home.ts +10 -0
- package/template/reference/templates/reference-template-bike/pages/product.ts +1 -1
- package/template/sections/example-section/ExampleSection.vue +8 -1
- package/template/sections/example-section/client.ts +1 -1
- package/template/sections/example-section/component/button/Button.vue +1 -1
- package/template/sections/example-section/component/image/Image.vue +1 -1
- package/template/sections/example-section/component/image/ImagesGrid.vue +1 -1
- package/template/sections/example-section/component/selectbox/Selectbox.vue +1 -1
- package/template/sections/example-section/component/title/Title.vue +1 -1
- package/template/sections/example-section/component/toggle/Toggle.vue +1 -1
- package/template/sections/example-section/entity/color.ts +2 -2
- package/template/sections/example-section/server.ts +1 -1
- package/template/sections/example-section/settings/translations.ts +1 -1
- package/template/sections/example-section/showcases/translations.ts +13 -13
- package/template/shared/components/LanguageSelector.vue +1 -1
- package/template/shared/translation.ts +16 -0
- package/template/shared/utils.ts +3 -1
- package/template/tsconfig.json +1 -0
- package/types.d.ts +6 -457
|
@@ -1,3 +1,316 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
}
|