@react-grab/cli 0.0.76 → 0.0.78
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/dist/cli.cjs +237 -9
- package/dist/cli.js +238 -10
- package/package.json +2 -1
package/dist/cli.cjs
CHANGED
|
@@ -9,6 +9,8 @@ var path = require('path');
|
|
|
9
9
|
var ni = require('@antfu/ni');
|
|
10
10
|
var colors = require('kleur/colors');
|
|
11
11
|
var ora = require('ora');
|
|
12
|
+
var http = require('http');
|
|
13
|
+
var httpProxyMiddleware = require('http-proxy-middleware');
|
|
12
14
|
|
|
13
15
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
16
|
|
|
@@ -1094,8 +1096,6 @@ var add = new commander.Command().name("add").description("add an agent integrat
|
|
|
1094
1096
|
try {
|
|
1095
1097
|
const cwd = opts.cwd;
|
|
1096
1098
|
const isNonInteractive = opts.yes;
|
|
1097
|
-
logger.log(`\u269B ${highlighter.info("React Grab")}`);
|
|
1098
|
-
logger.break();
|
|
1099
1099
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
1100
1100
|
const projectInfo = await detectProject(cwd);
|
|
1101
1101
|
if (!projectInfo.hasReactGrab) {
|
|
@@ -1181,7 +1181,11 @@ var add = new commander.Command().name("add").description("add an agent integrat
|
|
|
1181
1181
|
if (hasLayoutChanges || hasPackageJsonChanges) {
|
|
1182
1182
|
logger.break();
|
|
1183
1183
|
if (hasLayoutChanges) {
|
|
1184
|
-
printDiff(
|
|
1184
|
+
printDiff(
|
|
1185
|
+
result.filePath,
|
|
1186
|
+
result.originalContent,
|
|
1187
|
+
result.newContent
|
|
1188
|
+
);
|
|
1185
1189
|
}
|
|
1186
1190
|
if (hasPackageJsonChanges) {
|
|
1187
1191
|
if (hasLayoutChanges) {
|
|
@@ -1215,7 +1219,11 @@ var add = new commander.Command().name("add").description("add an agent integrat
|
|
|
1215
1219
|
`Installing ${packages.join(", ")}.`
|
|
1216
1220
|
).start();
|
|
1217
1221
|
try {
|
|
1218
|
-
installPackages(
|
|
1222
|
+
installPackages(
|
|
1223
|
+
packages,
|
|
1224
|
+
projectInfo.packageManager,
|
|
1225
|
+
projectInfo.projectRoot
|
|
1226
|
+
);
|
|
1219
1227
|
installSpinner.succeed();
|
|
1220
1228
|
} catch (error) {
|
|
1221
1229
|
installSpinner.fail();
|
|
@@ -1223,7 +1231,9 @@ var add = new commander.Command().name("add").description("add an agent integrat
|
|
|
1223
1231
|
}
|
|
1224
1232
|
}
|
|
1225
1233
|
if (hasLayoutChanges) {
|
|
1226
|
-
const writeSpinner = spinner(
|
|
1234
|
+
const writeSpinner = spinner(
|
|
1235
|
+
`Applying changes to ${result.filePath}.`
|
|
1236
|
+
).start();
|
|
1227
1237
|
const writeResult = applyTransform(result);
|
|
1228
1238
|
if (!writeResult.success) {
|
|
1229
1239
|
writeSpinner.fail();
|
|
@@ -1258,7 +1268,7 @@ var add = new commander.Command().name("add").description("add an agent integrat
|
|
|
1258
1268
|
handleError(error);
|
|
1259
1269
|
}
|
|
1260
1270
|
});
|
|
1261
|
-
var VERSION = "0.0.
|
|
1271
|
+
var VERSION = "0.0.78";
|
|
1262
1272
|
var REPORT_URL = "https://react-grab.com/api/report-cli";
|
|
1263
1273
|
var DOCS_URL = "https://github.com/aidenybai/react-grab";
|
|
1264
1274
|
var reportToCli = async (type, config, error) => {
|
|
@@ -1307,8 +1317,6 @@ var init = new commander.Command().name("init").description("initialize React Gr
|
|
|
1307
1317
|
try {
|
|
1308
1318
|
const cwd = opts.cwd;
|
|
1309
1319
|
const isNonInteractive = opts.yes;
|
|
1310
|
-
logger.log(`\u269B ${highlighter.info("React Grab")}`);
|
|
1311
|
-
logger.break();
|
|
1312
1320
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
1313
1321
|
const projectInfo = await detectProject(cwd);
|
|
1314
1322
|
if (projectInfo.hasReactGrab && !opts.force) {
|
|
@@ -1562,12 +1570,232 @@ var init = new commander.Command().name("init").description("initialize React Gr
|
|
|
1562
1570
|
await reportToCli("error", void 0, error);
|
|
1563
1571
|
}
|
|
1564
1572
|
});
|
|
1573
|
+
var DEFAULT_PROXY_PORT = 2e3;
|
|
1574
|
+
var REACT_GRAB_SCRIPT = '<script src="//unpkg.com/react-grab/dist/index.global.js"></script>';
|
|
1575
|
+
var buildProviderScript = (provider) => `<script src="//unpkg.com/${provider}/dist/client.global.js"></script>`;
|
|
1576
|
+
var findAvailablePort = async (startingPort, hostname) => {
|
|
1577
|
+
return new Promise((resolve) => {
|
|
1578
|
+
const server = http.createServer();
|
|
1579
|
+
server.listen(startingPort, hostname, () => {
|
|
1580
|
+
server.close(() => resolve(startingPort));
|
|
1581
|
+
});
|
|
1582
|
+
server.on("error", () => {
|
|
1583
|
+
resolve(findAvailablePort(startingPort + 1, hostname));
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
};
|
|
1587
|
+
var parseTargetUrl = (urlInput) => {
|
|
1588
|
+
if (urlInput.startsWith("http://") || urlInput.startsWith("https://")) {
|
|
1589
|
+
return urlInput;
|
|
1590
|
+
}
|
|
1591
|
+
return `http://${urlInput}`;
|
|
1592
|
+
};
|
|
1593
|
+
var resolveTargetUrl = async (initialUrl) => {
|
|
1594
|
+
try {
|
|
1595
|
+
const response = await fetch(initialUrl, {
|
|
1596
|
+
method: "HEAD",
|
|
1597
|
+
redirect: "follow"
|
|
1598
|
+
});
|
|
1599
|
+
return response.url;
|
|
1600
|
+
} catch {
|
|
1601
|
+
return initialUrl;
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
var start = new commander.Command().name("start").alias("proxy").description("start a proxy server for a given URL").argument("[url]", "target URL to proxy (e.g., localhost:3000)").option(
|
|
1605
|
+
"-p, --port <port>",
|
|
1606
|
+
"starting port for the proxy server",
|
|
1607
|
+
String(DEFAULT_PROXY_PORT)
|
|
1608
|
+
).option(
|
|
1609
|
+
"--host <hostname>",
|
|
1610
|
+
"hostname to bind the proxy server to",
|
|
1611
|
+
"localhost"
|
|
1612
|
+
).option(
|
|
1613
|
+
"--provider <package>",
|
|
1614
|
+
"provider package to run via npx (e.g., @react-grab/cursor)"
|
|
1615
|
+
).action(async (urlArg, opts) => {
|
|
1616
|
+
let url = urlArg;
|
|
1617
|
+
let provider = opts.provider;
|
|
1618
|
+
if (!url) {
|
|
1619
|
+
const { targetUrl: promptedUrl } = await prompts2__default.default({
|
|
1620
|
+
type: "text",
|
|
1621
|
+
name: "targetUrl",
|
|
1622
|
+
message: "Enter the target URL to proxy:",
|
|
1623
|
+
initial: "localhost:3000"
|
|
1624
|
+
});
|
|
1625
|
+
if (!promptedUrl) {
|
|
1626
|
+
logger.break();
|
|
1627
|
+
process.exit(1);
|
|
1628
|
+
}
|
|
1629
|
+
url = promptedUrl;
|
|
1630
|
+
if (!provider) {
|
|
1631
|
+
const { selectedProvider } = await prompts2__default.default({
|
|
1632
|
+
type: "select",
|
|
1633
|
+
name: "selectedProvider",
|
|
1634
|
+
message: `Select a ${highlighter.info("provider")} to use:`,
|
|
1635
|
+
choices: [
|
|
1636
|
+
{ title: "None", value: "" },
|
|
1637
|
+
{ title: "Claude Code", value: "@react-grab/claude-code" },
|
|
1638
|
+
{ title: "Cursor", value: "@react-grab/cursor" },
|
|
1639
|
+
{ title: "Opencode", value: "@react-grab/opencode" },
|
|
1640
|
+
{ title: "Ami", value: "@react-grab/ami" }
|
|
1641
|
+
]
|
|
1642
|
+
});
|
|
1643
|
+
if (selectedProvider === void 0) {
|
|
1644
|
+
logger.break();
|
|
1645
|
+
process.exit(1);
|
|
1646
|
+
}
|
|
1647
|
+
provider = selectedProvider || void 0;
|
|
1648
|
+
}
|
|
1649
|
+
logger.break();
|
|
1650
|
+
}
|
|
1651
|
+
const parsedUrl = parseTargetUrl(url);
|
|
1652
|
+
const targetUrl = await resolveTargetUrl(parsedUrl);
|
|
1653
|
+
const startingPort = parseInt(opts.port, 10);
|
|
1654
|
+
const hostname = opts.host || "localhost";
|
|
1655
|
+
if (isNaN(startingPort) || startingPort < 1 || startingPort > 65535) {
|
|
1656
|
+
logger.break();
|
|
1657
|
+
logger.error(
|
|
1658
|
+
"Invalid port number. Please provide a port between 1 and 65535."
|
|
1659
|
+
);
|
|
1660
|
+
logger.break();
|
|
1661
|
+
process.exit(1);
|
|
1662
|
+
}
|
|
1663
|
+
const proxyPort = await findAvailablePort(startingPort, hostname);
|
|
1664
|
+
const scriptsToInject = provider ? REACT_GRAB_SCRIPT + buildProviderScript(provider) : REACT_GRAB_SCRIPT;
|
|
1665
|
+
const injectScript = (html) => {
|
|
1666
|
+
const headCloseIndex = html.indexOf("</head>");
|
|
1667
|
+
if (headCloseIndex !== -1) {
|
|
1668
|
+
return html.slice(0, headCloseIndex) + scriptsToInject + html.slice(headCloseIndex);
|
|
1669
|
+
}
|
|
1670
|
+
const bodyCloseIndex = html.indexOf("</body>");
|
|
1671
|
+
if (bodyCloseIndex !== -1) {
|
|
1672
|
+
return html.slice(0, bodyCloseIndex) + scriptsToInject + html.slice(bodyCloseIndex);
|
|
1673
|
+
}
|
|
1674
|
+
return html + scriptsToInject;
|
|
1675
|
+
};
|
|
1676
|
+
const proxyMiddleware = httpProxyMiddleware.createProxyMiddleware({
|
|
1677
|
+
target: targetUrl,
|
|
1678
|
+
changeOrigin: true,
|
|
1679
|
+
followRedirects: false,
|
|
1680
|
+
ws: true,
|
|
1681
|
+
selfHandleResponse: true,
|
|
1682
|
+
cookieDomainRewrite: {
|
|
1683
|
+
"*": ""
|
|
1684
|
+
},
|
|
1685
|
+
autoRewrite: true,
|
|
1686
|
+
preserveHeaderKeyCase: true,
|
|
1687
|
+
xfwd: true,
|
|
1688
|
+
on: {
|
|
1689
|
+
error: (_error, _request, response) => {
|
|
1690
|
+
if ("writeHead" in response && !response.headersSent) {
|
|
1691
|
+
response.writeHead(503, { "Content-Type": "text/plain" });
|
|
1692
|
+
response.end(`Proxy error: Unable to connect to ${targetUrl}`);
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
proxyReq: (proxyRequest) => {
|
|
1696
|
+
proxyRequest.removeHeader("accept-encoding");
|
|
1697
|
+
},
|
|
1698
|
+
proxyRes: httpProxyMiddleware.responseInterceptor(async (responseBuffer, proxyResponse) => {
|
|
1699
|
+
const contentType = proxyResponse.headers["content-type"] || "";
|
|
1700
|
+
const isHtml = contentType.includes("text/html");
|
|
1701
|
+
if (!isHtml) {
|
|
1702
|
+
return responseBuffer;
|
|
1703
|
+
}
|
|
1704
|
+
const html = responseBuffer.toString("utf-8");
|
|
1705
|
+
return injectScript(html);
|
|
1706
|
+
})
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
const server = http.createServer((request, response) => {
|
|
1710
|
+
proxyMiddleware(request, response, (error) => {
|
|
1711
|
+
if (error) {
|
|
1712
|
+
logger.error(`Request error: ${error}`);
|
|
1713
|
+
response.writeHead(500);
|
|
1714
|
+
response.end("Internal Server Error");
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
});
|
|
1718
|
+
server.on("upgrade", proxyMiddleware.upgrade);
|
|
1719
|
+
const startSpinner = spinner("Starting.").start();
|
|
1720
|
+
const showSuccess = () => {
|
|
1721
|
+
startSpinner.succeed(`Open in your browser: http://${hostname}:${proxyPort}`);
|
|
1722
|
+
const commandParts = ["npx react-grab@latest start", url];
|
|
1723
|
+
if (opts.port !== String(DEFAULT_PROXY_PORT)) {
|
|
1724
|
+
commandParts.push(`--port=${opts.port}`);
|
|
1725
|
+
}
|
|
1726
|
+
if (hostname !== "localhost") {
|
|
1727
|
+
commandParts.push(`--host=${hostname}`);
|
|
1728
|
+
}
|
|
1729
|
+
if (provider) {
|
|
1730
|
+
commandParts.push(`--provider=${provider}`);
|
|
1731
|
+
}
|
|
1732
|
+
logger.break();
|
|
1733
|
+
logger.log(highlighter.dim(`$ ${commandParts.join(" ")}`));
|
|
1734
|
+
};
|
|
1735
|
+
let isServerReady = false;
|
|
1736
|
+
let isProviderReady = !provider;
|
|
1737
|
+
const checkReady = () => {
|
|
1738
|
+
if (isServerReady && isProviderReady) {
|
|
1739
|
+
showSuccess();
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
server.listen(proxyPort, hostname, () => {
|
|
1743
|
+
isServerReady = true;
|
|
1744
|
+
checkReady();
|
|
1745
|
+
});
|
|
1746
|
+
if (provider) {
|
|
1747
|
+
const providerProcess = child_process.spawn("npx", [`${provider}@latest`], {
|
|
1748
|
+
stdio: "ignore",
|
|
1749
|
+
shell: true,
|
|
1750
|
+
detached: false
|
|
1751
|
+
});
|
|
1752
|
+
const cleanup = () => {
|
|
1753
|
+
if (!providerProcess.killed) {
|
|
1754
|
+
providerProcess.kill();
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
process.on("exit", cleanup);
|
|
1758
|
+
process.on("SIGINT", () => {
|
|
1759
|
+
cleanup();
|
|
1760
|
+
process.exit(0);
|
|
1761
|
+
});
|
|
1762
|
+
process.on("SIGTERM", () => {
|
|
1763
|
+
cleanup();
|
|
1764
|
+
process.exit(0);
|
|
1765
|
+
});
|
|
1766
|
+
providerProcess.on("error", (error) => {
|
|
1767
|
+
startSpinner.fail(`Failed to start provider: ${error.message}`);
|
|
1768
|
+
});
|
|
1769
|
+
providerProcess.on("spawn", () => {
|
|
1770
|
+
isProviderReady = true;
|
|
1771
|
+
checkReady();
|
|
1772
|
+
});
|
|
1773
|
+
providerProcess.on("close", (code) => {
|
|
1774
|
+
if (code !== 0 && code !== null) {
|
|
1775
|
+
logger.error(`Provider exited with code ${code}`);
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
server.on("error", (error) => {
|
|
1780
|
+
logger.break();
|
|
1781
|
+
logger.error(`Server error: ${error.message}`);
|
|
1782
|
+
logger.break();
|
|
1783
|
+
process.exit(1);
|
|
1784
|
+
});
|
|
1785
|
+
});
|
|
1565
1786
|
|
|
1566
1787
|
// src/cli.ts
|
|
1567
|
-
var VERSION2 = "0.0.
|
|
1788
|
+
var VERSION2 = "0.0.78";
|
|
1789
|
+
var VERSION_API_URL = "https://react-grab.com/api/version";
|
|
1568
1790
|
process.on("SIGINT", () => process.exit(0));
|
|
1569
1791
|
process.on("SIGTERM", () => process.exit(0));
|
|
1792
|
+
try {
|
|
1793
|
+
fetch(`${VERSION_API_URL}?source=cli&t=${Date.now()}`).catch(() => {
|
|
1794
|
+
});
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1570
1797
|
var program = new commander.Command().name("react-grab").description("add React Grab to your project").version(VERSION2, "-v, --version", "display the version number");
|
|
1571
1798
|
program.addCommand(init);
|
|
1572
1799
|
program.addCommand(add);
|
|
1800
|
+
program.addCommand(start);
|
|
1573
1801
|
program.parse();
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import prompts2 from 'prompts';
|
|
4
|
-
import { execSync } from 'child_process';
|
|
4
|
+
import { spawn, execSync } from 'child_process';
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
|
|
6
6
|
import { join, basename } from 'path';
|
|
7
7
|
import { detect } from '@antfu/ni';
|
|
8
8
|
import { cyan, dim, green, yellow, red } from 'kleur/colors';
|
|
9
9
|
import ora from 'ora';
|
|
10
|
+
import { createServer } from 'http';
|
|
11
|
+
import { createProxyMiddleware, responseInterceptor } from 'http-proxy-middleware';
|
|
10
12
|
|
|
11
13
|
var detectPackageManager = async (projectRoot) => {
|
|
12
14
|
const detected = await detect({ cwd: projectRoot });
|
|
@@ -1087,8 +1089,6 @@ var add = new Command().name("add").description("add an agent integration").argu
|
|
|
1087
1089
|
try {
|
|
1088
1090
|
const cwd = opts.cwd;
|
|
1089
1091
|
const isNonInteractive = opts.yes;
|
|
1090
|
-
logger.log(`\u269B ${highlighter.info("React Grab")}`);
|
|
1091
|
-
logger.break();
|
|
1092
1092
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
1093
1093
|
const projectInfo = await detectProject(cwd);
|
|
1094
1094
|
if (!projectInfo.hasReactGrab) {
|
|
@@ -1174,7 +1174,11 @@ var add = new Command().name("add").description("add an agent integration").argu
|
|
|
1174
1174
|
if (hasLayoutChanges || hasPackageJsonChanges) {
|
|
1175
1175
|
logger.break();
|
|
1176
1176
|
if (hasLayoutChanges) {
|
|
1177
|
-
printDiff(
|
|
1177
|
+
printDiff(
|
|
1178
|
+
result.filePath,
|
|
1179
|
+
result.originalContent,
|
|
1180
|
+
result.newContent
|
|
1181
|
+
);
|
|
1178
1182
|
}
|
|
1179
1183
|
if (hasPackageJsonChanges) {
|
|
1180
1184
|
if (hasLayoutChanges) {
|
|
@@ -1208,7 +1212,11 @@ var add = new Command().name("add").description("add an agent integration").argu
|
|
|
1208
1212
|
`Installing ${packages.join(", ")}.`
|
|
1209
1213
|
).start();
|
|
1210
1214
|
try {
|
|
1211
|
-
installPackages(
|
|
1215
|
+
installPackages(
|
|
1216
|
+
packages,
|
|
1217
|
+
projectInfo.packageManager,
|
|
1218
|
+
projectInfo.projectRoot
|
|
1219
|
+
);
|
|
1212
1220
|
installSpinner.succeed();
|
|
1213
1221
|
} catch (error) {
|
|
1214
1222
|
installSpinner.fail();
|
|
@@ -1216,7 +1224,9 @@ var add = new Command().name("add").description("add an agent integration").argu
|
|
|
1216
1224
|
}
|
|
1217
1225
|
}
|
|
1218
1226
|
if (hasLayoutChanges) {
|
|
1219
|
-
const writeSpinner = spinner(
|
|
1227
|
+
const writeSpinner = spinner(
|
|
1228
|
+
`Applying changes to ${result.filePath}.`
|
|
1229
|
+
).start();
|
|
1220
1230
|
const writeResult = applyTransform(result);
|
|
1221
1231
|
if (!writeResult.success) {
|
|
1222
1232
|
writeSpinner.fail();
|
|
@@ -1251,7 +1261,7 @@ var add = new Command().name("add").description("add an agent integration").argu
|
|
|
1251
1261
|
handleError(error);
|
|
1252
1262
|
}
|
|
1253
1263
|
});
|
|
1254
|
-
var VERSION = "0.0.
|
|
1264
|
+
var VERSION = "0.0.78";
|
|
1255
1265
|
var REPORT_URL = "https://react-grab.com/api/report-cli";
|
|
1256
1266
|
var DOCS_URL = "https://github.com/aidenybai/react-grab";
|
|
1257
1267
|
var reportToCli = async (type, config, error) => {
|
|
@@ -1300,8 +1310,6 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
1300
1310
|
try {
|
|
1301
1311
|
const cwd = opts.cwd;
|
|
1302
1312
|
const isNonInteractive = opts.yes;
|
|
1303
|
-
logger.log(`\u269B ${highlighter.info("React Grab")}`);
|
|
1304
|
-
logger.break();
|
|
1305
1313
|
const preflightSpinner = spinner("Preflight checks.").start();
|
|
1306
1314
|
const projectInfo = await detectProject(cwd);
|
|
1307
1315
|
if (projectInfo.hasReactGrab && !opts.force) {
|
|
@@ -1555,12 +1563,232 @@ var init = new Command().name("init").description("initialize React Grab in your
|
|
|
1555
1563
|
await reportToCli("error", void 0, error);
|
|
1556
1564
|
}
|
|
1557
1565
|
});
|
|
1566
|
+
var DEFAULT_PROXY_PORT = 2e3;
|
|
1567
|
+
var REACT_GRAB_SCRIPT = '<script src="//unpkg.com/react-grab/dist/index.global.js"></script>';
|
|
1568
|
+
var buildProviderScript = (provider) => `<script src="//unpkg.com/${provider}/dist/client.global.js"></script>`;
|
|
1569
|
+
var findAvailablePort = async (startingPort, hostname) => {
|
|
1570
|
+
return new Promise((resolve) => {
|
|
1571
|
+
const server = createServer();
|
|
1572
|
+
server.listen(startingPort, hostname, () => {
|
|
1573
|
+
server.close(() => resolve(startingPort));
|
|
1574
|
+
});
|
|
1575
|
+
server.on("error", () => {
|
|
1576
|
+
resolve(findAvailablePort(startingPort + 1, hostname));
|
|
1577
|
+
});
|
|
1578
|
+
});
|
|
1579
|
+
};
|
|
1580
|
+
var parseTargetUrl = (urlInput) => {
|
|
1581
|
+
if (urlInput.startsWith("http://") || urlInput.startsWith("https://")) {
|
|
1582
|
+
return urlInput;
|
|
1583
|
+
}
|
|
1584
|
+
return `http://${urlInput}`;
|
|
1585
|
+
};
|
|
1586
|
+
var resolveTargetUrl = async (initialUrl) => {
|
|
1587
|
+
try {
|
|
1588
|
+
const response = await fetch(initialUrl, {
|
|
1589
|
+
method: "HEAD",
|
|
1590
|
+
redirect: "follow"
|
|
1591
|
+
});
|
|
1592
|
+
return response.url;
|
|
1593
|
+
} catch {
|
|
1594
|
+
return initialUrl;
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
var start = new Command().name("start").alias("proxy").description("start a proxy server for a given URL").argument("[url]", "target URL to proxy (e.g., localhost:3000)").option(
|
|
1598
|
+
"-p, --port <port>",
|
|
1599
|
+
"starting port for the proxy server",
|
|
1600
|
+
String(DEFAULT_PROXY_PORT)
|
|
1601
|
+
).option(
|
|
1602
|
+
"--host <hostname>",
|
|
1603
|
+
"hostname to bind the proxy server to",
|
|
1604
|
+
"localhost"
|
|
1605
|
+
).option(
|
|
1606
|
+
"--provider <package>",
|
|
1607
|
+
"provider package to run via npx (e.g., @react-grab/cursor)"
|
|
1608
|
+
).action(async (urlArg, opts) => {
|
|
1609
|
+
let url = urlArg;
|
|
1610
|
+
let provider = opts.provider;
|
|
1611
|
+
if (!url) {
|
|
1612
|
+
const { targetUrl: promptedUrl } = await prompts2({
|
|
1613
|
+
type: "text",
|
|
1614
|
+
name: "targetUrl",
|
|
1615
|
+
message: "Enter the target URL to proxy:",
|
|
1616
|
+
initial: "localhost:3000"
|
|
1617
|
+
});
|
|
1618
|
+
if (!promptedUrl) {
|
|
1619
|
+
logger.break();
|
|
1620
|
+
process.exit(1);
|
|
1621
|
+
}
|
|
1622
|
+
url = promptedUrl;
|
|
1623
|
+
if (!provider) {
|
|
1624
|
+
const { selectedProvider } = await prompts2({
|
|
1625
|
+
type: "select",
|
|
1626
|
+
name: "selectedProvider",
|
|
1627
|
+
message: `Select a ${highlighter.info("provider")} to use:`,
|
|
1628
|
+
choices: [
|
|
1629
|
+
{ title: "None", value: "" },
|
|
1630
|
+
{ title: "Claude Code", value: "@react-grab/claude-code" },
|
|
1631
|
+
{ title: "Cursor", value: "@react-grab/cursor" },
|
|
1632
|
+
{ title: "Opencode", value: "@react-grab/opencode" },
|
|
1633
|
+
{ title: "Ami", value: "@react-grab/ami" }
|
|
1634
|
+
]
|
|
1635
|
+
});
|
|
1636
|
+
if (selectedProvider === void 0) {
|
|
1637
|
+
logger.break();
|
|
1638
|
+
process.exit(1);
|
|
1639
|
+
}
|
|
1640
|
+
provider = selectedProvider || void 0;
|
|
1641
|
+
}
|
|
1642
|
+
logger.break();
|
|
1643
|
+
}
|
|
1644
|
+
const parsedUrl = parseTargetUrl(url);
|
|
1645
|
+
const targetUrl = await resolveTargetUrl(parsedUrl);
|
|
1646
|
+
const startingPort = parseInt(opts.port, 10);
|
|
1647
|
+
const hostname = opts.host || "localhost";
|
|
1648
|
+
if (isNaN(startingPort) || startingPort < 1 || startingPort > 65535) {
|
|
1649
|
+
logger.break();
|
|
1650
|
+
logger.error(
|
|
1651
|
+
"Invalid port number. Please provide a port between 1 and 65535."
|
|
1652
|
+
);
|
|
1653
|
+
logger.break();
|
|
1654
|
+
process.exit(1);
|
|
1655
|
+
}
|
|
1656
|
+
const proxyPort = await findAvailablePort(startingPort, hostname);
|
|
1657
|
+
const scriptsToInject = provider ? REACT_GRAB_SCRIPT + buildProviderScript(provider) : REACT_GRAB_SCRIPT;
|
|
1658
|
+
const injectScript = (html) => {
|
|
1659
|
+
const headCloseIndex = html.indexOf("</head>");
|
|
1660
|
+
if (headCloseIndex !== -1) {
|
|
1661
|
+
return html.slice(0, headCloseIndex) + scriptsToInject + html.slice(headCloseIndex);
|
|
1662
|
+
}
|
|
1663
|
+
const bodyCloseIndex = html.indexOf("</body>");
|
|
1664
|
+
if (bodyCloseIndex !== -1) {
|
|
1665
|
+
return html.slice(0, bodyCloseIndex) + scriptsToInject + html.slice(bodyCloseIndex);
|
|
1666
|
+
}
|
|
1667
|
+
return html + scriptsToInject;
|
|
1668
|
+
};
|
|
1669
|
+
const proxyMiddleware = createProxyMiddleware({
|
|
1670
|
+
target: targetUrl,
|
|
1671
|
+
changeOrigin: true,
|
|
1672
|
+
followRedirects: false,
|
|
1673
|
+
ws: true,
|
|
1674
|
+
selfHandleResponse: true,
|
|
1675
|
+
cookieDomainRewrite: {
|
|
1676
|
+
"*": ""
|
|
1677
|
+
},
|
|
1678
|
+
autoRewrite: true,
|
|
1679
|
+
preserveHeaderKeyCase: true,
|
|
1680
|
+
xfwd: true,
|
|
1681
|
+
on: {
|
|
1682
|
+
error: (_error, _request, response) => {
|
|
1683
|
+
if ("writeHead" in response && !response.headersSent) {
|
|
1684
|
+
response.writeHead(503, { "Content-Type": "text/plain" });
|
|
1685
|
+
response.end(`Proxy error: Unable to connect to ${targetUrl}`);
|
|
1686
|
+
}
|
|
1687
|
+
},
|
|
1688
|
+
proxyReq: (proxyRequest) => {
|
|
1689
|
+
proxyRequest.removeHeader("accept-encoding");
|
|
1690
|
+
},
|
|
1691
|
+
proxyRes: responseInterceptor(async (responseBuffer, proxyResponse) => {
|
|
1692
|
+
const contentType = proxyResponse.headers["content-type"] || "";
|
|
1693
|
+
const isHtml = contentType.includes("text/html");
|
|
1694
|
+
if (!isHtml) {
|
|
1695
|
+
return responseBuffer;
|
|
1696
|
+
}
|
|
1697
|
+
const html = responseBuffer.toString("utf-8");
|
|
1698
|
+
return injectScript(html);
|
|
1699
|
+
})
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
const server = createServer((request, response) => {
|
|
1703
|
+
proxyMiddleware(request, response, (error) => {
|
|
1704
|
+
if (error) {
|
|
1705
|
+
logger.error(`Request error: ${error}`);
|
|
1706
|
+
response.writeHead(500);
|
|
1707
|
+
response.end("Internal Server Error");
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
});
|
|
1711
|
+
server.on("upgrade", proxyMiddleware.upgrade);
|
|
1712
|
+
const startSpinner = spinner("Starting.").start();
|
|
1713
|
+
const showSuccess = () => {
|
|
1714
|
+
startSpinner.succeed(`Open in your browser: http://${hostname}:${proxyPort}`);
|
|
1715
|
+
const commandParts = ["npx react-grab@latest start", url];
|
|
1716
|
+
if (opts.port !== String(DEFAULT_PROXY_PORT)) {
|
|
1717
|
+
commandParts.push(`--port=${opts.port}`);
|
|
1718
|
+
}
|
|
1719
|
+
if (hostname !== "localhost") {
|
|
1720
|
+
commandParts.push(`--host=${hostname}`);
|
|
1721
|
+
}
|
|
1722
|
+
if (provider) {
|
|
1723
|
+
commandParts.push(`--provider=${provider}`);
|
|
1724
|
+
}
|
|
1725
|
+
logger.break();
|
|
1726
|
+
logger.log(highlighter.dim(`$ ${commandParts.join(" ")}`));
|
|
1727
|
+
};
|
|
1728
|
+
let isServerReady = false;
|
|
1729
|
+
let isProviderReady = !provider;
|
|
1730
|
+
const checkReady = () => {
|
|
1731
|
+
if (isServerReady && isProviderReady) {
|
|
1732
|
+
showSuccess();
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
server.listen(proxyPort, hostname, () => {
|
|
1736
|
+
isServerReady = true;
|
|
1737
|
+
checkReady();
|
|
1738
|
+
});
|
|
1739
|
+
if (provider) {
|
|
1740
|
+
const providerProcess = spawn("npx", [`${provider}@latest`], {
|
|
1741
|
+
stdio: "ignore",
|
|
1742
|
+
shell: true,
|
|
1743
|
+
detached: false
|
|
1744
|
+
});
|
|
1745
|
+
const cleanup = () => {
|
|
1746
|
+
if (!providerProcess.killed) {
|
|
1747
|
+
providerProcess.kill();
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
process.on("exit", cleanup);
|
|
1751
|
+
process.on("SIGINT", () => {
|
|
1752
|
+
cleanup();
|
|
1753
|
+
process.exit(0);
|
|
1754
|
+
});
|
|
1755
|
+
process.on("SIGTERM", () => {
|
|
1756
|
+
cleanup();
|
|
1757
|
+
process.exit(0);
|
|
1758
|
+
});
|
|
1759
|
+
providerProcess.on("error", (error) => {
|
|
1760
|
+
startSpinner.fail(`Failed to start provider: ${error.message}`);
|
|
1761
|
+
});
|
|
1762
|
+
providerProcess.on("spawn", () => {
|
|
1763
|
+
isProviderReady = true;
|
|
1764
|
+
checkReady();
|
|
1765
|
+
});
|
|
1766
|
+
providerProcess.on("close", (code) => {
|
|
1767
|
+
if (code !== 0 && code !== null) {
|
|
1768
|
+
logger.error(`Provider exited with code ${code}`);
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
server.on("error", (error) => {
|
|
1773
|
+
logger.break();
|
|
1774
|
+
logger.error(`Server error: ${error.message}`);
|
|
1775
|
+
logger.break();
|
|
1776
|
+
process.exit(1);
|
|
1777
|
+
});
|
|
1778
|
+
});
|
|
1558
1779
|
|
|
1559
1780
|
// src/cli.ts
|
|
1560
|
-
var VERSION2 = "0.0.
|
|
1781
|
+
var VERSION2 = "0.0.78";
|
|
1782
|
+
var VERSION_API_URL = "https://react-grab.com/api/version";
|
|
1561
1783
|
process.on("SIGINT", () => process.exit(0));
|
|
1562
1784
|
process.on("SIGTERM", () => process.exit(0));
|
|
1785
|
+
try {
|
|
1786
|
+
fetch(`${VERSION_API_URL}?source=cli&t=${Date.now()}`).catch(() => {
|
|
1787
|
+
});
|
|
1788
|
+
} catch {
|
|
1789
|
+
}
|
|
1563
1790
|
var program = new Command().name("react-grab").description("add React Grab to your project").version(VERSION2, "-v, --version", "display the version number");
|
|
1564
1791
|
program.addCommand(init);
|
|
1565
1792
|
program.addCommand(add);
|
|
1793
|
+
program.addCommand(start);
|
|
1566
1794
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-grab/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.78",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"react-grab": "./dist/cli.js"
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@antfu/ni": "^0.23.0",
|
|
25
25
|
"commander": "^14.0.0",
|
|
26
|
+
"http-proxy-middleware": "^3.0.3",
|
|
26
27
|
"kleur": "^4.1.5",
|
|
27
28
|
"ora": "^8.2.0",
|
|
28
29
|
"prompts": "^2.4.2"
|