From 8586a41f7c700eed802d39faa90e41e9ecf0d5c2 Mon Sep 17 00:00:00 2001 From: viktar-b <60892287+viktar-b@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:54:46 +0100 Subject: [PATCH 1/4] docs(windows): add explicit PowerShell setup and report recipes --- docs/development.md | 85 ++++++++++++++++++- packages/create-cs-object/README.md | 40 +++++++++ packages/create-cs-object/template/README.md | 28 ++++++ .../create-cs-object/template/authoring.md | 44 +++++++++- .../template/references/README.md | 58 ++++++++++++- packages/cso-cli/README.md | 32 +++++++ 6 files changed, 281 insertions(+), 6 deletions(-) diff --git a/docs/development.md b/docs/development.md index 4b94865..e5cfc7d 100644 --- a/docs/development.md +++ b/docs/development.md @@ -7,7 +7,7 @@ The [code map](code-map.md) explains responsibility and execution order. ## Setup -From the repository root: +From the repository root in a POSIX shell: ```sh npm ci @@ -21,6 +21,40 @@ npm run build:cli npx playwright install chromium ``` +For Windows 11 x64, use Node 24 and Python 3.11+ with PowerShell 5.1 or 7. +The following commands select an installed Python executable, then use a +repository virtualenv. If `python` is not on PATH, replace the first assignment +with your interpreter's absolute executable path. For a launcher-only install, +use `$env:PYTHON = py -3.11 -c 'import sys; print(sys.executable)'`. + + +```powershell +$env:PYTHON = (Get-Command python -CommandType Application).Source +npm.cmd ci +if ($LASTEXITCODE -ne 0) { throw 'Dependency installation failed.' } +& $env:PYTHON -m venv .venv +if ($LASTEXITCODE -ne 0) { throw 'Python environment creation failed.' } +$env:PYTHON = Join-Path $PWD '.venv\Scripts\python.exe' +& $env:PYTHON -m pip wheel --no-deps ./packages/cso-python --wheel-dir artifacts +if ($LASTEXITCODE -ne 0) { throw 'Python wheel build failed.' } +& $env:PYTHON -m pip install --no-index --find-links artifacts --force-reinstall cs-object +if ($LASTEXITCODE -ne 0) { throw 'Python wheel installation failed.' } +npm.cmd run build:cli +if ($LASTEXITCODE -ne 0) { throw 'CLI build failed.' } +& $env:PYTHON -I -X utf8 -m cso_python bindings examples/two-panel +if ($LASTEXITCODE -ne 0) { throw 'Two-panel binding generation failed.' } +& $env:PYTHON -I -X utf8 -m cso_python bindings examples/section-properties +if ($LASTEXITCODE -ne 0) { throw 'Section binding generation failed.' } +npx.cmd playwright install chromium +if ($LASTEXITCODE -ne 0) { throw 'Chromium installation failed.' } +``` + + +`PYTHON` contains one executable path, not `py -3.11` or other command arguments. +The call operator `&` handles paths with spaces. No environment activation or +global execution-policy change is needed. Use `npm.cmd` and `npx.cmd` in these +PowerShell recipes so PowerShell selects the command wrappers explicitly. + Rebuild and reinstall the wheel after Python source changes. Exporting `PYTHON` selects the installed interpreter for the CLI and test runners. A source/editable install does not prove wheel contents or behavior outside the checkout. @@ -39,6 +73,14 @@ empty states and standalone builds. Deployment settings live in [vercel.json](../vercel.json); its build context is the repository root. A local build does not establish a hosted deployment result. +In PowerShell, start the demo with `npm.cmd run dev` or build it with +`npm.cmd run build:demo`. For calculation verification and checked HTML/PDF, use the +[PowerShell CLI examples](../packages/cso-cli/README.md#powershell-51-and-7). + +Native automated checks use Windows Server 2025. They provide evidence for the +commands under test. Windows 11 foreground Ctrl+C/restart and human inspection +of delivered reports remain separate qualification steps. + ## Checks Run package behavior tests for the changed project, then the affected @@ -169,6 +211,47 @@ archive paths together in the consumer. Install the Python wheel into its chosen interpreter. Use the package manifests for versions and peer dependencies. These commands do not publish to npm or PyPI. +In PowerShell 5.1 or 7, after repository setup, pack the libraries and CLI and +install their archives into a new temporary consumer: + + +```powershell +$artifacts = Join-Path $PWD 'artifacts' +$utf8 = [System.Text.UTF8Encoding]::new($false) +[Console]::OutputEncoding = $utf8 +$packedText = npm.cmd pack --workspace '@cs-object/core' --workspace '@cs-object/react' --workspace '@cs-object/cli' --pack-destination $artifacts --ignore-scripts --json +if ($LASTEXITCODE -ne 0) { throw 'Archive creation failed.' } +$packed = ($packedText -join "`n") | ConvertFrom-Json +$archives = @($packed | ForEach-Object { Join-Path $artifacts $_.filename }) +$consumer = Join-Path ([System.IO.Path]::GetTempPath()) ('cso-consumer-' + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory $consumer | Out-Null +Push-Location $consumer +try { + npm.cmd init -y + if ($LASTEXITCODE -ne 0) { throw 'Consumer initialization failed.' } + npm.cmd install -- $archives + if ($LASTEXITCODE -ne 0) { throw 'Archive installation failed.' } + & $env:PYTHON -m venv .venv + if ($LASTEXITCODE -ne 0) { throw 'Consumer Python environment creation failed.' } + $env:PYTHON = Join-Path $PWD '.venv\Scripts\python.exe' + & $env:PYTHON -m pip install --no-index --find-links $artifacts cs-object + if ($LASTEXITCODE -ne 0) { throw 'Consumer wheel installation failed.' } + & (Join-Path $PWD 'node_modules\.bin\cso.cmd') --help + if ($LASTEXITCODE -ne 0) { throw 'Installed CLI launch failed.' } +} finally { + Pop-Location +} +``` + + +The archives use the builds produced during repository setup. Skipping the pack +lifecycle scripts keeps the captured output valid JSON. The consumer remains at +`$consumer`; select that directory to use its local CLI. Select the repository +interpreter again when returning to repository work. +This route needs no registry release for the CSO packages. It still downloads +third-party npm dependencies. The [initializer guide](../packages/create-cs-object/README.md) +owns project creation, which uses its declared dependency versions. + ## Registry releases [Release packages](../.github/workflows/release.yml) is a manual GitHub Actions diff --git a/packages/create-cs-object/README.md b/packages/create-cs-object/README.md index 6490575..e17ade5 100644 --- a/packages/create-cs-object/README.md +++ b/packages/create-cs-object/README.md @@ -9,6 +9,42 @@ cd my-report npm run dev ``` +In PowerShell 5.1 or 7, select Python 3.11+ and run the setup steps explicitly: + +```powershell +$env:PYTHON = (Get-Command python -CommandType Application).Source +& $env:PYTHON --version +``` + + +```powershell +npm.cmd create cs-object my-report -- --skip-install +if ($LASTEXITCODE -ne 0) { throw 'Project creation failed.' } +Set-Location my-report +``` + + + +```powershell +npm.cmd run setup +if ($LASTEXITCODE -ne 0) { throw 'Project setup failed.' } +``` + + + +```powershell +npm.cmd run dev -- --port 4173 +if ($LASTEXITCODE -ne 0) { throw 'The development server failed.' } +``` + + +Use an absolute Python executable path for `PYTHON` if the interpreter is not +on PATH. A launcher-only installation can supply that path with +`$env:PYTHON = py -3.11 -c 'import sys; print(sys.executable)'`. +`PYTHON` is an executable path, not a command such as `py -3.11`. +The project uses its own `.venv`; activation and execution-policy changes are +unnecessary. See the generated project's `README.md` for browser and API usage. + The initializer creates an owned project template, installs its exact CLI dependency, prepares `.venv`, and installs Chromium. It preserves generated files after setup failure; run `npm run setup` in the project to retry. @@ -25,3 +61,7 @@ through its own local CLI process. Package tests execute a real npm archive in a temporary consumer. Full runtime acceptance belongs to the repository's installed integration checks. +Before a registry release, use the repository's +[local archive workflow](../../docs/development.md#local-package-consumers). +An initializer archive alone still installs the dependency versions named by +its template; it does not select sibling archives automatically. diff --git a/packages/create-cs-object/template/README.md b/packages/create-cs-object/template/README.md index abbe8b0..cb43e31 100644 --- a/packages/create-cs-object/template/README.md +++ b/packages/create-cs-object/template/README.md @@ -7,6 +7,10 @@ Node dependencies, creates a private `.venv`, and installs Chromium for PDF outp npm run dev ``` +In PowerShell 5.1 or 7, use `npm.cmd run dev`. Commands that invoke the installed +CLI directly use `.\node_modules\.bin\cso.cmd`; see +[Check the result](authoring.md#check-the-result). + Open the localhost address printed in the terminal. Edit numeric inputs in the browser, calculate, and review the report. Download its PDF when needed. Change formulas in `calculations/report.cso.py`, then calculate again. If you change the declared @@ -43,6 +47,16 @@ The result is `{"area":6}`. Use the report ID from `reports.json` in place of accepts the same body and returns a captured run with report and download links. Wait for the initial report to load before testing input edits in the browser. +In PowerShell 5.1 or 7, the equivalent request avoids native-shell JSON quoting: + +```powershell +$request = @{ inputs = @{ width = 2; height = 3 } } | ConvertTo-Json +Invoke-RestMethod -Uri 'http://127.0.0.1:5173/api/reports/rectangle-area/calculate' -Method Post -ContentType 'application/json; charset=utf-8' -Body $request +``` + +Use the port printed by your development server, such as `4173` when you select +that port explicitly. The response's `area` property is `6`. + To choose a port, run `npm run dev -- --port 4173`. Use port `0` to select an available port. Stop the server with Ctrl+C and use the same command to restart. @@ -50,6 +64,20 @@ If setup failed or you used `--skip-install`, run `npm run setup`. Set `PYTHON` to a Python executable if automatic detection cannot find Python 3.11 or newer. Setup can be run again without replacing calculation files. +In PowerShell, retry with `npm.cmd run setup`. `PYTHON` accepts one absolute +executable path, including a path with spaces. Use +`& $env:PYTHON --version` to inspect it. Setup creates `.venv\Scripts\python.exe`. +You do not need to activate the environment or change an execution policy. + +Build the frontend from PowerShell with: + + +```powershell +npm.cmd run build +if ($LASTEXITCODE -ne 0) { throw 'The frontend build failed.' } +``` + + Before replacing the starter, update [the brief](brief.md), collect [references](references/README.md), and read [the authoring notes](authoring.md). Source-to-document consistency checks that the documented formulas agree with diff --git a/packages/create-cs-object/template/authoring.md b/packages/create-cs-object/template/authoring.md index 1cc4c29..efb6b8f 100644 --- a/packages/create-cs-object/template/authoring.md +++ b/packages/create-cs-object/template/authoring.md @@ -127,6 +127,18 @@ function: ./.venv/bin/python -m cso_python bindings calculations --check ``` +In PowerShell 5.1 or 7, use the project's interpreter directly: + + +```powershell +$env:PYTHON = Join-Path $PWD '.venv\Scripts\python.exe' +& $env:PYTHON -I -X utf8 -m cso_python bindings calculations +if ($LASTEXITCODE -ne 0) { throw 'Binding generation failed.' } +& $env:PYTHON -I -X utf8 -m cso_python bindings calculations --check +if ($LASTEXITCODE -ne 0) { throw 'Bindings are stale.' } +``` + + For a file `calculations/geometry.cso.py` with a public function `rectangle`, a parent in that directory can import and call it: @@ -155,6 +167,11 @@ metadata or public output selections. Formula-only edits may leave the interface current. Generation validates definitions but does not execute formulas. A missing or stale handle will not regenerate itself. +Save authored Python as UTF-8 with LF line endings. The project's `.gitattributes` +keeps Python and stub files at LF on Git checkout. Generated bindings already +use UTF-8/LF. Source hashes cover exact bytes, so an editor's encoding or newline +change can invalidate a reference even when the formula is unchanged. + Each distinct quantity needs distinct displayed notation. Repeated child calls qualify child glyphs using their call names. When a reference deliberately reuses a glyph in separate contexts, set a meaningful @@ -172,8 +189,7 @@ returned result in the report. Check that the displayed substitutions and outputs match the intended engineering method. From the project root, verify one execution and generate checked HTML with the -project's Python interpreter. These examples use a POSIX shell; on Windows the -interpreter is `.venv/Scripts/python.exe`. +project's Python interpreter. In a POSIX shell: ```sh PYTHON="$PWD/.venv/bin/python" npx --no-install cso verify calculations/report.cso.py \ @@ -183,6 +199,30 @@ PYTHON="$PWD/.venv/bin/python" npx --no-install cso html calculations/report.cso --function calculate --out output/report.html --check-layout --format json ``` +For the unchanged rectangle starter, these PowerShell 5.1 and 7 commands verify +width 2 and height 3, then write checked HTML and a PDF: + + +```powershell +$env:PYTHON = Join-Path $PWD '.venv\Scripts\python.exe' +$cso = Join-Path $PWD 'node_modules\.bin\cso.cmd' +$source = Join-Path $PWD 'calculations\report.cso.py' +& $cso verify $source --function calculate --input width=2 --input height=3 --format json +if ($LASTEXITCODE -ne 0) { throw 'Calculation verification failed.' } +New-Item -ItemType Directory -Force (Join-Path $PWD 'output') | Out-Null +& $cso html $source --function calculate --input width=2 --input height=3 --out (Join-Path $PWD 'output\report.html') --check-layout --format json +if ($LASTEXITCODE -ne 0) { throw 'Checked HTML generation failed.' } +& $cso pdf $source --function calculate --input width=2 --input height=3 --out (Join-Path $PWD 'output\report.pdf') --format json +if ($LASTEXITCODE -ne 0) { throw 'PDF generation failed.' } +``` + + +Use `&` when invoking an executable stored in a variable. These commands use the +project's installed CLI and managed interpreter, including when the project +path contains spaces. Setup installs the matching Chromium used by both report +commands. The [reference recipe](references/README.md#bind-an-independent-case) +shows how to save structured JSON as UTF-8 without a BOM in either PowerShell. + Repeat verification with `--input name=value` for representative and boundary cases. Check each command's exit status and report diagnostics. Open the HTML and inspect its content; automated layout checks leave visual inspection pending. diff --git a/packages/create-cs-object/template/references/README.md b/packages/create-cs-object/template/references/README.md index ddca4db..f31476f 100644 --- a/packages/create-cs-object/template/references/README.md +++ b/packages/create-cs-object/template/references/README.md @@ -18,7 +18,7 @@ area independently first: a 2 m by 3 m rectangle has area 6 m². The verificatio report supplies only source hashes, function and input metadata for binding; the expected value below is the hand-derived 6, not a captured result. -From the project root: +From the project root in a POSIX shell: ```sh PYTHON="$PWD/.venv/bin/python" npx --no-install cso verify calculations/report.cso.py \ @@ -33,7 +33,7 @@ Create `references/rectangle-reference.json` with the full case shape: import json from pathlib import Path -report = json.loads(Path("references/rectangle-check.json").read_text()) +report = json.loads(Path("references/rectangle-check.json").read_text(encoding="utf-8")) if not report["ok"]: raise SystemExit("Resolve verification diagnostics before binding a case") fields = ( @@ -59,7 +59,7 @@ reference = { }], } Path("references/rectangle-reference.json").write_text( - json.dumps(reference, indent=2) + "\n" + json.dumps(reference, indent=2) + "\n", encoding="utf-8", newline="\n" ) PY PYTHON="$PWD/.venv/bin/python" npx --no-install cso verify calculations/report.cso.py \ @@ -67,6 +67,58 @@ PYTHON="$PWD/.venv/bin/python" npx --no-install cso verify calculations/report.c --reference references/rectangle-reference.json --format json ``` +In PowerShell 5.1 or 7, run the following block instead. It captures only the +verification metadata needed to bind the independently derived value `6`. +The JSON stays inside PowerShell until the file write, so native argument +quoting cannot alter it. `Join-Path $PWD` gives the .NET writer an absolute path. + + +```powershell +$env:PYTHON = Join-Path $PWD '.venv\Scripts\python.exe' +$cso = Join-Path $PWD 'node_modules\.bin\cso.cmd' +$source = Join-Path $PWD 'calculations\report.cso.py' +$utf8 = [System.Text.UTF8Encoding]::new($false) +[Console]::OutputEncoding = $utf8 +$reportText = & $cso verify $source --function calculate --input width=2 --input height=3 --format json +if ($LASTEXITCODE -ne 0) { throw 'Calculation verification failed.' } +$report = ($reportText -join "`n") | ConvertFrom-Json +if (-not $report.ok) { throw 'Resolve verification diagnostics before binding a case.' } +$binding = [ordered]@{} +foreach ($name in @('entryModuleId', 'entrySourceHash', 'sourceClosureHash', 'function', 'resolvedInputs', 'resolvedInputKinds')) { + $binding[$name] = $report.provenance.$name +} +$reference = [ordered]@{ + referenceVersion = '1' + cases = @([ordered]@{ + id = 'rectangle-2-by-3' + revision = '1' + basis = [ordered]@{ + method = 'Hand-derived rectangle area' + derivation = 'Perpendicular sides: 2 m times 3 m equals 6 m^2.' + sourceDescription = 'Elementary geometry for the stated rectangle.' + } + binding = $binding + expected = @([ordered]@{ + symbolId = '["symbol","root","area"]' + value = 6 + unit = 'm^2' + }) + }) +} +$referencePath = Join-Path $PWD 'references\rectangle-reference.json' +$json = ($reference | ConvertTo-Json -Depth 20).Replace("`r`n", "`n") + "`n" +[System.IO.File]::WriteAllText($referencePath, $json, $utf8) +& $cso verify $source --function calculate --input width=2 --input height=3 --reference $referencePath --format json +if ($LASTEXITCODE -ne 0) { throw 'Independent reference verification failed.' } +``` + + +This writer produces UTF-8 without a BOM and uses LF. Do not use `>` or +`Out-File` to save source or JSON in Windows PowerShell 5.1; their default +encoding differs from this file format. The console encoding assignment makes +UTF-8 CLI output safe to capture before `ConvertFrom-Json`, including Unicode +paths and diagnostics. + Check that `checks.independentReferenceAgreement.status` is `passed` with one checked symbol. To use the case in the browser, add `"reference": "references/rectangle-reference.json"` to the starter entry in diff --git a/packages/cso-cli/README.md b/packages/cso-cli/README.md index 2581e05..68237f3 100644 --- a/packages/cso-cli/README.md +++ b/packages/cso-cli/README.md @@ -33,6 +33,38 @@ and 2 means invalid usage. Reports retain known source hashes, function, inputs, versions and diagnostics; successful HTML/PDF reports include output path and hash. Unknown provenance is not fabricated. See [report schemas](../../packages/cso-core/src/contracts/reports.ts). +### PowerShell 5.1 and 7 + +Complete [repository setup](../../docs/development.md#setup), then run these +commands from its root. They use the local CLI wrapper and the installed wheel +in `.venv`. A generated project's corresponding commands are in its `authoring.md`. + + +```powershell +$env:PYTHON = Join-Path $PWD '.venv\Scripts\python.exe' +$cso = Join-Path $PWD 'node_modules\.bin\cso.cmd' +$source = Join-Path $PWD 'examples\two-panel\estimate.cso.py' +$reference = Join-Path $PWD 'examples\two-panel\reference.json' +& $cso bindings (Join-Path $PWD 'examples\two-panel') +if ($LASTEXITCODE -ne 0) { throw 'Binding generation failed.' } +& $cso verify $source --function estimate --input width=2 --reference $reference --format json +if ($LASTEXITCODE -ne 0) { throw 'Calculation verification failed.' } +New-Item -ItemType Directory -Force (Join-Path $PWD 'output') | Out-Null +& $cso html $source --function estimate --input width=2 --reference $reference --out (Join-Path $PWD 'output\panels.html') --check-layout --format json +if ($LASTEXITCODE -ne 0) { throw 'Checked HTML generation failed.' } +& $cso pdf $source --function estimate --input width=2 --reference $reference --out (Join-Path $PWD 'output\panels.pdf') --format json +if ($LASTEXITCODE -ne 0) { throw 'PDF generation failed.' } +``` + + +Keep `PYTHON` as one executable path and invoke path variables with `&`. +For a standalone install, point it at the environment containing `cs-object`. +Use `--input name=value` to avoid shell-dependent JSON quoting. +If you save stdout, select UTF-8 explicitly; the generated project's +[reference recipe](../create-cs-object/template/references/README.md#bind-an-independent-case) +shows a PowerShell 5.1/7 capture and BOM-free UTF-8/LF write. Do not use +PowerShell 5.1 redirection to write source or JSON files. + ## Numerical checks [verifyExecution](../../packages/cso-core/src/verification/verify.ts) checks inputs, From cdcfcc3a7e68ffbc2c7446bef65800111018e2ab Mon Sep 17 00:00:00 2001 From: viktar-b <60892287+viktar-b@users.noreply.github.com> Date: Wed, 30 Sep 2026 11:11:43 +0100 Subject: [PATCH 2/4] docs(windows): preserve Unicode interpreter paths in launcher lookup --- docs/development.md | 3 ++- packages/create-cs-object/README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/development.md b/docs/development.md index e5cfc7d..f23138c 100644 --- a/docs/development.md +++ b/docs/development.md @@ -25,7 +25,8 @@ For Windows 11 x64, use Node 24 and Python 3.11+ with PowerShell 5.1 or 7. The following commands select an installed Python executable, then use a repository virtualenv. If `python` is not on PATH, replace the first assignment with your interpreter's absolute executable path. For a launcher-only install, -use `$env:PYTHON = py -3.11 -c 'import sys; print(sys.executable)'`. +use `$env:PYTHON = py -3.11 -c 'import json, sys; print(json.dumps(sys.executable))' | ConvertFrom-Json`. +The JSON capture preserves Unicode executable paths under legacy console encodings. ```powershell diff --git a/packages/create-cs-object/README.md b/packages/create-cs-object/README.md index e17ade5..9079e49 100644 --- a/packages/create-cs-object/README.md +++ b/packages/create-cs-object/README.md @@ -40,7 +40,8 @@ if ($LASTEXITCODE -ne 0) { throw 'The development server failed.' } Use an absolute Python executable path for `PYTHON` if the interpreter is not on PATH. A launcher-only installation can supply that path with -`$env:PYTHON = py -3.11 -c 'import sys; print(sys.executable)'`. +`$env:PYTHON = py -3.11 -c 'import json, sys; print(json.dumps(sys.executable))' | ConvertFrom-Json`. +The JSON capture preserves Unicode executable paths under legacy console encodings. `PYTHON` is an executable path, not a command such as `py -3.11`. The project uses its own `.venv`; activation and execution-policy changes are unnecessary. See the generated project's `README.md` for browser and API usage. From ce29f036bda10a10595e4a9a09faceabc75d3f3a Mon Sep 17 00:00:00 2001 From: viktar-b <60892287+viktar-b@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:15:19 +0100 Subject: [PATCH 3/4] test(docs): bind PowerShell recipes to the native workflow --- docs/development.md | 42 +++++++--- package.json | 4 +- packages/create-cs-object/README.md | 2 +- packages/cso-cli/README.md | 10 +-- scripts/check-windows-powershell-docs.test.ts | 76 +++++++++++++++++++ .../installed/windows-user-workflow.ps1 | 5 ++ 6 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 scripts/check-windows-powershell-docs.test.ts diff --git a/docs/development.md b/docs/development.md index f23138c..6f6fdfc 100644 --- a/docs/development.md +++ b/docs/development.md @@ -30,7 +30,7 @@ The JSON capture preserves Unicode executable paths under legacy console encodin ```powershell -$env:PYTHON = (Get-Command python -CommandType Application).Source +$env:PYTHON = Get-Command python -CommandType Application | Select-Object -First 1 -ExpandProperty Source npm.cmd ci if ($LASTEXITCODE -ne 0) { throw 'Dependency installation failed.' } & $env:PYTHON -m venv .venv @@ -97,7 +97,8 @@ access. PDF checks also need Chromium and Poppler. ## Continuous integration [CI](../.github/workflows/ci.yml) runs on every pull request and pushes to `main` -with Node 24 and Python 3.11 on Ubuntu. It keeps these required check names, +with Node 24 and Python 3.11 on Ubuntu and Windows Server 2025. It keeps these +required Ubuntu job names, with PR execution selected by the changed files: - `quality`: dependency audit, lint, typechecking, workspace tests and demo build. @@ -106,6 +107,14 @@ with PR execution selected by the changed files: type/export/browser consumers. Both archive commands are required when this job runs. Full installed consumers run on main, manual runs and before release. +The `windows-installed` matrix runs the maintained +[installed-user workflow](../tests/integration/installed/windows-user-workflow.ps1) +in PowerShell 5.1 and 7. It checks actual npm archives and the Python wheel in +external paths with spaces and Unicode, including browser/API behavior, checked +HTML/PDF, and owned process teardown and restart. Its two check names, +`windows-installed (powershell-5.1)` and `windows-installed (pwsh-7)`, are not +currently required protection contexts. + [Dependency review](../.github/workflows/dependency-review.yml) adds the required `dependency-review` check for newly introduced high or critical vulnerabilities, including development dependencies. The audit in `quality` also checks existing @@ -123,12 +132,18 @@ Like other `pull_request` workflows, changes to the workflow itself require revi CI's `changes` job selects PR checks: -| Changed files | `quality` | `isolation` | `installed-packages` | -| --- | --- | --- | --- | -| Only `.md` files | Skip | Skip | Skip | -| Only examples or integration tests, optionally with Markdown | Run | Skip | Skip | -| Package/app files or recognized dependency/build configuration | Run | Run | Skip | -| CI tooling, workflows or unrecognized paths | Run | Run | Run | +| Changed files | `quality` | `isolation` | `installed-packages` | `windows-installed` | +| --- | --- | --- | --- | --- | +| Only Windows workflow guides listed below | Skip | Skip | Skip | Run | +| Only other `.md` files | Skip | Skip | Skip | Skip | +| Only examples or integration tests, optionally with Markdown | Run | Skip | Skip | Run | +| Package/app files or recognized dependency/build configuration | Run | Run | Skip | Run | +| CI tooling, workflows or unrecognized paths | Run | Run | Run | Run | + +Windows guide selection covers `docs/development.md`, `docs/authoring.md`, +`docs/rendering.md`, package README files and initializer template Markdown. +The `run_windows` output selects both native shells, including for these +Markdown-only changes. The workflow defines the recognized paths. Mixed PRs run every job required by any changed path. Rename detection is disabled so both old and new paths count, @@ -146,6 +161,9 @@ Release publishing still depends on the full reusable CI workflow. `npm run test:ci` exercises the classifier in temporary Git repositories and checks the actual job conditions, fallback behavior and release dependencies. +It also [compares the ten published PowerShell blocks](../scripts/check-windows-powershell-docs.test.ts) +with the executed Windows workflow, allowing only line-ending and common +indentation differences. PR title validation, dependency review and GitHub-managed CodeQL keep their own triggers. @@ -180,11 +198,17 @@ to this pin through all required checks, including ESM/CJS builds, declarations, CLI/PDF acceptance and browser consumers. Remove the overrides when upstream ranges permit a patched version and fresh workspace/isolated installs confirm it. -Isolation and installed-package jobs retain logs and evidence for 14 days, +Isolation, installed-package and Windows jobs retain logs and evidence for 14 days, including generated PDFs and their hashes. Passing automated PDF checks leaves visual inspection pending. Use the [rendering guide](rendering.md#choose-verification-by-change) to select HTML or PDF checks and apply its delivery requirements. +Each Windows artifact set retains host versions, command statuses, archive and +binding hashes, reference agreement, HTML/PDF and browser-download evidence, +and port/restart receipts. Evidence is uploaded after success or failure unless +the job is cancelled. Automated termination uses `taskkill /T /F`; Windows 11 +foreground Ctrl+C and human PDF inspection remain separate qualifications. + ## Test data Keep the suite concentrated on public behavior and important failures. Package diff --git a/package.json b/package.json index f24c1ab..3dd936a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "test:packages": "npm run build:cli && tsx tests/integration/installed/acceptance.ts && npm run test:initializer", "test:initializer": "tsx tests/integration/installed/initializer-acceptance.ts", "typecheck": "npm run build:lib && tsc --noEmit --pretty false && npm run typecheck --workspaces", - "lint": "npm run lint --workspaces && biome lint tests scripts/check-library-archives.ts scripts/check-project-isolation.ts scripts/run-python-tests.ts scripts/check-pr-title.test.ts scripts/check-ci.test.ts", + "lint": "npm run lint --workspaces && biome lint tests scripts/check-library-archives.ts scripts/check-project-isolation.ts scripts/run-python-tests.ts scripts/check-pr-title.test.ts scripts/check-ci.test.ts scripts/check-windows-powershell-docs.test.ts", "format": "biome format --write ./apps/demo/app ./apps/demo/src ./packages/cso-core/src ./packages/cso-react/src ./packages/cso-core/tests ./packages/cso-react/tests ./tests", "test:library-archives": "tsx scripts/check-library-archives.ts", "test:python": "tsx scripts/run-python-tests.ts", @@ -30,7 +30,7 @@ "test:integration": "vitest run --config vitest.config.ts", "test:isolation": "tsx scripts/check-project-isolation.ts", "test:pr-title": "node --test scripts/check-pr-title.test.ts", - "test:ci": "node --test scripts/check-ci.test.ts" + "test:ci": "node --test scripts/check-ci.test.ts scripts/check-windows-powershell-docs.test.ts" }, "overrides": { "tsup": { diff --git a/packages/create-cs-object/README.md b/packages/create-cs-object/README.md index 9079e49..ed55536 100644 --- a/packages/create-cs-object/README.md +++ b/packages/create-cs-object/README.md @@ -12,7 +12,7 @@ npm run dev In PowerShell 5.1 or 7, select Python 3.11+ and run the setup steps explicitly: ```powershell -$env:PYTHON = (Get-Command python -CommandType Application).Source +$env:PYTHON = Get-Command python -CommandType Application | Select-Object -First 1 -ExpandProperty Source & $env:PYTHON --version ``` diff --git a/packages/cso-cli/README.md b/packages/cso-cli/README.md index 68237f3..035b744 100644 --- a/packages/cso-cli/README.md +++ b/packages/cso-cli/README.md @@ -42,17 +42,17 @@ in `.venv`. A generated project's corresponding commands are in its `authoring.m ```powershell $env:PYTHON = Join-Path $PWD '.venv\Scripts\python.exe' -$cso = Join-Path $PWD 'node_modules\.bin\cso.cmd' +$cso = Join-Path $PWD 'packages\cso-cli\dist\cli.js' $source = Join-Path $PWD 'examples\two-panel\estimate.cso.py' $reference = Join-Path $PWD 'examples\two-panel\reference.json' -& $cso bindings (Join-Path $PWD 'examples\two-panel') +& node.exe $cso bindings (Join-Path $PWD 'examples\two-panel') if ($LASTEXITCODE -ne 0) { throw 'Binding generation failed.' } -& $cso verify $source --function estimate --input width=2 --reference $reference --format json +& node.exe $cso verify $source --function estimate --input width=2 --reference $reference --format json if ($LASTEXITCODE -ne 0) { throw 'Calculation verification failed.' } New-Item -ItemType Directory -Force (Join-Path $PWD 'output') | Out-Null -& $cso html $source --function estimate --input width=2 --reference $reference --out (Join-Path $PWD 'output\panels.html') --check-layout --format json +& node.exe $cso html $source --function estimate --input width=2 --reference $reference --out (Join-Path $PWD 'output\panels.html') --check-layout --format json if ($LASTEXITCODE -ne 0) { throw 'Checked HTML generation failed.' } -& $cso pdf $source --function estimate --input width=2 --reference $reference --out (Join-Path $PWD 'output\panels.pdf') --format json +& node.exe $cso pdf $source --function estimate --input width=2 --reference $reference --out (Join-Path $PWD 'output\panels.pdf') --format json if ($LASTEXITCODE -ne 0) { throw 'PDF generation failed.' } ``` diff --git a/scripts/check-windows-powershell-docs.test.ts b/scripts/check-windows-powershell-docs.test.ts new file mode 100644 index 0000000..a268cc3 --- /dev/null +++ b/scripts/check-windows-powershell-docs.test.ts @@ -0,0 +1,76 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { test } from 'node:test'; + +type SnippetSpec = { id: string; guide: string }; +const snippets = [ + { id: 'repository-setup', guide: 'docs/development.md' }, + { id: 'archive-consumer', guide: 'docs/development.md' }, + { id: 'canonical-report', guide: 'packages/cso-cli/README.md' }, + { id: 'project-create', guide: 'packages/create-cs-object/README.md' }, + { id: 'project-setup', guide: 'packages/create-cs-object/README.md' }, + { id: 'project-dev', guide: 'packages/create-cs-object/README.md' }, + { id: 'project-build', guide: 'packages/create-cs-object/template/README.md' }, + { id: 'project-bindings', guide: 'packages/create-cs-object/template/authoring.md' }, + { id: 'project-report', guide: 'packages/create-cs-object/template/authoring.md' }, + { id: 'reference-authoring', guide: 'packages/create-cs-object/template/references/README.md' }, +] satisfies SnippetSpec[]; + +function normalize(lines: string[]): string { + const content = lines.filter((line) => line.trim().length > 0); + assert(content.length > 0, 'A marked command block must not be empty'); + const indent = Math.min(...content.map((line) => line.length - line.trimStart().length)); + return lines.map((line) => line.slice(indent)).join('\n'); +} + +function readBlocks(path: string, format: 'markdown' | 'powershell'): Map { + const source = readFileSync(new URL(`../${path}`, import.meta.url), 'utf8'); + const marker = format === 'markdown' + ? /^$/ + : /^# docs:([a-z-]+):(start|end)$/; + const blocks = new Map(); + let open: { id: string; lines: string[] } | undefined; + for (const line of source.replace(/\r\n/g, '\n').split('\n')) { + const match = marker.exec(line.trim()); + if (!match) { + if (open) open.lines.push(line); + continue; + } + const [, id, edge] = match; + assert(id !== undefined, `Missing snippet id in ${path}`); + if (edge === 'start') { + assert.equal(open, undefined, `Nested snippet ${id} in ${path}`); + assert(!blocks.has(id), `Duplicate snippet ${id} in ${path}`); + open = { id, lines: [] }; + } else { + assert(open && open.id === id, `Unpaired snippet end ${id} in ${path}`); + let lines = open.lines; + if (format === 'markdown') { + assert.equal(lines[0]?.trim(), '```powershell', `Missing PowerShell fence for ${id} in ${path}`); + assert.equal(lines.at(-1)?.trim(), '```', `Unclosed PowerShell fence for ${id} in ${path}`); + lines = lines.slice(1, -1); + } + blocks.set(id, normalize(lines)); + open = undefined; + } + } + assert.equal(open, undefined, `Unclosed snippet in ${path}`); + return blocks; +} + +test('published PowerShell commands match all ten native Windows workflow blocks', () => { + const workflow = readBlocks('tests/integration/installed/windows-user-workflow.ps1', 'powershell'); + assert.deepEqual([...workflow.keys()].sort(), snippets.map(({ id }) => id).sort()); + const published = new Map(); + for (const guide of new Set(snippets.map(({ guide }) => guide))) { + const blocks = readBlocks(guide, 'markdown'); + assert.deepEqual([...blocks.keys()].sort(), snippets.filter((snippet) => snippet.guide === guide).map(({ id }) => id).sort(), guide); + for (const [id, commands] of blocks) { + assert(!published.has(id), `Duplicate published snippet ${id}`); + published.set(id, commands); + } + } + for (const { id, guide } of snippets) { + assert.equal(published.get(id), workflow.get(id), `${guide}: ${id} differs from the native workflow`); + } +}); diff --git a/tests/integration/installed/windows-user-workflow.ps1 b/tests/integration/installed/windows-user-workflow.ps1 index 8fd2c80..ff5bb37 100644 --- a/tests/integration/installed/windows-user-workflow.ps1 +++ b/tests/integration/installed/windows-user-workflow.ps1 @@ -192,6 +192,11 @@ try { } Write-JsonNoBom (Join-Path $evidenceRoot 'versions.json') $versions + $activeBlock = 'documentation-blocks' + & node.exe --test (Join-Path $repository 'scripts\check-windows-powershell-docs.test.ts') + if ($LASTEXITCODE -ne 0) { throw 'Published PowerShell commands differ from the native workflow.' } + $commands.Add([ordered]@{ name = $activeBlock; status = 0 }) + Set-Location $repository $activeBlock = 'repository-setup' # docs:repository-setup:start From 8477eab357baf28d95c462456490d74c1db72200 Mon Sep 17 00:00:00 2001 From: viktar-b <60892287+viktar-b@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:17:48 +0100 Subject: [PATCH 4/4] fix(docs): reject every unknown PowerShell marker id --- scripts/check-windows-powershell-docs.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check-windows-powershell-docs.test.ts b/scripts/check-windows-powershell-docs.test.ts index a268cc3..be71ee8 100644 --- a/scripts/check-windows-powershell-docs.test.ts +++ b/scripts/check-windows-powershell-docs.test.ts @@ -26,8 +26,8 @@ function normalize(lines: string[]): string { function readBlocks(path: string, format: 'markdown' | 'powershell'): Map { const source = readFileSync(new URL(`../${path}`, import.meta.url), 'utf8'); const marker = format === 'markdown' - ? /^$/ - : /^# docs:([a-z-]+):(start|end)$/; + ? /^$/ + : /^# docs:(.+):(start|end)$/; const blocks = new Map(); let open: { id: string; lines: string[] } | undefined; for (const line of source.replace(/\r\n/g, '\n').split('\n')) {