@gxp-dev/tools 2.0.5

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 (145) hide show
  1. package/.github/workflows/npm-publish.yml +48 -0
  2. package/CLAUDE.md +400 -0
  3. package/README.md +247 -0
  4. package/REFACTOR_PLAN.md +194 -0
  5. package/bin/gx-devtools.js +87 -0
  6. package/bin/lib/cli.js +251 -0
  7. package/bin/lib/commands/assets.js +337 -0
  8. package/bin/lib/commands/build.js +259 -0
  9. package/bin/lib/commands/datastore.js +433 -0
  10. package/bin/lib/commands/dev.js +328 -0
  11. package/bin/lib/commands/extensions.js +298 -0
  12. package/bin/lib/commands/index.js +35 -0
  13. package/bin/lib/commands/init.js +307 -0
  14. package/bin/lib/commands/publish.js +189 -0
  15. package/bin/lib/commands/socket.js +158 -0
  16. package/bin/lib/commands/ssl.js +47 -0
  17. package/bin/lib/constants.js +120 -0
  18. package/bin/lib/tui/App.tsx +600 -0
  19. package/bin/lib/tui/components/CommandInput.tsx +278 -0
  20. package/bin/lib/tui/components/GeminiPanel.tsx +161 -0
  21. package/bin/lib/tui/components/Header.tsx +27 -0
  22. package/bin/lib/tui/components/LogPanel.tsx +122 -0
  23. package/bin/lib/tui/components/TabBar.tsx +56 -0
  24. package/bin/lib/tui/components/WelcomeScreen.tsx +80 -0
  25. package/bin/lib/tui/index.tsx +63 -0
  26. package/bin/lib/tui/services/ExtensionService.ts +122 -0
  27. package/bin/lib/tui/services/GeminiService.ts +395 -0
  28. package/bin/lib/tui/services/ServiceManager.ts +336 -0
  29. package/bin/lib/tui/services/SocketService.ts +204 -0
  30. package/bin/lib/tui/services/ViteService.ts +107 -0
  31. package/bin/lib/tui/services/index.ts +13 -0
  32. package/bin/lib/utils/files.js +180 -0
  33. package/bin/lib/utils/index.js +17 -0
  34. package/bin/lib/utils/paths.js +138 -0
  35. package/bin/lib/utils/prompts.js +71 -0
  36. package/bin/lib/utils/ssl.js +233 -0
  37. package/browser-extensions/README.md +1 -0
  38. package/browser-extensions/chrome/background.js +857 -0
  39. package/browser-extensions/chrome/content.js +51 -0
  40. package/browser-extensions/chrome/devtools.html +9 -0
  41. package/browser-extensions/chrome/devtools.js +23 -0
  42. package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
  43. package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
  44. package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
  45. package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
  46. package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
  47. package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
  48. package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
  49. package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
  50. package/browser-extensions/chrome/inspector.js +1087 -0
  51. package/browser-extensions/chrome/manifest.json +70 -0
  52. package/browser-extensions/chrome/panel.html +638 -0
  53. package/browser-extensions/chrome/panel.js +862 -0
  54. package/browser-extensions/chrome/popup.html +399 -0
  55. package/browser-extensions/chrome/popup.js +515 -0
  56. package/browser-extensions/chrome/rules.json +1 -0
  57. package/browser-extensions/chrome/test-chrome.html +145 -0
  58. package/browser-extensions/chrome/test-mixed-content.html +190 -0
  59. package/browser-extensions/chrome/test-uri-pattern.html +199 -0
  60. package/browser-extensions/firefox/README.md +134 -0
  61. package/browser-extensions/firefox/background.js +804 -0
  62. package/browser-extensions/firefox/content.js +120 -0
  63. package/browser-extensions/firefox/debug-errors.html +229 -0
  64. package/browser-extensions/firefox/debug-https.html +113 -0
  65. package/browser-extensions/firefox/devtools.html +9 -0
  66. package/browser-extensions/firefox/devtools.js +24 -0
  67. package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
  68. package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
  69. package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
  70. package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
  71. package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
  72. package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
  73. package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
  74. package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
  75. package/browser-extensions/firefox/inspector.js +1087 -0
  76. package/browser-extensions/firefox/manifest.json +67 -0
  77. package/browser-extensions/firefox/panel.html +638 -0
  78. package/browser-extensions/firefox/panel.js +862 -0
  79. package/browser-extensions/firefox/popup.html +525 -0
  80. package/browser-extensions/firefox/popup.js +536 -0
  81. package/browser-extensions/firefox/test-gramercy.html +126 -0
  82. package/browser-extensions/firefox/test-imports.html +58 -0
  83. package/browser-extensions/firefox/test-masking.html +147 -0
  84. package/browser-extensions/firefox/test-uri-pattern.html +199 -0
  85. package/docs/DOCUSAURUS_IMPORT.md +378 -0
  86. package/docs/_category_.json +8 -0
  87. package/docs/app-manifest.md +272 -0
  88. package/docs/building-for-platform.md +315 -0
  89. package/docs/dev-tools.md +291 -0
  90. package/docs/getting-started.md +180 -0
  91. package/docs/gxp-store.md +305 -0
  92. package/docs/index.md +44 -0
  93. package/package.json +77 -0
  94. package/runtime/PortalContainer.vue +326 -0
  95. package/runtime/dev-tools/DevToolsModal.vue +217 -0
  96. package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
  97. package/runtime/dev-tools/MockDataEditor.vue +621 -0
  98. package/runtime/dev-tools/SocketSimulator.vue +562 -0
  99. package/runtime/dev-tools/StoreInspector.vue +644 -0
  100. package/runtime/dev-tools/index.js +6 -0
  101. package/runtime/gxpStringsPlugin.js +428 -0
  102. package/runtime/index.html +22 -0
  103. package/runtime/main.js +32 -0
  104. package/runtime/mock-api/auth-middleware.js +97 -0
  105. package/runtime/mock-api/image-generator.js +221 -0
  106. package/runtime/mock-api/index.js +197 -0
  107. package/runtime/mock-api/response-generator.js +394 -0
  108. package/runtime/mock-api/route-generator.js +323 -0
  109. package/runtime/mock-api/socket-triggers.js +371 -0
  110. package/runtime/mock-api/spec-loader.js +300 -0
  111. package/runtime/server.js +180 -0
  112. package/runtime/stores/gxpPortalConfigStore.js +554 -0
  113. package/runtime/stores/index.js +6 -0
  114. package/runtime/vite-inspector-plugin.js +749 -0
  115. package/runtime/vite-source-tracker-plugin.js +232 -0
  116. package/runtime/vite.config.js +402 -0
  117. package/scripts/launch-chrome.js +90 -0
  118. package/scripts/pack-chrome.js +91 -0
  119. package/socket-events/AiSessionMessageCreated.json +18 -0
  120. package/socket-events/SocialStreamPostCreated.json +24 -0
  121. package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
  122. package/template/README.md +332 -0
  123. package/template/app-manifest.json +32 -0
  124. package/template/dev-assets/images/avatar-placeholder.png +0 -0
  125. package/template/dev-assets/images/background-placeholder.jpg +0 -0
  126. package/template/dev-assets/images/banner-placeholder.jpg +0 -0
  127. package/template/dev-assets/images/icon-placeholder.png +0 -0
  128. package/template/dev-assets/images/logo-placeholder.png +0 -0
  129. package/template/dev-assets/images/product-placeholder.jpg +0 -0
  130. package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
  131. package/template/env.example +51 -0
  132. package/template/gitignore +53 -0
  133. package/template/index.html +22 -0
  134. package/template/main.js +28 -0
  135. package/template/src/DemoPage.vue +459 -0
  136. package/template/src/Plugin.vue +38 -0
  137. package/template/src/stores/index.js +9 -0
  138. package/template/src/stores/test-data.json +173 -0
  139. package/template/theme-layouts/AdditionalStyling.css +0 -0
  140. package/template/theme-layouts/PrivateLayout.vue +39 -0
  141. package/template/theme-layouts/PublicLayout.vue +39 -0
  142. package/template/theme-layouts/SystemLayout.vue +39 -0
  143. package/template/vite.config.js +333 -0
  144. package/tsconfig.tui.json +21 -0
  145. package/vite.config.js +164 -0
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Assets Command
3
+ *
4
+ * Manages development assets and placeholder image generation.
5
+ */
6
+
7
+ const path = require("path");
8
+ const fs = require("fs");
9
+ const shell = require("shelljs");
10
+ const {
11
+ findProjectRoot,
12
+ resolveGxPaths,
13
+ safeCopyFile,
14
+ isImageMagickInstalled,
15
+ ensureImageMagickInstalled,
16
+ } = require("../utils");
17
+
18
+ /**
19
+ * Assets management command - manages development assets and placeholder generation
20
+ */
21
+ async function assetsCommand(argv) {
22
+ const action = argv.action;
23
+
24
+ if (action === "list") {
25
+ listDevelopmentAssets(argv);
26
+ } else if (action === "generate") {
27
+ await generatePlaceholderImage(argv);
28
+ } else if (action === "init") {
29
+ await initDevelopmentAssets();
30
+ } else {
31
+ console.error(
32
+ "❌ Invalid assets action. Use 'list', 'generate', or 'init'"
33
+ );
34
+ process.exit(1);
35
+ }
36
+ }
37
+
38
+ function listDevelopmentAssets(argv) {
39
+ const projectPath = findProjectRoot();
40
+ const devAssetsDir = path.join(projectPath, "dev-assets");
41
+
42
+ if (!fs.existsSync(devAssetsDir)) {
43
+ console.log("❌ No dev-assets directory found");
44
+ console.log("💡 Run 'gxdev assets init' to set up development assets");
45
+ return;
46
+ }
47
+ const finalPort = argv.port || process.env.NODE_PORT || 3000;
48
+
49
+ console.log("📁 Development Assets:");
50
+ console.log("");
51
+
52
+ const dirs = ["images", "videos"];
53
+ dirs.forEach((dir) => {
54
+ const dirPath = path.join(devAssetsDir, dir);
55
+ if (fs.existsSync(dirPath)) {
56
+ const files = fs.readdirSync(dirPath);
57
+ if (files.length > 0) {
58
+ console.log(`📸 ${dir}/`);
59
+ files.forEach((file) => {
60
+ const stats = fs.statSync(path.join(dirPath, file));
61
+ const size = (stats.size / 1024).toFixed(1);
62
+ console.log(` • ${file} (${size} KB)`);
63
+ console.log(
64
+ ` URL: https://localhost:${finalPort}/dev-assets/${dir}/${file}`
65
+ );
66
+ });
67
+ console.log("");
68
+ }
69
+ }
70
+ });
71
+
72
+ console.log("💡 Usage:");
73
+ console.log(" Add assets to your store:");
74
+ console.log(
75
+ ` gxpStore.updateAsset("my_image", "https://localhost:${finalPort}/dev-assets/images/my-image.jpg")`
76
+ );
77
+ }
78
+
79
+ async function generatePlaceholderImage(argv) {
80
+ if (!ensureImageMagickInstalled()) {
81
+ process.exit(1);
82
+ }
83
+
84
+ const projectPath = findProjectRoot();
85
+ const size = argv.size || "400x300";
86
+ const name = argv.name || "placeholder";
87
+ const format = argv.format || "png";
88
+ const count = Math.max(1, argv.count || 1);
89
+
90
+ const devAssetsDir = path.join(projectPath, "dev-assets", "images");
91
+ if (!fs.existsSync(devAssetsDir)) {
92
+ fs.mkdirSync(devAssetsDir, { recursive: true });
93
+ }
94
+
95
+ // Use magick command (ImageMagick 7) or convert (ImageMagick 6)
96
+ const magickCmd = shell.which("magick") ? "magick" : "convert";
97
+ const finalPort = argv.port || process.env.NODE_PORT || 3000;
98
+
99
+ const generatedAssets = [];
100
+
101
+ console.log(`🎨 Generating ${count} placeholder${count > 1 ? "s" : ""}...`);
102
+ console.log(`📐 Size: ${size}`);
103
+
104
+ for (let i = 0; i < count; i++) {
105
+ const color = argv.color || getRandomColor();
106
+ const style = getRandomStyle();
107
+ const suffix = count > 1 ? `-${i + 1}` : "";
108
+ const filename = `${name}${suffix}.${format}`;
109
+ const text =
110
+ argv.text ||
111
+ (count > 1 ? `${name} ${i + 1}\n${size}` : `${name}\n${size}`);
112
+ const outputPath = path.join(devAssetsDir, filename);
113
+
114
+ // Create command with style variations
115
+ const styleOptions = getStyleOptions(style, color);
116
+ const command = `${magickCmd} -size ${size} ${styleOptions.background} -gravity center ${styleOptions.text} -annotate +0+0 "${text}" "${outputPath}"`;
117
+
118
+ console.log(`🎨 Generating: ${filename} (${color}, ${style.name})`);
119
+
120
+ const result = shell.exec(command, { silent: true });
121
+
122
+ if (result.code === 0) {
123
+ console.log(`✅ Generated: ${filename}`);
124
+ generatedAssets.push({
125
+ name: count > 1 ? `${name}_${i + 1}` : name,
126
+ filename,
127
+ url: `https://localhost:${finalPort}/dev-assets/images/${filename}`,
128
+ color,
129
+ style: style.name,
130
+ });
131
+ } else {
132
+ console.error(`❌ Failed to generate ${filename}: ${result.stderr}`);
133
+ process.exit(1);
134
+ }
135
+ }
136
+
137
+ console.log("");
138
+ console.log("📁 Generated assets:");
139
+ generatedAssets.forEach((asset) => {
140
+ console.log(` • ${asset.filename} (${asset.color}, ${asset.style})`);
141
+ console.log(` URL: ${asset.url}`);
142
+ });
143
+
144
+ console.log("");
145
+ console.log("💡 Add to your store:");
146
+ generatedAssets.forEach((asset) => {
147
+ console.log(` gxpStore.updateAsset("${asset.name}", "${asset.url}")`);
148
+ });
149
+ }
150
+
151
+ function getRandomColor() {
152
+ const colors = [
153
+ "#FF6B6B",
154
+ "#4ECDC4",
155
+ "#45B7D1",
156
+ "#96CEB4",
157
+ "#FFEAA7",
158
+ "#DDA0DD",
159
+ "#98D8C8",
160
+ "#F7DC6F",
161
+ "#BB8FCE",
162
+ "#85C1E9",
163
+ ];
164
+ return colors[Math.floor(Math.random() * colors.length)];
165
+ }
166
+
167
+ function getRandomStyle() {
168
+ const styles = [
169
+ {
170
+ name: "solid",
171
+ description: "Solid background with white text",
172
+ },
173
+ {
174
+ name: "bright",
175
+ description: "Bright background with contrasting text",
176
+ },
177
+ {
178
+ name: "outline",
179
+ description: "Solid background with outlined text",
180
+ },
181
+ {
182
+ name: "shadow",
183
+ description: "Solid background with shadowed text",
184
+ },
185
+ {
186
+ name: "minimal",
187
+ description: "Clean minimal style with dark text",
188
+ },
189
+ ];
190
+ return styles[Math.floor(Math.random() * styles.length)];
191
+ }
192
+
193
+ function getStyleOptions(style, color) {
194
+ const darkerColor = adjustColor(color, -30);
195
+ const lighterColor = adjustColor(color, 30);
196
+
197
+ switch (style.name) {
198
+ case "bright":
199
+ return {
200
+ background: `"xc:${lighterColor}"`,
201
+ text: `-pointsize 24 -fill "${darkerColor}"`,
202
+ };
203
+ case "outline":
204
+ return {
205
+ background: `"xc:${color}"`,
206
+ text: `-pointsize 24 -fill none -stroke white -strokewidth 2`,
207
+ };
208
+ case "shadow":
209
+ return {
210
+ background: `"xc:${color}"`,
211
+ text: `-pointsize 24 -fill white -stroke black -strokewidth 1`,
212
+ };
213
+ case "minimal":
214
+ return {
215
+ background: `"xc:${lighterColor}"`,
216
+ text: `-pointsize 20 -fill "${darkerColor}"`,
217
+ };
218
+ default: // solid
219
+ return {
220
+ background: `"xc:${color}"`,
221
+ text: `-pointsize 24 -fill white`,
222
+ };
223
+ }
224
+ }
225
+
226
+ function adjustColor(hex, percent) {
227
+ // Remove # if present
228
+ hex = hex.replace("#", "");
229
+
230
+ // Ensure we have a valid 6-character hex
231
+ if (hex.length !== 6) {
232
+ console.error(`Invalid hex color: ${hex}`);
233
+ return "#000000";
234
+ }
235
+
236
+ // Parse RGB values
237
+ const r = parseInt(hex.substr(0, 2), 16);
238
+ const g = parseInt(hex.substr(2, 2), 16);
239
+ const b = parseInt(hex.substr(4, 2), 16);
240
+
241
+ // Check for NaN values
242
+ if (isNaN(r) || isNaN(g) || isNaN(b)) {
243
+ console.error(`Failed to parse hex color: ${hex}`);
244
+ return "#000000";
245
+ }
246
+
247
+ // Adjust brightness
248
+ const adjustValue = (value, percent) => {
249
+ const adjusted = value + (percent * 255) / 100;
250
+ return Math.max(0, Math.min(255, Math.round(adjusted)));
251
+ };
252
+
253
+ const newR = adjustValue(r, percent);
254
+ const newG = adjustValue(g, percent);
255
+ const newB = adjustValue(b, percent);
256
+
257
+ // Convert back to hex
258
+ const toHex = (value) => value.toString(16).padStart(2, "0");
259
+ return `#${toHex(newR)}${toHex(newG)}${toHex(newB)}`;
260
+ }
261
+
262
+ async function initDevelopmentAssets() {
263
+ const projectPath = findProjectRoot();
264
+ const devAssetsDir = path.join(projectPath, "dev-assets");
265
+
266
+ console.log("🎨 Setting up development assets...");
267
+
268
+ // Create directories
269
+ const dirs = ["images", "videos"];
270
+ dirs.forEach((dir) => {
271
+ const dirPath = path.join(devAssetsDir, dir);
272
+ if (!fs.existsSync(dirPath)) {
273
+ fs.mkdirSync(dirPath, { recursive: true });
274
+ console.log(`✓ Created ${dir}/ directory`);
275
+ }
276
+ });
277
+
278
+ // Copy starter assets from toolkit
279
+ const paths = resolveGxPaths();
280
+ const sourceAssetsDir = path.join(paths.templateDir, "dev-assets");
281
+
282
+ if (fs.existsSync(sourceAssetsDir)) {
283
+ console.log("📋 Copying starter assets...");
284
+
285
+ // Copy image assets
286
+ const sourceImagesDir = path.join(sourceAssetsDir, "images");
287
+ const destImagesDir = path.join(devAssetsDir, "images");
288
+
289
+ if (fs.existsSync(sourceImagesDir)) {
290
+ const imageFiles = fs.readdirSync(sourceImagesDir);
291
+ imageFiles.forEach((file) => {
292
+ const srcPath = path.join(sourceImagesDir, file);
293
+ const destPath = path.join(destImagesDir, file);
294
+ if (!fs.existsSync(destPath)) {
295
+ safeCopyFile(srcPath, destPath, `Asset: ${file}`);
296
+ }
297
+ });
298
+ }
299
+ }
300
+
301
+ // Add to .gitignore
302
+ const gitignorePath = path.join(projectPath, ".gitignore");
303
+ if (fs.existsSync(gitignorePath)) {
304
+ let gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
305
+ if (!gitignoreContent.includes("dev-assets/")) {
306
+ gitignoreContent +=
307
+ "\n# Development assets (add your own files here)\ndev-assets/\n";
308
+ fs.writeFileSync(gitignorePath, gitignoreContent);
309
+ console.log("✓ Added dev-assets/ to .gitignore");
310
+ }
311
+ }
312
+
313
+ console.log("✅ Development assets setup complete!");
314
+ console.log("");
315
+ console.log("📁 Directory structure:");
316
+ console.log(" dev-assets/");
317
+ console.log(" ├── images/ # Image placeholders");
318
+ console.log(" └── videos/ # Video placeholders");
319
+ console.log("");
320
+ console.log("💡 Commands:");
321
+ console.log(
322
+ " gxdev assets list # List all assets"
323
+ );
324
+ console.log(
325
+ " gxdev assets generate --size 800x600 # Generate placeholder"
326
+ );
327
+ console.log(
328
+ " gxdev assets generate --name logo --size 200x200 # Custom placeholder"
329
+ );
330
+ console.log(
331
+ " gxdev assets generate --name banner --count 5 # Generate 5 variants"
332
+ );
333
+ }
334
+
335
+ module.exports = {
336
+ assetsCommand,
337
+ };
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Build Command
3
+ *
4
+ * Builds the plugin for production and packages it as a .gxp file.
5
+ */
6
+
7
+ const path = require("path");
8
+ const fs = require("fs");
9
+ const shell = require("shelljs");
10
+ const archiver = require("archiver");
11
+ const { exportCmd } = require("../constants");
12
+ const { findProjectRoot, resolveGxPaths } = require("../utils");
13
+
14
+ /**
15
+ * Get the plugin name from package.json
16
+ */
17
+ function getPluginName(projectPath) {
18
+ try {
19
+ const packageJsonPath = path.join(projectPath, "package.json");
20
+ if (fs.existsSync(packageJsonPath)) {
21
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
22
+ // Clean the name for use as a filename
23
+ const name = packageJson.name || "plugin";
24
+ return name
25
+ .replace(/^@[^/]+\//, "") // Remove scope like @company/
26
+ .replace(/[^a-zA-Z0-9-_]/g, "-"); // Replace invalid chars with dash
27
+ }
28
+ } catch (error) {
29
+ console.warn("Could not read package.json, using default plugin name");
30
+ }
31
+ return "plugin";
32
+ }
33
+
34
+ /**
35
+ * Package the built plugin into a .gxp file
36
+ * @param {string} projectPath - Project root path
37
+ * @param {string} buildPath - Path where built files are (dist/build/)
38
+ * @param {string} outputPath - Path where .gxp file should be created (dist/)
39
+ */
40
+ async function packagePlugin(projectPath, buildPath, outputPath) {
41
+ const pluginName = getPluginName(projectPath);
42
+
43
+ console.log("\n📦 Packaging plugin...");
44
+
45
+ // Read app-manifest.json to get asset_dir
46
+ const manifestPath = path.join(projectPath, "app-manifest.json");
47
+ let assetDir = "/src/assets/"; // Default
48
+
49
+ if (fs.existsSync(manifestPath)) {
50
+ try {
51
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
52
+ if (manifest.asset_dir) {
53
+ assetDir = manifest.asset_dir;
54
+ }
55
+ } catch (error) {
56
+ console.warn("⚠️ Could not parse app-manifest.json, using default asset_dir");
57
+ }
58
+ } else {
59
+ console.warn("⚠️ app-manifest.json not found");
60
+ }
61
+
62
+ // Resolve asset directory path (remove leading slash for path.join)
63
+ const assetDirClean = assetDir.replace(/^\//, "").replace(/\/$/, "");
64
+ const assetSourcePath = path.join(projectPath, assetDirClean);
65
+ const assetDestPath = path.join(buildPath, "assets");
66
+
67
+ // Copy assets to dist/build/assets
68
+ if (fs.existsSync(assetSourcePath)) {
69
+ console.log(`📂 Copying assets from ${assetDirClean}/ to dist/build/assets/`);
70
+
71
+ // Create assets directory in build
72
+ if (!fs.existsSync(assetDestPath)) {
73
+ fs.mkdirSync(assetDestPath, { recursive: true });
74
+ }
75
+
76
+ // Copy all files from asset source to build/assets
77
+ copyDirectorySync(assetSourcePath, assetDestPath);
78
+ console.log("✓ Assets copied");
79
+ } else {
80
+ console.log(`ℹ️ No assets directory found at ${assetDirClean}/`);
81
+ }
82
+
83
+ // Copy app-manifest.json to dist/build/
84
+ if (fs.existsSync(manifestPath)) {
85
+ const manifestDestPath = path.join(buildPath, "app-manifest.json");
86
+ fs.copyFileSync(manifestPath, manifestDestPath);
87
+ console.log("✓ app-manifest.json copied to dist/build/");
88
+ }
89
+
90
+ // Create the .gxp package (zip file) in dist/
91
+ const gxpFileName = `${pluginName}.gxp`;
92
+ const gxpFilePath = path.join(outputPath, gxpFileName);
93
+
94
+ console.log(`📦 Creating ${gxpFileName}...`);
95
+
96
+ await createGxpPackage(buildPath, gxpFilePath);
97
+
98
+ console.log(`\n✅ Plugin packaged successfully!`);
99
+ console.log(`📁 Build files: dist/build/`);
100
+ console.log(`📁 Package: dist/${gxpFileName}`);
101
+
102
+ return gxpFilePath;
103
+ }
104
+
105
+ /**
106
+ * Recursively copy a directory
107
+ */
108
+ function copyDirectorySync(src, dest) {
109
+ if (!fs.existsSync(dest)) {
110
+ fs.mkdirSync(dest, { recursive: true });
111
+ }
112
+
113
+ const entries = fs.readdirSync(src, { withFileTypes: true });
114
+
115
+ for (const entry of entries) {
116
+ const srcPath = path.join(src, entry.name);
117
+ const destPath = path.join(dest, entry.name);
118
+
119
+ // Skip .gitkeep files
120
+ if (entry.name === ".gitkeep") continue;
121
+
122
+ if (entry.isDirectory()) {
123
+ copyDirectorySync(srcPath, destPath);
124
+ } else {
125
+ fs.copyFileSync(srcPath, destPath);
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Create the .gxp package (zip file containing plugin files)
132
+ */
133
+ function createGxpPackage(distPath, outputPath) {
134
+ return new Promise((resolve, reject) => {
135
+ const output = fs.createWriteStream(outputPath);
136
+ const archive = archiver("zip", {
137
+ zlib: { level: 9 } // Maximum compression
138
+ });
139
+
140
+ output.on("close", () => {
141
+ const sizeKB = (archive.pointer() / 1024).toFixed(2);
142
+ console.log(`✓ Package created (${sizeKB} KB)`);
143
+ resolve();
144
+ });
145
+
146
+ archive.on("error", (err) => {
147
+ reject(err);
148
+ });
149
+
150
+ archive.pipe(output);
151
+
152
+ // Add all JS files from dist
153
+ const jsFiles = fs.readdirSync(distPath).filter(f => f.endsWith(".js"));
154
+ jsFiles.forEach(file => {
155
+ archive.file(path.join(distPath, file), { name: file });
156
+ });
157
+
158
+ // Add all CSS files from dist
159
+ const cssFiles = fs.readdirSync(distPath).filter(f => f.endsWith(".css"));
160
+ cssFiles.forEach(file => {
161
+ archive.file(path.join(distPath, file), { name: file });
162
+ });
163
+
164
+ // Add app-manifest.json
165
+ const manifestPath = path.join(distPath, "app-manifest.json");
166
+ if (fs.existsSync(manifestPath)) {
167
+ archive.file(manifestPath, { name: "app-manifest.json" });
168
+ }
169
+
170
+ // Add assets directory
171
+ const assetsPath = path.join(distPath, "assets");
172
+ if (fs.existsSync(assetsPath)) {
173
+ archive.directory(assetsPath, "assets");
174
+ }
175
+
176
+ archive.finalize();
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Build command - builds the plugin for production
182
+ */
183
+ async function buildCommand(argv) {
184
+ const projectPath = findProjectRoot();
185
+ const paths = resolveGxPaths();
186
+ const distPath = path.join(projectPath, "dist");
187
+ const buildPath = path.join(distPath, "build");
188
+
189
+ console.log("🔨 Building plugin...\n");
190
+
191
+ const envVars = [];
192
+
193
+ // check if vite.config.js exists locally
194
+ let viteConfigPath = paths.viteConfigPath;
195
+ const localViteConfigPath = path.join(projectPath, "vite.config.js");
196
+ if (fs.existsSync(localViteConfigPath)) {
197
+ viteConfigPath = localViteConfigPath;
198
+ console.log(`📁 Using local vite.config.js: ${viteConfigPath}`);
199
+ }
200
+
201
+ // Set variables only if not already defined in environment
202
+ if (!process.env.NODE_LOG_LEVEL) {
203
+ envVars.push(
204
+ `${exportCmd} NODE_LOG_LEVEL=${argv["node-log-level"] || "error"}`
205
+ );
206
+ }
207
+ if (!process.env.COMPONENT_PATH) {
208
+ envVars.push(
209
+ `${exportCmd} COMPONENT_PATH=${argv["component-path"] || "./src/Plugin.vue"}`
210
+ );
211
+ }
212
+
213
+ const command = [
214
+ ...envVars,
215
+ `npx vite build --config "${viteConfigPath}"`,
216
+ ].join(" && ");
217
+
218
+ const result = shell.exec(command);
219
+
220
+ // Only proceed with packaging if build succeeded
221
+ if (result.code === 0) {
222
+ try {
223
+ // Move built files from dist/ to dist/build/
224
+ await moveBuildFiles(distPath, buildPath);
225
+ // Package the plugin (reads from buildPath, outputs .gxp to distPath)
226
+ await packagePlugin(projectPath, buildPath, distPath);
227
+ } catch (error) {
228
+ console.error("❌ Error packaging plugin:", error.message);
229
+ process.exit(1);
230
+ }
231
+ } else {
232
+ console.error("❌ Build failed");
233
+ process.exit(1);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Move built JS/CSS files from dist/ to dist/build/
239
+ */
240
+ async function moveBuildFiles(distPath, buildPath) {
241
+ // Create build directory
242
+ if (!fs.existsSync(buildPath)) {
243
+ fs.mkdirSync(buildPath, { recursive: true });
244
+ }
245
+
246
+ // Move all JS and CSS files to build directory
247
+ const files = fs.readdirSync(distPath);
248
+ for (const file of files) {
249
+ if (file.endsWith(".js") || file.endsWith(".css")) {
250
+ const srcFile = path.join(distPath, file);
251
+ const destFile = path.join(buildPath, file);
252
+ fs.renameSync(srcFile, destFile);
253
+ }
254
+ }
255
+ }
256
+
257
+ module.exports = {
258
+ buildCommand,
259
+ };