@gxp-dev/tools 2.0.11 → 2.0.12
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/bin/lib/cli.js +42 -0
- package/bin/lib/commands/extract-config.js +186 -0
- package/bin/lib/commands/index.js +2 -0
- package/bin/lib/commands/init.js +433 -180
- package/bin/lib/tui/App.tsx +107 -0
- package/bin/lib/utils/ai-scaffold.js +806 -0
- package/bin/lib/utils/extract-config.js +468 -0
- package/bin/lib/utils/files.js +43 -2
- package/bin/lib/utils/index.js +4 -0
- package/bin/lib/utils/prompts.js +352 -0
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +84 -0
- package/dist/tui/App.js.map +1 -1
- package/mcp/gxp-api-server.js +524 -0
- package/package.json +4 -2
- package/runtime/stores/gxpPortalConfigStore.js +9 -0
- package/template/.claude/agents/gxp-developer.md +335 -0
- package/template/.claude/settings.json +9 -0
- package/template/AGENTS.md +125 -0
- package/template/GEMINI.md +80 -0
- package/template/app-manifest.json +1 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract Config Utility
|
|
3
|
+
*
|
|
4
|
+
* Parses Vue/JS files in src/ directory to extract GxP store usage and directives,
|
|
5
|
+
* then generates/updates app-manifest.json with the extracted configuration.
|
|
6
|
+
*
|
|
7
|
+
* Extracts:
|
|
8
|
+
* - getString(key, default) -> strings.default
|
|
9
|
+
* - getSetting(key, default) -> settings
|
|
10
|
+
* - getAsset(key, default) -> assets
|
|
11
|
+
* - getState(key, default) -> triggerState
|
|
12
|
+
* - callApi(path, identifier) -> dependencies
|
|
13
|
+
* - listenSocket(socketName, event, callback) -> (tracked for reference)
|
|
14
|
+
* - gxp-string/v-gxp-string directives -> strings.default
|
|
15
|
+
* - gxp-setting/v-gxp-setting + gxp-string -> settings
|
|
16
|
+
* - gxp-asset/v-gxp-asset + gxp-string -> assets
|
|
17
|
+
* - gxp-state/v-gxp-state + gxp-string -> triggerState
|
|
18
|
+
* - gxp-src/v-gxp-src -> assets
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require("fs");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extract configuration from all Vue/JS files in the src directory
|
|
26
|
+
* @param {string} srcDir - Path to the src directory
|
|
27
|
+
* @returns {Object} Extracted configuration
|
|
28
|
+
*/
|
|
29
|
+
function extractConfigFromSource(srcDir) {
|
|
30
|
+
const config = {
|
|
31
|
+
strings: {},
|
|
32
|
+
settings: {},
|
|
33
|
+
assets: {},
|
|
34
|
+
triggerState: {},
|
|
35
|
+
dependencies: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(srcDir)) {
|
|
39
|
+
console.error(`❌ Source directory not found: ${srcDir}`);
|
|
40
|
+
return config;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Find all .vue and .js files recursively
|
|
44
|
+
const files = findFilesRecursive(srcDir, [".vue", ".js", ".ts", ".jsx", ".tsx"]);
|
|
45
|
+
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
49
|
+
const relativePath = path.relative(srcDir, file);
|
|
50
|
+
|
|
51
|
+
// Extract from JavaScript/TypeScript code
|
|
52
|
+
extractFromScript(content, config, relativePath);
|
|
53
|
+
|
|
54
|
+
// Extract from Vue templates
|
|
55
|
+
if (file.endsWith(".vue")) {
|
|
56
|
+
extractFromTemplate(content, config, relativePath);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.warn(`⚠ Could not parse ${file}: ${error.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return config;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Find files recursively with given extensions
|
|
68
|
+
* @param {string} dir - Directory to search
|
|
69
|
+
* @param {string[]} extensions - File extensions to match
|
|
70
|
+
* @returns {string[]} Array of file paths
|
|
71
|
+
*/
|
|
72
|
+
function findFilesRecursive(dir, extensions) {
|
|
73
|
+
const files = [];
|
|
74
|
+
|
|
75
|
+
function walk(currentDir) {
|
|
76
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
77
|
+
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
80
|
+
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
// Skip node_modules and hidden directories
|
|
83
|
+
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
84
|
+
walk(fullPath);
|
|
85
|
+
}
|
|
86
|
+
} else if (entry.isFile()) {
|
|
87
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
88
|
+
if (extensions.includes(ext)) {
|
|
89
|
+
files.push(fullPath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
walk(dir);
|
|
96
|
+
return files;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Extract store method calls from JavaScript/TypeScript code
|
|
101
|
+
* @param {string} content - File content
|
|
102
|
+
* @param {Object} config - Configuration object to populate
|
|
103
|
+
* @param {string} sourcePath - Source file path for logging
|
|
104
|
+
*/
|
|
105
|
+
function extractFromScript(content, config, sourcePath) {
|
|
106
|
+
// Extract getString calls: getString('key', 'default') or getString("key", "default")
|
|
107
|
+
const getStringRegex = /\.getString\s*\(\s*['"`]([^'"`]+)['"`]\s*(?:,\s*['"`]([^'"`]*)['"`])?\s*\)/g;
|
|
108
|
+
let match;
|
|
109
|
+
while ((match = getStringRegex.exec(content)) !== null) {
|
|
110
|
+
const key = match[1];
|
|
111
|
+
const defaultValue = match[2] || "";
|
|
112
|
+
if (!config.strings[key]) {
|
|
113
|
+
config.strings[key] = defaultValue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Extract getSetting calls: getSetting('key', 'default') or getSetting('key', value)
|
|
118
|
+
const getSettingRegex = /\.getSetting\s*\(\s*['"`]([^'"`]+)['"`]\s*(?:,\s*['"`]?([^'"`),]*)['"`]?)?\s*\)/g;
|
|
119
|
+
while ((match = getSettingRegex.exec(content)) !== null) {
|
|
120
|
+
const key = match[1];
|
|
121
|
+
let defaultValue = match[2] || "";
|
|
122
|
+
// Clean up the default value
|
|
123
|
+
defaultValue = defaultValue.trim();
|
|
124
|
+
// Try to parse as JSON for non-string values
|
|
125
|
+
if (defaultValue && !config.settings[key]) {
|
|
126
|
+
try {
|
|
127
|
+
config.settings[key] = JSON.parse(defaultValue);
|
|
128
|
+
} catch {
|
|
129
|
+
config.settings[key] = defaultValue;
|
|
130
|
+
}
|
|
131
|
+
} else if (!config.settings[key]) {
|
|
132
|
+
config.settings[key] = "";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Extract getAsset calls: getAsset('key', 'default')
|
|
137
|
+
const getAssetRegex = /\.getAsset\s*\(\s*['"`]([^'"`]+)['"`]\s*(?:,\s*['"`]([^'"`]*)['"`])?\s*\)/g;
|
|
138
|
+
while ((match = getAssetRegex.exec(content)) !== null) {
|
|
139
|
+
const key = match[1];
|
|
140
|
+
const defaultValue = match[2] || "";
|
|
141
|
+
if (!config.assets[key]) {
|
|
142
|
+
config.assets[key] = defaultValue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Extract getState calls: getState('key', default)
|
|
147
|
+
const getStateRegex = /\.getState\s*\(\s*['"`]([^'"`]+)['"`]\s*(?:,\s*['"`]?([^'"`),]*)['"`]?)?\s*\)/g;
|
|
148
|
+
while ((match = getStateRegex.exec(content)) !== null) {
|
|
149
|
+
const key = match[1];
|
|
150
|
+
let defaultValue = match[2] || "";
|
|
151
|
+
defaultValue = defaultValue.trim();
|
|
152
|
+
if (!config.triggerState[key]) {
|
|
153
|
+
try {
|
|
154
|
+
config.triggerState[key] = JSON.parse(defaultValue);
|
|
155
|
+
} catch {
|
|
156
|
+
config.triggerState[key] = defaultValue || null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Extract callApi calls: callApi('path', 'identifier') or apiGet/apiPost with identifier patterns
|
|
162
|
+
// Pattern: callApi('/path', 'identifier') or any api method with path
|
|
163
|
+
const callApiRegex = /\.(?:callApi|apiGet|apiPost|apiPut|apiPatch|apiDelete)\s*\(\s*['"`]([^'"`]+)['"`]\s*(?:,\s*['"`]([^'"`]+)['"`])?\s*\)/g;
|
|
164
|
+
while ((match = callApiRegex.exec(content)) !== null) {
|
|
165
|
+
const apiPath = match[1];
|
|
166
|
+
const identifier = match[2];
|
|
167
|
+
if (identifier) {
|
|
168
|
+
// Check if this dependency already exists
|
|
169
|
+
const exists = config.dependencies.some(
|
|
170
|
+
(dep) => dep.identifier === identifier && dep.path === apiPath
|
|
171
|
+
);
|
|
172
|
+
if (!exists) {
|
|
173
|
+
config.dependencies.push({
|
|
174
|
+
identifier: identifier,
|
|
175
|
+
path: apiPath,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Extract listenSocket calls for reference: listenSocket('socketName', 'event', callback)
|
|
182
|
+
// These help identify what socket events the app expects
|
|
183
|
+
const listenSocketRegex = /\.(?:listenSocket|useSocketListener)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*['"`]([^'"`]+)['"`]/g;
|
|
184
|
+
while ((match = listenSocketRegex.exec(content)) !== null) {
|
|
185
|
+
const socketName = match[1];
|
|
186
|
+
const eventName = match[2];
|
|
187
|
+
// Add as a dependency reference if not 'primary'
|
|
188
|
+
if (socketName !== "primary") {
|
|
189
|
+
const exists = config.dependencies.some(
|
|
190
|
+
(dep) => dep.identifier === socketName
|
|
191
|
+
);
|
|
192
|
+
if (!exists) {
|
|
193
|
+
config.dependencies.push({
|
|
194
|
+
identifier: socketName,
|
|
195
|
+
path: "",
|
|
196
|
+
events: { [eventName]: eventName },
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
// Add the event to existing dependency
|
|
200
|
+
const dep = config.dependencies.find((d) => d.identifier === socketName);
|
|
201
|
+
if (dep) {
|
|
202
|
+
dep.events = dep.events || {};
|
|
203
|
+
dep.events[eventName] = eventName;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extract directive usage from Vue templates
|
|
212
|
+
* @param {string} content - File content
|
|
213
|
+
* @param {Object} config - Configuration object to populate
|
|
214
|
+
* @param {string} sourcePath - Source file path for logging
|
|
215
|
+
*/
|
|
216
|
+
function extractFromTemplate(content, config, sourcePath) {
|
|
217
|
+
// Extract template section
|
|
218
|
+
const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
|
|
219
|
+
if (!templateMatch) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const template = templateMatch[1];
|
|
224
|
+
|
|
225
|
+
// Extract gxp-string/v-gxp-string without other gxp attributes (pure strings)
|
|
226
|
+
// Pattern: <tag gxp-string="key">default</tag> or <tag v-gxp-string="'key'">default</tag>
|
|
227
|
+
// But NOT when gxp-setting, gxp-asset, or gxp-state is present
|
|
228
|
+
const pureStringRegex = /<([a-z][a-z0-9-]*)\s+[^>]*(?:v-gxp-string|gxp-string)=["']([^"']+)["'][^>]*>([^<]*)</gi;
|
|
229
|
+
let match;
|
|
230
|
+
while ((match = pureStringRegex.exec(template)) !== null) {
|
|
231
|
+
const fullMatch = match[0];
|
|
232
|
+
const key = match[2].replace(/['"]/g, ""); // Remove quotes for v-gxp-string="'key'"
|
|
233
|
+
const defaultValue = match[3].trim();
|
|
234
|
+
|
|
235
|
+
// Check if this element has gxp-setting, gxp-asset, or gxp-state
|
|
236
|
+
const hasGxpSetting = /gxp-setting|v-gxp-setting/i.test(fullMatch);
|
|
237
|
+
const hasGxpAsset = /gxp-asset|v-gxp-asset/i.test(fullMatch);
|
|
238
|
+
const hasGxpState = /gxp-state|v-gxp-state/i.test(fullMatch);
|
|
239
|
+
|
|
240
|
+
if (hasGxpSetting) {
|
|
241
|
+
// gxp-string key with gxp-setting -> settings
|
|
242
|
+
if (!config.settings[key]) {
|
|
243
|
+
config.settings[key] = defaultValue;
|
|
244
|
+
}
|
|
245
|
+
} else if (hasGxpAsset) {
|
|
246
|
+
// gxp-string key with gxp-asset -> assets
|
|
247
|
+
if (!config.assets[key]) {
|
|
248
|
+
config.assets[key] = defaultValue;
|
|
249
|
+
}
|
|
250
|
+
} else if (hasGxpState) {
|
|
251
|
+
// gxp-string key with gxp-state -> triggerState
|
|
252
|
+
if (!config.triggerState[key]) {
|
|
253
|
+
config.triggerState[key] = defaultValue || null;
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
// Pure gxp-string -> strings
|
|
257
|
+
if (!config.strings[key]) {
|
|
258
|
+
config.strings[key] = defaultValue;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Extract standalone gxp-setting/v-gxp-setting with value (not using gxp-string for key)
|
|
264
|
+
// Pattern: <tag gxp-setting="key">default</tag>
|
|
265
|
+
const settingRegex = /<([a-z][a-z0-9-]*)\s+[^>]*(?:v-gxp-setting|gxp-setting)=["']([^"']+)["'][^>]*>([^<]*)</gi;
|
|
266
|
+
while ((match = settingRegex.exec(template)) !== null) {
|
|
267
|
+
const key = match[2].replace(/['"]/g, "");
|
|
268
|
+
const defaultValue = match[3].trim();
|
|
269
|
+
// Only add if key looks like an actual key (not empty)
|
|
270
|
+
if (key && !config.settings[key]) {
|
|
271
|
+
config.settings[key] = defaultValue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Extract standalone gxp-asset/v-gxp-asset with value
|
|
276
|
+
const assetRegex = /<([a-z][a-z0-9-]*)\s+[^>]*(?:v-gxp-asset|gxp-asset)=["']([^"']+)["'][^>]*>([^<]*)</gi;
|
|
277
|
+
while ((match = assetRegex.exec(template)) !== null) {
|
|
278
|
+
const key = match[2].replace(/['"]/g, "");
|
|
279
|
+
const defaultValue = match[3].trim();
|
|
280
|
+
if (key && !config.assets[key]) {
|
|
281
|
+
config.assets[key] = defaultValue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Extract gxp-src/v-gxp-src: <img gxp-src="key" src="default" />
|
|
286
|
+
// Pattern matches self-closing and regular tags with gxp-src and src attributes
|
|
287
|
+
const gxpSrcRegex = /<([a-z][a-z0-9-]*)\s+[^>]*(?:v-gxp-src|gxp-src)=["']([^"']+)["'][^>]*src=["']([^"']+)["'][^>]*\/?>/gi;
|
|
288
|
+
while ((match = gxpSrcRegex.exec(template)) !== null) {
|
|
289
|
+
const key = match[2].replace(/['"]/g, "");
|
|
290
|
+
const defaultSrc = match[3];
|
|
291
|
+
|
|
292
|
+
// Check if gxp-state is present
|
|
293
|
+
const fullMatch = match[0];
|
|
294
|
+
const hasGxpState = /gxp-state|v-gxp-state/i.test(fullMatch);
|
|
295
|
+
|
|
296
|
+
if (hasGxpState) {
|
|
297
|
+
if (!config.triggerState[key]) {
|
|
298
|
+
config.triggerState[key] = defaultSrc;
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
if (!config.assets[key]) {
|
|
302
|
+
config.assets[key] = defaultSrc;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Also try reverse order: src before gxp-src
|
|
308
|
+
const gxpSrcReverseRegex = /<([a-z][a-z0-9-]*)\s+[^>]*src=["']([^"']+)["'][^>]*(?:v-gxp-src|gxp-src)=["']([^"']+)["'][^>]*\/?>/gi;
|
|
309
|
+
while ((match = gxpSrcReverseRegex.exec(template)) !== null) {
|
|
310
|
+
const defaultSrc = match[2];
|
|
311
|
+
const key = match[3].replace(/['"]/g, "");
|
|
312
|
+
|
|
313
|
+
const fullMatch = match[0];
|
|
314
|
+
const hasGxpState = /gxp-state|v-gxp-state/i.test(fullMatch);
|
|
315
|
+
|
|
316
|
+
if (hasGxpState) {
|
|
317
|
+
if (!config.triggerState[key]) {
|
|
318
|
+
config.triggerState[key] = defaultSrc;
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
if (!config.assets[key]) {
|
|
322
|
+
config.assets[key] = defaultSrc;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Merge extracted config into existing manifest
|
|
330
|
+
* @param {Object} existingManifest - Current app-manifest.json content
|
|
331
|
+
* @param {Object} extractedConfig - Newly extracted configuration
|
|
332
|
+
* @param {Object} options - Merge options
|
|
333
|
+
* @returns {Object} Merged manifest
|
|
334
|
+
*/
|
|
335
|
+
function mergeConfig(existingManifest, extractedConfig, options = {}) {
|
|
336
|
+
const { overwrite = false } = options;
|
|
337
|
+
|
|
338
|
+
const merged = { ...existingManifest };
|
|
339
|
+
|
|
340
|
+
// Ensure nested structures exist
|
|
341
|
+
if (!merged.strings) merged.strings = {};
|
|
342
|
+
if (!merged.strings.default) merged.strings.default = {};
|
|
343
|
+
if (!merged.settings) merged.settings = {};
|
|
344
|
+
if (!merged.assets) merged.assets = {};
|
|
345
|
+
if (!merged.triggerState) merged.triggerState = {};
|
|
346
|
+
if (!merged.dependencies) merged.dependencies = [];
|
|
347
|
+
|
|
348
|
+
// Merge strings
|
|
349
|
+
for (const [key, value] of Object.entries(extractedConfig.strings)) {
|
|
350
|
+
if (overwrite || !merged.strings.default[key]) {
|
|
351
|
+
merged.strings.default[key] = value;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Merge settings
|
|
356
|
+
for (const [key, value] of Object.entries(extractedConfig.settings)) {
|
|
357
|
+
if (overwrite || merged.settings[key] === undefined) {
|
|
358
|
+
merged.settings[key] = value;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Merge assets
|
|
363
|
+
for (const [key, value] of Object.entries(extractedConfig.assets)) {
|
|
364
|
+
if (overwrite || !merged.assets[key]) {
|
|
365
|
+
merged.assets[key] = value;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Merge triggerState
|
|
370
|
+
for (const [key, value] of Object.entries(extractedConfig.triggerState)) {
|
|
371
|
+
if (overwrite || merged.triggerState[key] === undefined) {
|
|
372
|
+
merged.triggerState[key] = value;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Merge dependencies (by identifier)
|
|
377
|
+
for (const dep of extractedConfig.dependencies) {
|
|
378
|
+
const existingIndex = merged.dependencies.findIndex(
|
|
379
|
+
(d) => d.identifier === dep.identifier
|
|
380
|
+
);
|
|
381
|
+
if (existingIndex === -1) {
|
|
382
|
+
merged.dependencies.push(dep);
|
|
383
|
+
} else if (overwrite) {
|
|
384
|
+
merged.dependencies[existingIndex] = {
|
|
385
|
+
...merged.dependencies[existingIndex],
|
|
386
|
+
...dep,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return merged;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Generate a summary of extracted configuration
|
|
396
|
+
* @param {Object} config - Extracted configuration
|
|
397
|
+
* @returns {string} Summary text
|
|
398
|
+
*/
|
|
399
|
+
function generateSummary(config) {
|
|
400
|
+
const lines = [];
|
|
401
|
+
|
|
402
|
+
const stringCount = Object.keys(config.strings).length;
|
|
403
|
+
const settingCount = Object.keys(config.settings).length;
|
|
404
|
+
const assetCount = Object.keys(config.assets).length;
|
|
405
|
+
const stateCount = Object.keys(config.triggerState).length;
|
|
406
|
+
const depCount = config.dependencies.length;
|
|
407
|
+
|
|
408
|
+
lines.push("📊 Extraction Summary:");
|
|
409
|
+
lines.push("");
|
|
410
|
+
|
|
411
|
+
if (stringCount > 0) {
|
|
412
|
+
lines.push(`📝 Strings (${stringCount}):`);
|
|
413
|
+
for (const [key, value] of Object.entries(config.strings)) {
|
|
414
|
+
const displayValue = value ? `"${value}"` : "(empty)";
|
|
415
|
+
lines.push(` ${key}: ${displayValue}`);
|
|
416
|
+
}
|
|
417
|
+
lines.push("");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (settingCount > 0) {
|
|
421
|
+
lines.push(`⚙️ Settings (${settingCount}):`);
|
|
422
|
+
for (const [key, value] of Object.entries(config.settings)) {
|
|
423
|
+
lines.push(` ${key}: ${JSON.stringify(value)}`);
|
|
424
|
+
}
|
|
425
|
+
lines.push("");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (assetCount > 0) {
|
|
429
|
+
lines.push(`🖼️ Assets (${assetCount}):`);
|
|
430
|
+
for (const [key, value] of Object.entries(config.assets)) {
|
|
431
|
+
lines.push(` ${key}: ${value || "(empty)"}`);
|
|
432
|
+
}
|
|
433
|
+
lines.push("");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (stateCount > 0) {
|
|
437
|
+
lines.push(`🔄 Trigger State (${stateCount}):`);
|
|
438
|
+
for (const [key, value] of Object.entries(config.triggerState)) {
|
|
439
|
+
lines.push(` ${key}: ${JSON.stringify(value)}`);
|
|
440
|
+
}
|
|
441
|
+
lines.push("");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (depCount > 0) {
|
|
445
|
+
lines.push(`🔗 Dependencies (${depCount}):`);
|
|
446
|
+
for (const dep of config.dependencies) {
|
|
447
|
+
lines.push(` ${dep.identifier}: ${dep.path || "(no path)"}`);
|
|
448
|
+
if (dep.events) {
|
|
449
|
+
lines.push(` Events: ${Object.keys(dep.events).join(", ")}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
lines.push("");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (stringCount + settingCount + assetCount + stateCount + depCount === 0) {
|
|
456
|
+
lines.push(" No configuration found in source files.");
|
|
457
|
+
lines.push("");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return lines.join("\n");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
module.exports = {
|
|
464
|
+
extractConfigFromSource,
|
|
465
|
+
mergeConfig,
|
|
466
|
+
generateSummary,
|
|
467
|
+
findFilesRecursive,
|
|
468
|
+
};
|
package/bin/lib/utils/files.js
CHANGED
|
@@ -30,15 +30,18 @@ function safeCopyFile(src, dest, description) {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Creates package.json for new projects
|
|
33
|
+
* @param {string} projectPath - Path to project directory
|
|
34
|
+
* @param {string} projectName - Name of the project
|
|
35
|
+
* @param {string} description - Optional project description
|
|
33
36
|
*/
|
|
34
|
-
function createPackageJson(projectPath, projectName) {
|
|
37
|
+
function createPackageJson(projectPath, projectName, description = "") {
|
|
35
38
|
const packageJsonPath = path.join(projectPath, "package.json");
|
|
36
39
|
const globalConfig = loadGlobalConfig();
|
|
37
40
|
|
|
38
41
|
const packageJson = {
|
|
39
42
|
name: projectName,
|
|
40
43
|
version: "1.0.0",
|
|
41
|
-
description: `GxP Plugin: ${projectName}`,
|
|
44
|
+
description: description || `GxP Plugin: ${projectName}`,
|
|
42
45
|
main: "main.js",
|
|
43
46
|
scripts: {
|
|
44
47
|
...DEFAULT_SCRIPTS,
|
|
@@ -55,6 +58,43 @@ function createPackageJson(projectPath, projectName) {
|
|
|
55
58
|
console.log("✓ Created package.json");
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Updates app-manifest.json with project name and description
|
|
63
|
+
* @param {string} projectPath - Path to project directory
|
|
64
|
+
* @param {string} projectName - Name of the project
|
|
65
|
+
* @param {string} description - Optional project description
|
|
66
|
+
*/
|
|
67
|
+
function updateAppManifest(projectPath, projectName, description = "") {
|
|
68
|
+
const manifestPath = path.join(projectPath, "app-manifest.json");
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(manifestPath)) {
|
|
71
|
+
console.warn("⚠ app-manifest.json not found, skipping update");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
77
|
+
|
|
78
|
+
// Update name and description
|
|
79
|
+
manifest.name = projectName;
|
|
80
|
+
if (description) {
|
|
81
|
+
manifest.description = description;
|
|
82
|
+
} else {
|
|
83
|
+
manifest.description = `GxP Plugin: ${projectName}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Update strings with project name
|
|
87
|
+
if (manifest.strings && manifest.strings.default) {
|
|
88
|
+
manifest.strings.default.welcome_text = `Welcome to ${projectName}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, "\t"));
|
|
92
|
+
console.log("✓ Updated app-manifest.json with project details");
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn("⚠ Could not update app-manifest.json:", error.message);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
58
98
|
/**
|
|
59
99
|
* Installs npm dependencies
|
|
60
100
|
*/
|
|
@@ -173,6 +213,7 @@ function ensureImageMagickInstalled() {
|
|
|
173
213
|
module.exports = {
|
|
174
214
|
safeCopyFile,
|
|
175
215
|
createPackageJson,
|
|
216
|
+
updateAppManifest,
|
|
176
217
|
installDependencies,
|
|
177
218
|
updateExistingProject,
|
|
178
219
|
isImageMagickInstalled,
|
package/bin/lib/utils/index.js
CHANGED
|
@@ -8,10 +8,14 @@ const paths = require("./paths");
|
|
|
8
8
|
const ssl = require("./ssl");
|
|
9
9
|
const files = require("./files");
|
|
10
10
|
const prompts = require("./prompts");
|
|
11
|
+
const aiScaffold = require("./ai-scaffold");
|
|
12
|
+
const extractConfig = require("./extract-config");
|
|
11
13
|
|
|
12
14
|
module.exports = {
|
|
13
15
|
...paths,
|
|
14
16
|
...ssl,
|
|
15
17
|
...files,
|
|
16
18
|
...prompts,
|
|
19
|
+
...aiScaffold,
|
|
20
|
+
...extractConfig,
|
|
17
21
|
};
|