@servicenow/sdk-build-plugins 4.2.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acl-plugin.js +11 -0
- package/dist/acl-plugin.js.map +1 -1
- package/dist/applicability-plugin.d.ts +2 -0
- package/dist/applicability-plugin.js +72 -0
- package/dist/applicability-plugin.js.map +1 -0
- package/dist/atf/test-plugin.js +5 -2
- package/dist/atf/test-plugin.js.map +1 -1
- package/dist/basic-syntax-plugin.js +7 -1
- package/dist/basic-syntax-plugin.js.map +1 -1
- package/dist/business-rule-plugin.js +1 -0
- package/dist/business-rule-plugin.js.map +1 -1
- package/dist/call-expression-plugin.js +1 -107
- package/dist/call-expression-plugin.js.map +1 -1
- package/dist/column/column-to-record.d.ts +10 -3
- package/dist/column/column-to-record.js +44 -7
- package/dist/column/column-to-record.js.map +1 -1
- package/dist/column-plugin.d.ts +3 -1
- package/dist/column-plugin.js +12 -12
- package/dist/column-plugin.js.map +1 -1
- package/dist/dashboard/dashboard-component-property-defaults.d.ts +152 -0
- package/dist/dashboard/dashboard-component-property-defaults.js +264 -0
- package/dist/dashboard/dashboard-component-property-defaults.js.map +1 -0
- package/dist/dashboard/dashboard-component-resolver.d.ts +13 -0
- package/dist/dashboard/dashboard-component-resolver.js +69 -0
- package/dist/dashboard/dashboard-component-resolver.js.map +1 -0
- package/dist/dashboard/dashboard-plugin.d.ts +12 -0
- package/dist/dashboard/dashboard-plugin.js +397 -0
- package/dist/dashboard/dashboard-plugin.js.map +1 -0
- package/dist/data-plugin.d.ts +3 -0
- package/dist/data-plugin.js +61 -113
- package/dist/data-plugin.js.map +1 -1
- package/dist/email-notification-plugin.d.ts +2 -0
- package/dist/email-notification-plugin.js +541 -0
- package/dist/email-notification-plugin.js.map +1 -0
- package/dist/flow/constants/flow-plugin-constants.d.ts +58 -0
- package/dist/flow/constants/flow-plugin-constants.js +70 -0
- package/dist/flow/constants/flow-plugin-constants.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-constants.d.ts +38 -0
- package/dist/flow/flow-logic/flow-logic-constants.js +118 -0
- package/dist/flow/flow-logic/flow-logic-constants.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-diagnostics.d.ts +19 -0
- package/dist/flow/flow-logic/flow-logic-diagnostics.js +503 -0
- package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +62 -0
- package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +2092 -0
- package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-plugin.d.ts +52 -0
- package/dist/flow/flow-logic/flow-logic-plugin.js +283 -0
- package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-shapes.d.ts +104 -0
- package/dist/flow/flow-logic/flow-logic-shapes.js +201 -0
- package/dist/flow/flow-logic/flow-logic-shapes.js.map +1 -0
- package/dist/flow/plugins/approval-rules-plugin.d.ts +2 -0
- package/dist/flow/plugins/approval-rules-plugin.js +49 -0
- package/dist/flow/plugins/approval-rules-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-action-definition-plugin.d.ts +2 -0
- package/dist/flow/plugins/flow-action-definition-plugin.js +286 -0
- package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-data-pill-plugin.d.ts +9 -0
- package/dist/flow/plugins/flow-data-pill-plugin.js +212 -0
- package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-definition-plugin.d.ts +2 -0
- package/dist/flow/plugins/flow-definition-plugin.js +1668 -0
- package/dist/flow/plugins/flow-definition-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-diagnostics-plugin.d.ts +26 -0
- package/dist/flow/plugins/flow-diagnostics-plugin.js +217 -0
- package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-instance-plugin.d.ts +12 -0
- package/dist/flow/plugins/flow-instance-plugin.js +1205 -0
- package/dist/flow/plugins/flow-instance-plugin.js.map +1 -0
- package/dist/flow/plugins/flow-trigger-instance-plugin.d.ts +2 -0
- package/dist/flow/plugins/flow-trigger-instance-plugin.js +338 -0
- package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -0
- package/dist/flow/plugins/inline-script-plugin.d.ts +39 -0
- package/dist/flow/plugins/inline-script-plugin.js +80 -0
- package/dist/flow/plugins/inline-script-plugin.js.map +1 -0
- package/dist/flow/plugins/step-definition-plugin.d.ts +5 -0
- package/dist/flow/plugins/step-definition-plugin.js +71 -0
- package/dist/flow/plugins/step-definition-plugin.js.map +1 -0
- package/dist/flow/plugins/step-instance-plugin.d.ts +31 -0
- package/dist/flow/plugins/step-instance-plugin.js +339 -0
- package/dist/flow/plugins/step-instance-plugin.js.map +1 -0
- package/dist/flow/plugins/trigger-plugin.d.ts +2 -0
- package/dist/flow/plugins/trigger-plugin.js +96 -0
- package/dist/flow/plugins/trigger-plugin.js.map +1 -0
- package/dist/flow/plugins/wfa-datapill-plugin.d.ts +15 -0
- package/dist/flow/plugins/wfa-datapill-plugin.js +178 -0
- package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -0
- package/dist/flow/utils/approval-rules-processor.d.ts +13 -0
- package/dist/flow/utils/approval-rules-processor.js +267 -0
- package/dist/flow/utils/approval-rules-processor.js.map +1 -0
- package/dist/flow/utils/built-in-complex-objects.d.ts +19 -0
- package/dist/flow/utils/built-in-complex-objects.js +62 -0
- package/dist/flow/utils/built-in-complex-objects.js.map +1 -0
- package/dist/flow/utils/complex-object-resolver.d.ts +8 -0
- package/dist/flow/utils/complex-object-resolver.js +614 -0
- package/dist/flow/utils/complex-object-resolver.js.map +1 -0
- package/dist/flow/utils/complex-objects.d.ts +36 -0
- package/dist/flow/utils/complex-objects.js +481 -0
- package/dist/flow/utils/complex-objects.js.map +1 -0
- package/dist/flow/utils/data-pill-shapes.d.ts +58 -0
- package/dist/flow/utils/data-pill-shapes.js +135 -0
- package/dist/flow/utils/data-pill-shapes.js.map +1 -0
- package/dist/flow/utils/datapill-transformer.d.ts +110 -0
- package/dist/flow/utils/datapill-transformer.js +503 -0
- package/dist/flow/utils/datapill-transformer.js.map +1 -0
- package/dist/flow/utils/flow-constants.d.ts +72 -0
- package/dist/flow/utils/flow-constants.js +230 -0
- package/dist/flow/utils/flow-constants.js.map +1 -0
- package/dist/flow/utils/flow-io-to-record.d.ts +44 -0
- package/dist/flow/utils/flow-io-to-record.js +409 -0
- package/dist/flow/utils/flow-io-to-record.js.map +1 -0
- package/dist/flow/utils/flow-shapes.d.ts +161 -0
- package/dist/flow/utils/flow-shapes.js +255 -0
- package/dist/flow/utils/flow-shapes.js.map +1 -0
- package/dist/flow/utils/flow-to-xml.d.ts +16 -0
- package/dist/flow/utils/flow-to-xml.js +237 -0
- package/dist/flow/utils/flow-to-xml.js.map +1 -0
- package/dist/flow/utils/flow-variable-processor.d.ts +51 -0
- package/dist/flow/utils/flow-variable-processor.js +69 -0
- package/dist/flow/utils/flow-variable-processor.js.map +1 -0
- package/dist/flow/utils/label-cache-parser.d.ts +7 -0
- package/dist/flow/utils/label-cache-parser.js +24 -0
- package/dist/flow/utils/label-cache-parser.js.map +1 -0
- package/dist/flow/utils/label-cache-processor.d.ts +119 -0
- package/dist/flow/utils/label-cache-processor.js +719 -0
- package/dist/flow/utils/label-cache-processor.js.map +1 -0
- package/dist/flow/utils/pill-string-parser.d.ts +88 -0
- package/dist/flow/utils/pill-string-parser.js +306 -0
- package/dist/flow/utils/pill-string-parser.js.map +1 -0
- package/dist/flow/utils/schema-to-flow-object.d.ts +22 -0
- package/dist/flow/utils/schema-to-flow-object.js +318 -0
- package/dist/flow/utils/schema-to-flow-object.js.map +1 -0
- package/dist/flow/utils/service-catalog.d.ts +47 -0
- package/dist/flow/utils/service-catalog.js +137 -0
- package/dist/flow/utils/service-catalog.js.map +1 -0
- package/dist/flow/utils/utils.d.ts +117 -0
- package/dist/flow/utils/utils.js +345 -0
- package/dist/flow/utils/utils.js.map +1 -0
- package/dist/index.d.ts +20 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/list-plugin.js +1 -1
- package/dist/list-plugin.js.map +1 -1
- package/dist/now-attach-plugin.d.ts +1 -0
- package/dist/now-attach-plugin.js +10 -10
- package/dist/now-attach-plugin.js.map +1 -1
- package/dist/now-ref-plugin.js +1 -1
- package/dist/now-ref-plugin.js.map +1 -1
- package/dist/record-plugin.d.ts +29 -0
- package/dist/record-plugin.js +66 -7
- package/dist/record-plugin.js.map +1 -1
- package/dist/repack/index.d.ts +2 -0
- package/dist/repack/index.js +8 -0
- package/dist/repack/index.js.map +1 -1
- package/dist/rest-api-plugin.js +54 -44
- package/dist/rest-api-plugin.js.map +1 -1
- package/dist/server-module-plugin/index.d.ts +10 -0
- package/dist/server-module-plugin/index.js +83 -59
- package/dist/server-module-plugin/index.js.map +1 -1
- package/dist/service-catalog/catalog-clientscript-plugin.d.ts +2 -0
- package/dist/service-catalog/catalog-clientscript-plugin.js +117 -0
- package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -0
- package/dist/service-catalog/catalog-item-plugin.d.ts +2 -0
- package/dist/service-catalog/catalog-item-plugin.js +115 -0
- package/dist/service-catalog/catalog-item-plugin.js.map +1 -0
- package/dist/service-catalog/catalog-ui-policy-plugin.d.ts +2 -0
- package/dist/service-catalog/catalog-ui-policy-plugin.js +266 -0
- package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -0
- package/dist/service-catalog/index.d.ts +5 -0
- package/dist/service-catalog/index.js +22 -0
- package/dist/service-catalog/index.js.map +1 -0
- package/dist/service-catalog/record-to-shape.d.ts +6 -0
- package/dist/service-catalog/record-to-shape.js +93 -0
- package/dist/service-catalog/record-to-shape.js.map +1 -0
- package/dist/service-catalog/sc-record-producer-plugin.d.ts +2 -0
- package/dist/service-catalog/sc-record-producer-plugin.js +140 -0
- package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -0
- package/dist/service-catalog/service-catalog-base.d.ts +311 -0
- package/dist/service-catalog/service-catalog-base.js +542 -0
- package/dist/service-catalog/service-catalog-base.js.map +1 -0
- package/dist/service-catalog/service-catalog-diagnostics.d.ts +45 -0
- package/dist/service-catalog/service-catalog-diagnostics.js +172 -0
- package/dist/service-catalog/service-catalog-diagnostics.js.map +1 -0
- package/dist/service-catalog/shape-to-record.d.ts +8 -0
- package/dist/service-catalog/shape-to-record.js +235 -0
- package/dist/service-catalog/shape-to-record.js.map +1 -0
- package/dist/service-catalog/utils.d.ts +323 -0
- package/dist/service-catalog/utils.js +1216 -0
- package/dist/service-catalog/utils.js.map +1 -0
- package/dist/service-catalog/variable-helper.d.ts +43 -0
- package/dist/service-catalog/variable-helper.js +92 -0
- package/dist/service-catalog/variable-helper.js.map +1 -0
- package/dist/service-catalog/variable-set-plugin.d.ts +2 -0
- package/dist/service-catalog/variable-set-plugin.js +175 -0
- package/dist/service-catalog/variable-set-plugin.js.map +1 -0
- package/dist/service-catalog/variables-transform.d.ts +139 -0
- package/dist/service-catalog/variables-transform.js +403 -0
- package/dist/service-catalog/variables-transform.js.map +1 -0
- package/dist/sla/sla-validators.d.ts +61 -0
- package/dist/sla/sla-validators.js +224 -0
- package/dist/sla/sla-validators.js.map +1 -0
- package/dist/sla-plugin.d.ts +5 -0
- package/dist/sla-plugin.js +280 -0
- package/dist/sla-plugin.js.map +1 -0
- package/dist/static-content-plugin.js +25 -2
- package/dist/static-content-plugin.js.map +1 -1
- package/dist/table-plugin.js +32 -15
- package/dist/table-plugin.js.map +1 -1
- package/dist/ui-page-plugin.js +832 -19
- package/dist/ui-page-plugin.js.map +1 -1
- package/dist/ui-policy-plugin.js +5 -7
- package/dist/ui-policy-plugin.js.map +1 -1
- package/dist/utils.d.ts +10 -1
- package/dist/utils.js +16 -0
- package/dist/utils.js.map +1 -1
- package/dist/ux-list-menu-config-plugin.d.ts +2 -0
- package/dist/ux-list-menu-config-plugin.js +292 -0
- package/dist/ux-list-menu-config-plugin.js.map +1 -0
- package/dist/workspace-plugin/chrome-tab.d.ts +2 -0
- package/dist/workspace-plugin/chrome-tab.js +46 -0
- package/dist/workspace-plugin/chrome-tab.js.map +1 -0
- package/dist/workspace-plugin/constants.d.ts +52 -0
- package/dist/workspace-plugin/constants.js +56 -0
- package/dist/workspace-plugin/constants.js.map +1 -0
- package/dist/workspace-plugin/fluent-utils.d.ts +9 -0
- package/dist/workspace-plugin/fluent-utils.js +60 -0
- package/dist/workspace-plugin/fluent-utils.js.map +1 -0
- package/dist/workspace-plugin/page.d.ts +8 -0
- package/dist/workspace-plugin/page.js +108 -0
- package/dist/workspace-plugin/page.js.map +1 -0
- package/dist/workspace-plugin/screen.d.ts +1 -0
- package/dist/workspace-plugin/screen.js +38 -0
- package/dist/workspace-plugin/screen.js.map +1 -0
- package/dist/workspace-plugin/templates/index.d.ts +10 -0
- package/dist/workspace-plugin/templates/index.js +20 -0
- package/dist/workspace-plugin/templates/index.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-composition.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-composition.js +4043 -0
- package/dist/workspace-plugin/templates/record-page-composition.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-data.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-data.js +527 -0
- package/dist/workspace-plugin/templates/record-page-data.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-interalEventMappings.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-interalEventMappings.js +39 -0
- package/dist/workspace-plugin/templates/record-page-interalEventMappings.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-layoutModel.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-layoutModel.js +55 -0
- package/dist/workspace-plugin/templates/record-page-layoutModel.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page-properties.d.ts +1 -0
- package/dist/workspace-plugin/templates/record-page-properties.js +135 -0
- package/dist/workspace-plugin/templates/record-page-properties.js.map +1 -0
- package/dist/workspace-plugin/templates/record-page.d.ts +3 -0
- package/dist/workspace-plugin/templates/record-page.js +8 -0
- package/dist/workspace-plugin/templates/record-page.js.map +1 -0
- package/dist/workspace-plugin.d.ts +2 -0
- package/dist/workspace-plugin.js +453 -0
- package/dist/workspace-plugin.js.map +1 -0
- package/package.json +10 -12
- package/src/acl-plugin.ts +16 -1
- package/src/applicability-plugin.ts +82 -0
- package/src/atf/test-plugin.ts +6 -3
- package/src/basic-syntax-plugin.ts +10 -1
- package/src/business-rule-plugin.ts +2 -1
- package/src/call-expression-plugin.ts +2 -130
- package/src/column/column-to-record.ts +54 -8
- package/src/column-plugin.ts +29 -13
- package/src/dashboard/dashboard-component-property-defaults.ts +277 -0
- package/src/dashboard/dashboard-component-resolver.ts +69 -0
- package/src/dashboard/dashboard-plugin.ts +450 -0
- package/src/data-plugin.ts +67 -139
- package/src/email-notification-plugin.ts +850 -0
- package/src/flow/constants/flow-plugin-constants.ts +79 -0
- package/src/flow/flow-logic/flow-logic-constants.ts +120 -0
- package/src/flow/flow-logic/flow-logic-diagnostics.ts +591 -0
- package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +2550 -0
- package/src/flow/flow-logic/flow-logic-plugin.ts +337 -0
- package/src/flow/flow-logic/flow-logic-shapes.ts +215 -0
- package/src/flow/plugins/approval-rules-plugin.ts +48 -0
- package/src/flow/plugins/flow-action-definition-plugin.ts +295 -0
- package/src/flow/plugins/flow-data-pill-plugin.ts +258 -0
- package/src/flow/plugins/flow-definition-plugin.ts +2173 -0
- package/src/flow/plugins/flow-diagnostics-plugin.ts +280 -0
- package/src/flow/plugins/flow-instance-plugin.ts +1499 -0
- package/src/flow/plugins/flow-trigger-instance-plugin.ts +444 -0
- package/src/flow/plugins/inline-script-plugin.ts +83 -0
- package/src/flow/plugins/step-definition-plugin.ts +67 -0
- package/src/flow/plugins/step-instance-plugin.ts +431 -0
- package/src/flow/plugins/trigger-plugin.ts +95 -0
- package/src/flow/plugins/wfa-datapill-plugin.ts +213 -0
- package/src/flow/utils/approval-rules-processor.ts +298 -0
- package/src/flow/utils/built-in-complex-objects.ts +81 -0
- package/src/flow/utils/complex-object-resolver.ts +875 -0
- package/src/flow/utils/complex-objects.ts +656 -0
- package/src/flow/utils/data-pill-shapes.ts +165 -0
- package/src/flow/utils/datapill-transformer.ts +632 -0
- package/src/flow/utils/flow-constants.ts +285 -0
- package/src/flow/utils/flow-io-to-record.ts +533 -0
- package/src/flow/utils/flow-shapes.ts +296 -0
- package/src/flow/utils/flow-to-xml.ts +318 -0
- package/src/flow/utils/flow-variable-processor.ts +100 -0
- package/src/flow/utils/label-cache-parser.ts +37 -0
- package/src/flow/utils/label-cache-processor.ts +870 -0
- package/src/flow/utils/pill-string-parser.ts +375 -0
- package/src/flow/utils/schema-to-flow-object.ts +385 -0
- package/src/flow/utils/service-catalog.ts +174 -0
- package/src/flow/utils/utils.ts +395 -0
- package/src/index.ts +20 -1
- package/src/list-plugin.ts +1 -1
- package/src/now-attach-plugin.ts +14 -11
- package/src/now-ref-plugin.ts +1 -1
- package/src/record-plugin.ts +76 -11
- package/src/repack/index.ts +14 -0
- package/src/rest-api-plugin.ts +62 -50
- package/src/server-module-plugin/index.ts +112 -86
- package/src/service-catalog/catalog-clientscript-plugin.ts +140 -0
- package/src/service-catalog/catalog-item-plugin.ts +162 -0
- package/src/service-catalog/catalog-ui-policy-plugin.ts +324 -0
- package/src/service-catalog/index.ts +5 -0
- package/src/service-catalog/record-to-shape.ts +109 -0
- package/src/service-catalog/sc-record-producer-plugin.ts +201 -0
- package/src/service-catalog/service-catalog-base.ts +600 -0
- package/src/service-catalog/service-catalog-diagnostics.ts +254 -0
- package/src/service-catalog/shape-to-record.ts +279 -0
- package/src/service-catalog/utils.ts +1455 -0
- package/src/service-catalog/variable-helper.ts +135 -0
- package/src/service-catalog/variable-set-plugin.ts +197 -0
- package/src/service-catalog/variables-transform.ts +438 -0
- package/src/sla/sla-validators.ts +331 -0
- package/src/sla-plugin.ts +358 -0
- package/src/static-content-plugin.ts +25 -2
- package/src/table-plugin.ts +49 -16
- package/src/ui-page-plugin.ts +1063 -20
- package/src/ui-policy-plugin.ts +5 -9
- package/src/utils.ts +24 -1
- package/src/ux-list-menu-config-plugin.ts +312 -0
- package/src/workspace-plugin/chrome-tab.ts +44 -0
- package/src/workspace-plugin/constants.ts +53 -0
- package/src/workspace-plugin/fluent-utils.ts +60 -0
- package/src/workspace-plugin/page.ts +139 -0
- package/src/workspace-plugin/screen.ts +34 -0
- package/src/workspace-plugin/templates/index.ts +17 -0
- package/src/workspace-plugin/templates/record-page-composition.ts +4051 -0
- package/src/workspace-plugin/templates/record-page-data.ts +523 -0
- package/src/workspace-plugin/templates/record-page-interalEventMappings.ts +35 -0
- package/src/workspace-plugin/templates/record-page-layoutModel.ts +51 -0
- package/src/workspace-plugin/templates/record-page-properties.ts +131 -0
- package/src/workspace-plugin/templates/record-page.ts +6 -0
- package/src/workspace-plugin.ts +574 -0
|
@@ -0,0 +1,1455 @@
|
|
|
1
|
+
export const APPLIES_TO_CATALOG_ITEM = 'item'
|
|
2
|
+
import {
|
|
3
|
+
type Record,
|
|
4
|
+
PropertyAccessShape,
|
|
5
|
+
IdentifierShape,
|
|
6
|
+
ts,
|
|
7
|
+
type Database,
|
|
8
|
+
TemplateExpressionShape,
|
|
9
|
+
Shape,
|
|
10
|
+
TemplateSpanShape,
|
|
11
|
+
type ObjectShape,
|
|
12
|
+
type Diagnostics,
|
|
13
|
+
CallExpressionShape,
|
|
14
|
+
type Transform,
|
|
15
|
+
isGUID,
|
|
16
|
+
} from '@servicenow/sdk-build-core'
|
|
17
|
+
import { NowIncludeShape } from '../now-include-plugin'
|
|
18
|
+
import { VariableTypeName, VARIABLE_TYPE_TO_NAME } from './variable-helper'
|
|
19
|
+
|
|
20
|
+
// Note: Database, ObjectShape, Diagnostics, and CallExpressionShape are imported for validateRequestedForVariable
|
|
21
|
+
// and other utility functions that validate variable configurations
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validates that there's at most one RequestedForVariable in a variables configuration (used only for VariableSet)
|
|
25
|
+
* @param variablesShape - The ObjectShape containing the variables configuration
|
|
26
|
+
* @param diagnostics - Diagnostics instance for reporting errors
|
|
27
|
+
* @param context - Context where the validation is being performed ('VariableSet')
|
|
28
|
+
* @returns True if validation passes, false otherwise
|
|
29
|
+
*/
|
|
30
|
+
export function validateRequestedForVariable(
|
|
31
|
+
variablesShape: ObjectShape,
|
|
32
|
+
diagnostics: Diagnostics,
|
|
33
|
+
context: 'VariableSet' = 'VariableSet'
|
|
34
|
+
): boolean {
|
|
35
|
+
let requestedForCount = 0
|
|
36
|
+
const entries = Array.from(variablesShape.entries())
|
|
37
|
+
|
|
38
|
+
for (const [, value] of entries) {
|
|
39
|
+
const callExpr = value.as(CallExpressionShape)
|
|
40
|
+
const calleeName = callExpr.getCallee()
|
|
41
|
+
|
|
42
|
+
if (calleeName === VariableTypeName.REQUESTED_FOR) {
|
|
43
|
+
requestedForCount++
|
|
44
|
+
if (requestedForCount > 1) {
|
|
45
|
+
diagnostics.error(
|
|
46
|
+
variablesShape.getOriginalNode(),
|
|
47
|
+
`Only one RequestedForVariable is allowed in a ${context}`
|
|
48
|
+
)
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Combined validation for variable sets that performs multiple checks in a single loop:
|
|
59
|
+
* - At most one RequestedForVariable
|
|
60
|
+
* - Variable types not supported in multiRow variable sets
|
|
61
|
+
* @param variablesShape - The ObjectShape containing the variables configuration
|
|
62
|
+
* @param variableSetType - The type of the variable set ('singleRow' or 'multiRow')
|
|
63
|
+
* @param diagnostics - Diagnostics instance for reporting errors
|
|
64
|
+
* @param context - Context where the validation is being performed ('VariableSet')
|
|
65
|
+
* @returns True if validation passes, false otherwise
|
|
66
|
+
*/
|
|
67
|
+
export function validateVariableSetVariables(
|
|
68
|
+
variablesShape: ObjectShape,
|
|
69
|
+
variableSetType: string,
|
|
70
|
+
diagnostics: Diagnostics,
|
|
71
|
+
context: 'VariableSet' = 'VariableSet'
|
|
72
|
+
): boolean {
|
|
73
|
+
let requestedForCount = 0
|
|
74
|
+
|
|
75
|
+
// Only validate multiRow restrictions for multiRow variable sets
|
|
76
|
+
const isMultiRow = variableSetType === 'multiRow'
|
|
77
|
+
const unsupportedVariableTypes = isMultiRow
|
|
78
|
+
? new Set([
|
|
79
|
+
VariableTypeName.ATTACHMENT,
|
|
80
|
+
VariableTypeName.BREAK,
|
|
81
|
+
VariableTypeName.CONTAINER_END,
|
|
82
|
+
VariableTypeName.CONTAINER_START,
|
|
83
|
+
VariableTypeName.CONTAINER_SPLIT,
|
|
84
|
+
VariableTypeName.HTML,
|
|
85
|
+
VariableTypeName.LABEL,
|
|
86
|
+
VariableTypeName.CUSTOM, // Macro
|
|
87
|
+
VariableTypeName.CUSTOM_WITH_LABEL, // Macro with label
|
|
88
|
+
VariableTypeName.RICH_TEXT_LABEL,
|
|
89
|
+
VariableTypeName.UI_PAGE,
|
|
90
|
+
])
|
|
91
|
+
: null
|
|
92
|
+
|
|
93
|
+
const entries = Array.from(variablesShape.entries())
|
|
94
|
+
|
|
95
|
+
for (const [variableName, value] of entries) {
|
|
96
|
+
const callExpr = value.as(CallExpressionShape)
|
|
97
|
+
const calleeName = callExpr.getCallee()
|
|
98
|
+
|
|
99
|
+
// Check RequestedForVariable count
|
|
100
|
+
if (calleeName === VariableTypeName.REQUESTED_FOR) {
|
|
101
|
+
requestedForCount++
|
|
102
|
+
if (requestedForCount > 1) {
|
|
103
|
+
diagnostics.error(
|
|
104
|
+
variablesShape.getOriginalNode(),
|
|
105
|
+
`Only one RequestedForVariable is allowed in a ${context}`
|
|
106
|
+
)
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check multiRow variable type restrictions
|
|
112
|
+
if (isMultiRow && unsupportedVariableTypes && unsupportedVariableTypes.has(calleeName)) {
|
|
113
|
+
diagnostics.error(
|
|
114
|
+
value.getOriginalNode(),
|
|
115
|
+
`Variable type '${calleeName}' is not supported in multiRow variable sets. Variable '${variableName}' cannot be used.`
|
|
116
|
+
)
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @deprecated Use validateVariableSetVariables instead
|
|
126
|
+
* Validates that certain variable types are not used in multiRow variable sets.
|
|
127
|
+
* MultiRow variable sets don't support certain display/layout variables.
|
|
128
|
+
* @param variablesShape - The ObjectShape containing the variables configuration
|
|
129
|
+
* @param variableSetType - The type of the variable set ('singleRow' or 'multiRow')
|
|
130
|
+
* @param diagnostics - Diagnostics instance for reporting errors
|
|
131
|
+
* @returns True if validation passes, false otherwise
|
|
132
|
+
*/
|
|
133
|
+
export function validateVariableTypesForMultiRow(
|
|
134
|
+
variablesShape: ObjectShape,
|
|
135
|
+
variableSetType: string,
|
|
136
|
+
diagnostics: Diagnostics
|
|
137
|
+
): boolean {
|
|
138
|
+
return validateVariableSetVariables(variablesShape, variableSetType, diagnostics, 'VariableSet')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Optimized validation that checks all RequestedForVariable constraints in a single pass:
|
|
143
|
+
* - At most one RequestedForVariable in direct variables
|
|
144
|
+
* - Not used in both direct variables and attached variable sets
|
|
145
|
+
* - Only one RequestedForVariable across all attached variable sets
|
|
146
|
+
* @param arg - The ObjectShape containing the catalog item/record producer configuration
|
|
147
|
+
* @param diagnostics - Diagnostics instance for reporting errors
|
|
148
|
+
* @param context - Context where the validation is being performed ('CatalogItem' or 'RecordProducer')
|
|
149
|
+
* @returns True if validation passes, false otherwise
|
|
150
|
+
*/
|
|
151
|
+
export function validateRequestedForVariableConflict(
|
|
152
|
+
arg: ObjectShape,
|
|
153
|
+
diagnostics: Diagnostics,
|
|
154
|
+
context: 'CatalogItem' | 'RecordProducer'
|
|
155
|
+
): boolean {
|
|
156
|
+
// Single pass through direct variables: count RequestedForVariable instances
|
|
157
|
+
let requestedForCountInDirectVariables = 0
|
|
158
|
+
if (arg.get('variables').isDefined()) {
|
|
159
|
+
const variablesConfig = arg.get('variables').asObject()
|
|
160
|
+
const entries = Array.from(variablesConfig.entries())
|
|
161
|
+
|
|
162
|
+
for (const [, value] of entries) {
|
|
163
|
+
const callExpr = value.as(CallExpressionShape)
|
|
164
|
+
const calleeName = callExpr.getCallee()
|
|
165
|
+
|
|
166
|
+
if (calleeName === 'RequestedForVariable') {
|
|
167
|
+
requestedForCountInDirectVariables++
|
|
168
|
+
|
|
169
|
+
// Validate at most one in direct variables
|
|
170
|
+
if (requestedForCountInDirectVariables > 1) {
|
|
171
|
+
diagnostics.error(
|
|
172
|
+
variablesConfig.getOriginalNode(),
|
|
173
|
+
`Only one RequestedForVariable is allowed in a ${context}`
|
|
174
|
+
)
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check for RequestedForVariable conflicts across variable sets and direct variables
|
|
182
|
+
if (arg.get('variableSets').isDefined()) {
|
|
183
|
+
const variableSets = arg.get('variableSets').ifArray()?.getElements() ?? []
|
|
184
|
+
let requestedForVariableSetCount = 0
|
|
185
|
+
|
|
186
|
+
for (const variableSet of variableSets) {
|
|
187
|
+
const varObj = variableSet.asObject()
|
|
188
|
+
if (varObj.get('variableSet').isRecord()) {
|
|
189
|
+
const vsRecord = varObj.get('variableSet').asRecord()
|
|
190
|
+
const variableSetRelRecords = vsRecord.flat().filter((r: Record) => r.getTable() === 'item_option_new')
|
|
191
|
+
|
|
192
|
+
if (variableSetRelRecords && variableSetRelRecords.length > 0) {
|
|
193
|
+
for (const variableRecord of variableSetRelRecords) {
|
|
194
|
+
const varType = variableRecord.get('type')?.getValue()
|
|
195
|
+
const varTypeName = VARIABLE_TYPE_TO_NAME[varType as string]
|
|
196
|
+
|
|
197
|
+
if (varTypeName === 'RequestedForVariable') {
|
|
198
|
+
requestedForVariableSetCount++
|
|
199
|
+
|
|
200
|
+
// If direct variables also have RequestedForVariable
|
|
201
|
+
if (requestedForCountInDirectVariables > 0) {
|
|
202
|
+
diagnostics.error(
|
|
203
|
+
arg.get('variables').getOriginalNode(),
|
|
204
|
+
`RequestedForVariable cannot be used in both ${context} variables and attached VariableSets. Remove RequestedForVariable from either the ${context} variables or the attached VariableSet.`
|
|
205
|
+
)
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// If multiple variable sets have RequestedForVariable
|
|
210
|
+
if (requestedForVariableSetCount > 1) {
|
|
211
|
+
diagnostics.error(
|
|
212
|
+
arg.get('variableSets').getOriginalNode(),
|
|
213
|
+
`RequestedForVariable cannot be used in multiple VariableSets attached to the same ${context}. Only one RequestedForVariable is allowed across all VariableSets.`
|
|
214
|
+
)
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return true
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Validates that variable names (keys) in attached variable sets don't conflict with direct variable names
|
|
229
|
+
* @param arg - The ObjectShape containing the catalog item/record producer configuration
|
|
230
|
+
* @param diagnostics - Diagnostics instance for reporting errors
|
|
231
|
+
* @param context - Context where the validation is being performed ('CatalogItem' or 'RecordProducer')
|
|
232
|
+
* @returns True if validation passes, false otherwise
|
|
233
|
+
*/
|
|
234
|
+
export function validateVariableNameConflicts(
|
|
235
|
+
arg: ObjectShape,
|
|
236
|
+
diagnostics: Diagnostics,
|
|
237
|
+
context: 'CatalogItem' | 'RecordProducer'
|
|
238
|
+
): boolean {
|
|
239
|
+
// Collect all variable names from direct variables
|
|
240
|
+
const directVariableNames = new Set<string>()
|
|
241
|
+
if (arg.get('variables').isDefined()) {
|
|
242
|
+
const variablesConfig = arg.get('variables').asObject()
|
|
243
|
+
const entries = Array.from(variablesConfig.entries())
|
|
244
|
+
|
|
245
|
+
for (const [key] of entries) {
|
|
246
|
+
directVariableNames.add(key)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// If no direct variables, no conflict possible
|
|
251
|
+
if (directVariableNames.size === 0) {
|
|
252
|
+
return true
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check for name conflicts in attached variable sets
|
|
256
|
+
if (arg.get('variableSets').isDefined()) {
|
|
257
|
+
const variableSets = arg.get('variableSets').ifArray()?.getElements() ?? []
|
|
258
|
+
|
|
259
|
+
for (const variableSet of variableSets) {
|
|
260
|
+
const varObj = variableSet.asObject()
|
|
261
|
+
if (varObj.get('variableSet').isRecord()) {
|
|
262
|
+
const vsRecord = varObj.get('variableSet').asRecord()
|
|
263
|
+
const variableSetRelRecords = vsRecord.flat().filter((r: Record) => r.getTable() === 'item_option_new')
|
|
264
|
+
|
|
265
|
+
if (variableSetRelRecords && variableSetRelRecords.length > 0) {
|
|
266
|
+
for (const variableRecord of variableSetRelRecords) {
|
|
267
|
+
const varName = variableRecord.get('name')?.getValue() as string
|
|
268
|
+
|
|
269
|
+
if (varName && directVariableNames.has(varName)) {
|
|
270
|
+
diagnostics.error(
|
|
271
|
+
arg.get('variables').getOriginalNode(),
|
|
272
|
+
`Variable name conflict: '${varName}' is defined in both ${context} variables and an attached VariableSet. Each variable must have a unique name across the ${context} and all attached VariableSets.`
|
|
273
|
+
)
|
|
274
|
+
return false
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return true
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Safely converts a value to a number with a default fallback
|
|
287
|
+
* Handles empty strings and undefined values
|
|
288
|
+
* @param value - The value to convert
|
|
289
|
+
* @param defaultValue - The default value to use if conversion fails or value is empty/undefined
|
|
290
|
+
* @returns The converted number or default value
|
|
291
|
+
*/
|
|
292
|
+
export function convertToNumber(value: Shape, defaultValue: number = 0): number {
|
|
293
|
+
if (!value || value.isUndefined() || value.ifString()?.isEmpty()) {
|
|
294
|
+
return defaultValue
|
|
295
|
+
}
|
|
296
|
+
return value.toNumber()?.getValue() ?? defaultValue
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function getUITypeFromId(id: number): 'desktop' | 'mobileOrServicePortal' | 'all' {
|
|
300
|
+
switch (id) {
|
|
301
|
+
case 0:
|
|
302
|
+
return 'desktop'
|
|
303
|
+
case 1:
|
|
304
|
+
return 'mobileOrServicePortal'
|
|
305
|
+
case 10:
|
|
306
|
+
return 'all'
|
|
307
|
+
default:
|
|
308
|
+
throw Error('Invalid UI Type encountered, check XML data before transforming again.')
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function getUITypeId(value: string): number {
|
|
313
|
+
switch (value) {
|
|
314
|
+
case 'desktop':
|
|
315
|
+
return 0
|
|
316
|
+
case 'mobileOrServicePortal':
|
|
317
|
+
return 1
|
|
318
|
+
case 'all':
|
|
319
|
+
return 10
|
|
320
|
+
default:
|
|
321
|
+
return 0 // Default to desktop
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Mapping between ServiceNow variable set type database values and TypeScript enum values
|
|
327
|
+
*/
|
|
328
|
+
const VariableSetTypeMapping = {
|
|
329
|
+
one_to_one: 'singleRow',
|
|
330
|
+
one_to_many: 'multiRow',
|
|
331
|
+
} as const
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Converts ServiceNow variable set type database value to camelCase enum value
|
|
335
|
+
* @param dbValue - Database value ('one_to_one' or 'one_to_many')
|
|
336
|
+
* @returns TypeScript enum value ('singleRow' or 'multiRow')
|
|
337
|
+
*/
|
|
338
|
+
export function getVariableSetTypeFromDb(dbValue: string): 'singleRow' | 'multiRow' {
|
|
339
|
+
const type = VariableSetTypeMapping[dbValue as keyof typeof VariableSetTypeMapping]
|
|
340
|
+
if (!type) {
|
|
341
|
+
return 'singleRow' // Default to singleRow if invalid
|
|
342
|
+
}
|
|
343
|
+
return type as 'singleRow' | 'multiRow'
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Converts TypeScript enum value to ServiceNow variable set type database value
|
|
348
|
+
* @param value - TypeScript enum value ('singleRow' or 'multiRow')
|
|
349
|
+
* @returns Database value ('one_to_one' or 'one_to_many')
|
|
350
|
+
*/
|
|
351
|
+
export function getVariableSetTypeToDb(value: string): string {
|
|
352
|
+
switch (value) {
|
|
353
|
+
case 'singleRow':
|
|
354
|
+
return 'one_to_one'
|
|
355
|
+
case 'multiRow':
|
|
356
|
+
return 'one_to_many'
|
|
357
|
+
default:
|
|
358
|
+
return 'one_to_one' // Default to one_to_one if invalid
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Mapping between ServiceNow redirect URL database values and TypeScript enum values
|
|
364
|
+
*/
|
|
365
|
+
const RedirectUrlMapping = {
|
|
366
|
+
generated_record: 'generatedRecord',
|
|
367
|
+
catalog_home: 'catalogHomePage',
|
|
368
|
+
} as const
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Converts ServiceNow redirect URL database value to camelCase enum value
|
|
372
|
+
* @param dbValue - Database value ('generated_record' or 'catalog_home')
|
|
373
|
+
* @returns TypeScript enum value ('generatedRecord' or 'catalogHomePage')
|
|
374
|
+
*/
|
|
375
|
+
export function getRedirectUrlFromDb(dbValue: string): 'generatedRecord' | 'catalogHomePage' {
|
|
376
|
+
const type = RedirectUrlMapping[dbValue as keyof typeof RedirectUrlMapping]
|
|
377
|
+
if (!type) {
|
|
378
|
+
return 'generatedRecord' // Default to generatedRecord if invalid
|
|
379
|
+
}
|
|
380
|
+
return type as 'generatedRecord' | 'catalogHomePage'
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Converts TypeScript enum value to ServiceNow redirect URL database value
|
|
385
|
+
* @param value - TypeScript enum value ('generatedRecord' or 'catalogHomePage')
|
|
386
|
+
* @returns Database value ('generated_record' or 'catalog_home')
|
|
387
|
+
*/
|
|
388
|
+
export function getRedirectUrlToDb(value: string): string {
|
|
389
|
+
switch (value) {
|
|
390
|
+
case 'generatedRecord':
|
|
391
|
+
return 'generated_record'
|
|
392
|
+
case 'catalogHomePage':
|
|
393
|
+
return 'catalog_home'
|
|
394
|
+
default:
|
|
395
|
+
return 'generated_record' // Default to generated_record if invalid
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Mapping between ServiceNow fulfillment automation level database values and TypeScript enum values
|
|
401
|
+
*/
|
|
402
|
+
const FulfillmentAutomationLevelMapping = {
|
|
403
|
+
unspecified: 'unspecified',
|
|
404
|
+
manual: 'manual',
|
|
405
|
+
semi_automated: 'semiAutomated',
|
|
406
|
+
fully_automated: 'fullyAutomated',
|
|
407
|
+
} as const
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Converts ServiceNow fulfillment automation level database value to camelCase enum value
|
|
411
|
+
* @param dbValue - Database value ('unspecified', 'manual', 'semi_automated', or 'fully_automated')
|
|
412
|
+
* @returns TypeScript enum value ('unspecified', 'manual', 'semiAutomated', or 'fullyAutomated')
|
|
413
|
+
*/
|
|
414
|
+
export function getFulfillmentAutomationLevelFromDb(
|
|
415
|
+
dbValue: string
|
|
416
|
+
): 'unspecified' | 'manual' | 'semiAutomated' | 'fullyAutomated' {
|
|
417
|
+
const type = FulfillmentAutomationLevelMapping[dbValue as keyof typeof FulfillmentAutomationLevelMapping]
|
|
418
|
+
if (!type) {
|
|
419
|
+
return 'unspecified' // Default to unspecified if invalid
|
|
420
|
+
}
|
|
421
|
+
return type as 'unspecified' | 'manual' | 'semiAutomated' | 'fullyAutomated'
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Converts TypeScript enum value to ServiceNow fulfillment automation level database value
|
|
426
|
+
* @param value - TypeScript enum value ('unspecified', 'manual', 'semiAutomated', or 'fullyAutomated')
|
|
427
|
+
* @returns Database value ('unspecified', 'manual', 'semi_automated', or 'fully_automated')
|
|
428
|
+
*/
|
|
429
|
+
export function getFulfillmentAutomationLevelToDb(value: string): string {
|
|
430
|
+
switch (value) {
|
|
431
|
+
case 'unspecified':
|
|
432
|
+
return 'unspecified'
|
|
433
|
+
case 'manual':
|
|
434
|
+
return 'manual'
|
|
435
|
+
case 'semiAutomated':
|
|
436
|
+
return 'semi_automated'
|
|
437
|
+
case 'fullyAutomated':
|
|
438
|
+
return 'fully_automated'
|
|
439
|
+
default:
|
|
440
|
+
return 'unspecified' // Default to unspecified if invalid
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Mapping between ServiceNow availability database values and TypeScript enum values
|
|
446
|
+
*/
|
|
447
|
+
const AvailabilityMapping = {
|
|
448
|
+
on_desktop: 'desktopOnly',
|
|
449
|
+
on_mobile: 'mobileOnly',
|
|
450
|
+
on_both: 'both',
|
|
451
|
+
} as const
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Converts ServiceNow availability database value to camelCase enum value
|
|
455
|
+
* @param dbValue - Database value ('on_desktop', 'on_mobile', or 'on_both')
|
|
456
|
+
* @returns TypeScript enum value ('desktopOnly', 'mobileOnly', or 'both')
|
|
457
|
+
*/
|
|
458
|
+
export function getAvailabilityFromDb(dbValue: string): 'desktopOnly' | 'mobileOnly' | 'both' {
|
|
459
|
+
const type = AvailabilityMapping[dbValue as keyof typeof AvailabilityMapping]
|
|
460
|
+
if (!type) {
|
|
461
|
+
return 'desktopOnly' // Default to desktopOnly if invalid
|
|
462
|
+
}
|
|
463
|
+
return type as 'desktopOnly' | 'mobileOnly' | 'both'
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Converts TypeScript enum value to ServiceNow availability database value
|
|
468
|
+
* @param value - TypeScript enum value ('desktopOnly', 'mobileOnly', or 'both')
|
|
469
|
+
* @returns Database value ('on_desktop', 'on_mobile', or 'on_both')
|
|
470
|
+
*/
|
|
471
|
+
export function getAvailabilityToDb(value: string): string {
|
|
472
|
+
switch (value) {
|
|
473
|
+
case 'desktopOnly':
|
|
474
|
+
return 'on_desktop'
|
|
475
|
+
case 'mobileOnly':
|
|
476
|
+
return 'on_mobile'
|
|
477
|
+
case 'both':
|
|
478
|
+
return 'on_both'
|
|
479
|
+
default:
|
|
480
|
+
return 'on_desktop' // Default to on_desktop if invalid
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Mapping between ServiceNow mobile picture type database values and TypeScript enum values
|
|
486
|
+
*/
|
|
487
|
+
const MobilePictureTypeMapping = {
|
|
488
|
+
use_desktop_picture: 'desktopPicture',
|
|
489
|
+
use_mobile_picture: 'mobilePicture',
|
|
490
|
+
use_no_picture: 'noPicture',
|
|
491
|
+
} as const
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Converts ServiceNow mobile picture type database value to camelCase enum value
|
|
495
|
+
* @param dbValue - Database value ('use_desktop_picture', 'use_mobile_picture', or 'use_no_picture')
|
|
496
|
+
* @returns TypeScript enum value ('desktopPicture', 'mobilePicture', or 'noPicture')
|
|
497
|
+
*/
|
|
498
|
+
export function getMobilePictureTypeFromDb(dbValue: string): 'desktopPicture' | 'mobilePicture' | 'noPicture' {
|
|
499
|
+
const type = MobilePictureTypeMapping[dbValue as keyof typeof MobilePictureTypeMapping]
|
|
500
|
+
if (!type) {
|
|
501
|
+
return 'desktopPicture' // Default to desktopPicture if invalid
|
|
502
|
+
}
|
|
503
|
+
return type as 'desktopPicture' | 'mobilePicture' | 'noPicture'
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Converts TypeScript enum value to ServiceNow mobile picture type database value
|
|
508
|
+
* @param value - TypeScript enum value ('desktopPicture', 'mobilePicture', or 'noPicture')
|
|
509
|
+
* @returns Database value ('use_desktop_picture', 'use_mobile_picture', or 'use_no_picture')
|
|
510
|
+
*/
|
|
511
|
+
export function getMobilePictureTypeToDb(value: string): string {
|
|
512
|
+
switch (value) {
|
|
513
|
+
case 'desktopPicture':
|
|
514
|
+
return 'use_desktop_picture'
|
|
515
|
+
case 'mobilePicture':
|
|
516
|
+
return 'use_mobile_picture'
|
|
517
|
+
case 'noPicture':
|
|
518
|
+
return 'use_no_picture'
|
|
519
|
+
default:
|
|
520
|
+
return 'use_desktop_picture' // Default to use_desktop_picture if invalid
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Mapping between ServiceNow value action database values and TypeScript enum values
|
|
526
|
+
*/
|
|
527
|
+
const ValueActionMapping = {
|
|
528
|
+
clear_value: 'clearValue',
|
|
529
|
+
set_value: 'setValue',
|
|
530
|
+
} as const
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Converts ServiceNow value action database value to camelCase enum value
|
|
534
|
+
* @param dbValue - Database value ('clear_value', 'set_value', or 'ignore')
|
|
535
|
+
* @returns TypeScript enum value ('clearValue', 'setValue'), or undefined if 'ignore'
|
|
536
|
+
*/
|
|
537
|
+
export function getValueActionFromDb(dbValue: string): 'clearValue' | 'setValue' | undefined {
|
|
538
|
+
if (dbValue === 'ignore') {
|
|
539
|
+
return undefined
|
|
540
|
+
}
|
|
541
|
+
const type = ValueActionMapping[dbValue as keyof typeof ValueActionMapping]
|
|
542
|
+
if (!type) {
|
|
543
|
+
return undefined // Return undefined for 'ignore' or invalid values
|
|
544
|
+
}
|
|
545
|
+
return type as 'clearValue' | 'setValue'
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Converts TypeScript enum value to ServiceNow value action database value
|
|
550
|
+
* @param value - TypeScript enum value ('clearValue', 'setValue', or 'ignore')
|
|
551
|
+
* @returns Database value ('clear_value', 'set_value', or 'ignore')
|
|
552
|
+
*/
|
|
553
|
+
export function getValueActionToDb(value: string): string {
|
|
554
|
+
switch (value) {
|
|
555
|
+
case 'clearValue':
|
|
556
|
+
return 'clear_value'
|
|
557
|
+
case 'setValue':
|
|
558
|
+
return 'set_value'
|
|
559
|
+
case 'ignore':
|
|
560
|
+
return 'ignore'
|
|
561
|
+
default:
|
|
562
|
+
return 'ignore' // Default to ignore if invalid
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const VisibilityTypeMapping = {
|
|
567
|
+
Always: 1,
|
|
568
|
+
Bundle: 2,
|
|
569
|
+
Standalone: 3,
|
|
570
|
+
} as const
|
|
571
|
+
|
|
572
|
+
export function getVisibilityFromId(id: number): 'Always' | 'Bundle' | 'Standalone' {
|
|
573
|
+
const entry = Object.entries(VisibilityTypeMapping).find(([, value]) => value === id)
|
|
574
|
+
if (!entry) {
|
|
575
|
+
return 'Always' // Default to Always if invalid
|
|
576
|
+
}
|
|
577
|
+
return entry[0] as 'Always' | 'Bundle' | 'Standalone'
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export function getVisibilityId(value: string): number {
|
|
581
|
+
if (value in VisibilityTypeMapping) {
|
|
582
|
+
return VisibilityTypeMapping[value as keyof typeof VisibilityTypeMapping]
|
|
583
|
+
}
|
|
584
|
+
return 1 // Default to Always
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Finds a variable record from a catalog item's related records by variable name
|
|
589
|
+
* @param catalogItemRecord - The catalog item Record to search in
|
|
590
|
+
* @param variableName - The name of the variable to find
|
|
591
|
+
* @returns The variable record if found, undefined otherwise
|
|
592
|
+
*/
|
|
593
|
+
export function findNameInParent(catalogItemRecord: Record, variableName: string) {
|
|
594
|
+
const relatedRecords = catalogItemRecord.flat()
|
|
595
|
+
return relatedRecords.find(
|
|
596
|
+
(record: Record) =>
|
|
597
|
+
record.getTable() === 'item_option_new' && record.get('name')?.ifString()?.getValue() === variableName
|
|
598
|
+
)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Resolves a variable ID from a parent record and variable name with fallback logic
|
|
603
|
+
* @param parentRecord - The resolved parent record (may or may not be a Record instance)
|
|
604
|
+
* @param variableName - The name of the variable to resolve
|
|
605
|
+
* @returns The formatted variable ID with IO: prefix
|
|
606
|
+
*/
|
|
607
|
+
export function resolveVariableId(parentRecord: Record, variableName: string): string {
|
|
608
|
+
if (parentRecord.isRecord()) {
|
|
609
|
+
const variableRecord = findNameInParent(parentRecord, variableName)
|
|
610
|
+
if (variableRecord) {
|
|
611
|
+
return `IO:${variableRecord.getId().getValue()}`
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// Fallback to string value if variable record not found or parentRecord is not a Record
|
|
615
|
+
return `IO:${variableName}`
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Resolves catalog item and variable set references based on the applies_to field
|
|
620
|
+
* @param record - The source record containing catalog_item/cat_item and variable_set fields
|
|
621
|
+
* @param database - The database instance for looking up records
|
|
622
|
+
* @returns Object with catalogItemReference and variableSetReference
|
|
623
|
+
*/
|
|
624
|
+
export function resolveCatalogReferences(
|
|
625
|
+
record: Record,
|
|
626
|
+
database: Database
|
|
627
|
+
): {
|
|
628
|
+
catalogItemReference: IdentifierShape | string | undefined
|
|
629
|
+
variableSetReference: IdentifierShape | string | undefined
|
|
630
|
+
} {
|
|
631
|
+
const appliesTo = record.get('applies_to')?.ifString()?.getValue() || APPLIES_TO_CATALOG_ITEM
|
|
632
|
+
|
|
633
|
+
let catalogItemReference: IdentifierShape | string | undefined
|
|
634
|
+
let variableSetReference: IdentifierShape | string | undefined
|
|
635
|
+
|
|
636
|
+
const targetRecord = getTargetRecord(record, database)
|
|
637
|
+
|
|
638
|
+
if (targetRecord?.isRecord()) {
|
|
639
|
+
const identifier = parentIdentifier(targetRecord)
|
|
640
|
+
const recordId = targetRecord.getId().getValue()
|
|
641
|
+
|
|
642
|
+
if (appliesTo === APPLIES_TO_CATALOG_ITEM) {
|
|
643
|
+
catalogItemReference = identifier || recordId
|
|
644
|
+
} else if (appliesTo === 'set') {
|
|
645
|
+
variableSetReference = identifier || recordId
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
if (appliesTo === APPLIES_TO_CATALOG_ITEM) {
|
|
649
|
+
catalogItemReference =
|
|
650
|
+
record.get('catalog_item')?.ifString()?.getValue() || record.get('cat_item')?.ifString()?.getValue()
|
|
651
|
+
} else if (appliesTo === 'set') {
|
|
652
|
+
variableSetReference = record.get('variable_set')?.ifString()?.getValue()
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return { catalogItemReference, variableSetReference }
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Resolves a record reference (catalog item or variable set) to an IdentifierShape
|
|
661
|
+
* @param recordId - The sys_id of the record to resolve
|
|
662
|
+
* @param tableName - The table name ('sc_cat_item' or 'item_option_new_set')
|
|
663
|
+
* @param database - The database instance for looking up records
|
|
664
|
+
* @returns IdentifierShape if the record exists and has a name, otherwise the original sys_id or undefined
|
|
665
|
+
*/
|
|
666
|
+
export function resolveRecordReference(
|
|
667
|
+
recordId: string | undefined,
|
|
668
|
+
tableName: 'sc_cat_item' | 'item_option_new_set',
|
|
669
|
+
database: Database
|
|
670
|
+
): IdentifierShape | string | undefined {
|
|
671
|
+
if (!recordId || recordId.trim() === '') {
|
|
672
|
+
return undefined
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const record = database.get(tableName, recordId)
|
|
676
|
+
if (record?.isRecord()) {
|
|
677
|
+
const identifier = parentIdentifier(record)
|
|
678
|
+
return identifier || recordId
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return recordId
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export function parentIdentifier(parentRecord: Record): IdentifierShape | undefined {
|
|
685
|
+
// First try to get the name from the AST (for Fluent → platform transformation)
|
|
686
|
+
if (parentRecord.getCreator()) {
|
|
687
|
+
const parentName = parentRecord
|
|
688
|
+
?.getOriginalNode()
|
|
689
|
+
?.getFirstAncestorByKind(ts.SyntaxKind.VariableDeclaration)
|
|
690
|
+
?.getName()
|
|
691
|
+
|
|
692
|
+
if (parentName) {
|
|
693
|
+
return new IdentifierShape({
|
|
694
|
+
source: parentRecord.getOriginalNode(),
|
|
695
|
+
name: parentName,
|
|
696
|
+
value: parentRecord,
|
|
697
|
+
})
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Fallback: generate identifier name from record's name field (for platform → Fluent transformation)
|
|
702
|
+
const recordName =
|
|
703
|
+
parentRecord.get('sys_class_name')?.ifString()?.getValue() === 'item_option_new_set'
|
|
704
|
+
? parentRecord.get('title')?.ifString()?.getValue()
|
|
705
|
+
: parentRecord.get('name')?.ifString()?.getValue()
|
|
706
|
+
if (recordName) {
|
|
707
|
+
const identifierName = toValidIdentifier(recordName)
|
|
708
|
+
return new IdentifierShape({
|
|
709
|
+
source: parentRecord,
|
|
710
|
+
name: identifierName,
|
|
711
|
+
value: parentRecord,
|
|
712
|
+
})
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return undefined
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Creates a PropertyAccessShape for a catalog variable from an item record
|
|
720
|
+
* @param itemId - The item_option_new record
|
|
721
|
+
* @param source - The source record for the PropertyAccessShape
|
|
722
|
+
* @param parent - The parent record from the record
|
|
723
|
+
* @returns PropertyAccessShape representing catalogItem.variables.variableName
|
|
724
|
+
*/
|
|
725
|
+
export function createVariablePropertyAccess(
|
|
726
|
+
variableRecord: Record,
|
|
727
|
+
source: Record,
|
|
728
|
+
parent: Record
|
|
729
|
+
): PropertyAccessShape | undefined {
|
|
730
|
+
if (!variableRecord.getCreator()) {
|
|
731
|
+
return undefined
|
|
732
|
+
}
|
|
733
|
+
// Extract the catalog item variable name from the AST
|
|
734
|
+
const parentName = variableRecord
|
|
735
|
+
?.getOriginalNode()
|
|
736
|
+
?.getFirstAncestorByKind(ts.SyntaxKind.VariableDeclaration)
|
|
737
|
+
?.getName()
|
|
738
|
+
|
|
739
|
+
if (!parentName) {
|
|
740
|
+
return undefined
|
|
741
|
+
}
|
|
742
|
+
// Extract the variable property name
|
|
743
|
+
const varName = variableRecord?.getOriginalNode()?.getParent()?.asKind(ts.SyntaxKind.PropertyAssignment)?.getName()
|
|
744
|
+
|
|
745
|
+
if (!varName) {
|
|
746
|
+
return undefined
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Create identifier for the catalog item using parent's original node for proper import tracking
|
|
750
|
+
const parentIdentifier = new IdentifierShape({
|
|
751
|
+
source: parent.getOriginalNode(),
|
|
752
|
+
name: parentName,
|
|
753
|
+
value: parent,
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
// Create and return the property access shape
|
|
757
|
+
return new PropertyAccessShape({
|
|
758
|
+
source: source,
|
|
759
|
+
elements: [parentIdentifier, 'variables', varName],
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Resolves the appropriate record for catalog item or variable set operations
|
|
765
|
+
* @param record - The source record containing catalog_item/cat_item or variable_set references
|
|
766
|
+
* @param database - The database instance for looking up records by ID
|
|
767
|
+
* @returns The resolved record (sc_cat_item or item_option_new_set) or undefined if not found
|
|
768
|
+
*/
|
|
769
|
+
export function getTargetRecord(record: Record, database: Database): Record | undefined {
|
|
770
|
+
if (record.get('applies_to')?.ifString()?.getValue() === APPLIES_TO_CATALOG_ITEM) {
|
|
771
|
+
// Try catalog_item field (used in UI policies)
|
|
772
|
+
if (record.get('catalog_item')?.isDefined() && record.get('catalog_item')?.isRecord()) {
|
|
773
|
+
return record.get('catalog_item').asRecord()
|
|
774
|
+
}
|
|
775
|
+
// Try cat_item field (used in client scripts)
|
|
776
|
+
else if (record.get('cat_item')?.isDefined() && record.get('cat_item')?.isRecord()) {
|
|
777
|
+
return record.get('cat_item').asRecord()
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Try catalog_item string reference (used in UI policies)
|
|
781
|
+
else if (record.get('catalog_item')?.isDefined() && !record.get('catalog_item')?.ifString()?.isEmpty()) {
|
|
782
|
+
return database.get('sc_cat_item', record.get('catalog_item').asString()?.getValue())
|
|
783
|
+
}
|
|
784
|
+
// Try cat_item string reference (used in client scripts)
|
|
785
|
+
else if (record.get('cat_item')?.isDefined() && !record.get('cat_item')?.ifString()?.isEmpty()) {
|
|
786
|
+
return database.get('sc_cat_item', record.get('cat_item').asString()?.getValue())
|
|
787
|
+
}
|
|
788
|
+
//Fallback to sys_id
|
|
789
|
+
return undefined
|
|
790
|
+
} else {
|
|
791
|
+
// Handle variable set record reference
|
|
792
|
+
if (record.get('variable_set')?.isRecord()) {
|
|
793
|
+
return record.get('variable_set').asRecord()
|
|
794
|
+
}
|
|
795
|
+
// Handle variable set string reference
|
|
796
|
+
else if (!record.get('variable_set')?.ifString()?.isEmpty()) {
|
|
797
|
+
return database.get('item_option_new_set', record.get('variable_set').asString()?.getValue())
|
|
798
|
+
}
|
|
799
|
+
return undefined
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Resolves a variable name from a PropertyAccessShape
|
|
805
|
+
* @param variableNameShape - The shape containing the property access
|
|
806
|
+
* @returns An object containing the parent record and variable name
|
|
807
|
+
*/
|
|
808
|
+
export function resolveVariableAccess(variableNameShape: PropertyAccessShape):
|
|
809
|
+
| {
|
|
810
|
+
parentRecord: Record
|
|
811
|
+
variableName: string
|
|
812
|
+
}
|
|
813
|
+
| undefined {
|
|
814
|
+
const propertyAccess = variableNameShape
|
|
815
|
+
const variableName = propertyAccess.getLastElement().getName()
|
|
816
|
+
const parentIdentifier = propertyAccess.getElements()[0]
|
|
817
|
+
|
|
818
|
+
try {
|
|
819
|
+
const resolved = parentIdentifier.resolve(true)
|
|
820
|
+
if (!resolved || !resolved.isRecord()) {
|
|
821
|
+
return undefined
|
|
822
|
+
}
|
|
823
|
+
const parentRecord = resolved.asRecord()
|
|
824
|
+
return { parentRecord, variableName }
|
|
825
|
+
} catch (error) {
|
|
826
|
+
// Resolution failed, return undefined to allow fallback
|
|
827
|
+
return undefined
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Processes a catalog condition template expression
|
|
832
|
+
* @param conditionShape - The shape containing the catalog condition
|
|
833
|
+
* @param appliesTo - Optional appliesTo value for validation
|
|
834
|
+
* @param variableSetShape - Optional variable set shape for validation
|
|
835
|
+
* @param catalogItemShape - Optional catalog item shape for validation
|
|
836
|
+
* @param diagnostics - Optional diagnostics instance for reporting validation errors
|
|
837
|
+
* @returns The processed condition string with resolved variable references
|
|
838
|
+
*/
|
|
839
|
+
export function processCatalogCondition(
|
|
840
|
+
conditionShape: Shape<unknown>,
|
|
841
|
+
appliesTo?: 'set' | 'item',
|
|
842
|
+
variableSetShape?: Shape<unknown>,
|
|
843
|
+
catalogItemShape?: Shape<unknown>,
|
|
844
|
+
diagnostics?: Diagnostics
|
|
845
|
+
): string | undefined {
|
|
846
|
+
// Handle template expressions with variable references
|
|
847
|
+
if (conditionShape.is(TemplateExpressionShape)) {
|
|
848
|
+
const templateExpr = conditionShape.as(TemplateExpressionShape)
|
|
849
|
+
let result = templateExpr.getLiteralText()
|
|
850
|
+
|
|
851
|
+
// Process each span (interpolated expression)
|
|
852
|
+
for (const span of templateExpr.getSpans()) {
|
|
853
|
+
const expr = span.getExpression()
|
|
854
|
+
|
|
855
|
+
// Handle property access (e.g., catalogItem.variables.name)
|
|
856
|
+
if (expr.is(PropertyAccessShape)) {
|
|
857
|
+
const variableAccess = resolveVariableAccess(expr.as(PropertyAccessShape))
|
|
858
|
+
if (variableAccess) {
|
|
859
|
+
const variableId = resolveVariableId(variableAccess.parentRecord, variableAccess.variableName)
|
|
860
|
+
result += variableId
|
|
861
|
+
|
|
862
|
+
// Validate variable belongs to target based on appliesTo
|
|
863
|
+
const targetShape = appliesTo === 'set' ? variableSetShape : catalogItemShape
|
|
864
|
+
if (appliesTo && targetShape?.is(IdentifierShape)) {
|
|
865
|
+
validateVariableBelongsToTarget(
|
|
866
|
+
targetShape.as(IdentifierShape),
|
|
867
|
+
variableAccess.parentRecord,
|
|
868
|
+
appliesTo,
|
|
869
|
+
diagnostics
|
|
870
|
+
)
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
result += expr.toString().getValue()
|
|
874
|
+
}
|
|
875
|
+
} else {
|
|
876
|
+
// Fallback to string value
|
|
877
|
+
result += expr.toString().getValue()
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
result += span.getLiteralText()
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return result
|
|
884
|
+
} else {
|
|
885
|
+
// Handle plain string
|
|
886
|
+
if (conditionShape.ifString()?.isEmpty()) {
|
|
887
|
+
return undefined
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const conditionValue = conditionShape.toString()?.getValue()
|
|
891
|
+
if (!conditionValue) {
|
|
892
|
+
return undefined
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Add IO: prefixes back for XML format
|
|
896
|
+
// Handle patterns like: c34b1842b7321010e54deb56ee11a92b=21^ORc34b1842b7321010e54deb56ee11a92b=18^...
|
|
897
|
+
// Should become: IO:c34b1842b7321010e54deb56ee11a92b=21^ORIO:c34b1842b7321010e54deb56ee11a92b=18^...
|
|
898
|
+
// Match the correct ServiceNow catalog condition format
|
|
899
|
+
return conditionValue
|
|
900
|
+
.replace(/^(?!IO:)/, 'IO:') // Add IO: if not already present
|
|
901
|
+
.replace(/\^OR(?!IO:)/g, '^ORIO:') // Convert ^OR to ^ORIO: (but not if already ^ORIO:)
|
|
902
|
+
.replace(/\^NQ(?!IO:)/g, '^NQIO:') // Convert ^NQ to ^NQIO: (but not if already ^NQIO:)
|
|
903
|
+
.replace(/\^(?!(ORIO:|NQIO:|EQ))/g, '^IO:') // Add IO: after ^ except for ORIO:, NQIO:, EQ
|
|
904
|
+
.replace(/(?<!\^EQ)$/, '^EQ') // Add ^EQ at end if not already present
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Represents the result of parsing a single catalog condition part
|
|
910
|
+
*/
|
|
911
|
+
interface ParsedConditionPart {
|
|
912
|
+
logicalOperator: string
|
|
913
|
+
processedContent: string | PropertyAccessShape
|
|
914
|
+
operatorSuffix: string
|
|
915
|
+
hasPropertyAccess: boolean
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Parses a single catalog condition part and extracts logical operators and variable references
|
|
920
|
+
* @param part - The condition part to parse
|
|
921
|
+
* @param record - The source record
|
|
922
|
+
* @param database - The database instance for looking up records
|
|
923
|
+
* @returns Parsed condition part with extracted components
|
|
924
|
+
*/
|
|
925
|
+
export function parseConditionPart(part: string, record: Record, database: Database): ParsedConditionPart | undefined {
|
|
926
|
+
// Skip empty parts
|
|
927
|
+
if (!part.trim()) {
|
|
928
|
+
return undefined
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Check for logical operators at the beginning (only OR and NQ are valid prefixes)
|
|
932
|
+
const logicalOperatorMatch = part.match(/^(OR|NQ)/)
|
|
933
|
+
const logicalOperator: string = logicalOperatorMatch?.[1] || ''
|
|
934
|
+
const remainingPart = logicalOperator ? part.substring(logicalOperator.length) : part
|
|
935
|
+
|
|
936
|
+
// Handle different possible ID formats
|
|
937
|
+
let conditionVarId = ''
|
|
938
|
+
let operatorSuffix = ''
|
|
939
|
+
|
|
940
|
+
if (remainingPart.length >= 32) {
|
|
941
|
+
// Try standard 32-character ID first
|
|
942
|
+
const potentialId = remainingPart.substring(0, 32)
|
|
943
|
+
if (isGUID(potentialId)) {
|
|
944
|
+
conditionVarId = potentialId
|
|
945
|
+
operatorSuffix = remainingPart.substring(32)
|
|
946
|
+
} else {
|
|
947
|
+
// Look for other patterns like variable names
|
|
948
|
+
const varMatch = remainingPart.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(.*)/)
|
|
949
|
+
if (varMatch?.[1]) {
|
|
950
|
+
conditionVarId = varMatch[1]
|
|
951
|
+
operatorSuffix = varMatch[2] || ''
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const conditionVarRecord = conditionVarId.length === 32 ? database.get('item_option_new', conditionVarId) : null
|
|
957
|
+
const appliesToRecord = getTargetRecord(record, database)
|
|
958
|
+
|
|
959
|
+
if (conditionVarRecord && conditionVarId.length === 32 && appliesToRecord) {
|
|
960
|
+
const propAccess = createVariablePropertyAccess(conditionVarRecord, record, appliesToRecord)
|
|
961
|
+
if (propAccess) {
|
|
962
|
+
return {
|
|
963
|
+
logicalOperator,
|
|
964
|
+
processedContent: propAccess,
|
|
965
|
+
operatorSuffix,
|
|
966
|
+
hasPropertyAccess: true,
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Fallback: treat as literal text if no variable found
|
|
972
|
+
// Use remainingPart (without logical operator) to avoid duplicating the OR/NQ prefix
|
|
973
|
+
return {
|
|
974
|
+
logicalOperator,
|
|
975
|
+
processedContent: remainingPart,
|
|
976
|
+
operatorSuffix: '',
|
|
977
|
+
hasPropertyAccess: false,
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Processes catalog conditions and converts them to either a TemplateExpressionShape or a string
|
|
983
|
+
* @param record - The record containing the catalog conditions
|
|
984
|
+
* @param database - The database instance for looking up records
|
|
985
|
+
* @returns The processed conditions as a TemplateExpressionShape or string, or undefined if no conditions
|
|
986
|
+
*/
|
|
987
|
+
export function processCatalogConditionsToShape(
|
|
988
|
+
record: Record,
|
|
989
|
+
database: Database
|
|
990
|
+
): TemplateExpressionShape | string | undefined {
|
|
991
|
+
let conditionsValue = record.get('catalog_conditions')?.ifString()?.getValue()
|
|
992
|
+
if (!conditionsValue) {
|
|
993
|
+
return undefined
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Handle the real ServiceNow catalog condition format
|
|
997
|
+
// Real format: ^ORIO:OR needs to become ^OR (not ^OROR!)
|
|
998
|
+
conditionsValue = conditionsValue
|
|
999
|
+
.replace(/\^ORIO:/g, '^OR') // Handle ^ORIO: -> ^OR
|
|
1000
|
+
.replace(/\^NQIO:/g, '^NQ') // Handle ^NQIO: -> ^NQ
|
|
1001
|
+
.replace(/IO:/g, '') // Remove all remaining IO: prefixes
|
|
1002
|
+
.replace(/(?<!\^EQ)$/, '^EQ') // Add ^EQ at end if not already present
|
|
1003
|
+
|
|
1004
|
+
// Don't process ^OR again since we've already handled ^ORIO: above
|
|
1005
|
+
if (!conditionsValue) {
|
|
1006
|
+
return undefined
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const conditionParts = conditionsValue.split('^')
|
|
1010
|
+
let hasPropertyAccess = false
|
|
1011
|
+
const processedParts: (string | PropertyAccessShape)[] = []
|
|
1012
|
+
|
|
1013
|
+
conditionParts.forEach((part, index) => {
|
|
1014
|
+
const parsed = parseConditionPart(part, record, database)
|
|
1015
|
+
if (!parsed) {
|
|
1016
|
+
return // Skip empty parts
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if (parsed.hasPropertyAccess) {
|
|
1020
|
+
hasPropertyAccess = true
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// Build the complete part string with logical operator and suffix
|
|
1024
|
+
let partString = ''
|
|
1025
|
+
|
|
1026
|
+
// Add logical operator prefix if present
|
|
1027
|
+
if (parsed.logicalOperator) {
|
|
1028
|
+
partString += parsed.logicalOperator
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Add the main content (property access or literal)
|
|
1032
|
+
if (typeof parsed.processedContent === 'string') {
|
|
1033
|
+
partString += parsed.processedContent
|
|
1034
|
+
// Add operator suffix if present
|
|
1035
|
+
if (parsed.operatorSuffix) {
|
|
1036
|
+
partString += parsed.operatorSuffix
|
|
1037
|
+
}
|
|
1038
|
+
processedParts.push(partString)
|
|
1039
|
+
} else {
|
|
1040
|
+
// For property access, push logical operator prefix as literal text first
|
|
1041
|
+
if (partString) {
|
|
1042
|
+
processedParts.push(partString)
|
|
1043
|
+
}
|
|
1044
|
+
processedParts.push(parsed.processedContent)
|
|
1045
|
+
// Push operator suffix as literal text after the property access
|
|
1046
|
+
if (parsed.operatorSuffix) {
|
|
1047
|
+
processedParts.push(parsed.operatorSuffix)
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Add separator between parts (but not after last)
|
|
1052
|
+
if (index < conditionParts.length - 1) {
|
|
1053
|
+
processedParts.push('^')
|
|
1054
|
+
}
|
|
1055
|
+
})
|
|
1056
|
+
|
|
1057
|
+
if (!hasPropertyAccess) {
|
|
1058
|
+
return processedParts.join('')
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Build template expression
|
|
1062
|
+
const spans: TemplateSpanShape[] = []
|
|
1063
|
+
let currentIndex = 0
|
|
1064
|
+
|
|
1065
|
+
for (const part of processedParts) {
|
|
1066
|
+
if (part instanceof PropertyAccessShape) {
|
|
1067
|
+
const nextPropIndex = processedParts.findIndex(
|
|
1068
|
+
(p, idx) => idx > currentIndex && p instanceof PropertyAccessShape
|
|
1069
|
+
)
|
|
1070
|
+
const endIndex = nextPropIndex === -1 ? processedParts.length : nextPropIndex
|
|
1071
|
+
const literalText = processedParts.slice(currentIndex + 1, endIndex).join('')
|
|
1072
|
+
|
|
1073
|
+
spans.push(
|
|
1074
|
+
new TemplateSpanShape({
|
|
1075
|
+
source: record,
|
|
1076
|
+
expression: part,
|
|
1077
|
+
literalText,
|
|
1078
|
+
})
|
|
1079
|
+
)
|
|
1080
|
+
}
|
|
1081
|
+
currentIndex++
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const firstPropIndex = processedParts.findIndex((p) => p instanceof PropertyAccessShape)
|
|
1085
|
+
const initialLiteral = processedParts.slice(0, firstPropIndex).join('')
|
|
1086
|
+
|
|
1087
|
+
return new TemplateExpressionShape({
|
|
1088
|
+
source: record,
|
|
1089
|
+
literalText: initialLiteral,
|
|
1090
|
+
spans,
|
|
1091
|
+
})
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Converts a title to a valid ServiceNow internal name (snake_case)
|
|
1096
|
+
* @param title - The title to convert
|
|
1097
|
+
* @returns Snake_case internal name
|
|
1098
|
+
*/
|
|
1099
|
+
export function convertTitleToInternalName(title: string): string {
|
|
1100
|
+
return title
|
|
1101
|
+
.trim()
|
|
1102
|
+
.toLowerCase()
|
|
1103
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
1104
|
+
.replace(/^_+|_+$/g, '')
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Validation constants for internal names
|
|
1109
|
+
*/
|
|
1110
|
+
export const INTERNAL_NAME_REGEX = /^[a-z][a-z0-9_]*$/
|
|
1111
|
+
export const INTERNAL_NAME_MAX_LENGTH = 80
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Validates an internal name for ServiceNow compatibility
|
|
1115
|
+
* @param internalName - The internal name to validate
|
|
1116
|
+
* @returns Validation result with error message if invalid
|
|
1117
|
+
*/
|
|
1118
|
+
export function validateInternalName(internalName: string): {
|
|
1119
|
+
valid: boolean
|
|
1120
|
+
error?: string
|
|
1121
|
+
} {
|
|
1122
|
+
if (!INTERNAL_NAME_REGEX.test(internalName)) {
|
|
1123
|
+
return {
|
|
1124
|
+
valid: false,
|
|
1125
|
+
error:
|
|
1126
|
+
'VariableSet internalName must start with a lowercase letter and contain only lowercase letters, numbers, and underscores (snake_case format). ' +
|
|
1127
|
+
'No spaces, hyphens, or uppercase letters allowed.',
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (internalName.length > INTERNAL_NAME_MAX_LENGTH) {
|
|
1132
|
+
return {
|
|
1133
|
+
valid: false,
|
|
1134
|
+
error:
|
|
1135
|
+
`VariableSet internalName must be ${INTERNAL_NAME_MAX_LENGTH} characters or less. ` +
|
|
1136
|
+
`Current length: ${internalName.length}`,
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
return { valid: true }
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Converts role values (arrays or Role records) to comma-separated string for ServiceNow storage
|
|
1145
|
+
* Handles both string arrays and Role record references
|
|
1146
|
+
* @param v - Shape value containing role data
|
|
1147
|
+
* @returns Comma-separated role string or undefined
|
|
1148
|
+
*/
|
|
1149
|
+
export function convertRolesToString(v: Shape): string | undefined {
|
|
1150
|
+
return v
|
|
1151
|
+
.ifArray()
|
|
1152
|
+
?.pipe((r) =>
|
|
1153
|
+
Array.from(
|
|
1154
|
+
new Set(r.getElements().map((role) => role.ifRecord()?.get('name').getValue() ?? role.getValue()))
|
|
1155
|
+
).join(',')
|
|
1156
|
+
)
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Parses comma-separated string into an array
|
|
1161
|
+
* @param string - Comma-separated string (e.g., "admin,itil")
|
|
1162
|
+
* @returns Array of trimmed strings
|
|
1163
|
+
*/
|
|
1164
|
+
export function parseString(commaSeparatedString: Shape): string[] {
|
|
1165
|
+
if (commaSeparatedString.isUndefined() || commaSeparatedString.toString().isEmpty()) {
|
|
1166
|
+
return []
|
|
1167
|
+
}
|
|
1168
|
+
return commaSeparatedString
|
|
1169
|
+
.toString()
|
|
1170
|
+
.getValue()
|
|
1171
|
+
.split(',')
|
|
1172
|
+
.map((s) => s.trim())
|
|
1173
|
+
.filter(Boolean)
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Checks if the record should be written as a call expression
|
|
1177
|
+
* @param record - The record to check
|
|
1178
|
+
* @returns True if the record should be written as a call expression, false otherwise
|
|
1179
|
+
*/
|
|
1180
|
+
export function shouldWriteAsCallExpression(record: Record): boolean {
|
|
1181
|
+
const originalSource = record.getOriginalSource()
|
|
1182
|
+
return ts.Node.isNode(originalSource) && originalSource.isKind(ts.SyntaxKind.CallExpression)
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Converts a name to a valid JavaScript identifier
|
|
1187
|
+
* Examples:
|
|
1188
|
+
* "Backend item" -> "backendItem"
|
|
1189
|
+
* "Developer Workstation" -> "developerWorkstation"
|
|
1190
|
+
* "My-Special_Item 123" -> "mySpecialItem123"
|
|
1191
|
+
*/
|
|
1192
|
+
export function toValidIdentifier(name: string): string {
|
|
1193
|
+
return (
|
|
1194
|
+
name
|
|
1195
|
+
.trim()
|
|
1196
|
+
// Replace non-alphanumeric characters with spaces
|
|
1197
|
+
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
|
1198
|
+
// Convert to camelCase
|
|
1199
|
+
.split(' ')
|
|
1200
|
+
.filter(Boolean)
|
|
1201
|
+
.map((word, index) => {
|
|
1202
|
+
const lowerWord = word.toLowerCase()
|
|
1203
|
+
return index === 0 ? lowerWord : lowerWord.charAt(0).toUpperCase() + lowerWord.slice(1)
|
|
1204
|
+
})
|
|
1205
|
+
.join('')
|
|
1206
|
+
// Ensure it doesn't start with a number
|
|
1207
|
+
.replace(/^[0-9]/, '_$&')
|
|
1208
|
+
)
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Default delivery time value used when no delivery time is specified
|
|
1213
|
+
*/
|
|
1214
|
+
export const DEFAULT_DELIVERY_TIME = '1970-01-01 00:00:00'
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Creates a NowIncludeShape with a custom file path suffix for script separation
|
|
1218
|
+
*/
|
|
1219
|
+
export async function createScript(
|
|
1220
|
+
record: Record,
|
|
1221
|
+
scriptContent: string | Shape,
|
|
1222
|
+
transform: Transform,
|
|
1223
|
+
suffix: string
|
|
1224
|
+
): Promise<NowIncludeShape> {
|
|
1225
|
+
const baseName = await transform.getUpdateName(record)
|
|
1226
|
+
const content = scriptContent instanceof Shape ? scriptContent.toString().getValue() : scriptContent
|
|
1227
|
+
return new NowIncludeShape({
|
|
1228
|
+
source: record,
|
|
1229
|
+
path: `./${baseName}-${suffix}.js`,
|
|
1230
|
+
includedText: content,
|
|
1231
|
+
})
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* Default script
|
|
1236
|
+
*/
|
|
1237
|
+
export const defaultScript = `/** This script is executed before the Record is generated
|
|
1238
|
+
* \`current\`- GlideRecord produced by Record Producer
|
|
1239
|
+
* Don't use \`current.update()\` or \`current.insert()\` as the record is generated by Record Producer
|
|
1240
|
+
* Don't use \`current.setValue('sys_class_name', 'xxx')\` as this will trigger reparent flow and can cause data loss
|
|
1241
|
+
* Avoid \`current.setAbortAction()\` and generate a separate record
|
|
1242
|
+
* Use \`producer.var1\` to access variables
|
|
1243
|
+
*/`
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Default post insert script
|
|
1247
|
+
*/
|
|
1248
|
+
export const defaultpostInsertScript = `/**
|
|
1249
|
+
* This script is executed after the record is generated.
|
|
1250
|
+
* \`current\` Is the GlideRecord produced by Record Producer. Use \`current.update()\` to update the record
|
|
1251
|
+
* To access the variables, use \`producer.var1\` where var1 is the name of the variable
|
|
1252
|
+
* To access the Record Producer use \`cat_item\`
|
|
1253
|
+
*/`
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Default save script
|
|
1257
|
+
*/
|
|
1258
|
+
export const defaultSaveScript = `/**
|
|
1259
|
+
* This script is executed at every step save in Catalog Builder.
|
|
1260
|
+
* This script is executed before \`Script\` is executed.
|
|
1261
|
+
* \`current\` Is the GlideRecord produced by Record Producer.
|
|
1262
|
+
* To access the variables, use \`producer.var1\` where var1 is the name of the variable
|
|
1263
|
+
* To access the Record Producer use \`cat_item\`
|
|
1264
|
+
*/`
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Validates that a variable belongs to a target record (variable set or catalog item)
|
|
1268
|
+
* Reports diagnostic error if validation fails
|
|
1269
|
+
* @param targetShape - The IdentifierShape for the target reference (variable set or catalog item)
|
|
1270
|
+
* @param parentRecord - The parent record where the variable is defined
|
|
1271
|
+
* @param appliesTo - Whether the validation is for 'set' (variable set) or 'item' (catalog item)
|
|
1272
|
+
* @param diagnostics - Optional diagnostics instance for reporting errors
|
|
1273
|
+
* @returns true if the variable belongs to the target, false otherwise
|
|
1274
|
+
*/
|
|
1275
|
+
export function validateVariableBelongsToTarget(
|
|
1276
|
+
targetShape: IdentifierShape,
|
|
1277
|
+
parentRecord: Record,
|
|
1278
|
+
appliesTo: 'set' | 'item',
|
|
1279
|
+
diagnostics?: Diagnostics
|
|
1280
|
+
): boolean {
|
|
1281
|
+
const resolved = targetShape.resolve(true)
|
|
1282
|
+
if (!resolved?.isRecord()) {
|
|
1283
|
+
return false
|
|
1284
|
+
}
|
|
1285
|
+
const targetRecord = resolved.asRecord()
|
|
1286
|
+
|
|
1287
|
+
// Validate that parentRecord ID matches target record ID
|
|
1288
|
+
if (parentRecord.getId().getValue() === targetRecord.getId().getValue()) {
|
|
1289
|
+
return true
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (appliesTo === 'item') {
|
|
1293
|
+
// Validate that variable is from variable set AND that variable set is added to the catalog item
|
|
1294
|
+
const isValid = targetRecord
|
|
1295
|
+
.flat()
|
|
1296
|
+
.some(
|
|
1297
|
+
(r: Record) =>
|
|
1298
|
+
r.getTable() === 'io_set_item' &&
|
|
1299
|
+
r.get('sc_cat_item')?.asRecord()?.getId()?.getValue() === targetRecord.getId()?.getValue()
|
|
1300
|
+
)
|
|
1301
|
+
if (!isValid && diagnostics) {
|
|
1302
|
+
diagnostics.error(targetShape.getOriginalNode(), 'Variable is not in the catalog item')
|
|
1303
|
+
}
|
|
1304
|
+
return isValid
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// For 'set' case
|
|
1308
|
+
if (diagnostics) {
|
|
1309
|
+
diagnostics.error(targetShape.getOriginalNode(), 'Variable is not in the variable set')
|
|
1310
|
+
}
|
|
1311
|
+
return false
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Resolves a variable ID from variableNameShape and validates it belongs to the variable set if appliesTo is 'set'
|
|
1316
|
+
* @param variableNameShape - The shape from get('variableName', false)
|
|
1317
|
+
* @param appliesTo - The value from get('appliesTo')
|
|
1318
|
+
* @param variableSetShape - The shape from get('variableSet', false)
|
|
1319
|
+
* @param diagnostics - Diagnostics instance for reporting errors
|
|
1320
|
+
* @returns The resolved variable ID with IO: prefix, or undefined if resolution fails
|
|
1321
|
+
*/
|
|
1322
|
+
export function resolveAndValidateVariableId(
|
|
1323
|
+
variableNameShape: Shape<unknown> | undefined,
|
|
1324
|
+
appliesTo: 'set' | 'item',
|
|
1325
|
+
variableSetShape: Shape<unknown> | undefined,
|
|
1326
|
+
catalogItemShape: Shape<unknown> | undefined,
|
|
1327
|
+
diagnostics: Diagnostics
|
|
1328
|
+
): string | undefined {
|
|
1329
|
+
if (!variableNameShape) {
|
|
1330
|
+
return undefined
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
let variableId: string | undefined
|
|
1334
|
+
|
|
1335
|
+
if (variableNameShape.is(PropertyAccessShape)) {
|
|
1336
|
+
const variableAccess = resolveVariableAccess(variableNameShape.as(PropertyAccessShape))
|
|
1337
|
+
if (variableAccess) {
|
|
1338
|
+
const { parentRecord, variableName } = variableAccess
|
|
1339
|
+
variableId = resolveVariableId(parentRecord, variableName)
|
|
1340
|
+
|
|
1341
|
+
// Validate variable belongs to target based on appliesTo (default to 'item')
|
|
1342
|
+
const appliesToValue: 'set' | 'item' = appliesTo === 'set' ? 'set' : 'item'
|
|
1343
|
+
const targetShape = appliesToValue === 'set' ? variableSetShape : catalogItemShape
|
|
1344
|
+
if (targetShape?.is(IdentifierShape)) {
|
|
1345
|
+
validateVariableBelongsToTarget(
|
|
1346
|
+
targetShape.as(IdentifierShape),
|
|
1347
|
+
parentRecord,
|
|
1348
|
+
appliesToValue,
|
|
1349
|
+
diagnostics
|
|
1350
|
+
)
|
|
1351
|
+
}
|
|
1352
|
+
} else {
|
|
1353
|
+
// Fallback to string value if resolution fails
|
|
1354
|
+
variableId = `IO:${variableNameShape.toString()?.getValue()}`
|
|
1355
|
+
}
|
|
1356
|
+
} else {
|
|
1357
|
+
// Handle non-PropertyAccessShape case
|
|
1358
|
+
variableId = `IO:${variableNameShape.toString()?.getValue()}`
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
return variableId
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* Validates that each variable's 'field' value (when 'mapToField' is true) belongs to the record producer's target table.
|
|
1366
|
+
* Resolves the table record from arg.get('table'), collects field names from sys_documentation descendants via .flat(),
|
|
1367
|
+
* and checks each variable's 'field' against those keys.
|
|
1368
|
+
* @param arg - The ObjectShape containing the record producer configuration
|
|
1369
|
+
* @param diagnostics - Diagnostics instance for reporting errors
|
|
1370
|
+
* @param context - Context where the validation is being performed ('RecordProducer')
|
|
1371
|
+
* @returns True if all mapped fields belong to the table, false otherwise
|
|
1372
|
+
*/
|
|
1373
|
+
export function validateFieldNameBelongsToTable(arg: ObjectShape, diagnostics: Diagnostics, context: string): boolean {
|
|
1374
|
+
if (!arg.get('variables').isDefined()) {
|
|
1375
|
+
return true
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// Resolve the table record from arg.get('table')
|
|
1379
|
+
// table can be a direct record reference (IdentifierShape) or a plain string table name
|
|
1380
|
+
const tableShape = arg.get('table')
|
|
1381
|
+
|
|
1382
|
+
let tableRecord: Record | undefined
|
|
1383
|
+
|
|
1384
|
+
if (tableShape.isRecord()) {
|
|
1385
|
+
tableRecord = tableShape.asRecord()
|
|
1386
|
+
} else if (tableShape.is(IdentifierShape)) {
|
|
1387
|
+
const resolved = tableShape.as(IdentifierShape).resolve(true)
|
|
1388
|
+
if (resolved?.isRecord()) {
|
|
1389
|
+
tableRecord = resolved.asRecord()
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Collect field names from sys_documentation descendants via .flat() (only when table resolves to a record)
|
|
1394
|
+
let tableFieldSet: Set<string> | undefined
|
|
1395
|
+
|
|
1396
|
+
if (tableRecord) {
|
|
1397
|
+
const tableFields = tableRecord
|
|
1398
|
+
.flat()
|
|
1399
|
+
.filter((r: Record) => r.getTable() === 'sys_dictionary')
|
|
1400
|
+
.map((r: Record) => r.get('element')?.ifString()?.getValue())
|
|
1401
|
+
.filter((element): element is string => !!element)
|
|
1402
|
+
|
|
1403
|
+
if (tableFields.length > 0) {
|
|
1404
|
+
tableFieldSet = new Set(tableFields)
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Iterate over variables and validate field names for those with mapToField: true
|
|
1409
|
+
const variablesConfig = arg.get('variables').asObject()
|
|
1410
|
+
const entries = Array.from(variablesConfig.entries())
|
|
1411
|
+
|
|
1412
|
+
// Track which fields have already been mapped to detect duplicates (always enforced)
|
|
1413
|
+
const mappedFields = new Map<string, string>() // field -> first variableKey that mapped it
|
|
1414
|
+
|
|
1415
|
+
for (const [variableKey, value] of entries) {
|
|
1416
|
+
const callExpr = value.as(CallExpressionShape)
|
|
1417
|
+
const config = callExpr.getArgument(0).asObject()
|
|
1418
|
+
|
|
1419
|
+
const mapToField = config.get('mapToField')
|
|
1420
|
+
const isMapToFieldTrue = mapToField.isDefined() && mapToField.ifBoolean()?.getValue() === true
|
|
1421
|
+
|
|
1422
|
+
if (!isMapToFieldTrue) {
|
|
1423
|
+
continue
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
const fieldShape = config.get('field')
|
|
1427
|
+
const fieldValue = fieldShape.isDefined() ? fieldShape.ifString()?.getValue() : undefined
|
|
1428
|
+
|
|
1429
|
+
if (!fieldValue) {
|
|
1430
|
+
continue
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// Check field belongs to table (only when table resolved to a record with sys_documentation)
|
|
1434
|
+
if (tableFieldSet && !tableFieldSet.has(fieldValue)) {
|
|
1435
|
+
diagnostics.error(
|
|
1436
|
+
fieldShape,
|
|
1437
|
+
`${context} variable '${variableKey}': field '${fieldValue}' does not belong to the target table. Valid fields are: ${[...tableFieldSet].join(', ')}.`
|
|
1438
|
+
)
|
|
1439
|
+
return false
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Check if this field is already mapped by another variable (always enforced)
|
|
1443
|
+
const existingVariable = mappedFields.get(fieldValue)
|
|
1444
|
+
if (existingVariable) {
|
|
1445
|
+
diagnostics.error(
|
|
1446
|
+
fieldShape,
|
|
1447
|
+
`${context} variable '${variableKey}': field '${fieldValue}' is already mapped by variable '${existingVariable}'. Each table field can only be mapped by one variable.`
|
|
1448
|
+
)
|
|
1449
|
+
return false
|
|
1450
|
+
}
|
|
1451
|
+
mappedFields.set(fieldValue, variableKey)
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
return true
|
|
1455
|
+
}
|