@lightspeed/crane 1.4.0 → 1.4.2
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 +29 -4
- package/dist/app.d.mts +24 -24
- package/dist/app.d.ts +24 -24
- package/dist/cli.mjs +7 -7
- package/package.json +23 -21
- package/template/headers/example-header/showcases/1.ts +0 -1
- package/template/headers/example-header/showcases/2.ts +0 -1
- package/template/package.json +4 -3
- package/template/preview/shared/api-routes.ts +135 -0
- package/template/preview/shared/preview.ts +33 -0
- package/template/preview/shared/utils.ts +168 -1
- package/template/preview/vite.config.js +46 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightspeed/crane",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": "bin/crane.js",
|
|
6
6
|
"main": "./dist/app.mjs",
|
|
@@ -26,10 +26,12 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"dev": "unbuild --stub",
|
|
28
28
|
"build": "unbuild",
|
|
29
|
+
"link": "npm link",
|
|
29
30
|
"type-check": "tsc --noEmit",
|
|
30
31
|
"lint": "eslint \"./{src,tests}/**/*.{js,ts}\"",
|
|
31
32
|
"lint:all": "yarn lint && yarn type-check",
|
|
32
|
-
"test": "jest"
|
|
33
|
+
"test": "jest",
|
|
34
|
+
"test:end-to-end": "jest end-to-end.test.ts --no-coverage"
|
|
33
35
|
},
|
|
34
36
|
"engines": {
|
|
35
37
|
"node": "22"
|
|
@@ -55,50 +57,50 @@
|
|
|
55
57
|
"mock-fs": "^5.5.0",
|
|
56
58
|
"ts-jest": "^29.4.0",
|
|
57
59
|
"ts-node": "^10.9.2",
|
|
58
|
-
"typescript": "5.
|
|
60
|
+
"typescript": "5.9.2",
|
|
59
61
|
"unbuild": "^3.5.0",
|
|
60
62
|
"vite-plugin-dts": "^4.5.4",
|
|
61
63
|
"vite-plugin-static-copy": "^3.1.0"
|
|
62
64
|
},
|
|
63
65
|
"dependencies": {
|
|
64
|
-
"@jridgewell/sourcemap-codec": "^1.4
|
|
65
|
-
"@lightspeed/eslint-config-crane": "1.1.
|
|
66
|
+
"@jridgewell/sourcemap-codec": "^1.5.4",
|
|
67
|
+
"@lightspeed/eslint-config-crane": "1.1.3",
|
|
66
68
|
"@types/prompts": "^2.4.2",
|
|
67
|
-
"@vitejs/plugin-vue": "^
|
|
69
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
68
70
|
"adm-zip": "^0.5.16",
|
|
69
|
-
"ajv": "^8.
|
|
70
|
-
"ajv-formats": "^
|
|
71
|
+
"ajv": "^8.17.1",
|
|
72
|
+
"ajv-formats": "^3.0.1",
|
|
71
73
|
"axios": "^1.11.0",
|
|
72
74
|
"axios-concurrency": "^1.0.4",
|
|
73
75
|
"cac": "^6.7.14",
|
|
74
76
|
"cli-progress": "^3.12.0",
|
|
75
77
|
"eslint": "^9.33.0",
|
|
76
|
-
"fs-extra": "^11.
|
|
77
|
-
"glob": "^
|
|
78
|
+
"fs-extra": "^11.3.1",
|
|
79
|
+
"glob": "^11.0.3",
|
|
78
80
|
"jsonpath-plus": "^10.3.0",
|
|
79
81
|
"kolorist": "^1.8.0",
|
|
80
82
|
"prompts": "^2.4.2",
|
|
81
|
-
"semver": "^7.
|
|
82
|
-
"terser": "^5.
|
|
83
|
+
"semver": "^7.7.2",
|
|
84
|
+
"terser": "^5.44.0",
|
|
83
85
|
"tinycolor2": "^1.6.0",
|
|
84
|
-
"typescript": "5.
|
|
85
|
-
"vite": "^
|
|
86
|
-
"vite-plugin-checker": "^0.
|
|
86
|
+
"typescript": "5.9.2",
|
|
87
|
+
"vite": "^7.1.4",
|
|
88
|
+
"vite-plugin-checker": "^0.10.3",
|
|
87
89
|
"vite-plugin-compression": "^0.5.1",
|
|
88
90
|
"vite-plugin-externals": "^0.6.2",
|
|
89
|
-
"vite-tsconfig-paths": "^
|
|
90
|
-
"vue": "^3.
|
|
91
|
-
"vue-tsc": "^
|
|
91
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
92
|
+
"vue": "^3.5.21",
|
|
93
|
+
"vue-tsc": "^3.0.6"
|
|
92
94
|
},
|
|
93
95
|
"peerDependencies": {
|
|
94
|
-
"vue": "^3.
|
|
96
|
+
"vue": "^3.5.21"
|
|
95
97
|
},
|
|
96
98
|
"overrides": {
|
|
97
99
|
"axios-concurrency": {
|
|
98
100
|
"axios": "1.11.0"
|
|
99
101
|
},
|
|
100
102
|
"magic-string": "0.30.10",
|
|
101
|
-
"glob": "^
|
|
102
|
-
"stylus": "^0.
|
|
103
|
+
"glob": "^11.0.3",
|
|
104
|
+
"stylus": "^0.64.0"
|
|
103
105
|
}
|
|
104
106
|
}
|
package/template/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"name": "__placeholder__",
|
|
3
3
|
"private": true,
|
|
4
4
|
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"build": "crane build",
|
|
7
8
|
"deploy": "crane build && crane deploy"
|
|
@@ -10,13 +11,13 @@
|
|
|
10
11
|
"@lightspeed/crane": "latest",
|
|
11
12
|
"@lightspeed/eslint-config-crane": "latest",
|
|
12
13
|
"vue": "^3.4.0",
|
|
13
|
-
"eslint": "9.
|
|
14
|
+
"eslint": "9.33.0"
|
|
14
15
|
},
|
|
15
16
|
"engines": {
|
|
16
17
|
"node": ">=22"
|
|
17
18
|
},
|
|
18
19
|
"overrides": {
|
|
19
|
-
"glob": "^
|
|
20
|
-
"stylus": "^0.
|
|
20
|
+
"glob": "^11.0.3",
|
|
21
|
+
"stylus": "^0.64.0"
|
|
21
22
|
}
|
|
22
23
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { getContentAndDesign } from './preview';
|
|
3
|
+
import { fetchTiles, updateTilesSection, updateCustomContent } from './utils';
|
|
4
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Set no-cache headers to prevent browser caching
|
|
8
|
+
*/
|
|
9
|
+
function setNoCacheHeaders(res: ServerResponse): void {
|
|
10
|
+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
11
|
+
res.setHeader('Pragma', 'no-cache');
|
|
12
|
+
res.setHeader('Expires', '0');
|
|
13
|
+
res.setHeader('Surrogate-Control', 'no-store');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Handle GET /api/v1/tile
|
|
18
|
+
* Returns tile data with content and design
|
|
19
|
+
*/
|
|
20
|
+
export async function handleGetTile(
|
|
21
|
+
_req: IncomingMessage,
|
|
22
|
+
res: ServerResponse,
|
|
23
|
+
sectionName: string,
|
|
24
|
+
showcaseId: string
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
const distPath = path.resolve(process.cwd(), 'dist');
|
|
28
|
+
|
|
29
|
+
// Use getContentAndDesign from preview.ts to fetch content and design
|
|
30
|
+
const { content, design } = await getContentAndDesign(sectionName, showcaseId, distPath);
|
|
31
|
+
|
|
32
|
+
// Fetch and update tiles
|
|
33
|
+
const tilesResponse = await fetchTiles();
|
|
34
|
+
const responseData = updateTilesSection(tilesResponse, sectionName, showcaseId, content, design);
|
|
35
|
+
|
|
36
|
+
res.statusCode = 200;
|
|
37
|
+
res.setHeader('Content-Type', 'application/json');
|
|
38
|
+
setNoCacheHeaders(res);
|
|
39
|
+
res.end(JSON.stringify(responseData, null, 2));
|
|
40
|
+
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('[API Routes] ❌ Error loading tile data:', error);
|
|
43
|
+
res.statusCode = 500;
|
|
44
|
+
res.setHeader('Content-Type', 'application/json');
|
|
45
|
+
setNoCacheHeaders(res);
|
|
46
|
+
res.end(JSON.stringify({
|
|
47
|
+
error: 'Failed to load tile data',
|
|
48
|
+
message: error instanceof Error ? error.message : String(error),
|
|
49
|
+
sectionName,
|
|
50
|
+
showcaseId
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Handle POST /api/v1/custom_content
|
|
57
|
+
* Updates custom content for a section
|
|
58
|
+
*/
|
|
59
|
+
export async function handleUpdateCustomContent(
|
|
60
|
+
_req: IncomingMessage,
|
|
61
|
+
res: ServerResponse,
|
|
62
|
+
sectionName: string,
|
|
63
|
+
showcaseId: string
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
try {
|
|
66
|
+
const distPath = path.resolve(process.cwd(), 'dist');
|
|
67
|
+
let finalResponseData = null;
|
|
68
|
+
|
|
69
|
+
// Use updateCustomContent function
|
|
70
|
+
const updatedCustomContent = await updateCustomContent(sectionName, showcaseId, distPath);
|
|
71
|
+
|
|
72
|
+
if (updatedCustomContent) {
|
|
73
|
+
finalResponseData = updatedCustomContent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Build the response data
|
|
77
|
+
const responseData = finalResponseData || {
|
|
78
|
+
error: 'Failed to update custom content',
|
|
79
|
+
sectionName,
|
|
80
|
+
showcaseId
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
res.statusCode = finalResponseData ? 200 : 500;
|
|
84
|
+
res.setHeader('Content-Type', 'application/json');
|
|
85
|
+
setNoCacheHeaders(res);
|
|
86
|
+
res.end(JSON.stringify(responseData, null, 2));
|
|
87
|
+
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('[API Routes] ❌ Error in update-custom-content endpoint:', error);
|
|
90
|
+
res.statusCode = 500;
|
|
91
|
+
res.setHeader('Content-Type', 'application/json');
|
|
92
|
+
setNoCacheHeaders(res);
|
|
93
|
+
res.end(JSON.stringify({
|
|
94
|
+
error: 'Failed to process request',
|
|
95
|
+
message: error instanceof Error ? error.message : String(error),
|
|
96
|
+
sectionName,
|
|
97
|
+
showcaseId
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Main API router
|
|
104
|
+
* Routes requests to appropriate handlers
|
|
105
|
+
*/
|
|
106
|
+
export function handleApiRequest(
|
|
107
|
+
req: IncomingMessage,
|
|
108
|
+
res: ServerResponse,
|
|
109
|
+
sectionName: string,
|
|
110
|
+
showcaseId: string
|
|
111
|
+
): void {
|
|
112
|
+
const url = req.url || '';
|
|
113
|
+
|
|
114
|
+
// Route: GET /api/v1/tile
|
|
115
|
+
if (url.startsWith('/api/v1/tile')) {
|
|
116
|
+
handleGetTile(req, res, sectionName, showcaseId);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Route: POST /api/v1/custom_content
|
|
121
|
+
if (url.startsWith('/api/v1/custom_content')) {
|
|
122
|
+
handleUpdateCustomContent(req, res, sectionName, showcaseId);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Unknown API route
|
|
127
|
+
res.statusCode = 404;
|
|
128
|
+
res.setHeader('Content-Type', 'application/json');
|
|
129
|
+
setNoCacheHeaders(res);
|
|
130
|
+
res.end(JSON.stringify({
|
|
131
|
+
error: 'API route not found',
|
|
132
|
+
url: url
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
|
|
@@ -376,3 +376,36 @@ export async function renderShowcase(sectionName: string, showcaseId: string): P
|
|
|
376
376
|
mount('#app', state);
|
|
377
377
|
}
|
|
378
378
|
}
|
|
379
|
+
|
|
380
|
+
export async function getContentAndDesign(sectionName: string, showcaseId: string, distFolder:string) {
|
|
381
|
+
const content = await loadModule(`${distFolder}/sections/${sectionName}/js/settings/content.mjs`);
|
|
382
|
+
const contentTranslations = await loadModule(`${distFolder}/sections/${sectionName}/js/settings/translations.mjs`);
|
|
383
|
+
const showcaseTranslations = await loadModule(`${distFolder}/sections/${sectionName}/js/showcases/translations.mjs`);
|
|
384
|
+
const showcase = await loadModule(`${distFolder}/sections/${sectionName}/js/showcases/${showcaseId}.mjs`);
|
|
385
|
+
const design = await loadModule(`${distFolder}/sections/${sectionName}/js/settings/design.mjs`);
|
|
386
|
+
|
|
387
|
+
// Get showcase background and create background design
|
|
388
|
+
const showcaseBackground = showcase.default?.design?.background;
|
|
389
|
+
const backgroundDesign = createBackgroundDesign(showcaseBackground);
|
|
390
|
+
|
|
391
|
+
const ovveridenDesign = designTransformer(design.default, showcase.default.design || {});
|
|
392
|
+
|
|
393
|
+
const overriddenContent = getContentToRender(
|
|
394
|
+
content.default,
|
|
395
|
+
showcase.default.content || {},
|
|
396
|
+
contentTranslations.default.en,
|
|
397
|
+
showcaseTranslations.default.en,
|
|
398
|
+
sectionName
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
// Ensure background design always overrides any existing background
|
|
402
|
+
const finalDesign = {...ovveridenDesign};
|
|
403
|
+
finalDesign.background = backgroundDesign.background;
|
|
404
|
+
|
|
405
|
+
const state = {
|
|
406
|
+
content: overriddenContent,
|
|
407
|
+
design: finalDesign,
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
return state;
|
|
411
|
+
}
|
|
@@ -1,3 +1,170 @@
|
|
|
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
|
+
|
|
1
8
|
export async function loadModule(path: string): Promise<any> {
|
|
2
|
-
|
|
9
|
+
// Add cache busting parameter with timestamp
|
|
10
|
+
const cacheBuster = `?t=${Date.now()}`;
|
|
11
|
+
const pathWithCacheBuster = path + cacheBuster;
|
|
12
|
+
return import(/* @vite-ignore */ pathWithCacheBuster);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fetches tiles from the hardcoded URL using hardcoded bearer token
|
|
17
|
+
* @returns The tiles response object or null if failed
|
|
18
|
+
*/
|
|
19
|
+
export async function fetchTiles(): Promise<any> {
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(TILES_URL, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: {
|
|
24
|
+
'Authorization': BEARER_TOKEN
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
console.error('Failed to fetch tiles:', response.status, response.statusText);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tilesResponse = await response.json();
|
|
34
|
+
return tilesResponse;
|
|
35
|
+
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Error fetching tiles:', error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Updates the matching section in tiles with new content and design
|
|
44
|
+
* @param tilesResponse - The tiles response object
|
|
45
|
+
* @param sectionName - The section name to match (e.g., 'example-section')
|
|
46
|
+
* @param showcaseId - The showcase ID to match
|
|
47
|
+
* @param content - The new content to replace
|
|
48
|
+
* @param design - The new design to replace
|
|
49
|
+
* @returns The updated tiles response object
|
|
50
|
+
*/
|
|
51
|
+
export function updateTilesSection(
|
|
52
|
+
tilesResponse: any,
|
|
53
|
+
sectionName: string,
|
|
54
|
+
showcaseId: string,
|
|
55
|
+
content: any,
|
|
56
|
+
design: any
|
|
57
|
+
): any {
|
|
58
|
+
const targetPattern = `_${sectionName}_${showcaseId}`;
|
|
59
|
+
|
|
60
|
+
const targetItem = tilesResponse.tiles.find((item: any) => {
|
|
61
|
+
const itemId = item.sourceId;
|
|
62
|
+
if (itemId && typeof itemId === 'string') {
|
|
63
|
+
return itemId.includes(targetPattern);
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (targetItem) {
|
|
69
|
+
// Replace only content and design, preserve all other properties including tils
|
|
70
|
+
targetItem.content = content;
|
|
71
|
+
targetItem.design = design;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return tilesResponse;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Fetches tiles and returns the ID of a section matching the pattern
|
|
79
|
+
* @param sectionName - The section name to match (e.g., 'example-section')
|
|
80
|
+
* @param showcaseId - The showcase ID to match
|
|
81
|
+
* @returns The section ID or null if not found
|
|
82
|
+
*/
|
|
83
|
+
export async function getSectionId(sectionName: string, showcaseId: string): Promise<string | null> {
|
|
84
|
+
try {
|
|
85
|
+
const tilesResponse = await fetchTiles();
|
|
86
|
+
if (!tilesResponse || !tilesResponse.tiles) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const targetPattern = `_${sectionName}_${showcaseId}`;
|
|
91
|
+
|
|
92
|
+
const targetItem = tilesResponse.tiles.find((item: any) => {
|
|
93
|
+
const itemId = item.sourceId;
|
|
94
|
+
return itemId?.includes(targetPattern);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return targetItem ? targetItem.id : null;
|
|
98
|
+
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Error getting section ID:', error);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
/**
|
|
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
|
|
110
|
+
* @param distPath - The dist path for getContentAndDesign
|
|
111
|
+
* @returns The updated custom content response or null if failed
|
|
112
|
+
*/
|
|
113
|
+
export async function updateCustomContent(
|
|
114
|
+
sectionName: string,
|
|
115
|
+
showcaseId: string,
|
|
116
|
+
distPath: string
|
|
117
|
+
): Promise<any> {
|
|
118
|
+
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);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fetch content and design using getContentAndDesign from preview.ts
|
|
128
|
+
const { content, design } = await getContentAndDesign(sectionName, showcaseId, distPath);
|
|
129
|
+
|
|
130
|
+
// Fetch custom content
|
|
131
|
+
const response = await fetch(CUSTOM_CONTENT_URL, {
|
|
132
|
+
method: 'GET',
|
|
133
|
+
headers: {
|
|
134
|
+
'Authorization': BEARER_TOKEN
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
console.error('Failed to fetch custom content:', response.status, response.statusText);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const customContentResponse = await response.json();
|
|
144
|
+
|
|
145
|
+
if (!customContentResponse.customContent || !customContentResponse.customContent.sections) {
|
|
146
|
+
console.error('Invalid custom content response structure');
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Find the section by ID
|
|
151
|
+
const targetSection = customContentResponse.customContent.sections.find((section: any) => {
|
|
152
|
+
return section.state.data.tileId === sectionId;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!targetSection) {
|
|
156
|
+
console.error('Section not found in custom content with ID:', sectionId);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Update content and design
|
|
161
|
+
targetSection.state.data.content = JSON.stringify(content);
|
|
162
|
+
targetSection.state.data.design = JSON.stringify(design);
|
|
163
|
+
|
|
164
|
+
return customContentResponse;
|
|
165
|
+
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Error updating custom content:', error);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
3
170
|
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import {defineConfig} from 'vite';
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
|
+
import { handleApiRequest } from './shared/api-routes.js';
|
|
5
|
+
|
|
6
|
+
// Hardcoded configuration
|
|
7
|
+
const SECTION_NAME = 'example-section';
|
|
8
|
+
const SHOWCASE_ID = '1';
|
|
4
9
|
|
|
5
10
|
export default defineConfig({
|
|
6
11
|
root: '.',
|
|
7
12
|
server: {
|
|
8
13
|
fs: {strict: false},
|
|
14
|
+
cors: true,
|
|
15
|
+
headers: {
|
|
16
|
+
'Access-Control-Allow-Origin': '*',
|
|
17
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
18
|
+
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, Authorization',
|
|
19
|
+
},
|
|
9
20
|
},
|
|
10
21
|
plugins: [
|
|
11
22
|
{
|
|
@@ -44,9 +55,29 @@ export default defineConfig({
|
|
|
44
55
|
server.middlewares.use((req, res, next) => {
|
|
45
56
|
const url = req.url || ''
|
|
46
57
|
|
|
47
|
-
//
|
|
48
|
-
if (url.includes('
|
|
49
|
-
|
|
58
|
+
// Skip HMR and internal Vite requests
|
|
59
|
+
if (url.startsWith('/@') || url.includes('__vite') || url.includes('.hot-update.')) {
|
|
60
|
+
return next()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add CORS headers and disable caching for our API responses
|
|
64
|
+
if (url.startsWith('/api/') || url.startsWith('/chosen-section')) {
|
|
65
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
66
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
|
67
|
+
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization')
|
|
68
|
+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate')
|
|
69
|
+
res.setHeader('Pragma', 'no-cache')
|
|
70
|
+
res.setHeader('Expires', '0')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Handle preflight OPTIONS requests
|
|
74
|
+
if (req.method === 'OPTIONS') {
|
|
75
|
+
res.statusCode = 200
|
|
76
|
+
return res.end()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Let Vite handle all non-API requests for proper HMR
|
|
80
|
+
if (!url.startsWith('/api/') && !url.startsWith('/chosen-section')) {
|
|
50
81
|
return next()
|
|
51
82
|
}
|
|
52
83
|
|
|
@@ -64,6 +95,18 @@ export default defineConfig({
|
|
|
64
95
|
}))
|
|
65
96
|
}
|
|
66
97
|
|
|
98
|
+
// Handle API routes
|
|
99
|
+
if (url.startsWith('/api/v1/')) {
|
|
100
|
+
const sectionName = SECTION_NAME;
|
|
101
|
+
const showcaseId = SHOWCASE_ID;
|
|
102
|
+
|
|
103
|
+
// Delegate to API router
|
|
104
|
+
(async () => {
|
|
105
|
+
await handleApiRequest(req, res, sectionName, showcaseId);
|
|
106
|
+
})();
|
|
107
|
+
return; // Exit early for async handling
|
|
108
|
+
}
|
|
109
|
+
|
|
67
110
|
// all other requests
|
|
68
111
|
next()
|
|
69
112
|
}
|