@gxp-dev/tools 2.0.6 → 2.0.8

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 (100) hide show
  1. package/bin/lib/commands/build.js +18 -12
  2. package/browser-extensions/README.md +1 -0
  3. package/browser-extensions/chrome/background.js +857 -0
  4. package/browser-extensions/chrome/content.js +51 -0
  5. package/browser-extensions/chrome/devtools.html +9 -0
  6. package/browser-extensions/chrome/devtools.js +23 -0
  7. package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
  8. package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
  9. package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
  10. package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
  11. package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
  12. package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
  13. package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
  14. package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
  15. package/browser-extensions/chrome/inspector.js +1087 -0
  16. package/browser-extensions/chrome/manifest.json +70 -0
  17. package/browser-extensions/chrome/panel.html +638 -0
  18. package/browser-extensions/chrome/panel.js +862 -0
  19. package/browser-extensions/chrome/popup.html +399 -0
  20. package/browser-extensions/chrome/popup.js +515 -0
  21. package/browser-extensions/chrome/rules.json +1 -0
  22. package/browser-extensions/chrome/test-chrome.html +145 -0
  23. package/browser-extensions/chrome/test-mixed-content.html +190 -0
  24. package/browser-extensions/chrome/test-uri-pattern.html +199 -0
  25. package/browser-extensions/firefox/README.md +134 -0
  26. package/browser-extensions/firefox/background.js +804 -0
  27. package/browser-extensions/firefox/content.js +120 -0
  28. package/browser-extensions/firefox/debug-errors.html +229 -0
  29. package/browser-extensions/firefox/debug-https.html +113 -0
  30. package/browser-extensions/firefox/devtools.html +9 -0
  31. package/browser-extensions/firefox/devtools.js +24 -0
  32. package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
  33. package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
  34. package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
  35. package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
  36. package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
  37. package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
  38. package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
  39. package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
  40. package/browser-extensions/firefox/inspector.js +1087 -0
  41. package/browser-extensions/firefox/manifest.json +67 -0
  42. package/browser-extensions/firefox/panel.html +638 -0
  43. package/browser-extensions/firefox/panel.js +862 -0
  44. package/browser-extensions/firefox/popup.html +525 -0
  45. package/browser-extensions/firefox/popup.js +536 -0
  46. package/browser-extensions/firefox/test-gramercy.html +126 -0
  47. package/browser-extensions/firefox/test-imports.html +58 -0
  48. package/browser-extensions/firefox/test-masking.html +147 -0
  49. package/browser-extensions/firefox/test-uri-pattern.html +199 -0
  50. package/package.json +7 -2
  51. package/runtime/PortalContainer.vue +326 -0
  52. package/runtime/dev-tools/DevToolsModal.vue +217 -0
  53. package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
  54. package/runtime/dev-tools/MockDataEditor.vue +621 -0
  55. package/runtime/dev-tools/SocketSimulator.vue +562 -0
  56. package/runtime/dev-tools/StoreInspector.vue +644 -0
  57. package/runtime/dev-tools/index.js +6 -0
  58. package/runtime/gxpStringsPlugin.js +428 -0
  59. package/runtime/index.html +22 -0
  60. package/runtime/main.js +32 -0
  61. package/runtime/mock-api/auth-middleware.js +97 -0
  62. package/runtime/mock-api/image-generator.js +221 -0
  63. package/runtime/mock-api/index.js +197 -0
  64. package/runtime/mock-api/response-generator.js +394 -0
  65. package/runtime/mock-api/route-generator.js +323 -0
  66. package/runtime/mock-api/socket-triggers.js +371 -0
  67. package/runtime/mock-api/spec-loader.js +300 -0
  68. package/runtime/server.js +180 -0
  69. package/runtime/stores/gxpPortalConfigStore.js +554 -0
  70. package/runtime/stores/index.js +6 -0
  71. package/runtime/vite-inspector-plugin.js +749 -0
  72. package/runtime/vite-source-tracker-plugin.js +232 -0
  73. package/runtime/vite.config.js +402 -0
  74. package/scripts/launch-chrome.js +90 -0
  75. package/scripts/pack-chrome.js +91 -0
  76. package/socket-events/AiSessionMessageCreated.json +18 -0
  77. package/socket-events/SocialStreamPostCreated.json +24 -0
  78. package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
  79. package/template/README.md +332 -0
  80. package/template/app-manifest.json +32 -0
  81. package/template/dev-assets/images/avatar-placeholder.png +0 -0
  82. package/template/dev-assets/images/background-placeholder.jpg +0 -0
  83. package/template/dev-assets/images/banner-placeholder.jpg +0 -0
  84. package/template/dev-assets/images/icon-placeholder.png +0 -0
  85. package/template/dev-assets/images/logo-placeholder.png +0 -0
  86. package/template/dev-assets/images/product-placeholder.jpg +0 -0
  87. package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
  88. package/template/env.example +51 -0
  89. package/template/gitignore +53 -0
  90. package/template/index.html +22 -0
  91. package/template/main.js +28 -0
  92. package/template/src/DemoPage.vue +459 -0
  93. package/template/src/Plugin.vue +38 -0
  94. package/template/src/stores/index.js +9 -0
  95. package/template/src/stores/test-data.json +173 -0
  96. package/template/theme-layouts/AdditionalStyling.css +0 -0
  97. package/template/theme-layouts/PrivateLayout.vue +39 -0
  98. package/template/theme-layouts/PublicLayout.vue +39 -0
  99. package/template/theme-layouts/SystemLayout.vue +39 -0
  100. 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, '&amp;')
226
+ .replace(/"/g, '&quot;')
227
+ .replace(/'/g, '&#39;')
228
+ .replace(/</g, '&lt;')
229
+ .replace(/>/g, '&gt;');
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
+ });