browser4-cli 0.1.18 → 0.1.20
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/README.md +113 -12
- package/bin/browser4-cli-darwin-arm64 +0 -0
- package/bin/browser4-cli-darwin-x64 +0 -0
- package/bin/browser4-cli-linux-arm64 +0 -0
- package/bin/browser4-cli-linux-musl-arm64 +0 -0
- package/bin/browser4-cli-linux-musl-x64 +0 -0
- package/bin/browser4-cli-linux-x64 +0 -0
- package/bin/browser4-cli-win32-x64.exe +0 -0
- package/package.json +2 -2
- package/scripts/check-version-sync.js +0 -44
- package/scripts/sync-version.js +0 -277
package/README.md
CHANGED
|
@@ -109,10 +109,11 @@ browser4-cli -s=<session> <command> [args] [options]
|
|
|
109
109
|
|
|
110
110
|
| Flag | Description |
|
|
111
111
|
|--------------------|------------------------------------------------|
|
|
112
|
-
| `--help [command]` | Print help (optionally for a specific command) |
|
|
113
|
-
| `--version`
|
|
112
|
+
| `-h`, `--help [command]` | Print help (optionally for a specific command) |
|
|
113
|
+
| `-v`, `--version` | Print version |
|
|
114
114
|
| `-s=<name>` | Named session label |
|
|
115
115
|
| `--server=<url>` | Override Browser4 server URL |
|
|
116
|
+
| `--proxy=<url>` | Manual HTTP proxy override for downloads |
|
|
116
117
|
| `--json` | Emit machine-parseable JSON to stdout |
|
|
117
118
|
| `-q`, `--quiet` | Suppress normal output, only show errors |
|
|
118
119
|
|
|
@@ -120,6 +121,9 @@ browser4-cli -s=<session> <command> [args] [options]
|
|
|
120
121
|
single-line JSON envelope (`{"status":"ok","command":"<name>","output":{...}}`).
|
|
121
122
|
Omit `--json` for the default human-readable output.
|
|
122
123
|
|
|
124
|
+
`--proxy=<url>` sets a manual HTTP proxy used only for downloading the
|
|
125
|
+
Browser4 runtime bundle. It does not affect browser traffic.
|
|
126
|
+
|
|
123
127
|
`-q` / `--quiet` suppresses all normal stdout output. Errors and
|
|
124
128
|
progress messages still go to stderr. Combine with `--json` for
|
|
125
129
|
silent-on-success scripting: `browser4-cli --json -q open`.
|
|
@@ -138,20 +142,20 @@ The tables below mirror the commands surfaced by the global `browser4-cli help`
|
|
|
138
142
|
|
|
139
143
|
| Command | Description |
|
|
140
144
|
|---|---|
|
|
141
|
-
| `open [url]` | Open or switch to a browser session. Supports `--headed` (force visible window)
|
|
145
|
+
| `open [url]` | Open or switch to a browser session. Supports `--headed` (force visible window), `--headless` (force headless), `--profile=<path>`, `--profile-mode=<mode>` (temporary / sequential / default), and `--interact-level=<level>` (FASTEST / FAST / DEFAULT). |
|
|
142
146
|
| `close` | Close the active session |
|
|
143
147
|
| `goto <url>` | Navigate to a URL, auto-opening or refreshing the session if needed |
|
|
144
|
-
| `click <ref> [button]` | Click an element |
|
|
145
|
-
| `dblclick <ref> [button]` | Double-click an element |
|
|
146
|
-
| `type <text> [ref]` | Type text into the focused element or an optional target element |
|
|
147
|
-
| `fill <ref> <text>` | Fill text into an editable element |
|
|
148
|
+
| `click <ref> [button]` | Click an element. Supports `--modifiers` (modifier keys to press). |
|
|
149
|
+
| `dblclick <ref> [button]` | Double-click an element. Supports `--modifiers` (modifier keys to press). |
|
|
150
|
+
| `type <text> [ref]` | Type text into the focused element or an optional target element. Supports `--submit` (press Enter after typing). |
|
|
151
|
+
| `fill <ref> <text>` | Fill text into an editable element. Supports `--submit` (press Enter after filling). |
|
|
148
152
|
| `hover <ref>` | Hover over an element |
|
|
149
153
|
| `select <ref> <val>` | Select an option in a dropdown |
|
|
150
154
|
| `upload <ref> <file>` | Upload a file |
|
|
151
155
|
| `check <ref>` | Check a checkbox or radio button |
|
|
152
156
|
| `uncheck <ref>` | Uncheck a checkbox or radio button |
|
|
153
157
|
| `drag <startRef> <endRef>` | Drag and drop between two elements |
|
|
154
|
-
| `snapshot` | Capture accessibility snapshot |
|
|
158
|
+
| `snapshot` | Capture accessibility snapshot. Supports `--filename=<path>` to save snapshot to file. |
|
|
155
159
|
| `eval <expression> [ref]` | Evaluate JavaScript on the page or a target element. Use `--file=<path>` to read the expression from a file. |
|
|
156
160
|
| `get <mode> <selector> [name]` | Extract data from a page element. Modes: `text`, `html`, `box`, `styles`, `property`, `attr`. `name` is required for `property` and `attr`. |
|
|
157
161
|
| `scroll <direction> <pixels>` | Scroll the page. Direction: `up`, `down`, `left`, or `right`. |
|
|
@@ -190,7 +194,8 @@ The tables below mirror the commands surfaced by the global `browser4-cli help`
|
|
|
190
194
|
|
|
191
195
|
| Command | Description |
|
|
192
196
|
|---|---|
|
|
193
|
-
| `screenshot [ref]` | Take a screenshot (optionally of a specific element) |
|
|
197
|
+
| `screenshot [ref]` | Take a screenshot (optionally of a specific element). Supports `--filename=<path>` and `--full-page` (full scrollable page). |
|
|
198
|
+
| `pdf` | Save the current page as a PDF. Supports `--filename=<path>`. |
|
|
194
199
|
|
|
195
200
|
#### Tabs
|
|
196
201
|
|
|
@@ -211,7 +216,7 @@ Run `tab-list` first to find each tab's zero-based index. Pass that index to `ta
|
|
|
211
216
|
| `state-load <path>` | Restore cookies and localStorage from a saved state file |
|
|
212
217
|
| `cookie-list` | List all cookies (optionally filtered by `--domain` / `--path`) |
|
|
213
218
|
| `cookie-get <name>` | Get a cookie by name |
|
|
214
|
-
| `cookie-set <name> <value>` | Set a cookie
|
|
219
|
+
| `cookie-set <name> <value>` | Set a cookie. Supports `--path`, `--domain`, `--expires`, `--httpOnly`, `--secure`, `--sameSite` (Strict / Lax / None). |
|
|
215
220
|
| `cookie-delete <name>` | Delete a cookie by name |
|
|
216
221
|
| `cookie-clear` | Clear all cookies for the current page |
|
|
217
222
|
| `localstorage-list` | List all localStorage entries |
|
|
@@ -229,7 +234,7 @@ Run `tab-list` first to find each tab's zero-based index. Pass that index to `ta
|
|
|
229
234
|
|
|
230
235
|
| Command | Description |
|
|
231
236
|
|---|---|
|
|
232
|
-
| `list` | List browser sessions |
|
|
237
|
+
| `list` | List browser sessions. Supports `--all` (list sessions across all workspaces). |
|
|
233
238
|
| `close-all` | Close all browser sessions without stopping `Browser4.jar` / the Browser4 backend |
|
|
234
239
|
| `kill-all` | Forcefully stop `Browser4.jar` / the Browser4 backend and kill Browser4 browser processes |
|
|
235
240
|
|
|
@@ -241,6 +246,7 @@ Use `close-all` for session cleanup when you want to keep the current Browser4 s
|
|
|
241
246
|
|---|---|
|
|
242
247
|
| `install` | Download the Browser4 runtime bundle. Supports `--tag=<version>` to pin a release and `--force` to reinstall even when already present. |
|
|
243
248
|
| `upgrade` | Upgrade the Browser4 runtime bundle to the latest version or a specified `--tag` |
|
|
249
|
+
| `uninstall` | Remove globally installed browser4-cli (npm, cargo) and its runtime data. Supports `-y` / `--yes` (skip confirmation) and `--dry-run` (preview). |
|
|
244
250
|
| `stop` | Kill the Browser4 backend after closing all sessions |
|
|
245
251
|
| `status` | Check whether the Browser4 backend is reachable and healthy |
|
|
246
252
|
|
|
@@ -256,6 +262,17 @@ When a local Browser4 checkout is detected with the `browser4-bundle` module pre
|
|
|
256
262
|
`install` and `upgrade` auto-build the runtime bundle from source (via Maven) instead
|
|
257
263
|
of downloading.
|
|
258
264
|
|
|
265
|
+
`uninstall` attempts to remove browser4-cli from npm global packages and cargo
|
|
266
|
+
installs, and deletes the Browser4 runtime data and cache directories. It prompts
|
|
267
|
+
for confirmation unless `-y` / `--yes` is passed. Use `--dry-run` to preview what
|
|
268
|
+
would be removed without making changes. Does not require a running server.
|
|
269
|
+
|
|
270
|
+
```shell
|
|
271
|
+
browser4-cli uninstall
|
|
272
|
+
browser4-cli uninstall -y
|
|
273
|
+
browser4-cli uninstall --dry-run
|
|
274
|
+
```
|
|
275
|
+
|
|
259
276
|
### Advanced commands
|
|
260
277
|
|
|
261
278
|
These commands are intentionally omitted from the global `browser4-cli help` overview.
|
|
@@ -265,7 +282,7 @@ Query `browser4-cli help <command>` for the exact syntax when you need them.
|
|
|
265
282
|
|---|---|
|
|
266
283
|
| `batch [command...]` | Execute multiple commands in one invocation. Only DOM operations are supported (Core, Navigation, Keyboard, Mouse, Export, Tabs categories). Commands like `open`, `close`, `list`, `agent run`, etc. are not allowed in batch mode. |
|
|
267
284
|
| `console [min-level]` | List console messages |
|
|
268
|
-
| `extract <instruction>` | Extract structured data from the current page |
|
|
285
|
+
| `extract <instruction>` | Extract structured data from the current page. Supports `--schema` (JSON Schema for typed output). |
|
|
269
286
|
| `summarize [instruction]` | Summarize page content using AI |
|
|
270
287
|
| `agent run <task>` | Run an autonomous agent task |
|
|
271
288
|
| `agent status <id>` | Check the status of a running agent task |
|
|
@@ -433,6 +450,69 @@ browser4-cli swarm query --sql @query.sql --seed-file=./urls.txt --refresh
|
|
|
433
450
|
- Prefix the `--sql` value with `@` to read from a file (e.g. `--sql @query.sql`).
|
|
434
451
|
- All commands return a task ID; use `swarm status` / `swarm result` to track progress.
|
|
435
452
|
|
|
453
|
+
## DOM Snapshot commands (`domsnapshot <subcommand>`)
|
|
454
|
+
|
|
455
|
+
The `domsnapshot` commands capture and query a **static DOM snapshot** of the
|
|
456
|
+
current page. Unlike the real-time accessibility `snapshot`, a DOM snapshot is
|
|
457
|
+
a full HTML capture that can be queried repeatedly with CSS selectors and X-SQL
|
|
458
|
+
without re-fetching the page.
|
|
459
|
+
|
|
460
|
+
Use the spaced `domsnapshot <subcommand>` form:
|
|
461
|
+
|
|
462
|
+
```shell
|
|
463
|
+
browser4-cli domsnapshot
|
|
464
|
+
browser4-cli domsnapshot get text "#productTitle"
|
|
465
|
+
browser4-cli domsnapshot query --sql "SELECT dom_first_text(dom, '#title') AS title FROM dom(dom)"
|
|
466
|
+
browser4-cli domsnapshot export --file=page.html
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Command overview
|
|
470
|
+
|
|
471
|
+
| Command | Description |
|
|
472
|
+
|---|---|
|
|
473
|
+
| `domsnapshot` | Capture a static DOM snapshot of the current page |
|
|
474
|
+
| `domsnapshot get <field> [selector] [name]` | Extract elements from the static DOM snapshot |
|
|
475
|
+
| `domsnapshot query [url] --sql=<query>` | Run X-SQL against the DOM snapshot via the scrape API |
|
|
476
|
+
| `domsnapshot export [--file=<path>]` | Save full snapshot HTML content to a local file |
|
|
477
|
+
|
|
478
|
+
### `domsnapshot get` fields
|
|
479
|
+
|
|
480
|
+
| Field | Description |
|
|
481
|
+
|---|---|
|
|
482
|
+
| `text` | Extract text content from the matched element |
|
|
483
|
+
| `html` | Extract inner HTML from the matched element |
|
|
484
|
+
| `attr` | Extract an attribute value (requires `name`, e.g. `attr "#link" "href"`) |
|
|
485
|
+
|
|
486
|
+
`selector` defaults to `:root` when omitted. `name` (attribute name) is
|
|
487
|
+
required for the `attr` field.
|
|
488
|
+
|
|
489
|
+
### `domsnapshot query` with X-SQL
|
|
490
|
+
|
|
491
|
+
```shell
|
|
492
|
+
# Inline X-SQL:
|
|
493
|
+
browser4-cli domsnapshot query "https://example.com" --sql "
|
|
494
|
+
SELECT
|
|
495
|
+
dom_base_uri(dom) AS url,
|
|
496
|
+
dom_first_text(dom, 'h1') AS title
|
|
497
|
+
FROM dom(dom);
|
|
498
|
+
"
|
|
499
|
+
|
|
500
|
+
# From a file:
|
|
501
|
+
browser4-cli domsnapshot query --sql @query.sql
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
- `--sql` is **required**. Prefix with `@` to read from a file (e.g. `--sql @query.sql`).
|
|
505
|
+
- `url` is optional and defaults to the current session's page URL.
|
|
506
|
+
- These commands do not require an active swarm session.
|
|
507
|
+
|
|
508
|
+
### Notes
|
|
509
|
+
|
|
510
|
+
- `domsnapshot` commands are not supported inside `batch` mode.
|
|
511
|
+
- Use `domsnapshot get` for quick element extraction; use `domsnapshot query`
|
|
512
|
+
with X-SQL for structured multi-element extraction.
|
|
513
|
+
- The DOM snapshot is cached in the backend until the next `domsnapshot`
|
|
514
|
+
capture or session navigation.
|
|
515
|
+
|
|
436
516
|
## Element References
|
|
437
517
|
|
|
438
518
|
The `snapshot` command returns an accessibility tree where every interactive
|
|
@@ -517,12 +597,27 @@ browser4-cli open
|
|
|
517
597
|
browser4-cli open --headed https://browser4.io
|
|
518
598
|
browser4-cli open --headless https://browser4.io
|
|
519
599
|
|
|
600
|
+
# Open with a persistent profile
|
|
601
|
+
browser4-cli open --profile=~/.browser4/profiles/work
|
|
602
|
+
|
|
603
|
+
# Open with a temporary (single-use) profile
|
|
604
|
+
browser4-cli open --profile-mode=temporary https://browser4.io
|
|
605
|
+
|
|
606
|
+
# Open with interact-level control (FASTEST / FAST / DEFAULT)
|
|
607
|
+
browser4-cli open --interact-level=FAST https://browser4.io
|
|
608
|
+
|
|
520
609
|
# Navigate to a page — auto-opens a session if none is active
|
|
521
610
|
browser4-cli goto https://browser4.io
|
|
522
611
|
|
|
523
612
|
# Inspect the page — note the eN labels on interactive nodes
|
|
524
613
|
browser4-cli snapshot
|
|
525
614
|
|
|
615
|
+
# Capture a static DOM snapshot and extract data
|
|
616
|
+
browser4-cli domsnapshot
|
|
617
|
+
browser4-cli domsnapshot get text "#productTitle"
|
|
618
|
+
browser4-cli domsnapshot get attr "#productTitle" "href"
|
|
619
|
+
browser4-cli domsnapshot export --file=page.html
|
|
620
|
+
|
|
526
621
|
# Interact using refs from the snapshot
|
|
527
622
|
browser4-cli click e15
|
|
528
623
|
browser4-cli type "Hello World" e15
|
|
@@ -537,6 +632,12 @@ browser4-cli keyup Shift
|
|
|
537
632
|
# Take a screenshot and save it to disk
|
|
538
633
|
browser4-cli screenshot
|
|
539
634
|
|
|
635
|
+
# Take a full-page screenshot
|
|
636
|
+
browser4-cli screenshot --full-page
|
|
637
|
+
|
|
638
|
+
# Save the current page as a PDF
|
|
639
|
+
browser4-cli pdf --filename=page.pdf
|
|
640
|
+
|
|
540
641
|
# Inspect tab indices before switching tabs
|
|
541
642
|
browser4-cli tab-list
|
|
542
643
|
browser4-cli tab-select 1
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser4-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "Browser automation CLI for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"browser4": "./bin/browser4-cli.js"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"version:sync": "node
|
|
17
|
+
"version:sync": "node ../bin/version.mjs cli sync",
|
|
18
18
|
"version": "npm run version:sync && git add browser4-cli/Cargo.toml browser4-cli/Cargo.lock",
|
|
19
19
|
"build:native": "npm run version:sync && cargo build --release --manifest-path browser4-cli/Cargo.toml && node scripts/copy-native.js",
|
|
20
20
|
"build:linux": "npm run version:sync && docker compose -f docker/docker-compose.yml run --rm build-linux",
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Verifies that package.json and browser4-cli/Cargo.toml have the same version.
|
|
5
|
-
* Used in CI to catch version drift.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync } from 'fs';
|
|
9
|
-
import { dirname, join } from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
|
|
12
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const rootDir = join(__dirname, '..');
|
|
14
|
-
|
|
15
|
-
// Read package.json version
|
|
16
|
-
const packageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8'));
|
|
17
|
-
const packageVersion = packageJson.version;
|
|
18
|
-
|
|
19
|
-
// Read Cargo.toml version
|
|
20
|
-
const cargoToml = readFileSync(join(rootDir, 'browser4-cli/Cargo.toml'), 'utf-8');
|
|
21
|
-
const cargoVersionMatch = cargoToml.match(/^version\s*=\s*"([^"]*)"/m);
|
|
22
|
-
|
|
23
|
-
if (!cargoVersionMatch) {
|
|
24
|
-
console.error('Could not find version in browser4-cli/Cargo.toml');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const cargoVersion = cargoVersionMatch[1];
|
|
29
|
-
|
|
30
|
-
const mismatches = [];
|
|
31
|
-
if (packageVersion !== cargoVersion) {
|
|
32
|
-
mismatches.push(` browser4-cli/Cargo.toml: ${cargoVersion}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (mismatches.length > 0) {
|
|
36
|
-
console.error('Version mismatch detected!');
|
|
37
|
-
console.error(` package.json: ${packageVersion}`);
|
|
38
|
-
for (const m of mismatches) console.error(m);
|
|
39
|
-
console.error('');
|
|
40
|
-
console.error("Run 'pnpm run version:sync' to fix this.");
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
console.log(`Versions are in sync: ${packageVersion}`);
|
package/scripts/sync-version.js
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Syncs the CLI version from cli/VERSION-CLI to all dependent files.
|
|
5
|
-
*
|
|
6
|
-
* cli/VERSION-CLI is the single source of truth for the CLI version. This
|
|
7
|
-
* allows the backend Maven project and the CLI to be published separately with
|
|
8
|
-
* different versions. This script:
|
|
9
|
-
* - Reads the version from cli/VERSION-CLI
|
|
10
|
-
* - Strips any "-SNAPSHOT" suffix (if present)
|
|
11
|
-
* - Fetches the latest published version from npm and compares it
|
|
12
|
-
* against the local version, warning when the bump is neither a
|
|
13
|
-
* patch nor a minor increment.
|
|
14
|
-
* - Writes the version to cli/package.json
|
|
15
|
-
* - Writes the version to cli/browser4-cli/Cargo.toml
|
|
16
|
-
* - Updates Cargo.lock to match
|
|
17
|
-
*
|
|
18
|
-
* Usage:
|
|
19
|
-
* node cli/scripts/sync-version.js # sync all files
|
|
20
|
-
* node cli/scripts/sync-version.js --check # exit 1 if out of sync (CI lint)
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import {execSync} from "child_process";
|
|
24
|
-
import {readFileSync, writeFileSync} from "fs";
|
|
25
|
-
import {dirname, join, resolve} from "path";
|
|
26
|
-
import {fileURLToPath} from "url";
|
|
27
|
-
|
|
28
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
-
const scriptsDir = __dirname; // cli/scripts
|
|
30
|
-
const cliDir = resolve(scriptsDir, ".."); // cli
|
|
31
|
-
const cargoDir = join(cliDir, "browser4-cli"); // cli/browser4-cli
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Helpers
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
/** Strip the "-SNAPSHOT" suffix if present. */
|
|
38
|
-
function stripSnapshot(version) {
|
|
39
|
-
if (version.endsWith("-SNAPSHOT")) {
|
|
40
|
-
return version.slice(0, -"-SNAPSHOT".length);
|
|
41
|
-
}
|
|
42
|
-
return version;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Parse a semver string into { major, minor, patch } integers.
|
|
47
|
-
* Returns null for non-semver strings (e.g. snapshots, prereleases).
|
|
48
|
-
*/
|
|
49
|
-
function parseSemver(version) {
|
|
50
|
-
const m = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
51
|
-
if (!m) return null;
|
|
52
|
-
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Fetch the latest published version of a package from the npm registry.
|
|
57
|
-
* Returns the version string, or null if the package hasn't been published
|
|
58
|
-
* yet or the registry is unreachable.
|
|
59
|
-
*/
|
|
60
|
-
function getLatestPublishedVersion(packageName) {
|
|
61
|
-
try {
|
|
62
|
-
const raw = execSync(`npm view "${packageName}" version --json`, {
|
|
63
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
64
|
-
timeout: 10_000,
|
|
65
|
-
});
|
|
66
|
-
return JSON.parse(raw.toString().trim());
|
|
67
|
-
} catch {
|
|
68
|
-
// Package may not be published yet, or network is unavailable.
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Check whether `local` is an expected bump from `published`:
|
|
75
|
-
* - same version (already published)
|
|
76
|
-
* - next patch
|
|
77
|
-
* - next minor
|
|
78
|
-
*
|
|
79
|
-
* Returns { ok: true } or { ok: false, reason: "..." }.
|
|
80
|
-
*/
|
|
81
|
-
function checkVersionBump(published, local) {
|
|
82
|
-
const pub = parseSemver(published);
|
|
83
|
-
const loc = parseSemver(local);
|
|
84
|
-
|
|
85
|
-
if (!pub || !loc) {
|
|
86
|
-
// Can't compare — pre-release / snapshot versions are fine.
|
|
87
|
-
return { ok: true };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Same version — already published.
|
|
91
|
-
if (loc.major === pub.major && loc.minor === pub.minor && loc.patch === pub.patch) {
|
|
92
|
-
return { ok: true, note: `version ${local} is already published` };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Next patch: X.Y.Z → X.Y.(Z+1)
|
|
96
|
-
if (loc.major === pub.major && loc.minor === pub.minor && loc.patch === pub.patch + 1) {
|
|
97
|
-
return { ok: true };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Next minor: X.Y.Z → X.(Y+1).0
|
|
101
|
-
if (loc.major === pub.major && loc.minor === pub.minor + 1 && loc.patch === 0) {
|
|
102
|
-
return { ok: true };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Local is behind published.
|
|
106
|
-
if (
|
|
107
|
-
loc.major < pub.major ||
|
|
108
|
-
(loc.major === pub.major && loc.minor < pub.minor) ||
|
|
109
|
-
(loc.major === pub.major && loc.minor === pub.minor && loc.patch < pub.patch)
|
|
110
|
-
) {
|
|
111
|
-
return {
|
|
112
|
-
ok: false,
|
|
113
|
-
reason: `local version ${local} is behind the published version ${published}`,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Anything else — skipping versions, major bump, etc.
|
|
118
|
-
return {
|
|
119
|
-
ok: false,
|
|
120
|
-
reason: `version bump from ${published} to ${local} is neither a patch nor a minor increment`,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// Main
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
const checkOnly = process.argv.includes("--check");
|
|
129
|
-
|
|
130
|
-
// The npm package name (hard-coded — stable across versions)
|
|
131
|
-
const pkgName = "browser4-cli";
|
|
132
|
-
|
|
133
|
-
// 1. Read the CLI version from cli/VERSION-CLI (the single source of truth)
|
|
134
|
-
const versionCliPath = join(cliDir, "VERSION-CLI");
|
|
135
|
-
let versionRaw;
|
|
136
|
-
try {
|
|
137
|
-
versionRaw = readFileSync(versionCliPath, "utf-8").trim();
|
|
138
|
-
} catch {
|
|
139
|
-
console.error(`ERROR: Cannot read ${versionCliPath}`);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!versionRaw) {
|
|
144
|
-
console.error("ERROR: cli/VERSION-CLI is empty.");
|
|
145
|
-
process.exit(1);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const version = stripSnapshot(versionRaw);
|
|
149
|
-
if (checkOnly) console.log(`cli/VERSION-CLI: ${version}`);
|
|
150
|
-
|
|
151
|
-
// 2. Compare against the latest published version on npm
|
|
152
|
-
const publishedVersion = getLatestPublishedVersion(pkgName);
|
|
153
|
-
if (publishedVersion) {
|
|
154
|
-
console.log(`Latest published: ${pkgName}@${publishedVersion}`);
|
|
155
|
-
const bump = checkVersionBump(publishedVersion, version);
|
|
156
|
-
if (!bump.ok) {
|
|
157
|
-
console.warn(`\x1b[33mWARNING: ${bump.reason}\x1b[0m`);
|
|
158
|
-
} else if (bump.note) {
|
|
159
|
-
console.log(` (${bump.note})`);
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
console.log(`Latest published: (not found — package may not be published yet)`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
// 3. Sync cli/package.json
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
const packageJsonPath = join(cliDir, "package.json");
|
|
169
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
170
|
-
|
|
171
|
-
if (!packageJson.version) {
|
|
172
|
-
console.error("ERROR: cli/package.json does not contain a version field.");
|
|
173
|
-
process.exit(1);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (packageJson.version !== version) {
|
|
177
|
-
if (checkOnly) {
|
|
178
|
-
console.error(`MISMATCH: ${packageJsonPath} version is "${packageJson.version}", expected "${version}"`);
|
|
179
|
-
process.exitCode = 1;
|
|
180
|
-
} else {
|
|
181
|
-
const oldVersion = packageJson.version;
|
|
182
|
-
packageJson.version = version;
|
|
183
|
-
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
184
|
-
console.log(` Updated ${packageJsonPath}: ${oldVersion} -> ${version}`);
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
if (!checkOnly) console.log(` ${packageJsonPath} already up to date`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
// 4. Sync cli/browser4-cli/Cargo.toml
|
|
192
|
-
// ---------------------------------------------------------------------------
|
|
193
|
-
const cargoTomlPath = join(cargoDir, "Cargo.toml");
|
|
194
|
-
let cargoToml = readFileSync(cargoTomlPath, "utf-8");
|
|
195
|
-
const cargoVersionRegex = /^version\s*=\s*"[^"]*"/m;
|
|
196
|
-
const cargoMatch = cargoToml.match(cargoVersionRegex);
|
|
197
|
-
|
|
198
|
-
if (!cargoMatch) {
|
|
199
|
-
console.error("ERROR: Could not find version field in Cargo.toml");
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const cargoVersion = cargoMatch[0].match(/"([^"]*)"/)[1];
|
|
204
|
-
if (cargoVersion !== version) {
|
|
205
|
-
if (checkOnly) {
|
|
206
|
-
console.error(`MISMATCH: ${cargoTomlPath} version is "${cargoVersion}", expected "${version}"`);
|
|
207
|
-
process.exitCode = 1;
|
|
208
|
-
} else {
|
|
209
|
-
cargoToml = cargoToml.replace(cargoVersionRegex, `version = "${version}"`);
|
|
210
|
-
writeFileSync(cargoTomlPath, cargoToml);
|
|
211
|
-
console.log(` Updated ${cargoTomlPath}: ${cargoVersion} -> ${version}`);
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
if (!checkOnly) console.log(` ${cargoTomlPath} already up to date`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ---------------------------------------------------------------------------
|
|
218
|
-
// 5. Update Cargo.lock (only in sync mode)
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
if (!checkOnly && cargoVersion !== version) {
|
|
221
|
-
let lockUpdated = false;
|
|
222
|
-
try {
|
|
223
|
-
execSync("cargo update -p browser4-cli --offline", {
|
|
224
|
-
cwd: cargoDir,
|
|
225
|
-
stdio: "pipe",
|
|
226
|
-
});
|
|
227
|
-
console.log(" Updated Cargo.lock");
|
|
228
|
-
lockUpdated = true;
|
|
229
|
-
} catch {
|
|
230
|
-
try {
|
|
231
|
-
execSync("cargo update -p browser4-cli", {
|
|
232
|
-
cwd: cargoDir,
|
|
233
|
-
stdio: "pipe",
|
|
234
|
-
});
|
|
235
|
-
console.log(" Updated Cargo.lock");
|
|
236
|
-
lockUpdated = true;
|
|
237
|
-
} catch (e) {
|
|
238
|
-
console.error(` Warning: Could not update Cargo.lock via cargo: ${e.message}`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Fallback: edit Cargo.lock directly when cargo update fails (e.g. due to
|
|
243
|
-
// lock file version mismatch between the installed cargo and the lock file).
|
|
244
|
-
if (!lockUpdated) {
|
|
245
|
-
const cargoLockPath = join(cargoDir, "Cargo.lock");
|
|
246
|
-
try {
|
|
247
|
-
let lockContent = readFileSync(cargoLockPath, "utf-8");
|
|
248
|
-
// Match the [[package]] block for browser4-cli and replace its version.
|
|
249
|
-
// Works for both v3 and v4 lock file formats.
|
|
250
|
-
const lockPkgRegex = new RegExp(
|
|
251
|
-
`(\\[\\[package\\]\\][\\r\\n]+\\s*name\\s*=\\s*"browser4-cli"[\\r\\n]+\\s*version\\s*=\\s*)"[^"]*"`,
|
|
252
|
-
"m"
|
|
253
|
-
);
|
|
254
|
-
if (lockPkgRegex.test(lockContent)) {
|
|
255
|
-
lockContent = lockContent.replace(lockPkgRegex, `$1"${version}"`);
|
|
256
|
-
writeFileSync(cargoLockPath, lockContent);
|
|
257
|
-
console.log(` Updated Cargo.lock directly: ${cargoVersion} -> ${version}`);
|
|
258
|
-
lockUpdated = true;
|
|
259
|
-
} else {
|
|
260
|
-
console.error(" Warning: Could not find browser4-cli entry in Cargo.lock");
|
|
261
|
-
}
|
|
262
|
-
} catch (e2) {
|
|
263
|
-
console.error(` Warning: Could not update Cargo.lock directly: ${e2.message}`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// 6. Report
|
|
269
|
-
if (checkOnly) {
|
|
270
|
-
if (process.exitCode === 1) {
|
|
271
|
-
console.error("\nVersion mismatch detected! Run 'node cli/scripts/sync-version.js' to fix.");
|
|
272
|
-
} else {
|
|
273
|
-
console.log(`\nAll versions in sync with cli/VERSION-CLI: ${version}`);
|
|
274
|
-
}
|
|
275
|
-
} else {
|
|
276
|
-
console.log(`\nVersion sync complete: ${pkgName}@${version}`);
|
|
277
|
-
}
|