Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Coalesce Framework Development Instructions

Always follow these instructions first and fallback to additional search and context gathering only when the information here is incomplete or found to be in error.

## Project Overview

Coalesce is a framework for rapid development of ASP.NET Core + Vue.js web applications. It generates DTOs, API controllers, and TypeScript from Entity Framework Core models and other C# code.

## Prerequisites & Dependencies

The required tools and dependencies are automatically installed via the GitHub Actions workflow at `.github/workflows/copilot-setup-steps.yml`.

## Instructions

- Format PR titles with Semantic Commits. The work item number should follow the colon after the commit type like `feat: #12345 added ...`
- Always update the documentation when making changes or adding features that will affect developers who use Coalesce.
- Always add an entry to CHANGELOG.md when adding new features or fixing non-trivial bugs.
- Avoid making breaking changes if not necessary. A less obvious example of a breaking change would be changing an existing CSS class name.
- Consider adding or updating example files in `playground\Coalesce.Web.Vue3\src\examples` when making changes to coalesce-vue-vuetify.

## Validation Checklist

After making changes, ALWAYS run this validation sequence:

1. **Build verification**:
From the repo root:

```bash
npm ci
dotnet build
cd src/coalesce-vue && npm run build
cd ../coalesce-vue-vuetify3 && npm run build
```

2. **Test verification**:
From the repo root:

```bash
dotnet test
cd src/coalesce-vue && npm run test
cd ../coalesce-vue-vuetify3 && npm run test
```

3. **Template verification**:
If you make changes to the template in the `templates` directory, validate the changes by running `TestLocal.ps1 -- "--FeatureOne --FeatureTwo"` where the FeatureOne, FeatureTwo parameters are replaced with each flag that might affect the changes you made. Run it multiple times if there are different combinations of feature flags that might interact in different ways. The flags are the variables checked by the `#if` in the template code.

4. **Documentation verification**:
If the documentation was updated, run `npm run build` in the docs folder. You can expect the link checks to fail - you do not have access to the internet to validate the links.

REMEMBER: NEVER CANCEL long-running build operations.
50 changes: 50 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: "Copilot Setup Steps"

# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml

jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest

# Set the permissions to the lowest permissions possible needed for your steps.
# Copilot will be given its own token for its operations.
permissions:
contents: read

timeout-minutes: 30

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x

- name: Install JavaScript dependencies
run: npm ci

- name: Restore .NET packages
run: dotnet restore

- name: Build solution
run: dotnet build --no-restore --configuration Release

- name: Build coalesce-vue-vuetify3
working-directory: src/coalesce-vue-vuetify3
run: |
npm run build-local-deps
npm run build
26 changes: 25 additions & 1 deletion .github/workflows/part-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,38 @@ jobs:
- run: npm ci
working-directory: ${{ github.workspace }}

- run: npm run build
- name: Build docs (without linkcheck)
run: npm run build-only

- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: docs
path: docs/.vitepress/dist/Coalesce

linkcheck-docs:
runs-on: ubuntu-latest
needs: build-docs
defaults:
run:
shell: bash
working-directory: docs

steps:
- uses: actions/checkout@v4

- run: npm ci
# working-directory: ${{ github.workspace }}

- name: Download built docs
uses: actions/download-artifact@v4
with:
name: docs
path: docs/.vitepress/dist/Coalesce

- name: Run linkcheck
run: npm run linkcheck

validate-template:
runs-on: ubuntu-latest
name: "test template: ${{matrix.testCase}}"
Expand Down
9 changes: 7 additions & 2 deletions docs/check-links.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ let server;
try {
// Start static file server
server = createServer((req, res) =>
handler(req, res, { public: ".vitepress/dist" })
handler(req, res, { public: ".vitepress/dist" }),
);

await new Promise((resolve) => server.listen(8087, resolve));
Expand All @@ -32,7 +32,12 @@ try {
await waitOn({ resources: ["http://localhost:8087"], timeout: 30000 });

// Run linkcheck
await run("npm", ["run", "linkcheck"]);
await run("linkcheck", [
"localhost:8087/Coalesce",
"-e",
"--skip-file",
"./.vitepress/linkcheck-skip-file.txt",
]);

process.exit(0);
} catch (err) {
Expand Down
5 changes: 3 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"license": "Apache-2.0",
"scripts": {
"dev": "vitepress dev",
"linkcheck": "linkcheck localhost:8087/Coalesce -e --skip-file ./.vitepress/linkcheck-skip-file.txt",
"build": "cspell \"**/*.md\" && vitepress build --outDir .vitepress/dist/Coalesce && node check-links.mjs"
"build-only": "cspell \"**/*.md\" && vitepress build --outDir .vitepress/dist/Coalesce",
"linkcheck": "node check-links.mjs",
"build": "npm run build-only && npm run linkcheck"
},
"dependencies": {
"shiki": "1.4.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,9 @@ private static void WriteEtagProcessing(CSharpCodeBuilder b, MethodViewModel met
// in the querystring this means the client has prior knowledge
// about the current version via the VaryByProperty's value).

var clientCacheDurationSeconds = method.GetAttribute<ExecuteAttribute>().GetValue(a => a.ClientCacheDurationSeconds);
var clientCacheDurationSeconds = method
.GetAttribute<ExecuteAttribute>()
?.GetValue(a => a.ClientCacheDurationSeconds);
if (clientCacheDurationSeconds != null)
{
b.Line($"_cacheControlHeader.MaxAge = TimeSpan.FromSeconds({clientCacheDurationSeconds.Value});");
Expand Down
6 changes: 2 additions & 4 deletions src/IntelliTect.Coalesce/DataAnnotations/ExecuteAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,12 @@ public bool ValidateAttributes
/// Defaults to 30 days (2,592,000 seconds) if not specified.
/// </para>
/// </summary>
public int? ClientCacheDurationSeconds { get; set; }
public int ClientCacheDurationSeconds { get; set; }

/// <summary>
/// Gets the ClientCacheDuration as a TimeSpan based on ClientCacheDurationSeconds.
/// </summary>
public TimeSpan? ClientCacheDuration => ClientCacheDurationSeconds.HasValue
? TimeSpan.FromSeconds(ClientCacheDurationSeconds.Value)
: null;
public TimeSpan? ClientCacheDuration => TimeSpan.FromSeconds(ClientCacheDurationSeconds);
}

public enum HttpMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,5 @@ protected AttributeViewModel(ReflectionRepository? reflectionRepository)
return GetValue<T>(propertyExpression.GetExpressedProperty().Name);
}

public T? GetValue<T>(
Expression<Func<TAttribute, Nullable<T>>> propertyExpression,
Expression<Func<TAttribute, bool>>? hasValueExpression = null
)
where T : struct
{
// In reflection contexts, for attributes that can have "unset" as a value that means null,
// we have to check this state with an additional property. E.g. ExecuteAttribute.ValidateAttributes.
// In symbol contexts, the absence of syntax that assigns a value to the property implies this "unset" state.
if (hasValueExpression is not null)
{
// `hasValue` will always be non-null in reflection contexts,
// and always null in symbol contexts where the HasValue property won't ever be set.
var hasValue = GetValue<bool>(hasValueExpression.GetExpressedProperty().Name);
if (hasValue == false)
{
return null;
}
}


return GetValue<T>(propertyExpression.GetExpressedProperty().Name);
}

public abstract object? GetValue(string valueName);
}
Loading