Skip to content

Commit 2af7abe

Browse files
Complete comprehensive .github/copilot-instructions.md with validated commands and timing
Co-authored-by: michalJakubis <64188398+michalJakubis@users.noreply.github.com>
1 parent e778757 commit 2af7abe

36 files changed

+1930
-1763
lines changed

.github/copilot-instructions.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Xperience by Kentico Tag Manager
2+
3+
**Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.**
4+
5+
Xperience by Kentico Tag Manager is a .NET 8 class library that integrates with Xperience by Kentico CMS to enable marketers to include prebuilt and custom tags into websites. The project includes a sample DancingGoat application demonstrating the integration.
6+
7+
## Working Effectively
8+
9+
### Environment Setup
10+
- Install .NET SDK 8.0.411 exactly as specified in `global.json`:
11+
- `curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version 8.0.411`
12+
- `export PATH="$HOME/.dotnet:$PATH"`
13+
- Install Node.js 22.x for frontend builds (Client project requires >=22 <23):
14+
- `cd /tmp && wget https://nodejs.org/dist/v22.11.0/node-v22.11.0-linux-x64.tar.xz && tar -xf node-v22.11.0-linux-x64.tar.xz`
15+
- `export PATH="/tmp/node-v22.11.0-linux-x64/bin:$PATH"`
16+
- **CRITICAL**: Set both paths in environment: `export PATH="/tmp/node-v22.11.0-linux-x64/bin:$HOME/.dotnet:$PATH"`
17+
18+
### Build Commands - TIMING VERIFIED
19+
- **NEVER CANCEL BUILDS OR LONG-RUNNING COMMANDS** - Always use timeout of 600+ seconds.
20+
- `dotnet restore --locked-mode` -- takes ~2 seconds, NEVER CANCEL, timeout 300+ seconds
21+
- `dotnet build --configuration Release --no-restore` -- takes ~26 seconds with npm builds, NEVER CANCEL. Set timeout to 600+ seconds.
22+
- `dotnet build --configuration Debug` -- takes ~7 seconds for incremental builds, timeout 300+ seconds
23+
- `dotnet clean` -- takes ~1 second, quick cleanup command
24+
- `dotnet format src/Kentico.Xperience.TagManager/Kentico.Xperience.TagManager.csproj` -- fix code formatting, required before commits
25+
26+
### Test Commands - TIMING VERIFIED
27+
- `dotnet test --configuration Release --no-build --no-restore` -- takes ~2 seconds, but test project is currently empty
28+
- No functional tests are currently implemented
29+
30+
### Package Testing - TIMING VERIFIED
31+
To test local package changes:
32+
1. `dotnet pack ./src/Kentico.Xperience.TagManager -c Release -o nuget-local -p:SIGN_FILE=false` -- takes ~22 seconds, creates local NuGet package
33+
2. Update `Directory.Packages.props`: Set `Kentico.Xperience.TagManager` version to match the version in `Directory.Build.props` (currently 4.2.2)
34+
3. Update `nuget.config`: Uncomment `<package pattern="Kentico.Xperience.TagManager" />` in LocalPackages section
35+
4. `dotnet build -p:LOCAL_NUGET=true` -- takes ~7 seconds, builds solution using local package
36+
5. Verify DLL version in `examples/DancingGoat/bin/Debug/net8.0/` matches expected version
37+
6. **IMPORTANT**: Undo changes to `Directory.Packages.props` and `nuget.config` before committing
38+
39+
### Frontend Development - TIMING VERIFIED
40+
- Admin UI has two Node.js projects with different requirements:
41+
- `src/Kentico.Xperience.TagManager/Admin/Client/` (requires Node.js >=22 <23)
42+
- `src/Kentico.Xperience.TagManager/Admin/FrontEnd/` (requires Node.js >=20.11.0 <21)
43+
- Frontend builds are automatically triggered during .NET builds
44+
- Manual frontend commands (tested with Node 22):
45+
- `npm install` -- takes ~4 seconds in Client directory
46+
- `npm run build` -- takes ~7 seconds in Client directory
47+
- Webpack builds are integrated into .NET build process
48+
49+
## Validation
50+
51+
### Required Pre-Commit Validation
52+
- **CRITICAL**: `dotnet format src/Kentico.Xperience.TagManager/Kentico.Xperience.TagManager.csproj` -- MUST run before committing or CI will fail
53+
- Build succeeds: `dotnet build --configuration Release --no-restore`
54+
- Package creation works: `dotnet pack ./src/Kentico.Xperience.TagManager -c Release -o nuget-local -p:SIGN_FILE=false`
55+
56+
### Manual Testing Requirements
57+
- **CRITICAL**: Always test actual functionality after making changes, not just build success
58+
- Test scenario: Create a tag in the Tag Management UI, verify it renders on the DancingGoat sample site
59+
- The DancingGoat sample application requires a SQL Server database (see Database Setup below)
60+
- **VALIDATION SCENARIOS**: Test the tag manager by adding a Google Tag Manager snippet, saving it, and verifying it appears in page source
61+
62+
### Database Setup for DancingGoat Sample
63+
**Note**: The DancingGoat sample requires a full Xperience by Kentico database setup which is complex and may not be practical in all environments.
64+
- Requires SQL Server 2019+ compatible database
65+
- Follow [Xperience documentation](https://docs.xperience.io/xp26/developers-and-admins/installation#Installation-CreatetheprojectdatabaseCreateProjectDatabase) for database creation
66+
- Database setup is **not required** for library development and testing - only for full end-to-end validation
67+
68+
### Admin Development Mode
69+
For admin UI development, add to DancingGoat User Secrets:
70+
```json
71+
"CMSAdminClientModuleSettings": {
72+
"kentico-xperience-integrations-tagmanager": {
73+
"Mode": "Proxy",
74+
"Port": 3009
75+
}
76+
}
77+
```
78+
79+
## Common Tasks
80+
81+
### Repository Structure
82+
```
83+
.
84+
├── src/Kentico.Xperience.TagManager/ # Main library project
85+
│ ├── Admin/Client/ # React admin UI (Node 22+)
86+
│ ├── Admin/FrontEnd/ # Legacy admin frontend (Node 20+)
87+
│ ├── Snippets/ # Tag snippet factories
88+
│ └── Rendering/ # Tag rendering components
89+
├── examples/DancingGoat/ # Sample application
90+
├── tests/Kentico.Xperience.TagManager.Tests/ # Test project (empty)
91+
├── docs/ # Documentation
92+
├── .github/workflows/ci.yml # CI pipeline
93+
├── global.json # .NET SDK version (8.0.411)
94+
├── Directory.Build.props # Project properties
95+
├── Directory.Packages.props # NuGet package versions
96+
└── nuget.config # NuGet configuration
97+
```
98+
99+
### Key Files
100+
- `global.json` - Specifies exact .NET SDK version requirement (8.0.411)
101+
- `Directory.Build.props` - Contains version (4.2.2) and package metadata
102+
- `Directory.Packages.props` - Central package version management
103+
- `nuget.config` - Local package testing configuration
104+
- `.github/workflows/ci.yml` - Build and test automation
105+
- `.vscode/tasks.json` - VS Code build tasks
106+
107+
### VS Code Tasks (Alternative Commands)
108+
- `.NET: build (Solution)` - Standard build
109+
- `.NET: build (Solution) - LOCAL_NUGET` - Build with local package
110+
- `.NET: pack (TagManager)` - Create NuGet package
111+
- `.NET: format (TagManager)` - Format code
112+
- `npm: build - Admin/Client` - Build admin UI
113+
- `npm: build - Admin/FrontEnd` - Build legacy frontend
114+
115+
### Code Generation Commands (DancingGoat Only)
116+
If working with the DancingGoat sample content model:
117+
```bash
118+
cd examples/DancingGoat
119+
dotnet run --no-build -- --kxp-codegen --location "./Models/Schema/" --type ReusableFieldSchemas --namespace "DancingGoat.Models"
120+
dotnet run --no-build -- --kxp-codegen --location "./Models/Reusable/{name}/" --type ReusableContentTypes --include "DancingGoat.*" --namespace "DancingGoat.Models"
121+
dotnet run --no-build -- --kxp-codegen --location "./Models/WebPage/{name}/" --type PageContentTypes --include "DancingGoat.*" --namespace "DancingGoat.Models"
122+
```
123+
124+
### CI Pipeline Information
125+
- **Triggers**: Changes to .cs, .cshtml, .tsx, .js, .json, .csproj, .props, .targets, .sln files
126+
- **Commands**:
127+
1. `dotnet restore --locked-mode`
128+
2. `dotnet build --configuration Release --no-restore`
129+
3. `dotnet test --configuration Release --no-build --no-restore`
130+
- **Environment**: Ubuntu latest with PowerShell
131+
- Build failures typically indicate formatting issues - run `dotnet format` to fix
132+
133+
### Complete Development Workflow Example
134+
```bash
135+
# Setup environment
136+
export PATH="/tmp/node-v22.11.0-linux-x64/bin:$HOME/.dotnet:$PATH"
137+
cd /path/to/repo
138+
139+
# Make changes to code
140+
# ... edit files ...
141+
142+
# Format and build
143+
dotnet format src/Kentico.Xperience.TagManager/Kentico.Xperience.TagManager.csproj
144+
dotnet build --configuration Release --no-restore
145+
146+
# Test local package
147+
dotnet pack ./src/Kentico.Xperience.TagManager -c Release -o nuget-local -p:SIGN_FILE=false
148+
149+
# Update Directory.Packages.props version to 4.2.2
150+
# Uncomment package pattern in nuget.config
151+
dotnet build -p:LOCAL_NUGET=true
152+
153+
# Test DancingGoat if database available
154+
# ... manual testing ...
155+
156+
# Revert config changes before commit
157+
# Reset Directory.Packages.props and nuget.config
158+
```
159+
160+
### Important Notes
161+
- **TIMING**: Set timeouts of 600+ seconds for builds, 300+ seconds for other operations
162+
- **NODE VERSIONS**: Different frontend projects require different Node.js versions - ensure PATH is set correctly
163+
- **FORMATTING**: Always run `dotnet format` before committing - CI enforces formatting rules
164+
- **PACKAGE TESTING**: Local package testing requires manual configuration changes that must be reverted
165+
- **DATABASE**: Full sample application testing requires SQL Server database setup
166+
- **EMPTY TESTS**: Test project exists but contains no tests - manual validation is required
167+
168+
### Known Issues and Warnings (Non-Fatal)
169+
- Node.js version warnings during build (engines mismatch between projects) - these are non-fatal
170+
- Empty test project - no automated tests are implemented
171+
- Browserslist outdated warnings - can be ignored or fixed with `npx update-browserslist-db@latest`
172+
- Mixed line ending issues may require `dotnet format` to resolve
173+
- npm audit warnings about low severity vulnerabilities - can be ignored for development
Lines changed: 58 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,58 @@
1-
using CMS.ContentEngine;
2-
using CMS.DataProtection;
3-
4-
using Kentico.Xperience.Admin.Base.FormAnnotations;
5-
using Kentico.Xperience.Admin.Base.Forms;
6-
using Kentico.Xperience.TagManager.Admin.Components;
7-
using Kentico.Xperience.TagManager.Rendering;
8-
using Kentico.Xperience.TagManager.Snippets;
9-
10-
namespace Kentico.Xperience.TagManager.Admin;
11-
12-
internal class CodeSnippetConfigurationModel
13-
{
14-
[RequiredValidationRule]
15-
[TextInputComponent(Label = "Name", Order = 0)]
16-
public string Name { get; set; } = "";
17-
18-
[RequiredValidationRule]
19-
[ObjectIdSelectorComponent(objectType: ChannelInfo.OBJECT_TYPE, Label = "Channel", Order = 1, WhereConditionProviderType = typeof(ChannelSelectorWhereConditionProvider))]
20-
public IEnumerable<int> ChannelIDs { get; set; } = [];
21-
22-
[RequiredValidationRule]
23-
[TagManagerSnippetTypeDropdownComponent(Label = "Tag type", Order = 3)]
24-
public string? TagType { get; set; }
25-
26-
[CodeEditorComponent(Label = "Code", Order = 4, ExplanationTextAsHtml = true)]
27-
[VisibleIfEqualTo(nameof(TagType), CustomSnippetFactory.TAG_TYPE_NAME)]
28-
public string? Code { get; set; }
29-
30-
[RadioGroupComponent(Label = "Tag location", Order = 5, Options = CodeSnippetExtensions.LocationFormComponentOptions)]
31-
[VisibleIfEqualTo(nameof(TagType), CustomSnippetFactory.TAG_TYPE_NAME)]
32-
public string? Location { get; set; }
33-
34-
[TextInputComponent(Label = "Tag ID", Order = 4)]
35-
[VisibleIfNotEqualTo(nameof(TagType), CustomSnippetFactory.TAG_TYPE_NAME)]
36-
public string? TagIdentifier { get; set; }
37-
38-
[ObjectIdSelectorComponent(objectType: ConsentInfo.OBJECT_TYPE, Label = "Consent", Order = 6, Placeholder = "No consent needed")]
39-
public IEnumerable<int> ConsentIDs { get; set; } = [];
40-
41-
[CheckBoxComponent(Label = "Enable tag rendering", Order = 7)]
42-
public bool Enable { get; set; } = true;
43-
44-
[DropDownComponent(Label = "Kentico administration Display Mode", Options = CodeSnippetExtensions.DisplayModeFormComponentOptions, Order = 8)]
45-
public string DisplayMode { get; set; } = "None";
46-
47-
public void MapToChannelCodeSnippetInfo(ChannelCodeSnippetItemInfo info)
48-
{
49-
info.ChannelCodeSnippetItemChannelId = ChannelIDs.FirstOrDefault();
50-
info.ChannelCodeSnippetItemConsentId = ConsentIDs.FirstOrDefault();
51-
info.ChannelCodeSnippetItemLocation = Location;
52-
info.ChannelCodeSnippetItemType = TagType;
53-
info.ChannelCodeSnippetItemName = Name;
54-
info.ChannelCodeSnippetItemIdentifier = TagIdentifier;
55-
info.ChannelCodeSnippetItemCode = Code;
56-
info.ChannelCodeSnippetAdministrationDisplayMode = DisplayMode;
57-
info.ChannelCodeSnippetItemEnable = Enable;
58-
}
59-
}
1+
using CMS.ContentEngine;
2+
using CMS.DataProtection;
3+
4+
using Kentico.Xperience.Admin.Base.FormAnnotations;
5+
using Kentico.Xperience.Admin.Base.Forms;
6+
using Kentico.Xperience.TagManager.Admin.Components;
7+
using Kentico.Xperience.TagManager.Snippets;
8+
9+
namespace Kentico.Xperience.TagManager.Admin;
10+
11+
internal class CodeSnippetConfigurationModel
12+
{
13+
[RequiredValidationRule]
14+
[TextInputComponent(Label = "Name", Order = 0)]
15+
public string Name { get; set; } = "";
16+
17+
[RequiredValidationRule]
18+
[ObjectIdSelectorComponent(objectType: ChannelInfo.OBJECT_TYPE, Label = "Channel", Order = 1, WhereConditionProviderType = typeof(ChannelSelectorWhereConditionProvider))]
19+
public IEnumerable<int> ChannelIDs { get; set; } = [];
20+
21+
[RequiredValidationRule]
22+
[TagManagerSnippetTypeDropdownComponent(Label = "Tag type", Order = 3)]
23+
public string? TagType { get; set; }
24+
25+
[CodeEditorComponent(Label = "Code", Order = 4, ExplanationTextAsHtml = true)]
26+
[VisibleIfEqualTo(nameof(TagType), CustomSnippetFactory.TAG_TYPE_NAME)]
27+
public string? Code { get; set; }
28+
29+
[RadioGroupComponent(Label = "Tag location", Order = 5, Options = CodeSnippetExtensions.LocationFormComponentOptions)]
30+
[VisibleIfEqualTo(nameof(TagType), CustomSnippetFactory.TAG_TYPE_NAME)]
31+
public string? Location { get; set; }
32+
33+
[TextInputComponent(Label = "Tag ID", Order = 4)]
34+
[VisibleIfNotEqualTo(nameof(TagType), CustomSnippetFactory.TAG_TYPE_NAME)]
35+
public string? TagIdentifier { get; set; }
36+
37+
[ObjectIdSelectorComponent(objectType: ConsentInfo.OBJECT_TYPE, Label = "Consent", Order = 6, Placeholder = "No consent needed")]
38+
public IEnumerable<int> ConsentIDs { get; set; } = [];
39+
40+
[CheckBoxComponent(Label = "Enable tag rendering", Order = 7)]
41+
public bool Enable { get; set; } = true;
42+
43+
[DropDownComponent(Label = "Kentico administration Display Mode", Options = CodeSnippetExtensions.DisplayModeFormComponentOptions, Order = 8)]
44+
public string DisplayMode { get; set; } = "None";
45+
46+
public void MapToChannelCodeSnippetInfo(ChannelCodeSnippetItemInfo info)
47+
{
48+
info.ChannelCodeSnippetItemChannelId = ChannelIDs.FirstOrDefault();
49+
info.ChannelCodeSnippetItemConsentId = ConsentIDs.FirstOrDefault();
50+
info.ChannelCodeSnippetItemLocation = Location;
51+
info.ChannelCodeSnippetItemType = TagType;
52+
info.ChannelCodeSnippetItemName = Name;
53+
info.ChannelCodeSnippetItemIdentifier = TagIdentifier;
54+
info.ChannelCodeSnippetItemCode = Code;
55+
info.ChannelCodeSnippetAdministrationDisplayMode = DisplayMode;
56+
info.ChannelCodeSnippetItemEnable = Enable;
57+
}
58+
}
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
using Kentico.Xperience.TagManager.Rendering;
2-
3-
namespace Kentico.Xperience.TagManager.Admin;
4-
5-
internal static class CodeSnippetExtensions
6-
{
7-
public const string LocationFormComponentOptions = $"{nameof(CodeSnippetLocations.HeadTop)};Insert at the top of the head\r\n" +
8-
$"{nameof(CodeSnippetLocations.HeadBottom)};Insert at the bottom of the head\r\n" +
9-
$"{nameof(CodeSnippetLocations.BodyTop)};Insert at the top of the body\r\n" +
10-
$"{nameof(CodeSnippetLocations.BodyBottom)};Insert at the bottom of the body\r\n";
11-
12-
public const string DisplayModeFormComponentOptions = $"{nameof(CodeSnippetAdministrationDisplayMode.None)};Do not display in Administration\r\n" +
13-
$"{nameof(CodeSnippetAdministrationDisplayMode.PreviewOnly)};Display in the Preview view mode only\r\n" +
14-
$"{nameof(CodeSnippetAdministrationDisplayMode.PageBuilderOnly)};Display in the Page Builder only\r\n" +
15-
$"{nameof(CodeSnippetAdministrationDisplayMode.Both)};Display in the Preview view mode and the Page Builder";
16-
}
1+
using Kentico.Xperience.TagManager.Rendering;
2+
3+
namespace Kentico.Xperience.TagManager.Admin;
4+
5+
internal static class CodeSnippetExtensions
6+
{
7+
public const string LocationFormComponentOptions = $"{nameof(CodeSnippetLocations.HeadTop)};Insert at the top of the head\r\n" +
8+
$"{nameof(CodeSnippetLocations.HeadBottom)};Insert at the bottom of the head\r\n" +
9+
$"{nameof(CodeSnippetLocations.BodyTop)};Insert at the top of the body\r\n" +
10+
$"{nameof(CodeSnippetLocations.BodyBottom)};Insert at the bottom of the body\r\n";
11+
12+
public const string DisplayModeFormComponentOptions = $"{nameof(CodeSnippetAdministrationDisplayMode.None)};Do not display in Administration\r\n" +
13+
$"{nameof(CodeSnippetAdministrationDisplayMode.PreviewOnly)};Display in the Preview view mode only\r\n" +
14+
$"{nameof(CodeSnippetAdministrationDisplayMode.PageBuilderOnly)};Display in the Page Builder only\r\n" +
15+
$"{nameof(CodeSnippetAdministrationDisplayMode.Both)};Display in the Preview view mode and the Page Builder";
16+
}

0 commit comments

Comments
 (0)