hoversource 1.0.0

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
1
+ import Module, { register } from "node:module";
2
+ import { pathToFileURL, fileURLToPath } from "node:url";
3
+ import path from "node:path";
4
+ import fs from "node:fs";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ // Dynamically write the CJS wrapper to dist/ if it doesn't exist (since tsc doesn't transpile .cjs files)
8
+ const cjsWrapperPath = path.resolve(__dirname, "./custom-jsx-dev-runtime-cjs.cjs");
9
+ if (!fs.existsSync(cjsWrapperPath)) {
10
+ const cjsContent = `const path = require("path");
11
+ const wrapperPath = module.filename;
12
+ const originalPath = (globalThis.__HOVERSOURCE_ORIGINAL_CJS_RUNTIMES__ && globalThis.__HOVERSOURCE_ORIGINAL_CJS_RUNTIMES__[wrapperPath]) || "react/jsx-dev-runtime";
13
+ const original = require(originalPath);
14
+
15
+ exports.Fragment = original.Fragment;
16
+
17
+ exports.jsxDEV = function(type, props, key, isStaticChildren, source, self) {
18
+ if (typeof window === "undefined" && source && props && typeof type === "string") {
19
+ let filePath = source.fileName || "";
20
+ try {
21
+ const relative = path.relative(process.cwd(), filePath).replace(/\\\\/g, "/");
22
+ filePath = relative;
23
+ } catch {}
24
+
25
+ props["data-hoversource-loc"] = \`\${filePath}:\${source.lineNumber}:\${source.columnNumber}\`;
26
+ }
27
+ return original.jsxDEV(type, props, key, isStaticChildren, source, self);
28
+ };
29
+
30
+ exports.jsxsDEV = original.jsxsDEV || exports.jsxDEV;
31
+ `;
32
+ fs.writeFileSync(cjsWrapperPath, cjsContent, "utf-8");
33
+ }
34
+ // 1. Register ESM loader
35
+ try {
36
+ const loaderUrl = pathToFileURL(path.resolve(__dirname, "./loader.js")).href;
37
+ register(loaderUrl);
38
+ }
39
+ catch (err) {
40
+ if (process.env.DEBUG) {
41
+ console.debug("[HoverSource Bootstrap] Failed to register ESM loader:", err.message);
42
+ }
43
+ }
44
+ // 2. Patch CommonJS _resolveFilename
45
+ try {
46
+ const originalResolve = Module._resolveFilename;
47
+ // Registry for tracking original runtime paths mapped to the wrapper path
48
+ globalThis.__HOVERSOURCE_ORIGINAL_CJS_RUNTIMES__ = globalThis.__HOVERSOURCE_ORIGINAL_CJS_RUNTIMES__ || {};
49
+ Module._resolveFilename = function (request, parent, isMain, options) {
50
+ // Check for recursion guard: if parent is the CJS wrapper itself, do not redirect
51
+ const wrapperPath = path.resolve(__dirname, "./custom-jsx-dev-runtime-cjs.cjs");
52
+ const parentFile = parent?.filename || "";
53
+ if (parentFile === wrapperPath) {
54
+ return originalResolve.apply(this, arguments);
55
+ }
56
+ const isTarget = request === "react/jsx-dev-runtime" ||
57
+ request.endsWith("/react/jsx-dev-runtime") ||
58
+ request.replaceAll("\\", "/").includes("react/jsx-dev-runtime") ||
59
+ request.includes("next/dist/compiled/react/jsx-dev-runtime");
60
+ if (isTarget) {
61
+ try {
62
+ const originalPath = originalResolve.apply(this, arguments);
63
+ globalThis.__HOVERSOURCE_ORIGINAL_CJS_RUNTIMES__[wrapperPath] = originalPath;
64
+ return wrapperPath;
65
+ }
66
+ catch { }
67
+ }
68
+ return originalResolve.apply(this, arguments);
69
+ };
70
+ }
71
+ catch (err) {
72
+ if (process.env.DEBUG) {
73
+ console.debug("[HoverSource Bootstrap] Failed to patch CJS resolver:", err.message);
74
+ }
75
+ }
@@ -0,0 +1,19 @@
1
+ import { SslCredentials } from "./cert.js";
2
+ /**
3
+ * Returns the path/command for openssl.
4
+ * Fallbacks to Git for Windows openssl path if default openssl is not in the system path.
5
+ */
6
+ export declare function getOpensslCommand(): string;
7
+ /**
8
+ * Ensures the Root CA certificate exists and is registered in the OS Trusted Root store.
9
+ * Returns the path to the CA key and certificate files.
10
+ */
11
+ export declare function ensureRootCa(): {
12
+ caKeyPath: string;
13
+ caCertPath: string;
14
+ };
15
+ /**
16
+ * Generates and signs a local domain SSL certificate for the specified target host
17
+ * using the HoverSource Root CA.
18
+ */
19
+ export declare function getOrCreateCaSignedCert(targetHost: string): SslCredentials;
@@ -0,0 +1,180 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { execFileSync } from "node:child_process";
5
+ /**
6
+ * Returns the path/command for openssl.
7
+ * Fallbacks to Git for Windows openssl path if default openssl is not in the system path.
8
+ */
9
+ export function getOpensslCommand() {
10
+ if (process.platform === "win32") {
11
+ try {
12
+ execFileSync("openssl", ["version"], { stdio: "ignore" });
13
+ return "openssl";
14
+ }
15
+ catch {
16
+ const gitOpenssl = String.raw `C:\Program Files\Git\usr\bin\openssl.exe`;
17
+ if (fs.existsSync(gitOpenssl)) {
18
+ return gitOpenssl;
19
+ }
20
+ }
21
+ }
22
+ return "openssl";
23
+ }
24
+ /**
25
+ * Ensures the Root CA certificate exists and is registered in the OS Trusted Root store.
26
+ * Returns the path to the CA key and certificate files.
27
+ */
28
+ export function ensureRootCa() {
29
+ const sslDir = path.join(os.homedir(), ".hoversource", "ssl");
30
+ if (!fs.existsSync(sslDir)) {
31
+ fs.mkdirSync(sslDir, { recursive: true });
32
+ }
33
+ const caKeyPath = path.join(sslDir, "ca.key");
34
+ const caCertPath = path.join(sslDir, "ca.pem");
35
+ const openssl = getOpensslCommand();
36
+ // 1. Generate Root CA files if they don't exist
37
+ if (!fs.existsSync(caKeyPath) || !fs.existsSync(caCertPath)) {
38
+ try {
39
+ console.log(`[HoverSource SSL CA] Generating Root CA keys in ${sslDir}...`);
40
+ execFileSync(openssl, [
41
+ "req",
42
+ "-x509",
43
+ "-nodes",
44
+ "-new",
45
+ "-sha256",
46
+ "-days",
47
+ "3650",
48
+ "-newkey",
49
+ "rsa:2048",
50
+ "-keyout",
51
+ caKeyPath,
52
+ "-out",
53
+ caCertPath,
54
+ "-subj",
55
+ "/CN=HoverSourceRootCA",
56
+ ], { stdio: "ignore" });
57
+ }
58
+ catch (err) {
59
+ console.error(`[HoverSource SSL CA] Failed to generate Root CA:`, err.message);
60
+ throw new Error(`Failed to generate local Root CA keys. Please check openssl configuration.`);
61
+ }
62
+ }
63
+ // 2. Register Root CA to Windows Trusted Root store if not already done (Windows specific)
64
+ if (process.platform === "win32") {
65
+ try {
66
+ // Check if certificate is already trusted
67
+ execFileSync("certutil", ["-verifystore", "-user", "root", "HoverSourceRootCA"], { stdio: "ignore" });
68
+ }
69
+ catch {
70
+ try {
71
+ console.log(`[HoverSource SSL CA] Trusting HoverSource Development Root CA...`);
72
+ execFileSync("certutil", ["-addstore", "-user", "root", caCertPath], { stdio: "ignore" });
73
+ }
74
+ catch (err) {
75
+ console.warn(`[HoverSource SSL CA] Warning: Failed to trust Root CA in Windows certificate store:`, err.message);
76
+ }
77
+ }
78
+ }
79
+ return { caKeyPath, caCertPath };
80
+ }
81
+ /**
82
+ * Generates and signs a local domain SSL certificate for the specified target host
83
+ * using the HoverSource Root CA.
84
+ */
85
+ export function getOrCreateCaSignedCert(targetHost) {
86
+ const { caKeyPath, caCertPath } = ensureRootCa();
87
+ const tempDir = os.tmpdir();
88
+ const openssl = getOpensslCommand();
89
+ // Clean host name for filenames
90
+ const safeHost = targetHost.replace(/[^a-zA-Z0-9.-]/g, "_");
91
+ const domainKeyPath = path.join(tempDir, `hoversource-signed-${safeHost}.key`);
92
+ const domainCertPath = path.join(tempDir, `hoversource-signed-${safeHost}.crt`);
93
+ const domainCsrPath = path.join(tempDir, `hoversource-signed-${safeHost}.csr`);
94
+ const extConfigPath = path.join(tempDir, `hoversource-ext-${safeHost}.conf`);
95
+ // Check if it already exists and is relatively new (less than 30 days old)
96
+ if (fs.existsSync(domainKeyPath) && fs.existsSync(domainCertPath)) {
97
+ const keyStat = fs.statSync(domainKeyPath);
98
+ const ageInHours = (Date.now() - keyStat.mtimeMs) / (1000 * 60 * 60);
99
+ if (ageInHours < 24 * 30) {
100
+ return {
101
+ key: fs.readFileSync(domainKeyPath, "utf-8"),
102
+ cert: fs.readFileSync(domainCertPath, "utf-8"),
103
+ };
104
+ }
105
+ }
106
+ try {
107
+ // Generate extension config containing SAN (Subject Alternative Names)
108
+ const dnsNames = ["localhost", "127.0.0.1"];
109
+ if (targetHost && targetHost !== "localhost" && targetHost !== "127.0.0.1") {
110
+ dnsNames.push(targetHost);
111
+ }
112
+ const altNames = dnsNames.map((name, index) => {
113
+ const isIp = /^[0-9.]+$/.test(name);
114
+ return `${isIp ? "IP" : "DNS"}.${index + 1} = ${name}`;
115
+ }).join("\n");
116
+ const extConfig = `authorityKeyIdentifier=keyid,issuer
117
+ basicConstraints=CA:FALSE
118
+ keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
119
+ subjectAltName = @alt_names
120
+
121
+ [alt_names]
122
+ ${altNames}
123
+ `;
124
+ fs.writeFileSync(extConfigPath, extConfig.replaceAll("\r\n", "\n"), "utf-8");
125
+ // Generate Private Key and CSR for Domain
126
+ execFileSync(openssl, [
127
+ "req",
128
+ "-new",
129
+ "-newkey",
130
+ "rsa:2048",
131
+ "-nodes",
132
+ "-keyout",
133
+ domainKeyPath,
134
+ "-out",
135
+ domainCsrPath,
136
+ "-subj",
137
+ `/CN=${targetHost}`,
138
+ ], { stdio: "pipe" });
139
+ // Sign CSR with Root CA using Extension config
140
+ execFileSync(openssl, [
141
+ "x509",
142
+ "-req",
143
+ "-in",
144
+ domainCsrPath,
145
+ "-CA",
146
+ caCertPath,
147
+ "-CAkey",
148
+ caKeyPath,
149
+ "-CAcreateserial",
150
+ "-out",
151
+ domainCertPath,
152
+ "-days",
153
+ "365",
154
+ "-sha256",
155
+ "-extfile",
156
+ extConfigPath,
157
+ ], { stdio: "pipe" });
158
+ // Clean up temporary config and CSR
159
+ try {
160
+ fs.unlinkSync(extConfigPath);
161
+ fs.unlinkSync(domainCsrPath);
162
+ const serialPath = path.join(tempDir, `hoversource-signed-${safeHost}.srl`);
163
+ if (fs.existsSync(serialPath))
164
+ fs.unlinkSync(serialPath);
165
+ const localSerialPath = "ca.srl"; // Openssl sometimes places CA serial file in cwd
166
+ if (fs.existsSync(localSerialPath))
167
+ fs.unlinkSync(localSerialPath);
168
+ }
169
+ catch { }
170
+ return {
171
+ key: fs.readFileSync(domainKeyPath, "utf-8"),
172
+ cert: fs.readFileSync(domainCertPath, "utf-8"),
173
+ };
174
+ }
175
+ catch (err) {
176
+ const stderrStr = err.stderr ? ` Stderr: ${err.stderr.toString()}` : "";
177
+ console.error(`[HoverSource SSL CA] Failed to generate signed certificate for ${targetHost}:`, err.message + stderrStr);
178
+ throw new Error(`OpenSSL signed cert generation failed: ${err.message}${stderrStr}`);
179
+ }
180
+ }
package/dist/cert.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export interface SslCertConfig {
2
+ keyPath?: string;
3
+ certPath?: string;
4
+ targetHost?: string;
5
+ }
6
+ export interface SslCredentials {
7
+ key: string;
8
+ cert: string;
9
+ }
10
+ /**
11
+ * Returns SSL credentials for local HTTPS development.
12
+ * If user-defined key and cert paths are provided and exist, they are loaded.
13
+ * Otherwise, generates a self-signed SSL certificate for localhost dynamically in the temp folder.
14
+ */
15
+ export declare function getOrCreateLocalSslCert(config?: SslCertConfig): SslCredentials;
package/dist/cert.js ADDED
@@ -0,0 +1,76 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { execFileSync } from "node:child_process";
5
+ import { getOrCreateCaSignedCert, getOpensslCommand } from "./cert-ca.js";
6
+ /**
7
+ * Returns SSL credentials for local HTTPS development.
8
+ * If user-defined key and cert paths are provided and exist, they are loaded.
9
+ * Otherwise, generates a self-signed SSL certificate for localhost dynamically in the temp folder.
10
+ */
11
+ export function getOrCreateLocalSslCert(config = {}) {
12
+ if (config.keyPath && config.certPath) {
13
+ const keyAbsPath = path.resolve(config.keyPath);
14
+ const certAbsPath = path.resolve(config.certPath);
15
+ if (fs.existsSync(keyAbsPath) && fs.existsSync(certAbsPath)) {
16
+ return {
17
+ key: fs.readFileSync(keyAbsPath, "utf-8"),
18
+ cert: fs.readFileSync(certAbsPath, "utf-8"),
19
+ };
20
+ }
21
+ console.warn(`[HoverSource SSL] Provided certificate files not found: ${keyAbsPath} or ${certAbsPath}. Falling back to self-signed generator.`);
22
+ }
23
+ if (config.targetHost) {
24
+ try {
25
+ return getOrCreateCaSignedCert(config.targetHost);
26
+ }
27
+ catch (err) {
28
+ console.warn(`[HoverSource SSL] Failed to generate CA-signed cert for ${config.targetHost}. Falling back to default self-signed cert. Error: ${err.message}`);
29
+ }
30
+ }
31
+ // Fallback: Generate self-signed certificate in the OS temp directory
32
+ const tempDir = os.tmpdir();
33
+ const keyPath = path.join(tempDir, "hoversource-selfsigned-localhost.key");
34
+ const certPath = path.join(tempDir, "hoversource-selfsigned-localhost.crt");
35
+ // Re-use already generated certs in temp if they exist to avoid openssl cost on every startup
36
+ if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
37
+ const keyStat = fs.statSync(keyPath);
38
+ const ageInHours = (Date.now() - keyStat.mtimeMs) / (1000 * 60 * 60);
39
+ // Regenerate if older than 30 days
40
+ if (ageInHours < 24 * 30) {
41
+ return {
42
+ key: fs.readFileSync(keyPath, "utf-8"),
43
+ cert: fs.readFileSync(certPath, "utf-8"),
44
+ };
45
+ }
46
+ }
47
+ try {
48
+ console.log(`[HoverSource SSL] Generating self-signed SSL certificate for localhost...`);
49
+ // Run openssl command cross-platform.
50
+ const openssl = getOpensslCommand();
51
+ execFileSync(openssl, [
52
+ "req",
53
+ "-x509",
54
+ "-newkey",
55
+ "rsa:2048",
56
+ "-nodes",
57
+ "-sha256",
58
+ "-subj",
59
+ "/CN=localhost",
60
+ "-keyout",
61
+ keyPath,
62
+ "-out",
63
+ certPath,
64
+ "-days",
65
+ "365",
66
+ ], { stdio: "ignore" });
67
+ return {
68
+ key: fs.readFileSync(keyPath, "utf-8"),
69
+ cert: fs.readFileSync(certPath, "utf-8"),
70
+ };
71
+ }
72
+ catch (err) {
73
+ console.error(`[HoverSource SSL] Failed to generate self-signed certificate using 'openssl'. Make sure openssl is installed. Error: ${err.message}`);
74
+ throw new Error(`[HoverSource SSL] OpenSSL execution failed. Cannot start HTTPS proxy without valid certificates.`);
75
+ }
76
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ export { isPortFree, findFreePort, getPidUsingPort, getProcessName, killProcess, askQuestion, isZombieOfProject, resolveCompanionPort, resolveDevServerPort, resolveAllPorts } from "./port.js";
3
+ export type { DevServerPortResult, PortPlan } from "./port.js";
4
+ export declare function validateSafePath(p: string): string;
5
+ export declare function validateSafeCommand(cmd: string): string;
6
+ export declare function validateSafeUrl(urlStr: string): string;
7
+ export declare function detectDevServerPort(projectRoot: string, execCommand?: string): number;
8
+ export declare function resolveDebugPortConflicts(debugPort: number, projectRoot: string, autoResolve: boolean, args: any): Promise<{
9
+ resolvedDebugPort: number;
10
+ patchRestorer?: () => void;
11
+ }>;
12
+ export declare function runProxyMode(targetUrl: string, serverPort: number, args: any): Promise<void>;
13
+ export declare function waitForServer(port: number, timeoutMs?: number, hasExitedCheck?: () => boolean): Promise<boolean>;
14
+ export declare function cleanArgument(arg: string): string;
15
+ export declare function parseCommand(cmdString: string): {
16
+ command: string;
17
+ args: string[];
18
+ };
19
+ export declare function resolveWindowsCommand(command: string): string;
20
+ export declare function findScriptPath(projectRoot: string): string;
21
+ export declare function startCdpInjectionWatch(debugPort: number, scriptWithPort: string): Promise<void>;
22
+ export declare function handleExecMode(execArg: string, subcommand: string | undefined, projectRoot: string, debugPort: number, serverPort: number, args: Record<string, string | boolean>): Promise<void>;