@ai-ecoverse/slicc-capture 1.3.1 → 1.3.3
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 +62 -14
- package/host/capture.mjs +3 -1
- package/host/upgrade.mjs +16 -7
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -31,19 +31,13 @@ On Windows, in PowerShell:
|
|
|
31
31
|
irm https://get.sliccy.ai/slicc-capture.ps1 | iex
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Without get.sliccy.ai, the same installer comes straight from the release:
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
37
|
curl -fsSL https://github.com/ai-ecoverse/slicc-capture/releases/latest/download/install.sh | sh
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
| --- | --- | --- |
|
|
42
|
-
| `--version VERSION` | `-Version VERSION` | the release to install (default: the latest) |
|
|
43
|
-
| `--dir DIR` | `-Dir DIR` | where the launcher goes (default: `~/.local/bin`; on Windows `%LOCALAPPDATA%\slicc-capture\bin`, added to the user's `PATH`) |
|
|
44
|
-
| `--help` | `-Help` | print the usage and exit 0 |
|
|
45
|
-
|
|
46
|
-
Options reach `install.sh` after `sh -s --`, for example `curl -fsSL https://get.sliccy.ai/slicc-capture | sh -s -- --version 1.2.0 --dir ~/bin`, and `install.ps1` through a script block: `& ([scriptblock]::Create((irm https://get.sliccy.ai/slicc-capture.ps1))) -Version 1.2.0`. An unknown option, or `--dir` or `--version` without a value, prints `install.sh: <problem>` and the usage to stderr and exits 2; there are no environment overrides for the options. A failure is `install.sh: <problem>` (`install.ps1: <problem>`) on stderr and exit 1. `SLICC_INSTALL_BASE_URL` replaces the release download directory (by default `https://github.com/ai-ecoverse/slicc-capture/releases/latest/download`, or `…/download/v<version>` with `--version`), and `SLICC_NODE_DIST_BASE` replaces `https://nodejs.org/dist`. The installed command must answer `--version` with the version it was installed as (or the older `sccapture <version>`). On Windows the launcher is `sccapture.ps1`, next to a `sccapture.cmd` for `cmd.exe`; both only start `node.exe` with the entry script and the arguments, and `sccapture` itself never runs `cmd.exe`.
|
|
40
|
+
Options reach `install.sh` after `sh -s --`, for example `curl -fsSL https://get.sliccy.ai/slicc-capture | sh -s -- --version 1.2.0 --dir ~/bin`, and `install.ps1` through a script block: `& ([scriptblock]::Create((irm https://get.sliccy.ai/slicc-capture.ps1))) -Version 1.2.0`. On Windows the launchers are `sccapture.ps1` and `sccapture.cmd`, and the bin directory is added to the user's `PATH`. [The installer](#the-installer) has the details.
|
|
47
41
|
|
|
48
42
|
The get.sliccy.ai entries for `slicc-capture` and `slicc-capture.ps1` are added once the first release is out.
|
|
49
43
|
|
|
@@ -93,7 +87,7 @@ Errors, on stderr, from slicc 6 where they still apply:
|
|
|
93
87
|
| `screencapture: interrupted` (`SIGHUP`, `SIGINT` or `SIGTERM` during the request: the command stops the reader, so the page's `signal` aborts with `interrupted`, removes its temporary files and writes no file) | `128 + n` |
|
|
94
88
|
| `screencapture: failed to write file: <file>: <message>` | 1 |
|
|
95
89
|
| `screencapture: failed to create a temporary directory in <dir>` (before the request: `mktemp -d` failed in `$TMPDIR`, else `/tmp`; nothing is sent or written) | 1 |
|
|
96
|
-
| `screencapture: failed to create a temporary file in <dir>` (`-v` without a file, after the capture: `mktemp` failed;
|
|
90
|
+
| `screencapture: failed to create a temporary file in <dir>` (`-v` without a file, after the capture: `mktemp` failed; no file is written and no marker is printed; with `-c`, the clipboard copy has already been made) | 1 |
|
|
97
91
|
| `screencapture: failed to copy to clipboard: this page does not copy images (no clipboard.write)` (`ENOTSUP`: no page answers `clipboard.write`) | 1 |
|
|
98
92
|
| `screencapture: failed to copy to clipboard: <message>` (the page's message, else the errno's text) | 1 |
|
|
99
93
|
| `screencapture: /dev/embedder not available (needs slicc-kernel >= 1.153.0)` (a kernel that runs the commands without the device) | 1 |
|
|
@@ -224,6 +218,8 @@ Under Wayland, `grim` needs a compositor with wlr-screencopy (sway, Hyprland and
|
|
|
224
218
|
|
|
225
219
|
`sccapture` runs `powershell.exe` (else `pwsh.exe`) with `-NoProfile -NonInteractive -STA -EncodedCommand` and a fixed script; the file name and the option values reach it as data, in `SCCAPTURE_*` environment variables, never as script. (PowerShell reads a script on stdin line by line, as if typed, so a multi-line block in it can stop at the first blank line; `-EncodedCommand` is what slicc-clipboard uses.) The process is made per-monitor DPI-aware first (system DPI-aware before Windows 10 1703), so a scaled display is captured at its real resolution, even when the monitors' scales differ. It copies the virtual screen (every monitor), or the screen `-m`/`-D` names, or the `-R` rectangle of that (its `x,y` from the top left of the virtual screen, or of the screen `-m`/`-D` names), with `Graphics.CopyFromScreen`, draws the cursor for `-C`, and saves with the `-t` format's `ImageFormat`, or `Clipboard.SetImage` for `-c`.
|
|
226
220
|
|
|
221
|
+
In PowerShell, quote the rectangle: `sccapture -R '0,0,800,600' shot.png`. Unquoted, `0,0,800,600` is an array, which PowerShell passes to any program, `sccapture.ps1` included, as four arguments. In `cmd.exe` no quotes are needed. Run from PowerShell 7, `sccapture` captures through Windows PowerShell with pwsh's `PSModulePath` inherited; CI checks that this works.
|
|
222
|
+
|
|
227
223
|
### In SLICC, the SLICC commands win
|
|
228
224
|
|
|
229
225
|
The package has both `bin` and `slicc.commands`, and both name `sccapture`. As with slicc-clipboard, the kernel ignores a package's `bin` entry when the same package declares that name in `slicc.commands`, and bios removes the `$PNPM_HOME/bin` link pnpm makes for it, so the Node script, which SLICC has no Node to run, never shadows the SLICC command.
|
|
@@ -232,12 +228,63 @@ The package has both `bin` and `slicc.commands`, and both name `sccapture`. As w
|
|
|
232
228
|
|
|
233
229
|
Every host command takes `--upgrade`. It runs the latest release's installer (`install.sh`, or `install.ps1` on Windows) with `--dir` set to the directory the command is installed in, so the new version replaces the old one in place, then prints `<cmd>: <old> → <new>` (for example `sccapture: 1.2.0 → 1.3.0`), or `<cmd>: already up to date (<version>)`, to stdout and exits 0. If the installer fails, its stderr is passed through and the command exits 1. A command installed with npm prints `npm install -g @ai-ecoverse/slicc-capture@latest` to run instead, and exits 0. In SLICC, `--upgrade` prints `pnpm add -g @ai-ecoverse/slicc-capture@latest` to run, and exits 0; it installs nothing itself. `--upgrade` takes no other arguments.
|
|
234
230
|
|
|
235
|
-
For slicc-capture, the commands are `sccapture` on a host and `screencapture` and `sccapture` in SLICC.
|
|
236
|
-
|
|
237
|
-
The launcher the installer writes sets `SCCAPTURE_INSTALL_DIR` to its own directory; that is how `sccapture` knows the installer put it there (it removes the variable before it starts anything else), and without it `sccapture` counts as installed with npm. The installer is downloaded with Node's `fetch` from `https://github.com/ai-ecoverse/slicc-capture/releases/latest/download/` and run as `sh install.sh --dir DIR`, or `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File install.ps1 -Dir DIR` (else `pwsh.exe`); its stderr is passed through, and the line it prints, `sccapture <version> installed to <launcher>`, names the new version. Without `sh` (or PowerShell) it prints `sccapture: no shell found to run install.sh (tried sh)`, and a failed download `sccapture: could not download <url>: <reason>`, and exits 1. `SLICC_INSTALL_BASE_URL`, when set, replaces that release URL for `--upgrade` and for the installers' own downloads.
|
|
231
|
+
For slicc-capture, the commands are `sccapture` on a host and `screencapture` and `sccapture` in SLICC. [How `--upgrade` uses it](#how---upgrade-uses-it) has the details. A download that fails prints `sccapture: could not download <url>: <reason>`, one that takes more than 60 seconds `sccapture: download timed out`, and with no `sh` (or PowerShell) it prints `sccapture: no shell found to run install.sh (tried sh)`; each exits 1.
|
|
238
232
|
|
|
239
233
|
`--version` prints the version (`0.1.0`) to stdout and exits 0, on a host and in SLICC; the installers run the installed command with it and check it reports the version they installed. It takes no other arguments either.
|
|
240
234
|
|
|
235
|
+
## The installer
|
|
236
|
+
|
|
237
|
+
This section is the installer contract that slicc-open, slicc-capture, slicc-audio and slicc-man share ([slicc-open#7](https://github.com/ai-ecoverse/slicc-open/issues/7)). [slicc-open](https://github.com/ai-ecoverse/slicc-open)'s `install.sh`, `install.ps1`, `scripts/assets.mjs` and `host/upgrade.mjs` are the reference implementation, and slicc-capture's are copies of them with the names swapped. The names below are written as placeholders; for slicc-capture:
|
|
238
|
+
|
|
239
|
+
| placeholder | slicc-capture |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `<pkg>` | `slicc-capture` (the npm package is `@ai-ecoverse/<pkg>`, the repo `ai-ecoverse/<pkg>`) |
|
|
242
|
+
| `<cmd>` | `sccapture`, each of the package's host commands (its `bin`) |
|
|
243
|
+
| `<CMD>` | `SCCAPTURE`, `<cmd>` in upper case with `-` as `_` |
|
|
244
|
+
|
|
245
|
+
### Release assets
|
|
246
|
+
|
|
247
|
+
semantic-release's `@semantic-release/npm` packs the package into `release/` (`tarballDir`). `@semantic-release/exec` then runs `node scripts/assets.mjs release <version>`, which refuses (exit 1, leaving `release/` as it was, npm's tarball name included) when the tarball's `package.json` has no `bin` or lacks a file a `bin` entry names, and otherwise renames the tarball to `<pkg>-<version>.tgz`, copies `install.sh` and `install.ps1` next to it, and writes `SHA256SUMS`: one `<sha256> <name>` line each for the tarball, `install.sh` and `install.ps1`, in that order. `@semantic-release/github` attaches all four files to the release. `ci.yml` needs no release step of its own.
|
|
248
|
+
|
|
249
|
+
### Options and exit codes
|
|
250
|
+
|
|
251
|
+
| `install.sh` | `install.ps1` | |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| `--version VERSION` (or `--version=VERSION`; a leading `v` is dropped) | `-Version VERSION` | the release to install (default: the latest) |
|
|
254
|
+
| `--dir DIR` (or `--dir=DIR`) | `-Dir DIR` | where the launchers go (default: `~/.local/bin`; on Windows `%LOCALAPPDATA%\<pkg>\bin`) |
|
|
255
|
+
| `--help` (or `-h`) | `-Help` | print the usage to stdout and exit 0 |
|
|
256
|
+
|
|
257
|
+
Only the flags select anything: no environment variable stands in for `--version` or `--dir`. `install.sh` handles its options (`--help` and the usage errors included) before it works out any directory, so they work without `HOME`. `HOME` is needed only for the defaults: without it, and without both `--dir` and `XDG_DATA_HOME`, `install.sh` exits 1 with `install.sh: HOME is not set; give --dir and set XDG_DATA_HOME`. `install.sh` exits **0** when it installed, **1** when something failed (a download, a checksum, an archive that doesn't unpack, no Node and no way to get one), with `install.sh: <what>` on stderr, and **2** for an unknown option or a missing value, with the usage on stderr. `install.ps1` exits 0 or 1 the same way when it is run as a file, and its parameters are PowerShell's own, so an unknown one is PowerShell's error. Run through `irm … | iex` or a script block, it prints the error and returns instead of calling `exit`, so it never closes the user's PowerShell. It sets `$PSNativeCommandUseErrorActionPreference = $false` in its own scope, so a caller's `$true` (pwsh 7.4 and later) can't turn the exit code of `node`, `tar` or `npm` into an error that stops the install. It runs `node --version`, `tar` and `npm` with `$ErrorActionPreference = 'Continue'` in a child scope, because Windows PowerShell 5.1 turns each line a native command writes to a redirected stderr into an error, and under `'Stop'` the first one would end the install before its `install.ps1:` message.
|
|
258
|
+
|
|
259
|
+
### Node
|
|
260
|
+
|
|
261
|
+
The launchers run the package with Node 24 or later. The installer uses the first `node` on `PATH` when it is new enough, otherwise one it downloaded before, into `<data>/node`. Failing both, it downloads the newest LTS release of 24 or later (or the newest release, when no LTS qualifies) from `index.json`. That download is `node-<version>-<os>-<arch>.tar.gz` (`darwin` or `linux`, `x64` or `arm64`), or `node-<version>-win-<arch>.zip` on Windows, checked against that release's `SHASUMS256.txt`. An archive that doesn't unpack exits 1 with `install.sh: could not unpack node-<version>-<os>-<arch>.tar.gz` (or `install.ps1: could not unpack node-<version>-win-<arch>.zip`), and leaves neither its temporary directory nor a partial `<data>/node` behind. `.tar.gz` is used because `xz` isn't on every minimal Linux. On musl Linux, where nodejs.org has no build, the installer says to install Node with the package manager and exits 1.
|
|
262
|
+
|
|
263
|
+
### Downloads
|
|
264
|
+
|
|
265
|
+
| variable | default | |
|
|
266
|
+
| --- | --- | --- |
|
|
267
|
+
| `SLICC_INSTALL_BASE_URL` | `https://github.com/ai-ecoverse/<pkg>/releases/latest/download`, or `…/releases/download/v<version>` with `--version` | the directory holding the release assets (`SHA256SUMS`, `<pkg>-<version>.tgz`, `install.sh`, `install.ps1`) |
|
|
268
|
+
| `SLICC_NODE_DIST_BASE` | `https://nodejs.org/dist` | where `index.json` and `v<version>/SHASUMS256.txt` and the Node archives are |
|
|
269
|
+
|
|
270
|
+
`install.sh` fetches with `curl`, else `wget` (`curl` also reads the `file://` URLs the tests use), and checks checksums with `sha256sum`, else `shasum -a 256`. `install.ps1` uses `Invoke-WebRequest` and `Get-FileHash`. Without `--version`, the tarball is the `<pkg>-<version>.tgz` named in `SHA256SUMS`, so the version comes from the release itself. A tarball whose checksum doesn't match is refused before anything is unpacked.
|
|
271
|
+
|
|
272
|
+
### Files
|
|
273
|
+
|
|
274
|
+
The tarball is unpacked (with `tar`, `%SystemRoot%\System32\tar.exe` on Windows) into a temporary directory next to the versioned data directory `<data>/<version>`. A tarball that doesn't unpack exits 1 with `install.sh: could not unpack <pkg>-<version>.tgz` (or `install.ps1: could not unpack <pkg>-<version>.tgz`) and removes the partial directory. When its `package.json` has a non-empty `dependencies` or `optionalDependencies`, the installer removes `devDependencies` from that copy of `package.json` (npm would still resolve them against the registry, `--omit=dev` or not), then runs `npm install --omit=dev --ignore-scripts --no-audit --no-fund` there. It uses the npm that comes with the Node it picked, run by that Node (`<node dir>/../lib/node_modules/npm`, or `node_modules\npm` next to `node.exe`; `install.sh` falls back to `npm` on `PATH`). npm's output goes to stderr. Without such dependencies the step is skipped, so it needs neither npm nor the network. A failed `npm install`, or a missing npm, exits 1 and removes the partial directory. Only then does the directory replace `<data>/<version>`, so a failed install never leaves a broken version behind. `<data>` is `${XDG_DATA_HOME:-~/.local/share}/<pkg>`, or `%LOCALAPPDATA%\<pkg>` on Windows. Other versions stay where they are, and there is no `current` link. Each launcher names its version's entry script, `<data>/<version>/host/<cmd>.mjs`, and the Node it was installed with, by absolute path.
|
|
275
|
+
|
|
276
|
+
| platform | launchers in `DIR` | what they do |
|
|
277
|
+
| --- | --- | --- |
|
|
278
|
+
| macOS, Linux | `<cmd>`, a POSIX sh script | sets and exports `<CMD>_INSTALL_DIR='<DIR>'`, exports `NODE_USE_ENV_PROXY=1` unless it is already set, then `exec '<node>' '<entry>' "$@"`; every path is single-quoted, so no path is ever evaluated |
|
|
279
|
+
| Windows | `<cmd>.ps1` (UTF-8 with a BOM) | saves the caller's `$env:<CMD>_INSTALL_DIR` and sets it, sets `$env:NODE_USE_ENV_PROXY = '1'` when it is unset, runs `& '<node.exe>' '<entry>' @sccaptureArgs` in `try`, restores the old `<CMD>_INSTALL_DIR` in `finally` (or removes it when it was unset) and removes a `NODE_USE_ENV_PROXY` it set, then exits with node's exit code. A `.ps1` runs inside the caller's PowerShell session, and this keeps the variable from leaking into that session. `@sccaptureArgs` is `@args` with each array argument (`a,b`) flattened into separate arguments, as PowerShell passes them to a program, and each argument as text. When PowerShell passes arguments to programs the legacy way, which drops empty arguments and doesn't escape `"` (Windows PowerShell 5.1 whatever `$PSNativeCommandArgumentPassing` says, `$PSNativeCommandArgumentPassing = 'Legacy'`, or pwsh before 7.3 without the variable; pwsh 7.3 and later without it pass the standard way), then each `"` becomes `\"` (doubling the backslashes before it), an empty argument becomes `""`, and on Windows PowerShell an argument with whitespace gets its trailing backslashes doubled, so `node.exe` gets the arguments it was given. Since PowerShell reads an unquoted `a,b` as an array, `sccapture -R 0,0,800,600 shot.png` reaches `sccapture` as four arguments, so quote any argument with a comma in it (`sccapture -R '0,0,800,600' shot.png`). It also sets `$PSNativeCommandUseErrorActionPreference = $false` and `$ErrorActionPreference = 'Continue'` in its own scope, so neither a caller's `$true` (pwsh 7.4 and later) nor, on Windows PowerShell 5.1, a caller's `'Stop'` with stderr redirected can turn node's exit code or its stderr into an error |
|
|
280
|
+
| Windows | `<cmd>.cmd` (UTF-8, code page 65001 while it runs) | sets `<CMD>_INSTALL_DIR` and, when it is undefined, `NODE_USE_ENV_PROXY=1` (both local to the `.cmd` through `setlocal`), then runs `"<node.exe>" "<entry>" %* & call :done %%ERRORLEVEL%% & call exit /b %%ERRORLEVEL%%` as one line, so a `)` in an argument can't end anything early, and a launcher that `--upgrade` rewrites mid-run is never read again; `:done` restores the code page and returns node's status, and `call exit /b` exits with it explicitly, because a bare `exit /b` leaves the exit code of `cmd /c` (which PowerShell and every other program uses to run a `.cmd`) at 0. Arguments reach it through cmd's own parsing: `%`, `^`, `&` and `)` are safe inside `"…"`, and a `\"` inside one counts as a quote to cmd, so the quotes in an argument have to pair up |
|
|
281
|
+
|
|
282
|
+
Both Windows launchers only start `node.exe` with the entry script and the arguments, and `<cmd>` itself never runs `cmd.exe`. `NODE_USE_ENV_PROXY=1` makes Node's `fetch` (and so `--upgrade`) honour `HTTPS_PROXY`, `HTTP_PROXY` and `NO_PROXY` the way curl does. On Windows the installer adds `DIR` to the user's `PATH` when it isn't there already, and says so, along with the PowerShell command that removes it again. When `Get-ExecutionPolicy` is `Restricted`, `AllSigned` or `Undefined`, PowerShell won't run `<cmd>.ps1`, so `install.ps1` prints to stderr that `<cmd>` needs `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`, or `<cmd>.cmd` instead; it never changes the policy itself. elsewhere it prints the `export PATH=…` line to add when `DIR` isn't on `PATH`. After writing the launchers, the installer runs each command's `--version` and requires the version it installed, either alone (`1.2.0`) or in the older `<cmd> 1.2.0` form, so that a `--version` naming an older release still installs. Then it prints `<cmd> <version> installed to <launcher>` to stdout for each command.
|
|
283
|
+
|
|
284
|
+
### How `--upgrade` uses it
|
|
285
|
+
|
|
286
|
+
`<CMD>_INSTALL_DIR` is how a command knows the installer put it there. When it is set, `--upgrade` uses Node's `fetch` to download `install.sh` (or `install.ps1` on Windows) from `SLICC_INSTALL_BASE_URL`, or the latest release without it, into a temporary directory. The download has 60 seconds (`AbortSignal.timeout`); after that it prints `<cmd>: download timed out` to stderr and exits 1. It runs `sh install.sh --dir <CMD>_INSTALL_DIR`, or `powershell.exe` (else `pwsh.exe`) `-NoProfile -NonInteractive -ExecutionPolicy Bypass -File install.ps1 -Dir <CMD>_INSTALL_DIR` without `PSModulePath` in its environment, with the installer's stderr passed through. (Run from PowerShell 7, the command inherits pwsh's `PSModulePath`, and Windows PowerShell started with it loses `Get-FileHash`.) The new version is the one in the installer's `<cmd> <version> installed to` line. A download that fails (an HTTP error status prints `<cmd>: could not download <url>: HTTP <status>`, and its body is cancelled, not left open), an installer that exits non-zero or doesn't print that line, or a missing `sh` or PowerShell makes `--upgrade` exit 1. Without `<CMD>_INSTALL_DIR`, the command counts as installed with npm. Commands remove `<CMD>_INSTALL_DIR` from the environment of everything they start.
|
|
287
|
+
|
|
241
288
|
## Development
|
|
242
289
|
|
|
243
290
|
```sh
|
|
@@ -249,11 +296,12 @@ npm run test:unit
|
|
|
249
296
|
|
|
250
297
|
- `npm install` also installs the git hooks into this worktree's own `node_modules/` through slicc-shared-web's `slicc-install-hooks` and the committed `.githooks/`.
|
|
251
298
|
- `npm run lint`: Biome, the no-comment check and the other [slicc-shared-web](https://github.com/ai-ecoverse/slicc-shared-web) checks, then `shellcheck -s sh` and `dash -n` on `install.sh` and `bin/*`.
|
|
252
|
-
- `npm test`: the integration tests in `test/integration/`. `commands.test.mjs` runs `screencapture` and `sccapture` on `@ai-ecoverse/slicc-kernel/node` (the
|
|
299
|
+
- `npm test`: the integration tests in `test/integration/`. `commands.test.mjs` runs `screencapture` and `sccapture` on `@ai-ecoverse/slicc-kernel/node` (the newest kernel, pinned exactly) with wasm-bash and wasm-coreutils, and a fake `capture.screen` and `clipboard.write` handler (`createNodeKernel({ requests })`): every option's header, the bytes written unchanged, the errnos and their messages, `-c`, `-v`, and slicc-kernel 1.152.2, which has no `/dev/embedder`. `test/integration/repo.test.mjs` checks the workflow, the release config, the pinned devDependencies and what `npm pack` ships. The host tests run `host/` against fake `screencapture`, `grim`, `slurp`, `gnome-screenshot`, `import`, `scrot`, `wl-copy`, `xclip`, `xdg-open`, `powershell.exe` and `pwsh.exe` on a temporary `PATH`, and `--upgrade` against installers served from a local HTTP server (a 500 whose body never ends has to be closed within 2 seconds), with the platform forced, so each branch is checked on any machine; only CI touches a real screen. `install.test.mjs` runs `install.sh` on a tarball packed from the tree, with the Node on `PATH` and with a Node download from a fake `nodejs.org`, a truncated tarball and Node archive whose checksums match, and `--upgrade` against a release served from a local directory; `install.ps1` is checked statically, and run when `pwsh` is on `PATH`, also with `$PSNativeCommandUseErrorActionPreference = $true` and `$ErrorActionPreference = 'Stop'` (an install, a failed npm and the launcher's exit 2), and on a truncated tarball. `scripts/assets.mjs` has to leave a rejected tarball under npm's name and take one already renamed. On Windows the host tests fake a program with a copy of a small `.exe` that `csc.exe` builds from C# (`fake-windows.mjs`), which runs the fake's script with Node, so the Windows branches run for real; the macOS and Linux branches and the `sh`-based `--upgrade` tests are skipped there.
|
|
300
|
+
- `npm run test:windows`: on Windows, the host tests and `windows.test.mjs`, which installs a tarball packed from the tree with `install.ps1` under both Windows PowerShell and `pwsh`, from a local HTTP server into a temporary `-Dir` named `my 'bin' (x86) & 100%`; runs `sccapture.cmd` and `sccapture.ps1` with `--version`, `--help` and a usage error; checks the arguments `node` gets through both launchers (full of `)`, `&`, `%`, `^`, spaces, quotes, trailing backslashes and empty ones, in pwsh's `Standard` and `Legacy` argument passing and with `$PSNativeCommandArgumentPassing` removed, plus a comma list `a,'b c'`, through copies of the launchers that run a script printing its argv), and through them the `SCCAPTURE_*` data a fake `powershell.exe` gets for a file named `a ) & ^ 'f' b.png`; checks that neither launcher leaks `SCCAPTURE_INSTALL_DIR` or `NODE_USE_ENV_PROXY`, that the `.cmd` restores the code page, and that the printed PATH undo works; captures the real screen to a PNG through `sccapture.cmd` (also with pwsh's `PSModulePath`) and through `sccapture.ps1` from Windows PowerShell and from `pwsh`, checked by its magic bytes and its size; installs again with `$PSNativeCommandUseErrorActionPreference = $true` and `$ErrorActionPreference = 'Stop'`, where `sccapture.ps1 --nope` exits 2 and leaves both preferences as they were, and a truncated tarball, with `2>&1`, exits 1 with `could not unpack`; upgrades in place with `--upgrade` from `sccapture.cmd`, `sccapture.ps1` and `pwsh`; and downloads Node as a `.zip` from a fake `nodejs.org`, after refusing a truncated one. CI runs it on `windows-latest` (`windows-test` in `ci.yml`); elsewhere `windows.test.mjs` is skipped.
|
|
253
301
|
- `page.test.mjs` registers [the page module](#the-page-module) through `createNodeKernel({ requests })`, with a fake `display` and fake `ImageCapture`, `OffscreenCanvas` and `<video>` injected through `create`, and runs the real `screencapture`: the constraints, the crop, the type and quality, the delay and its progress, an interrupt in the picker and in the delay, **Stop sharing**, the picker's timeout, the errnos and their messages end to end, and that every track is stopped. `browser.test.mjs` runs the module in Chrome, through slicc-shared-web's harness, with a `display` that returns a canvas's `captureStream()`: PNG, JPEG and WebP, the crop, the `<video>` fallback, an abort and the stopped track. It runs on CI only, and is skipped without `CI`.
|
|
254
302
|
- `npm run test:unit`: the unit tests in the gitignored `test/unit/`, with coverage in `coverage/unit/lcov.info`. `npm run test:sh` runs the `test/unit/*.sh.mjs` ones, which trace `bin/` with `set -x` in the node kernel, and writes `coverage/sh/lcov.info`. The pre-commit hook wants 100% coverage of the changed lines of both, and runs `diff-cover` through `uvx`, so install [uv](https://docs.astral.sh/uv/) once.
|
|
255
303
|
|
|
256
|
-
CI is `.github/workflows/ci.yml`, the only workflow, which runs [slicc-shared-web's pipeline](https://github.com/ai-ecoverse/slicc-shared-web#pipeline) at the devDependency's version: `npm run lint`, then `npm test`, then on a pull request the Claude and Codex reviews. The merge queue requires `ci / integration` and `ci / review`. On a push to `main`, semantic-release releases and publishes to npm through trusted publishing, with provenance from `ci.yml`, and attaches the installers.
|
|
304
|
+
CI is `.github/workflows/ci.yml`, the only workflow, which runs [slicc-shared-web's pipeline](https://github.com/ai-ecoverse/slicc-shared-web#pipeline) at the devDependency's version: `npm run lint`, then `npm test`, and on a pull request and in the merge queue `npm run test:windows` on `windows-latest`, then on a pull request the Claude and Codex reviews. The merge queue requires `ci / integration` and `ci / review`. On a push to `main`, semantic-release releases and publishes to npm through trusted publishing, with provenance from `ci.yml`, and attaches the installers.
|
|
257
305
|
|
|
258
306
|
## License
|
|
259
307
|
|
package/host/capture.mjs
CHANGED
|
@@ -18,6 +18,7 @@ export async function run({
|
|
|
18
18
|
fds = [0, 1, 2],
|
|
19
19
|
entry = fileURLToPath(new URL('sccapture.mjs', import.meta.url)),
|
|
20
20
|
wait = (seconds) => setTimeout(seconds * 1000),
|
|
21
|
+
timeout = 60000,
|
|
21
22
|
} = {}) {
|
|
22
23
|
const os = names[platform] ? platform : 'linux';
|
|
23
24
|
const parsed = parse(argv);
|
|
@@ -34,7 +35,8 @@ export async function run({
|
|
|
34
35
|
say(fds[2], `sccapture: ${parsed.special[0]}: takes no other arguments\n${usage}`);
|
|
35
36
|
return 2;
|
|
36
37
|
}
|
|
37
|
-
if (parsed.special[0] === '--upgrade')
|
|
38
|
+
if (parsed.special[0] === '--upgrade')
|
|
39
|
+
return upgrade({ entry, env, platform: os, fds, timeout });
|
|
38
40
|
const pkg = JSON.parse(readFileSync(join(dirname(dirname(entry)), 'package.json'), 'utf8'));
|
|
39
41
|
say(fds[1], `${pkg.version}\n`);
|
|
40
42
|
return 0;
|
package/host/upgrade.mjs
CHANGED
|
@@ -15,13 +15,17 @@ function version(entry) {
|
|
|
15
15
|
return JSON.parse(readFileSync(join(dirname(dirname(entry)), 'package.json'), 'utf8')).version;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
async function download(url) {
|
|
18
|
+
async function download(url, timeout) {
|
|
19
19
|
try {
|
|
20
|
-
const response = await fetch(url, { redirect: 'follow' });
|
|
21
|
-
if (!response.ok)
|
|
20
|
+
const response = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(timeout) });
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
await response.body?.cancel().catch(() => {});
|
|
23
|
+
return { error: `could not download ${url}: HTTP ${response.status}` };
|
|
24
|
+
}
|
|
22
25
|
return { text: await response.text() };
|
|
23
26
|
} catch (error) {
|
|
24
|
-
return { error:
|
|
27
|
+
if (error.name === 'TimeoutError') return { error: 'download timed out' };
|
|
28
|
+
return { error: `could not download ${url}: ${error.cause?.message ?? error.message}` };
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -64,7 +68,7 @@ function wait(path, args, { env, stderr }) {
|
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
export async function upgrade({ entry, env, platform, fds }) {
|
|
71
|
+
export async function upgrade({ entry, env, platform, fds, timeout }) {
|
|
68
72
|
const dir = env.SCCAPTURE_INSTALL_DIR;
|
|
69
73
|
if (!dir) {
|
|
70
74
|
say(fds[1], 'npm install -g @ai-ecoverse/slicc-capture@latest\n');
|
|
@@ -72,8 +76,8 @@ export async function upgrade({ entry, env, platform, fds }) {
|
|
|
72
76
|
}
|
|
73
77
|
const name = platform === 'win32' ? 'install.ps1' : 'install.sh';
|
|
74
78
|
const url = `${(env.SLICC_INSTALL_BASE_URL || latest).replace(/\/+$/, '')}/${name}`;
|
|
75
|
-
const fetched = await download(url);
|
|
76
|
-
if (fetched.error) return fail(fds,
|
|
79
|
+
const fetched = await download(url, timeout);
|
|
80
|
+
if (fetched.error) return fail(fds, fetched.error);
|
|
77
81
|
const scratch = mkdtempSync(join(tmpdir(), 'sccapture-upgrade-'));
|
|
78
82
|
try {
|
|
79
83
|
const path = join(scratch, name);
|
|
@@ -85,6 +89,11 @@ export async function upgrade({ entry, env, platform, fds }) {
|
|
|
85
89
|
}
|
|
86
90
|
const child = { ...env };
|
|
87
91
|
delete child.SCCAPTURE_INSTALL_DIR;
|
|
92
|
+
if (platform === 'win32') {
|
|
93
|
+
for (const key of Object.keys(child)) {
|
|
94
|
+
if (key.toUpperCase() === 'PSMODULEPATH') delete child[key];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
88
97
|
const result = await wait(run.tool.path, run.args, { env: child, stderr: fds[2] });
|
|
89
98
|
if (result.error) return fail(fds, `${run.tool.file}: ${result.error.message}`);
|
|
90
99
|
if (result.code !== 0) return 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-capture",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "screencapture for SLICC, and sccapture for SLICC and any host",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"lint": "slicc-lint && shellcheck -s sh -e SC1091 install.sh bin/* && for f in install.sh bin/*; do dash -n \"$f\" || exit 1; done",
|
|
54
54
|
"format": "biome check --write .",
|
|
55
55
|
"test": "node --test --test-timeout=120000 test/integration/*.test.mjs",
|
|
56
|
+
"test:windows": "node --test --test-concurrency=1 --test-timeout=600000 test/integration/host.test.mjs test/integration/windows.test.mjs",
|
|
56
57
|
"test:unit": "mcr -c node_modules/@ai-ecoverse/slicc-shared-web/mcr.config.mjs node --test 'test/unit/**/*.test.mjs'",
|
|
57
58
|
"test:sh": "node scripts/cover.mjs",
|
|
58
59
|
"build": "npm pack --dry-run",
|
|
@@ -61,8 +62,8 @@
|
|
|
61
62
|
"release": "semantic-release"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
|
-
"@ai-ecoverse/slicc-cdp": "1.
|
|
65
|
-
"@ai-ecoverse/slicc-kernel": "1.
|
|
65
|
+
"@ai-ecoverse/slicc-cdp": "1.41.1",
|
|
66
|
+
"@ai-ecoverse/slicc-kernel": "1.157.1",
|
|
66
67
|
"@ai-ecoverse/slicc-shared-web": "2.12.0",
|
|
67
68
|
"@ai-ecoverse/wasm-bash": "5.3.0-23",
|
|
68
69
|
"@ai-ecoverse/wasm-coreutils": "9.12.0-15",
|