@servicenow/sdk-build-plugins 4.4.0 → 4.5.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.
Files changed (287) hide show
  1. package/dist/acl-plugin.js +54 -4
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/applicability-plugin.js +2 -0
  4. package/dist/applicability-plugin.js.map +1 -1
  5. package/dist/application-menu-plugin.js +2 -0
  6. package/dist/application-menu-plugin.js.map +1 -1
  7. package/dist/arrow-function-plugin.d.ts +6 -1
  8. package/dist/arrow-function-plugin.js +105 -12
  9. package/dist/arrow-function-plugin.js.map +1 -1
  10. package/dist/atf/test-plugin.js +2 -0
  11. package/dist/atf/test-plugin.js.map +1 -1
  12. package/dist/basic-syntax-plugin.js +20 -0
  13. package/dist/basic-syntax-plugin.js.map +1 -1
  14. package/dist/call-expression-plugin.js +1 -0
  15. package/dist/call-expression-plugin.js.map +1 -1
  16. package/dist/claims-plugin.js +1 -0
  17. package/dist/claims-plugin.js.map +1 -1
  18. package/dist/client-script-plugin.js +1 -0
  19. package/dist/client-script-plugin.js.map +1 -1
  20. package/dist/column-plugin.js +1 -0
  21. package/dist/column-plugin.js.map +1 -1
  22. package/dist/cross-scope-privilege-plugin.js +1 -0
  23. package/dist/cross-scope-privilege-plugin.js.map +1 -1
  24. package/dist/dashboard/dashboard-plugin.js +2 -0
  25. package/dist/dashboard/dashboard-plugin.js.map +1 -1
  26. package/dist/data-plugin.js +1 -0
  27. package/dist/data-plugin.js.map +1 -1
  28. package/dist/email-notification-plugin.js +9 -13
  29. package/dist/email-notification-plugin.js.map +1 -1
  30. package/dist/flow/constants/flow-plugin-constants.d.ts +1 -1
  31. package/dist/flow/constants/flow-plugin-constants.js +1 -1
  32. package/dist/flow/constants/flow-plugin-constants.js.map +1 -1
  33. package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +82 -2
  34. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +48 -40
  35. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -1
  36. package/dist/flow/flow-logic/flow-logic-plugin.js +1 -0
  37. package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -1
  38. package/dist/flow/plugins/approval-rules-plugin.js +1 -0
  39. package/dist/flow/plugins/approval-rules-plugin.js.map +1 -1
  40. package/dist/flow/plugins/flow-action-definition-plugin.js +4 -2
  41. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
  42. package/dist/flow/plugins/flow-data-pill-plugin.js +1 -0
  43. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -1
  44. package/dist/flow/plugins/flow-definition-plugin.js +8 -3
  45. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
  46. package/dist/flow/plugins/flow-diagnostics-plugin.js +1 -0
  47. package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -1
  48. package/dist/flow/plugins/flow-instance-plugin.js +68 -12
  49. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
  50. package/dist/flow/plugins/flow-trigger-instance-plugin.js +1 -0
  51. package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -1
  52. package/dist/flow/plugins/inline-script-plugin.js +1 -0
  53. package/dist/flow/plugins/inline-script-plugin.js.map +1 -1
  54. package/dist/flow/plugins/step-definition-plugin.js +3 -2
  55. package/dist/flow/plugins/step-definition-plugin.js.map +1 -1
  56. package/dist/flow/plugins/step-instance-plugin.js +1 -0
  57. package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
  58. package/dist/flow/plugins/trigger-plugin.js +2 -0
  59. package/dist/flow/plugins/trigger-plugin.js.map +1 -1
  60. package/dist/flow/plugins/wfa-datapill-plugin.js +1 -0
  61. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -1
  62. package/dist/flow/post-install.d.ts +2 -0
  63. package/dist/flow/post-install.js +58 -0
  64. package/dist/flow/post-install.js.map +1 -0
  65. package/dist/flow/utils/complex-objects.js +4 -2
  66. package/dist/flow/utils/complex-objects.js.map +1 -1
  67. package/dist/flow/utils/flow-constants.d.ts +24 -0
  68. package/dist/flow/utils/flow-constants.js +29 -2
  69. package/dist/flow/utils/flow-constants.js.map +1 -1
  70. package/dist/flow/utils/flow-to-xml.d.ts +3 -2
  71. package/dist/flow/utils/flow-to-xml.js +3 -4
  72. package/dist/flow/utils/flow-to-xml.js.map +1 -1
  73. package/dist/flow/utils/label-cache-processor.d.ts +5 -0
  74. package/dist/flow/utils/label-cache-processor.js +14 -2
  75. package/dist/flow/utils/label-cache-processor.js.map +1 -1
  76. package/dist/flow/utils/service-catalog.js +5 -1
  77. package/dist/flow/utils/service-catalog.js.map +1 -1
  78. package/dist/form-plugin.d.ts +2 -0
  79. package/dist/form-plugin.js +1134 -0
  80. package/dist/form-plugin.js.map +1 -0
  81. package/dist/html-import-plugin.js +1 -0
  82. package/dist/html-import-plugin.js.map +1 -1
  83. package/dist/import-sets-plugin.js +2 -0
  84. package/dist/import-sets-plugin.js.map +1 -1
  85. package/dist/index.d.ts +9 -0
  86. package/dist/index.js +13 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/instance-scan-plugin.d.ts +2 -0
  89. package/dist/instance-scan-plugin.js +298 -0
  90. package/dist/instance-scan-plugin.js.map +1 -0
  91. package/dist/json-plugin.js +1 -0
  92. package/dist/json-plugin.js.map +1 -1
  93. package/dist/list-plugin.js +1 -0
  94. package/dist/list-plugin.js.map +1 -1
  95. package/dist/now-attach-plugin.js +1 -0
  96. package/dist/now-attach-plugin.js.map +1 -1
  97. package/dist/now-config-plugin.js +659 -51
  98. package/dist/now-config-plugin.js.map +1 -1
  99. package/dist/now-id-plugin.js +1 -0
  100. package/dist/now-id-plugin.js.map +1 -1
  101. package/dist/now-include-plugin.js +1 -0
  102. package/dist/now-include-plugin.js.map +1 -1
  103. package/dist/now-ref-plugin.js +1 -0
  104. package/dist/now-ref-plugin.js.map +1 -1
  105. package/dist/now-unresolved-plugin.js +1 -0
  106. package/dist/now-unresolved-plugin.js.map +1 -1
  107. package/dist/package-json-plugin.js +1 -0
  108. package/dist/package-json-plugin.js.map +1 -1
  109. package/dist/property-plugin.js +3 -1
  110. package/dist/property-plugin.js.map +1 -1
  111. package/dist/record-plugin.d.ts +30 -0
  112. package/dist/record-plugin.js +37 -1
  113. package/dist/record-plugin.js.map +1 -1
  114. package/dist/repack/lint/Rules.d.ts +11 -2
  115. package/dist/repack/lint/Rules.js +160 -16
  116. package/dist/repack/lint/Rules.js.map +1 -1
  117. package/dist/repack/lint/index.d.ts +10 -5
  118. package/dist/repack/lint/index.js +76 -50
  119. package/dist/repack/lint/index.js.map +1 -1
  120. package/dist/rest-api-plugin.js +14 -0
  121. package/dist/rest-api-plugin.js.map +1 -1
  122. package/dist/role-plugin.js +1 -0
  123. package/dist/role-plugin.js.map +1 -1
  124. package/dist/schedule-script/index.d.ts +1 -0
  125. package/dist/schedule-script/index.js +18 -0
  126. package/dist/schedule-script/index.js.map +1 -0
  127. package/dist/schedule-script/scheduled-script-plugin.d.ts +2 -0
  128. package/dist/schedule-script/scheduled-script-plugin.js +551 -0
  129. package/dist/schedule-script/scheduled-script-plugin.js.map +1 -0
  130. package/dist/schedule-script/timeZoneConverter.d.ts +61 -0
  131. package/dist/schedule-script/timeZoneConverter.js +170 -0
  132. package/dist/schedule-script/timeZoneConverter.js.map +1 -0
  133. package/dist/script-action-plugin.js +2 -0
  134. package/dist/script-action-plugin.js.map +1 -1
  135. package/dist/script-include-plugin.js +2 -0
  136. package/dist/script-include-plugin.js.map +1 -1
  137. package/dist/server-module-plugin/index.js +13 -2
  138. package/dist/server-module-plugin/index.js.map +1 -1
  139. package/dist/service-catalog/catalog-clientscript-plugin.js +2 -0
  140. package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -1
  141. package/dist/service-catalog/catalog-item-plugin.js +2 -0
  142. package/dist/service-catalog/catalog-item-plugin.js.map +1 -1
  143. package/dist/service-catalog/catalog-ui-policy-plugin.js +2 -0
  144. package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -1
  145. package/dist/service-catalog/sc-record-producer-plugin.js +2 -0
  146. package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -1
  147. package/dist/service-catalog/service-catalog-diagnostics.d.ts +6 -0
  148. package/dist/service-catalog/service-catalog-diagnostics.js +20 -0
  149. package/dist/service-catalog/service-catalog-diagnostics.js.map +1 -1
  150. package/dist/service-catalog/shape-to-record.js +7 -2
  151. package/dist/service-catalog/shape-to-record.js.map +1 -1
  152. package/dist/service-catalog/variable-set-plugin.js +2 -0
  153. package/dist/service-catalog/variable-set-plugin.js.map +1 -1
  154. package/dist/service-portal/angular-provider-plugin.js +2 -0
  155. package/dist/service-portal/angular-provider-plugin.js.map +1 -1
  156. package/dist/service-portal/dependency-plugin.js +5 -31
  157. package/dist/service-portal/dependency-plugin.js.map +1 -1
  158. package/dist/service-portal/menu-plugin.d.ts +2 -0
  159. package/dist/service-portal/menu-plugin.js +353 -0
  160. package/dist/service-portal/menu-plugin.js.map +1 -0
  161. package/dist/service-portal/page-plugin.d.ts +2 -0
  162. package/dist/service-portal/page-plugin.js +702 -0
  163. package/dist/service-portal/page-plugin.js.map +1 -0
  164. package/dist/service-portal/portal-plugin.d.ts +2 -0
  165. package/dist/service-portal/portal-plugin.js +296 -0
  166. package/dist/service-portal/portal-plugin.js.map +1 -0
  167. package/dist/service-portal/theme-plugin.d.ts +2 -0
  168. package/dist/service-portal/theme-plugin.js +112 -0
  169. package/dist/service-portal/theme-plugin.js.map +1 -0
  170. package/dist/service-portal/utils.d.ts +8 -0
  171. package/dist/service-portal/utils.js +50 -0
  172. package/dist/service-portal/utils.js.map +1 -0
  173. package/dist/service-portal/widget-plugin.js +45 -8
  174. package/dist/service-portal/widget-plugin.js.map +1 -1
  175. package/dist/sla-plugin.js +2 -0
  176. package/dist/sla-plugin.js.map +1 -1
  177. package/dist/static-content-plugin.js +1 -0
  178. package/dist/static-content-plugin.js.map +1 -1
  179. package/dist/table-plugin.js +1 -0
  180. package/dist/table-plugin.js.map +1 -1
  181. package/dist/ui-action-plugin.js +2 -0
  182. package/dist/ui-action-plugin.js.map +1 -1
  183. package/dist/ui-page-plugin.js +33 -8
  184. package/dist/ui-page-plugin.js.map +1 -1
  185. package/dist/ui-policy-plugin.js +1 -0
  186. package/dist/ui-policy-plugin.js.map +1 -1
  187. package/dist/user-preference-plugin.js +2 -0
  188. package/dist/user-preference-plugin.js.map +1 -1
  189. package/dist/utils.d.ts +20 -2
  190. package/dist/utils.js +34 -3
  191. package/dist/utils.js.map +1 -1
  192. package/dist/ux-list-menu-config-plugin.js +2 -0
  193. package/dist/ux-list-menu-config-plugin.js.map +1 -1
  194. package/dist/view-plugin.js +1 -0
  195. package/dist/view-plugin.js.map +1 -1
  196. package/dist/workspace-plugin.js +2 -0
  197. package/dist/workspace-plugin.js.map +1 -1
  198. package/package.json +10 -11
  199. package/src/_types/eslint-community-eslint-utils.d.ts +15 -0
  200. package/src/acl-plugin.ts +97 -8
  201. package/src/applicability-plugin.ts +2 -0
  202. package/src/application-menu-plugin.ts +2 -0
  203. package/src/arrow-function-plugin.ts +128 -13
  204. package/src/atf/test-plugin.ts +2 -0
  205. package/src/basic-syntax-plugin.ts +21 -0
  206. package/src/call-expression-plugin.ts +1 -0
  207. package/src/claims-plugin.ts +1 -0
  208. package/src/client-script-plugin.ts +2 -1
  209. package/src/column-plugin.ts +1 -0
  210. package/src/cross-scope-privilege-plugin.ts +2 -1
  211. package/src/dashboard/dashboard-plugin.ts +2 -0
  212. package/src/data-plugin.ts +1 -0
  213. package/src/email-notification-plugin.ts +3 -23
  214. package/src/flow/constants/flow-plugin-constants.ts +1 -1
  215. package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +47 -45
  216. package/src/flow/flow-logic/flow-logic-plugin.ts +1 -0
  217. package/src/flow/plugins/approval-rules-plugin.ts +1 -0
  218. package/src/flow/plugins/flow-action-definition-plugin.ts +4 -2
  219. package/src/flow/plugins/flow-data-pill-plugin.ts +1 -0
  220. package/src/flow/plugins/flow-definition-plugin.ts +10 -4
  221. package/src/flow/plugins/flow-diagnostics-plugin.ts +1 -0
  222. package/src/flow/plugins/flow-instance-plugin.ts +103 -14
  223. package/src/flow/plugins/flow-trigger-instance-plugin.ts +1 -0
  224. package/src/flow/plugins/inline-script-plugin.ts +1 -0
  225. package/src/flow/plugins/step-definition-plugin.ts +3 -2
  226. package/src/flow/plugins/step-instance-plugin.ts +1 -0
  227. package/src/flow/plugins/trigger-plugin.ts +2 -0
  228. package/src/flow/plugins/wfa-datapill-plugin.ts +1 -0
  229. package/src/flow/post-install.ts +92 -0
  230. package/src/flow/utils/complex-objects.ts +10 -2
  231. package/src/flow/utils/flow-constants.ts +30 -1
  232. package/src/flow/utils/flow-to-xml.ts +4 -4
  233. package/src/flow/utils/label-cache-processor.ts +14 -2
  234. package/src/flow/utils/service-catalog.ts +5 -2
  235. package/src/form-plugin.ts +1411 -0
  236. package/src/html-import-plugin.ts +1 -0
  237. package/src/import-sets-plugin.ts +2 -0
  238. package/src/index.ts +9 -0
  239. package/src/instance-scan-plugin.ts +318 -0
  240. package/src/json-plugin.ts +1 -0
  241. package/src/list-plugin.ts +2 -1
  242. package/src/now-attach-plugin.ts +1 -0
  243. package/src/now-config-plugin.ts +833 -53
  244. package/src/now-id-plugin.ts +1 -0
  245. package/src/now-include-plugin.ts +1 -0
  246. package/src/now-ref-plugin.ts +1 -0
  247. package/src/now-unresolved-plugin.ts +1 -0
  248. package/src/package-json-plugin.ts +1 -0
  249. package/src/property-plugin.ts +3 -1
  250. package/src/record-plugin.ts +42 -2
  251. package/src/repack/lint/Rules.ts +171 -22
  252. package/src/repack/lint/index.ts +80 -56
  253. package/src/rest-api-plugin.ts +21 -1
  254. package/src/role-plugin.ts +2 -1
  255. package/src/schedule-script/index.ts +1 -0
  256. package/src/schedule-script/scheduled-script-plugin.ts +679 -0
  257. package/src/schedule-script/timeZoneConverter.ts +188 -0
  258. package/src/script-action-plugin.ts +2 -0
  259. package/src/script-include-plugin.ts +2 -0
  260. package/src/server-module-plugin/index.ts +14 -2
  261. package/src/service-catalog/catalog-clientscript-plugin.ts +2 -0
  262. package/src/service-catalog/catalog-item-plugin.ts +2 -0
  263. package/src/service-catalog/catalog-ui-policy-plugin.ts +2 -0
  264. package/src/service-catalog/sc-record-producer-plugin.ts +2 -0
  265. package/src/service-catalog/service-catalog-diagnostics.ts +30 -0
  266. package/src/service-catalog/shape-to-record.ts +8 -2
  267. package/src/service-catalog/variable-set-plugin.ts +2 -0
  268. package/src/service-portal/angular-provider-plugin.ts +2 -0
  269. package/src/service-portal/dependency-plugin.ts +6 -53
  270. package/src/service-portal/menu-plugin.ts +435 -0
  271. package/src/service-portal/page-plugin.ts +830 -0
  272. package/src/service-portal/portal-plugin.ts +319 -0
  273. package/src/service-portal/theme-plugin.ts +135 -0
  274. package/src/service-portal/utils.ts +69 -0
  275. package/src/service-portal/widget-plugin.ts +79 -9
  276. package/src/sla-plugin.ts +2 -0
  277. package/src/static-content-plugin.ts +1 -0
  278. package/src/table-plugin.ts +2 -1
  279. package/src/ui-action-plugin.ts +2 -0
  280. package/src/ui-page-plugin.ts +34 -8
  281. package/src/ui-policy-plugin.ts +2 -1
  282. package/src/user-preference-plugin.ts +2 -0
  283. package/src/utils.ts +42 -2
  284. package/src/ux-list-menu-config-plugin.ts +2 -0
  285. package/src/view-plugin.ts +1 -0
  286. package/src/workspace-plugin.ts +2 -0
  287. package/src/_types/eslint-plugin-es-x.d.ts +0 -17
@@ -1,11 +1,455 @@
1
- import { NowConfig, ObjectShape, MODULE_RESOLUTION, Plugin, Shape } from '@servicenow/sdk-build-core'
1
+ import {
2
+ NowConfig,
3
+ ObjectShape,
4
+ MODULE_RESOLUTION,
5
+ Plugin,
6
+ Shape,
7
+ type Record as SdkRecord,
8
+ type Factory,
9
+ type Diagnostics,
10
+ } from '@servicenow/sdk-build-core'
2
11
 
3
12
  import { JsonFileShape } from './json-plugin'
4
13
 
5
14
  export class NowConfigShape extends ObjectShape {}
6
15
 
16
+ // ============================================================================
17
+ // Application Runtime Policy Helper Functions
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Maps Fluent mode values to ServiceNow XML state values for performance policy
22
+ */
23
+ const PERFORMANCE_MODE_MAP = {
24
+ disabled: 'disabled',
25
+ enforced: 'enforced',
26
+ logOnly: 'log_only',
27
+ } as const satisfies Record<string, string>
28
+
29
+ /**
30
+ * Reverse mapping: ServiceNow XML state values to Fluent mode values
31
+ */
32
+ const PERFORMANCE_MODE_REVERSE_MAP = {
33
+ disabled: 'disabled',
34
+ enforced: 'enforced',
35
+ log_only: 'logOnly',
36
+ } as const satisfies Record<string, string>
37
+
38
+ /**
39
+ * Maps applicationRuntimePolicy to performance policy mode
40
+ */
41
+ const ARP_TO_PERFORMANCE_POLICY_MODE_MAP = {
42
+ none: 'disabled',
43
+ tracking: 'log_only',
44
+ enforcing: 'enforced',
45
+ } as const satisfies Record<string, string>
46
+
47
+ /**
48
+ * Maps Application Runtime Policy values to required Runtime Access Tracking values
49
+ * - none: RAT can be anything (no restriction)
50
+ * - tracking: RAT must be permissive
51
+ * - enforcing: RAT must be enforcing
52
+ */
53
+ const ARP_TO_RAT_MAP = {
54
+ none: null, // No restriction - RAT can be any value
55
+ tracking: 'permissive',
56
+ enforcing: 'enforcing',
57
+ } as const satisfies Record<string, string | null>
58
+
59
+ // ============================================================================
60
+ // Network Policy Validation Functions
61
+ // ============================================================================
62
+
63
+ /**
64
+ * Validates host field format and content
65
+ * Uses URL API for parsing with manual wildcard validation (URL can't handle wildcards)
66
+ * Matches platform business rule validation logic
67
+ */
68
+ function validateHost(host: string | undefined, policyType: string, diagnostics: Diagnostics, source: Shape): boolean {
69
+ if (!host || !host.trim()) {
70
+ return true
71
+ }
72
+
73
+ const hostValue = host.trim()
74
+
75
+ // Use URL API for scheme/port parsing — replace wildcards with placeholder since URL can't handle them
76
+ const parseableUrl = hostValue.replace(/\*/g, 'wildcard-placeholder')
77
+ let url: URL
78
+ try {
79
+ url = new URL(parseableUrl)
80
+ } catch {
81
+ // URL throws for invalid ports (>65535) among other reasons
82
+ // Check if the host has an out-of-range port and diagnose it
83
+ const portMatch = hostValue.match(/:(\d+)\s*$/)
84
+ if (portMatch) {
85
+ const port = parseInt(portMatch[1]!, 10)
86
+ if (isNaN(port) || port < 1 || port > 65535) {
87
+ diagnostics.error(source, `Invalid port "${portMatch[1]}". Must be 1-65535.`)
88
+ return false
89
+ }
90
+ }
91
+ // Zod regex handles basic format validation; if URL can't parse for other reasons, skip
92
+ return true
93
+ }
94
+
95
+ const scheme = url.protocol.slice(0, -1) // Remove trailing ':'
96
+
97
+ // Validate scheme based on policy type
98
+ // Platform uses system properties to configure allowed schemes,
99
+ // but we use hardcoded defaults here for build-time validation
100
+ const allowedSchemes = getValidSchemes(policyType)
101
+ if (!allowedSchemes.includes(scheme)) {
102
+ diagnostics.error(
103
+ source,
104
+ `Invalid URL scheme "${scheme}" for policy type "${policyType}". Allowed schemes: ${allowedSchemes.join(', ')}`
105
+ )
106
+ return false
107
+ }
108
+
109
+ // Validate wildcards on original hostname (not the placeholder)
110
+ const originalHostname = url.hostname.replace(/wildcard-placeholder/g, '*')
111
+ if (!validateWildcards(originalHostname, diagnostics, source)) {
112
+ return false
113
+ }
114
+
115
+ // Validate port range using URL's parsed port
116
+ if (url.port) {
117
+ const port = parseInt(url.port, 10)
118
+ if (port < 1 || port > 65535) {
119
+ diagnostics.error(source, `Invalid port "${url.port}". Must be 1-65535.`)
120
+ return false
121
+ }
122
+ }
123
+
124
+ return true
125
+ }
126
+
127
+ /**
128
+ * Gets valid URL schemes based on policy type
129
+ * Matches platform business rule defaults for glide.arp.csp.allowed.url.schemes
130
+ * and glide.arp.server.outbound.allowed.url.schemes
131
+ */
132
+ function getValidSchemes(policyType: string): string[] {
133
+ // CSP script-src only allows HTTPS
134
+ if (policyType === 'csp_script_src') {
135
+ return ['https']
136
+ }
137
+ // CSP connect-src allows HTTPS and WSS
138
+ if (policyType === 'csp_connect_src') {
139
+ return ['https', 'wss']
140
+ }
141
+ // Server outbound allows all four protocols
142
+ if (policyType === 'now_outbound') {
143
+ return ['http', 'https', 'ws', 'wss']
144
+ }
145
+ // For inbound policies, host should be empty, but if provided, use https
146
+ return ['https']
147
+ }
148
+
149
+ /**
150
+ * Validates wildcard patterns in hostname
151
+ */
152
+ function validateWildcards(hostPart: string, diagnostics: Diagnostics, source: Shape): boolean {
153
+ if (hostPart === '*') {
154
+ diagnostics.error(source, 'Single wildcard "*" not allowed. Use "*.example.com" format.')
155
+ return false
156
+ }
157
+
158
+ if (hostPart.indexOf('*') === -1) {
159
+ return true
160
+ }
161
+
162
+ const parts = hostPart.split('.')
163
+ let wildcardCount = 0
164
+
165
+ for (let i = 0; i < parts.length; i++) {
166
+ const part = parts[i]!
167
+ if (part === '*') {
168
+ wildcardCount++
169
+ if (i !== 0 || wildcardCount > 1) {
170
+ diagnostics.error(source, `Wildcard must be first segment only. Invalid: "${hostPart}"`)
171
+ return false
172
+ }
173
+ } else if (part.indexOf('*') !== -1) {
174
+ diagnostics.error(source, `Partial wildcards not allowed: "${part}"`)
175
+ return false
176
+ }
177
+ }
178
+
179
+ return true
180
+ }
181
+
182
+ /**
183
+ * Validates path field format
184
+ * Each path in the array must start with single forward slash
185
+ */
186
+ function validatePath(path: string[] | undefined, diagnostics: Diagnostics, source: Shape): boolean {
187
+ if (!path || path.length === 0) {
188
+ return true
189
+ }
190
+
191
+ for (const singlePath of path) {
192
+ const trimmedPath = singlePath.trim()
193
+ if (!trimmedPath.startsWith('/') || trimmedPath.startsWith('//')) {
194
+ diagnostics.error(source, `Path must start with single "/": "${trimmedPath}"`)
195
+ return false
196
+ }
197
+ }
198
+
199
+ return true
200
+ }
201
+
202
+ /**
203
+ * Validates network policy fields for cross-field and business rules
204
+ */
205
+ function validateNetworkPolicy(
206
+ policy: { policyType?: unknown; host?: unknown; path?: unknown },
207
+ diagnostics: Diagnostics,
208
+ source: Shape
209
+ ): boolean {
210
+ let isValid = true
211
+
212
+ const policyType = typeof policy.policyType === 'string' ? policy.policyType : ''
213
+ const host = typeof policy.host === 'string' ? policy.host : undefined
214
+ const path = Array.isArray(policy.path) ? policy.path : undefined
215
+
216
+ if (host && !validateHost(host, policyType, diagnostics, source)) {
217
+ isValid = false
218
+ }
219
+
220
+ if (path && !validatePath(path, diagnostics, source)) {
221
+ isValid = false
222
+ }
223
+
224
+ return isValid
225
+ }
226
+
227
+ /**
228
+ * Creates network policy records from config
229
+ */
230
+ async function createNetworkPolicyRecords(
231
+ networkPolicies: Shape[],
232
+ factory: Factory,
233
+ source: Shape,
234
+ diagnostics: Diagnostics
235
+ ): Promise<SdkRecord[]> {
236
+ const records: SdkRecord[] = []
237
+
238
+ for (const policy of networkPolicies) {
239
+ const policyObj = policy.asObject()
240
+
241
+ // Validate policy fields (complex validations that can't be done in schema)
242
+ const policyData = {
243
+ policyType: policyObj.get('policyType')?.getValue(),
244
+ host: policyObj.get('host')?.getValue(),
245
+ path: policyObj.get('path')?.getValue(),
246
+ }
247
+ validateNetworkPolicy(policyData, diagnostics, policy)
248
+
249
+ const record = await factory.createRecord({
250
+ source: source,
251
+ table: 'sys_arp_network_policy',
252
+ explicitId: policyObj.get('$id'),
253
+ properties: policyObj.transform(({ $ }) => ({
254
+ active: $.def(true),
255
+ policy_type: $.from('policyType'),
256
+ status: $,
257
+ host: $,
258
+ scheme: $,
259
+ // Join path array with newlines for XML storage
260
+ path: $.from('path').map(
261
+ (pathShape) =>
262
+ pathShape
263
+ .ifArray()
264
+ ?.getElements()
265
+ .map((e: Shape) => e.getValue())
266
+ .join('\n') ?? ''
267
+ ),
268
+ resource: $,
269
+ short_description: $.from('shortDescription'),
270
+ })),
271
+ })
272
+
273
+ records.push(record)
274
+ }
275
+
276
+ return records
277
+ }
278
+
279
+ /**
280
+ * Creates wildcard policy record from config
281
+ */
282
+ async function createWildcardPolicyRecord(
283
+ wildcardPolicy: ObjectShape,
284
+ factory: Factory,
285
+ source: Shape,
286
+ scopeId: string,
287
+ diagnostics: Diagnostics
288
+ ): Promise<SdkRecord> {
289
+ // Extract nested pillar objects
290
+ const networkPillar = wildcardPolicy.get('network')?.ifDefined()?.asObject()
291
+ const scriptingPillar = wildcardPolicy.get('scripting')?.ifDefined()?.asObject()
292
+ const arlPillar = wildcardPolicy.get('arl')?.ifDefined()?.asObject()
293
+
294
+ // Validate pillar active/wildcard consistency
295
+ const networkActive = networkPillar?.get('active')?.toBoolean().getValue() ?? false
296
+ const networkWildcardShape = networkPillar?.get('networkWildcard')?.ifArray()
297
+ const networkWildcard = networkWildcardShape?.getElements()
298
+ if (!networkActive && networkWildcard && networkWildcard.length > 0) {
299
+ diagnostics.warn(networkWildcardShape!, 'networkWildcard values will be ignored when network.active is false')
300
+ }
301
+ if (networkActive && (!networkWildcard || networkWildcard.length === 0)) {
302
+ diagnostics.hint(
303
+ networkPillar!.get('active'),
304
+ 'Network pillar is active but has no networkWildcard selections. Consider adding networkWildcard values or setting active to false.'
305
+ )
306
+ }
307
+
308
+ const scriptingActive = scriptingPillar?.get('active')?.toBoolean().getValue() ?? false
309
+ const scriptingWildcardShape = scriptingPillar?.get('scriptingWildcard')?.ifArray()
310
+ const scriptingWildcard = scriptingWildcardShape?.getElements()
311
+ if (!scriptingActive && scriptingWildcard && scriptingWildcard.length > 0) {
312
+ diagnostics.warn(
313
+ scriptingWildcardShape!,
314
+ 'scriptingWildcard values will be ignored when scripting.active is false'
315
+ )
316
+ }
317
+ if (scriptingActive && (!scriptingWildcard || scriptingWildcard.length === 0)) {
318
+ diagnostics.hint(
319
+ scriptingPillar!.get('active'),
320
+ 'Scripting pillar is active but has no scriptingWildcard selections. Consider adding scriptingWildcard values or setting active to false.'
321
+ )
322
+ }
323
+
324
+ const arlActive = arlPillar?.get('active')?.toBoolean().getValue() ?? false
325
+ const arlWildcardShape = arlPillar?.get('arlWildcard')?.ifArray()
326
+ const arlWildcard = arlWildcardShape?.getElements()
327
+ if (!arlActive && arlWildcard && arlWildcard.length > 0) {
328
+ diagnostics.warn(arlWildcardShape!, 'arlWildcard values will be ignored when arl.active is false')
329
+ }
330
+ if (arlActive && (!arlWildcard || arlWildcard.length === 0)) {
331
+ diagnostics.hint(
332
+ arlPillar!.get('active'),
333
+ 'ARL pillar is active but has no arlWildcard selections. Consider adding arlWildcard values or setting active to false.'
334
+ )
335
+ }
336
+
337
+ return await factory.createRecord({
338
+ source: source,
339
+ table: 'sys_arp_segment_policy',
340
+ explicitId: wildcardPolicy.get('$id'),
341
+ properties: wildcardPolicy.transform(({ $ }) => ({
342
+ sys_scope: $.val(scopeId),
343
+ active: $.def(false),
344
+ short_description: $.from('shortDescription').def(''),
345
+ arp_record: $.from('record').toBoolean().def(false),
346
+ // Network pillar
347
+ arp_network: $.val(networkPillar?.get('active')?.toBoolean().getValue() ?? false),
348
+ arp_network_wildcard: $.val(
349
+ networkPillar
350
+ ?.get('networkWildcard')
351
+ ?.ifArray()
352
+ ?.getElements()
353
+ .map((e) => e.getValue())
354
+ .join(',') ?? ''
355
+ ),
356
+ // Scripting pillar
357
+ arp_script: $.val(scriptingPillar?.get('active')?.toBoolean().getValue() ?? false),
358
+ arp_script_wildcard: $.val(
359
+ scriptingPillar
360
+ ?.get('scriptingWildcard')
361
+ ?.ifArray()
362
+ ?.getElements()
363
+ .map((e) => e.getValue())
364
+ .join(',') ?? ''
365
+ ),
366
+ // ARL pillar
367
+ arp_arl: $.val(arlPillar?.get('active')?.toBoolean().getValue() ?? false),
368
+ arp_arl_wildcard: $.val(
369
+ arlPillar
370
+ ?.get('arlWildcard')
371
+ ?.ifArray()
372
+ ?.getElements()
373
+ .map((e) => e.getValue())
374
+ .join(',') ?? ''
375
+ ),
376
+ })),
377
+ })
378
+ }
379
+
380
+ /**
381
+ * Creates performance policy record from config
382
+ * Mode is auto-derived from applicationRuntimePolicy if not explicitly set
383
+ */
384
+ async function createPerformancePolicyRecord(
385
+ performancePolicy: ObjectShape,
386
+ factory: Factory,
387
+ source: Shape,
388
+ scopeId: string,
389
+ applicationRuntimePolicy: string,
390
+ diagnostics: Diagnostics
391
+ ): Promise<SdkRecord> {
392
+ // Derive state from applicationRuntimePolicy
393
+ const derivedMode =
394
+ ARP_TO_PERFORMANCE_POLICY_MODE_MAP[
395
+ applicationRuntimePolicy as keyof typeof ARP_TO_PERFORMANCE_POLICY_MODE_MAP
396
+ ] ?? 'log_only'
397
+
398
+ // Check if user explicitly set mode and it differs from auto-derived value
399
+ const explicitModeShape = performancePolicy.get('mode').ifDefined()
400
+ if (explicitModeShape) {
401
+ const explicitMode = explicitModeShape.toString().getValue()
402
+ const explicitModeInXml = PERFORMANCE_MODE_MAP[explicitMode as keyof typeof PERFORMANCE_MODE_MAP]
403
+ if (explicitModeInXml && explicitModeInXml !== derivedMode) {
404
+ const derivedModeInFluent = Object.entries(PERFORMANCE_MODE_MAP).find(
405
+ ([_, xml]) => xml === derivedMode
406
+ )?.[0]
407
+ diagnostics.warn(
408
+ explicitModeShape,
409
+ `Performance policy mode '${explicitMode}' differs from auto-derived mode '${derivedModeInFluent}' based on applicationRuntimePolicy='${applicationRuntimePolicy}'. The explicit mode will be used.`
410
+ )
411
+ }
412
+ }
413
+
414
+ return await factory.createRecord({
415
+ source: source,
416
+ table: 'sys_app_resource_limit_template',
417
+ explicitId: performancePolicy.get('$id'),
418
+ properties: performancePolicy.transform(({ $ }) => ({
419
+ template_name: $.from('name'),
420
+ // Auto-derive appCondition to query current scope
421
+ app_condition: $.val(`sys_id=${scopeId}`),
422
+ scheduled_job_limit: $.from('scheduledJobLimit').def(20),
423
+ event_handler_limit: $.from('eventHandlerLimit').def(20),
424
+ api_transaction_limit: $.from('apiTransactionLimit').def(30),
425
+ interactive_transaction_limit: $.from('interactiveTransactionLimit').def(30),
426
+ // State is auto-derived from applicationRuntimePolicy unless explicitly overridden
427
+ state: $.from('mode')
428
+ .map((v) => {
429
+ const explicitMode = v.getValue() as string
430
+ const mappedMode = PERFORMANCE_MODE_MAP[explicitMode as keyof typeof PERFORMANCE_MODE_MAP]
431
+ if (explicitMode && !mappedMode) {
432
+ // Invalid mode value - should have been caught by schema validation
433
+ // but add a diagnostic warning just in case
434
+ diagnostics.warn(
435
+ v,
436
+ `Invalid mode value '${explicitMode}'. Valid values are: disabled, enforced, logOnly. Using auto-derived mode instead.`
437
+ )
438
+ }
439
+ return mappedMode ?? derivedMode
440
+ })
441
+ .def(derivedMode),
442
+ enable_auto_gen_limits: $.val(true), // Always enabled, hidden from user
443
+ order: $.val(100), // Default order, hidden from user
444
+ })),
445
+ })
446
+ }
447
+
448
+ // ============================================================================
449
+
7
450
  export const NowConfigPlugin = Plugin.create({
8
451
  name: 'NowConfigPlugin',
452
+ docs: [],
9
453
  files: [
10
454
  {
11
455
  matcher: /\Wnow\.config\.json$/,
@@ -14,15 +458,243 @@ export const NowConfigPlugin = Plugin.create({
14
458
  ],
15
459
  records: {
16
460
  sys_app: {
17
- toShape(record, { packageJson, config }) {
461
+ relationships: {
462
+ sys_arp_network_policy: {
463
+ via: 'sys_scope',
464
+ descendant: true,
465
+ },
466
+ sys_arp_segment_policy: {
467
+ via: 'sys_scope',
468
+ descendant: true,
469
+ },
470
+ sys_app_resource_limit_template: {
471
+ via: 'sys_scope',
472
+ descendant: true,
473
+ },
474
+ },
475
+ toShape(record, { packageJson, config, database }) {
18
476
  const { name: packageName } = packageJson
477
+
478
+ // Query policy records directly from database
479
+ // During transform, the database only contains records from the current scope's XML
480
+ const networkPolicyRecords = database.query('sys_arp_network_policy')
481
+ const segmentPolicyRecords = database.query('sys_arp_segment_policy')
482
+ const resourceLimitRecords = database.query('sys_app_resource_limit_template')
483
+
484
+ // Transform network policies (exclude default values)
485
+ const networkPolicies = networkPolicyRecords.map((policyRecord) => {
486
+ const policyType = policyRecord.get('policy_type').toString().getValue()
487
+ const result: Record<string, unknown> = {
488
+ $id: policyRecord.getId().getValue(),
489
+ policyType: policyType,
490
+ }
491
+
492
+ // Only include active if not default (true)
493
+ const active = policyRecord.get('active').toBoolean().getValue()
494
+ if (active !== true) {
495
+ result['active'] = active
496
+ }
497
+
498
+ // Status is required - always include it (default to 'requested' if missing for backwards compatibility)
499
+ const status = policyRecord.get('status').toString().getValue() || 'requested'
500
+ result['status'] = status
501
+
502
+ const host = policyRecord.get('host').ifDefined()?.toString().getValue()
503
+ if (host) {
504
+ result['host'] = host
505
+ }
506
+ const scheme = policyRecord.get('scheme').ifDefined()?.toString().getValue()
507
+ if (scheme) {
508
+ result['scheme'] = scheme
509
+ }
510
+ const pathVal = policyRecord.get('path').ifDefined()?.toString().getValue()
511
+ if (pathVal) {
512
+ // Split path string by newlines and clean each path
513
+ // ServiceNow stores multi-line fields with CRLF (\r\n) which get XML-encoded as \n&#13;
514
+ // The fast-xml-parser doesn't decode numeric character references, so we strip them manually
515
+ result['path'] = pathVal
516
+ .split('\n')
517
+ .map((p) => p.replace(/&#13;/g, '').trim())
518
+ .filter((p) => p)
519
+ }
520
+ const resource = policyRecord.get('resource').ifDefined()?.toString().getValue()
521
+ if (resource) {
522
+ result['resource'] = resource
523
+ }
524
+ const shortDesc = policyRecord.get('short_description').ifDefined()?.toString().getValue()
525
+ if (shortDesc) {
526
+ result['shortDescription'] = shortDesc
527
+ }
528
+ return result
529
+ })
530
+
531
+ // Transform wildcard/segment policy (only one per scope, exclude default values)
532
+ let wildcardPolicy: Record<string, unknown> | undefined
533
+ const segmentRecord = segmentPolicyRecords[0]
534
+ if (segmentRecord) {
535
+ wildcardPolicy = {
536
+ $id: segmentRecord.getId().getValue(),
537
+ }
538
+
539
+ // Only include active if not default (false)
540
+ const active = segmentRecord.get('active').toBoolean().getValue()
541
+ if (active !== false) {
542
+ wildcardPolicy['active'] = active
543
+ }
544
+
545
+ const shortDesc = segmentRecord.get('short_description').ifDefined()?.toString().getValue()
546
+ if (shortDesc) {
547
+ wildcardPolicy['shortDescription'] = shortDesc
548
+ }
549
+
550
+ // Network pillar
551
+ const networkActive = segmentRecord.get('arp_network').ifDefined()?.toBoolean().getValue()
552
+ const networkWildcardStr = segmentRecord
553
+ .get('arp_network_wildcard')
554
+ .ifDefined()
555
+ ?.toString()
556
+ .getValue()
557
+ const networkWildcardArr = networkWildcardStr
558
+ ? networkWildcardStr.split(',').filter((s: string) => s.trim())
559
+ : []
560
+ if (networkActive !== undefined || networkWildcardArr.length > 0) {
561
+ const networkPillar: Record<string, unknown> = {}
562
+ // Only include active if not default (false)
563
+ if (networkActive !== false) {
564
+ networkPillar['active'] = networkActive ?? false
565
+ }
566
+ if (networkWildcardArr.length > 0) {
567
+ networkPillar['networkWildcard'] = networkWildcardArr
568
+ }
569
+ if (Object.keys(networkPillar).length > 0) {
570
+ wildcardPolicy['network'] = networkPillar
571
+ }
572
+ }
573
+
574
+ // Scripting pillar
575
+ const scriptingActive = segmentRecord.get('arp_script').ifDefined()?.toBoolean().getValue()
576
+ const scriptWildcardStr = segmentRecord
577
+ .get('arp_script_wildcard')
578
+ .ifDefined()
579
+ ?.toString()
580
+ .getValue()
581
+ const scriptWildcardArr = scriptWildcardStr
582
+ ? scriptWildcardStr.split(',').filter((s: string) => s.trim())
583
+ : []
584
+ if (scriptingActive !== undefined || scriptWildcardArr.length > 0) {
585
+ const scriptingPillar: Record<string, unknown> = {}
586
+ // Only include active if not default (false)
587
+ if (scriptingActive !== false) {
588
+ scriptingPillar['active'] = scriptingActive ?? false
589
+ }
590
+ if (scriptWildcardArr.length > 0) {
591
+ scriptingPillar['scriptingWildcard'] = scriptWildcardArr
592
+ }
593
+ if (Object.keys(scriptingPillar).length > 0) {
594
+ wildcardPolicy['scripting'] = scriptingPillar
595
+ }
596
+ }
597
+
598
+ // ARL pillar
599
+ const arlActive = segmentRecord.get('arp_arl').ifDefined()?.toBoolean().getValue()
600
+ const arlWildcardStr = segmentRecord.get('arp_arl_wildcard').ifDefined()?.toString().getValue()
601
+ const arlWildcardArr = arlWildcardStr
602
+ ? arlWildcardStr.split(',').filter((s: string) => s.trim())
603
+ : []
604
+ if (arlActive !== undefined || arlWildcardArr.length > 0) {
605
+ const arlPillar: Record<string, unknown> = {}
606
+ // Only include active if not default (false)
607
+ if (arlActive !== false) {
608
+ arlPillar['active'] = arlActive ?? false
609
+ }
610
+ if (arlWildcardArr.length > 0) {
611
+ arlPillar['arlWildcard'] = arlWildcardArr
612
+ }
613
+ if (Object.keys(arlPillar).length > 0) {
614
+ wildcardPolicy['arl'] = arlPillar
615
+ }
616
+ }
617
+
618
+ // Record flag - only include if not default (false)
619
+ const arpRecord = segmentRecord.get('arp_record').ifDefined()?.toBoolean().getValue()
620
+ if (arpRecord !== undefined && arpRecord !== false) {
621
+ wildcardPolicy['record'] = arpRecord
622
+ }
623
+
624
+ // If wildcardPolicy is empty, set to undefined
625
+ if (Object.keys(wildcardPolicy).length === 0) {
626
+ wildcardPolicy = undefined
627
+ }
628
+ }
629
+
630
+ // Transform performance/resource limit policy (only one per scope, exclude default values)
631
+ let performancePolicy: Record<string, unknown> | undefined
632
+ const limitRecord = resourceLimitRecords[0]
633
+ if (limitRecord) {
634
+ performancePolicy = {
635
+ $id: limitRecord.getId().getValue(),
636
+ name: limitRecord.get('template_name').toString().getValue(),
637
+ }
638
+
639
+ // Only include quota limits if not default values
640
+ const scheduledJobLimit = limitRecord.get('scheduled_job_limit').ifDefined()?.toNumber().getValue()
641
+ if (scheduledJobLimit !== undefined && scheduledJobLimit !== 20) {
642
+ performancePolicy['scheduledJobLimit'] = scheduledJobLimit
643
+ }
644
+ const eventHandlerLimit = limitRecord.get('event_handler_limit').ifDefined()?.toNumber().getValue()
645
+ if (eventHandlerLimit !== undefined && eventHandlerLimit !== 20) {
646
+ performancePolicy['eventHandlerLimit'] = eventHandlerLimit
647
+ }
648
+ const apiTransactionLimit = limitRecord
649
+ .get('api_transaction_limit')
650
+ .ifDefined()
651
+ ?.toNumber()
652
+ .getValue()
653
+ if (apiTransactionLimit !== undefined && apiTransactionLimit !== 30) {
654
+ performancePolicy['apiTransactionLimit'] = apiTransactionLimit
655
+ }
656
+ const interactiveTransactionLimit = limitRecord
657
+ .get('interactive_transaction_limit')
658
+ .ifDefined()
659
+ ?.toNumber()
660
+ .getValue()
661
+ if (interactiveTransactionLimit !== undefined && interactiveTransactionLimit !== 30) {
662
+ performancePolicy['interactiveTransactionLimit'] = interactiveTransactionLimit
663
+ }
664
+
665
+ // Only include mode if it differs from auto-derived value based on applicationRuntimePolicy
666
+ // This ensures round-trip consistency with toRecord behavior
667
+ const arpValue =
668
+ record.get('application_runtime_policy').ifDefined()?.toString().getValue() || 'none'
669
+ const autoDerivedMode =
670
+ ARP_TO_PERFORMANCE_POLICY_MODE_MAP[
671
+ arpValue as keyof typeof ARP_TO_PERFORMANCE_POLICY_MODE_MAP
672
+ ] ?? 'log_only'
673
+ const state = limitRecord.get('state').ifDefined()?.toString().getValue()
674
+ if (state && state !== autoDerivedMode) {
675
+ const mode =
676
+ PERFORMANCE_MODE_REVERSE_MAP[state as keyof typeof PERFORMANCE_MODE_REVERSE_MAP] ?? state
677
+ performancePolicy['mode'] = mode
678
+ }
679
+ }
680
+
19
681
  return {
20
682
  success: true,
21
683
  value: new NowConfigShape({
22
684
  source: record,
23
685
  properties: record.transform(({ $ }) => ({
24
- name: $.toString().def(packageName),
686
+ // Required fields
687
+ scope: $.from('scope').toString(),
688
+ scopeId: $.val(record.getId().getValue()),
689
+ name: $.from('name').toString().def(packageName),
25
690
  active: $.toBoolean().def(true),
691
+ // Include ARP configuration
692
+ applicationRuntimePolicy: $.from('application_runtime_policy').def('none'),
693
+ // Policy records are always included in config regardless of ARP mode
694
+ // The platform will honor or ignore them based on applicationRuntimePolicy value
695
+ ...(networkPolicies.length > 0 ? { networkPolicies: $.val(networkPolicies) } : {}),
696
+ ...(wildcardPolicy ? { wildcardPolicy: $.val(wildcardPolicy) } : {}),
697
+ ...(performancePolicy ? { performancePolicy: $.val(performancePolicy) } : {}),
26
698
  accessControls: $.from(
27
699
  'scoped_administration',
28
700
  'restrict_table_access',
@@ -121,6 +793,28 @@ export const NowConfigPlugin = Plugin.create({
121
793
  }
122
794
  },
123
795
  },
796
+
797
+ // Policy table configurations for transform (XML to Fluent)
798
+ // These records are merged into now.config.json via sys_app.toShape descendants query
799
+ // toNoOpShape prevents RecordPlugin from creating separate files for these descendants
800
+ sys_arp_network_policy: {
801
+ coalesce: ['policy_type', 'host', 'scheme', 'path', 'resource'],
802
+ toShape(record) {
803
+ return { success: true, value: Shape.noOp(record) }
804
+ },
805
+ },
806
+ sys_arp_segment_policy: {
807
+ coalesce: ['sys_scope'],
808
+ toShape(record) {
809
+ return { success: true, value: Shape.noOp(record) }
810
+ },
811
+ },
812
+ sys_app_resource_limit_template: {
813
+ coalesce: ['template_name'],
814
+ toShape(record) {
815
+ return { success: true, value: Shape.noOp(record) }
816
+ },
817
+ },
124
818
  },
125
819
  shapes: [
126
820
  {
@@ -150,58 +844,144 @@ export const NowConfigPlugin = Plugin.create({
150
844
  const accessControls = config.get('accessControls').ifDefined()?.asObject()
151
845
  const licensing = config.get('licensing').ifDefined()?.asObject()
152
846
 
847
+ // Application Runtime Policy configuration
848
+ const applicationRuntimePolicy = config.get('applicationRuntimePolicy').ifString()?.getValue() ?? 'none'
849
+ const networkPoliciesShape = config.get('networkPolicies')?.ifArray()?.getElements() ?? []
850
+ const wildcardPolicyShape = config.get('wildcardPolicy')?.ifDefined()?.asObject()
851
+ const performancePolicyShape = config.get('performancePolicy')?.ifDefined()?.asObject()
852
+
853
+ const hasPolicyDefinitions =
854
+ networkPoliciesShape.length > 0 || wildcardPolicyShape || performancePolicyShape
855
+
856
+ // Warn if ARP is 'none' but policies are defined
857
+ if (applicationRuntimePolicy === 'none' && hasPolicyDefinitions) {
858
+ diagnostics.warn(
859
+ config,
860
+ `Application Runtime Policy is set to 'none'. Policy records will be created but the ServiceNow platform will not enforce them. Set applicationRuntimePolicy to 'tracking' or 'enforcing' to enable policy enforcement.`
861
+ )
862
+ }
863
+
864
+ // Create sys_app record
865
+ const sysAppRecord = await factory.createRecord({
866
+ source: config,
867
+ table: 'sys_app',
868
+ properties: config.transform(({ $ }) => ({
869
+ active: $.toBoolean().def(true),
870
+ // Application Runtime Policy field - only include if not 'none' (default)
871
+ ...(applicationRuntimePolicy && applicationRuntimePolicy !== 'none'
872
+ ? { application_runtime_policy: $.val(applicationRuntimePolicy) }
873
+ : {}),
874
+ scoped_administration: $.val(accessControls?.get('scopedAdministration'))
875
+ .toBoolean()
876
+ .def(false),
877
+ can_edit_in_studio: $.val(accessControls?.get('canEditInStudio')).toBoolean().def(true),
878
+ js_level: $.from('jsLevel').toString().def('es_latest'),
879
+ restrict_table_access: $.val(accessControls?.get('restrictTableAccess')).toBoolean().def(false),
880
+ runtime_access_tracking: $.map(() => {
881
+ const ratShape = accessControls?.get('runtimeAccessTracking')
882
+ const userProvidedRat = ratShape?.getValue() as string | undefined
883
+
884
+ // ARP may require a specific RAT value
885
+ const requiredRat = ARP_TO_RAT_MAP[applicationRuntimePolicy as keyof typeof ARP_TO_RAT_MAP]
886
+
887
+ // Error only when user *explicitly* set a conflicting value — not when it was omitted
888
+ if (userProvidedRat && requiredRat && userProvidedRat !== requiredRat) {
889
+ diagnostics.error(
890
+ ratShape || config.get('accessControls'),
891
+ `Runtime Access Tracking is set to '${userProvidedRat}' but Application Runtime Policy '${applicationRuntimePolicy}' requires '${requiredRat}'. ` +
892
+ `Set runtimeAccessTracking to '${requiredRat}' to match the Application Runtime Policy.`
893
+ )
894
+ }
895
+
896
+ // Hint when ARP auto-derives RAT and user also set it explicitly (even if not conflicting)
897
+ if (userProvidedRat && requiredRat && userProvidedRat === requiredRat) {
898
+ diagnostics.hint(
899
+ ratShape || config.get('accessControls'),
900
+ `runtimeAccessTracking is auto-derived from applicationRuntimePolicy='${applicationRuntimePolicy}'. ` +
901
+ `The explicit value '${userProvidedRat}' can be removed.`
902
+ )
903
+ }
904
+
905
+ // Auto-derive from ARP when it specifies a required value; otherwise fall back to
906
+ // the user-provided value or the platform default of 'permissive'.
907
+ const rat = requiredRat ?? userProvidedRat ?? 'permissive'
908
+
909
+ // Convert 'none' to undefined for XML (empty field).
910
+ // Round-trip safe: toShape maps empty string back to 'none' (line 796-798)
911
+ if (rat === 'none') {
912
+ return undefined
913
+ }
914
+ return rat
915
+ }),
916
+ licensable: $.val(licensing?.get('licensable')).toBoolean().def(true),
917
+ enforce_license: $.val(licensing?.get('enforceLicense')).toString().def('log'),
918
+ license_model: $.val(licensing?.get('licenseModel')).toString().def('none'),
919
+ menu: $.toString().def(''),
920
+ user_role: $.val(accessControls?.get('userRole')).toString().def(''),
921
+ short_description: $.from('description').toString().def(''),
922
+ logo: $.toString().def(''),
923
+ guided_setup_guid: $.from('guidedSetupGuid').toString().def(''),
924
+ subscription_entitlement: $.val(licensing?.get('subscriptionEntitlement')).toString().def(''),
925
+ private: $.val(accessControls?.get('private')).toBoolean().def(false),
926
+ trackable: $.val(accessControls?.get('trackable')).toBoolean().def(true),
927
+ uninstall_blocked: $.val(accessControls?.get('uninstallBlocked')).toBoolean().def(false),
928
+ hide_on_ui: $.val(accessControls?.get('hideOnUI')).toBoolean().def(false),
929
+ installed_as_dependency: $.from('installedAsDependency').toBoolean().def(false),
930
+ license_category: $.val(licensing?.get('licenseCategory')).toString().def('none'),
931
+ scope: $.val(scope),
932
+ package_json: $.val(packageJsonPath),
933
+ name: $.val(name),
934
+ source: $.val(scope),
935
+ sys_id: $.from('scopeId'),
936
+ version: $.val(version),
937
+ package_resolver_version: $.from('packageResolverVersion').def(
938
+ scope === 'global' ? MODULE_RESOLUTION.V2 : MODULE_RESOLUTION.V1
939
+ ),
940
+ })),
941
+ })
942
+
943
+ const policyRecords: SdkRecord[] = []
944
+ const scopeId = config.get('scopeId').asString().getValue()
945
+
946
+ // Create network policy records
947
+ if (networkPoliciesShape.length > 0) {
948
+ const networkRecords = await createNetworkPolicyRecords(
949
+ networkPoliciesShape,
950
+ factory,
951
+ config,
952
+ diagnostics
953
+ )
954
+ policyRecords.push(...networkRecords)
955
+ }
956
+
957
+ // Create wildcard policy record
958
+ if (wildcardPolicyShape) {
959
+ const wildcardRecord = await createWildcardPolicyRecord(
960
+ wildcardPolicyShape,
961
+ factory,
962
+ config,
963
+ scopeId,
964
+ diagnostics
965
+ )
966
+ policyRecords.push(wildcardRecord)
967
+ }
968
+
969
+ // Create performance policy record
970
+ if (performancePolicyShape) {
971
+ const performanceRecord = await createPerformancePolicyRecord(
972
+ performancePolicyShape,
973
+ factory,
974
+ config,
975
+ scopeId,
976
+ applicationRuntimePolicy,
977
+ diagnostics
978
+ )
979
+ policyRecords.push(performanceRecord)
980
+ }
981
+
153
982
  return {
154
983
  success: true,
155
- value: await factory.createRecord({
156
- source: config,
157
- table: 'sys_app',
158
- properties: config.transform(({ $ }) => ({
159
- active: $.toBoolean().def(true),
160
- scoped_administration: $.val(accessControls?.get('scopedAdministration'))
161
- .toBoolean()
162
- .def(false),
163
- can_edit_in_studio: $.val(accessControls?.get('canEditInStudio')).toBoolean().def(true),
164
- js_level: $.from('jsLevel').toString().def('es_latest'),
165
- restrict_table_access: $.val(accessControls?.get('restrictTableAccess'))
166
- .toBoolean()
167
- .def(false),
168
- runtime_access_tracking: $.map(() => {
169
- const rac = accessControls?.get('runtimeAccessTracking').getValue()
170
- if (!rac) {
171
- return 'permissive'
172
- } else if (rac === 'none') {
173
- return undefined
174
- }
175
- return rac
176
- }),
177
- licensable: $.val(licensing?.get('licensable')).toBoolean().def(true),
178
- enforce_license: $.val(licensing?.get('enforceLicense')).toString().def('log'),
179
- license_model: $.val(licensing?.get('licenseModel')).toString().def('none'),
180
- menu: $.toString().def(''),
181
- user_role: $.val(accessControls?.get('userRole')).toString().def(''),
182
- short_description: $.from('description').toString().def(''),
183
- logo: $.toString().def(''),
184
- guided_setup_guid: $.from('guidedSetupGuid').toString().def(''),
185
- subscription_entitlement: $.val(licensing?.get('subscriptionEntitlement'))
186
- .toString()
187
- .def(''),
188
- private: $.val(accessControls?.get('private')).toBoolean().def(false),
189
- trackable: $.val(accessControls?.get('trackable')).toBoolean().def(true),
190
- uninstall_blocked: $.val(accessControls?.get('uninstallBlocked')).toBoolean().def(false),
191
- hide_on_ui: $.val(accessControls?.get('hideOnUI')).toBoolean().def(false),
192
- installed_as_dependency: $.from('installedAsDependency').toBoolean().def(false),
193
- license_category: $.val(licensing?.get('licenseCategory')).toString().def('none'),
194
- scope: $.val(scope),
195
- package_json: $.val(packageJsonPath),
196
- name: $.val(name),
197
- source: $.val(scope),
198
- sys_id: $.from('scopeId'),
199
- version: $.val(version),
200
- package_resolver_version: $.from('packageResolverVersion').def(
201
- scope === 'global' ? MODULE_RESOLUTION.V2 : MODULE_RESOLUTION.V1
202
- ),
203
- })),
204
- }),
984
+ value: sysAppRecord.with(...policyRecords),
205
985
  }
206
986
  },
207
987
  },