contactsheet 0.1.0 → 0.1.2

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.js CHANGED
@@ -29,6 +29,7 @@ function loadConfig(cwd, flags) {
29
29
  // 末尾斜杠去掉,后面一律靠 `${target}/xxx` 拼
30
30
  target: String(pick("target") ?? DEFAULT_TARGET).replace(/\/+$/, ""),
31
31
  port,
32
+ portExplicit: flags.port !== void 0,
32
33
  appDir: pick("appDir") ?? detectAppDir(projectRoot),
33
34
  designDir: pick("designDir") ?? DEFAULT_DESIGN_DIR
34
35
  };
@@ -515,6 +516,10 @@ async function runInit(cwd, flags) {
515
516
  console.log(" 1. \u7167\u5E38\u8D77\u4F60\u81EA\u5DF1\u7684 dev server(next dev),\u786E\u8BA4\u5B83\u5728 " + cfg.target);
516
517
  console.log(" 2. \u53E6\u5F00\u4E00\u4E2A\u7EC8\u7AEF:npx contactsheet");
517
518
  console.log(` 3. \u6253\u5F00 http://localhost:${cfg.port}/__cs`);
519
+ console.log("");
520
+ console.log("\u7AEF\u53E3\u90FD\u53EF\u4EE5\u6362:dev server \u4E0D\u5728 " + cfg.target + " \u5C31 --target <url>,");
521
+ console.log(` ${cfg.port} \u88AB\u5360\u5C31 --port <n>(\u6216\u6539 contactsheet.config.json \u540E\u91CD\u8DD1 init,MCP/hook \u4F1A\u8DDF\u7740\u6362)\u3002`);
522
+ console.log(` ${cfg.port} \u88AB\u522B\u7684\u8FDB\u7A0B\u5360\u7740\u65F6,contactsheet \u4F1A\u81EA\u52A8\u987A\u5EF6\u5230\u4E0B\u4E00\u4E2A\u7A7A\u95F2\u7AEF\u53E3\u5E76\u63D0\u793A\u3002`);
518
523
  }
519
524
  function assertNextProject(root) {
520
525
  const pkgPath = join(root, "package.json");
@@ -543,11 +548,21 @@ function detectAppDir2(root) {
543
548
  contactsheet v1 \u53EA\u652F\u6301 App Router(\u753B\u677F\u8DEF\u7531\u8981\u6CE8\u5165\u5230 app \u76EE\u5F55\u91CC)\u3002Pages Router \u9879\u76EE\u6682\u65F6\u7528\u4E0D\u4E86\u3002`
544
549
  );
545
550
  }
551
+ function detectDevTarget(root) {
552
+ try {
553
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
554
+ const dev = pkg.scripts?.["dev"] ?? "";
555
+ const m = dev.match(/(?:-p|--port)[ =](\d{2,5})/) ?? dev.match(/\bPORT=(\d{2,5})/);
556
+ if (m && m[1] !== "3000") return `http://localhost:${m[1]}`;
557
+ } catch {
558
+ }
559
+ return null;
560
+ }
546
561
  function writeConfigFile(root, appDir, flags) {
547
562
  const file = join(root, "contactsheet.config.json");
548
563
  const existing = readJsonObject(file) ?? {};
549
564
  const resolved = {
550
- target: pickString(flags.target, existing["target"], DEFAULT_TARGET2),
565
+ target: pickString(flags.target, existing["target"], detectDevTarget(root) ?? DEFAULT_TARGET2),
551
566
  port: pickNumber(flags.port, existing["port"], DEFAULT_PORT2),
552
567
  appDir: pickString(flags.appDir, existing["appDir"], appDir),
553
568
  designDir: pickString(flags.designDir, existing["designDir"], DEFAULT_DESIGN_DIR2)
@@ -1550,19 +1565,14 @@ async function startServer(cfg) {
1550
1565
  s.on("close", () => sockets.delete(s));
1551
1566
  });
1552
1567
  const host = cfg.host ?? "127.0.0.1";
1553
- await new Promise((resolve2, reject) => {
1554
- server.once("error", reject);
1555
- server.listen(cfg.port, host, () => {
1556
- server.off("error", reject);
1557
- resolve2();
1558
- });
1559
- });
1568
+ const port = await listenWithFallback(server, cfg, host);
1560
1569
  if (host !== "127.0.0.1" && host !== "localhost") {
1561
1570
  console.warn(
1562
- `[contactsheet] \u26A0\uFE0F \u76D1\u542C\u5728 ${host}:${cfg.port} \u2014\u2014 \u753B\u5E03\u65E0\u9274\u6743,\u540C\u7F51\u7EDC\u7684\u4EFB\u4F55\u4EBA\u90FD\u80FD\u8BFB\u4F60\u7684\u6279\u6CE8\u3001\u5E76\u5411\u4F60\u7684 Claude Code \u4F1A\u8BDD\u63A8\u9001\u6D88\u606F\u3002\u53EA\u5728\u53EF\u4FE1\u7F51\u7EDC\u8FD9\u4E48\u505A\u3002`
1571
+ `[contactsheet] \u26A0\uFE0F \u76D1\u542C\u5728 ${host}:${port} \u2014\u2014 \u753B\u5E03\u65E0\u9274\u6743,\u540C\u7F51\u7EDC\u7684\u4EFB\u4F55\u4EBA\u90FD\u80FD\u8BFB\u4F60\u7684\u6279\u6CE8\u3001\u5E76\u5411\u4F60\u7684 Claude Code \u4F1A\u8BDD\u63A8\u9001\u6D88\u606F\u3002\u53EA\u5728\u53EF\u4FE1\u7F51\u7EDC\u8FD9\u4E48\u505A\u3002`
1563
1572
  );
1564
1573
  }
1565
1574
  return {
1575
+ port,
1566
1576
  async close() {
1567
1577
  closeAll();
1568
1578
  proxyLog.close();
@@ -1637,6 +1647,53 @@ function downPage(target) {
1637
1647
  function escapeHtml(s) {
1638
1648
  return s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
1639
1649
  }
1650
+ async function occupiedBySameProject(cfg, port) {
1651
+ try {
1652
+ const ctl = AbortSignal.timeout(800);
1653
+ const r = await fetch(`http://127.0.0.1:${port}/__cs/api/state`, { signal: ctl });
1654
+ if (!r.ok) return false;
1655
+ const data = await r.json();
1656
+ return data?.projectRoot === cfg.projectRoot;
1657
+ } catch {
1658
+ return false;
1659
+ }
1660
+ }
1661
+ async function listenWithFallback(server, cfg, host) {
1662
+ const tryListen = (port) => new Promise((resolve2, reject) => {
1663
+ const onErr = (err) => {
1664
+ if (err.code === "EADDRINUSE") resolve2(false);
1665
+ else reject(err);
1666
+ };
1667
+ server.once("error", onErr);
1668
+ server.listen(port, host, () => {
1669
+ server.off("error", onErr);
1670
+ resolve2(true);
1671
+ });
1672
+ });
1673
+ if (await tryListen(cfg.port)) return cfg.port;
1674
+ if (await occupiedBySameProject(cfg, cfg.port)) {
1675
+ console.log(
1676
+ `[contactsheet] \u672C\u9879\u76EE\u5DF2\u7ECF\u6709\u4E00\u4E2A contactsheet \u5728\u8DD1\u4E86,\u76F4\u63A5\u7528\u5B83:http://localhost:${cfg.port}/__cs`
1677
+ );
1678
+ process.exit(0);
1679
+ }
1680
+ if (cfg.portExplicit) {
1681
+ throw new Error(
1682
+ `\u7AEF\u53E3 ${cfg.port} \u88AB\u5176\u4ED6\u8FDB\u7A0B\u5360\u7528\u3002\u6362\u4E00\u4E2A --port,\u6216\u627E\u51FA\u5360\u7528\u8005:lsof -nP -iTCP:${cfg.port} -sTCP:LISTEN`
1683
+ );
1684
+ }
1685
+ for (let p = cfg.port + 1; p <= cfg.port + 20; p++) {
1686
+ if (await tryListen(p)) {
1687
+ console.warn(
1688
+ `[contactsheet] \u26A0\uFE0F \u7AEF\u53E3 ${cfg.port} \u88AB\u5360,\u5DF2\u987A\u5EF6\u5230 ${p} \u2192 http://localhost:${p}/__cs
1689
+ \u6CE8\u610F:\u6362\u7AEF\u53E3 = \u6362\u6D4F\u89C8\u5668\u6E90\u3002\u753B\u677F\u4F4D\u7F6E/\u6298\u53E0/\u5E95\u8272\u8FD9\u4E9B\u672C\u673A\u8BB0\u5FC6\u6309\u6E90\u9694\u79BB,\u8FD9\u4E2A\u7AEF\u53E3\u4E0B\u662F\u5168\u65B0\u7684;
1690
+ .mcp.json \u4E0E hook \u4ECD\u6307\u5411 ${cfg.port}\u3002\u60F3\u8981\u7A33\u5B9A,\u8BF7\u91CA\u653E ${cfg.port},\u6216\u6539 config \u7684 port \u540E\u91CD\u8DD1 npx contactsheet init\u3002`
1691
+ );
1692
+ return p;
1693
+ }
1694
+ }
1695
+ throw new Error(`\u4ECE ${cfg.port} \u5F80\u4E0A\u8FDE\u8BD5 20 \u4E2A\u7AEF\u53E3\u90FD\u88AB\u5360,\u7528 --port \u6307\u5B9A\u4E00\u4E2A\u7A7A\u95F2\u7AEF\u53E3`);
1696
+ }
1640
1697
 
1641
1698
  // src/watch/index.ts
1642
1699
  import path7 from "node:path";
@@ -1743,7 +1800,7 @@ async function up(flags) {
1743
1800
  () => startWatcher(cfg, (entries) => broadcast({ type: "registry", entries }))
1744
1801
  );
1745
1802
  const server = await startServer(cfg);
1746
- banner(cfg);
1803
+ banner(cfg, server.port);
1747
1804
  let closing = false;
1748
1805
  const shutdown = async (sig) => {
1749
1806
  if (closing) return;
@@ -1772,9 +1829,9 @@ async function quiet(fn) {
1772
1829
  } catch {
1773
1830
  }
1774
1831
  }
1775
- function banner(cfg) {
1832
+ function banner(cfg, port) {
1776
1833
  console.log(`
1777
- contactsheet http://localhost:${cfg.port}/__cs
1834
+ contactsheet http://localhost:${port}/__cs
1778
1835
  \u4EE3\u7406\u76EE\u6807 ${cfg.target}
1779
1836
  \u753B\u677F\u76EE\u5F55 ${cfg.designDir}/ \xB7 app \u76EE\u5F55 ${cfg.appDir}/
1780
1837
  Ctrl-C \u9000\u51FA