Skip to content

Commit bd12c96

Browse files
committed
#242 feat: Add VS Code settings integration for project.json schema support
1 parent 4c68def commit bd12c96

11 files changed

Lines changed: 256 additions & 10 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
1010
- `project.json` now has a single authoritative JSON schema hosted at `https://www.novamoduletools.com/schema/v{major}/project.json`.
1111
- `Invoke-NovaBuild` exports the schema to `docs/schema/v{major}/project.json` during build, creating the directory when missing.
1212
- `nova init` injects `"$schema": "https://www.novamoduletools.com/schema/v{major}/project.json"` into every scaffolded project.
13+
- `nova init` also writes `.vscode/settings.json` mapping `project.json` to the hosted schema URL so VS Code trusts the remote schema automatically without prompting.
1314
- VS Code picks up the schema automatically to provide field validation, autocomplete, and hover descriptions while editing `project.json`.
1415
- The two partial internal schemas (`Schema-Build.json`, `Schema-Pester.json`) are removed; `Schema-Project.json` is the new single source.
1516

@@ -32,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
3233
- The progress text now stays simple while the bar itself reflects the real test-driven completion state.
3334
- The configured Pester output still flows live so `project.json` settings such as `Pester.Output.Verbosity = "Detailed"` remain visible.
3435
- Progress-enabled private workflows now have stronger mirrored progress-contract tests, and the repository adds a guardrail test so future `Write-Progress` workflows cannot land without matching test ownership.
36+
- `Package.Types` values in `project.json` now report a human-readable validation error in VS Code instead of showing the raw regex pattern. VS Code now shows the accepted values (`NuGet`, `Zip`, `.nupkg`, `.zip`) directly in the error.
3537

3638
### Security
3739

RELEASE_NOTE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This file summarizes the release notes for NovaModuleTools. **UNRELEASED** chang
77
### Added
88

99
- `nova init` now injects a `"$schema"` property into new projects pointing to the hosted versioned JSON schema, enabling VS Code IntelliSense, autocomplete, and hover descriptions while editing `project.json`.
10+
- `nova init` also writes `.vscode/settings.json` mapping `project.json` to the schema URL so VS Code trusts it automatically — no manual "Allow" step required.
1011

1112
### Changed
1213

@@ -24,6 +25,7 @@ This file summarizes the release notes for NovaModuleTools. **UNRELEASED** chang
2425
- `Publish-NovaModule` no longer loses its completion summary after local publish import or CI session refresh steps reload the module.
2526
- `Test-NovaBuild` and `% nova test` now keep the Nova progress display visibly active during long Pester runs instead of appearing stuck on one step while tests continue.
2627
- During the long Pester phase, Nova now drives the progress bar from discovered and completed Pester tests instead of elapsed time, keeps the progress text simple, and still shows configured Pester output such as `Pester.Output.Verbosity = "Detailed"`.
28+
- Invalid `Package.Types` values in `project.json` now show the accepted values (`NuGet`, `Zip`, `.nupkg`, `.zip`) directly in VS Code instead of the raw regex pattern.
2729

2830
### Security
2931

docs/NovaModuleTools/en-US/Initialize-NovaModule.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ PS> Initialize-NovaModule [-Path <string>] [-Example] [-WhatIf] [-Confirm] [<Com
2525

2626
## DESCRIPTION
2727

28-
`Initialize-NovaModule` creates a new project folder, the standard `src/` layout, and a starter `project.json` file.
28+
`Initialize-NovaModule` creates a new project folder, the standard `src/` layout, a starter `project.json` file, and a `.vscode/settings.json` entry that maps `project.json` to the versioned JSON schema so VS Code provides IntelliSense without a manual trust prompt.
2929

3030
The standard scaffold `project.json` includes Nova's standard Pester defaults, including an opt-in `CodeCoverage` block with `Enabled=false`, common `src/` coverage paths, JaCoCo output in `artifacts/coverage.xml`, and a `90` percent target that can be enabled later.
3131

docs/project-json-reference.html

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,13 @@ <h2>Top-level project settings</h2>
206206
<div class="notice">
207207
<strong>VS Code IntelliSense.</strong>
208208
<p>When <code>nova init</code> scaffolds a new project, it injects a <code>$schema</code> field
209-
that points to the versioned public schema at
210-
<code>https://www.novamoduletools.com/schema/v{major}/project.json</code>. VS Code picks this
211-
up automatically to provide field validation, autocomplete, and hover descriptions while you
212-
edit <code>project.json</code>. You can add the field manually to any existing project by
213-
inserting it as the first key in the file.</p>
209+
pointing to the versioned public schema at
210+
<code>https://www.novamoduletools.com/schema/v{major}/project.json</code>, and also writes a
211+
<code>.vscode/settings.json</code> entry that maps <code>project.json</code> to the same URL.
212+
Together these two outputs let VS Code load the schema and provide IntelliSense, field
213+
validation, and hover descriptions without a manual trust prompt. To enable this on an existing
214+
project, add the <code>$schema</code> field as the first key in <code>project.json</code> and
215+
add the matching <code>json.schemas</code> entry to <code>.vscode/settings.json</code>.</p>
214216
</div>
215217
<div class="table-scroll">
216218
<table>

docs/schema/v3/project.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@
130130
"description": "List of package types to produce.",
131131
"items": {
132132
"type": "string",
133-
"pattern": "^(?:[Nn][Uu][Gg][Ee][Tt]|[Zz][Ii][Pp]|\\.[Nn][Uu][Pp][Kk][Gg]|\\.[Zz][Ii][Pp])$"
133+
"enum": ["NuGet", "Zip", ".nupkg", ".zip"],
134+
"description": "Valid values: NuGet, Zip, .nupkg, .zip (case-insensitive at runtime)."
134135
}
135136
},
136137
"Latest": {

src/private/scaffold/InvokeNovaModuleInitializationWorkflow.ps1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ function Invoke-NovaModuleInitializationWorkflow {
1515
Write-NovaModuleProjectJson -Answer $WorkflowContext.AnswerSet -ProjectJsonFile $WorkflowContext.Layout.ProjectJsonFile -Example:$WorkflowContext.Example
1616
}
1717

18+
Invoke-NovaModuleInitializationStep -Activity $progressActivity -Status 'Writing VS Code settings' -PercentComplete 75 -Action {
19+
Write-NovaVsCodeSettings -ProjectRoot $WorkflowContext.Layout.Project
20+
}
21+
1822
if ($WorkflowContext.AnswerSet.EnableAgenticCopilot -eq 'Yes') {
1923
Invoke-NovaModuleInitializationStep -Activity $progressActivity -Status 'Applying Agentic Copilot starter' -PercentComplete 85 -Action {
2024
Initialize-NovaModuleAgenticCopilotScaffold -Answer $WorkflowContext.AnswerSet -ProjectRoot $WorkflowContext.Layout.Project -Example:$WorkflowContext.Example
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
function Write-NovaVsCodeSettings {
2+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'VS Code settings is the domain term for the .vscode/settings.json file.')]
3+
[CmdletBinding()]
4+
param(
5+
[Parameter(Mandatory)][string]$ProjectRoot
6+
)
7+
8+
$moduleVersion = $ExecutionContext.SessionState.Module.Version
9+
if ($null -eq $moduleVersion) { return }
10+
11+
$schemaUrl = "https://www.novamoduletools.com/schema/v$($moduleVersion.Major)/project.json"
12+
$vsCodeDir = Join-Path $ProjectRoot '.vscode'
13+
$settingsFile = Join-Path $vsCodeDir 'settings.json'
14+
15+
if (Test-Path -LiteralPath $settingsFile) {
16+
Add-NovaVsCodeJsonSchemaEntry -SettingsFile $settingsFile -SchemaUrl $schemaUrl
17+
} else {
18+
New-NovaVsCodeSettingsFile -VsCodeDir $vsCodeDir -SettingsFile $settingsFile -SchemaUrl $schemaUrl
19+
}
20+
}
21+
22+
function Add-NovaVsCodeJsonSchemaEntry {
23+
[CmdletBinding()]
24+
param(
25+
[Parameter(Mandatory)][string]$SettingsFile,
26+
[Parameter(Mandatory)][string]$SchemaUrl
27+
)
28+
29+
$settings = Get-Content -LiteralPath $SettingsFile -Raw | ConvertFrom-Json -AsHashtable
30+
if (-not $settings) { $settings = @{} }
31+
if (-not $settings.ContainsKey('json.schemas')) { $settings['json.schemas'] = @() }
32+
33+
$schemas = @($settings['json.schemas'])
34+
$alreadyMapped = $schemas | Where-Object {
35+
$fm = Resolve-NovaFileMatchArray $_
36+
($fm -contains '/project.json') -or ($fm -contains 'project.json')
37+
}
38+
if ($alreadyMapped) { return }
39+
40+
$settings['json.schemas'] = $schemas + @{ fileMatch = @('/project.json'); url = $SchemaUrl }
41+
$settings | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $SettingsFile -Encoding utf8NoBOM
42+
}
43+
44+
function Resolve-NovaFileMatchArray {
45+
[CmdletBinding()]
46+
param($Entry)
47+
48+
$raw = if ($Entry -is [hashtable]) { $Entry['fileMatch'] } else { $Entry.fileMatch }
49+
return @($raw)
50+
}
51+
52+
function New-NovaVsCodeSettingsFile {
53+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private file-writing helper. ShouldProcess is managed at the public Initialize-NovaModule command level.')]
54+
[CmdletBinding()]
55+
param(
56+
[Parameter(Mandatory)][string]$VsCodeDir,
57+
[Parameter(Mandatory)][string]$SettingsFile,
58+
[Parameter(Mandatory)][string]$SchemaUrl
59+
)
60+
61+
if (-not (Test-Path -LiteralPath $VsCodeDir)) {
62+
New-Item -ItemType Directory -Path $VsCodeDir | Out-Null
63+
}
64+
@{ 'json.schemas' = @(@{ fileMatch = @('/project.json'); url = $SchemaUrl }) } |
65+
ConvertTo-Json -Depth 10 |
66+
Set-Content -LiteralPath $SettingsFile -Encoding utf8NoBOM
67+
}

src/resources/Schema-Project.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@
130130
"description": "List of package types to produce.",
131131
"items": {
132132
"type": "string",
133-
"pattern": "^(?:[Nn][Uu][Gg][Ee][Tt]|[Zz][Ii][Pp]|\\.[Nn][Uu][Pp][Kk][Gg]|\\.[Zz][Ii][Pp])$"
133+
"enum": ["NuGet", "Zip", ".nupkg", ".zip"],
134+
"description": "Valid values: NuGet, Zip, .nupkg, .zip (case-insensitive at runtime)."
134135
}
135136
},
136137
"Latest": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
function Initialize-NovaModuleScaffold {param($Answer, $Paths, [switch]$Example)}
22
function Write-NovaModuleProjectJson {param($Answer, [string]$ProjectJsonFile, [switch]$Example)}
3+
function Write-NovaVsCodeSettings {param([string]$ProjectRoot)}
34
function Initialize-NovaModuleAgenticCopilotScaffold {param($Answer, [string]$ProjectRoot, [switch]$Example)}
45
function Write-Message {param([Parameter(ValueFromPipeline = $true)]$InputObject, [string]$color)}

tests/private/scaffold/InvokeNovaModuleInitializationWorkflow.Tests.ps1

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Describe 'Invoke-NovaModuleInitializationWorkflow' {
1717
It 'initializes the scaffold and writes the project.json before announcing completion' {
1818
Mock Initialize-NovaModuleScaffold {}
1919
Mock Write-NovaModuleProjectJson {}
20+
Mock Write-NovaVsCodeSettings {}
2021
Mock Initialize-NovaModuleAgenticCopilotScaffold {}
2122
Mock Write-Message {}
2223
Mock Write-Progress {}
@@ -25,14 +26,18 @@ Describe 'Invoke-NovaModuleInitializationWorkflow' {
2526

2627
Assert-MockCalled Initialize-NovaModuleScaffold -Times 1
2728
Assert-MockCalled Write-NovaModuleProjectJson -Times 1
29+
Assert-MockCalled Write-NovaVsCodeSettings -Times 1 -ParameterFilter { $ProjectRoot -eq '/tmp/DemoModule' }
2830
Assert-MockCalled Initialize-NovaModuleAgenticCopilotScaffold -Times 0
29-
Assert-MockCalled Write-Progress -Times 3
31+
Assert-MockCalled Write-Progress -Times 4
3032
Assert-MockCalled Write-Progress -Times 1 -ParameterFilter {
3133
$Status -eq 'Creating scaffold files' -and $PercentComplete -eq 25
3234
}
3335
Assert-MockCalled Write-Progress -Times 1 -ParameterFilter {
3436
$Status -eq 'Writing project.json' -and $PercentComplete -eq 60
3537
}
38+
Assert-MockCalled Write-Progress -Times 1 -ParameterFilter {
39+
$Status -eq 'Writing VS Code settings' -and $PercentComplete -eq 75
40+
}
3641
Assert-MockCalled Write-Progress -Times 1 -ParameterFilter {$Completed}
3742
Assert-MockCalled Write-Message -Times 4
3843
Assert-MockCalled Write-Message -Times 1 -ParameterFilter {
@@ -46,6 +51,7 @@ Describe 'Invoke-NovaModuleInitializationWorkflow' {
4651
It 'invokes the Agentic Copilot scaffold step when the answer set requests it' {
4752
Mock Initialize-NovaModuleScaffold {}
4853
Mock Write-NovaModuleProjectJson {}
54+
Mock Write-NovaVsCodeSettings {}
4955
Mock Initialize-NovaModuleAgenticCopilotScaffold {}
5056
Mock Write-Message {}
5157
Mock Write-Progress {}
@@ -58,7 +64,7 @@ Describe 'Invoke-NovaModuleInitializationWorkflow' {
5864
Invoke-NovaModuleInitializationWorkflow -WorkflowContext $contextWithAgentic
5965

6066
Assert-MockCalled Initialize-NovaModuleAgenticCopilotScaffold -Times 1
61-
Assert-MockCalled Write-Progress -Times 4
67+
Assert-MockCalled Write-Progress -Times 5
6268
Assert-MockCalled Write-Progress -Times 1 -ParameterFilter {
6369
$Status -eq 'Applying Agentic Copilot starter' -and $PercentComplete -eq 85
6470
}
@@ -68,6 +74,7 @@ Describe 'Invoke-NovaModuleInitializationWorkflow' {
6874
It 'suggests Test-NovaBuild as the next step for the example scaffold' {
6975
Mock Initialize-NovaModuleScaffold {}
7076
Mock Write-NovaModuleProjectJson {}
77+
Mock Write-NovaVsCodeSettings {}
7178
Mock Initialize-NovaModuleAgenticCopilotScaffold {}
7279
Mock Write-Message {}
7380
Mock Write-Progress {}

0 commit comments

Comments
 (0)