This is an automated email from the ASF dual-hosted git repository.

thiagoelg pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git


The following commit(s) were added to refs/heads/main by this push:
     new af6dbf7f888 kie-issue#2216: Dev Deployments: Support recursive/nested 
token interpolation and remove non-standard JSON Patch operation (#3403)
af6dbf7f888 is described below

commit af6dbf7f888716738566ba147053dc6d54869b9b
Author: Adarsh vk <[email protected]>
AuthorDate: Thu Feb 5 19:02:30 2026 +0530

    kie-issue#2216: Dev Deployments: Support recursive/nested token 
interpolation and remove non-standard JSON Patch operation (#3403)
---
 packages/k8s-yaml-to-apiserver-requests/README.md  |   6 +-
 .../jest.config.js}                                |  35 +-
 .../k8s-yaml-to-apiserver-requests/package.json    |  13 +-
 .../src/interpolateK8sResourceYaml.ts              | 140 +++-
 .../src/patchK8sResourceYaml.ts                    |  46 +-
 .../tests/jsonPatch/fixtures.ts                    | 727 +++++++++++++++++++++
 .../tests/jsonPatch/patchK8sResourceYaml.test.ts   |  51 ++
 .../tests/tokenInterpolation/fixtures.ts           | 363 ++++++++++
 .../interpolateK8sResourceYaml.test.ts             |  61 ++
 .../tsconfig.tests.json                            |   3 +
 .../devDeployments/services/KubernetesService.ts   |   3 +-
 .../KieSandboxDevDeploymentRequiredPatches.ts      |  26 +-
 .../customImage/DeploymentYaml.ts                  |  24 +-
 .../deploymentOptions/customImage/IngressYaml.ts   |  18 +-
 .../deploymentOptions/customImage/RouteYaml.ts     |  18 +-
 .../deploymentOptions/customImage/ServiceYaml.ts   |  18 +-
 .../quarkusBlankApp/DeploymentYaml.ts              |  28 +-
 .../quarkusBlankApp/FormWebappIngressYaml.ts       |  18 +-
 .../quarkusBlankApp/FormWebappRouteYaml.ts         |  18 +-
 .../quarkusBlankApp/FormWebappServiceYaml.ts       |  18 +-
 .../quarkusBlankApp/IngressYaml.ts                 |  18 +-
 .../deploymentOptions/quarkusBlankApp/RouteYaml.ts |  18 +-
 .../quarkusBlankApp/ServiceYaml.ts                 |  18 +-
 .../kubernetes/KubernetesDeploymentOptions.ts      |  12 +-
 .../openshift/OpenShiftDeploymentOptions.ts        |   2 +-
 packages/online-editor/webpack.config.ts           |   4 +
 pnpm-lock.yaml                                     |  32 +-
 repo/graph.dot                                     |   1 +
 repo/graph.json                                    |   5 +
 29 files changed, 1538 insertions(+), 206 deletions(-)

diff --git a/packages/k8s-yaml-to-apiserver-requests/README.md 
b/packages/k8s-yaml-to-apiserver-requests/README.md
index 39a86e2e025..8d9615a82c0 100644
--- a/packages/k8s-yaml-to-apiserver-requests/README.md
+++ b/packages/k8s-yaml-to-apiserver-requests/README.md
@@ -21,7 +21,11 @@ Library to map Kubernetes API resources and apply resource 
YAMLs
 
 ## Features
 
-- interpolateK8sResourceYamls(): Given an YAML and a tokens map, interpolate 
the YAML content with values provided by the token map. Tokens are represented 
with `${{token.path.name}}`.
+- interpolateK8sResourceYamls(): Given a YAML and a tokens map, interpolate 
the YAML content with values provided by the token map. Tokens are represented 
with `${{ keyName }}` for simple flat keys or `${{ $.path.to.value }}` for JSON 
path queries. Supports recursive/nested token references. Uses [RFC 
9535](https://datatracker.ietf.org/doc/html/rfc9535) compliant JSON path syntax 
via [`jsonpath-rfc9535`](https://www.npmjs.com/package/jsonpath-rfc9535).
+  - Example (simple): `${{ serviceName }}` or `${{ $.serviceName }}`
+  - Example (JSON path nested): `${{ $.deployment.metadata.name }}`
+  - Example (recursive/nested): `${{ $.resources['${{ $.resourceType }}'].host 
}}`
+- patchK8sResourceYaml(): Apply JSON Patch operations to Kubernetes resource 
YAMLs with token interpolation support.
 - parseK8sResourceYaml(): Given a YAML file, return an array of JSON objects 
parsed.
 - buildK8sApiServerEndpointsByResourceKind(): Generates a map of Kubernetes 
resources and their API endpoints.
 - callK8sApiServer(): Make the requests to the Kubernetes APIServer in order 
to create given resources.
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/ServiceYaml.ts
 b/packages/k8s-yaml-to-apiserver-requests/jest.config.js
similarity index 53%
copy from 
packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/ServiceYaml.ts
copy to packages/k8s-yaml-to-apiserver-requests/jest.config.js
index 4fc2184d088..cd848227e0f 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/ServiceYaml.ts
+++ b/packages/k8s-yaml-to-apiserver-requests/jest.config.js
@@ -17,27 +17,14 @@
  * under the License.
  */
 
-export function ServiceYaml() {
-  return `
-kind: Service
-apiVersion: v1
-metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
-  labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
-spec:
-  ports:
-    - protocol: TCP
-      port: 8080
-      targetPort: 8080
-  selector:
-    app: \${{ devDeployment.uniqueName }}
-    deploymentconfig: \${{ devDeployment.uniqueName }}
-  type: ClusterIP
-`;
-}
+const { config, babelTransform, typescriptTransform } = 
require("@kie-tools/jest-base/jest.config");
+
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+  ...config,
+  testEnvironment: "node",
+  transform: {
+    ...babelTransform,
+    ...typescriptTransform,
+  },
+};
diff --git a/packages/k8s-yaml-to-apiserver-requests/package.json 
b/packages/k8s-yaml-to-apiserver-requests/package.json
index 231409be193..2527dbb3d5b 100644
--- a/packages/k8s-yaml-to-apiserver-requests/package.json
+++ b/packages/k8s-yaml-to-apiserver-requests/package.json
@@ -18,13 +18,15 @@
   ],
   "scripts": {
     "build:dev": "rimraf dist && tsc -p tsconfig.json",
-    "build:prod": "pnpm lint && rimraf dist && tsc -p tsconfig.json",
+    "build:prod": "pnpm lint && rimraf dist && tsc -p tsconfig.json && pnpm 
test",
     "lint": "run-script-if --bool \"$(build-env linters.run)\" --then 
\"kie-tools--eslint ./src\"",
-    "start": "ts-node ./dev/main.ts"
+    "start": "ts-node ./dev/main.ts",
+    "test": "run-script-if --ignore-errors \"$(build-env 
tests.ignoreFailures)\" --bool \"$(build-env tests.run)\" --then \"jest 
--silent --verbose --passWithNoTests\""
   },
   "dependencies": {
     "fast-json-patch": "^3.1.1",
-    "js-yaml": "^4.1.1"
+    "js-yaml": "^4.1.1",
+    "jsonpath-rfc9535": "^1.3.0"
   },
   "devDependencies": {
     "@babel/core": "^7.16.0",
@@ -32,10 +34,15 @@
     "@babel/preset-react": "^7.16.0",
     "@kie-tools/cors-proxy-api": "workspace:*",
     "@kie-tools/eslint": "workspace:*",
+    "@kie-tools/jest-base": "workspace:*",
     "@kie-tools/root-env": "workspace:*",
     "@kie-tools/tsconfig": "workspace:*",
+    "@types/jest": "^29.5.12",
     "@types/js-yaml": "^4.0.5",
+    "jest": "^29.7.0",
+    "jest-junit": "^16.0.0",
     "rimraf": "^3.0.2",
+    "ts-jest": "^29.1.5",
     "ts-node": "^10.9.2",
     "typescript": "^5.5.3"
   }
diff --git 
a/packages/k8s-yaml-to-apiserver-requests/src/interpolateK8sResourceYaml.ts 
b/packages/k8s-yaml-to-apiserver-requests/src/interpolateK8sResourceYaml.ts
index 44312a82311..642923671ae 100644
--- a/packages/k8s-yaml-to-apiserver-requests/src/interpolateK8sResourceYaml.ts
+++ b/packages/k8s-yaml-to-apiserver-requests/src/interpolateK8sResourceYaml.ts
@@ -17,8 +17,10 @@
  * under the License.
  */
 
+import { query as jsonPathQuery } from "jsonpath-rfc9535";
+
 export type TokenMap = {
-  [x: string]: string | number | boolean | TokenMap;
+  [x: string]: string | number | boolean | TokenMap | TokenMap[];
 };
 
 type FlattenedTokenMap = Record<string, string>;
@@ -28,6 +30,10 @@ const TEMPLATE = {
   CLOSE: "}}",
 } as const;
 
+const MAX_INTERPOLATION_DEPTH = 50;
+
+const TOKEN_REGEX_TEST = new 
RegExp(`${escapeRegex(TEMPLATE.OPEN)}[\\s\\S]*?${escapeRegex(TEMPLATE.CLOSE)}`);
+
 function escapeRegex(regexInput: string) {
   return regexInput.replace(/[/\-\\^$*+?.()|[\]{}]/g, "\\$&");
 }
@@ -68,17 +74,143 @@ function trimTokensFromInputText(inputText: string, 
flattenedTokens: FlattenedTo
   return trimmedInputText;
 }
 
-export function interpolateK8sResourceYaml(k8sResourceYaml: string, tokenMap?: 
TokenMap) {
+function extractTokenBody(token: string): string {
+  const openLen = TEMPLATE.OPEN.length;
+  const closeLen = TEMPLATE.CLOSE.length;
+  return token.slice(openLen, token.length - closeLen).trim();
+}
+
+function containsToken(input: string): boolean {
+  TOKEN_REGEX_TEST.lastIndex = 0;
+  return TOKEN_REGEX_TEST.test(input);
+}
+
+/**
+ * Finds the end position of a balanced token starting at startPos.
+ * Handles nested ${{ }} by counting depth.
+ */
+function findBalancedTokenEnd(input: string, startPos: number): number {
+  const openLen = TEMPLATE.OPEN.length;
+  const closeLen = TEMPLATE.CLOSE.length;
+  let depth = 0;
+  let i = startPos;
+
+  while (i < input.length) {
+    if (input.substring(i, i + openLen) === TEMPLATE.OPEN) {
+      depth++;
+      i += openLen;
+    } else if (input.substring(i, i + closeLen) === TEMPLATE.CLOSE) {
+      depth--;
+      if (depth === 0) {
+        return i + closeLen;
+      }
+      i += closeLen;
+    } else {
+      i++;
+    }
+  }
+  return -1; // Unbalanced
+}
+
+function evaluateJsonPath(jsonPath: string, tokenMap: TokenMap): string | 
undefined {
+  const result = jsonPathQuery(tokenMap, jsonPath);
+
+  if (result.length === 0) {
+    return undefined;
+  }
+
+  const value = result[0];
+
+  if (value === null || value === undefined) {
+    return undefined;
+  }
+
+  return String(value);
+}
+
+function resolveToken(token: string, tokenMap: TokenMap, depth: number, cache: 
Map<string, string>): string {
+  const body = extractTokenBody(token);
+
+  if (cache.has(body)) {
+    return cache.get(body)!;
+  }
+
+  const resolvedBody = containsToken(body) ? interpolateStringRecursive(body, 
tokenMap, depth + 1, cache) : body;
+
+  const value = evaluateJsonPath(resolvedBody, tokenMap);
+  if (value === undefined) {
+    throw new Error(`Unresolvable token: ${resolvedBody}`);
+  }
+
+  const finalValue = containsToken(value) ? interpolateStringRecursive(value, 
tokenMap, depth + 1, cache) : value;
+
+  cache.set(body, finalValue);
+  return finalValue;
+}
+
+function interpolateStringRecursive(
+  input: string,
+  tokenMap: TokenMap,
+  depth: number = 0,
+  cache: Map<string, string> = new Map()
+): string {
+  if (depth >= MAX_INTERPOLATION_DEPTH) {
+    throw new Error(
+      `Max interpolation depth (${MAX_INTERPOLATION_DEPTH}) exceeded. Possible 
circular reference in tokens.`
+    );
+  }
+
+  if (!containsToken(input)) {
+    return input;
+  }
+
+  const parts: string[] = [];
+  let lastIndex = 0;
+  let currentIndex = 0;
+
+  while (currentIndex < input.length) {
+    const openIndex = input.indexOf(TEMPLATE.OPEN, currentIndex);
+
+    if (openIndex === -1) {
+      parts.push(input.substring(lastIndex));
+      break;
+    }
+
+    if (openIndex > lastIndex) {
+      parts.push(input.substring(lastIndex, openIndex));
+    }
+
+    const tokenEnd = findBalancedTokenEnd(input, openIndex);
+    if (tokenEnd === -1) {
+      const preview = input.substring(openIndex, Math.min(openIndex + 50, 
input.length));
+      throw new Error(`Unbalanced token braces at position ${openIndex}: 
"${preview}..."`);
+    }
+
+    const token = input.substring(openIndex, tokenEnd);
+    const resolvedValue = resolveToken(token, tokenMap, depth, cache);
+    parts.push(resolvedValue);
+
+    lastIndex = tokenEnd;
+    currentIndex = tokenEnd;
+  }
+
+  return parts.join("");
+}
+
+export function interpolateK8sResourceYaml(k8sResourceYaml: string, tokenMap?: 
TokenMap): string {
   if (!tokenMap) {
     return k8sResourceYaml;
   }
 
   const flattenedTokens = flattenTokenMap(tokenMap);
-
   const trimmedTokensFromInputText = trimTokensFromInputText(k8sResourceYaml, 
flattenedTokens);
 
-  return Object.entries(flattenedTokens).reduce(
+  let result = Object.entries(flattenedTokens).reduce(
     (result, [tokenName, tokenValue]) => 
result.replaceAll(`${TEMPLATE.OPEN}${tokenName}${TEMPLATE.CLOSE}`, tokenValue),
     trimmedTokensFromInputText
   );
+
+  result = interpolateStringRecursive(result, tokenMap);
+
+  return result;
 }
diff --git 
a/packages/k8s-yaml-to-apiserver-requests/src/patchK8sResourceYaml.ts 
b/packages/k8s-yaml-to-apiserver-requests/src/patchK8sResourceYaml.ts
index d13158d8999..10a2ca6bd62 100644
--- a/packages/k8s-yaml-to-apiserver-requests/src/patchK8sResourceYaml.ts
+++ b/packages/k8s-yaml-to-apiserver-requests/src/patchK8sResourceYaml.ts
@@ -17,23 +17,15 @@
  * under the License.
  */
 
-import { Operation, TestOperation, applyPatch, getValueByPointer } from 
"fast-json-patch";
+import { Operation, TestOperation, applyPatch } from "fast-json-patch";
 import { parseK8sResourceYamls } from "./parseK8sResourceYamls";
 import * as jsYaml from "js-yaml";
 import { TokenMap, interpolateK8sResourceYaml } from 
"./interpolateK8sResourceYaml";
 import { consoleDebugMessage } from "./common";
 
-export type CheckTypeOperation = {
-  path: string;
-  op: "checkType";
-  type: "array" | "object" | "basic" | "null" | "undefined";
-};
-
-export type PatchOperation = Operation | CheckTypeOperation;
-
 export type ResourcePatch = {
   testFilters?: TestOperation<any>[];
-  jsonPatches: PatchOperation[];
+  jsonPatches: Operation[];
 };
 
 // Because the characters '~' (%x7E) and '/' (%x2F) have special
@@ -45,32 +37,6 @@ export function encodeJsonPatchSubpath(path: string) {
   return path.replaceAll("~", "~0").replaceAll("/", "~1");
 }
 
-function isValueOfType(type: CheckTypeOperation["type"], value: any) {
-  switch (type) {
-    case "null":
-    case "undefined":
-      if (!value) {
-        return true;
-      }
-      return false;
-    case "array":
-      if (Array.isArray(value)) {
-        return true;
-      }
-      return false;
-    case "object":
-      if (typeof value === "object" && !Array.isArray(value)) {
-        return true;
-      }
-      return false;
-    case "basic":
-      if (typeof value === "boolean" || typeof value === "string" || typeof 
value === "number") {
-        return true;
-      }
-      return false;
-  }
-}
-
 export function patchK8sResourceYaml(k8sResourceYaml: string, patches: 
ResourcePatch[], parametersTokens?: TokenMap) {
   const parsedAndPatchedYamls = 
parseK8sResourceYamls(k8sResourceYaml.split("\n---\n")).map((resource) => {
     let updatedResource = resource;
@@ -81,14 +47,6 @@ export function patchK8sResourceYaml(k8sResourceYaml: 
string, patches: ResourceP
           : applyPatch(updatedResource, patch.testFilters, false, 
false).every(({ test }) => Boolean(test));
         if (testFiltersResults) {
           for (const jsonPatch of patch.jsonPatches) {
-            if (jsonPatch.op === "checkType") {
-              const value = getValueByPointer(updatedResource, jsonPatch.path);
-              if (isValueOfType(jsonPatch.type, value)) {
-                continue;
-              } else {
-                break;
-              }
-            }
             try {
               const { newDocument } = applyPatch(updatedResource, [jsonPatch], 
false, false);
               updatedResource = newDocument;
diff --git 
a/packages/k8s-yaml-to-apiserver-requests/tests/jsonPatch/fixtures.ts 
b/packages/k8s-yaml-to-apiserver-requests/tests/jsonPatch/fixtures.ts
new file mode 100644
index 00000000000..5ec5ab0429f
--- /dev/null
+++ b/packages/k8s-yaml-to-apiserver-requests/tests/jsonPatch/fixtures.ts
@@ -0,0 +1,727 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { TokenMap } from "../../src/interpolateK8sResourceYaml";
+import { ResourcePatch } from "../../src/patchK8sResourceYaml";
+
+type BaseTestCase = {
+  name: string;
+  given: {
+    yaml: string;
+    patches: ResourcePatch[];
+    tokenMap?: TokenMap;
+  };
+};
+
+type TestCase = BaseTestCase & {
+  expected: string;
+};
+
+type NegativeTestCase = BaseTestCase & {
+  expected: string;
+};
+
+// ADD OPERATION TEST CASES
+
+export const ADD_OPERATION_TEST_CASES: TestCase[] = [
+  {
+    name: "should add a label to existing labels",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels/environment",
+              value: "production",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+    environment: production
+spec:
+  replicas: 1
+`,
+  },
+  {
+    name: "should add multiple labels",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels/environment",
+              value: "production",
+            },
+            {
+              op: "add",
+              path: "/metadata/labels/team",
+              value: "platform",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+    environment: production
+    team: platform
+spec:
+  replicas: 1
+`,
+  },
+  {
+    name: "should add an annotation to existing annotations",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  annotations:
+    description: "My application"
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/annotations/version",
+              value: "1.0.0",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  annotations:
+    description: "My application"
+    version: 1.0.0
+spec:
+  replicas: 1
+`,
+  },
+];
+
+// REPLACE OPERATION TEST CASES
+
+export const REPLACE_OPERATION_TEST_CASES: TestCase[] = [
+  {
+    name: "should replace replicas value",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "replace",
+              path: "/spec/replicas",
+              value: 3,
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: my-app
+`,
+  },
+  {
+    name: "should replace metadata name",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "replace",
+              path: "/metadata/name",
+              value: "new-app-name",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: new-app-name
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+  },
+];
+
+// REMOVE OPERATION TEST CASES
+
+export const REMOVE_OPERATION_TEST_CASES: NegativeTestCase[] = [
+  {
+    name: "should remove a label",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "remove",
+              path: "/metadata/labels/app",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels: {}
+spec:
+  replicas: 1
+`,
+  },
+  {
+    name: "should remove an annotation",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  annotations:
+    description: "My application"
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "remove",
+              path: "/metadata/annotations/description",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  annotations: {}
+spec:
+  replicas: 1
+`,
+  },
+];
+
+// TEST OPERATION WITH RESOURCE PATCH TEST CASES
+
+export const TEST_OPERATION_TEST_CASES: TestCase[] = [
+  {
+    name: "should apply patch when labels are undefined",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+      patches: [
+        {
+          testFilters: [
+            {
+              op: "test",
+              path: "/metadata/labels",
+              value: undefined,
+            },
+          ],
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels",
+              value: {},
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels: {}
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+  },
+  {
+    name: "should apply patch when labels are null",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels: null
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          testFilters: [
+            {
+              op: "test",
+              path: "/metadata/labels",
+              value: null,
+            },
+          ],
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels",
+              value: {},
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels: {}
+spec:
+  replicas: 1
+`,
+  },
+  {
+    name: "should not apply patch when test filter fails",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          testFilters: [
+            {
+              op: "test",
+              path: "/metadata/labels",
+              value: undefined,
+            },
+          ],
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels",
+              value: {},
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+spec:
+  replicas: 1
+`,
+  },
+  {
+    name: "should apply patch without test filters",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels/environment",
+              value: "production",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+    environment: production
+spec:
+  replicas: 1
+`,
+  },
+];
+
+// MULTIPLE RESOURCE PATCHES TEST CASES
+
+export const MULTIPLE_PATCHES_TEST_CASES: TestCase[] = [
+  {
+    name: "should apply multiple resource patches in order",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+      patches: [
+        {
+          testFilters: [
+            {
+              op: "test",
+              path: "/metadata/labels",
+              value: undefined,
+            },
+          ],
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels",
+              value: {},
+            },
+          ],
+        },
+        {
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels/environment",
+              value: "production",
+            },
+          ],
+        },
+        {
+          jsonPatches: [
+            {
+              op: "replace",
+              path: "/spec/replicas",
+              value: 5,
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    environment: production
+spec:
+  replicas: 5
+  selector:
+    matchLabels:
+      app: my-app
+`,
+  },
+  {
+    name: "should skip patches when test filters fail",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          testFilters: [
+            {
+              op: "test",
+              path: "/metadata/labels",
+              value: undefined,
+            },
+          ],
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels",
+              value: {},
+            },
+          ],
+        },
+        {
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels/environment",
+              value: "production",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+    environment: production
+spec:
+  replicas: 1
+`,
+  },
+];
+
+// EDGE CASE TEST CASES
+
+export const EDGE_CASE_TEST_CASES: TestCase[] = [
+  {
+    name: "should handle empty patches array",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+      patches: [],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+  },
+  {
+    name: "should silently skip invalid patch paths",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/nonexistent/path/to/field",
+              value: "test",
+            },
+          ],
+        },
+      ],
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: my-app
+`,
+  },
+];
+
+// TOKEN INTERPOLATION TEST CASES
+
+export const TOKEN_INTERPOLATION_TEST_CASES: TestCase[] = [
+  {
+    name: "should interpolate tokens in patch values",
+    given: {
+      yaml: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+spec:
+  replicas: 1
+`,
+      patches: [
+        {
+          jsonPatches: [
+            {
+              op: "add",
+              path: "/metadata/labels/partOf",
+              value: "${{ $.devDeployment.uniqueName }}",
+            },
+          ],
+        },
+      ],
+      tokenMap: {
+        devDeployment: {
+          uniqueName: "my-unique-name",
+        },
+      },
+    },
+    expected: `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-app
+  labels:
+    app: my-app
+    partOf: my-unique-name
+spec:
+  replicas: 1
+`,
+  },
+];
diff --git 
a/packages/k8s-yaml-to-apiserver-requests/tests/jsonPatch/patchK8sResourceYaml.test.ts
 
b/packages/k8s-yaml-to-apiserver-requests/tests/jsonPatch/patchK8sResourceYaml.test.ts
new file mode 100644
index 00000000000..4898086658b
--- /dev/null
+++ 
b/packages/k8s-yaml-to-apiserver-requests/tests/jsonPatch/patchK8sResourceYaml.test.ts
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { load as yamlLoad } from "js-yaml";
+import { patchK8sResourceYaml } from "../../src/patchK8sResourceYaml";
+import {
+  ADD_OPERATION_TEST_CASES,
+  REPLACE_OPERATION_TEST_CASES,
+  REMOVE_OPERATION_TEST_CASES,
+  TEST_OPERATION_TEST_CASES,
+  MULTIPLE_PATCHES_TEST_CASES,
+  EDGE_CASE_TEST_CASES,
+  TOKEN_INTERPOLATION_TEST_CASES,
+} from "./fixtures";
+
+describe("JSON Patch Operations", () => {
+  describe.each([
+    ["Add Operations", ADD_OPERATION_TEST_CASES],
+    ["Replace Operations", REPLACE_OPERATION_TEST_CASES],
+    ["Remove Operations", REMOVE_OPERATION_TEST_CASES],
+    ["Test Operations with ResourcePatch", TEST_OPERATION_TEST_CASES],
+    ["Multiple ResourcePatches", MULTIPLE_PATCHES_TEST_CASES],
+    ["Edge Cases", EDGE_CASE_TEST_CASES],
+    ["Token Interpolation in Patches", TOKEN_INTERPOLATION_TEST_CASES],
+  ])("%s", (_suiteName, testCases) => {
+    testCases.forEach(({ name, given, expected }) => {
+      it(name, () => {
+        const result = patchK8sResourceYaml(given.yaml, given.patches, 
given.tokenMap);
+        const resultParsed = yamlLoad(result);
+        const expectedParsed = yamlLoad(expected);
+        expect(resultParsed).toEqual(expectedParsed);
+      });
+    });
+  });
+});
diff --git 
a/packages/k8s-yaml-to-apiserver-requests/tests/tokenInterpolation/fixtures.ts 
b/packages/k8s-yaml-to-apiserver-requests/tests/tokenInterpolation/fixtures.ts
new file mode 100644
index 00000000000..a1c8e879165
--- /dev/null
+++ 
b/packages/k8s-yaml-to-apiserver-requests/tests/tokenInterpolation/fixtures.ts
@@ -0,0 +1,363 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { TokenMap } from "../../src/interpolateK8sResourceYaml";
+
+type BaseTestCase = {
+  name: string;
+  given: {
+    yaml: string;
+    tokenMap: TokenMap;
+  };
+};
+
+type TestCase = BaseTestCase & {
+  expected: string;
+};
+
+type ErrorTestCase = BaseTestCase & {
+  shouldThrow: true;
+};
+
+export const BASIC_TOKEN_TEST_CASES: TestCase[] = [
+  {
+    name: "should replace simple tokens",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ serviceName }}
+`,
+      tokenMap: {
+        serviceName: "my-service",
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: my-service
+`,
+  },
+  {
+    name: "should replace multiple tokens",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ serviceName }}
+  namespace: \${{ namespace }}
+`,
+      tokenMap: {
+        serviceName: "my-service",
+        namespace: "default",
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: my-service
+  namespace: default
+`,
+  },
+];
+
+export const JSON_PATH_TEST_CASES: TestCase[] = [
+  {
+    name: "should resolve JSON Path expressions",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ $.deployment.name }}
+`,
+      tokenMap: {
+        deployment: {
+          name: "my-deployment",
+        },
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: my-deployment
+`,
+  },
+  {
+    name: "should resolve nested JSON Path expressions",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ $.deployment.metadata.name }}
+`,
+      tokenMap: {
+        deployment: {
+          metadata: {
+            name: "nested-deployment",
+          },
+        },
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: nested-deployment
+`,
+  },
+  {
+    name: "should resolve array access in JSON Path",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ $.deployments[0].name }}
+`,
+      tokenMap: {
+        deployments: [{ name: "first-deployment" }, { name: 
"second-deployment" }],
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: first-deployment
+`,
+  },
+];
+
+export const RECURSIVE_TOKEN_TEST_CASES: TestCase[] = [
+  {
+    name: "should resolve nested tokens",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ $.resources.Route['\${{ $.uniqueName }}'].host }}
+`,
+      tokenMap: {
+        uniqueName: "my-route",
+        resources: {
+          Route: {
+            "my-route": {
+              host: "my-route.example.com",
+            },
+          },
+        },
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: my-route.example.com
+`,
+  },
+  {
+    name: "should resolve deeply nested tokens",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ $.resources['\${{ $.kind }}']['\${{ $.name }}'].host }}
+`,
+      tokenMap: {
+        kind: "Route",
+        name: "my-route",
+        resources: {
+          Route: {
+            "my-route": {
+              host: "deeply-nested.example.com",
+            },
+          },
+        },
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: deeply-nested.example.com
+`,
+  },
+];
+
+export const MAX_DEPTH_TEST_CASE: ErrorTestCase = {
+  name: "should throw error when max depth is exceeded",
+  given: {
+    yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ $.a }}
+`,
+    tokenMap: {
+      a: "${{ $.b }}",
+      b: "${{ $.c }}",
+      c: "${{ $.a }}",
+    },
+  },
+  shouldThrow: true,
+};
+
+export const EDGE_CASE_TEST_CASES: TestCase[] = [
+  {
+    name: "should handle empty token map",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: static-name
+  namespace: default
+`,
+      tokenMap: {},
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: static-name
+  namespace: default
+`,
+  },
+  {
+    name: "should handle YAML without tokens",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: static-name
+  namespace: default
+`,
+      tokenMap: {
+        unused: "value",
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: static-name
+  namespace: default
+`,
+  },
+  {
+    name: "should handle numeric values",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+spec:
+  replicas: \${{ $.replicas }}
+`,
+      tokenMap: {
+        replicas: 3,
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+spec:
+  replicas: 3
+`,
+  },
+  {
+    name: "should handle boolean values",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+spec:
+  enabled: \${{ $.enabled }}
+`,
+      tokenMap: {
+        enabled: true,
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+spec:
+  enabled: true
+`,
+  },
+  {
+    name: "should handle mixed flat and JSONPath tokens in same YAML",
+    given: {
+      yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ serviceName }}
+  namespace: \${{ $.config.namespace }}
+  labels:
+    app: \${{ appLabel }}
+    version: \${{ $.deployment.version }}
+`,
+      tokenMap: {
+        serviceName: "mixed-service",
+        appLabel: "my-app",
+        config: {
+          namespace: "production",
+        },
+        deployment: {
+          version: "v1.2.3",
+        },
+      },
+    },
+    expected: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: mixed-service
+  namespace: production
+  labels:
+    app: my-app
+    version: v1.2.3
+`,
+  },
+];
+
+export const UNRESOLVABLE_TOKEN_TEST_CASE: ErrorTestCase = {
+  name: "should throw error for unresolvable tokens",
+  given: {
+    yaml: `
+apiVersion: v1
+kind: Service
+metadata:
+  name: \${{ $.nonexistent }}
+`,
+    tokenMap: {
+      other: "value",
+    },
+  },
+  shouldThrow: true,
+};
diff --git 
a/packages/k8s-yaml-to-apiserver-requests/tests/tokenInterpolation/interpolateK8sResourceYaml.test.ts
 
b/packages/k8s-yaml-to-apiserver-requests/tests/tokenInterpolation/interpolateK8sResourceYaml.test.ts
new file mode 100644
index 00000000000..14c599573a5
--- /dev/null
+++ 
b/packages/k8s-yaml-to-apiserver-requests/tests/tokenInterpolation/interpolateK8sResourceYaml.test.ts
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { load as yamlLoad } from "js-yaml";
+import { interpolateK8sResourceYaml } from 
"../../src/interpolateK8sResourceYaml";
+import {
+  BASIC_TOKEN_TEST_CASES,
+  JSON_PATH_TEST_CASES,
+  RECURSIVE_TOKEN_TEST_CASES,
+  MAX_DEPTH_TEST_CASE,
+  EDGE_CASE_TEST_CASES,
+  UNRESOLVABLE_TOKEN_TEST_CASE,
+} from "./fixtures";
+
+describe("Token Interpolation", () => {
+  describe.each([
+    ["Basic Token Interpolation", BASIC_TOKEN_TEST_CASES],
+    ["JSON Path Token Interpolation", JSON_PATH_TEST_CASES],
+    ["Recursive Token Interpolation", RECURSIVE_TOKEN_TEST_CASES],
+    ["Edge Cases", EDGE_CASE_TEST_CASES],
+  ])("%s", (_suiteName, testCases) => {
+    testCases.forEach(({ name, given, expected }) => {
+      it(name, () => {
+        const result = interpolateK8sResourceYaml(given.yaml, given.tokenMap);
+        const resultParsed = yamlLoad(result);
+        const expectedParsed = yamlLoad(expected);
+        expect(resultParsed).toEqual(expectedParsed);
+      });
+    });
+  });
+
+  describe("Error Cases", () => {
+    it(MAX_DEPTH_TEST_CASE.name, () => {
+      expect(() =>
+        interpolateK8sResourceYaml(MAX_DEPTH_TEST_CASE.given.yaml, 
MAX_DEPTH_TEST_CASE.given.tokenMap)
+      ).toThrow();
+    });
+
+    it(UNRESOLVABLE_TOKEN_TEST_CASE.name, () => {
+      expect(() =>
+        interpolateK8sResourceYaml(UNRESOLVABLE_TOKEN_TEST_CASE.given.yaml, 
UNRESOLVABLE_TOKEN_TEST_CASE.given.tokenMap)
+      ).toThrow();
+    });
+  });
+});
diff --git a/packages/k8s-yaml-to-apiserver-requests/tsconfig.tests.json 
b/packages/k8s-yaml-to-apiserver-requests/tsconfig.tests.json
new file mode 100644
index 00000000000..fc8520e7376
--- /dev/null
+++ b/packages/k8s-yaml-to-apiserver-requests/tsconfig.tests.json
@@ -0,0 +1,3 @@
+{
+  "extends": "./tsconfig.json"
+}
diff --git 
a/packages/online-editor/src/devDeployments/services/KubernetesService.ts 
b/packages/online-editor/src/devDeployments/services/KubernetesService.ts
index 716e3ec1a90..6be9eb1c0c5 100644
--- a/packages/online-editor/src/devDeployments/services/KubernetesService.ts
+++ b/packages/online-editor/src/devDeployments/services/KubernetesService.ts
@@ -236,7 +236,8 @@ export class KubernetesService {
 
       args.actions?.forEach(({ resourcePatches }) => {
         if (resourcePatches) {
-          resultYaml = patchK8sResourceYaml(resultYaml, resourcePatches, 
args.parametersTokens);
+          const mergedTokens = { ...args.parametersTokens, ...args.tokens };
+          resultYaml = patchK8sResourceYaml(resultYaml, resourcePatches, 
mergedTokens);
         }
       });
 
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/KieSandboxDevDeploymentRequiredPatches.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/KieSandboxDevDeploymentRequiredPatches.ts
index 92e56f2ff78..3b5aef61a26 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/KieSandboxDevDeploymentRequiredPatches.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/KieSandboxDevDeploymentRequiredPatches.ts
@@ -34,10 +34,12 @@ export const requiredAnnotations = {
 export function KieSandboxDevDeploymentRequiredPatches(): ResourcePatch[] {
   return [
     {
-      jsonPatches: [
-        { op: "checkType", path: "/metadata/labels", type: "null" },
-        { op: "add", path: "/metadata/labels", value: {} },
-      ],
+      testFilters: [{ op: "test", path: "/metadata/labels", value: undefined 
}],
+      jsonPatches: [{ op: "add", path: "/metadata/labels", value: {} }],
+    },
+    {
+      testFilters: [{ op: "test", path: "/metadata/labels", value: null }],
+      jsonPatches: [{ op: "add", path: "/metadata/labels", value: {} }],
     },
     {
       jsonPatches: [
@@ -49,27 +51,29 @@ export function KieSandboxDevDeploymentRequiredPatches(): 
ResourcePatch[] {
         {
           op: "add",
           path: 
`/metadata/labels/${encodeJsonPatchSubpath(requiredLabels.partOf)}`,
-          value: "${{ devDeployment.uniqueName }}",
+          value: "${{ $.devDeployment.uniqueName }}",
         },
       ],
     },
     {
-      jsonPatches: [
-        { op: "checkType", path: "/metadata/annotations", type: "null" },
-        { op: "add", path: "/metadata/annotations", value: {} },
-      ],
+      testFilters: [{ op: "test", path: "/metadata/annotations", value: 
undefined }],
+      jsonPatches: [{ op: "add", path: "/metadata/annotations", value: {} }],
+    },
+    {
+      testFilters: [{ op: "test", path: "/metadata/annotations", value: null 
}],
+      jsonPatches: [{ op: "add", path: "/metadata/annotations", value: {} }],
     },
     {
       jsonPatches: [
         {
           op: "add",
           path: 
`/metadata/annotations/${encodeJsonPatchSubpath(requiredAnnotations.workspaceId)}`,
-          value: "${{ devDeployment.workspace.id }}",
+          value: "${{ $.devDeployment.workspace.id }}",
         },
         {
           op: "add",
           path: 
`/metadata/annotations/${encodeJsonPatchSubpath(requiredAnnotations.workspaceName)}`,
-          value: "${{ devDeployment.workspace.name }}",
+          value: "${{ $.devDeployment.workspace.name }}",
         },
       ],
     },
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/DeploymentYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/DeploymentYaml.ts
index 5efbbd35e55..acd513bbdb9 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/DeploymentYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/DeploymentYaml.ts
@@ -24,27 +24,27 @@ export function DeploymentYaml(args: DeploymentOptionArgs) {
 kind: Deployment
 apiVersion: apps/v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
 spec:
   replicas: 1
   selector:
     matchLabels:
-      app: \${{ devDeployment.uniqueName }}
+      app: \${{ $.devDeployment.uniqueName }}
   template:
     metadata:
       labels:
-        app: \${{ devDeployment.uniqueName }}
-        deploymentconfig: \${{ devDeployment.uniqueName }}
+        app: \${{ $.devDeployment.uniqueName }}
+        deploymentconfig: \${{ $.devDeployment.uniqueName }}
     spec:
       containers:
-        - name: \${{ devDeployment.uniqueName }}
+        - name: \${{ $.devDeployment.uniqueName }}
           image: ${args.quarkusBlankAppImageUrl}
           imagePullPolicy: ${args.imagePullPolicy}
           resources:
@@ -55,6 +55,6 @@ spec:
               protocol: TCP
           env:
             - name: DEV_DEPLOYMENT__UPLOAD_SERVICE_API_KEY
-              value: \${{ devDeployment.uploadService.apiKey }}
+              value: \${{ $.devDeployment.uploadService.apiKey }}
 `;
 }
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/IngressYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/IngressYaml.ts
index d6eca8454b9..3c8245964fb 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/IngressYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/IngressYaml.ts
@@ -22,14 +22,14 @@ export function IngressYaml() {
 kind: Ingress
 apiVersion: networking.k8s.io/v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
   annotations:
     nginx.ingress.kubernetes.io/backend-protocol: HTTP
     nginx.ingress.kubernetes.io/ssl-redirect: "false"
@@ -37,11 +37,11 @@ spec:
   rules:
     - http:
         paths:
-          - path: /\${{ devDeployment.uniqueName }}
+          - path: /\${{ $.devDeployment.uniqueName }}
             pathType: Prefix
             backend:
               service:
-                name: \${{ devDeployment.uniqueName }}
+                name: \${{ $.devDeployment.uniqueName }}
                 port:
                   number: 8080
 `;
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/RouteYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/RouteYaml.ts
index e6422466a11..a60d00c6fc1 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/RouteYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/RouteYaml.ts
@@ -21,18 +21,18 @@ export const RouteYaml = () => `
 kind: Route
 apiVersion: route.openshift.io/v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
 spec:
-  subdomain: \${{ devDeployment.uniqueName }}
+  subdomain: \${{ $.devDeployment.uniqueName }}
   to:
-    name: \${{ devDeployment.uniqueName }}
+    name: \${{ $.devDeployment.uniqueName }}
     kind: Service
   port:
     targetPort: 8080
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/ServiceYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/ServiceYaml.ts
index 4fc2184d088..268dafa471f 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/ServiceYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/customImage/ServiceYaml.ts
@@ -22,22 +22,22 @@ export function ServiceYaml() {
 kind: Service
 apiVersion: v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
 spec:
   ports:
     - protocol: TCP
       port: 8080
       targetPort: 8080
   selector:
-    app: \${{ devDeployment.uniqueName }}
-    deploymentconfig: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    deploymentconfig: \${{ $.devDeployment.uniqueName }}
   type: ClusterIP
 `;
 }
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/DeploymentYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/DeploymentYaml.ts
index aeb1d2414a4..543df22b6ea 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/DeploymentYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/DeploymentYaml.ts
@@ -24,30 +24,30 @@ export function DeploymentYaml(args: DeploymentOptionArgs) {
 kind: Deployment
 apiVersion: apps/v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
 spec:
   replicas: 1
   selector:
     matchLabels:
-      app: \${{ devDeployment.uniqueName }}
+      app: \${{ $.devDeployment.uniqueName }}
   template:
     metadata:
       labels:
-        app: \${{ devDeployment.uniqueName }}
-        deploymentconfig: \${{ devDeployment.uniqueName }}
+        app: \${{ $.devDeployment.uniqueName }}
+        deploymentconfig: \${{ $.devDeployment.uniqueName }}
     spec:
       volumes:
-        - name: \${{ devDeployment.uniqueName }}-volume
+        - name: \${{ $.devDeployment.uniqueName }}-volume
           emptyDir: {}
       containers:
-        - name: \${{ devDeployment.uniqueName }}
+        - name: \${{ $.devDeployment.uniqueName }}
           image: ${args.quarkusBlankAppImageUrl}
           imagePullPolicy: ${args.imagePullPolicy}
           resources:
@@ -58,9 +58,9 @@ spec:
               protocol: TCP
           env:
             - name: DEV_DEPLOYMENT__UPLOAD_SERVICE_API_KEY
-              value: \${{ devDeployment.uploadService.apiKey }}
+              value: \${{ $.devDeployment.uploadService.apiKey }}
           volumeMounts:
             - mountPath: /tmp/app
-              name: \${{ devDeployment.uniqueName }}-volume
+              name: \${{ $.devDeployment.uniqueName }}-volume
 `;
 }
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappIngressYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappIngressYaml.ts
index 78ae3f4e7b0..962165f286a 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappIngressYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappIngressYaml.ts
@@ -22,14 +22,14 @@ export function FormWebappIngressYaml() {
 kind: Ingress
 apiVersion: networking.k8s.io/v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}-dmn-form-webapp
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}-dmn-form-webapp
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName 
}}-dmn-form-webapp
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName 
}}-dmn-form-webapp
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}-dmn-form-webapp
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName 
}}-dmn-form-webapp
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName 
}}-dmn-form-webapp
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}-dmn-form-webapp
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
   annotations:
     nginx.ingress.kubernetes.io/backend-protocol: HTTP
     nginx.ingress.kubernetes.io/ssl-redirect: "false"
@@ -38,11 +38,11 @@ spec:
   rules:
     - http:
         paths:
-          - path: /\${{ devDeployment.uniqueName }}/form-webapp(/|$)(.*)
+          - path: /\${{ $.devDeployment.uniqueName }}/form-webapp(/|$)(.*)
             pathType: Prefix
             backend:
               service:
-                name: \${{ devDeployment.uniqueName }}-dmn-form-webapp
+                name: \${{ $.devDeployment.uniqueName }}-dmn-form-webapp
                 port:
                   number: 8081
 `;
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappRouteYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappRouteYaml.ts
index 1f30c83f46e..c9c3a9e73a9 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappRouteYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappRouteYaml.ts
@@ -22,22 +22,22 @@ export function FormWebappRouteYaml() {
 kind: Route
 apiVersion: route.openshift.io/v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}-dmn-form-webapp
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}-dmn-form-webapp
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName 
}}-dmn-form-webapp
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName 
}}-dmn-form-webapp
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}-dmn-form-webapp
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName 
}}-dmn-form-webapp
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName 
}}-dmn-form-webapp
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}-dmn-form-webapp
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
     type: sharded
   annotations:
     haproxy.router.openshift.io/rewrite-target: /
 spec:
-  subdomain: \${{ devDeployment.uniqueName }}
+  subdomain: \${{ $.devDeployment.uniqueName }}
   path: /form-webapp
   to:
-    name: \${{ devDeployment.uniqueName }}-dmn-form-webapp
+    name: \${{ $.devDeployment.uniqueName }}-dmn-form-webapp
     kind: Service
   port:
     targetPort: 8081
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappServiceYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappServiceYaml.ts
index 246fd9e4f13..69e403526b7 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappServiceYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/FormWebappServiceYaml.ts
@@ -22,22 +22,22 @@ export function FormWebappServiceYaml() {
 kind: Service
 apiVersion: v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}-dmn-form-webapp
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}-dmn-form-webapp
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName 
}}-dmn-form-webapp
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName 
}}-dmn-form-webapp
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}-dmn-form-webapp
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName 
}}-dmn-form-webapp
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName 
}}-dmn-form-webapp
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}-dmn-form-webapp
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
 spec:
   ports:
     - protocol: TCP
       port: 8081
       targetPort: 8081
   selector:
-    app: \${{ devDeployment.uniqueName }}
-    deploymentconfig: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    deploymentconfig: \${{ $.devDeployment.uniqueName }}
   type: ClusterIP
 `;
 }
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/IngressYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/IngressYaml.ts
index d6eca8454b9..3c8245964fb 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/IngressYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/IngressYaml.ts
@@ -22,14 +22,14 @@ export function IngressYaml() {
 kind: Ingress
 apiVersion: networking.k8s.io/v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
   annotations:
     nginx.ingress.kubernetes.io/backend-protocol: HTTP
     nginx.ingress.kubernetes.io/ssl-redirect: "false"
@@ -37,11 +37,11 @@ spec:
   rules:
     - http:
         paths:
-          - path: /\${{ devDeployment.uniqueName }}
+          - path: /\${{ $.devDeployment.uniqueName }}
             pathType: Prefix
             backend:
               service:
-                name: \${{ devDeployment.uniqueName }}
+                name: \${{ $.devDeployment.uniqueName }}
                 port:
                   number: 8080
 `;
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/RouteYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/RouteYaml.ts
index cb8a41e17e0..1dca0686000 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/RouteYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/RouteYaml.ts
@@ -22,18 +22,18 @@ export function RouteYaml() {
 kind: Route
 apiVersion: route.openshift.io/v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
 spec:
-  subdomain: \${{ devDeployment.uniqueName }}
+  subdomain: \${{ $.devDeployment.uniqueName }}
   to:
-    name: \${{ devDeployment.uniqueName }}
+    name: \${{ $.devDeployment.uniqueName }}
     kind: Service
   port:
     targetPort: 8080
diff --git 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/ServiceYaml.ts
 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/ServiceYaml.ts
index 4fc2184d088..268dafa471f 100644
--- 
a/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/ServiceYaml.ts
+++ 
b/packages/online-editor/src/devDeployments/services/deploymentOptions/quarkusBlankApp/ServiceYaml.ts
@@ -22,22 +22,22 @@ export function ServiceYaml() {
 kind: Service
 apiVersion: v1
 metadata:
-  name: \${{ devDeployment.uniqueName }}
-  namespace: \${{ devDeployment.kubernetes.namespace }}
+  name: \${{ $.devDeployment.uniqueName }}
+  namespace: \${{ $.devDeployment.kubernetes.namespace }}
   labels:
-    app: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/component: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/instance: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/name: \${{ devDeployment.uniqueName }}
-    app.kubernetes.io/part-of: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/component: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/instance: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/name: \${{ $.devDeployment.uniqueName }}
+    app.kubernetes.io/part-of: \${{ $.devDeployment.uniqueName }}
 spec:
   ports:
     - protocol: TCP
       port: 8080
       targetPort: 8080
   selector:
-    app: \${{ devDeployment.uniqueName }}
-    deploymentconfig: \${{ devDeployment.uniqueName }}
+    app: \${{ $.devDeployment.uniqueName }}
+    deploymentconfig: \${{ $.devDeployment.uniqueName }}
   type: ClusterIP
 `;
 }
diff --git 
a/packages/online-editor/src/devDeployments/services/kubernetes/KubernetesDeploymentOptions.ts
 
b/packages/online-editor/src/devDeployments/services/kubernetes/KubernetesDeploymentOptions.ts
index a12930f752d..720e7f56378 100644
--- 
a/packages/online-editor/src/devDeployments/services/kubernetes/KubernetesDeploymentOptions.ts
+++ 
b/packages/online-editor/src/devDeployments/services/kubernetes/KubernetesDeploymentOptions.ts
@@ -43,7 +43,7 @@ export function KubernetesDeploymentOptions(args: 
DeploymentOptionArgs): Array<D
                 op: "add",
                 path: "/spec/template/spec/containers/-",
                 value: {
-                  name: "${{ devDeployment.uniqueName }}-dmn-form-webapp",
+                  name: "${{ $.devDeployment.uniqueName }}-dmn-form-webapp",
                   image: args.dmnFormWebappImageUrl,
                   imagePullPolicy: args.imagePullPolicy,
                   ports: [{ containerPort: 8081, protocol: "TCP" }],
@@ -62,12 +62,12 @@ export function KubernetesDeploymentOptions(args: 
DeploymentOptionArgs): Array<D
           {
             op: "add",
             path: "/spec/template/spec/containers/0/env/-",
-            value: { name: "ROOT_PATH", value: "/${{ devDeployment.uniqueName 
}}" },
+            value: { name: "ROOT_PATH", value: "/${{ 
$.devDeployment.uniqueName }}" },
           },
           {
             op: "add",
             path: "/spec/template/spec/containers/0/env/-",
-            value: { name: "DEV_DEPLOYMENT__UPLOAD_SERVICE_ROOT_PATH", value: 
"${{ devDeployment.uniqueName }}" },
+            value: { name: "DEV_DEPLOYMENT__UPLOAD_SERVICE_ROOT_PATH", value: 
"${{ $.devDeployment.uniqueName }}" },
           },
         ],
       },
@@ -80,7 +80,7 @@ export function KubernetesDeploymentOptions(args: 
DeploymentOptionArgs): Array<D
         id: "command",
         name: "Command",
         description: "The command to be executed when the container starts",
-        defaultValue: "./mvnw quarkus:dev -Dquarkus.http.root-path=/${{ 
devDeployment.uniqueName }}",
+        defaultValue: "./mvnw quarkus:dev -Dquarkus.http.root-path=/${{ 
$.devDeployment.uniqueName }}",
         type: "text",
         resourcePatches: [
           {
@@ -104,12 +104,12 @@ export function KubernetesDeploymentOptions(args: 
DeploymentOptionArgs): Array<D
           {
             op: "add",
             path: "/spec/template/spec/containers/0/env/-",
-            value: { name: "ROOT_PATH", value: "/${{ devDeployment.uniqueName 
}}" },
+            value: { name: "ROOT_PATH", value: "/${{ 
$.devDeployment.uniqueName }}" },
           },
           {
             op: "add",
             path: "/spec/template/spec/containers/0/env/-",
-            value: { name: "DEV_DEPLOYMENT__UPLOAD_SERVICE_ROOT_PATH", value: 
"${{ devDeployment.uniqueName }}" },
+            value: { name: "DEV_DEPLOYMENT__UPLOAD_SERVICE_ROOT_PATH", value: 
"${{ $.devDeployment.uniqueName }}" },
           },
         ],
       },
diff --git 
a/packages/online-editor/src/devDeployments/services/openshift/OpenShiftDeploymentOptions.ts
 
b/packages/online-editor/src/devDeployments/services/openshift/OpenShiftDeploymentOptions.ts
index d9d557d48e7..1be0798c9e7 100644
--- 
a/packages/online-editor/src/devDeployments/services/openshift/OpenShiftDeploymentOptions.ts
+++ 
b/packages/online-editor/src/devDeployments/services/openshift/OpenShiftDeploymentOptions.ts
@@ -44,7 +44,7 @@ export function OpenShiftDeploymentOptions(args: 
DeploymentOptionArgs): Array<De
                 op: "add",
                 path: "/spec/template/spec/containers/-",
                 value: {
-                  name: "${{ devDeployment.uniqueName }}-dmn-form-webapp",
+                  name: "${{ $.devDeployment.uniqueName }}-dmn-form-webapp",
                   image: args.dmnFormWebappImageUrl,
                   imagePullPolicy: args.imagePullPolicy,
                   ports: [{ containerPort: 8081, protocol: "TCP" }],
diff --git a/packages/online-editor/webpack.config.ts 
b/packages/online-editor/webpack.config.ts
index 7baa9cf393b..e0728215a47 100644
--- a/packages/online-editor/webpack.config.ts
+++ b/packages/online-editor/webpack.config.ts
@@ -149,6 +149,10 @@ export default async (webpackEnv: any, webpackArgv: any) 
=> {
             // The @kubernetes-models sub-packages source maps are not 
published, so we need to ignore their warnings for now.
             module: /@kubernetes-models/,
           },
+          {
+            // The jsonpath-rfc9535 package source maps are not published, so 
we ignore their warnings.
+            module: /jsonpath-rfc9535/,
+          },
         ],
       }),
       devServer: {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9e0f5e4f82b..682bb56da5b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6362,7 +6362,7 @@ importers:
         version: link:../root-env
       jest:
         specifier: ^29.7.0
-        version: 
29.7.0(@types/[email protected])([email protected])([email protected](@types/[email protected]))
+        version: 
29.7.0(@types/[email protected])([email protected])([email protected](@types/[email protected]))
 
   packages/json-yaml-language-service:
     dependencies:
@@ -6445,6 +6445,9 @@ importers:
       js-yaml:
         specifier: ^4.1.1
         version: 4.1.1
+      jsonpath-rfc9535:
+        specifier: ^1.3.0
+        version: 1.3.0
     devDependencies:
       '@babel/core':
         specifier: ^7.16.0
@@ -6461,18 +6464,33 @@ importers:
       '@kie-tools/eslint':
         specifier: workspace:*
         version: link:../eslint
+      '@kie-tools/jest-base':
+        specifier: workspace:*
+        version: link:../jest-base
       '@kie-tools/root-env':
         specifier: workspace:*
         version: link:../root-env
       '@kie-tools/tsconfig':
         specifier: workspace:*
         version: link:../tsconfig
+      '@types/jest':
+        specifier: ^29.5.12
+        version: 29.5.12
       '@types/js-yaml':
         specifier: ^4.0.5
         version: 4.0.5
+      jest:
+        specifier: ^29.7.0
+        version: 
29.7.0(@types/[email protected])([email protected])([email protected](@swc/[email protected])(@types/[email protected])([email protected]))
+      jest-junit:
+        specifier: ^16.0.0
+        version: 16.0.0
       rimraf:
         specifier: ^3.0.2
         version: 3.0.2
+      ts-jest:
+        specifier: ^29.1.5
+        version: 
29.1.5(@babel/[email protected])(@jest/[email protected])(@jest/[email protected])([email protected](@babel/[email protected]))([email protected](@types/[email protected])([email protected])([email protected](@swc/[email protected])(@types/[email protected])([email protected])))([email protected])
       ts-node:
         specifier: ^10.9.2
         version: 10.9.2(@swc/[email protected])(@types/[email protected])([email protected])
@@ -26995,6 +27013,10 @@ packages:
     resolution: {integrity: 
sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
     engines: {'0': node >= 0.2.0}
 
+  [email protected]:
+    resolution: {integrity: 
sha512-3jFHya7oZ45aDxIIdx+/zQARahHXxFSMWBkcBUldfXpLS9VCXDJyTKt35kQfEXLqh0K3Ixw/9xFnvcDStaxh7Q==}
+    engines: {node: '>=20'}
+
   [email protected]:
     resolution: {integrity: 
sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
     engines: {node: '>=0.10.0'}
@@ -54172,6 +54194,8 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]: {}
 
   [email protected]:
@@ -59042,7 +59066,7 @@ snapshots:
 
   [email protected]([email protected]):
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.25
+      '@jridgewell/trace-mapping': 0.3.31
       jest-worker: 27.5.1
       schema-utils: 3.3.0
       serialize-javascript: 6.0.2
@@ -59365,7 +59389,7 @@ snapshots:
       json5: 2.2.3
       lodash.memoize: 4.1.2
       make-error: 1.3.6
-      semver: 7.5.4
+      semver: 7.7.3
       typescript: 5.5.3
       yargs-parser: 21.1.1
     optionalDependencies:
@@ -60857,7 +60881,7 @@ snapshots:
       '@webassemblyjs/wasm-parser': 1.12.1
       acorn: 8.12.1
       acorn-import-attributes: 1.9.5([email protected])
-      browserslist: 4.23.3
+      browserslist: 4.27.0
       chrome-trace-event: 1.0.4
       enhanced-resolve: 5.17.1
       es-module-lexer: 1.5.4
diff --git a/repo/graph.dot b/repo/graph.dot
index 4c971339062..bef6744d46a 100644
--- a/repo/graph.dot
+++ b/repo/graph.dot
@@ -460,6 +460,7 @@ digraph G {
   "@kie-tools/jest-base" -> "@kie-tools/root-env" [ style = "dashed", color = 
"blue" ];
   "@kie-tools/json-yaml-language-service" -> "@kie-tools/yaml-language-server" 
[ style = "solid", color = "blue" ];
   "@kie-tools-core/k8s-yaml-to-apiserver-requests" -> 
"@kie-tools/cors-proxy-api" [ style = "dashed", color = "purple" ];
+  "@kie-tools-core/k8s-yaml-to-apiserver-requests" -> "@kie-tools/jest-base" [ 
style = "dashed", color = "purple" ];
   "@kie-tools-core/keyboard-shortcuts" -> "@kie-tools-core/envelope-bus" [ 
style = "solid", color = "purple" ];
   "@kie-tools-core/keyboard-shortcuts" -> "@kie-tools-core/operating-system" [ 
style = "solid", color = "purple" ];
   "@kie-tools/kie-bc-editors" -> "@kie-tools/dmn-language-service" [ style = 
"solid", color = "blue" ];
diff --git a/repo/graph.json b/repo/graph.json
index 100d8b2f1e4..107dc22797f 100644
--- a/repo/graph.json
+++ b/repo/graph.json
@@ -1901,6 +1901,11 @@
         "target": "@kie-tools/cors-proxy-api",
         "weight": 1
       },
+      {
+        "source": "@kie-tools-core/k8s-yaml-to-apiserver-requests",
+        "target": "@kie-tools/jest-base",
+        "weight": 1
+      },
       {
         "source": "kie-editors-dev-vscode-extension",
         "target": "@kie-tools-core/vscode-extension",


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to