@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,749 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GxP Component Inspector Vite Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides API endpoints for the browser extension to:
|
|
5
|
+
* - Get component information
|
|
6
|
+
* - Extract strings to the stringsList
|
|
7
|
+
* - Update Vue source files
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate a key from text content
|
|
15
|
+
* @param {string} text - The text to convert to a key
|
|
16
|
+
* @returns {string} - A valid key for stringsList
|
|
17
|
+
*/
|
|
18
|
+
function textToKey(text) {
|
|
19
|
+
return text
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
22
|
+
.replace(/\s+/g, '_')
|
|
23
|
+
.substring(0, 40)
|
|
24
|
+
.replace(/_+$/, ''); // Remove trailing underscores
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse JSON body from request
|
|
29
|
+
*/
|
|
30
|
+
async function parseBody(req) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
let body = '';
|
|
33
|
+
req.on('data', chunk => body += chunk);
|
|
34
|
+
req.on('end', () => {
|
|
35
|
+
try {
|
|
36
|
+
resolve(JSON.parse(body));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
reject(new Error('Invalid JSON'));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
req.on('error', reject);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Send JSON response
|
|
47
|
+
*/
|
|
48
|
+
function sendJson(res, data, status = 200) {
|
|
49
|
+
res.statusCode = status;
|
|
50
|
+
res.setHeader('Content-Type', 'application/json');
|
|
51
|
+
res.end(JSON.stringify(data));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create the inspector plugin
|
|
56
|
+
*/
|
|
57
|
+
export function gxpInspectorPlugin() {
|
|
58
|
+
return {
|
|
59
|
+
name: 'gxp-inspector',
|
|
60
|
+
|
|
61
|
+
configureServer(server) {
|
|
62
|
+
// API endpoint prefix
|
|
63
|
+
const API_PREFIX = '/__gxp-inspector';
|
|
64
|
+
|
|
65
|
+
// Watch for app-manifest.json changes and trigger HMR
|
|
66
|
+
const manifestPath = path.join(process.cwd(), 'app-manifest.json');
|
|
67
|
+
let manifestWatcher = null;
|
|
68
|
+
|
|
69
|
+
// Setup manifest file watcher
|
|
70
|
+
try {
|
|
71
|
+
// Use chokidar if available (Vite uses it internally)
|
|
72
|
+
if (server.watcher) {
|
|
73
|
+
server.watcher.add(manifestPath);
|
|
74
|
+
server.watcher.on('change', (changedPath) => {
|
|
75
|
+
if (changedPath === manifestPath || changedPath.endsWith('app-manifest.json')) {
|
|
76
|
+
console.log('[GxP Inspector] app-manifest.json changed, sending HMR update');
|
|
77
|
+
try {
|
|
78
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
79
|
+
// Send custom HMR event to all connected clients
|
|
80
|
+
server.ws.send({
|
|
81
|
+
type: 'custom',
|
|
82
|
+
event: 'gxp:manifest-update',
|
|
83
|
+
data: manifest
|
|
84
|
+
});
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.warn('[GxP Inspector] Could not parse app-manifest.json:', e.message);
|
|
87
|
+
// Send reload signal if parse failed
|
|
88
|
+
server.ws.send({
|
|
89
|
+
type: 'custom',
|
|
90
|
+
event: 'gxp:manifest-reload',
|
|
91
|
+
data: {}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
console.log('[GxP Inspector] Watching app-manifest.json for changes');
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.warn('[GxP Inspector] Could not setup manifest watcher:', e.message);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
server.middlewares.use(async (req, res, next) => {
|
|
103
|
+
// Only handle our API endpoints
|
|
104
|
+
if (!req.url?.startsWith(API_PREFIX)) {
|
|
105
|
+
return next();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const endpoint = req.url.replace(API_PREFIX, '').split('?')[0];
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// GET /ping - Health check
|
|
112
|
+
if (req.method === 'GET' && endpoint === '/ping') {
|
|
113
|
+
return sendJson(res, {
|
|
114
|
+
success: true,
|
|
115
|
+
status: 'ok',
|
|
116
|
+
version: '1.0.0',
|
|
117
|
+
projectRoot: process.cwd()
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// GET /strings - Get current strings from app-manifest.json
|
|
122
|
+
if (req.method === 'GET' && endpoint === '/strings') {
|
|
123
|
+
const manifestPath = path.join(process.cwd(), 'app-manifest.json');
|
|
124
|
+
|
|
125
|
+
if (!fs.existsSync(manifestPath)) {
|
|
126
|
+
// Return empty strings if manifest doesn't exist yet
|
|
127
|
+
return sendJson(res, {
|
|
128
|
+
success: true,
|
|
129
|
+
stringsList: {}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
134
|
+
return sendJson(res, {
|
|
135
|
+
success: true,
|
|
136
|
+
stringsList: manifest.strings?.default || {}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// POST /lookup-string - Check if a text value exists in manifest and return its key
|
|
141
|
+
if (req.method === 'POST' && endpoint === '/lookup-string') {
|
|
142
|
+
const body = await parseBody(req);
|
|
143
|
+
const { text, filePath } = body;
|
|
144
|
+
|
|
145
|
+
if (!text) {
|
|
146
|
+
return sendJson(res, {
|
|
147
|
+
success: false,
|
|
148
|
+
error: 'text is required'
|
|
149
|
+
}, 400);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const manifestPath = path.join(process.cwd(), 'app-manifest.json');
|
|
153
|
+
let foundKey = null;
|
|
154
|
+
let isFromGetString = false;
|
|
155
|
+
|
|
156
|
+
// Check if text exists as a value in the manifest
|
|
157
|
+
if (fs.existsSync(manifestPath)) {
|
|
158
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
159
|
+
const strings = manifest.strings?.default || {};
|
|
160
|
+
|
|
161
|
+
// Find key by value
|
|
162
|
+
for (const [key, value] of Object.entries(strings)) {
|
|
163
|
+
if (value === text) {
|
|
164
|
+
foundKey = key;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If we found a key, check if the source file uses getString with that key
|
|
171
|
+
if (foundKey && filePath) {
|
|
172
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
173
|
+
if (fs.existsSync(fullPath)) {
|
|
174
|
+
const fileContent = fs.readFileSync(fullPath, 'utf-8');
|
|
175
|
+
// Check for getString('key' or getString("key"
|
|
176
|
+
const getStringRegex = new RegExp(`getString\\s*\\(\\s*['"]${foundKey}['"]`, 'g');
|
|
177
|
+
if (getStringRegex.test(fileContent)) {
|
|
178
|
+
isFromGetString = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return sendJson(res, {
|
|
184
|
+
success: true,
|
|
185
|
+
found: foundKey !== null,
|
|
186
|
+
key: foundKey,
|
|
187
|
+
isFromGetString: isFromGetString,
|
|
188
|
+
text: text
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// POST /update-string - Update an existing gxp-string attribute key/value in manifest and source
|
|
193
|
+
if (req.method === 'POST' && endpoint === '/update-string') {
|
|
194
|
+
const body = await parseBody(req);
|
|
195
|
+
const {
|
|
196
|
+
oldKey, // The current key
|
|
197
|
+
newKey, // The new key (can be same as oldKey)
|
|
198
|
+
newValue, // The new default value (text content)
|
|
199
|
+
filePath // The Vue file path
|
|
200
|
+
} = body;
|
|
201
|
+
|
|
202
|
+
if (!oldKey || !newKey || !filePath) {
|
|
203
|
+
return sendJson(res, {
|
|
204
|
+
success: false,
|
|
205
|
+
error: 'oldKey, newKey, and filePath are required'
|
|
206
|
+
}, 400);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
210
|
+
|
|
211
|
+
if (!fs.existsSync(fullPath)) {
|
|
212
|
+
return sendJson(res, {
|
|
213
|
+
success: false,
|
|
214
|
+
error: `File not found: ${filePath}`
|
|
215
|
+
}, 404);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Read the Vue file
|
|
219
|
+
let fileContent = fs.readFileSync(fullPath, 'utf-8');
|
|
220
|
+
|
|
221
|
+
// Find and replace the gxp-string attribute
|
|
222
|
+
// Match: gxp-string="oldKey"
|
|
223
|
+
const oldAttrRegex = new RegExp(`gxp-string="${oldKey}"`, 'g');
|
|
224
|
+
|
|
225
|
+
let replaced = false;
|
|
226
|
+
if (oldAttrRegex.test(fileContent)) {
|
|
227
|
+
oldAttrRegex.lastIndex = 0;
|
|
228
|
+
fileContent = fileContent.replace(oldAttrRegex, `gxp-string="${newKey}"`);
|
|
229
|
+
replaced = true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!replaced) {
|
|
233
|
+
return sendJson(res, {
|
|
234
|
+
success: false,
|
|
235
|
+
error: `Could not find gxp-string="${oldKey}" in ${filePath}`
|
|
236
|
+
}, 400);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Write the updated file
|
|
240
|
+
fs.writeFileSync(fullPath, fileContent, 'utf-8');
|
|
241
|
+
|
|
242
|
+
// Update app-manifest.json
|
|
243
|
+
const manifestPath = path.join(process.cwd(), 'app-manifest.json');
|
|
244
|
+
let manifest = {};
|
|
245
|
+
|
|
246
|
+
if (fs.existsSync(manifestPath)) {
|
|
247
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
248
|
+
} else {
|
|
249
|
+
manifest = {
|
|
250
|
+
name: "GxToolkit",
|
|
251
|
+
version: "1.0.0",
|
|
252
|
+
description: "GxToolkit",
|
|
253
|
+
manifest_version: 3,
|
|
254
|
+
settings: {},
|
|
255
|
+
strings: { default: {} },
|
|
256
|
+
assets: {}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
manifest.strings = manifest.strings || { default: {} };
|
|
261
|
+
manifest.strings.default = manifest.strings.default || {};
|
|
262
|
+
|
|
263
|
+
// Remove old key if different from new key
|
|
264
|
+
if (oldKey !== newKey && manifest.strings.default[oldKey] !== undefined) {
|
|
265
|
+
delete manifest.strings.default[oldKey];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Set new key with new value
|
|
269
|
+
if (newValue) {
|
|
270
|
+
manifest.strings.default[newKey] = newValue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
274
|
+
|
|
275
|
+
return sendJson(res, {
|
|
276
|
+
success: true,
|
|
277
|
+
oldKey,
|
|
278
|
+
newKey,
|
|
279
|
+
newValue,
|
|
280
|
+
file: filePath
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// POST /extract-string - Extract a string by adding gxp-string attribute
|
|
285
|
+
if (req.method === 'POST' && endpoint === '/extract-string') {
|
|
286
|
+
const body = await parseBody(req);
|
|
287
|
+
const {
|
|
288
|
+
text, // The original text to extract
|
|
289
|
+
key, // Optional: custom key (otherwise generated from text)
|
|
290
|
+
filePath, // The Vue file path (relative to project root)
|
|
291
|
+
} = body;
|
|
292
|
+
|
|
293
|
+
if (!text || !filePath) {
|
|
294
|
+
return sendJson(res, {
|
|
295
|
+
success: false,
|
|
296
|
+
error: 'text and filePath are required'
|
|
297
|
+
}, 400);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const stringKey = key || textToKey(text);
|
|
301
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
302
|
+
|
|
303
|
+
// Validate file exists
|
|
304
|
+
if (!fs.existsSync(fullPath)) {
|
|
305
|
+
return sendJson(res, {
|
|
306
|
+
success: false,
|
|
307
|
+
error: `File not found: ${filePath}`
|
|
308
|
+
}, 404);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Read the Vue file
|
|
312
|
+
let fileContent = fs.readFileSync(fullPath, 'utf-8');
|
|
313
|
+
|
|
314
|
+
// Find and replace the text in the template section
|
|
315
|
+
const templateMatch = fileContent.match(/<template>([\s\S]*?)<\/template>/);
|
|
316
|
+
if (!templateMatch) {
|
|
317
|
+
return sendJson(res, {
|
|
318
|
+
success: false,
|
|
319
|
+
error: 'No template section found in file'
|
|
320
|
+
}, 400);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let template = templateMatch[1];
|
|
324
|
+
const originalTemplate = template;
|
|
325
|
+
|
|
326
|
+
let replaced = false;
|
|
327
|
+
|
|
328
|
+
// Pattern: Find element containing the text and add gxp-string attribute
|
|
329
|
+
// Match: <tag ...>text</tag> or <tag ...>text< (self-closing or nested)
|
|
330
|
+
// We need to find the opening tag that contains this text
|
|
331
|
+
|
|
332
|
+
// First try: Look for >text< pattern and work backwards to find the opening tag
|
|
333
|
+
const textPattern = new RegExp(`(<([a-zA-Z][a-zA-Z0-9-]*)([^>]*)>\\s*)${escapeRegex(text)}(\\s*<)`, 'g');
|
|
334
|
+
|
|
335
|
+
if (textPattern.test(template)) {
|
|
336
|
+
textPattern.lastIndex = 0; // Reset regex
|
|
337
|
+
template = template.replace(textPattern, (match, openTag, tagName, attrs, closeStart) => {
|
|
338
|
+
// Check if gxp-string attribute already exists
|
|
339
|
+
if (attrs.includes('gxp-string=')) {
|
|
340
|
+
return match; // Already has the attribute
|
|
341
|
+
}
|
|
342
|
+
// Add gxp-string attribute to the opening tag
|
|
343
|
+
const newOpenTag = `<${tagName}${attrs} gxp-string="${stringKey}">`;
|
|
344
|
+
return `${newOpenTag}${text}${closeStart}`;
|
|
345
|
+
});
|
|
346
|
+
replaced = true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// If pattern 1 didn't work, try a simpler approach for standalone text
|
|
350
|
+
if (!replaced) {
|
|
351
|
+
// Look for the text and find its parent tag
|
|
352
|
+
const simpleTextPattern = new RegExp(`(>\\s*)${escapeRegex(text)}(\\s*</)`, 'g');
|
|
353
|
+
if (simpleTextPattern.test(template)) {
|
|
354
|
+
// We found the text, now we need to add attribute to parent
|
|
355
|
+
// This is trickier - let's find the text position and work backwards
|
|
356
|
+
const textIndex = template.indexOf(`>${text}<`);
|
|
357
|
+
if (textIndex !== -1) {
|
|
358
|
+
// Find the opening tag before this text
|
|
359
|
+
let tagStart = textIndex;
|
|
360
|
+
while (tagStart > 0 && template[tagStart] !== '<') {
|
|
361
|
+
tagStart--;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Extract the tag
|
|
365
|
+
const tagEnd = template.indexOf('>', tagStart);
|
|
366
|
+
if (tagEnd !== -1 && tagEnd <= textIndex) {
|
|
367
|
+
const openTag = template.substring(tagStart, tagEnd + 1);
|
|
368
|
+
// Check if it already has gxp-string
|
|
369
|
+
if (!openTag.includes('gxp-string=')) {
|
|
370
|
+
// Add the attribute before the closing >
|
|
371
|
+
const newOpenTag = openTag.replace(/>$/, ` gxp-string="${stringKey}">`);
|
|
372
|
+
template = template.substring(0, tagStart) + newOpenTag + template.substring(tagEnd + 1);
|
|
373
|
+
replaced = true;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!replaced) {
|
|
381
|
+
return sendJson(res, {
|
|
382
|
+
success: false,
|
|
383
|
+
error: `Could not find "${text}" in template section`,
|
|
384
|
+
suggestion: 'The text might be part of a more complex expression or already extracted'
|
|
385
|
+
}, 400);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Update the file content with new template
|
|
389
|
+
fileContent = fileContent.replace(originalTemplate, template);
|
|
390
|
+
|
|
391
|
+
// Write the updated file
|
|
392
|
+
fs.writeFileSync(fullPath, fileContent, 'utf-8');
|
|
393
|
+
|
|
394
|
+
// Now update app-manifest.json with the new string
|
|
395
|
+
const manifestPath = path.join(process.cwd(), 'app-manifest.json');
|
|
396
|
+
let manifest = {};
|
|
397
|
+
|
|
398
|
+
// Create default manifest structure if file doesn't exist
|
|
399
|
+
if (fs.existsSync(manifestPath)) {
|
|
400
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
401
|
+
} else {
|
|
402
|
+
// Create default manifest
|
|
403
|
+
manifest = {
|
|
404
|
+
name: "GxToolkit",
|
|
405
|
+
version: "1.0.0",
|
|
406
|
+
description: "GxToolkit",
|
|
407
|
+
manifest_version: 3,
|
|
408
|
+
settings: {},
|
|
409
|
+
strings: {
|
|
410
|
+
default: {}
|
|
411
|
+
},
|
|
412
|
+
assets: {}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Ensure strings object exists
|
|
417
|
+
manifest.strings = manifest.strings || { default: {} };
|
|
418
|
+
manifest.strings.default = manifest.strings.default || {};
|
|
419
|
+
|
|
420
|
+
// Only add if not already exists
|
|
421
|
+
if (!manifest.strings.default[stringKey]) {
|
|
422
|
+
manifest.strings.default[stringKey] = text;
|
|
423
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return sendJson(res, {
|
|
427
|
+
success: true,
|
|
428
|
+
key: stringKey,
|
|
429
|
+
text: text,
|
|
430
|
+
file: filePath,
|
|
431
|
+
attributeAdded: true
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// POST /add-string - Just add a string to app-manifest.json without modifying source
|
|
436
|
+
if (req.method === 'POST' && endpoint === '/add-string') {
|
|
437
|
+
const body = await parseBody(req);
|
|
438
|
+
const { key, value } = body;
|
|
439
|
+
|
|
440
|
+
if (!key || !value) {
|
|
441
|
+
return sendJson(res, {
|
|
442
|
+
success: false,
|
|
443
|
+
error: 'key and value are required'
|
|
444
|
+
}, 400);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const manifestPath = path.join(process.cwd(), 'app-manifest.json');
|
|
448
|
+
let manifest = {};
|
|
449
|
+
|
|
450
|
+
// Create default manifest structure if file doesn't exist
|
|
451
|
+
if (fs.existsSync(manifestPath)) {
|
|
452
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
453
|
+
} else {
|
|
454
|
+
manifest = {
|
|
455
|
+
name: "GxToolkit",
|
|
456
|
+
version: "1.0.0",
|
|
457
|
+
description: "GxToolkit",
|
|
458
|
+
manifest_version: 3,
|
|
459
|
+
settings: {},
|
|
460
|
+
strings: {
|
|
461
|
+
default: {}
|
|
462
|
+
},
|
|
463
|
+
assets: {}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Ensure strings object exists
|
|
468
|
+
manifest.strings = manifest.strings || { default: {} };
|
|
469
|
+
manifest.strings.default = manifest.strings.default || {};
|
|
470
|
+
manifest.strings.default[key] = value;
|
|
471
|
+
|
|
472
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
473
|
+
|
|
474
|
+
return sendJson(res, {
|
|
475
|
+
success: true,
|
|
476
|
+
key,
|
|
477
|
+
value
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// GET /file - Get file content
|
|
482
|
+
if (req.method === 'GET' && endpoint === '/file') {
|
|
483
|
+
const url = new URL(req.url, 'http://localhost');
|
|
484
|
+
const filePath = url.searchParams.get('path');
|
|
485
|
+
|
|
486
|
+
if (!filePath) {
|
|
487
|
+
return sendJson(res, {
|
|
488
|
+
success: false,
|
|
489
|
+
error: 'path parameter required'
|
|
490
|
+
}, 400);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
494
|
+
|
|
495
|
+
if (!fs.existsSync(fullPath)) {
|
|
496
|
+
return sendJson(res, {
|
|
497
|
+
success: false,
|
|
498
|
+
error: 'File not found'
|
|
499
|
+
}, 404);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
503
|
+
|
|
504
|
+
return sendJson(res, {
|
|
505
|
+
success: true,
|
|
506
|
+
path: filePath,
|
|
507
|
+
content
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// POST /update-file - Direct file update
|
|
512
|
+
if (req.method === 'POST' && endpoint === '/update-file') {
|
|
513
|
+
const body = await parseBody(req);
|
|
514
|
+
const { filePath, content, backup } = body;
|
|
515
|
+
|
|
516
|
+
if (!filePath || content === undefined) {
|
|
517
|
+
return sendJson(res, {
|
|
518
|
+
success: false,
|
|
519
|
+
error: 'filePath and content are required'
|
|
520
|
+
}, 400);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
524
|
+
|
|
525
|
+
// Create backup if requested
|
|
526
|
+
if (backup && fs.existsSync(fullPath)) {
|
|
527
|
+
const backupPath = fullPath + '.backup';
|
|
528
|
+
fs.copyFileSync(fullPath, backupPath);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
532
|
+
|
|
533
|
+
return sendJson(res, {
|
|
534
|
+
success: true,
|
|
535
|
+
path: filePath
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// POST /analyze-text - Analyze if text content comes from a dynamic expression
|
|
540
|
+
if (req.method === 'POST' && endpoint === '/analyze-text') {
|
|
541
|
+
const body = await parseBody(req);
|
|
542
|
+
const { text, filePath } = body;
|
|
543
|
+
|
|
544
|
+
if (!text || !filePath) {
|
|
545
|
+
return sendJson(res, {
|
|
546
|
+
success: false,
|
|
547
|
+
error: 'text and filePath are required'
|
|
548
|
+
}, 400);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
552
|
+
|
|
553
|
+
if (!fs.existsSync(fullPath)) {
|
|
554
|
+
return sendJson(res, {
|
|
555
|
+
success: false,
|
|
556
|
+
error: `File not found: ${filePath}`
|
|
557
|
+
}, 404);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const fileContent = fs.readFileSync(fullPath, 'utf-8');
|
|
561
|
+
|
|
562
|
+
// Extract template section
|
|
563
|
+
const templateMatch = fileContent.match(/<template>([\s\S]*?)<\/template>/);
|
|
564
|
+
if (!templateMatch) {
|
|
565
|
+
return sendJson(res, {
|
|
566
|
+
success: true,
|
|
567
|
+
isDynamic: false,
|
|
568
|
+
reason: 'No template section found'
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const template = templateMatch[1];
|
|
573
|
+
const result = {
|
|
574
|
+
isDynamic: false,
|
|
575
|
+
expressionType: null,
|
|
576
|
+
expression: null,
|
|
577
|
+
sourceKey: null
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// Check if the exact text appears as static content (not in an expression)
|
|
581
|
+
// Static text would appear as >text< without {{ }}
|
|
582
|
+
const staticTextPattern = new RegExp(`>\\s*${escapeRegex(text)}\\s*<`, 'g');
|
|
583
|
+
const hasStaticText = staticTextPattern.test(template);
|
|
584
|
+
|
|
585
|
+
// Check for template expressions {{ ... }} that might produce this text
|
|
586
|
+
// We look for expressions in the template and check if the text could come from them
|
|
587
|
+
const expressionPattern = /\{\{\s*([^}]+)\s*\}\}/g;
|
|
588
|
+
const expressions = [];
|
|
589
|
+
let match;
|
|
590
|
+
|
|
591
|
+
while ((match = expressionPattern.exec(template)) !== null) {
|
|
592
|
+
expressions.push({
|
|
593
|
+
full: match[0],
|
|
594
|
+
expression: match[1].trim(),
|
|
595
|
+
index: match.index
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Extract script setup section to find variable definitions
|
|
600
|
+
const scriptSetupMatch = fileContent.match(/<script\s+setup[^>]*>([\s\S]*?)<\/script>/);
|
|
601
|
+
const scriptMatch = fileContent.match(/<script(?!\s+setup)[^>]*>([\s\S]*?)<\/script>/);
|
|
602
|
+
const scriptContent = scriptSetupMatch?.[1] || scriptMatch?.[1] || '';
|
|
603
|
+
|
|
604
|
+
// Check for getString calls that might produce this text
|
|
605
|
+
// Pattern: gxpStore.getString('key') or getString('key')
|
|
606
|
+
const getStringPattern = /(?:gxpStore\.)?getString\s*\(\s*['"]([^'"]+)['"]/g;
|
|
607
|
+
const getStringCalls = [];
|
|
608
|
+
|
|
609
|
+
while ((match = getStringPattern.exec(fileContent)) !== null) {
|
|
610
|
+
getStringCalls.push({
|
|
611
|
+
key: match[1],
|
|
612
|
+
full: match[0]
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Check manifest for getString keys that match this text
|
|
617
|
+
const manifestPath = path.join(process.cwd(), 'app-manifest.json');
|
|
618
|
+
let manifestStrings = {};
|
|
619
|
+
if (fs.existsSync(manifestPath)) {
|
|
620
|
+
try {
|
|
621
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
622
|
+
manifestStrings = manifest.strings?.default || {};
|
|
623
|
+
} catch (e) {
|
|
624
|
+
// Ignore parse errors
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Check if any getString call's value matches the text
|
|
629
|
+
for (const call of getStringCalls) {
|
|
630
|
+
if (manifestStrings[call.key] === text) {
|
|
631
|
+
result.isDynamic = true;
|
|
632
|
+
result.expressionType = 'getString';
|
|
633
|
+
result.expression = `getString('${call.key}')`;
|
|
634
|
+
result.sourceKey = call.key;
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// If not a getString match, check if text appears inside a template expression context
|
|
640
|
+
// This is a heuristic: if the text does NOT appear as static content but we have expressions,
|
|
641
|
+
// it's likely dynamic
|
|
642
|
+
if (!result.isDynamic && !hasStaticText && expressions.length > 0) {
|
|
643
|
+
// Check each expression to see if it could produce this text
|
|
644
|
+
for (const expr of expressions) {
|
|
645
|
+
// Check if expression references a variable that might contain this text
|
|
646
|
+
// Look for the variable in script content
|
|
647
|
+
const varName = expr.expression.split('.')[0].split('(')[0].trim();
|
|
648
|
+
|
|
649
|
+
// Check for ref/reactive definitions
|
|
650
|
+
const refPattern = new RegExp(`(?:const|let|var)\\s+${varName}\\s*=\\s*(?:ref|reactive)\\s*\\(\\s*['"]${escapeRegex(text)}['"]`, 'g');
|
|
651
|
+
const constPattern = new RegExp(`(?:const|let|var)\\s+${varName}\\s*=\\s*['"]${escapeRegex(text)}['"]`, 'g');
|
|
652
|
+
|
|
653
|
+
if (refPattern.test(scriptContent) || constPattern.test(scriptContent)) {
|
|
654
|
+
result.isDynamic = true;
|
|
655
|
+
result.expressionType = 'variable';
|
|
656
|
+
result.expression = expr.expression;
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Check for computed properties or store getters
|
|
661
|
+
if (expr.expression.includes('Store') || expr.expression.includes('store')) {
|
|
662
|
+
result.isDynamic = true;
|
|
663
|
+
result.expressionType = 'store';
|
|
664
|
+
result.expression = expr.expression;
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Check for gxp-string attribute with this text - if found, it's managed by directive
|
|
671
|
+
const gxpStringPattern = new RegExp(`gxp-string=["'][^"']+["'][^>]*>\\s*${escapeRegex(text)}\\s*<`, 'g');
|
|
672
|
+
if (gxpStringPattern.test(template)) {
|
|
673
|
+
result.isDynamic = true;
|
|
674
|
+
result.expressionType = 'gxp-directive';
|
|
675
|
+
result.expression = 'gxp-string directive';
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Additional check: look for the text value in props/settings patterns
|
|
679
|
+
const settingsPattern = /(?:pluginVars|settings|props)\s*\.\s*(\w+)/g;
|
|
680
|
+
while ((match = settingsPattern.exec(template)) !== null) {
|
|
681
|
+
// Check if this setting might produce the text
|
|
682
|
+
result.possibleSettings = result.possibleSettings || [];
|
|
683
|
+
result.possibleSettings.push(match[1]);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return sendJson(res, {
|
|
687
|
+
success: true,
|
|
688
|
+
...result,
|
|
689
|
+
hasStaticText,
|
|
690
|
+
expressionCount: expressions.length,
|
|
691
|
+
getStringCallsCount: getStringCalls.length
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// GET /component-files - List Vue component files
|
|
696
|
+
if (req.method === 'GET' && endpoint === '/component-files') {
|
|
697
|
+
const srcDir = path.join(process.cwd(), 'src');
|
|
698
|
+
const files = [];
|
|
699
|
+
|
|
700
|
+
function scanDir(dir, relativePath = '') {
|
|
701
|
+
if (!fs.existsSync(dir)) return;
|
|
702
|
+
|
|
703
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
704
|
+
for (const entry of entries) {
|
|
705
|
+
const entryPath = path.join(relativePath, entry.name);
|
|
706
|
+
if (entry.isDirectory()) {
|
|
707
|
+
scanDir(path.join(dir, entry.name), entryPath);
|
|
708
|
+
} else if (entry.name.endsWith('.vue')) {
|
|
709
|
+
files.push('src/' + entryPath);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
scanDir(srcDir);
|
|
715
|
+
|
|
716
|
+
return sendJson(res, {
|
|
717
|
+
success: true,
|
|
718
|
+
files
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Unknown endpoint
|
|
723
|
+
return sendJson(res, {
|
|
724
|
+
success: false,
|
|
725
|
+
error: 'Unknown endpoint'
|
|
726
|
+
}, 404);
|
|
727
|
+
|
|
728
|
+
} catch (error) {
|
|
729
|
+
console.error('[GxP Inspector] Error:', error);
|
|
730
|
+
return sendJson(res, {
|
|
731
|
+
success: false,
|
|
732
|
+
error: error.message
|
|
733
|
+
}, 500);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
console.log('[GxP Inspector] API endpoints available at /__gxp-inspector/*');
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Escape special regex characters
|
|
744
|
+
*/
|
|
745
|
+
function escapeRegex(string) {
|
|
746
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export default gxpInspectorPlugin;
|