Skip to content

Commit d652577

Browse files
authored
Adding some more copilot customisations (#742)
1 parent d13d43d commit d652577

File tree

4 files changed

+283
-0
lines changed

4 files changed

+283
-0
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
---
2+
description: "This chat mode is to create a new .NET Aspire hosting integration, using the design in the Community Toolkit repo."
3+
tools: [
4+
"changes",
5+
"codebase",
6+
"editFiles",
7+
"fetch",
8+
"new",
9+
"problems",
10+
"runCommands",
11+
"runTasks",
12+
"search",
13+
"searchResults",
14+
"usages",
15+
]
16+
---
17+
18+
# New Aspire Hosting Integration
19+
20+
You are going to develop a new .NET Aspire hosting integration. The following in the process in which you need to go through to complete the task.
21+
22+
## 1. Collect Requirements
23+
24+
You are going to need to know:
25+
26+
- The name of the hosting integration.
27+
- The description of the hosting integration.
28+
29+
This will be important to ensure that the hosting integration is created correctly.
30+
31+
Ideally, the user should provide a URL for the docs on how to run the tool that the hosting integration is for, using the `fetch` tool, so that you can use it to understand how the tool works and how to implement the hosting integration.
32+
33+
## 2. Scaffold the C# project
34+
35+
Start by creating a new C# class library project in the `src` folder. The project should be named `CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>`, where `<HostingIntegrationName>` is the name of the hosting integration you are creating.
36+
37+
It can be created using the following command:
38+
39+
```bash
40+
dotnet new classlib -n CommunityToolkit.Aspire.Hosting.<HostingIntegrationName> -o src/CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>
41+
```
42+
43+
Once created the `csproj` file can be stripped back to just the following minimal starting point:
44+
45+
```xml
46+
<Project Sdk="Microsoft.NET.Sdk">
47+
48+
<PropertyGroup>
49+
<AdditionalPackageTags>hosting $HostingIntegrationName</AdditionalPackageTags>
50+
<Description>$HostingIntegrationDescription.</Description>
51+
</PropertyGroup>
52+
53+
</Project>
54+
```
55+
56+
Also create an empty `README.md` file in the root of the project with the following content:
57+
58+
```markdown
59+
# CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>
60+
61+
TODO: Add a description of the hosting integration.
62+
```
63+
64+
Ensure that the project is added to the solution file, which is in the repo root, and can be done using:
65+
66+
```bash
67+
dotnet sln add src/CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>
68+
```
69+
70+
## 3. Implement the Hosting Integration
71+
72+
An integration consists of two main parts, a `Resource` implementation, and extension methods for adding it to the `IDistributedApplicationBuilder`.
73+
74+
### 3.1 Create the Resource
75+
76+
There are multiple choices for the `Resource` implementation. Using the knowledge of the thing to be hosted, you can choose the most appropriate from the list:
77+
78+
- `ExecutableResource` - For running a local executable (e.g. Node.js, Python, Rust, etc.)
79+
- `ContainerResource` - For running a container image using Docker.
80+
- `Resource` - For running a generic resource that does not fit into the above categories.
81+
82+
Here is an example of how to implement a `ContainerResource`:
83+
84+
```csharp
85+
namespace Aspire.Hosting.ApplicationModel;
86+
87+
/// <summary>
88+
/// A resource that represents an Ollama container.
89+
/// </summary>
90+
/// <remarks>
91+
/// Constructs an <see cref="OllamaResource"/>.
92+
/// </remarks>
93+
/// <param name="name">The name for the resource.</param>
94+
public class OllamaResource(string name) : ContainerResource(name), IResourceWithConnectionString
95+
{
96+
internal const string OllamaEndpointName = "http";
97+
98+
private readonly List<string> _models = [];
99+
100+
private EndpointReference? _primaryEndpointReference;
101+
102+
/// <summary>
103+
/// Adds a model to the list of models to download on initial startup.
104+
/// </summary>
105+
public IReadOnlyList<string> Models => _models;
106+
107+
/// <summary>
108+
/// Gets the endpoint for the Ollama server.
109+
/// </summary>
110+
public EndpointReference PrimaryEndpoint => _primaryEndpointReference ??= new(this, OllamaEndpointName);
111+
112+
/// <summary>
113+
/// Gets the connection string expression for the Ollama server.
114+
/// </summary>
115+
public ReferenceExpression ConnectionStringExpression =>
116+
ReferenceExpression.Create(
117+
$"Endpoint={PrimaryEndpoint.Property(EndpointProperty.Scheme)}://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"
118+
);
119+
120+
/// <summary>
121+
/// Adds a model to the list of models to download on initial startup.
122+
/// </summary>
123+
/// <param name="modelName">The name of the model</param>
124+
public void AddModel(string modelName)
125+
{
126+
ArgumentException.ThrowIfNullOrEmpty(modelName, nameof(modelName));
127+
if (!_models.Contains(modelName))
128+
{
129+
_models.Add(modelName);
130+
}
131+
}
132+
}
133+
```
134+
135+
Resources can also have endpoints, which are used to connect to the resource, they can also expose a connection string expression, which is used to connect to the resource in a more generic way.
136+
137+
The following requirements **must** be met when implementing a resource:
138+
139+
- Namespace: `Aspire.Hosting.ApplicationModel`.
140+
- Class name: `<HostingIntegrationName>Resource`.
141+
- Inherits from `Resource`, `ContainerResource`, or `ExecutableResource`.
142+
- Public constructor that takes a `string name` parameter.
143+
- If the resource has a connection string, it must implement `IResourceWithConnectionString`.
144+
- If the resource has endpoints, it must implement `IResourceWithEndpoints`.
145+
- Public methods, properties, and events should be documented with XML comments.
146+
147+
### 3.2 Create the Extension Methods
148+
149+
Next, you need to create extension methods for the `IDistributedApplicationBuilder` to add the resource to the application. This is done by creating a static class with a method that takes an `IDistributedApplicationBuilder` and returns an `IResourceBuilder<T>`.
150+
151+
Here is an example of how to implement the extension method for the `OllamaResource`:
152+
153+
```csharp
154+
namespace Aspire.Hosting;
155+
156+
/// <summary>
157+
/// Extension methods for adding a Bun app to a <see cref="IDistributedApplicationBuilder"/>.
158+
/// </summary>
159+
public static class BunAppExtensions
160+
{
161+
/// <summary>
162+
/// Adds a Bun app to the builder.
163+
/// </summary>
164+
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/> to add the resource to.</param>
165+
/// <param name="name">The name of the resource.</param>
166+
/// <param name="workingDirectory">The working directory.</param>
167+
/// <param name="entryPoint">The entry point, either a file or package.json script name.</param>
168+
/// <param name="watch">Whether to watch for changes.</param>
169+
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
170+
public static IResourceBuilder<BunAppResource> AddBunApp(
171+
this IDistributedApplicationBuilder builder,
172+
[ResourceName] string name,
173+
string? workingDirectory = null,
174+
string entryPoint = "index.ts",
175+
bool watch = false)
176+
{
177+
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
178+
ArgumentException.ThrowIfNullOrEmpty(name, nameof(name));
179+
ArgumentException.ThrowIfNullOrEmpty(entryPoint, nameof(entryPoint));
180+
181+
workingDirectory ??= Path.Combine("..", name);
182+
183+
var resource = new BunAppResource(name, PathNormalizer.NormalizePathForCurrentPlatform(Path.Combine(builder.AppHostDirectory, workingDirectory)));
184+
185+
string[] args = watch ? ["--watch", "run", entryPoint] : ["run", entryPoint];
186+
187+
return builder.AddResource(resource)
188+
.WithBunDefaults()
189+
.WithArgs(args);
190+
}
191+
}
192+
```
193+
194+
The extension method should meet the following requirements:
195+
196+
- Namespace: `Aspire.Hosting`.
197+
- Class name: `<HostingIntegrationName>Extensions`.
198+
- Static class with a static method.
199+
- Method name: `Add<HostingIntegrationName>`.
200+
- Method parameters:
201+
- `IDistributedApplicationBuilder builder` - The builder to add the resource to.
202+
- `string name` - The name of the resource, decorated with `[ResourceName]`.
203+
- Additional parameters as required by the resource.
204+
- Returns an `IResourceBuilder<T>` where `T` is the resource type.
205+
- The method should call `builder.AddResource(resource)` to add the resource to the builder.
206+
- Perform `ArgumentNullException.ThrowIfNull` and `ArgumentException.ThrowIfNullOrEmpty` checks on the parameters.
207+
208+
## 4. Sample Usage
209+
210+
You need to create a sample usage of the hosting integration in the `examples` folder. This should be a minimal example that demonstrates how to use the hosting integration in a .NET Aspire application.
211+
212+
Start by scaffolding a new .NET Aspire App Host project in the `examples` folder. This can be done using the following command:
213+
214+
```bash
215+
dotnet new aspire-apphost -n CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>.AppHost -o examples/CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>.AppHost
216+
```
217+
218+
Make sure to add the project to the solution with `dotnet sln add examples/CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>.AppHost`.
219+
220+
Once created, refer to existing AppHost `csproj` files to ensure that the right packages are referenced, such as `CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>`. For the `Sdk`, ensure the version is `$(AspireAppHostSdkVersion)`, since we use a MSBuild variable to ensure that a consistent version is used across all App Host projects. Any `PackageReference` elements should **not** have a version specified.
221+
222+
Next, edit the `AppHost.cs` file that the template created to use the hosting integration.
223+
224+
Here is an example of how to use the `BunResource` in the `AppHost.cs` file:
225+
226+
```csharp
227+
var builder = DistributedApplication.CreateBuilder(args);
228+
229+
var api = builder.AddBunApp("api")
230+
.WithBunPackageInstallation()
231+
.WithHttpEndpoint(env: "PORT")
232+
.WithHttpHealthCheck("/");
233+
234+
builder.Build().Run();
235+
```
236+
237+
Ensure that the example is a minimal working example that can be run using the `dotnet run` command.
238+
239+
## 5. Tests
240+
241+
You need to create tests for the hosting integration. This should include unit tests for the resource implementation and integration tests for the extension methods.
242+
243+
The tests should be placed in a new test project in the `tests` folder. The project should be named `CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>.Tests`, and can be created using the following command:
244+
245+
```bash
246+
dotnet new xunit -n CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>.Tests -o tests/CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>.Tests
247+
```
248+
249+
Make sure to add the project to the solution with `dotnet sln add examples/CommunityToolkit.Aspire.Hosting.<HostingIntegrationName>.AppHost`.
250+
251+
Ensure that the test project references the hosting integration project and any necessary Aspire packages. Refer to other test projects in the `tests` folder for guidance on how to set up the project file.
252+
253+
Once the project is created, you can start writing tests for the resource implementation and extension methods. Ensure that the tests cover all public methods and properties of the resource, as well as the extension methods.
254+
255+
## 6. Documentation
256+
257+
Once the integration is implemented, you need to update the `README.md` file in the hosting integration project to include a description of the hosting integration, how to use it, and any other relevant information.
258+
259+
Also, update the root `README.md` file of the repo to include a link to the new hosting integration in the table that exists.

.github/copilot-instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
- Use file-scoped namespaces.
88
- All public members require doc comments.
99
- Prefer type declarations over `var` when the type isn't obvious.
10+
- Use the C# Collection Initializer syntax, `List<T> items = []` (where `List<T>` could be any collection type), rather than `new()`.
11+
- Use `is not null` or `is null` over `!= null` and `== null`.
1012

1113
## Sample hosting integration
1214

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
mode: agent
3+
tools: ['githubRepo', 'github', 'get_issue', 'get_issue_comments', 'get_me', 'list_issues']
4+
---
5+
6+
Search the current repo (using #githubRepo for the repo info) and list any issues you find (using #list_issues) that are assigned to me.
7+
8+
Suggest issues that I might want to focus on based on their age, the amount of comments, and their status (open/closed).
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
mode: agent
3+
tools: ['githubRepo', 'github', 'get_me', 'get_pull_request', 'get_pull_request_comments', 'get_pull_request_diff', 'get_pull_request_files', 'get_pull_request_reviews', 'get_pull_request_status', 'list_pull_requests', 'request_copilot_review']
4+
---
5+
6+
Search the current repo (using #githubRepo for the repo info) and list any pull requests you find (using #list_pull_requests) that are assigned to me.
7+
8+
Describe the purpose and details of each pull request.
9+
10+
If a PR is waiting for someone to review, highlight that in the response.
11+
12+
If there were any check failures on the PR, describe them and suggest possible fixes.
13+
14+
If there was no review done by Copilot, offer to request one using #request_copilot_review.

0 commit comments

Comments
 (0)