@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,180 @@
1
+ /**
2
+ * File Utilities
3
+ *
4
+ * Handles file operations like safe copying and package.json management.
5
+ */
6
+
7
+ const path = require("path");
8
+ const fs = require("fs");
9
+ const shell = require("shelljs");
10
+ const {
11
+ REQUIRED_DEPENDENCIES,
12
+ REQUIRED_DEV_DEPENDENCIES,
13
+ DEFAULT_SCRIPTS,
14
+ } = require("../constants");
15
+ const { loadGlobalConfig } = require("./paths");
16
+
17
+ /**
18
+ * Copies a file from source to destination, creating directories if needed
19
+ */
20
+ function safeCopyFile(src, dest, description) {
21
+ if (!fs.existsSync(dest)) {
22
+ console.log(`Creating ${description}`);
23
+ const destDir = path.dirname(dest);
24
+ if (!fs.existsSync(destDir)) {
25
+ fs.mkdirSync(destDir, { recursive: true });
26
+ }
27
+ fs.copyFileSync(src, dest);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Creates package.json for new projects
33
+ */
34
+ function createPackageJson(projectPath, projectName) {
35
+ const packageJsonPath = path.join(projectPath, "package.json");
36
+ const globalConfig = loadGlobalConfig();
37
+
38
+ const packageJson = {
39
+ name: projectName,
40
+ version: "1.0.0",
41
+ description: `GxP Plugin: ${projectName}`,
42
+ main: "main.js",
43
+ scripts: {
44
+ ...DEFAULT_SCRIPTS,
45
+ placeholder:
46
+ "gxdev assets generate --size 400x300 --name custom-placeholder",
47
+ },
48
+ dependencies: REQUIRED_DEPENDENCIES,
49
+ devDependencies: REQUIRED_DEV_DEPENDENCIES,
50
+ author: globalConfig.author || "Your Name",
51
+ license: "ISC",
52
+ };
53
+
54
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
55
+ console.log("โœ“ Created package.json");
56
+ }
57
+
58
+ /**
59
+ * Installs npm dependencies
60
+ */
61
+ function installDependencies(projectPath) {
62
+ console.log("\n๐Ÿ“ฆ Installing dependencies...");
63
+ const currentDir = process.cwd();
64
+
65
+ try {
66
+ process.chdir(projectPath);
67
+ const result = shell.exec("npm install", { silent: false });
68
+ if (result.code !== 0) {
69
+ console.warn("โš  npm install completed with warnings");
70
+ }
71
+ } finally {
72
+ process.chdir(currentDir);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Updates existing project's package.json with missing dependencies and scripts
78
+ */
79
+ function updateExistingProject(projectPath) {
80
+ const packageJsonPath = path.join(projectPath, "package.json");
81
+
82
+ if (!fs.existsSync(packageJsonPath)) {
83
+ return false;
84
+ }
85
+
86
+ try {
87
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
88
+ let updated = false;
89
+
90
+ // Check and add missing dependencies
91
+ if (!packageJson.dependencies) {
92
+ packageJson.dependencies = {};
93
+ }
94
+
95
+ for (const [dep, version] of Object.entries(REQUIRED_DEPENDENCIES)) {
96
+ if (!packageJson.dependencies[dep]) {
97
+ packageJson.dependencies[dep] = version;
98
+ console.log(` + Adding dependency: ${dep}@${version}`);
99
+ updated = true;
100
+ }
101
+ }
102
+
103
+ // Check and add missing dev dependencies
104
+ if (!packageJson.devDependencies) {
105
+ packageJson.devDependencies = {};
106
+ }
107
+
108
+ for (const [dep, version] of Object.entries(REQUIRED_DEV_DEPENDENCIES)) {
109
+ if (!packageJson.devDependencies[dep]) {
110
+ packageJson.devDependencies[dep] = version;
111
+ console.log(` + Adding devDependency: ${dep}@${version}`);
112
+ updated = true;
113
+ }
114
+ }
115
+
116
+ // Check and add missing scripts
117
+ if (!packageJson.scripts) {
118
+ packageJson.scripts = {};
119
+ }
120
+
121
+ for (const [script, command] of Object.entries(DEFAULT_SCRIPTS)) {
122
+ if (!packageJson.scripts[script]) {
123
+ packageJson.scripts[script] = command;
124
+ console.log(` + Adding script: ${script}`);
125
+ updated = true;
126
+ }
127
+ }
128
+
129
+ if (updated) {
130
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
131
+ console.log("โœ“ Updated package.json");
132
+ return true;
133
+ }
134
+
135
+ return false;
136
+ } catch (error) {
137
+ console.error("Error updating package.json:", error.message);
138
+ return false;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Checks if ImageMagick is available globally
144
+ */
145
+ function isImageMagickInstalled() {
146
+ return shell.which("magick") !== null || shell.which("convert") !== null;
147
+ }
148
+
149
+ /**
150
+ * Ensures ImageMagick is available for placeholder generation
151
+ */
152
+ function ensureImageMagickInstalled() {
153
+ if (isImageMagickInstalled()) {
154
+ console.log("โœ“ ImageMagick is available");
155
+ return true;
156
+ }
157
+
158
+ console.log("โš ๏ธ ImageMagick not found");
159
+ console.log("๐Ÿ“ฆ ImageMagick is required for generating placeholder images");
160
+ console.log("");
161
+ console.log("๐ŸŽ macOS: brew install imagemagick");
162
+ console.log("๐Ÿง Ubuntu/Debian: sudo apt-get install imagemagick");
163
+ console.log(
164
+ "๐ŸŸฆ Windows: Download from https://imagemagick.org/script/download.php#windows"
165
+ );
166
+ console.log("");
167
+ console.log("๐Ÿ’ก After installation, you can generate placeholders with:");
168
+ console.log(" gxdev assets generate --size 400x300 --name my-placeholder");
169
+ console.log(" gxdev assets generate --name icons --count 3 --size 64x64");
170
+ return false;
171
+ }
172
+
173
+ module.exports = {
174
+ safeCopyFile,
175
+ createPackageJson,
176
+ installDependencies,
177
+ updateExistingProject,
178
+ isImageMagickInstalled,
179
+ ensureImageMagickInstalled,
180
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Utility Functions Index
3
+ *
4
+ * Re-exports all utility modules for convenient importing.
5
+ */
6
+
7
+ const paths = require("./paths");
8
+ const ssl = require("./ssl");
9
+ const files = require("./files");
10
+ const prompts = require("./prompts");
11
+
12
+ module.exports = {
13
+ ...paths,
14
+ ...ssl,
15
+ ...files,
16
+ ...prompts,
17
+ };
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Path Resolution Utilities
3
+ *
4
+ * Handles finding project roots and resolving paths to toolkit resources.
5
+ */
6
+
7
+ const path = require("path");
8
+ const fs = require("fs");
9
+ const os = require("os");
10
+ const { isWin, PACKAGE_NAME } = require("../constants");
11
+
12
+ /**
13
+ * Determines the correct binary name based on platform and architecture
14
+ */
15
+ function getBinaryName() {
16
+ if (isWin) return "gento-win";
17
+ if (process.arch === "x64") return "gento-darwin-amd64";
18
+ return "gento";
19
+ }
20
+
21
+ /**
22
+ * Finds the project root directory by looking for package.json
23
+ */
24
+ function findProjectRoot() {
25
+ let currentDir = process.cwd();
26
+
27
+ while (currentDir !== path.dirname(currentDir)) {
28
+ if (fs.existsSync(path.join(currentDir, "package.json"))) {
29
+ return currentDir;
30
+ }
31
+ currentDir = path.dirname(currentDir);
32
+ }
33
+
34
+ return process.cwd(); // Fallback to current directory
35
+ }
36
+
37
+ /**
38
+ * Resolves paths for gx-devtools resources based on installation context
39
+ *
40
+ * Directory structure:
41
+ * - /template/ - Files copied to new projects during init
42
+ * - /runtime/ - Files used from node_modules (not copied to projects)
43
+ * - /socket-events/ - Socket event templates
44
+ */
45
+ function resolveGxPaths() {
46
+ const projectRoot = findProjectRoot();
47
+
48
+ // Try local installation first
49
+ const localNodeModules = path.join(projectRoot, "node_modules", PACKAGE_NAME);
50
+ if (fs.existsSync(localNodeModules)) {
51
+ return {
52
+ gentoPath: path.join(localNodeModules, "bin", getBinaryName()),
53
+ viteConfigPath: path.join(localNodeModules, "runtime", "vite.config.js"),
54
+ templateDir: path.join(localNodeModules, "template"),
55
+ runtimeDir: path.join(localNodeModules, "runtime"),
56
+ socketEventsDir: path.join(localNodeModules, "socket-events"),
57
+ packageRoot: localNodeModules,
58
+ // Legacy alias for backward compatibility
59
+ configDir: path.join(localNodeModules, "template"),
60
+ };
61
+ }
62
+
63
+ // Try global installation (or running from the toolkit itself)
64
+ const globalNodeModules = path.join(__dirname, "..", "..", "..");
65
+ return {
66
+ gentoPath: path.join(globalNodeModules, "bin", getBinaryName()),
67
+ viteConfigPath: path.join(globalNodeModules, "runtime", "vite.config.js"),
68
+ templateDir: path.join(globalNodeModules, "template"),
69
+ runtimeDir: path.join(globalNodeModules, "runtime"),
70
+ socketEventsDir: path.join(globalNodeModules, "socket-events"),
71
+ packageRoot: globalNodeModules,
72
+ // Legacy alias for backward compatibility
73
+ configDir: path.join(globalNodeModules, "template"),
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Resolves file path checking local project first, then package
79
+ *
80
+ * @param {string} fileName - The file to find
81
+ * @param {string} subDir - Subdirectory within the search locations
82
+ * @param {string} packageLocation - Which package directory to check: 'template', 'runtime', or 'socket-events'
83
+ */
84
+ function resolveFilePath(fileName, subDir = "", packageLocation = "template") {
85
+ const projectRoot = findProjectRoot();
86
+ const paths = resolveGxPaths();
87
+
88
+ // Check local project first
89
+ const localPath = path.join(projectRoot, subDir, fileName);
90
+ if (fs.existsSync(localPath)) {
91
+ return { path: localPath, isLocal: true };
92
+ }
93
+
94
+ // Determine which package directory to check
95
+ let packageDir;
96
+ switch (packageLocation) {
97
+ case "runtime":
98
+ packageDir = paths.runtimeDir;
99
+ break;
100
+ case "socket-events":
101
+ packageDir = paths.socketEventsDir;
102
+ break;
103
+ default:
104
+ packageDir = paths.templateDir;
105
+ }
106
+
107
+ // Fall back to package version
108
+ const packagePath = path.join(packageDir, subDir, fileName);
109
+ if (fs.existsSync(packagePath)) {
110
+ return { path: packagePath, isLocal: false };
111
+ }
112
+
113
+ // Return package path even if doesn't exist (for error handling)
114
+ return { path: packagePath, isLocal: false };
115
+ }
116
+
117
+ /**
118
+ * Loads global configuration if available
119
+ */
120
+ function loadGlobalConfig() {
121
+ const globalConfigPath = path.join(os.homedir(), "gxdev-default-config.json");
122
+ if (fs.existsSync(globalConfigPath)) {
123
+ try {
124
+ return JSON.parse(fs.readFileSync(globalConfigPath, "utf-8"));
125
+ } catch (error) {
126
+ console.warn("Warning: Could not parse global configuration");
127
+ }
128
+ }
129
+ return {};
130
+ }
131
+
132
+ module.exports = {
133
+ getBinaryName,
134
+ findProjectRoot,
135
+ resolveGxPaths,
136
+ resolveFilePath,
137
+ loadGlobalConfig,
138
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * User Prompts Utility
3
+ *
4
+ * Handles interactive CLI prompts.
5
+ */
6
+
7
+ const readline = require("readline");
8
+
9
+ /**
10
+ * Prompts user for input
11
+ * @param {string} question - The question to ask
12
+ * @returns {Promise<string>} The user's answer
13
+ */
14
+ function promptUser(question) {
15
+ const rl = readline.createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ });
19
+
20
+ return new Promise((resolve) => {
21
+ rl.question(question, (answer) => {
22
+ rl.close();
23
+ resolve(answer);
24
+ });
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Prompts user for yes/no confirmation
30
+ * @param {string} question - The question to ask
31
+ * @param {boolean} defaultYes - Whether default is yes (Y/n) or no (y/N)
32
+ * @returns {Promise<boolean>} True if user confirmed
33
+ */
34
+ async function confirmPrompt(question, defaultYes = true) {
35
+ const suffix = defaultYes ? "(Y/n)" : "(y/N)";
36
+ const answer = await promptUser(`${question} ${suffix}: `);
37
+
38
+ if (answer === "") {
39
+ return defaultYes;
40
+ }
41
+
42
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
43
+ }
44
+
45
+ /**
46
+ * Prompts user to select from a list of options
47
+ * @param {string} question - The question to ask
48
+ * @param {string[]} options - Array of options
49
+ * @returns {Promise<number>} Index of selected option
50
+ */
51
+ async function selectPrompt(question, options) {
52
+ console.log(question);
53
+ options.forEach((opt, i) => {
54
+ console.log(` ${i + 1}. ${opt}`);
55
+ });
56
+
57
+ const answer = await promptUser("Enter number: ");
58
+ const index = parseInt(answer, 10) - 1;
59
+
60
+ if (index >= 0 && index < options.length) {
61
+ return index;
62
+ }
63
+
64
+ return 0; // Default to first option
65
+ }
66
+
67
+ module.exports = {
68
+ promptUser,
69
+ confirmPrompt,
70
+ selectPrompt,
71
+ };
@@ -0,0 +1,233 @@
1
+ /**
2
+ * SSL Certificate Management
3
+ *
4
+ * Handles SSL certificate generation and management using mkcert.
5
+ */
6
+
7
+ const path = require("path");
8
+ const fs = require("fs");
9
+ const shell = require("shelljs");
10
+
11
+ /**
12
+ * Checks if mkcert is installed globally
13
+ */
14
+ function isMkcertInstalled() {
15
+ return shell.which("mkcert") !== null;
16
+ }
17
+
18
+ /**
19
+ * Installs mkcert globally if not already installed
20
+ */
21
+ function ensureMkcertInstalled() {
22
+ if (isMkcertInstalled()) {
23
+ console.log("โœ“ mkcert is already installed globally");
24
+ return true;
25
+ }
26
+
27
+ console.log("Installing mkcert globally...");
28
+ const result = shell.exec("npm install -g mkcert", { silent: true });
29
+
30
+ if (result.code === 0) {
31
+ console.log("โœ“ mkcert installed successfully");
32
+ return true;
33
+ } else {
34
+ console.warn("โš  Could not install mkcert globally, will use local version");
35
+ return false;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Finds existing SSL certificates in the certs directory, including those with suffixes
41
+ */
42
+ function findExistingCertificates(certsDir) {
43
+ if (!fs.existsSync(certsDir)) {
44
+ return null;
45
+ }
46
+
47
+ const files = fs.readdirSync(certsDir);
48
+
49
+ // Look for localhost certificates (with or without suffixes)
50
+ const certFile = files.find(
51
+ (f) =>
52
+ f.startsWith("localhost") && f.endsWith(".pem") && !f.includes("-key")
53
+ );
54
+ const keyFile = files.find(
55
+ (f) => f.startsWith("localhost") && f.endsWith("-key.pem")
56
+ );
57
+
58
+ if (certFile && keyFile) {
59
+ const certPath = path.join(certsDir, certFile);
60
+ const keyPath = path.join(certsDir, keyFile);
61
+
62
+ // Verify files actually exist and have content
63
+ try {
64
+ const certStats = fs.statSync(certPath);
65
+ const keyStats = fs.statSync(keyPath);
66
+
67
+ if (certStats.size > 0 && keyStats.size > 0) {
68
+ return { certPath, keyPath };
69
+ }
70
+ } catch (error) {
71
+ // Files don't exist or can't be read
72
+ }
73
+ }
74
+
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Cleans up old SSL certificate files to prevent naming conflicts
80
+ */
81
+ function cleanupOldCertificates(certsDir) {
82
+ if (!fs.existsSync(certsDir)) {
83
+ return;
84
+ }
85
+
86
+ try {
87
+ const files = fs.readdirSync(certsDir);
88
+ const certFiles = files.filter(
89
+ (f) =>
90
+ f.startsWith("localhost") &&
91
+ (f.endsWith(".pem") || f.endsWith("-key.pem"))
92
+ );
93
+
94
+ if (certFiles.length > 0) {
95
+ console.log("๐Ÿงน Cleaning up old certificate files...");
96
+ certFiles.forEach((file) => {
97
+ const filePath = path.join(certsDir, file);
98
+ try {
99
+ fs.unlinkSync(filePath);
100
+ console.log(` Removed: ${file}`);
101
+ } catch (error) {
102
+ console.warn(` Could not remove ${file}: ${error.message}`);
103
+ }
104
+ });
105
+ }
106
+ } catch (error) {
107
+ console.warn("โš  Could not clean up old certificates:", error.message);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Generates SSL certificates for localhost using mkcert
113
+ */
114
+ function generateSSLCertificates(projectPath) {
115
+ const certsDir = path.join(projectPath, ".certs");
116
+
117
+ // Create .certs directory
118
+ if (!fs.existsSync(certsDir)) {
119
+ fs.mkdirSync(certsDir, { recursive: true });
120
+ }
121
+
122
+ // Check for existing certificates (including those with suffixes like +2)
123
+ const existingCerts = findExistingCertificates(certsDir);
124
+ if (existingCerts) {
125
+ console.log("โœ“ SSL certificates already exist");
126
+ return existingCerts;
127
+ }
128
+
129
+ console.log("Generating SSL certificates for localhost...");
130
+
131
+ // Clean up any leftover certificate files to avoid naming conflicts
132
+ cleanupOldCertificates(certsDir);
133
+
134
+ // Try global mkcert first
135
+ let mkcertCmd = "mkcert";
136
+ if (!isMkcertInstalled()) {
137
+ // Use local mkcert via npx
138
+ mkcertCmd = "npx mkcert";
139
+ }
140
+
141
+ // Change to certs directory and generate certificates
142
+ const currentDir = process.cwd();
143
+ try {
144
+ process.chdir(certsDir);
145
+
146
+ // Install CA if needed (only for global mkcert)
147
+ if (isMkcertInstalled()) {
148
+ shell.exec(`${mkcertCmd} -install`, { silent: true });
149
+ }
150
+
151
+ // Generate certificates for localhost
152
+ const result = shell.exec(`${mkcertCmd} localhost 127.0.0.1 ::1`, {
153
+ silent: true,
154
+ });
155
+
156
+ if (result.code === 0) {
157
+ // Find the actual generated certificate files
158
+ const generatedCerts = findExistingCertificates(certsDir);
159
+ if (generatedCerts) {
160
+ console.log("โœ“ SSL certificates generated successfully");
161
+ console.log(
162
+ `๐Ÿ“ Certificate: ${path.basename(generatedCerts.certPath)}`
163
+ );
164
+ console.log(`๐Ÿ”‘ Key: ${path.basename(generatedCerts.keyPath)}`);
165
+ return generatedCerts;
166
+ } else {
167
+ console.warn(
168
+ "โš  Certificates generated but not found in expected location"
169
+ );
170
+ return null;
171
+ }
172
+ } else {
173
+ console.warn(
174
+ "โš  Failed to generate SSL certificates, falling back to HTTP"
175
+ );
176
+ return null;
177
+ }
178
+ } catch (error) {
179
+ console.warn("โš  Error generating SSL certificates:", error.message);
180
+ return null;
181
+ } finally {
182
+ process.chdir(currentDir);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Updates the .env file with the actual SSL certificate paths
188
+ */
189
+ function updateEnvWithCertPaths(projectPath, certs) {
190
+ const envPath = path.join(projectPath, ".env");
191
+
192
+ if (!fs.existsSync(envPath)) {
193
+ console.warn("โš  .env file not found, skipping certificate path update");
194
+ return;
195
+ }
196
+
197
+ try {
198
+ let envContent = fs.readFileSync(envPath, "utf-8");
199
+
200
+ // Get just the filenames from the full paths
201
+ const certFileName = path.basename(certs.certPath);
202
+ const keyFileName = path.basename(certs.keyPath);
203
+
204
+ // Update CERT_PATH and KEY_PATH with actual filenames
205
+ envContent = envContent.replace(
206
+ /CERT_PATH=.*$/m,
207
+ `CERT_PATH=.certs/${certFileName}`
208
+ );
209
+ envContent = envContent.replace(
210
+ /KEY_PATH=.*$/m,
211
+ `KEY_PATH=.certs/${keyFileName}`
212
+ );
213
+
214
+ fs.writeFileSync(envPath, envContent);
215
+ console.log("โœ“ Updated .env with SSL certificate paths");
216
+ console.log(` CERT_PATH=.certs/${certFileName}`);
217
+ console.log(` KEY_PATH=.certs/${keyFileName}`);
218
+ } catch (error) {
219
+ console.warn(
220
+ "โš  Could not update .env with certificate paths:",
221
+ error.message
222
+ );
223
+ }
224
+ }
225
+
226
+ module.exports = {
227
+ isMkcertInstalled,
228
+ ensureMkcertInstalled,
229
+ findExistingCertificates,
230
+ cleanupOldCertificates,
231
+ generateSSLCertificates,
232
+ updateEnvWithCertPaths,
233
+ };
@@ -0,0 +1 @@
1
+