@ai-ecoverse/slicc-man 1.2.3 → 1.2.4
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 +4 -4
- package/dist/slicc.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -60,8 +60,8 @@ slicc-man's `install.sh`, `install.ps1`, `scripts/assets.mjs` and `host/upgrade.
|
|
|
60
60
|
| platform | launcher in `DIR` | what it does |
|
|
61
61
|
| --- | --- | --- |
|
|
62
62
|
| macOS, Linux | `scman`, a POSIX sh script | sets and exports `SCMAN_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 |
|
|
63
|
-
| Windows | `scman.ps1` (UTF-8 with a BOM) | saves the caller's `$env:SCMAN_INSTALL_DIR` and sets it, sets `$env:NODE_USE_ENV_PROXY = '1'` when it is unset, runs `& '<node.exe>' '<entry>' @scmanArgs` in `try`, restores the old `SCMAN_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
|
|
64
|
-
| Windows | `scman.cmd` (UTF-8, code page 65001 while it runs) | sets `SCMAN_INSTALL_DIR` and, when it is undefined, `NODE_USE_ENV_PROXY=1` (both local 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 `--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 |
|
|
63
|
+
| Windows | `scman.ps1` (UTF-8 with a BOM) | saves the caller's `$env:SCMAN_INSTALL_DIR` and sets it, sets `$env:NODE_USE_ENV_PROXY = '1'` when it is unset, runs `& '<node.exe>' '<entry>' @scmanArgs` in `try`, restores the old `SCMAN_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 launcher whose `node.exe` can't start (deleted, or not a program) exits 1: it sets `$global:LASTEXITCODE = $null` before the call and keeps 1 when node left it unset, and a `catch` writes PowerShell's error to stderr with `Write-Error` and keeps 1, so neither the exit code of the caller's previous program nor a caller's `trap { continue }` makes it exit 0. A `.ps1` runs inside the caller's session, and this keeps the variables from leaking into it. `@scmanArgs` 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 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), 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, `scman https://x/?a=1,2` reaches `scman` as two arguments, so quote any argument with a comma in it (`scman 'https://x/?a=1,2'`). 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 |
|
|
64
|
+
| Windows | `scman.cmd` (UTF-8, code page 65001 while it runs) | sets `SCMAN_INSTALL_DIR` and, when it is undefined, `NODE_USE_ENV_PROXY=1` (both local 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 `--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. A `node.exe` that can't start leaves cmd's own non-zero code (9009 when it is missing). 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 |
|
|
65
65
|
|
|
66
66
|
Both Windows launchers only start `node.exe` with the entry script and the arguments. `NODE_USE_ENV_PROXY=1` makes Node's `fetch` (pages, the sitemap and `--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 (`To undo: [Environment]::SetEnvironmentVariable('Path', …, 'User')`). When `Get-ExecutionPolicy` is `Restricted`, `AllSigned` or `Undefined`, PowerShell won't run `scman.ps1`, so `install.ps1` prints to stderr that it needs `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`, or `scman.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 `scman --version` and requires the version it installed, alone (`1.2.0`) or as `scman 1.2.0` from older releases, then prints `scman <version> installed to <launcher>` to stdout.
|
|
67
67
|
|
|
@@ -195,8 +195,8 @@ npm run test:unit
|
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
- `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`.
|
|
198
|
-
- `npm test`: the integration tests in `test/integration/`. `slicc.test.mjs` installs the files `npm pack` ships into a headless slicc-kernel in Node (`createNodeKernel`, with wasm-bash and wasm-coreutils), and runs `man`, `apropos`, `whatis` and `scman` there against a local HTTP server on an ephemeral port, reached through the kernel's network transport as `http://host.slicc.internal:<port>` (`SLICC_MAN_URL`), with fake `less` and `hello` commands on `PATH` and a terminal from `openTerminal` for the pager. `host.test.mjs` runs `scman` (in-process, so a terminal can be faked, and as `host/scman.mjs`) against a local HTTP server on an ephemeral port standing in for www.sliccy.com (`SLICC_MAN_URL`), serving copies of the site's `tree.md` and `man.md` (`pages.json`, with their expected renderings), with a temporary cache and fake tools, `less`, `cmd` and `powershell.exe` among them, on a temporary `PATH`, and `win32` forced for the Windows lookup, pager, cache and `--upgrade` checks, so they run on any machine; only CI touches the real site. A fake is a Node script; on Windows it is a copy of a small `.exe` that `csc.exe` builds from C# (`fake-windows.mjs`), which runs the fake's script with Node, so there the Windows branches run for real, the pager through the real `cmd.exe`. `install.test.mjs` runs `scripts/assets.mjs` and `install.sh` on a tarball packed from the tree, with the Node on `PATH` and with a Node download from a fake nodejs.org, without `HOME`, with dependencies for npm to install, a truncated tarball and Node archive whose checksums match, and `--upgrade` against a release served from a local server; `install.ps1` is checked statically, and run when `pwsh` is on `PATH`, where a strict caller (`$PSNativeCommandUseErrorActionPreference = $true`, `$ErrorActionPreference = 'Stop'`), the launchers' environment and the argument passing of `scman.ps1` (hard arguments and `a,'b c' d`, under Standard, Legacy and no `$PSNativeCommandArgumentPassing`) are checked through a copy whose entry script prints its argv. `repo.test.mjs` checks the workflow, the release config, the package's shape, what `npm pack` ships and the repo rules.
|
|
199
|
-
- `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`; runs `scman.cmd` and `scman.ps1` with `--version`, `--help`, a usage error and a page from a local site, cached in `%LOCALAPPDATA%\slicc-man\cache`; passes arguments full of `)`, `&`, `%`, `^`, spaces and quotes through copies of both launchers whose entry script prints its argv; checks that neither leaks `SCMAN_INSTALL_DIR` or `NODE_USE_ENV_PROXY`, that the `.cmd` keeps the exit code and restores the code page, and that the printed PATH undo works; upgrades in place with `--upgrade`, from a `cmd` that has pwsh's `PSModulePath`; and downloads Node as a `.zip` from a fake nodejs.org, after refusing a truncated one; the installs and `scman.ps1` run under a strict caller too, and a truncated tarball exits 1 there. Elsewhere `windows.test.mjs` is skipped.
|
|
198
|
+
- `npm test`: the integration tests in `test/integration/`. `slicc.test.mjs` installs the files `npm pack` ships into a headless slicc-kernel in Node (`createNodeKernel`, with wasm-bash and wasm-coreutils), and runs `man`, `apropos`, `whatis` and `scman` there against a local HTTP server on an ephemeral port, reached through the kernel's network transport as `http://host.slicc.internal:<port>` (`SLICC_MAN_URL`), with fake `less` and `hello` commands on `PATH` and a terminal from `openTerminal` for the pager. `host.test.mjs` runs `scman` (in-process, so a terminal can be faked, and as `host/scman.mjs`) against a local HTTP server on an ephemeral port standing in for www.sliccy.com (`SLICC_MAN_URL`), serving copies of the site's `tree.md` and `man.md` (`pages.json`, with their expected renderings), with a temporary cache and fake tools, `less`, `cmd` and `powershell.exe` among them, on a temporary `PATH`, and `win32` forced for the Windows lookup, pager, cache and `--upgrade` checks, so they run on any machine; only CI touches the real site. A fake is a Node script; on Windows it is a copy of a small `.exe` that `csc.exe` builds from C# (`fake-windows.mjs`), which runs the fake's script with Node, so there the Windows branches run for real, the pager through the real `cmd.exe`. `install.test.mjs` runs `scripts/assets.mjs` and `install.sh` on a tarball packed from the tree, with the Node on `PATH` and with a Node download from a fake nodejs.org, without `HOME`, with dependencies for npm to install, a truncated tarball and Node archive whose checksums match, and `--upgrade` against a release served from a local server; `install.ps1` is checked statically, and run when `pwsh` is on `PATH`, where a strict caller (`$PSNativeCommandUseErrorActionPreference = $true`, `$ErrorActionPreference = 'Stop'`), the launchers' environment and the argument passing of `scman.ps1` (hard arguments and `a,'b c' d`, under Standard, Legacy and no `$PSNativeCommandArgumentPassing`) are checked through a copy whose entry script prints its argv, and a `scman.ps1` pointed at a `node` that is missing or isn't a program must exit 1. `repo.test.mjs` checks the workflow, the release config, the package's shape, what `npm pack` ships and the repo rules.
|
|
199
|
+
- `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`; runs `scman.cmd` and `scman.ps1` with `--version`, `--help`, a usage error and a page from a local site, cached in `%LOCALAPPDATA%\slicc-man\cache`; passes arguments full of `)`, `&`, `%`, `^`, spaces and quotes through copies of both launchers whose entry script prints its argv; checks that neither leaks `SCMAN_INSTALL_DIR` or `NODE_USE_ENV_PROXY`, that both exit non-zero (the `.ps1` exits 1, after a program that exited 0, for a default, a strict, a `trap { continue }` and a redirected-stderr caller) once the copied `node.exe` they were installed with is replaced by a file that isn't a program and then deleted, that the `.cmd` keeps the exit code and restores the code page, and that the printed PATH undo works; upgrades in place with `--upgrade`, from a `cmd` that has pwsh's `PSModulePath`; and downloads Node as a `.zip` from a fake nodejs.org, after refusing a truncated one; the installs and `scman.ps1` run under a strict caller too, and a truncated tarball exits 1 there. Elsewhere `windows.test.mjs` is skipped.
|
|
200
200
|
- `npm run test:unit`: the unit tests in the gitignored `test/unit/`, with Node's coverage in `coverage/unit/lcov.info`. The pre-commit hook wants 100% coverage of the changed lines.
|
|
201
201
|
|
|
202
202
|
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, with Node 26, which the headless kernel needs: `npm run lint`, then `npm test` on Linux, and on a pull request and in the merge queue `npm run test:windows` on `windows-latest` (`windows-test` in `ci.yml`), 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 `slicc-man-<version>.tgz`, `SHA256SUMS`, `install.sh` and `install.ps1` to the GitHub release (see [the installer](#the-installer)).
|
package/dist/slicc.mjs
CHANGED
|
@@ -54,6 +54,6 @@ ${and} -l, --long don't cut lines to the terminal width
|
|
|
54
54
|
${parsed.usage?usage(kind,name):""}`),USAGE;if(parsed.help)return await io.out(usage(kind,name)),OK;if(parsed.version)return await io.out(`${io.version}
|
|
55
55
|
`),OK;if(parsed.flags.upgrade)return io.upgrade();let url=base(env),ctx={io,env,name,flags:parsed.flags,values:parsed.values,width:width(env,io),styled:!!io.tty&&!env.NO_COLOR,site:site({url,store:io.store(folder(url)),fetch:io.fetch,now:io.now,name,timeout:io.timeout})};return kind!=="man"?keywords(kind,ctx,parsed):parsed.flags.apropos?keywords("apropos",ctx,parsed):parsed.flags.whatis?keywords("whatis",ctx,parsed):parsed.operands.length===0?(await io.err(`What manual page do you want?
|
|
56
56
|
For example, try 'man man'.
|
|
57
|
-
`),USAGE):parsed.flags.local?local(ctx,parsed.operands):pages(ctx,parsed)}var reasons={ENOENT:"No such file or directory",EACCES:"Permission denied",EISDIR:"Is a directory",ENOTDIR:"Not a directory"};var package_default={version:"1.2.
|
|
57
|
+
`),USAGE):parsed.flags.local?local(ctx,parsed.operands):pages(ctx,parsed)}var reasons={ENOENT:"No such file or directory",EACCES:"Permission denied",EISDIR:"Is a directory",ENOTDIR:"Not a directory"};var package_default={version:"1.2.4"};var kinds={apropos:"apropos",whatis:"whatis"},decoder=new TextDecoder,counter=0;function why(error){return reasons[error?.code]??error?.message??String(error)}function store(fs,dir){let file=id=>`${dir}/${id}.json`;return{async read(id){try{return decoder.decode(await fs.readFile(file(id)))}catch{return}},async write(id,text){await fs.mkdir(dir,{recursive:!0});let temp=`${dir}/.${id}.${Date.now()}.${counter++}.tmp`;try{await fs.writeFile(temp,text),await fs.rename(temp,file(id))}catch(error){throw await fs.unlink(temp).catch(()=>{}),error}},async remove(id){await fs.unlink(file(id)).catch(()=>{})}}}async function which(ctx,file){for(let dir of String(ctx.env.PATH??"").split(":")){let path=ctx.resolve(`${dir||"."}/${file}`);try{let stat=await ctx.fs.stat(path);if(stat.isFile&&(stat.mode??493)&73)return!0}catch{}}return!1}async function input(ctx){let chunks=[];for(;;){let chunk=await ctx.read(0);if(chunk.length===0)break;chunks.push(chunk)}let all=new Uint8Array(chunks.reduce((sum,chunk)=>sum+chunk.length,0)),at=0;for(let chunk of chunks)all.set(chunk,at),at+=chunk.length;return decoder.decode(all)}async function read(ctx,file){try{return file==="-"?await input(ctx):decoder.decode(await ctx.fs.readFile(ctx.resolve(file)))}catch(error){throw new Error(why(error))}}async function page(ctx,name,command,text){let err=line2=>ctx.write(2,line2);await ctx.signals.on("SIGINT",()=>{});try{let child;try{child=await ctx.spawn({argv:["sh","-c",command],stdin:"pipe"})}catch(error){return await err(`${name}: ${command}: ${why(error)}
|
|
58
58
|
`),2}let writer=child.stdin.getWriter();await writer.write(new TextEncoder().encode(text)).catch(()=>{}),await writer.close().catch(()=>{});let{status}=await child.wait();return status===126||status===127?2:0}finally{await ctx.signals.reset("SIGINT")}}var escapes=/(?:\u001b[\]P^_X]|[\u0090\u0098\u009d-\u009f])[\s\S]*?(?:\u0007|\u001b\\|\u009c|$)|(?:\u001b\[|\u009b)[0-?]*[ -/]*[@-~]|\u001b[ -/]*[0-~]?/g;function help(ctx,topic){let result=ctx.sync.run({argv:[topic,"--help"],stdin:"ignore",stderr:"stdout"}),text=decoder.decode(result.stdout??new Uint8Array);return clean(text.replace(escapes,""))}async function columns(ctx,tty){if(tty)try{return(await ctx.tty.size(1)).columns}catch{return}}async function main(ctx){let name=String(ctx.argv[0]??"man").split("/").pop(),kind=kinds[name]??"man",tty=await ctx.isatty(1),root=`${String(ctx.env.HOME||"/root").replace(/\/+$/,"")}/.cache/slicc-man`,io={tty,columns:await columns(ctx,tty),fetch:(url,init)=>ctx.fetch(url,init),now:Date.now,version:package_default.version,out:text=>ctx.write(1,text),err:text=>ctx.write(2,text),store:dir=>store(ctx.fs,`${root}/${dir}`),which:file=>which(ctx,file),page:(command,text)=>page(ctx,name,command,text),read:file=>read(ctx,file),help:topic=>help(ctx,topic),upgrade:async()=>(await ctx.write(1,`pnpm add -g @ai-ecoverse/slicc-man@latest
|
|
59
59
|
`),0)};return run2({kind,name,argv:ctx.argv.slice(1),env:ctx.env,io})}export{main as default,help,page,store,which};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-man",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "man, apropos and whatis for SLICC, and scman for SLICC, macOS, Linux and Windows, reading pages from www.sliccy.com/man",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"release": "semantic-release"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@ai-ecoverse/slicc-kernel": "1.
|
|
77
|
+
"@ai-ecoverse/slicc-kernel": "1.159.0",
|
|
78
78
|
"@ai-ecoverse/slicc-shared-web": "2.12.0",
|
|
79
79
|
"@ai-ecoverse/wasm-bash": "5.3.0-23",
|
|
80
80
|
"@ai-ecoverse/wasm-coreutils": "9.12.0-15",
|