Skip to content

Add Generic Manifest.PrivateData Support to NovaModuleTools #263

Description

@stiwicourage

Feature: Support Structured Manifest.PrivateData

NovaModuleTools should support the standard PowerShell module manifest field:

Manifest.PrivateData

The previous implementation attempt established an important PowerShell limitation:

New-ModuleManifest -PrivateData

does not correctly serialize arbitrary nested structured values.

Nested hashtables and arrays can be converted to string representations instead of valid PowerShell data literals.

The PowerShell .psd1 manifest format itself supports nested PrivateData, so implement the feature in NovaModuleTools without relying on New-ModuleManifest to serialize the structured PrivateData value.

Goal

Support configuration such as:

{
    "Manifest": {
        "PrivateData": {
            "ExampleProduct": {
                "Enabled": true,
                "ApiVersion": "1",
                "RetryCount": 3,
                "Nested": {
                    "Mode": "Test"
                }
            }
        }
    }
}

and generate a valid PowerShell module manifest containing equivalent native PowerShell data:

PrivateData = @{
    ExampleProduct = @{
        Enabled = $true
        ApiVersion = '1'
        RetryCount = 3
        Nested = @{
            Mode = 'Test'
        }
    }
}

The generated manifest must successfully load through:

Import-PowerShellDataFile

and validate through:

Test-ModuleManifest

Architecture

Keep the existing Nova manifest-generation architecture.

Continue using:

New-ModuleManifest

for the normal PowerShell module manifest generation.

However, do not pass arbitrary structured consumer data through:

New-ModuleManifest -PrivateData

when that would cause PowerShell to stringify or flatten the data.

Instead, Nova should own serialization of the configured structured Manifest.PrivateData value into valid PowerShell data syntax.

Implement this as a small, generic manifest-data serialization capability.

The implementation must not be specific to any particular consumer or metadata structure.

Schema

Add optional:

Manifest.PrivateData

to the existing project schema.

It must support arbitrary consumer-defined keys and nested structures.

Existing projects without Manifest.PrivateData must remain unchanged.

Serialization

Implement a generic serializer capable of converting the JSON/PowerShell representation used by Nova into valid PowerShell data-file syntax.

At minimum support the value types that are valid and useful in module manifest private data:

object / hashtable
array
string
boolean
integer
number
null

Examples:

JSON true
→ $true

JSON false
→ $false

JSON null
→ $null

JSON string
→ correctly quoted PowerShell string

JSON number
→ PowerShell numeric literal

JSON array
→ @( ... )

JSON object
→ @{ ... }

Nested combinations must work recursively.

String escaping

String serialization must correctly escape values for PowerShell single-quoted string literals.

For example, an apostrophe must not result in an invalid or executable manifest.

Do not construct executable PowerShell expressions from consumer values.

The output must remain valid PowerShell data suitable for:

Import-PowerShellDataFile

Treat values as data, not code.

Deterministic output

Serialization should be deterministic.

Given the same project.json, Nova should generate the same PrivateData representation.

Preserve configuration/property order where the existing configuration representation provides a stable order. Otherwise use a deterministic ordering.

This avoids unnecessary changes in generated manifests.

Integration with New-ModuleManifest

Inspect the current generated manifest structure and choose the smallest robust integration point.

A suitable approach may be:

  1. Generate the normal manifest using New-ModuleManifest.
  2. Locate the generated top-level PrivateData assignment structurally and replace that generated block with Nova's serialized PrivateData block.

Do not use fragile line-number assumptions.

Do not use broad regular expressions that can accidentally match nested or commented content.

If a safer implementation is possible by composing the final manifest from the output/template produced by New-ModuleManifest, prefer that.

The important requirement is:

New-ModuleManifest
    → remains responsible for the standard manifest

Nova
    → becomes responsible for serializing configured structured PrivateData

Keep this responsibility isolated so it can easily be removed in the future if PowerShell gains correct structured PrivateData serialization.

Preserve PowerShell-generated PSData

Be careful with the relationship between:

PrivateData

and:

PrivateData.PSData

Nova already uses standard manifest properties such as:

Tags
ProjectUri
LicenseUri
ReleaseNotes
Prerelease
RequireLicenseAcceptance
ExternalModuleDependencies

which PowerShell represents under:

PrivateData = @{
    PSData = @{
        ...
    }
}

Adding consumer-defined Manifest.PrivateData must not accidentally remove or overwrite this PowerShell/Nova-generated PSData.

The final manifest may therefore need to merge:

PowerShell/Nova-generated PSData
+
consumer-defined Manifest.PrivateData

into one valid top-level PrivateData hashtable.

Define and test the collision behavior.

Do not silently overwrite existing generated metadata.

Tests

Add focused serializer tests covering:

string
string containing apostrophe
empty string
boolean true
boolean false
integer
number
null
empty object
nested object
array
nested arrays/objects

Also test values that could otherwise produce invalid PowerShell syntax.

Build-level integration test

Create or extend a fixture containing:

{
    "Manifest": {
        "PrivateData": {
            "ExampleProduct": {
                "Enabled": true,
                "ApiVersion": "1",
                "RetryCount": 3,
                "Nested": {
                    "Mode": "Test"
                },
                "Values": [
                    "one",
                    "two"
                ]
            }
        }
    }
}

Build through the normal Nova build pipeline.

Then load the actual generated .psd1:

$manifest = Import-PowerShellDataFile -Path $manifestPath

and verify:

$manifest.PrivateData.ExampleProduct.Enabled |
    Should -BeTrue

$manifest.PrivateData.ExampleProduct.ApiVersion |
    Should -Be '1'

$manifest.PrivateData.ExampleProduct.RetryCount |
    Should -Be 3

$manifest.PrivateData.ExampleProduct.Nested.Mode |
    Should -Be 'Test'

$manifest.PrivateData.ExampleProduct.Values |
    Should -Be @('one', 'two')

Also run:

Test-ModuleManifest

against the generated manifest.

PSData regression test

Add a fixture/test where both standard Nova manifest metadata and custom PrivateData exist.

For example:

{
    "Manifest": {
        "Tags": [
            "Example"
        ],
        "PrivateData": {
            "ExampleProduct": {
                "Enabled": true
            }
        }
    }
}

Verify that the generated manifest contains both:

PrivateData.PSData.Tags

and:

PrivateData.ExampleProduct.Enabled

with the expected values.

Backward compatibility

Projects without:

Manifest.PrivateData

must continue through the existing build path without unnecessary manifest rewriting or output changes.

Only projects that explicitly configure structured PrivateData should require the additional serialization behavior.

Documentation

Document:

Manifest.PrivateData

as arbitrary consumer-defined module metadata.

Explain that Nova serializes this data into the generated module manifest because PowerShell's New-ModuleManifest does not correctly preserve arbitrary nested structured values through its -PrivateData parameter.

Keep this implementation detail concise.

Validation

Run the complete existing NovaModuleTools quality gates.

Requirements:

0 failed tests
no coverage regression
no new static-analysis findings
valid generated manifests

Completion report

Report:

  1. Schema changes.
  2. Serializer design.
  3. Supported value types.
  4. String escaping behavior.
  5. How deterministic output is achieved.
  6. How the generated PrivateData block is integrated with New-ModuleManifest.
  7. How existing PSData is preserved and merged.
  8. Collision behavior.
  9. Serializer tests.
  10. Build-level integration tests.
  11. Import-PowerShellDataFile results.
  12. Test-ModuleManifest results.
  13. Backward-compatibility results.
  14. Final test count and coverage.
  15. Static-analysis and build results.
  16. Any remaining PowerShell limitations discovered.

Activity

  1. added a commit that references this issue on Oct 7, 2026
  2. added 2 commits that reference this issue on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions