@guanzhu.me/pw-cli 0.0.9 → 0.0.11

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/bin/pw-cli.js CHANGED
@@ -603,17 +603,32 @@ EXAMPLES
603
603
  // run-script — delegates to our CDP-based executor
604
604
  // ---------------------------------------------------------------------------
605
605
  async function handleRunScript(rawArgv) {
606
- const { getConnection, killBrowser } = require('../src/browser-manager');
606
+ const { getConnection } = require('../src/browser-manager');
607
607
  const { execScript } = require('../src/executor');
608
608
 
609
609
  // parse: pw-cli [global-opts] run-script <file> [script-args...]
610
610
  const rsIdx = rawArgv.indexOf('run-script');
611
611
  const afterRs = rawArgv.slice(rsIdx + 1);
612
612
  const globalBefore = rawArgv.slice(0, rsIdx);
613
+ const options = parsePwCliGlobalOptions(globalBefore);
614
+
615
+ // Separate flags (before script path) from script path + script args
616
+ const flags = [];
617
+ let restIdx = 0;
618
+ for (let i = 0; i < afterRs.length; i++) {
619
+ if (afterRs[i].startsWith('-')) {
620
+ flags.push(afterRs[i]);
621
+ } else {
622
+ restIdx = i;
623
+ break;
624
+ }
625
+ }
626
+ // Merge any flags after run-script into options
627
+ const afterOptions = parsePwCliGlobalOptions(flags);
628
+ Object.assign(options, afterOptions);
613
629
 
614
- // extract --headless from global opts
615
- const headless = hasFlag(globalBefore, '--headless');
616
- const [scriptPath, ...scriptArgs] = afterRs;
630
+ const positionals = afterRs.slice(restIdx);
631
+ const [scriptPath, ...scriptArgs] = positionals;
617
632
 
618
633
  if (!scriptPath) {
619
634
  process.stderr.write('pw-cli: run-script requires a script path\n\nUsage: pw-cli run-script <file.js> [args...]\n');
@@ -624,12 +639,12 @@ async function handleRunScript(rawArgv) {
624
639
  process.exit(3);
625
640
  }
626
641
 
627
- const conn = await getConnection({ headless, profile: 'default', port: 9223 });
642
+ const conn = await getConnection(options);
628
643
  try {
629
644
  const result = await execScript(scriptPath, scriptArgs, conn);
630
645
  if (result !== undefined) console.log(result);
631
646
  } catch (err) {
632
- process.stderr.write(`pw-cli: ${err.message}\n`);
647
+ process.stderr.write(`pw-cli: ${err.message || err}\n`);
633
648
  process.exit(1);
634
649
  } finally {
635
650
  await conn.browser.close();
@@ -693,6 +708,12 @@ async function main() {
693
708
  const rawArgv = process.argv.slice(2);
694
709
  const { command, session } = getCommandAndSession(rawArgv);
695
710
 
711
+ if (hasFlag(rawArgv, '--version', '-V')) {
712
+ const pkg = require('../package.json');
713
+ console.log(`${pkg.name}@${pkg.version}`);
714
+ return;
715
+ }
716
+
696
717
  if (!command || command === 'help' || (rawArgv.length === 1 && hasFlag(rawArgv, '--help', '-h'))) {
697
718
  printMainHelp();
698
719
  return;
@@ -724,14 +745,15 @@ async function main() {
724
745
  }
725
746
 
726
747
  // Ensures a browser is reachable via CDP; if not, spawns playwright-cli open first.
748
+ // Returns the CDP port number.
727
749
  async function ensureBrowserRunning() {
728
750
  const { getPlaywrightCliCdpPort } = require('../src/browser-manager');
729
751
  const { probeCDP } = require('../src/utils');
730
752
  const { readState } = require('../src/state');
731
753
  const cliPort = getPlaywrightCliCdpPort();
732
- if (cliPort && await probeCDP(cliPort, 2000)) return;
754
+ if (cliPort && await probeCDP(cliPort, 2000)) return cliPort;
733
755
  const state = readState();
734
- if (state && await probeCDP(state.port, 2000)) return;
756
+ if (state && await probeCDP(state.port, 2000)) return state.port;
735
757
  // No browser reachable — start one via playwright-cli
736
758
  const { spawnSync } = require('child_process');
737
759
  const res = spawnSync(process.execPath, [cliPath, 'open', '--headed', '--persistent', '--profile', DEFAULT_PROFILE], {
@@ -742,30 +764,31 @@ async function main() {
742
764
  process.stderr.write('pw-cli: failed to open browser\n');
743
765
  process.exit(res.status || 1);
744
766
  }
767
+ // After spawning, re-detect the port
768
+ const newCliPort = getPlaywrightCliCdpPort();
769
+ if (newCliPort) return newCliPort;
770
+ const newState = readState();
771
+ return newState ? newState.port : null;
745
772
  }
746
773
 
747
- // ── goto: navigate the tracked active tab ────────────────────────────────
774
+ // ── goto: navigate the active tab (detected via CDP /json/list) ──────────
748
775
  if (command === 'goto') {
749
776
  const gotoIdx = rawArgv.indexOf('goto');
750
777
  const afterGoto = rawArgv.slice(gotoIdx + 1);
751
778
  const rawUrl = afterGoto.find(a => !a.startsWith('-'));
752
779
  if (rawUrl) {
753
780
  const fullUrl = /^https?:\/\//.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
754
- const activeTabFile = JSON.stringify(path.join(PW_CLI_DIR, 'active-tab.json'));
781
+ const cdpPort = await ensureBrowserRunning();
782
+ const { fetchActivePageUrl } = require('../src/utils');
783
+ const activeUrl = await fetchActivePageUrl(cdpPort);
755
784
  const navCode = `async (page, context) => {
756
785
  const pages = context.pages();
757
786
  let target = pages[pages.length - 1] || page;
758
- try {
759
- const saved = JSON.parse(require('fs').readFileSync(${activeTabFile}, 'utf8'));
760
- const match = pages.find(p => p.url() === saved.url);
761
- if (match) target = match;
762
- } catch {}
787
+ ${activeUrl ? `const match = pages.find(p => p.url() === ${JSON.stringify(activeUrl)});
788
+ if (match) target = match;` : ''}
763
789
  await target.goto(${JSON.stringify(fullUrl)}, { waitUntil: 'domcontentloaded', timeout: 0 });
764
- const resultUrl = target.url();
765
- try { require('fs').writeFileSync(${activeTabFile}, JSON.stringify({ url: resultUrl })); } catch {}
766
- return resultUrl;
790
+ return target.url();
767
791
  }`;
768
- await ensureBrowserRunning();
769
792
  await handleRunCode(['run-code', navCode]);
770
793
  return;
771
794
  }
@@ -812,13 +835,10 @@ async function main() {
812
835
  const rawUrlArg = afterOpen.find(a => !a.startsWith('-') && /^(https?:\/\/|[a-zA-Z0-9]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,})/.test(a));
813
836
  const urlArg = rawUrlArg && !/^https?:\/\//.test(rawUrlArg) ? `https://${rawUrlArg}` : rawUrlArg;
814
837
  if (urlArg) {
815
- const activeTabFile = JSON.stringify(path.join(PW_CLI_DIR, 'active-tab.json'));
816
838
  const navCode = `async page => {
817
839
  const newPage = await page.context().newPage();
818
840
  await newPage.goto(${JSON.stringify(urlArg)}, { waitUntil: 'domcontentloaded', timeout: 0 });
819
- const resultUrl = newPage.url();
820
- try { require('fs').writeFileSync(${activeTabFile}, JSON.stringify({ url: resultUrl })); } catch {}
821
- return resultUrl;
841
+ return newPage.url();
822
842
  }`;
823
843
  await ensureBrowserRunning();
824
844
  await handleRunCode(['run-code', navCode]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanzhu.me/pw-cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Persistent Playwright browser CLI with headed defaults, profile support, queueing, and script execution",
5
5
  "bin": {
6
6
  "pw-cli": "./bin/pw-cli.js"
package/src/utils.js CHANGED
@@ -52,4 +52,27 @@ function sleep(ms) {
52
52
  return new Promise(r => setTimeout(r, ms));
53
53
  }
54
54
 
55
- module.exports = { readStdin, die, probeCDP, findFreePort, sleep };
55
+ /**
56
+ * Fetch the URL of the most recently active page tab via Chrome's /json/list endpoint.
57
+ * Chrome returns targets ordered by most-recently-activated first.
58
+ * Returns the URL string, or null if no page target found.
59
+ */
60
+ function fetchActivePageUrl(port) {
61
+ return new Promise(resolve => {
62
+ const req = http.get(`http://127.0.0.1:${port}/json/list`, { timeout: 3000 }, res => {
63
+ let data = '';
64
+ res.on('data', chunk => { data += chunk; });
65
+ res.on('end', () => {
66
+ try {
67
+ const targets = JSON.parse(data);
68
+ const page = targets.find(t => t.type === 'page');
69
+ resolve(page ? page.url : null);
70
+ } catch { resolve(null); }
71
+ });
72
+ });
73
+ req.on('error', () => resolve(null));
74
+ req.on('timeout', () => { req.destroy(); resolve(null); });
75
+ });
76
+ }
77
+
78
+ module.exports = { readStdin, die, probeCDP, findFreePort, sleep, fetchActivePageUrl };