Repository: redhat-developer/rhdh-plugins
Pull request #701: chore(orchestrator): http test server
Author: login: mareklibra
Created: 2025-04-29T13:14:21Z | Merged: 2025-05-06T11:32:18Z

Adding dev-only HTTP server providing test content for the dynamic widgets



Diff:
diff --git a/workspaces/orchestrator/.changeset/beige-ducks-juggle.md b/workspaces/orchestrator/.changeset/beige-ducks-juggle.md
new file mode 100644
index 0000000000..8a1cb1392d
--- /dev/null
+++ b/workspaces/orchestrator/.changeset/beige-ducks-juggle.md
@@ -0,0 +1,5 @@
+---
+'@red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets': patch
+---
+
+Setting up dev environment for the Orchestrator form widgets (dev-only HTTP server)
diff --git a/workspaces/orchestrator/README.md b/workspaces/orchestrator/README.md
index c75bea4c92..067657f61f 100644
--- a/workspaces/orchestrator/README.md
+++ b/workspaces/orchestrator/README.md
@@ -32,9 +32,14 @@ These plugins farther import following isolated plugins:
    - Provides the workflow execution form.
 
 4. **`backstage-plugin-orchestrator-form-api`**
+
    - Defines the API for extending the workflow execution form.
    - For more details, see the [Extensible Form Documentation](./docs/extensibleForm.md).
 
+5. **`backstage-plugin-orchestrator-form-widgets`**
+
+   - Provides the workflow execution form custom widgets.
+
 For more details about architecture, see the [Architecture](https://www.rhdhorchestrator.io/main/docs/architecture/) page.
 
 ## Install as a static plugin
diff --git a/workspaces/orchestrator/app-config.yaml b/workspaces/orchestrator/app-config.yaml
index dfe80693d0..a8837b1173 100644
--- a/workspaces/orchestrator/app-config.yaml
+++ b/workspaces/orchestrator/app-config.yaml
@@ -43,20 +43,23 @@ integrations: {}
 #  github:
 #    - host: github.com
 #      token: ${GITHUB_TOKEN}
-#  gitlab:
-#    - host: gitlab.com
-#      token: ${GITLAB_TOKEN}
 
 proxy:
-  '/quay/api':
-    target: 'https://quay.io'
-    headers:
-      X-Requested-With: 'XMLHttpRequest'
-      # Uncomment the following line to access a private Quay Repository using a token
-      # Authorization: 'Bearer <YOUR TOKEN>'
-    changeOrigin: true
-    # Change to "false" in case of using self hosted quay instance with a self-signed certificate
-    secure: true
+  reviveConsumedRequestBodies: true
+  endpoints:
+    '/quay/api':
+      target: 'https://quay.io'
+      headers:
+        X-Requested-With: 'XMLHttpRequest'
+        # Uncomment the following line to access a private Quay Repository using a token
+        # Authorization: 'Bearer <YOUR TOKEN>'
+      changeOrigin: true
+      # Change to "false" in case of using self hosted quay instance with a self-signed certificate
+      secure: true
+    '/mytesthttpserver':
+      target: 'http://localhost:12345'
+      allowedMethods: ['GET', 'POST']
+      allowedHeaders: ['test-header']
 
 quay:
   uiUrl: 'https://quay.io'
@@ -73,6 +76,14 @@ auth:
   environment: development
   providers:
     guest: {}
+    gitlab:
+      development:
+        clientId: ${AUTH_GITLAB_CLIENT_ID}
+        clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
+        signIn:
+          resolvers:
+            - resolver: emailMatchingUserEntityProfileEmail
+            # - resolver: usernameMatchingUserEntityName
     github:
       development:
         clientId: ${AUTH_GITHUB_CLIENT_ID}
diff --git a/workspaces/orchestrator/packages/app/src/App.tsx b/workspaces/orchestrator/packages/app/src/App.tsx
index 6ac00ecae2..2d447d0348 100644
--- a/workspaces/orchestrator/packages/app/src/App.tsx
+++ b/workspaces/orchestrator/packages/app/src/App.tsx
@@ -21,7 +21,7 @@ import {
   OAuthRequestDialog,
   SignInPage,
 } from '@backstage/core-components';
-import { githubAuthApiRef } from '@backstage/core-plugin-api';
+import { githubAuthApiRef, gitlabAuthApiRef } from '@backstage/core-plugin-api';
 import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs';
 import {
   CatalogEntityPage,
@@ -91,6 +91,12 @@ const app = createApp({
             message: 'Sign in using GitHub',
             apiRef: githubAuthApiRef,
           },
+          {
+            id: 'gitlab-auth-provider',
+            title: 'GitLab',
+            message: 'Sign in using GitLab',
+            apiRef: gitlabAuthApiRef,
+          },
         ]}
       />
     ),
diff --git a/workspaces/orchestrator/packages/backend/package.json b/workspaces/orchestrator/packages/backend/package.json
index 147ac35699..204821c68b 100644
--- a/workspaces/orchestrator/packages/backend/package.json
+++ b/workspaces/orchestrator/packages/backend/package.json
@@ -27,6 +27,7 @@
     "@backstage/plugin-app-backend": "^0.5.0",
     "@backstage/plugin-auth-backend": "^0.24.4",
     "@backstage/plugin-auth-backend-module-github-provider": "^0.3.1",
+    "@backstage/plugin-auth-backend-module-gitlab-provider": "^0.3.2",
     "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.6",
     "@backstage/plugin-auth-node": "^0.6.1",
     "@backstage/plugin-catalog-backend": "^1.32.0",
diff --git a/workspaces/orchestrator/packages/backend/src/index.ts b/workspaces/orchestrator/packages/backend/src/index.ts
index a0314cf30a..d1e087d45a 100644
--- a/workspaces/orchestrator/packages/backend/src/index.ts
+++ b/workspaces/orchestrator/packages/backend/src/index.ts
@@ -29,6 +29,7 @@ backend.add(import('@backstage/plugin-auth-backend'));
 backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
 // See https://backstage.io/docs/auth/guest/provider
 backend.add(import('@backstage/plugin-auth-backend-module-github-provider'));
+backend.add(import('@backstage/plugin-auth-backend-module-gitlab-provider'));
 
 // catalog plugin
 backend.add(import('@backstage/plugin-catalog-backend'));
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/README.md b/workspaces/orchestrator/plugins/orchestrator-form-widgets/README.md
index 40e39bf20b..8271b76530 100644
--- a/workspaces/orchestrator/plugins/orchestrator-form-widgets/README.md
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/README.md
@@ -1,6 +1,6 @@
 # orchestrator-form-widgets
 
-This Backstage frontend plugin provides default, but optional, set of RJSF form widgets for the Orchestrator workflow execution page.
+This Backstage frontend plugin provides default, but optional, set of RJSF form widgets for the Orchestrator workflow execution page as described [here](../orchestrator-form-api/README.md).
 
 ## Deployment
 
@@ -68,3 +68,113 @@ Example of response:
   }
 }
 ```
+
+## HTTP Test Server
+
+**For the development purposes only**, there is `http-test-server`, very simple Express Node.js server which responds with JSON schema chunks for the `SchemaUpdater` and other active widgets.
+
+Moreover, it does extensive logging of the received HTTP requests for debugging during the widget's development.
+
+Refer [http-test-server](./http-test-server/README.md) for more details.
+
+## How to develop widgets
+
+We do not have a dedicated developer Backstage instance to host dev environment for the widget library. We might add it in the future.
+
+So far the development can be done via Orchestrator's dev instance (see `./workspaces/orchestrator/packages/backend` and `./workspaces/orchestrator/packages/frontend`).
+This instance is already configured to statically load the widget library (see `createApp()` in `./workspaces/orchestrator/packages/app/src/App.tsx`).
+
+To develop the widgets, we recommend to uncomment or farther configure the `integrations:` and `auth:` sections in the `./workspaces/orchestrator/app-config.yaml` to be able to test various SCM Auth providers within `$${{}}` templates.
+
+Please note, that the `proxy:` section is already pre-configured to match the `http-test-server` listening on localhost.
+
+Make sure your user has an entity in the Catalog and the authentication is otherwise configured so the tokens and user's profile info can be fetched by the workflow and `$${{identityApi.[various_keys]}}` templating.
+
+- Once configured, do:
+
+```bash
+cd ./workspaces/orchestrator
+yarn install
+```
+
+```bash
+cd ./workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server
+yarn update-running-workflow # which will copy the workflow under Backstage backend cache
+```
+
+Re-run the `yarn update-running-workflow` **whenever the example `dynamic.schema.sw` workflow is changed**.
+Subsequently, delete the SonataFlow dev-container and restart the Backstage's backend process (as described in the next step).
+
+- In one terminal:
+
+```bash
+# have docker/podman configured and running
+
+# optional: docker rm --force `docker ps --all -q`
+
+cd ./workspaces/orchestrator/packages/backend
+
+export GITLAB_TOKEN=......
+export GITHUB_TOKEN=......
+export AUTH_GITHUB_CLIENT_ID=.......
+export AUTH_GITHUB_CLIENT_SECRET=...........
+export NOTIFICATIONS_BEARER_TOKEN=fooo
+# export NODE_OPTIONS="${NODE_OPTIONS:-} --no-node-snapshot" # use with Node 20+
+
+yarn start
+```
+
+- In second terminal:
+
+```bash
+cd ./workspaces/orchestrator/packages/app
+yarn start
+```
+
+- In third terminal, run the HTTP test server:
+
+```bash
+cd ./workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server
+
+yarn install
+yarn start
+```
+
+### The dynamic_schema workflow
+
+There is `dynamic_schema` workflow located under `http-test-server/exampleWorkflows`.
+It's purpose is to have a playground when developing the widgets.
+
+The URLs referenced from this workflow's data input schema rely on proxy configured in the `./workspaces/orchestrator/app-config.yaml` which assumes the `http-test-server` to be running.
+
+This dev-only workflow is similar to https://github.com/rhdhorchestrator/backstage-orchestrator-workflows/blob/main/workflows/dynamic.schema.sw.json .
+The difference is in the URLs used - the backstage-orchestrator-workflows' one references public Git Hub HTTP server, so no extra steps in running the `http-test-server` are needed.
+
+## Development of a workflow using orchestrator-form-widgets
+
+Developing workflows with `orchestrator-form-widgets` follows principles similar to standard workflow creation, with one critical enhancement: the ability to incorporate dynamic UI elements via the `ui:widget` property in your data input schema.
+
+Key Differentiators:
+
+- Dynamic UI Integration: Reference custom UI widgets directly in your schema using `ui:widget`, enabling interactive components like `ActiveTextInput` or `ActiveDropdown`.
+- Backend Flexibility: A live HTTP server is required to:
+  - Serve JSON Schema snippets for the SchemaUpdater.
+  - Provide default data or option lists.
+  - Handle complex validation logic for widgets.
+
+Deployment Considerations:
+
+- Use one or multiple servers depending on organizational needs.
+- Ensure endpoint structures and response formats exactly match the naming conventions and data structures defined in your schema’s `ui:props` by the creator of workflow's `data input schema`.
+
+TODO: describe components and provide snippets of code (based on the ADR).
+
+### SchemaUpdater
+
+TBD
+
+### ActiveTextInput
+
+TBD
+
+### TBD: other sections - similar to the ADR
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/.yarn/install-state.gz b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/.yarn/install-state.gz
new file mode 100644
index 0000000000..29fa96e970
Binary files /dev/null and b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/.yarn/install-state.gz differ
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/README.md b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/README.md
new file mode 100644
index 0000000000..7949234d0c
--- /dev/null
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/README.md
@@ -0,0 +1,50 @@
+# Development-only HTTP server
+
+The purpose of this Express Node.js server is to respond with JSON Schema chunks and other test data to ease development of the active widgets, like the `SchemaUpdater`.
+
+The production environment is expected to provide another HTTP server (unrelated to this testing one) dynamically providing `SchemaUpdater` snippets or fetch/validation functionality for other widgets.
+
+## How to run
+
+```bash
+yarn install
+yarn start
+```
+
+## How to invoke - examples
+
+### Hello world
+
+```
+curl localhost:12345
+```
+
+### HTTP GET with headers
+
+```
+curl -H "Accept: application/json" -H "foo: bar" localhost:12345/chunk01
+```
+
+### HTTP POST with headers and body
+
+```
+curl -H 'Content-Type: application/json' -H "Accept: application/json" -X POST -d '{"param1":"foo"}' -H "foo: bar" localhost:12345/chunk01
+```
+
+leads to:
+
+```
+request:  {
+  originalUrl: '/chunk01',
+  method: 'POST',
+  headers: {
+    host: 'localhost:12345',
+    'user-agent': 'curl/8.9.1',
+    'content-type': 'application/json',
+    accept: 'application/json',
+    foo: 'bar',
+    'content-length': '16'
+  },
+  body: { param1: 'foo' }
+}
+```
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/exampleWorkflows/dynamic.schema.sw.json b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/exampleWorkflows/dynamic.schema.sw.json
new file mode 100644
index 0000000000..304479e05e
--- /dev/null
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/exampleWorkflows/dynamic.schema.sw.json
@@ -0,0 +1,40 @@
+{
+  "id": "dynamic_schema",
+  "version": "1.0",
+  "specVersion": "0.8",
+  "name": "Dynamic Input Schema Workflow",
+  "description": "JSON based workflow demonstrating use of the orchestrator-form-widgets",
+  "dataInputSchema": "schemas/dynamic_schema__main-schema.json",
+  "start": "Start",
+  "functions": [
+    {
+      "name": "sysout",
+      "type": "custom",
+      "operation": "sysout"
+    }
+  ],
+  "states": [
+    {
+      "name": "Start",
+      "type": "inject",
+      "data": {},
+      "transition": "End"
+    },
+    {
+      "name": "End",
+      "type": "operation",
+      "actions": [
+        {
+          "name": "sysout",
+          "functionRef": {
+            "refName": "sysout",
+            "arguments": {
+              "message": ".firstStep.fooTheFirst"
+            }
+          }
+        }
+      ],
+      "end": true
+    }
+  ]
+}
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/exampleWorkflows/schemas/dynamic_schema__main-schema.json b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/exampleWorkflows/schemas/dynamic_schema__main-schema.json
new file mode 100644
index 0000000000..22f97741b9
--- /dev/null
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/exampleWorkflows/schemas/dynamic_schema__main-schema.json
@@ -0,0 +1,66 @@
+{
+  "$id": "classpath:/schemas/dynamic_schema__main-schema.json",
+  "title": "Data Input Schema",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "type": "object",
+  "properties": {
+    "firstStep": {
+      "type": "object",
+      "title": "Example use",
+      "properties": {
+        "fooTheFirst": {
+          "type": "string",
+          "title": "First page simple input field"
+        },
+        "mySchemaUpdater": {
+          "type": "string",
+          "ui:widget": "SchemaUpdater",
+          "ui:props": {
+            "fetch:url": "http://localhost:7007/api/proxy/mytesthttpserver/chunk01",
+            "fetch:headers": {
+              "test-header": "Executed for the $${{identityApi.displayName}} user with $${{githubAuthApi.profileName}} GitHub profile name."
+            }
+          }
+        },
+        "placeholderOne": {
+          "type": "string",
+          "title": "This field and its title are visible until replaced by any SchemaUpdater."
+        }
+      }
+    },
+    "secondStep": {
+      "type": "object",
+      "title": "Second page of the wizard",
+      "properties": {
+        "myTextField": {
+          "type": "string",
+          "title": "Provide name of the next component. Try 'password' or 'complex'"
+        },
+        "addressOrPassword": {
+          "type": "string",
+          "title": "This title is not rendered. Will be replaced by myAnotherSchemaUpdater",
+          "ui:widget": "hidden"
+        },
+        "placeholderThree": {
+          "type": "object",
+          "ui:widget": "hidden"
+        },
+        "myAnotherSchemaUpdater": {
+          "type": "string",
+          "ui:widget": "SchemaUpdater",
+          "ui:props": {
+            "fetch:url": "http://localhost:7007/api/proxy/mytesthttpserver/chunk02",
+            "fetch:method": "POST",
+            "fetch:headers": {
+              "not-used-but-anyway": "$${{githubAuthApi.token}}"
+            },
+            "fetch:body": {
+              "nextField": "$${{current.secondStep.myTextField}}"
+            }
+          }
+        }
+      }
+    }
+  },
+  "required": ["firstStep", "secondStep"]
+}
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/index.js b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/index.js
new file mode 100644
index 0000000000..e464e3d79e
--- /dev/null
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/index.js
@@ -0,0 +1,124 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const express = require('express');
+
+const app = express();
+app.disable('x-powered-by');
+
+const port = 12345;
+app.use(express.json());
+
+const logRequest = req => {
+  // eslint-disable-next-line no-console
+  console.log('request: ', {
+    originalUrl: req.originalUrl,
+    method: req.method,
+    headers: req.headers,
+    body: req.body,
+  });
+};
+
+app.get('/', (req, res) => {
+  res.send('Hello World from Orchestrator HTTP test server!');
+});
+
+app.get('/chunk01', (req, res) => {
+  logRequest(req);
+
+  const testHeader = req.headers['test-header'];
+
+  const chunk01 = {
+    placeholderOne: {
+      type: 'string',
+      title: `This is input box for the placeholderOne supplied by chunk01 with HTTP test-header "${testHeader}"`,
+    },
+  };
+
+  res.send(JSON.stringify(chunk01));
+});
+
+app.post('/chunk02', (req, res) => {
+  logRequest(req);
+
+  const nextField = req.body?.nextField;
+
+  let chunk02 = {
+    addressOrPassword: {
+      type: 'string',
+      'ui:widget': 'hidden',
+    },
+    placeholderThree: {
+      type: 'string',
+      'ui:widget': 'hidden',
+    },
+  };
+
+  if (nextField === 'password') {
+    chunk02 = {
+      addressOrPassword: {
+        type: 'string',
+        title: `Let's use password input-field then.`,
+        'ui:widget': 'password',
+      },
+      placeholderThree: {
+        type: 'string',
+        'ui:widget': 'hidden',
+      },
+    };
+  }
+
+  if (nextField === 'complex') {
+    chunk02 = {
+      placeholderThree: {
+        type: 'object',
+        title: `Let's use a complex set of new widgets then.`,
+        properties: {
+          name: {
+            type: 'string',
+            title: 'Name',
+          },
+          surname: {
+            type: 'string',
+            title: 'Surname',
+          },
+          myradio: {
+            type: 'boolean',
+            title: 'A radio button',
+            oneOf: [
+              { const: true, title: 'Custom label for true' },
+              { const: false, title: 'Custom label for false' },
+            ],
+            'ui:widget': 'radio',
+          },
+        },
+      },
+      addressOrPassword: {
+        type: 'string',
+        title: 'Address via placeholder "addressOrPassword"',
+      },
+    };
+  }
+
+  res.send(JSON.stringify(chunk02));
+});
+
+app.listen(port, () => {
+  // eslint-disable-next-line no-console
+  console.info(
+    `Simple HTTP server for orchestrator-form-widgets development only. Listening on ${port} port.`,
+  );
+});
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/package.json b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/package.json
new file mode 100644
index 0000000000..9aadf143a8
--- /dev/null
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/package.json
@@ -0,0 +1,22 @@
+{
+  "name": "http-test-server",
+  "version": "5.2.3",
+  "license": "Apache-2.0",
+  "main": "index.ts",
+  "scripts": {
+    "start": "node ./index.js",
+    "prettier:check": "prettier --ignore-unknown --check .",
+    "prettier:fix": "prettier --ignore-unknown --write .",
+    "update-running-workflow": "cp -r ./exampleWorkflows/* ../../../packages/backend/.devModeTemp/repository/workflows/"
+  },
+  "dependencies": {
+    "express": "^5.1.0"
+  },
+  "devDependencies": {
+    "prettier": "3.4.2"
+  },
+  "maintainers": [
+    "@mlibra"
+  ],
+  "author": "The Backstage Community"
+}
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/yarn.lock b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/yarn.lock
new file mode 100644
index 0000000000..36cb5f485a
--- /dev/null
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/yarn.lock
@@ -0,0 +1,640 @@
+# This file is generated by running "yarn install" inside your project.
+# Manual changes might be lost - proceed with caution!
+
+__metadata:
+  version: 6
+  cacheKey: 8
+
+"accepts@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "accepts@npm:2.0.0"
+  dependencies:
+    mime-types: ^3.0.0
+    negotiator: ^1.0.0
+  checksum: 49fe6c050cb6f6ff4e771b4d88324fca4d3127865f2473872e818dca127d809ba3aa8fdfc7acb51dd3c5bade7311ca6b8cfff7015ea6db2f7eb9c8444d223a4f
+  languageName: node
+  linkType: hard
+
+"body-parser@npm:^2.2.0":
+  version: 2.2.0
+  resolution: "body-parser@npm:2.2.0"
+  dependencies:
+    bytes: ^3.1.2
+    content-type: ^1.0.5
+    debug: ^4.4.0
+    http-errors: ^2.0.0
+    iconv-lite: ^0.6.3
+    on-finished: ^2.4.1
+    qs: ^6.14.0
+    raw-body: ^3.0.0
+    type-is: ^2.0.0
+  checksum: 7fe3a2d288f0b632528d6ccb90052d1a9492c5b79d5716d32c8de1f5fb8237b0d31ee5050e1d0b7ff143a492ff151804612c6e2686a222a1d4c9e2e6531b8fb2
+  languageName: node
+  linkType: hard
+
+"bytes@npm:3.1.2, bytes@npm:^3.1.2":
+  version: 3.1.2
+  resolution: "bytes@npm:3.1.2"
+  checksum: e4bcd3948d289c5127591fbedf10c0b639ccbf00243504e4e127374a15c3bc8eed0d28d4aaab08ff6f1cf2abc0cce6ba3085ed32f4f90e82a5683ce0014e1b6e
+  languageName: node
+  linkType: hard
+
+"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "call-bind-apply-helpers@npm:1.0.2"
+  dependencies:
+    es-errors: ^1.3.0
+    function-bind: ^1.1.2
+  checksum: b2863d74fcf2a6948221f65d95b91b4b2d90cfe8927650b506141e669f7d5de65cea191bf788838bc40d13846b7886c5bc5c84ab96c3adbcf88ad69a72fcdc6b
+  languageName: node
+  linkType: hard
+
+"call-bound@npm:^1.0.2":
+  version: 1.0.4
+  resolution: "call-bound@npm:1.0.4"
+  dependencies:
+    call-bind-apply-helpers: ^1.0.2
+    get-intrinsic: ^1.3.0
+  checksum: 2f6399488d1c272f56306ca60ff696575e2b7f31daf23bc11574798c84d9f2759dceb0cb1f471a85b77f28962a7ac6411f51d283ea2e45319009a19b6ccab3b2
+  languageName: node
+  linkType: hard
+
+"content-disposition@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "content-disposition@npm:1.0.0"
+  dependencies:
+    safe-buffer: 5.2.1
+  checksum: b27e2579fefe0ecf78238bb652fbc750671efce8344f0c6f05235b12433e6a965adb40906df1ac1fdde23e8f9f0e58385e44640e633165420f3f47d830ae0398
+  languageName: node
+  linkType: hard
+
+"content-type@npm:^1.0.5":
+  version: 1.0.5
+  resolution: "content-type@npm:1.0.5"
+  checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766
+  languageName: node
+  linkType: hard
+
+"cookie-signature@npm:^1.2.1":
+  version: 1.2.2
+  resolution: "cookie-signature@npm:1.2.2"
+  checksum: 1ad4f9b3907c9f3673a0f0a07c0a23da7909ac6c9204c5d80a0ec102fe50ccc45f27fdf496361840d6c132c5bb0037122c0a381f856d070183d1ebe3e5e041ff
+  languageName: node
+  linkType: hard
+
+"cookie@npm:^0.7.1":
+  version: 0.7.2
+  resolution: "cookie@npm:0.7.2"
+  checksum: 9bf8555e33530affd571ea37b615ccad9b9a34febbf2c950c86787088eb00a8973690833b0f8ebd6b69b753c62669ea60cec89178c1fb007bf0749abed74f93e
+  languageName: node
+  linkType: hard
+
+"debug@npm:^4.3.5, debug@npm:^4.4.0":
+  version: 4.4.0
+  resolution: "debug@npm:4.4.0"
+  dependencies:
+    ms: ^2.1.3
+  peerDependenciesMeta:
+    supports-color:
+      optional: true
+  checksum: fb42df878dd0e22816fc56e1fdca9da73caa85212fbe40c868b1295a6878f9101ae684f4eeef516c13acfc700f5ea07f1136954f43d4cd2d477a811144136479
+  languageName: node
+  linkType: hard
+
+"depd@npm:2.0.0, depd@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "depd@npm:2.0.0"
+  checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a
+  languageName: node
+  linkType: hard
+
+"dunder-proto@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "dunder-proto@npm:1.0.1"
+  dependencies:
+    call-bind-apply-helpers: ^1.0.1
+    es-errors: ^1.3.0
+    gopd: ^1.2.0
+  checksum: 149207e36f07bd4941921b0ca929e3a28f1da7bd6b6ff8ff7f4e2f2e460675af4576eeba359c635723dc189b64cdd4787e0255897d5b135ccc5d15cb8685fc90
+  languageName: node
+  linkType: hard
+
+"ee-first@npm:1.1.1":
+  version: 1.1.1
+  resolution: "ee-first@npm:1.1.1"
+  checksum: 1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f
+  languageName: node
+  linkType: hard
+
+"encodeurl@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "encodeurl@npm:2.0.0"
+  checksum: abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe
+  languageName: node
+  linkType: hard
+
+"es-define-property@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "es-define-property@npm:1.0.1"
+  checksum: 0512f4e5d564021c9e3a644437b0155af2679d10d80f21adaf868e64d30efdfbd321631956f20f42d655fedb2e3a027da479fad3fa6048f768eb453a80a5f80a
+  languageName: node
+  linkType: hard
+
+"es-errors@npm:^1.3.0":
+  version: 1.3.0
+  resolution: "es-errors@npm:1.3.0"
+  checksum: ec1414527a0ccacd7f15f4a3bc66e215f04f595ba23ca75cdae0927af099b5ec865f9f4d33e9d7e86f512f252876ac77d4281a7871531a50678132429b1271b5
+  languageName: node
+  linkType: hard
+
+"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1":
+  version: 1.1.1
+  resolution: "es-object-atoms@npm:1.1.1"
+  dependencies:
+    es-errors: ^1.3.0
+  checksum: 214d3767287b12f36d3d7267ef342bbbe1e89f899cfd67040309fc65032372a8e60201410a99a1645f2f90c1912c8c49c8668066f6bdd954bcd614dda2e3da97
+  languageName: node
+  linkType: hard
+
+"escape-html@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "escape-html@npm:1.0.3"
+  checksum: 6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24
+  languageName: node
+  linkType: hard
+
+"etag@npm:^1.8.1":
+  version: 1.8.1
+  resolution: "etag@npm:1.8.1"
+  checksum: 571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff
+  languageName: node
+  linkType: hard
+
+"express@npm:^5.1.0":
+  version: 5.1.0
+  resolution: "express@npm:5.1.0"
+  dependencies:
+    accepts: ^2.0.0
+    body-parser: ^2.2.0
+    content-disposition: ^1.0.0
+    content-type: ^1.0.5
+    cookie: ^0.7.1
+    cookie-signature: ^1.2.1
+    debug: ^4.4.0
+    encodeurl: ^2.0.0
+    escape-html: ^1.0.3
+    etag: ^1.8.1
+    finalhandler: ^2.1.0
+    fresh: ^2.0.0
+    http-errors: ^2.0.0
+    merge-descriptors: ^2.0.0
+    mime-types: ^3.0.0
+    on-finished: ^2.4.1
+    once: ^1.4.0
+    parseurl: ^1.3.3
+    proxy-addr: ^2.0.7
+    qs: ^6.14.0
+    range-parser: ^1.2.1
+    router: ^2.2.0
+    send: ^1.1.0
+    serve-static: ^2.2.0
+    statuses: ^2.0.1
+    type-is: ^2.0.1
+    vary: ^1.1.2
+  checksum: 06e6141780c6c4780111f971ce062c83d4cf4862c40b43caf1d95afcbb58d7422c560503b8c9d04c7271511525d09cbdbe940bcaad63970fd4c1b9f6fd713bdb
+  languageName: node
+  linkType: hard
+
+"finalhandler@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "finalhandler@npm:2.1.0"
+  dependencies:
+    debug: ^4.4.0
+    encodeurl: ^2.0.0
+    escape-html: ^1.0.3
+    on-finished: ^2.4.1
+    parseurl: ^1.3.3
+    statuses: ^2.0.1
+  checksum: 27ca9cc83b1384ba37959eb95bc7e62bc0bf4d6f6af63f6d38821cf7499b113e34b23f96a2a031616817f73986f94deea67c2f558de9daf406790c181a2501df
+  languageName: node
+  linkType: hard
+
+"forwarded@npm:0.2.0":
+  version: 0.2.0
+  resolution: "forwarded@npm:0.2.0"
+  checksum: fd27e2394d8887ebd16a66ffc889dc983fbbd797d5d3f01087c020283c0f019a7d05ee85669383d8e0d216b116d720fc0cef2f6e9b7eb9f4c90c6e0bc7fd28e6
+  languageName: node
+  linkType: hard
+
+"fresh@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "fresh@npm:2.0.0"
+  checksum: 38b9828352c6271e2a0dd8bdd985d0100dbbc4eb8b6a03286071dd6f7d96cfaacd06d7735701ad9a95870eb3f4555e67c08db1dcfe24c2e7bb87383c72fae1d2
+  languageName: node
+  linkType: hard
+
+"function-bind@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "function-bind@npm:1.1.2"
+  checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1
+  languageName: node
+  linkType: hard
+
+"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0":
+  version: 1.3.0
+  resolution: "get-intrinsic@npm:1.3.0"
+  dependencies:
+    call-bind-apply-helpers: ^1.0.2
+    es-define-property: ^1.0.1
+    es-errors: ^1.3.0
+    es-object-atoms: ^1.1.1
+    function-bind: ^1.1.2
+    get-proto: ^1.0.1
+    gopd: ^1.2.0
+    has-symbols: ^1.1.0
+    hasown: ^2.0.2
+    math-intrinsics: ^1.1.0
+  checksum: 301008e4482bb9a9cb49e132b88fee093bff373b4e6def8ba219b1e96b60158a6084f273ef5cafe832e42cd93462f4accb46a618d35fe59a2b507f2388c5b79d
+  languageName: node
+  linkType: hard
+
+"get-proto@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "get-proto@npm:1.0.1"
+  dependencies:
+    dunder-proto: ^1.0.1
+    es-object-atoms: ^1.0.0
+  checksum: 4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b
+  languageName: node
+  linkType: hard
+
+"gopd@npm:^1.2.0":
+  version: 1.2.0
+  resolution: "gopd@npm:1.2.0"
+  checksum: cc6d8e655e360955bdccaca51a12a474268f95bb793fc3e1f2bdadb075f28bfd1fd988dab872daf77a61d78cbaf13744bc8727a17cfb1d150d76047d805375f3
+  languageName: node
+  linkType: hard
+
+"has-symbols@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "has-symbols@npm:1.1.0"
+  checksum: b2316c7302a0e8ba3aaba215f834e96c22c86f192e7310bdf689dd0e6999510c89b00fbc5742571507cebf25764d68c988b3a0da217369a73596191ac0ce694b
+  languageName: node
+  linkType: hard
+
+"hasown@npm:^2.0.2":
+  version: 2.0.2
+  resolution: "hasown@npm:2.0.2"
+  dependencies:
+    function-bind: ^1.1.2
+  checksum: e8516f776a15149ca6c6ed2ae3110c417a00b62260e222590e54aa367cbcd6ed99122020b37b7fbdf05748df57b265e70095d7bf35a47660587619b15ffb93db
+  languageName: node
+  linkType: hard
+
+"http-errors@npm:2.0.0, http-errors@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "http-errors@npm:2.0.0"
+  dependencies:
+    depd: 2.0.0
+    inherits: 2.0.4
+    setprototypeof: 1.2.0
+    statuses: 2.0.1
+    toidentifier: 1.0.1
+  checksum: 9b0a3782665c52ce9dc658a0d1560bcb0214ba5699e4ea15aefb2a496e2ca83db03ebc42e1cce4ac1f413e4e0d2d736a3fd755772c556a9a06853ba2a0b7d920
+  languageName: node
+  linkType: hard
+
+"http-test-server@workspace:.":
+  version: 0.0.0-use.local
+  resolution: "http-test-server@workspace:."
+  dependencies:
+    express: ^5.1.0
+    prettier: 3.4.2
+  languageName: unknown
+  linkType: soft
+
+"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.3":
+  version: 0.6.3
+  resolution: "iconv-lite@npm:0.6.3"
+  dependencies:
+    safer-buffer: ">= 2.1.2 < 3.0.0"
+  checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf
+  languageName: node
+  linkType: hard
+
+"inherits@npm:2.0.4":
+  version: 2.0.4
+  resolution: "inherits@npm:2.0.4"
+  checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1
+  languageName: node
+  linkType: hard
+
+"ipaddr.js@npm:1.9.1":
+  version: 1.9.1
+  resolution: "ipaddr.js@npm:1.9.1"
+  checksum: f88d3825981486f5a1942414c8d77dd6674dd71c065adcfa46f578d677edcb99fda25af42675cb59db492fdf427b34a5abfcde3982da11a8fd83a500b41cfe77
+  languageName: node
+  linkType: hard
+
+"is-promise@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "is-promise@npm:4.0.0"
+  checksum: 0b46517ad47b00b6358fd6553c83ec1f6ba9acd7ffb3d30a0bf519c5c69e7147c132430452351b8a9fc198f8dd6c4f76f8e6f5a7f100f8c77d57d9e0f4261a8a
+  languageName: node
+  linkType: hard
+
+"math-intrinsics@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "math-intrinsics@npm:1.1.0"
+  checksum: 0e513b29d120f478c85a70f49da0b8b19bc638975eca466f2eeae0071f3ad00454c621bf66e16dd435896c208e719fc91ad79bbfba4e400fe0b372e7c1c9c9a2
+  languageName: node
+  linkType: hard
+
+"media-typer@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "media-typer@npm:1.1.0"
+  checksum: a58dd60804df73c672942a7253ccc06815612326dc1c0827984b1a21704466d7cde351394f47649e56cf7415e6ee2e26e000e81b51b3eebb5a93540e8bf93cbd
+  languageName: node
+  linkType: hard
+
+"merge-descriptors@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "merge-descriptors@npm:2.0.0"
+  checksum: e383332e700a94682d0125a36c8be761142a1320fc9feeb18e6e36647c9edf064271645f5669b2c21cf352116e561914fd8aa831b651f34db15ef4038c86696a
+  languageName: node
+  linkType: hard
+
+"mime-db@npm:^1.54.0":
+  version: 1.54.0
+  resolution: "mime-db@npm:1.54.0"
+  checksum: e99aaf2f23f5bd607deb08c83faba5dd25cf2fec90a7cc5b92d8260867ee08dab65312e1a589e60093dc7796d41e5fae013268418482f1db4c7d52d0a0960ac9
+  languageName: node
+  linkType: hard
+
+"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "mime-types@npm:3.0.1"
+  dependencies:
+    mime-db: ^1.54.0
+  checksum: 8d497ad5cb2dd1210ac7d049b5de94af0b24b45a314961e145b44389344604d54752f03bc00bf880c0da60a214be6fb6d423d318104f02c28d95dd8ebeea4fb4
+  languageName: node
+  linkType: hard
+
+"ms@npm:^2.1.3":
+  version: 2.1.3
+  resolution: "ms@npm:2.1.3"
+  checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d
+  languageName: node
+  linkType: hard
+
+"negotiator@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "negotiator@npm:1.0.0"
+  checksum: 20ebfe79b2d2e7cf9cbc8239a72662b584f71164096e6e8896c8325055497c96f6b80cd22c258e8a2f2aa382a787795ec3ee8b37b422a302c7d4381b0d5ecfbb
+  languageName: node
+  linkType: hard
+
+"object-inspect@npm:^1.13.3":
+  version: 1.13.4
+  resolution: "object-inspect@npm:1.13.4"
+  checksum: 582810c6a8d2ef988ea0a39e69e115a138dad8f42dd445383b394877e5816eb4268489f316a6f74ee9c4e0a984b3eab1028e3e79d62b1ed67c726661d55c7a8b
+  languageName: node
+  linkType: hard
+
+"on-finished@npm:^2.4.1":
+  version: 2.4.1
+  resolution: "on-finished@npm:2.4.1"
+  dependencies:
+    ee-first: 1.1.1
+  checksum: d20929a25e7f0bb62f937a425b5edeb4e4cde0540d77ba146ec9357f00b0d497cdb3b9b05b9c8e46222407d1548d08166bff69cc56dfa55ba0e4469228920ff0
+  languageName: node
+  linkType: hard
+
+"once@npm:^1.4.0":
+  version: 1.4.0
+  resolution: "once@npm:1.4.0"
+  dependencies:
+    wrappy: 1
+  checksum: cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68
+  languageName: node
+  linkType: hard
+
+"parseurl@npm:^1.3.3":
+  version: 1.3.3
+  resolution: "parseurl@npm:1.3.3"
+  checksum: 407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2
+  languageName: node
+  linkType: hard
+
+"path-to-regexp@npm:^8.0.0":
+  version: 8.2.0
+  resolution: "path-to-regexp@npm:8.2.0"
+  checksum: 56e13e45962e776e9e7cd72e87a441cfe41f33fd539d097237ceb16adc922281136ca12f5a742962e33d8dda9569f630ba594de56d8b7b6e49adf31803c5e771
+  languageName: node
+  linkType: hard
+
+"prettier@npm:3.4.2":
+  version: 3.4.2
+  resolution: "prettier@npm:3.4.2"
+  bin:
+    prettier: bin/prettier.cjs
+  checksum: 061c84513db62d3944c8dc8df36584dad82883ce4e49efcdbedd8703dce5b173c33fd9d2a4e1725d642a3b713c932b55418342eaa347479bc4a9cca114a04cd0
+  languageName: node
+  linkType: hard
+
+"proxy-addr@npm:^2.0.7":
+  version: 2.0.7
+  resolution: "proxy-addr@npm:2.0.7"
+  dependencies:
+    forwarded: 0.2.0
+    ipaddr.js: 1.9.1
+  checksum: 29c6990ce9364648255454842f06f8c46fcd124d3e6d7c5066df44662de63cdc0bad032e9bf5a3d653ff72141cc7b6019873d685708ac8210c30458ad99f2b74
+  languageName: node
+  linkType: hard
+
+"qs@npm:^6.14.0":
+  version: 6.14.0
+  resolution: "qs@npm:6.14.0"
+  dependencies:
+    side-channel: ^1.1.0
+  checksum: 189b52ad4e9a0da1a16aff4c58b2a554a8dad9bd7e287c7da7446059b49ca2e33a49e570480e8be406b87fccebf134f51c373cbce36c8c83859efa0c9b71d635
+  languageName: node
+  linkType: hard
+
+"range-parser@npm:^1.2.1":
+  version: 1.2.1
+  resolution: "range-parser@npm:1.2.1"
+  checksum: 0a268d4fea508661cf5743dfe3d5f47ce214fd6b7dec1de0da4d669dd4ef3d2144468ebe4179049eff253d9d27e719c88dae55be64f954e80135a0cada804ec9
+  languageName: node
+  linkType: hard
+
+"raw-body@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "raw-body@npm:3.0.0"
+  dependencies:
+    bytes: 3.1.2
+    http-errors: 2.0.0
+    iconv-lite: 0.6.3
+    unpipe: 1.0.0
+  checksum: 25b7cf7964183db322e819050d758a5abd0f22c51e9f37884ea44a9ed6855a1fb61f8caa8ec5b61d07e69f54db43dbbc08ad98ef84556696d6aa806be247af0e
+  languageName: node
+  linkType: hard
+
+"router@npm:^2.2.0":
+  version: 2.2.0
+  resolution: "router@npm:2.2.0"
+  dependencies:
+    debug: ^4.4.0
+    depd: ^2.0.0
+    is-promise: ^4.0.0
+    parseurl: ^1.3.3
+    path-to-regexp: ^8.0.0
+  checksum: 4c3bec8011ed10bb07d1ee860bc715f245fff0fdff991d8319741d2932d89c3fe0a56766b4fa78e95444bc323fd2538e09c8e43bfbd442c2a7fab67456df7fa5
+  languageName: node
+  linkType: hard
+
+"safe-buffer@npm:5.2.1":
+  version: 5.2.1
+  resolution: "safe-buffer@npm:5.2.1"
+  checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491
+  languageName: node
+  linkType: hard
+
+"safer-buffer@npm:>= 2.1.2 < 3.0.0":
+  version: 2.1.2
+  resolution: "safer-buffer@npm:2.1.2"
+  checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0
+  languageName: node
+  linkType: hard
+
+"send@npm:^1.1.0, send@npm:^1.2.0":
+  version: 1.2.0
+  resolution: "send@npm:1.2.0"
+  dependencies:
+    debug: ^4.3.5
+    encodeurl: ^2.0.0
+    escape-html: ^1.0.3
+    etag: ^1.8.1
+    fresh: ^2.0.0
+    http-errors: ^2.0.0
+    mime-types: ^3.0.1
+    ms: ^2.1.3
+    on-finished: ^2.4.1
+    range-parser: ^1.2.1
+    statuses: ^2.0.1
+  checksum: 7557ee6c1c257a1c53b402b4fba8ed88c95800b08abe085fc79e0824869274f213491be2efb2df3de228c70e4d40ce2019e5f77b58c42adb97149135420c3f34
+  languageName: node
+  linkType: hard
+
+"serve-static@npm:^2.2.0":
+  version: 2.2.0
+  resolution: "serve-static@npm:2.2.0"
+  dependencies:
+    encodeurl: ^2.0.0
+    escape-html: ^1.0.3
+    parseurl: ^1.3.3
+    send: ^1.2.0
+  checksum: 74f39e88f0444aa6732aae3b9597739c47552adecdc83fa32aa42555e76f1daad480d791af73894655c27a2d378275a461e691cead33fb35d8b976f1e2d24665
+  languageName: node
+  linkType: hard
+
+"setprototypeof@npm:1.2.0":
+  version: 1.2.0
+  resolution: "setprototypeof@npm:1.2.0"
+  checksum: be18cbbf70e7d8097c97f713a2e76edf84e87299b40d085c6bf8b65314e994cc15e2e317727342fa6996e38e1f52c59720b53fe621e2eb593a6847bf0356db89
+  languageName: node
+  linkType: hard
+
+"side-channel-list@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "side-channel-list@npm:1.0.0"
+  dependencies:
+    es-errors: ^1.3.0
+    object-inspect: ^1.13.3
+  checksum: 603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f
+  languageName: node
+  linkType: hard
+
+"side-channel-map@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "side-channel-map@npm:1.0.1"
+  dependencies:
+    call-bound: ^1.0.2
+    es-errors: ^1.3.0
+    get-intrinsic: ^1.2.5
+    object-inspect: ^1.13.3
+  checksum: 42501371cdf71f4ccbbc9c9e2eb00aaaab80a4c1c429d5e8da713fd4d39ef3b8d4a4b37ed4f275798a65260a551a7131fd87fe67e922dba4ac18586d6aab8b06
+  languageName: node
+  linkType: hard
+
+"side-channel-weakmap@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "side-channel-weakmap@npm:1.0.2"
+  dependencies:
+    call-bound: ^1.0.2
+    es-errors: ^1.3.0
+    get-intrinsic: ^1.2.5
+    object-inspect: ^1.13.3
+    side-channel-map: ^1.0.1
+  checksum: a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736
+  languageName: node
+  linkType: hard
+
+"side-channel@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "side-channel@npm:1.1.0"
+  dependencies:
+    es-errors: ^1.3.0
+    object-inspect: ^1.13.3
+    side-channel-list: ^1.0.0
+    side-channel-map: ^1.0.1
+    side-channel-weakmap: ^1.0.2
+  checksum: bf73d6d6682034603eb8e99c63b50155017ed78a522d27c2acec0388a792c3ede3238b878b953a08157093b85d05797217d270b7666ba1f111345fbe933380ff
+  languageName: node
+  linkType: hard
+
+"statuses@npm:2.0.1, statuses@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "statuses@npm:2.0.1"
+  checksum: 18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb
+  languageName: node
+  linkType: hard
+
+"toidentifier@npm:1.0.1":
+  version: 1.0.1
+  resolution: "toidentifier@npm:1.0.1"
+  checksum: 952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45
+  languageName: node
+  linkType: hard
+
+"type-is@npm:^2.0.0, type-is@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "type-is@npm:2.0.1"
+  dependencies:
+    content-type: ^1.0.5
+    media-typer: ^1.1.0
+    mime-types: ^3.0.0
+  checksum: 0266e7c782238128292e8c45e60037174d48c6366bb2d45e6bd6422b611c193f83409a8341518b6b5f33f8e4d5a959f38658cacfea77f0a3505b9f7ac1ddec8f
+  languageName: node
+  linkType: hard
+
+"unpipe@npm:1.0.0":
+  version: 1.0.0
+  resolution: "unpipe@npm:1.0.0"
+  checksum: 4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2
+  languageName: node
+  linkType: hard
+
+"vary@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "vary@npm:1.1.2"
+  checksum: ae0123222c6df65b437669d63dfa8c36cee20a504101b2fcd97b8bf76f91259c17f9f2b4d70a1e3c6bbcee7f51b28392833adb6b2770b23b01abec84e369660b
+  languageName: node
+  linkType: hard
+
+"wrappy@npm:1":
+  version: 1.0.2
+  resolution: "wrappy@npm:1.0.2"
+  checksum: 159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5
+  languageName: node
+  linkType: hard
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/package.json b/workspaces/orchestrator/plugins/orchestrator-form-widgets/package.json
index 1f1de54161..52c13e9897 100644
--- a/workspaces/orchestrator/plugins/orchestrator-form-widgets/package.json
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/package.json
@@ -76,6 +76,7 @@
     "@testing-library/jest-dom": "^6.0.0",
     "@testing-library/react": "^14.0.0",
     "@testing-library/user-event": "^14.0.0",
+    "express": "^5.1.0",
     "msw": "^1.0.0",
     "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
   },
diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/utils/getRequestInit.ts b/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/utils/getRequestInit.ts
index be0e01964e..0dda570a1e 100644
--- a/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/utils/getRequestInit.ts
+++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/utils/getRequestInit.ts
@@ -68,11 +68,14 @@ export const getRequestInit = async (
     }
   }
 
+  const headersInit: HeadersInit = {
+    'Content-Type': 'application/json',
+  };
+
   const headers = uiProps[`${prefix}:headers`];
   if (headers) {
     if (typeof headers === 'object') {
       const headersObject = headers as JsonObject;
-      const headersInit: HeadersInit = {};
 
       const keys = Object.keys(headers);
       const values = await Promise.all(
@@ -88,12 +91,11 @@ export const getRequestInit = async (
       keys.forEach((key, idx) => {
         headersInit[key] = values[idx];
       });
-
-      requestInit.headers = headersInit;
     } else {
       throw new Error('fetch:body must be object for POST requests');
     }
   }
+  requestInit.headers = headersInit;
 
   return requestInit;
 };
diff --git a/workspaces/orchestrator/yarn.lock b/workspaces/orchestrator/yarn.lock
index 08d1cbe9f5..2ba96e82cb 100644
--- a/workspaces/orchestrator/yarn.lock
+++ b/workspaces/orchestrator/yarn.lock
@@ -3140,22 +3140,22 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@backstage/backend-plugin-api@npm:^1.0.0, @backstage/backend-plugin-api@npm:^1.0.1, @backstage/backend-plugin-api@npm:^1.0.2, @backstage/backend-plugin-api@npm:^1.1.1, @backstage/backend-plugin-api@npm:^1.2.0, @backstage/backend-plugin-api@npm:^1.2.1":
-  version: 1.2.1
-  resolution: "@backstage/backend-plugin-api@npm:1.2.1"
+"@backstage/backend-plugin-api@npm:^1.0.0, @backstage/backend-plugin-api@npm:^1.0.1, @backstage/backend-plugin-api@npm:^1.0.2, @backstage/backend-plugin-api@npm:^1.1.1, @backstage/backend-plugin-api@npm:^1.2.0, @backstage/backend-plugin-api@npm:^1.2.1, @backstage/backend-plugin-api@npm:^1.3.0":
+  version: 1.3.0
+  resolution: "@backstage/backend-plugin-api@npm:1.3.0"
   dependencies:
     "@backstage/cli-common": ^0.1.15
     "@backstage/config": ^1.3.2
     "@backstage/errors": ^1.2.7
-    "@backstage/plugin-auth-node": ^0.6.1
+    "@backstage/plugin-auth-node": ^0.6.2
     "@backstage/plugin-permission-common": ^0.8.4
-    "@backstage/plugin-permission-node": ^0.9.0
+    "@backstage/plugin-permission-node": ^0.9.1
     "@backstage/types": ^1.2.1
     "@types/express": ^4.17.6
     "@types/luxon": ^3.0.0
     knex: ^3.0.0
     luxon: ^3.0.0
-  checksum: 6f8668404fc0761f34100eb8ac7e992eae77c8af9660b21306f48b71b2b66fbc65505ec284d53634dc460300465e5761a23442b903a557c86738ad9f08b53815
+  checksum: cd702c9f54db8758bda04430a7e46cbf7dda0ce2bf77653f9a1eeb7a750d90324fef2d63d19e662c80e1c75da419bbd52a2235683132845678e446ca41885654
   languageName: node
   linkType: hard
 
@@ -4039,16 +4039,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@backstage/plugin-auth-backend-module-gitlab-provider@npm:^0.3.1":
-  version: 0.3.1
-  resolution: "@backstage/plugin-auth-backend-module-gitlab-provider@npm:0.3.1"
+"@backstage/plugin-auth-backend-module-gitlab-provider@npm:^0.3.1, @backstage/plugin-auth-backend-module-gitlab-provider@npm:^0.3.2":
+  version: 0.3.2
+  resolution: "@backstage/plugin-auth-backend-module-gitlab-provider@npm:0.3.2"
   dependencies:
-    "@backstage/backend-plugin-api": ^1.2.1
-    "@backstage/plugin-auth-node": ^0.6.1
+    "@backstage/backend-plugin-api": ^1.3.0
+    "@backstage/plugin-auth-node": ^0.6.2
     express: ^4.18.2
     passport: ^0.7.0
     passport-gitlab2: ^5.0.0
-  checksum: 1d7790f52f36c7792b7f34f13b6d9217acb61a76a0930fb2620797b82308b17e528e7973b6de59ac62c6ca4e3d9ee6e96d12b93a0be0d365ebd583845b61ca75
+  checksum: c4bfbd25e0894a2cac795f28d03791461e3f5d0423b55178c0bcb107b1ba73379d0fca177c32639d9ad443d02e26daaccf4355762977e89ed4d2a154c804aee9
   languageName: node
   linkType: hard
 
@@ -4244,11 +4244,11 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@backstage/plugin-auth-node@npm:^0.6.0, @backstage/plugin-auth-node@npm:^0.6.1":
-  version: 0.6.1
-  resolution: "@backstage/plugin-auth-node@npm:0.6.1"
+"@backstage/plugin-auth-node@npm:^0.6.0, @backstage/plugin-auth-node@npm:^0.6.1, @backstage/plugin-auth-node@npm:^0.6.2":
+  version: 0.6.2
+  resolution: "@backstage/plugin-auth-node@npm:0.6.2"
   dependencies:
-    "@backstage/backend-plugin-api": ^1.2.1
+    "@backstage/backend-plugin-api": ^1.3.0
     "@backstage/catalog-client": ^1.9.1
     "@backstage/catalog-model": ^1.7.3
     "@backstage/config": ^1.3.2
@@ -4263,7 +4263,7 @@ __metadata:
     zod: ^3.22.4
     zod-to-json-schema: ^3.21.4
     zod-validation-error: ^3.4.0
-  checksum: 3ea509a085cf864138819658608e53978ce7847303e021679a25a23a1a241daf15c95c4e14f1e823a833a89dbd0a462e35e428a40493875b8488be77ed3583af
+  checksum: 322c0218046a6283853527280f86f8fbd7f1b7b26fc1b4cdc15f05da78f78523b0e7d482009970cf8a8ebf5d67fd5a107f3657fbc848b807e08572051c328b3c
   languageName: node
   linkType: hard
 
@@ -4817,21 +4817,21 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@backstage/plugin-permission-node@npm:^0.9.0":
-  version: 0.9.0
-  resolution: "@backstage/plugin-permission-node@npm:0.9.0"
+"@backstage/plugin-permission-node@npm:^0.9.0, @backstage/plugin-permission-node@npm:^0.9.1":
+  version: 0.9.1
+  resolution: "@backstage/plugin-permission-node@npm:0.9.1"
   dependencies:
-    "@backstage/backend-plugin-api": ^1.2.1
+    "@backstage/backend-plugin-api": ^1.3.0
     "@backstage/config": ^1.3.2
     "@backstage/errors": ^1.2.7
-    "@backstage/plugin-auth-node": ^0.6.1
+    "@backstage/plugin-auth-node": ^0.6.2
     "@backstage/plugin-permission-common": ^0.8.4
     "@types/express": ^4.17.6
     express: ^4.17.1
     express-promise-router: ^4.1.0
     zod: ^3.22.4
     zod-to-json-schema: ^3.20.4
-  checksum: c78374ebbb2de9b84b74289be8f06b35b8824e97415661d5310e22d4c4bae0f8f8cb2baf3d811898652b1667991819b2853122cdaf1691d33eef79410a1a8342
+  checksum: 13da8f40dcf913367e826e148d36469049736010549640eaf91a0e5e2d534d6de523f69db88e3ae9c29dcc861f7ef57f97806d6d1a3213aca2672518ea34d111
   languageName: node
   linkType: hard
 
@@ -11485,6 +11485,7 @@ __metadata:
     "@testing-library/jest-dom": ^6.0.0
     "@testing-library/react": ^14.0.0
     "@testing-library/user-event": ^14.0.0
+    express: ^5.1.0
     json-schema: ^0.4.0
     lodash: ^4.17.21
     msw: ^1.0.0
@@ -15812,6 +15813,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"accepts@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "accepts@npm:2.0.0"
+  dependencies:
+    mime-types: ^3.0.0
+    negotiator: ^1.0.0
+  checksum: 49fe6c050cb6f6ff4e771b4d88324fca4d3127865f2473872e818dca127d809ba3aa8fdfc7acb51dd3c5bade7311ca6b8cfff7015ea6db2f7eb9c8444d223a4f
+  languageName: node
+  linkType: hard
+
 "acorn-globals@npm:^7.0.0":
   version: 7.0.1
   resolution: "acorn-globals@npm:7.0.1"
@@ -16960,6 +16971,7 @@ __metadata:
     "@backstage/plugin-app-backend": ^0.5.0
     "@backstage/plugin-auth-backend": ^0.24.4
     "@backstage/plugin-auth-backend-module-github-provider": ^0.3.1
+    "@backstage/plugin-auth-backend-module-gitlab-provider": ^0.3.2
     "@backstage/plugin-auth-backend-module-guest-provider": ^0.2.6
     "@backstage/plugin-auth-node": ^0.6.1
     "@backstage/plugin-catalog-backend": ^1.32.0
@@ -17286,6 +17298,23 @@ __metadata:
   languageName: node
   linkType: hard
 
+"body-parser@npm:^2.2.0":
+  version: 2.2.0
+  resolution: "body-parser@npm:2.2.0"
+  dependencies:
+    bytes: ^3.1.2
+    content-type: ^1.0.5
+    debug: ^4.4.0
+    http-errors: ^2.0.0
+    iconv-lite: ^0.6.3
+    on-finished: ^2.4.1
+    qs: ^6.14.0
+    raw-body: ^3.0.0
+    type-is: ^2.0.0
+  checksum: 7fe3a2d288f0b632528d6ccb90052d1a9492c5b79d5716d32c8de1f5fb8237b0d31ee5050e1d0b7ff143a492ff151804612c6e2686a222a1d4c9e2e6531b8fb2
+  languageName: node
+  linkType: hard
+
 "bonjour-service@npm:^1.0.11, bonjour-service@npm:^1.2.1":
   version: 1.3.0
   resolution: "bonjour-service@npm:1.3.0"
@@ -17611,7 +17640,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"bytes@npm:3.1.2":
+"bytes@npm:3.1.2, bytes@npm:^3.1.2":
   version: 3.1.2
   resolution: "bytes@npm:3.1.2"
   checksum: e4bcd3948d289c5127591fbedf10c0b639ccbf00243504e4e127374a15c3bc8eed0d28d4aaab08ff6f1cf2abc0cce6ba3085ed32f4f90e82a5683ce0014e1b6e
@@ -18638,6 +18667,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"content-disposition@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "content-disposition@npm:1.0.0"
+  dependencies:
+    safe-buffer: 5.2.1
+  checksum: b27e2579fefe0ecf78238bb652fbc750671efce8344f0c6f05235b12433e6a965adb40906df1ac1fdde23e8f9f0e58385e44640e633165420f3f47d830ae0398
+  languageName: node
+  linkType: hard
+
 "content-type@npm:^1.0.4, content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5":
   version: 1.0.5
   resolution: "content-type@npm:1.0.5"
@@ -18683,6 +18721,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"cookie-signature@npm:^1.2.1":
+  version: 1.2.2
+  resolution: "cookie-signature@npm:1.2.2"
+  checksum: 1ad4f9b3907c9f3673a0f0a07c0a23da7909ac6c9204c5d80a0ec102fe50ccc45f27fdf496361840d6c132c5bb0037122c0a381f856d070183d1ebe3e5e041ff
+  languageName: node
+  linkType: hard
+
 "cookie@npm:0.7.1":
   version: 0.7.1
   resolution: "cookie@npm:0.7.1"
@@ -18690,7 +18735,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:~0.7.2":
+"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:~0.7.2":
   version: 0.7.2
   resolution: "cookie@npm:0.7.2"
   checksum: 9bf8555e33530affd571ea37b615ccad9b9a34febbf2c950c86787088eb00a8973690833b0f8ebd6b69b753c62669ea60cec89178c1fb007bf0749abed74f93e
@@ -19585,15 +19630,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5":
-  version: 4.3.7
-  resolution: "debug@npm:4.3.7"
+"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0":
+  version: 4.4.0
+  resolution: "debug@npm:4.4.0"
   dependencies:
     ms: ^2.1.3
   peerDependenciesMeta:
     supports-color:
       optional: true
-  checksum: 822d74e209cd910ef0802d261b150314bbcf36c582ccdbb3e70f0894823c17e49a50d3e66d96b633524263975ca16b6a833f3e3b7e030c157169a5fabac63160
+  checksum: fb42df878dd0e22816fc56e1fdca9da73caa85212fbe40c868b1295a6878f9101ae684f4eeef516c13acfc700f5ea07f1136954f43d4cd2d477a811144136479
   languageName: node
   linkType: hard
 
@@ -20420,7 +20465,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"encodeurl@npm:~2.0.0":
+"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0":
   version: 2.0.0
   resolution: "encodeurl@npm:2.0.0"
   checksum: abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe
@@ -21561,7 +21606,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"etag@npm:~1.8.1":
+"etag@npm:^1.8.1, etag@npm:~1.8.1":
   version: 1.8.1
   resolution: "etag@npm:1.8.1"
   checksum: 571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff
@@ -21762,6 +21807,41 @@ __metadata:
   languageName: node
   linkType: hard
 
+"express@npm:^5.1.0":
+  version: 5.1.0
+  resolution: "express@npm:5.1.0"
+  dependencies:
+    accepts: ^2.0.0
+    body-parser: ^2.2.0
+    content-disposition: ^1.0.0
+    content-type: ^1.0.5
+    cookie: ^0.7.1
+    cookie-signature: ^1.2.1
+    debug: ^4.4.0
+    encodeurl: ^2.0.0
+    escape-html: ^1.0.3
+    etag: ^1.8.1
+    finalhandler: ^2.1.0
+    fresh: ^2.0.0
+    http-errors: ^2.0.0
+    merge-descriptors: ^2.0.0
+    mime-types: ^3.0.0
+    on-finished: ^2.4.1
+    once: ^1.4.0
+    parseurl: ^1.3.3
+    proxy-addr: ^2.0.7
+    qs: ^6.14.0
+    range-parser: ^1.2.1
+    router: ^2.2.0
+    send: ^1.1.0
+    serve-static: ^2.2.0
+    statuses: ^2.0.1
+    type-is: ^2.0.1
+    vary: ^1.1.2
+  checksum: 06e6141780c6c4780111f971ce062c83d4cf4862c40b43caf1d95afcbb58d7422c560503b8c9d04c7271511525d09cbdbe940bcaad63970fd4c1b9f6fd713bdb
+  languageName: node
+  linkType: hard
+
 "extend@npm:3.0.2, extend@npm:^3.0.0, extend@npm:^3.0.2, extend@npm:~3.0.2":
   version: 3.0.2
   resolution: "extend@npm:3.0.2"
@@ -22141,6 +22221,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"finalhandler@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "finalhandler@npm:2.1.0"
+  dependencies:
+    debug: ^4.4.0
+    encodeurl: ^2.0.0
+    escape-html: ^1.0.3
+    on-finished: ^2.4.1
+    parseurl: ^1.3.3
+    statuses: ^2.0.1
+  checksum: 27ca9cc83b1384ba37959eb95bc7e62bc0bf4d6f6af63f6d38821cf7499b113e34b23f96a2a031616817f73986f94deea67c2f558de9daf406790c181a2501df
+  languageName: node
+  linkType: hard
+
 "find-file-up@npm:^2.0.1":
   version: 2.0.1
   resolution: "find-file-up@npm:2.0.1"
@@ -22492,6 +22586,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"fresh@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "fresh@npm:2.0.0"
+  checksum: 38b9828352c6271e2a0dd8bdd985d0100dbbc4eb8b6a03286071dd6f7d96cfaacd06d7735701ad9a95870eb3f4555e67c08db1dcfe24c2e7bb87383c72fae1d2
+  languageName: node
+  linkType: hard
+
 "fromentries@npm:^1.3.1":
   version: 1.3.2
   resolution: "fromentries@npm:1.3.2"
@@ -23683,7 +23784,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"http-errors@npm:2.0.0":
+"http-errors@npm:2.0.0, http-errors@npm:^2.0.0":
   version: 2.0.0
   resolution: "http-errors@npm:2.0.0"
   dependencies:
@@ -27486,6 +27587,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"merge-descriptors@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "merge-descriptors@npm:2.0.0"
+  checksum: e383332e700a94682d0125a36c8be761142a1320fc9feeb18e6e36647c9edf064271645f5669b2c21cf352116e561914fd8aa831b651f34db15ef4038c86696a
+  languageName: node
+  linkType: hard
+
 "merge-stream@npm:^2.0.0":
   version: 2.0.0
   resolution: "merge-stream@npm:2.0.0"
@@ -27877,10 +27985,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mime-db@npm:>= 1.43.0 < 2":
-  version: 1.53.0
-  resolution: "mime-db@npm:1.53.0"
-  checksum: 3fd9380bdc0b085d0b56b580e4f89ca4fc3b823722310d795c248f0806b9a80afd5d8f4347f015ad943b9ecfa7cc0b71dffa0db96fa776d01a13474821a2c7fb
+"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.54.0":
+  version: 1.54.0
+  resolution: "mime-db@npm:1.54.0"
+  checksum: e99aaf2f23f5bd607deb08c83faba5dd25cf2fec90a7cc5b92d8260867ee08dab65312e1a589e60093dc7796d41e5fae013268418482f1db4c7d52d0a0960ac9
   languageName: node
   linkType: hard
 
@@ -27893,6 +28001,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "mime-types@npm:3.0.1"
+  dependencies:
+    mime-db: ^1.54.0
+  checksum: 8d497ad5cb2dd1210ac7d049b5de94af0b24b45a314961e145b44389344604d54752f03bc00bf880c0da60a214be6fb6d423d318104f02c28d95dd8ebeea4fb4
+  languageName: node
+  linkType: hard
+
 "mime@npm:1.6.0":
   version: 1.6.0
   resolution: "mime@npm:1.6.0"
@@ -28572,6 +28689,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"negotiator@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "negotiator@npm:1.0.0"
+  checksum: 20ebfe79b2d2e7cf9cbc8239a72662b584f71164096e6e8896c8325055497c96f6b80cd22c258e8a2f2aa382a787795ec3ee8b37b422a302c7d4381b0d5ecfbb
+  languageName: node
+  linkType: hard
+
 "neo-async@npm:^2.6.2":
   version: 2.6.2
   resolution: "neo-async@npm:2.6.2"
@@ -29829,7 +29953,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"parseurl@npm:^1.3.2, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3":
+"parseurl@npm:^1.3.2, parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3":
   version: 1.3.3
   resolution: "parseurl@npm:1.3.3"
   checksum: 407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2
@@ -31168,7 +31292,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"proxy-addr@npm:~2.0.7":
+"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7":
   version: 2.0.7
   resolution: "proxy-addr@npm:2.0.7"
   dependencies:
@@ -31283,7 +31407,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.9.3, qs@npm:^6.9.4":
+"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.9.3, qs@npm:^6.9.4":
   version: 6.14.0
   resolution: "qs@npm:6.14.0"
   dependencies:
@@ -31450,6 +31574,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"raw-body@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "raw-body@npm:3.0.0"
+  dependencies:
+    bytes: 3.1.2
+    http-errors: 2.0.0
+    iconv-lite: 0.6.3
+    unpipe: 1.0.0
+  checksum: 25b7cf7964183db322e819050d758a5abd0f22c51e9f37884ea44a9ed6855a1fb61f8caa8ec5b61d07e69f54db43dbbc08ad98ef84556696d6aa806be247af0e
+  languageName: node
+  linkType: hard
+
 "raw-loader@npm:^4.0.2":
   version: 4.0.2
   resolution: "raw-loader@npm:4.0.2"
@@ -33016,6 +33152,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"router@npm:^2.2.0":
+  version: 2.2.0
+  resolution: "router@npm:2.2.0"
+  dependencies:
+    debug: ^4.4.0
+    depd: ^2.0.0
+    is-promise: ^4.0.0
+    parseurl: ^1.3.3
+    path-to-regexp: ^8.0.0
+  checksum: 4c3bec8011ed10bb07d1ee860bc715f245fff0fdff991d8319741d2932d89c3fe0a56766b4fa78e95444bc323fd2538e09c8e43bfbd442c2a7fab67456df7fa5
+  languageName: node
+  linkType: hard
+
 "rrweb-cssom@npm:^0.8.0":
   version: 0.8.0
   resolution: "rrweb-cssom@npm:0.8.0"
@@ -33328,6 +33477,25 @@ __metadata:
   languageName: node
   linkType: hard
 
+"send@npm:^1.1.0, send@npm:^1.2.0":
+  version: 1.2.0
+  resolution: "send@npm:1.2.0"
+  dependencies:
+    debug: ^4.3.5
+    encodeurl: ^2.0.0
+    escape-html: ^1.0.3
+    etag: ^1.8.1
+    fresh: ^2.0.0
+    http-errors: ^2.0.0
+    mime-types: ^3.0.1
+    ms: ^2.1.3
+    on-finished: ^2.4.1
+    range-parser: ^1.2.1
+    statuses: ^2.0.1
+  checksum: 7557ee6c1c257a1c53b402b4fba8ed88c95800b08abe085fc79e0824869274f213491be2efb2df3de228c70e4d40ce2019e5f77b58c42adb97149135420c3f34
+  languageName: node
+  linkType: hard
+
 "seq-queue@npm:^0.0.5":
   version: 0.0.5
   resolution: "seq-queue@npm:0.0.5"
@@ -33389,6 +33557,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"serve-static@npm:^2.2.0":
+  version: 2.2.0
+  resolution: "serve-static@npm:2.2.0"
+  dependencies:
+    encodeurl: ^2.0.0
+    escape-html: ^1.0.3
+    parseurl: ^1.3.3
+    send: ^1.2.0
+  checksum: 74f39e88f0444aa6732aae3b9597739c47552adecdc83fa32aa42555e76f1daad480d791af73894655c27a2d378275a461e691cead33fb35d8b976f1e2d24665
+  languageName: node
+  linkType: hard
+
 "set-blocking@npm:^2.0.0":
   version: 2.0.0
   resolution: "set-blocking@npm:2.0.0"
@@ -34094,7 +34274,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"statuses@npm:2.0.1":
+"statuses@npm:2.0.1, statuses@npm:^2.0.1":
   version: 2.0.1
   resolution: "statuses@npm:2.0.1"
   checksum: 18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb
@@ -35633,6 +35813,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"type-is@npm:^2.0.0, type-is@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "type-is@npm:2.0.1"
+  dependencies:
+    content-type: ^1.0.5
+    media-typer: ^1.1.0
+    mime-types: ^3.0.0
+  checksum: 0266e7c782238128292e8c45e60037174d48c6366bb2d45e6bd6422b611c193f83409a8341518b6b5f33f8e4d5a959f38658cacfea77f0a3505b9f7ac1ddec8f
+  languageName: node
+  linkType: hard
+
 "typed-array-buffer@npm:^1.0.3":
   version: 1.0.3
   resolution: "typed-array-buffer@npm:1.0.3"


Reviews:
user: login: mareklibra
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T13:15:18Z
user: login: batzionb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T12:34:38Z
user: login: batzionb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T13:34:20Z
user: login: mareklibra
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T06:58:04Z
user: login: mareklibra
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T10:35:53Z
user: login: batzionb
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T11:32:11Z

Review comments:
diff_hunk: @@ -38,25 +38,31 @@ backend:
   #   dangerouslyDisableDefaultAuthPolicy: true
   # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
 
-integrations: {}
-#integrations:
-#  github:
-#    - host: github.com
-#      token: ${GITHUB_TOKEN}
-#  gitlab:
-#    - host: gitlab.com
-#      token: ${GITLAB_TOKEN}
+#integrations: {}
+integrations:
+  #  github:
+  #    - host: github.com
+  #      token: ${GITHUB_TOKEN}
+  gitlab:
path: workspaces/orchestrator/app-config.yaml
user: login: mareklibra
body: draft only - will comment-out again
created_at: 2025-04-29T13:15:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,50 @@
+# Development-only HTTP server
+
+The purpose of this Express Node.js server is to respond with JSON Schema chunks and other test data to ease development of the active widgets, like the `SchemaUpdater`.
+
+The production environment is expected to provide another HTTP server (unrelated to this testing one) dynamically providing `SchemaUpdater` snippets or fetch/validation functionality for other widgets.
+
path: workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/README.md
user: login: batzionb
body: I think the documentation should include how to create the workflow that users the server, by running yarn "update-running-workflow" and mention that it'll only work with the dev mode
created_at: 2025-05-05T12:34:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+{
+  "$id": "classpath:/schemas/dynamic_schema__main-schema.json",
+  "title": "Data Input Schema",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "type": "object",
+  "properties": {
+    "firstStep": {
+      "type": "object",
+      "title": "Example use",
+      "properties": {
+        "fooTheFirst": {
+          "type": "string",
+          "title": "First page simple input field"
+        },
+        "mySchemaUpdater": {
+          "type": "string",
+          "ui:widget": "SchemaUpdater",
+          "ui:props": {
+            "fetch:url": "http://localhost:7007/api/proxy/mytesthttpserver/chunk01",
+            "fetch:headers": {
+              "test-header": "Executed for the $${{identityApi.displayName}} user with $${{gitlabAuthApi.profileName}} gitlab profile name."
+            }
+          }
+        },
+        "placeholderOne": {
+          "type": "string",
+          "title": "This field and its title are visible until replaced by any SchemaUpdater."
+        }
+      }
+    },
+    "secondStep": {
+      "type": "object",
+      "title": "Second page of the wizard",
+      "properties": {
+        "myTextField": {
+          "type": "string",
+          "title": "Provide name of the next component. Try 'password' or 'complex'"
+        },
+        "addressOrPassword": {
+          "type": "string",
+          "title": "This title is not rendered. Will be replaced by myAnotherSchemaUpdater",
+          "ui:widget": "hidden"
+        },
+        "placeholderThree": {
+          "type": "object",
+          "ui:widget": "hidden"
+        },
+        "myAnotherSchemaUpdater": {
+          "type": "string",
+          "ui:widget": "SchemaUpdater",
+          "ui:props": {
+            "fetch:url": "http://localhost:7007/api/proxy/mytesthttpserver/chunk02",
+            "fetch:method": "POST",
+            "fetch:headers": {
+              "not-used-but-anyway": "$${{githubAuthApi.token}}"
path: workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/exampleWorkflows/schemas/dynamic_schema__main-schema.json
user: login: batzionb
body: unify auth provider to make it easier to use
created_at: 2025-05-05T13:34:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+{
+  "$id": "classpath:/schemas/dynamic_schema__main-schema.json",
+  "title": "Data Input Schema",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "type": "object",
+  "properties": {
+    "firstStep": {
+      "type": "object",
+      "title": "Example use",
+      "properties": {
+        "fooTheFirst": {
+          "type": "string",
+          "title": "First page simple input field"
+        },
+        "mySchemaUpdater": {
+          "type": "string",
+          "ui:widget": "SchemaUpdater",
+          "ui:props": {
+            "fetch:url": "http://localhost:7007/api/proxy/mytesthttpserver/chunk01",
+            "fetch:headers": {
+              "test-header": "Executed for the $${{identityApi.displayName}} user with $${{gitlabAuthApi.profileName}} gitlab profile name."
+            }
+          }
+        },
+        "placeholderOne": {
+          "type": "string",
+          "title": "This field and its title are visible until replaced by any SchemaUpdater."
+        }
+      }
+    },
+    "secondStep": {
+      "type": "object",
+      "title": "Second page of the wizard",
+      "properties": {
+        "myTextField": {
+          "type": "string",
+          "title": "Provide name of the next component. Try 'password' or 'complex'"
+        },
+        "addressOrPassword": {
+          "type": "string",
+          "title": "This title is not rendered. Will be replaced by myAnotherSchemaUpdater",
+          "ui:widget": "hidden"
+        },
+        "placeholderThree": {
+          "type": "object",
+          "ui:widget": "hidden"
+        },
+        "myAnotherSchemaUpdater": {
+          "type": "string",
+          "ui:widget": "SchemaUpdater",
+          "ui:props": {
+            "fetch:url": "http://localhost:7007/api/proxy/mytesthttpserver/chunk02",
+            "fetch:method": "POST",
+            "fetch:headers": {
+              "not-used-but-anyway": "$${{githubAuthApi.token}}"
path: workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/exampleWorkflows/schemas/dynamic_schema__main-schema.json
user: login: mareklibra
body: We can do it for simplicity now, since the other providers have already been tested. Using GitHub
created_at: 2025-05-06T06:58:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,50 @@
+# Development-only HTTP server
+
+The purpose of this Express Node.js server is to respond with JSON Schema chunks and other test data to ease development of the active widgets, like the `SchemaUpdater`.
+
+The production environment is expected to provide another HTTP server (unrelated to this testing one) dynamically providing `SchemaUpdater` snippets or fetch/validation functionality for other widgets.
+
path: workspaces/orchestrator/plugins/orchestrator-form-widgets/http-test-server/README.md
user: login: mareklibra
body: Added a commit to show the direction. We will handle the documentation within [FLPATH-2291](https://issues.redhat.com/browse/FLPATH-2291)
created_at: 2025-05-06T10:35:53Z
author_association: MEMBER

PR comments:
user: login: rhdh-gh-app[bot]
created_at: 2025-04-29T13:14:38Z
body: ## Changed Packages

| Package Name | Package Path | Changeset Bump | Current Version |
|:-------------|:-------------|:--------------:|:----------------|
| app | workspaces/orchestrator/packages/app | **none** | `v0.0.3` |
| backend | workspaces/orchestrator/packages/backend | **none** | `v0.0.0` |
| @red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets | workspaces/orchestrator/plugins/orchestrator-form-widgets | **patch** | `v0.1.0` |


<!-- changeset-feedback -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-06T10:37:13Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=redhat-developer_rhdh-plugins&pullRequest=701) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&pullRequest=701&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&pullRequest=701&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=redhat-developer_rhdh-plugins&pullRequest=701&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=redhat-developer_rhdh-plugins&pullRequest=701&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.4% Duplication on New Code](https://sonarcloud.io/component_measures?id=redhat-developer_rhdh-plugins&pullRequest=701&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=redhat-developer_rhdh-plugins&pullRequest=701)


author_association: NONE