Skip to content

feat(windows): env/path helpers + dsc escape hatch - #48

Merged
ela-fer merged 3 commits into
mainfrom
feat/windows-env-path
Jun 26, 2026
Merged

feat(windows): env/path helpers + dsc escape hatch#48
ela-fer merged 3 commits into
mainfrom
feat/windows-env-path

Conversation

@ela-fer

@ela-fer ela-fer commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

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. properties is 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.

opts accepts { scope?: "user" | "machine"; dependsOn?: ... }. scope defaults 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/path through PSDscResources/Environment via the Microsoft.DSC/PowerShell adapter. 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/PowerShell adapter errors out (Can not perform this operation on the adapter itself), even elevated.
  • PSDscResources/Environment needs 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:

envMicrosoft.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\Environmentwrites without elevation (confirmed on hardware).
  • scope: "machine"HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment — requires elevation (non-elevated write fails with Permission 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.

pathMicrosoft.DSC.Transitional/WindowsPowerShellScript (built-in, idempotent)

The registry resource has no append/merge semantics — it would overwrite PATH wholesale. So path emits an idempotent script resource, the same pattern Microsoft uses for PATH in their WindowsDeveloperConfig repo. The generated testScript/setScript:

  • Surgical, not a sweep. add only appends the target dir if missing; remove only 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.)
  • Appends at the END so user dirs don't shadow system binaries.
  • Preserves the registry ValueKind — PATH is often REG_EXPAND_SZ (%USERPROFILE%…); the script reads the original kind and restores ExpandString if a write downgraded it, while still broadcasting WM_SETTINGCHANGE.
  • Idempotent via testScript (second apply is a no-op; confirmed on hardware).

env/path build on the same internal DSC emission layer as windows.dsc(...), but call it directly rather than routing through the public windows.dsc helper, so the public APIs are not coupled. env and path deliberately use different underlying resources — each picks the best mechanism for the job.

Implementation

  • Zero-dependency YAML value serializer (src/backends/windows/yaml.ts) so arbitrary properties (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.
  • 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 + the two-pass naming plan. The emitter's ad-hoc scalar quoting is unified onto the shared serializer.

Tests

  • Snapshot coverage for env.set/env.remove (user + machine scope) and path.add/path.remove, asserting the surgical-add guard, END-append, ValueKind preserve/restore, and testScript idempotency.
  • A test asserting env emits Microsoft.Windows/Registry and path emits Microsoft.DSC.Transitional/WindowsPowerShellScript (not the PSDSC adapter).
  • Generated YAML validated through a real YAML parser to confirm the inline PowerShell deserializes as real multiline script and the structure matches DSC v3.
  • Full suite 183 passing, tsc --noEmit clean.
  • Example (examples/windows/) + README updated to the env.set/remove + path.add/remove API, noting scope: "machine" requires an elevated apply.

Hardware-validated facts

  • Microsoft.Windows/Registry writes User env (HKCU) without elevation; Machine (HKLM) requires it.
  • Microsoft.DSC.Transitional/WindowsPowerShellScript is built into DSC 3.2.2 (no module install).
  • Non-elevated Machine write fails with: 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 the preview dist-tag as @adrifer/winix@0.2.0-preview.4.

ela-fer and others added 3 commits June 26, 2026 11:51
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(...), and windows.path.add/remove(...), all returning handles that participate in dependsOn.
  • 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.

Comment thread src/helpers/windows.ts
Comment thread tests/windows-backend.test.ts
@ela-fer
ela-fer merged commit 4ec4d07 into main Jun 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants