feat(windows): env/path helpers + dsc escape hatch - #48
Merged
Conversation
Adds the next slice of the Windows backend: declarative environment
variables, PATH entries, and a generic DSC v3 escape hatch.
New authoring surface:
- windows.dsc({ type, properties, dependsOn? }) declares any DSC v3
resource. `properties` is emitted verbatim as YAML.
- windows.env({ name, value?, ensure?, target?, dependsOn? }) manages a
user/machine environment variable.
- windows.path({ value, ensure?, target?, dependsOn? }) appends a
directory to PATH idempotently (Path: true de-duplicates).
DSC v3 has no native environment resource yet (v3.2.0 added Service,
FirewallRule, and SSH resources but not Environment), so env/path emit the
PSDscResources/Environment resource through the Microsoft.DSC/PowerShell
adapter. That adapter detail is hidden behind the typed helpers; the public
API stays stable if a native resource lands later.
Implementation:
- New zero-dependency YAML value serializer (src/backends/windows/yaml.ts)
so arbitrary `properties` (nested objects, the adapter's `resources:`
array) round-trip. Keeps the project's zero-runtime-deps property.
- New WinDscResource IR + ResourceRef `dsc` kind so dsc/env/path handles
participate in dependsOn ordering like packages and raw commands.
- Emitter renders generic DSC resources via the shared serializer and the
two-pass naming plan; the emitter's ad-hoc scalar quoting is unified onto
the shared serializer (a type with `/` now renders unquoted).
Tests: +23 (13 YAML serializer unit tests, 10 helper/emitter tests incl.
snapshots and cross-kind dependsOn). Generated YAML validated through a real
YAML parser. Full suite 178 passing. Example and README extended.
There was a problem hiding this comment.
Pull request overview
This PR expands Winix’s Windows backend authoring surface by adding typed helpers for environment variables and PATH management, plus a generic DSC v3 “escape hatch” resource, and updates the Windows emitter to serialize arbitrary DSC resource properties through a shared, dependency-free YAML serializer.
Changes:
- Add
windows.dsc(...)(generic DSC resource),windows.env.set/remove(...), andwindows.path.add/remove(...), all returning handles that participate independsOn. - Introduce a minimal YAML serializer (
src/backends/windows/yaml.ts) and route DSC resource property emission through it in the Windows backend. - Add/extend unit + integration tests, snapshots, and Windows example/docs/spec updates for the new APIs.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/windows-yaml.test.ts | Unit tests for the new YAML serializer behavior and edge cases. |
| tests/windows-example.test.ts | Integration snapshot test for examples/windows output stability. |
| tests/windows-backend.test.ts | Coverage for new helpers and emitter output (including dependsOn across kinds). |
| tests/type-fixtures/static-types.ts | Static typing fixtures for new Windows namespaces and options. |
| tests/snapshots/windows-example.test.ts.snap | Updated snapshot for Windows example bundle output. |
| tests/snapshots/windows-backend.test.ts.snap | New/updated snapshots for DSC/env/path emission. |
| src/types/windows.ts | New Windows IR types for DSC resources and JSON-like properties. |
| src/index.ts | Export updated Windows helper types from the public entrypoint. |
| src/helpers/windows.ts | Implement windows.dsc, windows.env.*, windows.path.*, and dependsOn support. |
| src/helpers/index.ts | Re-export the new Windows helper/type surfaces. |
| src/core/types.ts | Extend ResourceRef with a dsc kind for cross-resource dependsOn. |
| src/backends/windows/yaml.ts | Add dependency-free YAML serialization utilities used by the Windows emitter. |
| src/backends/windows/index.ts | Emit generic DSC resources and unify scalar handling via the shared YAML serializer. |
| spec/research/windows-scenarios.md | Update research notes to reflect implemented env/path approach. |
| spec/proposals/windows-backend.md | Update proposal phase/status and API examples for env/path and DSC. |
| examples/windows/winix.config.ts | Update example config to demonstrate env/path and handle-based ordering. |
| examples/windows/README.md | Document env/path behavior and the new windows.dsc escape hatch. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds the next slice of the Windows backend (Phase 3): declarative environment variables, PATH entries, and a generic DSC v3 escape hatch.
New authoring surface
windows.dsc({ type, properties, dependsOn? })— declare any DSC v3 resource.propertiesis emitted verbatim as YAML. The backend's generic escape hatch.windows.env.set(name, value, opts?)/windows.env.remove(name, opts?)— manage a user/machine environment variable.windows.path.add(dir, opts?)/windows.path.remove(dir, opts?)— add/remove a directory on PATH idempotently, without clobbering the rest of PATH.optsaccepts{ scope?: "user" | "machine"; dependsOn?: ... }.scopedefaults to"user"(writes to the per-user hive, no elevation required).scope: "machine"writes the system-wide hive and requires an elevated apply.Why this shape (validated on real hardware)
The original plan emitted
env/paththroughPSDscResources/Environmentvia theMicrosoft.DSC/PowerShelladapter. Testing on real Windows (DSC 3.2.2, winget 1.29.20-preview) showed that path fails, for reasons no amount of reasoning would have surfaced:Microsoft.DSC/PowerShelladapter errors out (Can not perform this operation on the adapter itself), even elevated.PSDscResources/Environmentneeds a PowerShell Gallery module that isn't installed by default (module not found).So the adapter approach was dropped in favor of native / built-in resources, each chosen as the best fit and validated on hardware:
env→Microsoft.Windows/Registry(native, built into DSC 3.x)Windows env vars are registry values. The native registry resource writes them directly: no PSDSC adapter, no external module.
scope: "user"→HKCU\Environment— writes without elevation (confirmed on hardware).scope: "machine"→HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment— requires elevation (non-elevated write fails withPermission denied).set→_exist: true+valueData: { String: <value> };remove→_exist: false.This also recovers the "persist a value for me without admin" case that the old Environment resource (Process/Machine only) could not express.
path→Microsoft.DSC.Transitional/WindowsPowerShellScript(built-in, idempotent)The registry resource has no append/merge semantics — it would overwrite PATH wholesale. So
pathemits an idempotent script resource, the same pattern Microsoft uses for PATH in theirWindowsDeveloperConfigrepo. The generatedtestScript/setScript:addonly appends the target dir if missing;removeonly removes the target dir. It does not dedupe, reorder, or normalize the rest of PATH. (A naive split/dedupe/rejoin silently collapsed a 50-entry PATH to 42 in testing — that bug is explicitly avoided.)REG_EXPAND_SZ(%USERPROFILE%…); the script reads the original kind and restoresExpandStringif a write downgraded it, while still broadcastingWM_SETTINGCHANGE.testScript(second apply is a no-op; confirmed on hardware).env/pathbuild on the same internal DSC emission layer aswindows.dsc(...), but call it directly rather than routing through the publicwindows.dschelper, so the public APIs are not coupled.envandpathdeliberately use different underlying resources — each picks the best mechanism for the job.Implementation
src/backends/windows/yaml.ts) so arbitraryproperties(nested objects, inline scripts) round-trip. Keeps the project's zero-runtime-deps property — we render Nix by hand, we can render YAML by hand.WinDscResourceIR +ResourceRefdsckind so dsc/env/path handles participate independsOnordering like packages and raw commands.Tests
env.set/env.remove(user + machine scope) andpath.add/path.remove, asserting the surgical-add guard, END-append, ValueKind preserve/restore, andtestScriptidempotency.envemitsMicrosoft.Windows/RegistryandpathemitsMicrosoft.DSC.Transitional/WindowsPowerShellScript(not the PSDSC adapter).tsc --noEmitclean.examples/windows/) + README updated to theenv.set/remove+path.add/removeAPI, notingscope: "machine"requires an elevated apply.Hardware-validated facts
Microsoft.Windows/Registrywrites User env (HKCU) without elevation; Machine (HKLM) requires it.Microsoft.DSC.Transitional/WindowsPowerShellScriptis built into DSC 3.2.2 (no module install).Registry key: Permission denied for given path: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment".Public contract
Preview/unmerged, so the env/path surface changed shape (object-form → intention-named sub-methods).
windows.dsc(...)stays stable. Available to try on thepreviewdist-tag as@adrifer/winix@0.2.0-preview.4.