Skip to content

Commit 41e7b4c

Browse files
Merge pull request #6 from ktsu-dev/feature/ktsu-ecosystem-integration
Integrate ktsu ecosystem libraries into SchemaEditor
2 parents 2c8e392 + 7c1e616 commit 41e7b4c

16 files changed

Lines changed: 411 additions & 3971 deletions

.github/workflows/dotnet.yml

Lines changed: 78 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,25 @@ on:
1111
schedule:
1212
- cron: "0 23 * * *" # Daily at 11 PM UTC
1313
workflow_dispatch: # Allow manual triggers
14+
inputs:
15+
version-bump:
16+
description: 'Version bump type'
17+
required: false
18+
default: 'auto'
19+
type: choice
20+
options:
21+
- auto
22+
- patch
23+
- minor
24+
- major
1425

1526
concurrency:
1627
group: ${{ github.workflow }}-${{ github.ref }}
1728
cancel-in-progress: true
1829

1930
# Default permissions
20-
permissions: read-all
31+
permissions:
32+
contents: read
2133

2234
env:
2335
DOTNET_VERSION: "10.0" # Only needed for actions/setup-dotnet
@@ -35,7 +47,6 @@ jobs:
3547
version: ${{ steps.pipeline.outputs.version }}
3648
release_hash: ${{ steps.pipeline.outputs.release_hash }}
3749
should_release: ${{ steps.pipeline.outputs.should_release }}
38-
skipped_release: ${{ steps.pipeline.outputs.skipped_release }}
3950

4051
steps:
4152
- name: Set up JDK 17
@@ -95,72 +106,73 @@ jobs:
95106
New-Item -Path .\.sonar\scanner -ItemType Directory
96107
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
97108
109+
- name: Configure SonarQube exclusions
110+
shell: bash
111+
run: |
112+
EXCLUSIONS="_temp/**,_actions/**"
113+
if [ "${{ github.event.repository.name }}" != "KtsuBuild" ]; then
114+
EXCLUSIONS="$EXCLUSIONS,**/KtsuBuild/**"
115+
fi
116+
echo "SONAR_EXCLUSIONS=$EXCLUSIONS" >> $GITHUB_ENV
117+
98118
- name: Begin SonarQube
99119
if: ${{ env.SONAR_TOKEN != '' }}
100120
env:
101121
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
102122
shell: powershell
103123
run: |
104-
.\.sonar\scanner\dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" /o:"${{ github.repository_owner }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths="coverage/coverage.xml" /d:sonar.coverage.exclusions="**/*Test*.cs,**/*.Tests.cs,**/*.Tests/**/*,**/obj/**/*,**/*.dll" /d:sonar.cs.vstest.reportsPaths="coverage/TestResults/**/*.trx"
124+
.\.sonar\scanner\dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" /o:"${{ github.repository_owner }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths="coverage/coverage.xml" /d:sonar.coverage.exclusions="**/*Test*.cs,**/*.Tests.cs,**/*.Tests/**/*,**/obj/**/*,**/*.dll" /d:sonar.cs.vstest.reportsPaths="coverage/TestResults/**/*.trx" /d:sonar.exclusions="${{ env.SONAR_EXCLUSIONS }}"
105125
106-
- name: Run PSBuild Pipeline
126+
- name: Clone KtsuBuild (Latest Tag)
127+
run: |
128+
LATEST_TAG=$(git ls-remote --tags https://github.com/ktsu-dev/KtsuBuild.git | grep -o 'refs/tags/v[0-9]*\.[0-9]*\.[0-9]*$' | sed 's/refs\/tags\///' | sort -V | tail -1 || true)
129+
if [ -z "$LATEST_TAG" ]; then
130+
echo "No version tags found, falling back to HEAD"
131+
git clone --depth 1 https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
132+
else
133+
echo "Cloning KtsuBuild at tag: $LATEST_TAG"
134+
git clone --depth 1 --branch "$LATEST_TAG" https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
135+
fi
136+
shell: bash
137+
138+
- name: Run KtsuBuild CI Pipeline
107139
id: pipeline
108140
shell: pwsh
109141
env:
110142
GH_TOKEN: ${{ github.token }}
143+
NUGET_API_KEY: ${{ secrets.NUGET_KEY }}
144+
KTSU_PACKAGE_KEY: ${{ secrets.KTSU_PACKAGE_KEY }}
145+
EXPECTED_OWNER: ktsu-dev
111146
run: |
112-
# Import the PSBuild module
113-
Import-Module ${{ github.workspace }}/scripts/PSBuild.psm1
114-
115-
# Get build configuration
116-
$buildConfig = Get-BuildConfiguration `
117-
-ServerUrl "${{ github.server_url }}" `
118-
-GitRef "${{ github.ref }}" `
119-
-GitSha "${{ github.sha }}" `
120-
-GitHubOwner "${{ github.repository_owner }}" `
121-
-GitHubRepo "${{ github.repository }}" `
122-
-GithubToken "${{ github.token }}" `
123-
-NuGetApiKey "${{ secrets.NUGET_KEY }}" `
124-
-KtsuPackageKey "${{ secrets.KTSU_PACKAGE_KEY }}" `
125-
-WorkspacePath "${{ github.workspace }}" `
126-
-ExpectedOwner "ktsu-dev" `
127-
-ChangelogFile "CHANGELOG.md" `
128-
-AssetPatterns @("staging/*.nupkg", "staging/*.zip")
129-
130-
if (-not $buildConfig.Success) {
131-
throw $buildConfig.Error
147+
# Run the CI pipeline
148+
$versionBump = "${{ github.event.inputs.version-bump }}"
149+
150+
# Build arguments array - only add --version-bump if explicitly set (for backward compatibility during bootstrap)
151+
$args = @("ci", "--workspace", "${{ github.workspace }}", "--verbose")
152+
if (![string]::IsNullOrEmpty($versionBump) -and $versionBump -ne "auto") {
153+
$args += @("--version-bump", $versionBump)
132154
}
133155
134-
# Run the complete CI/CD pipeline
135-
$result = Invoke-CIPipeline `
136-
-BuildConfiguration $buildConfig.Data
156+
& dotnet run --project "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI" -- @args
137157
138-
if (-not $result.Success) {
139-
Write-Information "CI/CD pipeline failed: $($result.Error)" -Tags "Invoke-CIPipeline"
140-
Write-Information "Stack Trace: $($result.StackTrace)" -Tags "Invoke-CIPipeline"
141-
Write-Information "Build Configuration: $($buildConfig.Data | ConvertTo-Json -Depth 10)" -Tags "Invoke-CIPipeline"
142-
throw $result.Error
143-
}
158+
# Set outputs for downstream jobs
159+
$version = (Get-Content "${{ github.workspace }}/VERSION.md" -Raw).Trim()
160+
"version=$version" >> $env:GITHUB_OUTPUT
144161
145-
# Set outputs for GitHub Actions from build configuration and pipeline result
146-
# Use pipeline result values when available (for skipped releases), otherwise use buildConfig
147-
if ($result.Data.SkippedRelease) {
148-
"version=$($result.Data.Version)" >> $env:GITHUB_OUTPUT
149-
"release_hash=$($result.Data.ReleaseHash)" >> $env:GITHUB_OUTPUT
150-
"should_release=$($buildConfig.Data.ShouldRelease)" >> $env:GITHUB_OUTPUT
151-
"skipped_release=true" >> $env:GITHUB_OUTPUT
152-
} else {
153-
"version=$($buildConfig.Data.Version)" >> $env:GITHUB_OUTPUT
154-
"release_hash=$($buildConfig.Data.ReleaseHash)" >> $env:GITHUB_OUTPUT
155-
"should_release=$($buildConfig.Data.ShouldRelease)" >> $env:GITHUB_OUTPUT
156-
# Check for skipped release from buildConfig as fallback
157-
if ($buildConfig.Data.SkippedRelease) {
158-
"skipped_release=true" >> $env:GITHUB_OUTPUT
159-
}
160-
}
162+
$releaseHash = git rev-parse HEAD
163+
"release_hash=$releaseHash" >> $env:GITHUB_OUTPUT
164+
165+
# Compute should_release (same logic as BuildConfigurationProvider)
166+
$isMain = "${{ github.ref }}" -eq "refs/heads/main"
167+
$isTagged = [bool](git tag --points-at "${{ github.sha }}" 2>$null)
168+
$isFork = "${{ github.event.repository.fork }}" -eq "true"
169+
$isExpectedOwner = "${{ github.repository_owner }}" -eq "ktsu-dev"
170+
$isOfficial = (-not $isFork) -and $isExpectedOwner
171+
$shouldRelease = $isMain -and (-not $isTagged) -and $isOfficial
172+
"should_release=$($shouldRelease.ToString().ToLower())" >> $env:GITHUB_OUTPUT
161173
162174
- name: End SonarQube
163-
if: env.SONAR_TOKEN != '' && steps.pipeline.outputs.skipped_release != 'true'
175+
if: env.SONAR_TOKEN != ''
164176
env:
165177
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
166178
shell: powershell
@@ -169,7 +181,7 @@ jobs:
169181
170182
- name: Upload Coverage Report
171183
uses: actions/upload-artifact@v4
172-
if: always() && steps.pipeline.outputs.skipped_release != 'true'
184+
if: always()
173185
with:
174186
name: coverage-report
175187
path: |
@@ -179,7 +191,7 @@ jobs:
179191
winget:
180192
name: Update Winget Manifests
181193
needs: build
182-
if: needs.build.outputs.should_release == 'true' && needs.build.outputs.skipped_release != 'true'
194+
if: needs.build.outputs.should_release == 'true'
183195
runs-on: windows-latest
184196
timeout-minutes: 10
185197
permissions:
@@ -197,14 +209,24 @@ jobs:
197209
with:
198210
dotnet-version: ${{ env.DOTNET_VERSION }}.x
199211

212+
- name: Clone KtsuBuild (Latest Tag)
213+
run: |
214+
LATEST_TAG=$(git ls-remote --tags https://github.com/ktsu-dev/KtsuBuild.git | grep -o 'refs/tags/v[0-9]*\.[0-9]*\.[0-9]*$' | sed 's/refs\/tags\///' | sort -V | tail -1 || true)
215+
if [ -z "$LATEST_TAG" ]; then
216+
echo "No version tags found, falling back to HEAD"
217+
git clone --depth 1 https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
218+
else
219+
echo "Cloning KtsuBuild at tag: $LATEST_TAG"
220+
git clone --depth 1 --branch "$LATEST_TAG" https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
221+
fi
222+
shell: bash
223+
200224
- name: Update Winget Manifests
201225
shell: pwsh
202226
env:
203227
GH_TOKEN: ${{ github.token }}
204228
run: |
205-
# Use enhanced script with auto-detection capabilities
206-
Write-Host "Updating winget manifests for version ${{ needs.build.outputs.version }}"
207-
.\scripts\update-winget-manifests.ps1 -Version "${{ needs.build.outputs.version }}"
229+
dotnet run --project "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI" -- winget generate --version "${{ needs.build.outputs.version }}" --workspace "${{ github.workspace }}" --verbose
208230
209231
- name: Upload Updated Manifests
210232
uses: actions/upload-artifact@v4
@@ -216,7 +238,7 @@ jobs:
216238
security:
217239
name: Security Scanning
218240
needs: build
219-
if: needs.build.outputs.should_release == 'true' && needs.build.outputs.skipped_release != 'true'
241+
if: needs.build.outputs.should_release == 'true'
220242
runs-on: windows-latest
221243
timeout-minutes: 10
222244
permissions:

Directory.Packages.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
<ItemGroup>
77
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
88
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
9+
<PackageVersion Include="ktsu.DeepClone" Version="2.0.8" />
910
<PackageVersion Include="ktsu.Extensions" Version="1.5.6" />
11+
<PackageVersion Include="ktsu.FuzzySearch" Version="1.2.5" />
12+
<PackageVersion Include="ktsu.IntervalAction" Version="1.3.7" />
13+
<PackageVersion Include="ktsu.Keybinding.Core" Version="1.0.6" />
14+
<PackageVersion Include="ktsu.UndoRedo.Core" Version="1.0.6" />
1015
<PackageVersion Include="ktsu.RoundTripStringJsonConverter" Version="1.0.10" />
1116
<PackageVersion Include="ktsu.SemanticString" Version="1.4.0" />
1217
<PackageVersion Include="ktsu.Semantics.Strings" Version="1.0.35" />

Schema/Models/Schema.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,60 @@ public static bool TryGetChild<TName, TChild>(TName name, Collection<TChild> col
211211
return null;
212212
}
213213

214+
/// <summary>
215+
/// Restores a previously removed child back into a collection.
216+
/// Used for undo operations where the original object reference is preserved.
217+
/// </summary>
218+
/// <typeparam name="TChild">The type of the child.</typeparam>
219+
/// <typeparam name="TName">The type of the name.</typeparam>
220+
/// <param name="child">The child to restore.</param>
221+
/// <param name="collection">The collection to restore the child into.</param>
222+
/// <returns>True if the child was restored; false if a child with the same name already exists.</returns>
223+
public bool RestoreChild<TChild, TName>(TChild child, Collection<TChild> collection)
224+
where TChild : SchemaChild<TName>, new()
225+
where TName : SemanticString<TName>, ISchemaChildName, new()
226+
{
227+
Ensure.NotNull(child);
228+
Ensure.NotNull(collection);
229+
230+
if (GetChild(child.Name, collection) is not null)
231+
{
232+
return false;
233+
}
234+
235+
child.AssociateWith(this);
236+
collection.Add(child);
237+
return true;
238+
}
239+
240+
/// <summary>
241+
/// Restores a previously removed class back into the schema.
242+
/// </summary>
243+
/// <param name="schemaClass">The class to restore.</param>
244+
/// <returns>True if restored; false if a class with the same name already exists.</returns>
245+
public bool RestoreClass(SchemaClass schemaClass) => RestoreChild<SchemaClass, ClassName>(schemaClass, ClassesInternal);
246+
247+
/// <summary>
248+
/// Restores a previously removed enum back into the schema.
249+
/// </summary>
250+
/// <param name="schemaEnum">The enum to restore.</param>
251+
/// <returns>True if restored; false if an enum with the same name already exists.</returns>
252+
public bool RestoreEnum(SchemaEnum schemaEnum) => RestoreChild<SchemaEnum, EnumName>(schemaEnum, EnumsInternal);
253+
254+
/// <summary>
255+
/// Restores a previously removed data source back into the schema.
256+
/// </summary>
257+
/// <param name="dataSource">The data source to restore.</param>
258+
/// <returns>True if restored; false if a data source with the same name already exists.</returns>
259+
public bool RestoreDataSource(DataSource dataSource) => RestoreChild<DataSource, DataSourceName>(dataSource, DataSourcesInternal);
260+
261+
/// <summary>
262+
/// Restores a previously removed code generator back into the schema.
263+
/// </summary>
264+
/// <param name="codeGenerator">The code generator to restore.</param>
265+
/// <returns>True if restored; false if a code generator with the same name already exists.</returns>
266+
public bool RestoreCodeGenerator(SchemaCodeGenerator codeGenerator) => RestoreChild<SchemaCodeGenerator, CodeGeneratorName>(codeGenerator, CodeGeneratorsInternal);
267+
214268
internal bool TryRemoveEnum(SchemaEnum schemaEnum) => TryRemoveChild(schemaEnum, EnumsInternal);
215269

216270
internal bool TryRemoveClass(SchemaClass schemaClass) => TryRemoveChild(schemaClass, ClassesInternal);

Schema/Models/SchemaClass.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ public class SchemaClass : SchemaChild<ClassName>
6666
/// <returns>True if the member was removed; otherwise, false.</returns>
6767
internal bool TryRemoveMember(SchemaMember member) => MembersInternal.Remove(member);
6868

69+
/// <summary>
70+
/// Restores a previously removed member back into the class.
71+
/// Used for undo operations where the original object reference is preserved.
72+
/// </summary>
73+
/// <param name="member">The member to restore.</param>
74+
/// <returns>True if the member was restored; false if a member with the same name already exists.</returns>
75+
public bool RestoreMember(SchemaMember member)
76+
{
77+
Ensure.NotNull(member);
78+
79+
if (MembersInternal.Any(m => m.Name == member.Name))
80+
{
81+
return false;
82+
}
83+
84+
member.AssociateWith(this);
85+
MembersInternal.Add(member);
86+
return true;
87+
}
88+
6989
/// <summary>
7090
/// Tries to get a member by name.
7191
/// </summary>

0 commit comments

Comments
 (0)