From 7f0f91adabf3b7455ad0c11dd55b4b55d3eb28c2 Mon Sep 17 00:00:00 2001 From: Sean Steimer Date: Wed, 17 Jun 2026 11:03:31 +0100 Subject: [PATCH 1/3] fix(source): accept raw body PUT/POST for application/json Previously only text/html was handled by the raw body path in putHelper; application/json (and any other non-form, non-media SUPPORTED_TYPE) fell through to undefined and was silently dropped. Co-Authored-By: Claude Sonnet 4.6 --- docs/openapi/source-api.yaml | 53 +++++++++++++++++++++++++++++++++++- src/helpers/source.js | 4 +-- test/helpers/source.test.js | 10 +++++-- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/docs/openapi/source-api.yaml b/docs/openapi/source-api.yaml index e6f53c84..953690f0 100644 --- a/docs/openapi/source-api.yaml +++ b/docs/openapi/source-api.yaml @@ -24,6 +24,47 @@ source: $ref: "./responses.yaml#/404" '500': $ref: "./responses.yaml#/500" + put: + operationId: putSource + tags: + - Source + summary: Create or replace source content + description: | + Create or replace a content **source** within an organization. + + Accepts either a `multipart/form-data` body with the content in the `data` field, or a + raw request body for text-based types (`text/html`, `application/json`). + + **Important:** For files, the `path` parameter must include the file extension (e.g., `myfile.html`, `data.json`). + For folders, omit the extension (e.g., `myfolder`). + parameters: + - $ref: "./parameters.yaml#/orgParam" + - $ref: "./parameters.yaml#/repoParam" + - $ref: "./parameters.yaml#/pathParam" + requestBody: + content: + multipart/form-data: + schema: + $ref: './schemas.yaml#/source' + examples: + multipart: + $ref: "./payloads.yaml#/copySourceForm" + text/html: + schema: + type: string + description: Raw HTML content. + application/json: + schema: + description: Raw JSON content. + responses: + '201': + $ref: "./responses.yaml#/201" + '400': + $ref: "./responses.yaml#/400" + '401': + $ref: "./responses.yaml#/401" + '500': + $ref: "./responses.yaml#/500" post: operationId: createSource tags: @@ -31,7 +72,10 @@ source: summary: Create source content description: | Create a content **source** within an organization. - + + Accepts either a `multipart/form-data` body with the content in the `data` field, or a + raw request body for text-based types (`text/html`, `application/json`). + **Important:** For files, the `path` parameter must include the file extension (e.g., `myfile.html`, `data.json`). For folders, omit the extension (e.g., `myfolder`). Examples: @@ -49,6 +93,13 @@ source: examples: multipart: $ref: "./payloads.yaml#/copySourceForm" + text/html: + schema: + type: string + description: Raw HTML content. + application/json: + schema: + description: Raw JSON content. responses: '201': $ref: "./responses.yaml#/201" diff --git a/src/helpers/source.js b/src/helpers/source.js index 49963a31..1be034ff 100644 --- a/src/helpers/source.js +++ b/src/helpers/source.js @@ -9,7 +9,7 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -import { FORM_TYPES } from '../utils/constants.js'; +import { FORM_TYPES, SUPPORTED_TYPES, MEDIA_TYPES } from '../utils/constants.js'; import normalizeCharset from '../utils/charset.js'; /** @@ -78,7 +78,7 @@ export async function putHelper(req, env, daCtx) { if (FORM_TYPES.some((type) => type === contentType)) return formPutHandler(req, env, daCtx); - if (contentType === 'text/html') { + if (SUPPORTED_TYPES.includes(contentType) && !MEDIA_TYPES.includes(contentType)) { return rawBodyPutHandler(req, contentType); } diff --git a/test/helpers/source.test.js b/test/helpers/source.test.js index aaaeddf7..fea37345 100644 --- a/test/helpers/source.test.js +++ b/test/helpers/source.test.js @@ -119,9 +119,10 @@ describe('Source helper', () => { assert.strictEqual(helped, undefined); }); - it('Returns undefined for application/json raw body', async () => { + it('Handles application/json raw body', async () => { + const json = JSON.stringify({ title: 'Café' }); const opts = { - body: JSON.stringify({ title: 'Café' }), + body: json, method: 'PUT', headers: new Headers({ 'Content-Type': 'application/json', @@ -130,7 +131,10 @@ describe('Source helper', () => { const req = new Request(MOCK_URL, opts); const helped = await putHelper(req, env, daCtx); - assert.strictEqual(helped, undefined); + assert(helped.data instanceof File); + assert.strictEqual(helped.data.type, 'application/json'); + const text = await helped.data.text(); + assert.strictEqual(text, json); }); it('Handles text/html with existing charset', async () => { From 662b4adc234a24fedd63493f4747dd6ffb79c34e Mon Sep 17 00:00:00 2001 From: Sean Steimer Date: Wed, 17 Jun 2026 11:04:25 +0100 Subject: [PATCH 2/3] docs(source): rebuild redoc bundle with PUT operation Co-Authored-By: Claude Sonnet 4.6 --- docs/index.html | 57 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/docs/index.html b/docs/index.html index 7a24f3bd..64e16b7c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -139,12 +139,17 @@ data-styled.g37[id="sc-eknHtZ"]{content:"ghzOpX,"}/*!sc*/ .eyTvTk{position:absolute;pointer-events:none;z-index:1;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);right:8px;margin:auto;text-align:center;}/*!sc*/ .eyTvTk polyline{color:white;}/*!sc*/ -data-styled.g38[id="sc-pYNGo"]{content:"eyTvTk,"}/*!sc*/ +.eDAldT{position:absolute;pointer-events:none;z-index:1;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);right:8px;margin:auto;text-align:center;}/*!sc*/ +data-styled.g38[id="sc-pYNGo"]{content:"eyTvTk,eDAldT,"}/*!sc*/ .dbfEBv{box-sizing:border-box;min-width:100px;outline:none;display:inline-block;border-radius:2px;border:1px solid rgba(38, 50, 56, 0.5);vertical-align:bottom;padding:2px 0px 2px 6px;position:relative;width:auto;background:white;color:#263238;font-family:Montserrat,sans-serif;font-size:0.929em;line-height:1.5em;cursor:pointer;transition:border 0.25s ease,color 0.25s ease,box-shadow 0.25s ease;}/*!sc*/ .dbfEBv label{box-sizing:border-box;min-width:100px;outline:none;display:inline-block;font-family:Montserrat,sans-serif;color:#333333;vertical-align:bottom;width:auto;text-transform:none;padding:0 22px 0 4px;font-size:0.929em;line-height:1.5em;font-family:inherit;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;}/*!sc*/ .dbfEBv .dropdown-select{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;border:none;appearance:none;cursor:pointer;color:#333333;line-height:inherit;font-family:inherit;}/*!sc*/ .dbfEBv:hover,.dbfEBv:focus-within{border:1px solid #32329f;color:#32329f;box-shadow:0px 0px 0px 1px #32329f;}/*!sc*/ data-styled.g39[id="sc-cCVJLD"]{content:"dbfEBv,"}/*!sc*/ +.gIthhL{margin-left:10px;text-transform:none;font-size:0.969em;font-size:1em;border:none;padding:0 1.2em 0 0;background:transparent;}/*!sc*/ +.gIthhL:hover,.gIthhL:focus-within{border:none;box-shadow:none;}/*!sc*/ +.gIthhL:hover label,.gIthhL:focus-within label{color:#32329f;text-shadow:0px 0px 0px #32329f;}/*!sc*/ +data-styled.g40[id="sc-jYczwO"]{content:"gIthhL,"}/*!sc*/ .cFlAeY{margin-left:10px;text-transform:none;font-size:0.929em;color:black;}/*!sc*/ data-styled.g41[id="sc-dNFkOE"]{content:"cFlAeY,"}/*!sc*/ .kbZred{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;}/*!sc*/ @@ -350,9 +355,10 @@ .iPCVMX:focus{box-shadow:inset 0 2px 2px rgba(0, 0, 0, 0.45),0 2px 0 rgba(128, 128, 128, 0.25);}/*!sc*/ data-styled.g113[id="sc-buTqWO"]{content:"iPCVMX,"}/*!sc*/ .dynMBc{font-size:0.929em;line-height:20px;background-color:#2F8132;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/ +.dBzsUh{font-size:0.929em;line-height:20px;background-color:#95507c;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/ .kwcmyC{font-size:0.929em;line-height:20px;background-color:#186FAF;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/ .gKcHYQ{font-size:0.929em;line-height:20px;background-color:#cc3333;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/ -data-styled.g114[id="sc-fQLpxn"]{content:"dynMBc,kwcmyC,gKcHYQ,"}/*!sc*/ +data-styled.g114[id="sc-fQLpxn"]{content:"dynMBc,dBzsUh,kwcmyC,gKcHYQ,"}/*!sc*/ .ga-DQLq{position:absolute;width:100%;z-index:100;background:#fafafa;color:#263238;box-sizing:border-box;box-shadow:0 0 6px rgba(0, 0, 0, 0.33);overflow:hidden;border-bottom-left-radius:4px;border-bottom-right-radius:4px;transition:all 0.25s ease;visibility:hidden;transform:translateY(-50%) scaleY(0);}/*!sc*/ data-styled.g115[id="sc-ecJghI"]{content:"ga-DQLq,"}/*!sc*/ .icOxsG{padding:10px;}/*!sc*/ @@ -412,7 +418,7 @@ -

Resource not found.

Response samples

Content type
<body>
+

Response samples

Content type
<body>
   <header></header>
   <main>
     <div></div>
   </main>
 </body>
-

Create source content

Create or replace source content

Create or replace a content source within an organization.

+

Accepts either a multipart/form-data body with the content in the data field, or a +raw request body for text-based types (text/html, application/json).

+

Important: For files, the path parameter must include the file extension (e.g., myfile.html, data.json). +For folders, omit the extension (e.g., myfolder).

+
Authorizations:
bearer
path Parameters
org
required
string

The organization.

+
repo
required
string

Name of the repository.

+
path
required
string

Path to the source content.

+
Request Body schema:
data
string <binary>

The content to store at the specified location.

+

Responses

Request samples

Content type
destination: '/aemsites/geometrixx/path/to/file.html'
+

Response samples

Content type
application/json
{}

Create source content

Create a content source within an organization.

+

Accepts either a multipart/form-data body with the content in the data field, or a +raw request body for text-based types (text/html, application/json).

Important: For files, the path parameter must include the file extension (e.g., myfile.html, data.json). For folders, omit the extension (e.g., myfolder). Examples:

@@ -484,7 +521,7 @@ " class="sc-eVqvcJ sc-fszimp kIppRw drqpJr">

Name of the repository.

path
required
string

Path to the source content.

-
Request Body schema: multipart/form-data
data
string <binary>
Request Body schema:
data
string <binary>

The content to store at the specified location.

Responses

Request samples

Content type
multipart/form-data
destination: '/aemsites/geometrixx/path/to/file.html'
-

Response samples

Content type
application/json
{}

Delete source content

Request samples

Content type
destination: '/aemsites/geometrixx/path/to/file.html'
+

Response samples

Content type
application/json
{}

Delete source content

Name of the repository.

path
required
string

Path to the source content.

+
header Parameters
da-continuation-token
string

Continuation token from the previous list response header.

Responses

Request samples

Content type
multipart/form-data
destination: '/aemsites/geometrixx/path/to/file.html'
 

Response samples

Content type
application/json
{}