@gxp-dev/tools 2.0.6 → 2.0.7
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/browser-extensions/README.md +1 -0
- package/browser-extensions/chrome/background.js +857 -0
- package/browser-extensions/chrome/content.js +51 -0
- package/browser-extensions/chrome/devtools.html +9 -0
- package/browser-extensions/chrome/devtools.js +23 -0
- package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
- package/browser-extensions/chrome/inspector.js +1087 -0
- package/browser-extensions/chrome/manifest.json +70 -0
- package/browser-extensions/chrome/panel.html +638 -0
- package/browser-extensions/chrome/panel.js +862 -0
- package/browser-extensions/chrome/popup.html +399 -0
- package/browser-extensions/chrome/popup.js +515 -0
- package/browser-extensions/chrome/rules.json +1 -0
- package/browser-extensions/chrome/test-chrome.html +145 -0
- package/browser-extensions/chrome/test-mixed-content.html +190 -0
- package/browser-extensions/chrome/test-uri-pattern.html +199 -0
- package/browser-extensions/firefox/README.md +134 -0
- package/browser-extensions/firefox/background.js +804 -0
- package/browser-extensions/firefox/content.js +120 -0
- package/browser-extensions/firefox/debug-errors.html +229 -0
- package/browser-extensions/firefox/debug-https.html +113 -0
- package/browser-extensions/firefox/devtools.html +9 -0
- package/browser-extensions/firefox/devtools.js +24 -0
- package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
- package/browser-extensions/firefox/inspector.js +1087 -0
- package/browser-extensions/firefox/manifest.json +67 -0
- package/browser-extensions/firefox/panel.html +638 -0
- package/browser-extensions/firefox/panel.js +862 -0
- package/browser-extensions/firefox/popup.html +525 -0
- package/browser-extensions/firefox/popup.js +536 -0
- package/browser-extensions/firefox/test-gramercy.html +126 -0
- package/browser-extensions/firefox/test-imports.html +58 -0
- package/browser-extensions/firefox/test-masking.html +147 -0
- package/browser-extensions/firefox/test-uri-pattern.html +199 -0
- package/package.json +7 -2
- package/runtime/PortalContainer.vue +326 -0
- package/runtime/dev-tools/DevToolsModal.vue +217 -0
- package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
- package/runtime/dev-tools/MockDataEditor.vue +621 -0
- package/runtime/dev-tools/SocketSimulator.vue +562 -0
- package/runtime/dev-tools/StoreInspector.vue +644 -0
- package/runtime/dev-tools/index.js +6 -0
- package/runtime/gxpStringsPlugin.js +428 -0
- package/runtime/index.html +22 -0
- package/runtime/main.js +32 -0
- package/runtime/mock-api/auth-middleware.js +97 -0
- package/runtime/mock-api/image-generator.js +221 -0
- package/runtime/mock-api/index.js +197 -0
- package/runtime/mock-api/response-generator.js +394 -0
- package/runtime/mock-api/route-generator.js +323 -0
- package/runtime/mock-api/socket-triggers.js +371 -0
- package/runtime/mock-api/spec-loader.js +300 -0
- package/runtime/server.js +180 -0
- package/runtime/stores/gxpPortalConfigStore.js +554 -0
- package/runtime/stores/index.js +6 -0
- package/runtime/vite-inspector-plugin.js +749 -0
- package/runtime/vite-source-tracker-plugin.js +232 -0
- package/runtime/vite.config.js +402 -0
- package/scripts/launch-chrome.js +90 -0
- package/scripts/pack-chrome.js +91 -0
- package/socket-events/AiSessionMessageCreated.json +18 -0
- package/socket-events/SocialStreamPostCreated.json +24 -0
- package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
- package/template/README.md +332 -0
- package/template/app-manifest.json +32 -0
- package/template/dev-assets/images/avatar-placeholder.png +0 -0
- package/template/dev-assets/images/background-placeholder.jpg +0 -0
- package/template/dev-assets/images/banner-placeholder.jpg +0 -0
- package/template/dev-assets/images/icon-placeholder.png +0 -0
- package/template/dev-assets/images/logo-placeholder.png +0 -0
- package/template/dev-assets/images/product-placeholder.jpg +0 -0
- package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
- package/template/env.example +51 -0
- package/template/gitignore +53 -0
- package/template/index.html +22 -0
- package/template/main.js +28 -0
- package/template/src/DemoPage.vue +459 -0
- package/template/src/Plugin.vue +38 -0
- package/template/src/stores/index.js +9 -0
- package/template/src/stores/test-data.json +173 -0
- package/template/theme-layouts/AdditionalStyling.css +0 -0
- package/template/theme-layouts/PrivateLayout.vue +39 -0
- package/template/theme-layouts/PublicLayout.vue +39 -0
- package/template/theme-layouts/SystemLayout.vue +39 -0
- package/template/vite.config.js +333 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GxP Source Tracker Vite Plugin
|
|
3
|
+
*
|
|
4
|
+
* Transforms Vue templates at compile time to add data-gxp-expr attributes
|
|
5
|
+
* to elements that contain template expressions or dynamic directives.
|
|
6
|
+
*
|
|
7
|
+
* This uses a more reliable approach - adding attributes directly to the
|
|
8
|
+
* elements rather than using comments.
|
|
9
|
+
*
|
|
10
|
+
* Example transformations:
|
|
11
|
+
*
|
|
12
|
+
* <span>{{ title }}</span>
|
|
13
|
+
* Becomes:
|
|
14
|
+
* <span data-gxp-expr="title">{{ title }}</span>
|
|
15
|
+
*
|
|
16
|
+
* <div v-html="content"></div>
|
|
17
|
+
* Becomes:
|
|
18
|
+
* <div v-html="content" data-gxp-expr="v-html:content"></div>
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create the source tracker plugin
|
|
23
|
+
*/
|
|
24
|
+
export function gxpSourceTrackerPlugin(options = {}) {
|
|
25
|
+
const {
|
|
26
|
+
enabled = true,
|
|
27
|
+
attrName = 'data-gxp-expr'
|
|
28
|
+
} = options;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
name: 'gxp-source-tracker',
|
|
32
|
+
enforce: 'pre',
|
|
33
|
+
|
|
34
|
+
apply(config, { command }) {
|
|
35
|
+
return enabled && command === 'serve';
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
transform(code, id) {
|
|
39
|
+
if (!id.endsWith('.vue')) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const templateMatch = code.match(/<template>([\s\S]*?)<\/template>/);
|
|
44
|
+
if (!templateMatch) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Debug: log which file we're processing
|
|
49
|
+
const fileName = id.split('/').pop();
|
|
50
|
+
console.log(`[GxP Source Tracker] Processing: ${fileName}`);
|
|
51
|
+
|
|
52
|
+
let template = templateMatch[1];
|
|
53
|
+
let modified = false;
|
|
54
|
+
|
|
55
|
+
// Track which elements we've already processed to avoid duplicates
|
|
56
|
+
const processed = new Set();
|
|
57
|
+
|
|
58
|
+
// Process each element that has {{ expression }} as its content
|
|
59
|
+
// We need to find the element that DIRECTLY contains the expression
|
|
60
|
+
// Strategy: Find all {{ expr }} and trace back to their parent element
|
|
61
|
+
|
|
62
|
+
// Strategy: Find each {{ expression }} and trace back to find its parent element
|
|
63
|
+
// This handles cases where expressions are nested or mixed with other content
|
|
64
|
+
|
|
65
|
+
const exprPattern = /\{\{([\s\S]*?)\}\}/g;
|
|
66
|
+
let match;
|
|
67
|
+
|
|
68
|
+
// Map to track which elements we've already added attributes to
|
|
69
|
+
// Key: element start position, Value: array of expressions
|
|
70
|
+
const elementExpressions = new Map();
|
|
71
|
+
|
|
72
|
+
while ((match = exprPattern.exec(template)) !== null) {
|
|
73
|
+
const exprStart = match.index;
|
|
74
|
+
const expression = match[1].trim();
|
|
75
|
+
|
|
76
|
+
// Find the opening tag that contains this expression
|
|
77
|
+
// Look backwards from the expression to find the nearest unclosed tag
|
|
78
|
+
const beforeExpr = template.substring(0, exprStart);
|
|
79
|
+
|
|
80
|
+
// Find the last opening tag before this expression
|
|
81
|
+
// We need to find a tag that hasn't been closed yet
|
|
82
|
+
let depth = 0;
|
|
83
|
+
let tagMatch;
|
|
84
|
+
let parentTagInfo = null;
|
|
85
|
+
|
|
86
|
+
// Find all tags before this expression
|
|
87
|
+
const tagPattern = /<\/?([a-zA-Z][a-zA-Z0-9-]*)([^>]*)>/g;
|
|
88
|
+
const tags = [];
|
|
89
|
+
|
|
90
|
+
while ((tagMatch = tagPattern.exec(beforeExpr)) !== null) {
|
|
91
|
+
const isClosing = tagMatch[0].startsWith('</');
|
|
92
|
+
const isSelfClosing = tagMatch[0].endsWith('/>') ||
|
|
93
|
+
['br', 'hr', 'img', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'param', 'source', 'track', 'wbr'].includes(tagMatch[1].toLowerCase());
|
|
94
|
+
|
|
95
|
+
tags.push({
|
|
96
|
+
tagName: tagMatch[1],
|
|
97
|
+
attrs: tagMatch[2],
|
|
98
|
+
start: tagMatch.index,
|
|
99
|
+
fullMatch: tagMatch[0],
|
|
100
|
+
isClosing,
|
|
101
|
+
isSelfClosing
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Walk through tags to find the immediate parent (last unclosed tag)
|
|
106
|
+
const stack = [];
|
|
107
|
+
for (const tag of tags) {
|
|
108
|
+
if (tag.isSelfClosing) continue;
|
|
109
|
+
|
|
110
|
+
if (tag.isClosing) {
|
|
111
|
+
// Pop from stack
|
|
112
|
+
if (stack.length > 0 && stack[stack.length - 1].tagName.toLowerCase() === tag.tagName.toLowerCase()) {
|
|
113
|
+
stack.pop();
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
stack.push(tag);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The last item in the stack is our parent element
|
|
121
|
+
if (stack.length > 0) {
|
|
122
|
+
parentTagInfo = stack[stack.length - 1];
|
|
123
|
+
|
|
124
|
+
// Skip script/style tags
|
|
125
|
+
if (['script', 'style'].includes(parentTagInfo.tagName.toLowerCase())) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Skip if already has data-gxp-expr in the original attrs
|
|
130
|
+
if (parentTagInfo.attrs.includes(attrName)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Add this expression to the parent element's list
|
|
135
|
+
const key = parentTagInfo.start;
|
|
136
|
+
if (!elementExpressions.has(key)) {
|
|
137
|
+
elementExpressions.set(key, {
|
|
138
|
+
tagInfo: parentTagInfo,
|
|
139
|
+
expressions: []
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
elementExpressions.get(key).expressions.push(expression);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Now build replacements for each element that has expressions
|
|
147
|
+
const replacements = [];
|
|
148
|
+
|
|
149
|
+
for (const [start, data] of elementExpressions) {
|
|
150
|
+
const { tagInfo, expressions } = data;
|
|
151
|
+
|
|
152
|
+
// Only add expressions that are direct children (not from nested elements)
|
|
153
|
+
// For now, take all unique expressions
|
|
154
|
+
const uniqueExprs = [...new Set(expressions)];
|
|
155
|
+
const exprValue = uniqueExprs.join('; ');
|
|
156
|
+
|
|
157
|
+
const oldOpenTag = tagInfo.fullMatch;
|
|
158
|
+
// Insert the attribute before the closing >
|
|
159
|
+
const newOpenTag = oldOpenTag.replace(/>$/, ` ${attrName}="${escapeAttr(exprValue)}">`);
|
|
160
|
+
|
|
161
|
+
replacements.push({
|
|
162
|
+
start: tagInfo.start,
|
|
163
|
+
oldText: oldOpenTag,
|
|
164
|
+
newText: newOpenTag
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
modified = true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Apply replacements in reverse order to preserve indices
|
|
171
|
+
replacements.sort((a, b) => b.start - a.start);
|
|
172
|
+
for (const r of replacements) {
|
|
173
|
+
template = template.substring(0, r.start) +
|
|
174
|
+
r.newText +
|
|
175
|
+
template.substring(r.start + r.oldText.length);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Also handle v-html, v-text directives
|
|
179
|
+
// Add data-gxp-expr to elements that have these
|
|
180
|
+
template = template.replace(
|
|
181
|
+
/<([a-zA-Z][a-zA-Z0-9-]*)([^>]*)(v-html)="([^"]+)"([^>]*)>/g,
|
|
182
|
+
(match, tag, before, directive, expr, after) => {
|
|
183
|
+
if (match.includes(attrName)) return match;
|
|
184
|
+
return `<${tag}${before}${directive}="${expr}"${after} ${attrName}="${escapeAttr('v-html:' + expr)}">`;
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
template = template.replace(
|
|
189
|
+
/<([a-zA-Z][a-zA-Z0-9-]*)([^>]*)(v-text)="([^"]+)"([^>]*)>/g,
|
|
190
|
+
(match, tag, before, directive, expr, after) => {
|
|
191
|
+
if (match.includes(attrName)) return match;
|
|
192
|
+
modified = true;
|
|
193
|
+
return `<${tag}${before}${directive}="${expr}"${after} ${attrName}="${escapeAttr('v-text:' + expr)}">`;
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Handle :textContent binding
|
|
198
|
+
template = template.replace(
|
|
199
|
+
/<([a-zA-Z][a-zA-Z0-9-]*)([^>]*):textContent="([^"]+)"([^>]*)>/g,
|
|
200
|
+
(match, tag, before, expr, after) => {
|
|
201
|
+
if (match.includes(attrName)) return match;
|
|
202
|
+
modified = true;
|
|
203
|
+
return `<${tag}${before}:textContent="${expr}"${after} ${attrName}="${escapeAttr(':textContent:' + expr)}">`;
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (!modified) {
|
|
208
|
+
console.log(`[GxP Source Tracker] No expressions found in: ${fileName}`);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(`[GxP Source Tracker] Added data-gxp-expr to ${elementExpressions.size} elements in: ${fileName}`);
|
|
213
|
+
const newCode = code.replace(/<template>[\s\S]*?<\/template>/, `<template>${template}</template>`);
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
code: newCode,
|
|
217
|
+
map: null
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function escapeAttr(str) {
|
|
224
|
+
return str
|
|
225
|
+
.replace(/&/g, '&')
|
|
226
|
+
.replace(/"/g, '"')
|
|
227
|
+
.replace(/'/g, ''')
|
|
228
|
+
.replace(/</g, '<')
|
|
229
|
+
.replace(/>/g, '>');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default gxpSourceTrackerPlugin;
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { defineConfig, loadEnv } from "vite";
|
|
2
|
+
import vue from "@vitejs/plugin-vue";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import externalGlobals from "rollup-plugin-external-globals";
|
|
7
|
+
import { gxpInspectorPlugin } from "./vite-inspector-plugin.js";
|
|
8
|
+
import { gxpSourceTrackerPlugin } from "./vite-source-tracker-plugin.js";
|
|
9
|
+
|
|
10
|
+
// Environment URL configuration for API proxy
|
|
11
|
+
const ENVIRONMENT_URLS = {
|
|
12
|
+
production: "https://api.gramercy.cloud",
|
|
13
|
+
staging: "https://api.efz-staging.env.eventfinity.app",
|
|
14
|
+
testing: "https://api.zenith-develop-testing.env.eventfinity.app",
|
|
15
|
+
develop: "https://api.zenith-develop.env.eventfinity.app",
|
|
16
|
+
local: "https://dashboard.eventfinity.test",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the API proxy target URL based on environment
|
|
21
|
+
*/
|
|
22
|
+
function getApiProxyTarget(env) {
|
|
23
|
+
const apiEnv = env.API_ENV || "mock";
|
|
24
|
+
|
|
25
|
+
// Custom URL takes precedence
|
|
26
|
+
if (env.API_BASE_URL) {
|
|
27
|
+
return env.API_BASE_URL;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Mock uses local mock-api server (no proxy needed, handled separately)
|
|
31
|
+
if (apiEnv === "mock") {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Look up environment URL
|
|
36
|
+
return ENVIRONMENT_URLS[apiEnv] || ENVIRONMENT_URLS.production;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the library name from package.json
|
|
41
|
+
*/
|
|
42
|
+
function getLibName() {
|
|
43
|
+
try {
|
|
44
|
+
const packageJsonPath = path.resolve(process.cwd(), "package.json");
|
|
45
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
46
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
47
|
+
// Convert package name to a valid JS identifier
|
|
48
|
+
// e.g., "@company/my-plugin" -> "MyPlugin"
|
|
49
|
+
const name = packageJson.name || "Plugin";
|
|
50
|
+
return name
|
|
51
|
+
.replace(/[@\/\-]/g, " ")
|
|
52
|
+
.replace(/\b\w/g, (l) => l.toUpperCase())
|
|
53
|
+
.replace(/\s/g, "");
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn("Could not read package.json, using default lib name");
|
|
57
|
+
}
|
|
58
|
+
return "Plugin";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Setup HTTPS configuration if certificates are available
|
|
63
|
+
*/
|
|
64
|
+
function getHttpsConfig(env) {
|
|
65
|
+
const useHttps = env.USE_HTTPS === "true";
|
|
66
|
+
const certPath = env.CERT_PATH;
|
|
67
|
+
const keyPath = env.KEY_PATH;
|
|
68
|
+
|
|
69
|
+
if (!useHttps || !certPath || !keyPath) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Resolve paths relative to project root
|
|
74
|
+
const resolvedCertPath = path.resolve(process.cwd(), certPath);
|
|
75
|
+
const resolvedKeyPath = path.resolve(process.cwd(), keyPath);
|
|
76
|
+
|
|
77
|
+
// Check if certificate files exist
|
|
78
|
+
if (!fs.existsSync(resolvedCertPath) || !fs.existsSync(resolvedKeyPath)) {
|
|
79
|
+
console.warn("⚠ SSL certificate files not found, falling back to HTTP");
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
return {
|
|
85
|
+
key: fs.readFileSync(resolvedKeyPath),
|
|
86
|
+
cert: fs.readFileSync(resolvedCertPath),
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn("⚠ Failed to read SSL certificates, falling back to HTTP");
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Find the gx-devtools package directory (works for both local and global installs)
|
|
96
|
+
*/
|
|
97
|
+
function findToolkitPath() {
|
|
98
|
+
const packageName = "@gramercytech/gx-devtools";
|
|
99
|
+
|
|
100
|
+
// Try local node_modules first
|
|
101
|
+
const localPath = path.resolve(process.cwd(), "node_modules", packageName);
|
|
102
|
+
if (fs.existsSync(localPath)) {
|
|
103
|
+
return localPath;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Try to find via require.resolve
|
|
107
|
+
try {
|
|
108
|
+
const pkgPath = require.resolve(`${packageName}/package.json`);
|
|
109
|
+
return path.dirname(pkgPath);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// Fallback: assume we're in the toolkit itself during development
|
|
112
|
+
return process.cwd();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if a file exists locally in the project
|
|
118
|
+
*/
|
|
119
|
+
function hasLocalFile(fileName) {
|
|
120
|
+
const localPath = path.resolve(process.cwd(), fileName);
|
|
121
|
+
return fs.existsSync(localPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default defineConfig(({ mode }) => {
|
|
125
|
+
// Load environment variables from project directory
|
|
126
|
+
const env = loadEnv(mode, process.cwd(), "");
|
|
127
|
+
|
|
128
|
+
// Get lib name from package.json
|
|
129
|
+
const libName = getLibName();
|
|
130
|
+
|
|
131
|
+
// Find the toolkit path for runtime imports
|
|
132
|
+
const toolkitPath = findToolkitPath();
|
|
133
|
+
const runtimeDir = path.resolve(toolkitPath, "runtime");
|
|
134
|
+
|
|
135
|
+
// Check for local dev files
|
|
136
|
+
const hasLocalIndexHtml = hasLocalFile("index.html");
|
|
137
|
+
const hasLocalMainJs = hasLocalFile("main.js");
|
|
138
|
+
|
|
139
|
+
// Log which files are being used
|
|
140
|
+
console.log(`📄 index.html: ${hasLocalIndexHtml ? "local" : "runtime"}`);
|
|
141
|
+
console.log(`📄 main.js: ${hasLocalMainJs ? "local" : "runtime"}`);
|
|
142
|
+
|
|
143
|
+
// Create plugin to serve runtime files (index.html and main.js) if no local ones exist
|
|
144
|
+
const runtimeFilesPlugin = {
|
|
145
|
+
name: "runtime-files",
|
|
146
|
+
configureServer(server) {
|
|
147
|
+
server.middlewares.use((req, res, next) => {
|
|
148
|
+
// Serve runtime index.html for root requests (if no local index.html)
|
|
149
|
+
if (
|
|
150
|
+
!hasLocalIndexHtml &&
|
|
151
|
+
(req.url === "/" || req.url === "/index.html")
|
|
152
|
+
) {
|
|
153
|
+
const runtimeIndexPath = path.join(runtimeDir, "index.html");
|
|
154
|
+
if (fs.existsSync(runtimeIndexPath)) {
|
|
155
|
+
// Read and transform the runtime index.html
|
|
156
|
+
server
|
|
157
|
+
.transformIndexHtml(
|
|
158
|
+
req.url,
|
|
159
|
+
fs.readFileSync(runtimeIndexPath, "utf-8")
|
|
160
|
+
)
|
|
161
|
+
.then((html) => {
|
|
162
|
+
res.setHeader("Content-Type", "text/html");
|
|
163
|
+
res.end(html);
|
|
164
|
+
})
|
|
165
|
+
.catch((err) => {
|
|
166
|
+
console.error("Error transforming index.html:", err);
|
|
167
|
+
next(err);
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Serve runtime main.js for @gx-runtime/main.js requests (if no local main.js)
|
|
174
|
+
if (
|
|
175
|
+
!hasLocalMainJs &&
|
|
176
|
+
(req.url === "/@gx-runtime/main.js" ||
|
|
177
|
+
req.url?.startsWith("/@gx-runtime/main.js?"))
|
|
178
|
+
) {
|
|
179
|
+
const runtimeMainPath = path.join(runtimeDir, "main.js");
|
|
180
|
+
if (fs.existsSync(runtimeMainPath)) {
|
|
181
|
+
// Use the real path to handle symlinks correctly
|
|
182
|
+
const realMainPath = fs.realpathSync(runtimeMainPath);
|
|
183
|
+
server
|
|
184
|
+
.transformRequest(realMainPath)
|
|
185
|
+
.then((result) => {
|
|
186
|
+
if (result) {
|
|
187
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
188
|
+
res.end(result.code);
|
|
189
|
+
} else {
|
|
190
|
+
next();
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
.catch((err) => {
|
|
194
|
+
console.error("Error transforming main.js:", err);
|
|
195
|
+
next(err);
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
next();
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Determine if HTTPS is enabled
|
|
207
|
+
const useHttps = getHttpsConfig(env) !== false;
|
|
208
|
+
|
|
209
|
+
// Get API proxy target for non-mock environments
|
|
210
|
+
const apiProxyTarget = getApiProxyTarget(env);
|
|
211
|
+
if (apiProxyTarget) {
|
|
212
|
+
console.log(`🔀 API Proxy: /api-proxy -> ${apiProxyTarget}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
// Root is always the project directory
|
|
217
|
+
root: process.cwd(),
|
|
218
|
+
// Expose environment variables to the browser
|
|
219
|
+
define: {
|
|
220
|
+
"import.meta.env.VITE_API_ENV": JSON.stringify(env.API_ENV || "mock"),
|
|
221
|
+
"import.meta.env.VITE_API_BASE_URL": JSON.stringify(
|
|
222
|
+
env.API_BASE_URL || ""
|
|
223
|
+
),
|
|
224
|
+
"import.meta.env.VITE_API_KEY": JSON.stringify(env.API_KEY || ""),
|
|
225
|
+
"import.meta.env.VITE_API_PROJECT_ID": JSON.stringify(
|
|
226
|
+
env.API_PROJECT_ID || ""
|
|
227
|
+
),
|
|
228
|
+
"import.meta.env.VITE_USE_HTTPS": JSON.stringify(
|
|
229
|
+
useHttps ? "true" : "false"
|
|
230
|
+
),
|
|
231
|
+
"import.meta.env.VITE_NODE_PORT": JSON.stringify(env.NODE_PORT || "3060"),
|
|
232
|
+
"import.meta.env.VITE_SOCKET_IO_PORT": JSON.stringify(
|
|
233
|
+
env.SOCKET_IO_PORT || "3069"
|
|
234
|
+
),
|
|
235
|
+
},
|
|
236
|
+
plugins: [
|
|
237
|
+
runtimeFilesPlugin,
|
|
238
|
+
// Source tracker must run BEFORE vue() to transform templates before compilation
|
|
239
|
+
gxpSourceTrackerPlugin(),
|
|
240
|
+
vue(),
|
|
241
|
+
// GxP Inspector plugin for browser extension integration
|
|
242
|
+
gxpInspectorPlugin(),
|
|
243
|
+
externalGlobals(
|
|
244
|
+
{
|
|
245
|
+
vue: "Vue",
|
|
246
|
+
pinia: "Pinia",
|
|
247
|
+
"@/stores/gxpPortalConfigStore":
|
|
248
|
+
"(window.useGxpStore || (() => { console.warn('useGxpStore not found on window, using fallback'); return {}; }))",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
include: ["src/**"],
|
|
252
|
+
}
|
|
253
|
+
),
|
|
254
|
+
// Custom request logging and CORS plugin
|
|
255
|
+
{
|
|
256
|
+
name: "request-logger-cors",
|
|
257
|
+
configureServer(server) {
|
|
258
|
+
server.middlewares.use((req, res, next) => {
|
|
259
|
+
const start = Date.now();
|
|
260
|
+
const originalEnd = res.end;
|
|
261
|
+
|
|
262
|
+
// Add CORS headers to all responses
|
|
263
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
264
|
+
res.setHeader(
|
|
265
|
+
"Access-Control-Allow-Methods",
|
|
266
|
+
"GET, POST, PUT, DELETE, OPTIONS"
|
|
267
|
+
);
|
|
268
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
269
|
+
res.setHeader("Access-Control-Allow-Credentials", "false");
|
|
270
|
+
|
|
271
|
+
// Handle preflight requests
|
|
272
|
+
if (req.method === "OPTIONS") {
|
|
273
|
+
res.statusCode = 200;
|
|
274
|
+
res.end();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
res.end = function (...args) {
|
|
279
|
+
const duration = Date.now() - start;
|
|
280
|
+
const status = res.statusCode;
|
|
281
|
+
const method = req.method;
|
|
282
|
+
const url = req.url;
|
|
283
|
+
const referer = req.headers.referer || "direct";
|
|
284
|
+
const origin = req.headers.origin || "unknown";
|
|
285
|
+
|
|
286
|
+
console.log(
|
|
287
|
+
`[${new Date().toISOString()}] ${method} ${url} ${status} (${duration}ms) - Origin: ${origin} - Referer: ${referer}`
|
|
288
|
+
);
|
|
289
|
+
originalEnd.apply(this, args);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
next();
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
logLevel: env.NODE_LOG_LEVEL || "error",
|
|
298
|
+
clearScreen: false,
|
|
299
|
+
server: {
|
|
300
|
+
port: parseInt(env.NODE_PORT) || 3060,
|
|
301
|
+
strictPort: true,
|
|
302
|
+
https: getHttpsConfig(env),
|
|
303
|
+
cors: {
|
|
304
|
+
origin: "*",
|
|
305
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
306
|
+
allowedHeaders: ["*"],
|
|
307
|
+
credentials: false,
|
|
308
|
+
},
|
|
309
|
+
hmr: {
|
|
310
|
+
clientPort:
|
|
311
|
+
parseInt(env.CLIENT_PORT) || parseInt(env.NODE_PORT) || 3060,
|
|
312
|
+
},
|
|
313
|
+
host: true, // Allow access from network
|
|
314
|
+
// Allow serving files from the toolkit runtime directory
|
|
315
|
+
// Also resolve symlinks to allow files from the real path
|
|
316
|
+
fs: {
|
|
317
|
+
allow: [process.cwd(), toolkitPath, fs.realpathSync(toolkitPath)],
|
|
318
|
+
},
|
|
319
|
+
// API proxy for non-mock environments
|
|
320
|
+
proxy: apiProxyTarget
|
|
321
|
+
? {
|
|
322
|
+
"/api-proxy": {
|
|
323
|
+
target: apiProxyTarget,
|
|
324
|
+
changeOrigin: true,
|
|
325
|
+
rewrite: (path) => path.replace(/^\/api-proxy/, ""),
|
|
326
|
+
secure: false,
|
|
327
|
+
configure: (proxy, options) => {
|
|
328
|
+
proxy.on("proxyReq", (proxyReq, req) => {
|
|
329
|
+
// Forward the API key as Authorization header if set
|
|
330
|
+
const apiKey = env.API_KEY;
|
|
331
|
+
if (apiKey) {
|
|
332
|
+
proxyReq.setHeader("Authorization", `Bearer ${apiKey}`);
|
|
333
|
+
}
|
|
334
|
+
console.log(
|
|
335
|
+
`[API Proxy] ${req.method} ${
|
|
336
|
+
req.url
|
|
337
|
+
} -> ${apiProxyTarget}${req.url.replace(
|
|
338
|
+
/^\/api-proxy/,
|
|
339
|
+
""
|
|
340
|
+
)}`
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
proxy.on("proxyRes", (proxyRes, req) => {
|
|
344
|
+
console.log(
|
|
345
|
+
`[API Proxy] ${req.method} ${req.url} <- ${proxyRes.statusCode}`
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
proxy.on("error", (err, req) => {
|
|
349
|
+
console.error(`[API Proxy] Error: ${err.message}`);
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
: {},
|
|
355
|
+
},
|
|
356
|
+
build: {
|
|
357
|
+
// Build output goes to project directory
|
|
358
|
+
outDir: path.resolve(process.cwd(), "dist"),
|
|
359
|
+
lib: {
|
|
360
|
+
entry: [
|
|
361
|
+
path.resolve(process.cwd(), env.COMPONENT_PATH || "./src/Plugin.vue"),
|
|
362
|
+
],
|
|
363
|
+
name: libName,
|
|
364
|
+
fileName: (format) => `plugin.${format}.js`,
|
|
365
|
+
formats: ["es"],
|
|
366
|
+
},
|
|
367
|
+
rollupOptions: {
|
|
368
|
+
// Make sure Vue and Pinia are treated as external dependencies
|
|
369
|
+
external: ["vue", "pinia"],
|
|
370
|
+
output: {
|
|
371
|
+
globals: {
|
|
372
|
+
vue: "Vue",
|
|
373
|
+
pinia: "Pinia",
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
resolve: {
|
|
379
|
+
alias: {
|
|
380
|
+
// Client project's source directory
|
|
381
|
+
"@": path.resolve(process.cwd(), "src"),
|
|
382
|
+
// Theme layouts in client project
|
|
383
|
+
"@layouts": path.resolve(process.cwd(), "theme-layouts"),
|
|
384
|
+
// GxP Toolkit runtime (PortalContainer, etc.) - from node_modules
|
|
385
|
+
"@gx-runtime": runtimeDir,
|
|
386
|
+
// Ensure single Vue and Pinia instances
|
|
387
|
+
vue: path.resolve(process.cwd(), "node_modules/vue"),
|
|
388
|
+
pinia: path.resolve(process.cwd(), "node_modules/pinia"),
|
|
389
|
+
},
|
|
390
|
+
// Dedupe Vue and Pinia to ensure only one instance is used
|
|
391
|
+
dedupe: ["vue", "pinia"],
|
|
392
|
+
},
|
|
393
|
+
// Force Vite to pre-bundle these dependencies to ensure single instances
|
|
394
|
+
optimizeDeps: {
|
|
395
|
+
include: ["vue", "pinia"],
|
|
396
|
+
},
|
|
397
|
+
// SSR configuration to handle externals properly
|
|
398
|
+
ssr: {
|
|
399
|
+
external: ["vue", "pinia"],
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
});
|