Skip to content

Commit b4e9068

Browse files
hifi-philPhil Whittakerclaude
authored
Add universal media upload tools with URL and base64 support (#31)
* Update create-temporary-file to accept base64 encoded data - Changed schema from ReadStream to base64 string input for MCP compatibility - Converts base64 → Buffer → temp file → ReadStream for Umbraco API - Uses os.tmpdir() for temporary file storage - Automatic cleanup of temp files in finally block - Updated tests to use base64 encoding - All tests passing (11/11) This makes the tool compatible with LLM/MCP usage where files are provided as base64 strings rather than file system streams. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Add editorAlias and entityType to media value objects - Updated media-builder to include editorAlias and entityType in image values - Fixed focalPoint to use correct properties (left, top) - Changed temporaryFileId property name (was temporaryFilId) - Added documentation to create-media tool with complete example - Documented API quirk in docs/comments.md - Added experimental test-file-format tool for testing file upload formats These fields are required by the Umbraco API but not documented in the OpenAPI spec. Without them, media items are created but files are not properly uploaded/attached. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Add universal media upload tools with URL and base64 support - Add create-media tool supporting filePath, URL, and base64 sources - Add create-media-multiple tool for batch uploads (max 20 files) - Implement automatic MIME type detection using mime-types library - Add comprehensive media upload helpers with proper error handling - Fix extension handling: only add to temp files, not media item names - Add test infrastructure including builders and helpers - Add integration tests with snapshot testing - Support all media types: Image, File, Video, Audio, SVG, etc. Technical improvements: - Use mime-types library for robust MIME type to extension mapping - Proper temp file cleanup after uploads - SVG media type auto-correction (Image → Vector Graphic) - Continue-on-error strategy for batch uploads - Comprehensive test coverage with proper cleanup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Remove obsolete TEST_FILE_FORMAT_README.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Remove obsolete test-file-format.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Phil Whittaker <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent bf7e116 commit b4e9068

31 files changed

+1338
-163
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Universal Media Upload Implementation
2+
3+
## Overview
4+
5+
The media upload system has been redesigned to provide a unified, simplified interface for uploading any type of media file to Umbraco. This simplifies the standard two-step process (create temporary file → create media) with a single tool call that handles all media types.
6+
7+
## Architecture Decision: Single Universal Tool
8+
9+
Instead of creating separate tools for each media type (create-image, create-pdf, create-video, etc.), we implemented a **single universal tool** that:
10+
- Accepts any media type via an explicit `mediaTypeName` parameter
11+
- Trusts the LLM to specify the correct media type based on context
12+
- Only validates SVG files (the one exception where file type matters for technical reasons)
13+
- Supports custom media types created in Umbraco via dynamic API lookup
14+
15+
### Why Trust the LLM?
16+
17+
**Advantages:**
18+
- ✅ LLMs understand semantic context better than file extensions
19+
- ✅ Simpler implementation - no complex extension mapping tables
20+
- ✅ Works seamlessly with custom media types
21+
- ✅ Explicit is better than implicit
22+
- ✅ Dynamic lookup ensures compatibility with any Umbraco installation
23+
24+
**The Only Exception - SVG:**
25+
- SVGs can be mistaken for images by LLMs
26+
- File extension check is simple and reliable for this one case
27+
- Auto-correct prevents technical errors (SVG uploaded as "Image" type fails in Umbraco)
28+
29+
## Tools Implemented
30+
31+
### 1. create-media
32+
33+
**Purpose:** Upload any single media file to Umbraco
34+
35+
**Schema:**
36+
```typescript
37+
{
38+
sourceType: "filePath" | "url" | "base64",
39+
name: string,
40+
mediaTypeName: string, // Required: explicit media type
41+
filePath?: string, // Required if sourceType = "filePath"
42+
fileUrl?: string, // Required if sourceType = "url"
43+
fileAsBase64?: string, // Required if sourceType = "base64"
44+
parentId?: string // Optional: parent folder UUID
45+
}
46+
```
47+
48+
**Supported Media Types:**
49+
- **Image** - jpg, png, gif, webp (supports cropping features)
50+
- **Article** - pdf, docx, doc
51+
- **Audio** - mp3, wav, etc.
52+
- **Video** - mp4, webm, etc.
53+
- **Vector Graphic (SVG)** - svg files only
54+
- **File** - any other file type
55+
- **Custom** - any custom media type name created in Umbraco
56+
57+
**Source Types:**
58+
1. **filePath** - Most efficient, zero token overhead, works with any size file
59+
2. **url** - Fetch from web URL
60+
3. **base64** - Only for small files (<10KB) due to token usage
61+
62+
**Example Usage:**
63+
```typescript
64+
// Upload an image from local filesystem
65+
{
66+
sourceType: "filePath",
67+
name: "Product Photo",
68+
mediaTypeName: "Image",
69+
filePath: "/path/to/image.jpg"
70+
}
71+
72+
// Upload a PDF from URL
73+
{
74+
sourceType: "url",
75+
name: "Annual Report",
76+
mediaTypeName: "Article",
77+
fileUrl: "https://example.com/report.pdf"
78+
}
79+
80+
// Upload small image as base64
81+
{
82+
sourceType: "base64",
83+
name: "Icon",
84+
mediaTypeName: "Image",
85+
fileAsBase64: "iVBORw0KGgoAAAANS..."
86+
}
87+
```
88+
89+
### 2. create-media-multiple
90+
91+
**Purpose:** Batch upload multiple media files (maximum 20 per batch)
92+
93+
**Schema:**
94+
```typescript
95+
{
96+
sourceType: "filePath" | "url", // No base64 for batch uploads
97+
files: Array<{
98+
name: string,
99+
filePath?: string,
100+
fileUrl?: string,
101+
mediaTypeName?: string // Optional per-file override, defaults to "File"
102+
}>,
103+
parentId?: string // Optional: parent folder for all files
104+
}
105+
```
106+
107+
**Features:**
108+
- Sequential processing to avoid API overload
109+
- Continue-on-error strategy - individual failures don't stop the batch
110+
- Returns detailed results per file with success/error status
111+
- Validates 20-file batch limit
112+
113+
**Example Usage:**
114+
```typescript
115+
{
116+
sourceType: "filePath",
117+
files: [
118+
{ name: "Photo 1", filePath: "/path/to/photo1.jpg", mediaTypeName: "Image" },
119+
{ name: "Photo 2", filePath: "/path/to/photo2.jpg", mediaTypeName: "Image" },
120+
{ name: "Document", filePath: "/path/to/doc.pdf", mediaTypeName: "Article" }
121+
],
122+
parentId: "parent-folder-id"
123+
}
124+
```
125+

package-lock.json

Lines changed: 10 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"axios": "^1.8.4",
5454
"dotenv": "^16.5.0",
5555
"form-data": "^4.0.4",
56+
"mime-types": "^3.0.1",
5657
"qs": "^6.14.0",
5758
"uuid": "^11.1.0",
5859
"yargs": "^18.0.0",
@@ -61,6 +62,7 @@
6162
"devDependencies": {
6263
"@types/dotenv": "^6.1.1",
6364
"@types/jest": "^29.5.14",
65+
"@types/mime-types": "^3.0.1",
6466
"@types/node": "^22.14.1",
6567
"@types/qs": "^6.9.18",
6668
"copyfiles": "^2.4.1",

src/helpers/mcp/create-umbraco-tool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { CurrentUserResponseModel } from "@/umb-management-api/schemas/index.js"
66
export const CreateUmbracoTool =
77
<Args extends undefined | ZodRawShape = any>(
88
name: string,
9-
description: string,
9+
description: string,
1010
schema: Args,
1111
handler: ToolCallback<Args>,
1212
enabled?: (user: CurrentUserResponseModel) => boolean

src/test-helpers/create-snapshot-result.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ export function createSnapshotResult(result: any, idToReplace?: string) {
8989
url.replace(/\/[a-f0-9]{40}\.jpg/, "/NORMALIZED_AVATAR.jpg")
9090
);
9191
}
92+
// Normalize media URLs that contain dynamic path segments
93+
if (item.urlInfos && Array.isArray(item.urlInfos)) {
94+
item.urlInfos = item.urlInfos.map((urlInfo: any) => ({
95+
...urlInfo,
96+
url: urlInfo.url ? urlInfo.url.replace(/\/media\/[a-z0-9]+\//i, "/media/NORMALIZED_PATH/") : urlInfo.url
97+
}));
98+
}
9299
return item;
93100
}
94101

@@ -138,6 +145,13 @@ export function createSnapshotResult(result: any, idToReplace?: string) {
138145
url.replace(/\/[a-f0-9]{40}\.jpg/, "/NORMALIZED_AVATAR.jpg")
139146
);
140147
}
148+
// Normalize media URLs that contain dynamic path segments
149+
if (parsed.urlInfos && Array.isArray(parsed.urlInfos)) {
150+
parsed.urlInfos = parsed.urlInfos.map((urlInfo: any) => ({
151+
...urlInfo,
152+
url: urlInfo.url ? urlInfo.url.replace(/\/media\/[a-z0-9]+\//i, "/media/NORMALIZED_PATH/") : urlInfo.url
153+
}));
154+
}
141155
// Normalize document version references
142156
if (parsed.document) {
143157
parsed.document = { ...parsed.document, id: BLANK_UUID };
@@ -168,10 +182,11 @@ export function createSnapshotResult(result: any, idToReplace?: string) {
168182
// For list responses
169183
const parsed = JSON.parse(item.text);
170184
if (Array.isArray(parsed)) {
171-
// Handle ancestors API response
185+
// Handle ancestors API response and other array responses
186+
const normalized = parsed.map(normalizeItem);
172187
return {
173188
...item,
174-
text: JSON.stringify(parsed.map(normalizeItem)),
189+
text: JSON.stringify(normalized),
175190
};
176191
}
177192
// Handle other list responses

src/umb-management-api/tools/indexer/__tests__/__snapshots__/get-indexer-by-index-name.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exports[`get-indexer-by-index-name should get index by name 1`] = `
44
{
55
"content": [
66
{
7-
"text": "{"name":"ExternalIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"ExternalSearcher","documentCount":62,"fieldCount":50,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/externalindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/externalindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":true,"SupportProtectedContent":false}}",
7+
"text": "{"name":"ExternalIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"ExternalSearcher","documentCount":72,"fieldCount":49,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/externalindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/externalindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":true,"SupportProtectedContent":false}}",
88
"type": "text",
99
},
1010
],

src/umb-management-api/tools/indexer/__tests__/__snapshots__/get-indexer.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exports[`get-indexer should list all indexes with default parameters 1`] = `
44
{
55
"content": [
66
{
7-
"text": "{"total":6,"items":[{"name":"DeliveryApiContentIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"DeliveryApiContentSearcher","documentCount":39,"fieldCount":20,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/deliveryapicontentindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/deliveryapicontentindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":false,"PublishedValuesOnly":false}},{"name":"ExternalIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"ExternalSearcher","documentCount":62,"fieldCount":50,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/externalindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/externalindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":true,"SupportProtectedContent":false}},{"name":"InternalIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"InternalSearcher","documentCount":74,"fieldCount":50,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"CultureInvariantWhitespaceAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/internalindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/internalindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":false,"SupportProtectedContent":true}},{"name":"MembersIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"MembersSearcher","documentCount":2,"fieldCount":9,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"CultureInvariantWhitespaceAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/membersindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/membersindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":false,"IncludeFields":["id","nodeName","updateDate","loginName","email","__Key"]}},{"name":"PDFIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"PDFSearcher","documentCount":0,"fieldCount":0,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/pdfindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/pdfindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory"}},{"name":"WorkflowIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"WorkflowSearcher","documentCount":0,"fieldCount":0,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/workflowindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/workflowindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":false}}]}",
7+
"text": "{"total":6,"items":[{"name":"DeliveryApiContentIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"DeliveryApiContentSearcher","documentCount":39,"fieldCount":20,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/deliveryapicontentindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/deliveryapicontentindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":false,"PublishedValuesOnly":false}},{"name":"ExternalIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"ExternalSearcher","documentCount":72,"fieldCount":49,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/externalindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/externalindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":true,"SupportProtectedContent":false}},{"name":"InternalIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"InternalSearcher","documentCount":72,"fieldCount":49,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"CultureInvariantWhitespaceAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/internalindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/internalindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":false,"SupportProtectedContent":true}},{"name":"MembersIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"MembersSearcher","documentCount":1,"fieldCount":9,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"CultureInvariantWhitespaceAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/membersindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/membersindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":false,"IncludeFields":["id","nodeName","updateDate","loginName","email","__Key"]}},{"name":"PDFIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"PDFSearcher","documentCount":0,"fieldCount":0,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/pdfindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/pdfindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory"}},{"name":"WorkflowIndex","healthStatus":{"status":"Healthy","message":null},"canRebuild":true,"searcherName":"WorkflowSearcher","documentCount":0,"fieldCount":0,"providerProperties":{"CommitCount":0,"DefaultAnalyzer":"StandardAnalyzer","LuceneDirectory":"NRTCachingDirectory","LuceneIndexFolder":"/niofsdirectory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/workflowindex lockfactory=noprefixsimplefslockfactory@/users/philw/projects/umbraco-mcp/infrastructure/test-umbraco/mcptestsite/umbraco/data/temp/examineindexes/workflowindex","DirectoryFactory":"Umbraco.Cms.Infrastructure.Examine.ConfigurationEnabledDirectoryFactory","EnableDefaultEventHandler":true,"PublishedValuesOnly":false}}]}",
88
"type": "text",
99
},
1010
],

0 commit comments

Comments
 (0)