Skip to content

Commit a825515

Browse files
committed
feat!: implement explicit dependency management with GitHub Actions integration
BREAKING CHANGE: nuget-prepare-extensions task now requires explicit --client-version parameter Major improvements: - Replace 500+ lines of duplicated NuGet task code with centralized PackageOperations service - Add explicit dependency switching via dedicated nuget-prepare-extensions task - Implement GitHub Actions output integration for version passing between workflow steps - Remove automatic conditional logic in favor of explicit task orchestration Changes: - feat(build): add PackageOperations service with PackSinglePackage() and PublishSinglePackage() methods - feat(build): add nuget-prepare-extensions task for explicit Extensions project dependency switching - feat(build): add --client-version parameter to BuildContext for explicit version passing - feat(ci): implement GitHub Actions output for LocalStack.Client version in pack operations - refactor(build): simplify NuGet tasks to single-line orchestration calls - refactor(ci): update CI/CD workflow to use explicit dependency preparation step - refactor(ci): update manual publishing workflow to use explicit prepare task - build: rename ci.yml to ci-cd.yml for clarity Technical details: - Eliminated ~500 lines of code duplication across 3 NuGet task files - Added GitHub Actions GITHUB_OUTPUT integration for version communication - Implemented proper separation of concerns between version generation and consumption - Maintained backward compatibility for all existing workflow parameters - Added comprehensive validation for required parameters This resolves the chicken-and-egg problem in CI/CD where Extensions package needed the exact version of LocalStack.Client that was just published to GitHub Packages.
1 parent fe499af commit a825515

File tree

14 files changed

+533
-471
lines changed

14 files changed

+533
-471
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ jobs:
156156
cat nuget.config
157157
158158
- name: "Pack & Publish LocalStack.Client"
159+
id: pack-client
159160
run: |
160161
echo "🔨 Building and publishing LocalStack.Client package..."
161162
./build.sh --target nuget-pack-and-publish \
@@ -165,6 +166,14 @@ jobs:
165166
--branch-name ${{ github.ref_name }} \
166167
--package-secret ${{ secrets.GITHUB_TOKEN }}
167168
169+
- name: "Prepare Extensions Project"
170+
run: |
171+
echo "🔧 Preparing Extensions project to use LocalStack.Client package..."
172+
./build.sh --target nuget-prepare-extensions \
173+
--package-source github \
174+
--package-id LocalStack.Client.Extensions \
175+
--client-version ${{ steps.pack-client.outputs.client-version }}
176+
168177
- name: "Pack & Publish LocalStack.Client.Extensions"
169178
run: |
170179
echo "🔨 Building and publishing LocalStack.Client.Extensions package..."

.github/workflows/publish-nuget.yml

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,13 @@ jobs:
8787
echo "🔧 Using GitHub-optimized nuget.config..."
8888
cp .github/nuget.config nuget.config
8989
90-
- name: "Remove Project Reference & Add Package Reference"
90+
- name: "Prepare Extensions Project"
9191
if: ${{ github.event.inputs.package-id == 'LocalStack.Client.Extensions' }}
9292
run: |
93-
cd src/LocalStack.Client.Extensions/
94-
95-
# Remove project reference
96-
dotnet remove reference ../LocalStack.Client/LocalStack.Client.csproj
97-
98-
# Add package reference based on target source
99-
if [ "${{ github.event.inputs.package-source }}" == "github" ]; then
100-
dotnet add package LocalStack.Client \
101-
--version ${{ github.event.inputs.package-version }} \
102-
--source github-packages
103-
else
104-
dotnet add package LocalStack.Client \
105-
--version ${{ github.event.inputs.package-version }}
106-
fi
93+
./build.sh --target nuget-prepare-extensions \
94+
--package-source ${{ github.event.inputs.package-source }} \
95+
--package-id ${{ github.event.inputs.package-id }} \
96+
--package-version ${{ github.event.inputs.package-version }}
10797
10898
- name: "Pack NuGet Package"
10999
run: |

build/LocalStack.Build/BuildContext.cs

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@ public sealed class BuildContext : FrostingContext
1111
public const string NuGetPackageSource = "nuget";
1212
public const string MyGetPackageSource = "myget";
1313

14+
// Cached package versions to ensure consistency across pack/publish operations
15+
private string? _clientPackageVersion;
16+
private string? _extensionsPackageVersion;
17+
1418
public BuildContext(ICakeContext context) : base(context)
1519
{
1620
BuildConfiguration = context.Argument("config", "Release");
1721
ForceBuild = context.Argument("force-build", defaultValue: false);
1822
ForceRestore = context.Argument("force-restore", defaultValue: false);
1923
PackageVersion = context.Argument("package-version", "x.x.x");
24+
ClientVersion = context.Argument("client-version", default(string));
2025
PackageId = context.Argument("package-id", default(string));
2126
PackageSecret = context.Argument("package-secret", default(string));
2227
PackageSource = context.Argument("package-source", GitHubPackageSource);
@@ -64,6 +69,8 @@ public BuildContext(ICakeContext context) : base(context)
6469

6570
public string PackageVersion { get; }
6671

72+
public string ClientVersion { get; }
73+
6774
public string PackageId { get; }
6875

6976
public string PackageSecret { get; }
@@ -98,6 +105,44 @@ public BuildContext(ICakeContext context) : base(context)
98105

99106
public ConvertableFilePath LocalStackClientExtProjFile { get; }
100107

108+
/// <summary>
109+
/// Gets the effective package version for LocalStack.Client package.
110+
/// This value is cached to ensure consistency across pack and publish operations.
111+
/// </summary>
112+
public string GetClientPackageVersion()
113+
{
114+
return _clientPackageVersion ??= UseDirectoryPropsVersion
115+
? GetDynamicVersionFromProps("PackageMainVersion")
116+
: PackageVersion;
117+
}
118+
119+
/// <summary>
120+
/// Gets the effective package version for LocalStack.Client.Extensions package.
121+
/// This value is cached to ensure consistency across pack and publish operations.
122+
/// </summary>
123+
public string GetExtensionsPackageVersion()
124+
{
125+
return _extensionsPackageVersion ??= UseDirectoryPropsVersion
126+
? GetDynamicVersionFromProps("PackageExtensionVersion")
127+
: PackageVersion;
128+
}
129+
130+
/// <summary>
131+
/// Gets the effective package version for the specified package ID.
132+
/// This method provides a unified interface for accessing cached package versions.
133+
/// </summary>
134+
/// <param name="packageId">The package ID (LocalStack.Client or LocalStack.Client.Extensions)</param>
135+
/// <returns>The cached package version</returns>
136+
public string GetEffectivePackageVersion(string packageId)
137+
{
138+
return packageId switch
139+
{
140+
LocalStackClientProjName => GetClientPackageVersion(),
141+
LocalStackClientExtensionsProjName => GetExtensionsPackageVersion(),
142+
_ => throw new ArgumentException($"Unknown package ID: {packageId}", nameof(packageId))
143+
};
144+
}
145+
101146
public static void ValidateArgument(string argumentName, string argument)
102147
{
103148
if (string.IsNullOrWhiteSpace(argument))
@@ -191,48 +236,6 @@ public void InstallMonoOnLinux()
191236
this.Information("✅ Mono installation completed successfully");
192237
}
193238

194-
public string GetProjectVersion()
195-
{
196-
if (UseDirectoryPropsVersion)
197-
{
198-
return GetDynamicVersionFromProps("PackageMainVersion");
199-
}
200-
201-
// Original logic for backward compatibility
202-
FilePath file = this.File("./src/Directory.Build.props");
203-
this.Information(file.FullPath);
204-
205-
string project = File.ReadAllText(file.FullPath, Encoding.UTF8);
206-
int startIndex = project.IndexOf("<Version>", StringComparison.Ordinal) + "<Version>".Length;
207-
int endIndex = project.IndexOf("</Version>", startIndex, StringComparison.Ordinal);
208-
209-
string version = project[startIndex..endIndex];
210-
version = $"{version}.{PackageVersion}";
211-
212-
return version;
213-
}
214-
215-
public string GetExtensionProjectVersion()
216-
{
217-
if (UseDirectoryPropsVersion)
218-
{
219-
return GetDynamicVersionFromProps("PackageExtensionVersion");
220-
}
221-
222-
// Original logic for backward compatibility
223-
FilePath file = this.File(LocalStackClientExtProjFile);
224-
this.Information(file.FullPath);
225-
226-
string project = File.ReadAllText(file.FullPath, Encoding.UTF8);
227-
int startIndex = project.IndexOf("<Version>", StringComparison.Ordinal) + "<Version>".Length;
228-
int endIndex = project.IndexOf("</Version>", startIndex, StringComparison.Ordinal);
229-
230-
string version = project[startIndex..endIndex];
231-
version = $"{version}.{PackageVersion}";
232-
233-
return version;
234-
}
235-
236239
/// <summary>
237240
/// Gets the target frameworks for a specific package using the existing proven method
238241
/// </summary>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[TaskName("build"), IsDependentOn(typeof(InitTask))]
2+
public sealed class BuildTask : FrostingTask<BuildContext>
3+
{
4+
public override void Run(BuildContext context)
5+
{
6+
context.DotNetBuild(context.SlnFilePath, new DotNetBuildSettings { Configuration = context.BuildConfiguration });
7+
}
8+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[TaskName("init")]
2+
public sealed class InitTask : FrostingTask<BuildContext>
3+
{
4+
public override void Run(BuildContext context)
5+
{
6+
ConsoleHelper.WriteRule("Initialization");
7+
ConsoleHelper.WriteHeader();
8+
9+
context.StartProcess("dotnet", new ProcessSettings { Arguments = "--info" });
10+
11+
if (!context.IsRunningOnUnix())
12+
{
13+
return;
14+
}
15+
16+
context.StartProcess("git", new ProcessSettings { Arguments = "config --global core.autocrlf true" });
17+
}
18+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using LocalStack.Build.CakeTasks.Nuget.Services;
2+
3+
[TaskName("nuget-pack-and-publish")]
4+
public sealed class NugetPackAndPublishTask : FrostingTask<BuildContext>
5+
{
6+
public override void Run(BuildContext context)
7+
{
8+
ConsoleHelper.WriteRule("Pack & Publish Pipeline");
9+
10+
string effectiveVersion = context.GetEffectivePackageVersion(context.PackageId);
11+
ConsoleHelper.WriteInfo($"Using consistent version: {effectiveVersion}");
12+
13+
ConsoleHelper.WriteProcessing("Step 1: Creating package...");
14+
PackageOperations.PackSinglePackage(context, context.PackageId);
15+
16+
ConsoleHelper.WriteProcessing("Step 2: Publishing package...");
17+
PackageOperations.PublishSinglePackage(context, context.PackageId);
18+
19+
ConsoleHelper.WriteSuccess("Pack & Publish pipeline completed successfully!");
20+
ConsoleHelper.WriteRule();
21+
}
22+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using LocalStack.Build.CakeTasks.Nuget.Services;
2+
3+
[TaskName("nuget-pack")]
4+
public sealed class NugetPackTask : FrostingTask<BuildContext>
5+
{
6+
public override void Run(BuildContext context)
7+
{
8+
// Display header
9+
ConsoleHelper.WriteRule("Package Creation");
10+
11+
// If no specific package ID is provided, pack all packages
12+
if (string.IsNullOrEmpty(context.PackageId))
13+
{
14+
PackAllPackages(context);
15+
}
16+
else
17+
{
18+
PackSinglePackage(context, context.PackageId);
19+
}
20+
21+
ConsoleHelper.WriteRule();
22+
}
23+
24+
private static void PackAllPackages(BuildContext context)
25+
{
26+
foreach (string packageId in context.PackageIdProjMap.Keys)
27+
{
28+
ConsoleHelper.WriteInfo($"Creating package: {packageId}");
29+
PackSinglePackage(context, packageId);
30+
}
31+
}
32+
33+
private static void PackSinglePackage(BuildContext context, string packageId)
34+
{
35+
PackageOperations.PackSinglePackage(context, packageId);
36+
}
37+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
[TaskName("nuget-prepare-extensions")]
2+
public sealed class NugetPrepareExtensionsTask : FrostingTask<BuildContext>
3+
{
4+
public override void Run(BuildContext context)
5+
{
6+
ConsoleHelper.WriteRule("Prepare Extensions Project");
7+
8+
// Validate that this is for Extensions package
9+
if (context.PackageId != BuildContext.LocalStackClientExtensionsProjName)
10+
{
11+
throw new InvalidOperationException($"This task is only for {BuildContext.LocalStackClientExtensionsProjName}, but received: {context.PackageId}");
12+
}
13+
14+
// Client version must be explicitly provided
15+
if (string.IsNullOrWhiteSpace(context.ClientVersion))
16+
{
17+
throw new InvalidOperationException("Client version must be specified via --client-version parameter. This task does not generate versions automatically.");
18+
}
19+
20+
ConsoleHelper.WriteInfo($"Preparing Extensions project for LocalStack.Client v{context.ClientVersion}");
21+
ConsoleHelper.WriteInfo($"Package source: {context.PackageSource}");
22+
23+
PrepareExtensionsProject(context, context.ClientVersion);
24+
25+
ConsoleHelper.WriteSuccess("Extensions project preparation completed!");
26+
ConsoleHelper.WriteRule();
27+
}
28+
29+
private static void PrepareExtensionsProject(BuildContext context, string version)
30+
{
31+
ConsoleHelper.WriteProcessing("Updating Extensions project dependencies...");
32+
33+
try
34+
{
35+
// Use the Extensions project file path directly
36+
string extensionsProject = context.LocalStackClientExtProjFile.Path.FullPath;
37+
var clientProjectRef = context.File(context.LocalStackClientProjFile.Path.FullPath);
38+
39+
// Remove project reference
40+
context.DotNetRemoveReference(extensionsProject, [clientProjectRef]);
41+
ConsoleHelper.WriteInfo("Removed project reference to LocalStack.Client");
42+
43+
// Add package reference with specific version and source
44+
var packageSettings = new DotNetPackageAddSettings
45+
{
46+
Version = version
47+
};
48+
49+
// Add source if not NuGet (GitHub Packages, MyGet, etc.)
50+
if (context.PackageSource != BuildContext.NuGetPackageSource)
51+
{
52+
packageSettings.Source = context.PackageSourceMap[context.PackageSource];
53+
ConsoleHelper.WriteInfo($"Using package source: {context.PackageSource}");
54+
}
55+
56+
context.DotNetAddPackage(BuildContext.LocalStackClientProjName, extensionsProject, packageSettings);
57+
ConsoleHelper.WriteSuccess($"Added package reference for {BuildContext.LocalStackClientProjName} v{version}");
58+
}
59+
catch (Exception ex)
60+
{
61+
ConsoleHelper.WriteError($"Failed to prepare Extensions project: {ex.Message}");
62+
throw;
63+
}
64+
}
65+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using LocalStack.Build.CakeTasks.Nuget.Services;
2+
3+
[TaskName("nuget-push")]
4+
public sealed class NugetPushTask : FrostingTask<BuildContext>
5+
{
6+
public override void Run(BuildContext context)
7+
{
8+
ConsoleHelper.WriteRule("Package Publishing");
9+
10+
PackageOperations.PublishSinglePackage(context, context.PackageId);
11+
12+
ConsoleHelper.WriteRule();
13+
}
14+
}

0 commit comments

Comments
 (0)