@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
@@ -0,0 +1,679 @@
1
+ import {
2
+ CallExpressionShape,
3
+ Plugin,
4
+ DurationShape,
5
+ TimeShape,
6
+ timeFieldToXML,
7
+ formatDateToPlatformFormat,
8
+ type Shape,
9
+ } from '@servicenow/sdk-build-core'
10
+ import { NowIdShape } from '../now-id-plugin'
11
+ import { NowIncludeShape } from '../now-include-plugin'
12
+ import { createSdkDocEntry, toReference } from '../utils'
13
+ import { dateTimeFieldToXML, convertXMLToDateTime, formatTimeDataToDateTime, formatToUTC } from './timeZoneConverter'
14
+
15
+ const DEFAULT_DATE_TIME = '1970-01-01 00:00:00'
16
+
17
+ /**
18
+ * Calculate the default entered_run_time for 00:00:00 based on timezone
19
+ * @param timeZone - The IANA timezone identifier
20
+ * @returns Formatted datetime string representing 00:00:00 in the specified timezone
21
+ */
22
+ function getDefaultRunTime(timeZone: string): string {
23
+ try {
24
+ // Floating timezone → return raw epoch
25
+ if (timeZone === 'floating') {
26
+ return DEFAULT_DATE_TIME
27
+ }
28
+
29
+ // Valid timezone → treat reference as LOCAL in that timezone → convert to UTC
30
+ const utcDate = timeFieldToXML({ hours: 0, minutes: 0, seconds: 0 }, timeZone)
31
+ return formatDateToPlatformFormat(utcDate)
32
+ } catch (error) {
33
+ return DEFAULT_DATE_TIME
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Map day of week numeric values to day names
39
+ */
40
+ const dayOfWeekMap = new Map([
41
+ ['1', 'monday'],
42
+ ['2', 'tuesday'],
43
+ ['3', 'wednesday'],
44
+ ['4', 'thursday'],
45
+ ['5', 'friday'],
46
+ ['6', 'saturday'],
47
+ ['7', 'sunday'],
48
+ ])
49
+
50
+ const offsetTypeMap = new Map([
51
+ ['1', 'past'],
52
+ ['2', 'future'],
53
+ ])
54
+
55
+ /**
56
+ * Reverse map: offset type names to numeric values (generated from offsetTypeMap)
57
+ */
58
+ const reverseOffsetTypeMap = new Map(Array.from(offsetTypeMap.entries()).map(([num, type]) => [type, num]))
59
+
60
+ /**
61
+ * Reverse map: day names to numeric values (generated from dayOfWeekMap)
62
+ */
63
+ const reverseDayOfWeekMap = new Map(Array.from(dayOfWeekMap.entries()).map(([num, day]) => [day, num]))
64
+
65
+ /**
66
+ * Negate a duration object (multiply all values by -1)
67
+ * @param duration - Duration object with days, hours, minutes, seconds
68
+ * @returns Negated duration object
69
+ */
70
+ function negateDuration(duration: { days?: number; hours?: number; minutes?: number; seconds?: number }) {
71
+ return {
72
+ days: duration.days ? -duration.days : 0,
73
+ hours: duration.hours ? -duration.hours : 0,
74
+ minutes: duration.minutes ? -duration.minutes : 0,
75
+ seconds: duration.seconds ? -duration.seconds : 0,
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Add duration to a Date object in place
81
+ * @param date - Date object to modify
82
+ * @param duration - Duration to add (days, hours, minutes, seconds)
83
+ */
84
+ function addDuration(date: Date, duration: { days?: number; hours?: number; minutes?: number; seconds?: number }) {
85
+ if (duration.days) {
86
+ date.setDate(date.getDate() + duration.days)
87
+ }
88
+ if (duration.hours) {
89
+ date.setHours(date.getHours() + duration.hours)
90
+ }
91
+ if (duration.minutes) {
92
+ date.setMinutes(date.getMinutes() + duration.minutes)
93
+ }
94
+ if (duration.seconds) {
95
+ date.setSeconds(date.getSeconds() + duration.seconds)
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Add duration to a datetime string without timezone conversion
101
+ * @param dateTimeStr - Datetime string in format 'YYYY-MM-DD HH:MM:SS'
102
+ * @param duration - Duration to add (days, hours, minutes, seconds)
103
+ * @returns New datetime string with duration added
104
+ */
105
+ function addDurationToString(
106
+ dateTimeStr: string,
107
+ duration: { days?: number; hours?: number; minutes?: number; seconds?: number }
108
+ ): string {
109
+ const parts = dateTimeStr.split(' ')
110
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
111
+ return dateTimeStr
112
+ }
113
+
114
+ const dateParts = parts[0].split('-').map(Number)
115
+ const timeParts = parts[1].split(':').map(Number)
116
+
117
+ if (dateParts.length !== 3 || timeParts.length !== 3) {
118
+ return dateTimeStr
119
+ }
120
+ if (dateParts.some(isNaN) || timeParts.some(isNaN)) {
121
+ return dateTimeStr
122
+ }
123
+
124
+ const date = new Date(
125
+ dateParts[0] ?? 0,
126
+ (dateParts[1] ?? 1) - 1,
127
+ dateParts[2] ?? 1,
128
+ timeParts[0] ?? 0,
129
+ timeParts[1] ?? 0,
130
+ timeParts[2] ?? 0
131
+ )
132
+ addDuration(date, duration)
133
+
134
+ const newYear = date.getFullYear()
135
+ const newMonth = String(date.getMonth() + 1).padStart(2, '0')
136
+ const newDay = String(date.getDate()).padStart(2, '0')
137
+ const newHours = String(date.getHours()).padStart(2, '0')
138
+ const newMinutes = String(date.getMinutes()).padStart(2, '0')
139
+ const newSeconds = String(date.getSeconds()).padStart(2, '0')
140
+ return `${newYear}-${newMonth}-${newDay} ${newHours}:${newMinutes}:${newSeconds}`
141
+ }
142
+
143
+ /**
144
+ * Calculate the recurring interval in days for different run types
145
+ * @param frequency - The type of recurring schedule
146
+ * @returns Number of days in the interval, or undefined if not a fixed-interval type
147
+ */
148
+ function calculateRecurringInterval(frequency: string): number | undefined {
149
+ const recurrenceIntervals: Record<string, number> = {
150
+ daily: 1,
151
+ weekly: 7,
152
+ day_and_month_in_year: 365,
153
+ day_week_month_year: 365,
154
+ }
155
+ return recurrenceIntervals[frequency]
156
+ }
157
+
158
+ /**
159
+ * Calculate minimum end date for monthly recurring jobs
160
+ * @param executionStart - Start datetime string
161
+ * @param maxDrift - Optional max drift duration
162
+ * @returns Object with minEndStr and intervalDescription
163
+ */
164
+ function calculateMonthlyMinEndDate(
165
+ executionStart: string,
166
+ maxDrift?: { days?: number; hours?: number; minutes?: number; seconds?: number }
167
+ ): { minEndStr: string | undefined; intervalDescription: string | undefined } {
168
+ const parts = executionStart.split(' ')
169
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
170
+ return { minEndStr: undefined, intervalDescription: undefined }
171
+ }
172
+
173
+ const [datePart] = parts
174
+ const dateParts = datePart.split('-').map(Number)
175
+ if (dateParts.length !== 3) {
176
+ return { minEndStr: undefined, intervalDescription: undefined }
177
+ }
178
+
179
+ const [year, month, day] = dateParts
180
+ const nextDate = new Date(year!, month!, day!)
181
+ const nextYear = nextDate.getFullYear()
182
+ const nextMonth = String(nextDate.getMonth() + 1).padStart(2, '0')
183
+ const nextDay = String(nextDate.getDate()).padStart(2, '0')
184
+ let minEndStr = `${nextYear}-${nextMonth}-${nextDay} ${parts[1]}`
185
+
186
+ if (maxDrift) {
187
+ minEndStr = addDurationToString(minEndStr, negateDuration(maxDrift))
188
+ }
189
+
190
+ const intervalDescription = `1 month${maxDrift ? ' - maxDrift' : ''}`
191
+ return { minEndStr, intervalDescription }
192
+ }
193
+
194
+ /**
195
+ * Calculate minimum end date for periodically recurring jobs
196
+ * @param executionStart - Start datetime string
197
+ * @param executionInterval - Duration for the period
198
+ * @param maxDrift - Optional max drift duration
199
+ * @returns Object with minEndStr and intervalDescription
200
+ */
201
+ function calculatePeriodicallyMinEndDate(
202
+ executionStart: string,
203
+ executionInterval: { days?: number; hours?: number; minutes?: number; seconds?: number },
204
+ maxDrift?: { days?: number; hours?: number; minutes?: number; seconds?: number }
205
+ ): { minEndStr: string; intervalDescription: string } {
206
+ let minEndStr = addDurationToString(executionStart, executionInterval)
207
+
208
+ if (maxDrift) {
209
+ minEndStr = addDurationToString(minEndStr, negateDuration(maxDrift))
210
+ }
211
+
212
+ const intervalDescription = `executionInterval${maxDrift ? ' - maxDrift' : ''}`
213
+ return { minEndStr, intervalDescription }
214
+ }
215
+
216
+ /**
217
+ * Calculate minimum end date for fixed-interval recurring jobs (daily, weekly, yearly)
218
+ * @param executionStart - Start datetime string
219
+ * @param intervalDays - Number of days in the interval
220
+ * @param maxDrift - Optional max drift duration
221
+ * @returns Object with minEndStr and intervalDescription
222
+ */
223
+ function calculateFixedIntervalMinEndDate(
224
+ executionStart: string,
225
+ intervalDays: number,
226
+ maxDrift?: { days?: number; hours?: number; minutes?: number; seconds?: number }
227
+ ): { minEndStr: string; intervalDescription: string } {
228
+ let minEndStr = addDurationToString(executionStart, { days: intervalDays })
229
+
230
+ if (maxDrift) {
231
+ minEndStr = addDurationToString(minEndStr, negateDuration(maxDrift))
232
+ }
233
+
234
+ const intervalDescription = `${intervalDays} day${intervalDays > 1 ? 's' : ''}${maxDrift ? ' - maxDrift' : ''}`
235
+ return { minEndStr, intervalDescription }
236
+ }
237
+
238
+ /**
239
+ * Validate that executionEnd allows at least one execution for recurring jobs
240
+ * Based on ServiceNow's "Ensure Valid Schedule" business rules
241
+ * @param executionStart - Start datetime string
242
+ * @param executionEnd - End datetime string
243
+ * @param frequency - Type of recurring schedule
244
+ * @param executionIntervalShape - Shape containing executionInterval duration (for periodically jobs)
245
+ * @param maxDriftShape - Shape containing maxDrift duration
246
+ * @returns Object with minEndStr and intervalDescription if validation is needed, undefined otherwise
247
+ */
248
+ function validateRecurringJobSchedule(
249
+ executionStart: string,
250
+ executionEnd: string,
251
+ frequency: string,
252
+ executionIntervalShape: Shape,
253
+ maxDriftShape: Shape
254
+ ): { minEndStr: string | undefined; intervalDescription: string | undefined } | undefined {
255
+ if (frequency === 'once' || frequency === 'on_demand') {
256
+ return
257
+ }
258
+
259
+ const maxDrift = maxDriftShape.is(DurationShape) ? maxDriftShape.as(DurationShape).getDuration() : undefined
260
+
261
+ let minEndStr: string | undefined
262
+ let intervalDescription: string | undefined
263
+
264
+ if (frequency === 'periodically' && executionIntervalShape.is(DurationShape)) {
265
+ const duration = executionIntervalShape.as(DurationShape).getDuration()
266
+ const result = calculatePeriodicallyMinEndDate(executionStart, duration, maxDrift)
267
+ minEndStr = result.minEndStr
268
+ intervalDescription = result.intervalDescription
269
+ } else if (frequency === 'monthly' || frequency === 'week_in_month') {
270
+ const result = calculateMonthlyMinEndDate(executionStart, maxDrift)
271
+ minEndStr = result.minEndStr
272
+ intervalDescription = result.intervalDescription
273
+ } else {
274
+ const intervalDays = calculateRecurringInterval(frequency)
275
+ if (intervalDays) {
276
+ const result = calculateFixedIntervalMinEndDate(executionStart, intervalDays, maxDrift)
277
+ minEndStr = result.minEndStr
278
+ intervalDescription = result.intervalDescription
279
+ }
280
+ }
281
+
282
+ if (minEndStr && executionEnd < minEndStr) {
283
+ return { minEndStr, intervalDescription }
284
+ }
285
+
286
+ return undefined
287
+ }
288
+
289
+ export const ScheduledScriptPlugin = Plugin.create({
290
+ name: 'ScheduledScriptPlugin',
291
+ docs: [createSdkDocEntry('ScheduledScript', ['sysauto_script'])],
292
+ records: {
293
+ sysauto_script: {
294
+ async toShape(record, { transform }) {
295
+ const scriptValue = record.get('script').ifString()?.ifNotEmpty()
296
+ const script = scriptValue
297
+ ? await NowIncludeShape.fromRecord(record, record.get('script'), transform)
298
+ : undefined
299
+ const timeZone = record.get('time_zone').ifString()?.ifNotEmpty()?.getValue()
300
+
301
+ return {
302
+ success: true,
303
+ value: new CallExpressionShape({
304
+ source: record,
305
+ callee: 'ScheduledScript',
306
+ args: [
307
+ record.transform(({ $ }) => ({
308
+ $id: $.val(NowIdShape.from(record)),
309
+ name: $,
310
+ active: $.from('active').toBoolean().def(true),
311
+ conditional: $.toBoolean().def(false),
312
+ condition: $.def(''),
313
+
314
+ // Offset configuration
315
+ offset: $.map((v) => {
316
+ const stringShape = v.ifString()?.ifNotEmpty()
317
+ if (!stringShape) {
318
+ return undefined
319
+ }
320
+ return DurationShape.from(record, stringShape)
321
+ }),
322
+ offsetType: $.from('offset_type').map((v) => {
323
+ const value = v.ifString()?.getValue()
324
+ // Map: 1='past', 2='future', 0=undefined (no offset)
325
+ if (!value || value === '0') {
326
+ return undefined
327
+ }
328
+ return offsetTypeMap.get(value)
329
+ }),
330
+
331
+ // Run as configuration
332
+ runAs: $.from('run_as').def(''),
333
+ userTimeZone: $.from('run_as_tz').def(''),
334
+
335
+ // Schedule type and timing
336
+ frequency: $.from('run_type'),
337
+ dayOfWeek: $.from('run_dayofweek').map((v) => {
338
+ const val = v.ifString()?.getValue()
339
+ return val ? dayOfWeekMap.get(val) : undefined
340
+ }),
341
+ daysOfWeek: $.from('run_daysofweek').map((v) => {
342
+ const val = v.ifString()?.getValue()
343
+ if (!val) {
344
+ return undefined
345
+ }
346
+ // Convert numeric string like '1234' to array of day names
347
+ const days = val
348
+ .split('')
349
+ .map((num) => dayOfWeekMap.get(num))
350
+ .filter(Boolean)
351
+ return days.length > 0 ? days : undefined
352
+ }),
353
+ dayOfMonth: $.from('run_dayofmonth').map((v) => {
354
+ const val = v.ifString()?.ifNotEmpty()
355
+ return val ? val.toNumber() : undefined
356
+ }),
357
+ weekInMonth: $.from('run_weekinmonth').map((v) => {
358
+ const val = v.ifString()?.ifNotEmpty()
359
+ return val ? val.toNumber() : undefined
360
+ }),
361
+ month: $.from('run_month').map((v) => {
362
+ const val = v.ifString()?.ifNotEmpty()
363
+ return val ? val.toNumber() : undefined
364
+ }),
365
+
366
+ // Time and duration configuration
367
+ executionTime: $.from('run_time').map((v) => {
368
+ const stringShape = v.ifString()?.ifNotEmpty()
369
+ if (!stringShape) {
370
+ return undefined
371
+ }
372
+ // Get timezone from time_zone field
373
+
374
+ // If timezone is 'floating', treat it as UTC for TimeShape conversion
375
+ const tz = timeZone === 'floating' ? 'UTC' : timeZone
376
+ // Convert to TimeShape with timezone
377
+ const timeShape = TimeShape.from(record, stringShape, tz)
378
+ const timeData = timeShape.getTimeData()
379
+ // Return undefined if Time object would be empty (no hours, minutes, or seconds)
380
+ if (!timeData.hours && !timeData.minutes && !timeData.seconds) {
381
+ return undefined
382
+ }
383
+ return timeShape
384
+ }),
385
+ executionInterval: $.from('run_period').map((v) => {
386
+ const stringShape = v.ifString()?.ifNotEmpty()
387
+ if (!stringShape) {
388
+ return undefined
389
+ }
390
+ return DurationShape.from(record, stringShape)
391
+ }),
392
+ executionStart: $.from('run_start').map((v) => {
393
+ const stringShape = v.ifString()?.ifNotEmpty()
394
+ if (stringShape) {
395
+ return convertXMLToDateTime(stringShape.getValue(), timeZone)
396
+ }
397
+ return undefined
398
+ }),
399
+ executionEnd: $.from('run_end').map((v) => {
400
+ const stringShape = v.ifString()?.ifNotEmpty()
401
+ if (stringShape) {
402
+ return convertXMLToDateTime(stringShape.getValue(), timeZone)
403
+ }
404
+ return undefined
405
+ }),
406
+
407
+ maxDrift: $.from('max_drift').map((v) => {
408
+ const stringShape = v.ifString()?.ifNotEmpty()
409
+ if (!stringShape) {
410
+ return undefined
411
+ }
412
+ return DurationShape.from(record, stringShape)
413
+ }),
414
+
415
+ // Additional scheduling options
416
+ repeatEvery: $.from('repeat_every').map((v) =>
417
+ v.ifString()?.isEmpty() || v.isUndefined() ? undefined : v.toNumber().getValue()
418
+ ),
419
+ upgradeSafe: $.from('upgrade_safe').toBoolean().def(false),
420
+ timeZone: $.from('time_zone').map((v) => {
421
+ const val = v.ifString()?.getValue()
422
+ return val || undefined
423
+ }),
424
+ businessCalendar: $.from('business_calendar').def(''),
425
+ advanced: $.toBoolean().def(false),
426
+
427
+ // Protection policy
428
+ protectionPolicy: $.from('sys_policy').map((v) => {
429
+ const val = v.ifString()?.ifNotEmpty()?.getValue()
430
+ // Only return valid values: 'read' or 'protected'
431
+ return val === 'read' || val === 'protected' ? val : undefined
432
+ }),
433
+
434
+ // Script
435
+ script: $.val(script),
436
+ })),
437
+ ],
438
+ }),
439
+ }
440
+ },
441
+ },
442
+ },
443
+ shapes: [
444
+ {
445
+ shape: CallExpressionShape,
446
+ fileTypes: ['fluent'],
447
+ async toRecord(callExpression, { factory, diagnostics }) {
448
+ if (callExpression.getCallee() !== 'ScheduledScript') {
449
+ return { success: false }
450
+ }
451
+
452
+ const args = callExpression.getArgument(0).asObject()
453
+
454
+ // Validate executionInterval is defined when frequency is 'periodically'
455
+ const frequency = args.get('frequency').ifString()?.getValue()
456
+ if (frequency === 'periodically' && args.get('executionInterval').isUndefined()) {
457
+ diagnostics.error(
458
+ args.get('frequency'),
459
+ `executionInterval must be defined when frequency is 'periodically'`
460
+ )
461
+ }
462
+
463
+ // Validate daysOfWeek is defined when frequency is 'weekly'
464
+ if (frequency === 'weekly' && args.get('daysOfWeek').isUndefined()) {
465
+ diagnostics.error(args.get('frequency'), `daysOfWeek must be defined when frequency is 'weekly'`)
466
+ }
467
+
468
+ // Validate businessCalendar is not empty when frequency is business calendar related
469
+ const businessCalendarArg = args.get('businessCalendar')
470
+ const businessCalendar = toReference(businessCalendarArg)
471
+
472
+ if (
473
+ (frequency === 'business_calendar_start' || frequency === 'business_calendar_end') &&
474
+ !businessCalendar
475
+ ) {
476
+ diagnostics.error(
477
+ businessCalendarArg,
478
+ `businessCalendar cannot be empty when frequency is '${frequency}'. Provide a valid business calendar reference.`
479
+ )
480
+ }
481
+
482
+ // Validate executionStart datetime format
483
+ const startDate = args.get('executionStart').ifString()?.getValue()
484
+
485
+ if (startDate && !/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(startDate)) {
486
+ diagnostics.error(
487
+ args.get('executionStart'),
488
+ `Invalid datetime format for executionStart: '${startDate}'. Expected format: 'YYYY-MM-DD HH:MM:SS' (e.g., '2024-01-01 00:00:00')`
489
+ )
490
+ }
491
+
492
+ // Validate executionEnd datetime format
493
+ const endDate = args.get('executionEnd').ifString()?.getValue()
494
+
495
+ if (endDate) {
496
+ if (!/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(endDate)) {
497
+ diagnostics.error(
498
+ args.get('executionEnd'),
499
+ `Invalid datetime format for executionEnd: '${endDate}'. Expected format: 'YYYY-MM-DD HH:MM:SS' (e.g., '2024-12-31 23:59:59')`
500
+ )
501
+ }
502
+ // Validate executionStart and executionEnd relationship
503
+ else if (startDate) {
504
+ const start = new Date(startDate)
505
+ const end = new Date(endDate)
506
+
507
+ if (end <= start) {
508
+ diagnostics.error(
509
+ args.get('executionEnd'),
510
+ `executionEnd ('${endDate}') must be after executionStart ('${startDate}')`
511
+ )
512
+ }
513
+
514
+ // Validate recurring job schedule allows at least one execution
515
+ else {
516
+ const validationResult = validateRecurringJobSchedule(
517
+ startDate,
518
+ endDate,
519
+ frequency ?? 'daily',
520
+ args.get('executionInterval'),
521
+ args.get('maxDrift')
522
+ )
523
+
524
+ if (validationResult) {
525
+ diagnostics.error(
526
+ args.get('executionEnd'),
527
+ `executionEnd ('${endDate}') may not allow any executions for recurring job type '${frequency}'. ` +
528
+ `Minimum executionEnd should be at least '${validationResult.minEndStr}' (executionStart + ${validationResult.intervalDescription})`
529
+ )
530
+ }
531
+ }
532
+ }
533
+ }
534
+
535
+ // Check timezone before transformation
536
+ const timeZone = args.get('timeZone').ifString()?.getValue()
537
+
538
+ // Validate executionTime has timezone if ScheduledScript has timeZone
539
+ const executionTimeArg = args.get('executionTime')
540
+ if (timeZone && timeZone !== 'floating' && executionTimeArg.is(TimeShape)) {
541
+ const timeShape = executionTimeArg.as(TimeShape)
542
+ const timeShapeTimeZone = timeShape.getTimeZone()
543
+ if (!timeShapeTimeZone) {
544
+ diagnostics.error(
545
+ executionTimeArg,
546
+ `executionTime must include timezone when ScheduledScript timeZone is '${timeZone}'. ` +
547
+ `Use Time({ hours: ..., minutes: ..., }, '${timeZone}' }) instead of Time({ hours: ..., minutes: ... }).`
548
+ )
549
+ } else if (timeShapeTimeZone !== timeZone) {
550
+ diagnostics.error(
551
+ executionTimeArg,
552
+ `executionTime timezone '${timeShapeTimeZone}' does not match ScheduledScript timeZone '${timeZone}'. ` +
553
+ `They should be the same.`
554
+ )
555
+ }
556
+ }
557
+
558
+ // Calculate default entered_run_time based on timezone
559
+ const defaultRunTime = getDefaultRunTime(timeZone ?? '')
560
+
561
+ const record = await factory.createRecord({
562
+ source: callExpression,
563
+ table: 'sysauto_script',
564
+ explicitId: args.get('$id'),
565
+ properties: args.transform(({ $ }) => ({
566
+ name: $,
567
+ active: $.from('active').def(true),
568
+ conditional: $.def(false),
569
+ condition: $.toCdata(),
570
+
571
+ // Offset configuration
572
+ offset: $.map((v) => v.toString()),
573
+ offset_type: $.from('offsetType').map((v) => {
574
+ const val = v.ifString()?.getValue()
575
+ return val ? reverseOffsetTypeMap.get(val) : '0'
576
+ }),
577
+
578
+ // Run as configuration
579
+ run_as: $.from('runAs').map(toReference),
580
+ run_as_tz: $.from('userTimeZone'),
581
+
582
+ // Schedule type and timing
583
+ run_type: $.from('frequency').def('daily'),
584
+ run_dayofweek: $.from('dayOfWeek').map((v) => {
585
+ const val = v.ifString()?.getValue()
586
+ return val ? reverseDayOfWeekMap.get(val) : '1'
587
+ }),
588
+ run_daysofweek: $.from('daysOfWeek').map((v) => {
589
+ // Convert array of day names to numeric string like '1234'
590
+ if (v.isArray()) {
591
+ const nums = v
592
+ .asArray()
593
+ .map((item) => {
594
+ const dayName = item.ifString()?.getValue()
595
+ return dayName ? reverseDayOfWeekMap.get(dayName) : null
596
+ })
597
+ .filter(Boolean)
598
+ return nums.length > 0 ? nums.join('') : undefined
599
+ }
600
+ return undefined
601
+ }),
602
+ run_dayofmonth: $.from('dayOfMonth').def('1'),
603
+ run_weekinmonth: $.from('weekInMonth').def('1'),
604
+ run_month: $.from('month').def('1'),
605
+
606
+ // Time and duration configuration
607
+ run_time: $.from('executionTime').map((v) => {
608
+ if (v.isUndefined()) {
609
+ return defaultRunTime
610
+ }
611
+ // If timezone is floating, return the actual time without UTC conversion
612
+ if (timeZone === 'floating' && v.is(TimeShape)) {
613
+ const timeData = v.as(TimeShape).getTimeData()
614
+ return formatTimeDataToDateTime(timeData)
615
+ }
616
+ return v.toString()
617
+ }),
618
+ entered_time: $.from('executionTime')
619
+ .map((v) => {
620
+ if (v.is(TimeShape)) {
621
+ const timeData = v.as(TimeShape).getTimeData()
622
+ return formatTimeDataToDateTime(timeData)
623
+ }
624
+ return undefined
625
+ })
626
+ .def('1970-01-01 00:00:00'),
627
+ run_period: $.from('executionInterval').map((v) => v.toString()),
628
+ run_start: $.from('executionStart')
629
+ .map((v) => {
630
+ const dateTimeStr = v.ifString()?.getValue()
631
+ if (dateTimeStr) {
632
+ return dateTimeFieldToXML(dateTimeStr, timeZone)
633
+ }
634
+ return undefined
635
+ })
636
+ .def(formatToUTC()),
637
+ run_end: $.from('executionEnd').map((v) => {
638
+ const dateTimeStr = v.ifString()?.getValue()
639
+ if (!dateTimeStr) {
640
+ return undefined
641
+ }
642
+ return dateTimeFieldToXML(dateTimeStr, timeZone)
643
+ }),
644
+ entered_run_start: $.from('executionStart').map((v) => {
645
+ const dateTimeStr = v.ifString()?.getValue()
646
+ if (dateTimeStr && timeZone) {
647
+ return dateTimeStr
648
+ }
649
+ return undefined
650
+ }),
651
+ entered_run_end: $.from('executionEnd').map((v) => {
652
+ const dateTimeStr = v.ifString()?.getValue()
653
+ if (dateTimeStr && timeZone) {
654
+ return dateTimeStr
655
+ }
656
+ return undefined
657
+ }),
658
+ max_drift: $.from('maxDrift').map((v) => v.toString()),
659
+
660
+ // Additional scheduling options
661
+ repeat_every: $.from('repeatEvery'),
662
+ upgrade_safe: $.from('upgradeSafe').def(false),
663
+ time_zone: $.from('timeZone'),
664
+ business_calendar: $.val(businessCalendar),
665
+ advanced: $.def(false),
666
+
667
+ // Protection policy
668
+ sys_policy: $.from('protectionPolicy'),
669
+
670
+ // Script
671
+ script: $.toCdata(),
672
+ })),
673
+ })
674
+
675
+ return { success: true, value: record }
676
+ },
677
+ },
678
+ ],
679
+ })