@servicenow/sdk-build-core 4.4.1 → 4.6.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 (56) hide show
  1. package/dist/compression.d.ts +2 -1
  2. package/dist/compression.js +5 -2
  3. package/dist/compression.js.map +1 -1
  4. package/dist/keys-registry.d.ts +2 -1
  5. package/dist/keys-registry.js +15 -15
  6. package/dist/keys-registry.js.map +1 -1
  7. package/dist/now-config.d.ts +476 -5
  8. package/dist/now-config.js +165 -1
  9. package/dist/now-config.js.map +1 -1
  10. package/dist/plugins/index.d.ts +1 -0
  11. package/dist/plugins/index.js +1 -0
  12. package/dist/plugins/index.js.map +1 -1
  13. package/dist/plugins/plugin.d.ts +17 -18
  14. package/dist/plugins/plugin.js +52 -31
  15. package/dist/plugins/plugin.js.map +1 -1
  16. package/dist/plugins/post-install.d.ts +31 -0
  17. package/dist/plugins/post-install.js +3 -0
  18. package/dist/plugins/post-install.js.map +1 -0
  19. package/dist/plugins/shape.js +4 -1
  20. package/dist/plugins/shape.js.map +1 -1
  21. package/dist/taxonomy.js +50 -0
  22. package/dist/taxonomy.js.map +1 -1
  23. package/dist/telemetry/clients/abstract-client.d.ts +23 -2
  24. package/dist/telemetry/clients/abstract-client.js +80 -0
  25. package/dist/telemetry/clients/abstract-client.js.map +1 -1
  26. package/dist/telemetry/clients/browser-client.d.ts +15 -9
  27. package/dist/telemetry/clients/browser-client.js +8 -82
  28. package/dist/telemetry/clients/browser-client.js.map +1 -1
  29. package/dist/telemetry/clients/node-client.d.ts +28 -6
  30. package/dist/telemetry/clients/node-client.js +37 -101
  31. package/dist/telemetry/clients/node-client.js.map +1 -1
  32. package/dist/telemetry/factory.js +4 -6
  33. package/dist/telemetry/factory.js.map +1 -1
  34. package/dist/telemetry/types.d.ts +1 -1
  35. package/dist/typescript.d.ts +8 -0
  36. package/dist/typescript.js +12 -1
  37. package/dist/typescript.js.map +1 -1
  38. package/dist/xml.js +3 -1
  39. package/dist/xml.js.map +1 -1
  40. package/now.config.schema.json +386 -2
  41. package/package.json +5 -6
  42. package/src/compression.ts +7 -2
  43. package/src/keys-registry.ts +14 -12
  44. package/src/now-config.ts +204 -1
  45. package/src/plugins/index.ts +1 -0
  46. package/src/plugins/plugin.ts +86 -63
  47. package/src/plugins/post-install.ts +34 -0
  48. package/src/plugins/shape.ts +4 -1
  49. package/src/taxonomy.ts +53 -0
  50. package/src/telemetry/clients/abstract-client.ts +104 -3
  51. package/src/telemetry/clients/browser-client.ts +12 -95
  52. package/src/telemetry/clients/node-client.ts +39 -114
  53. package/src/telemetry/factory.ts +5 -8
  54. package/src/telemetry/types.ts +2 -1
  55. package/src/typescript.ts +12 -1
  56. package/src/xml.ts +4 -1
package/src/now-config.ts CHANGED
@@ -23,6 +23,192 @@ export const MODULE_RESOLUTION = {
23
23
  V2: '2.0.0',
24
24
  }
25
25
 
26
+ // ============================================================================
27
+ // Application Runtime Policy Schemas
28
+ // ============================================================================
29
+
30
+ /**
31
+ * Network Policy Schema
32
+ * Defines network access policies for the application (sys_arp_network_policy)
33
+ */
34
+ const NetworkPolicySchema = z
35
+ .object({
36
+ $id: z
37
+ .string()
38
+ .regex(/^[0-9a-f]{32}$/, 'Must be a 32-character lowercase hexadecimal string')
39
+ .optional(),
40
+ active: z.boolean().default(true),
41
+ policyType: z.enum([
42
+ 'csp_script_src',
43
+ 'csp_connect_src',
44
+ 'now_inbound_scoped',
45
+ 'now_inbound_global',
46
+ 'now_outbound',
47
+ ]),
48
+ status: z.enum(['requested', 'allowed', 'denied']),
49
+ host: z
50
+ .string()
51
+ .max(253)
52
+ .regex(
53
+ /^[a-z][a-z0-9+.-]*:\/\/[^/\s]+$/,
54
+ 'Host must include scheme (e.g., https://api.example.com) and cannot contain paths'
55
+ )
56
+ .optional(),
57
+ scheme: z.enum(['http', 'https', 'ws', 'wss']).optional(),
58
+ path: z.array(z.string().max(1000)).max(100).optional(),
59
+ resource: z
60
+ .string()
61
+ .max(4000)
62
+ .regex(
63
+ /^[^\s,\n\r]+\.[^\s,\n\r]+(\.[^\s,\n\r]+)?$/,
64
+ 'Resource must be "processor.subprocessor" or "processor.subprocessor.context" without spaces or commas'
65
+ )
66
+ .optional(),
67
+ shortDescription: z.string().max(1024).optional(),
68
+ })
69
+ .refine(
70
+ (data) => {
71
+ // Validate conditional field requirements based on policy type
72
+ const { policyType, host, scheme, path, resource } = data
73
+
74
+ switch (policyType) {
75
+ case 'csp_script_src':
76
+ case 'csp_connect_src':
77
+ // Host required, resource must be empty
78
+ if (!host || !host.trim()) {
79
+ return false
80
+ }
81
+ if (resource && resource.trim()) {
82
+ return false
83
+ }
84
+ break
85
+
86
+ case 'now_inbound_global':
87
+ // Scheme required, host must be empty, path required, resource must be empty
88
+ if (!scheme || !scheme.trim()) {
89
+ return false
90
+ }
91
+ if (host && host.trim()) {
92
+ return false
93
+ }
94
+ if (!path || path.length === 0) {
95
+ return false
96
+ }
97
+ if (resource && resource.trim()) {
98
+ return false
99
+ }
100
+ break
101
+
102
+ case 'now_outbound':
103
+ // Host required, resource must be empty
104
+ if (!host || !host.trim()) {
105
+ return false
106
+ }
107
+ if (resource && resource.trim()) {
108
+ return false
109
+ }
110
+ break
111
+
112
+ case 'now_inbound_scoped': {
113
+ // Host must be empty, either path or resource (not both)
114
+ if (host && host.trim()) {
115
+ return false
116
+ }
117
+ const hasResource = resource && resource.trim()
118
+ const hasPath = path && path.length > 0
119
+ if (!hasResource && !hasPath) {
120
+ return false
121
+ }
122
+ if (hasResource && hasPath) {
123
+ return false
124
+ }
125
+ break
126
+ }
127
+ }
128
+
129
+ return true
130
+ },
131
+ {
132
+ message: 'Field requirements not met for policy type',
133
+ }
134
+ )
135
+
136
+ export type NetworkPolicy = z.infer<typeof NetworkPolicySchema>
137
+
138
+ /**
139
+ * Network Pillar Schema for Wildcard Policy
140
+ */
141
+ const NetworkPillarSchema = z.object({
142
+ active: z.boolean().default(false),
143
+ networkWildcard: z
144
+ .array(
145
+ z.enum(['now_outbound', 'now_inbound_global', 'now_inbound_scoped', 'csp_connect_src', 'csp_script_src'])
146
+ )
147
+ .optional(),
148
+ })
149
+
150
+ /**
151
+ * Scripting Pillar Schema for Wildcard Policy
152
+ */
153
+ const ScriptingPillarSchema = z.object({
154
+ active: z.boolean().default(false),
155
+ scriptingWildcard: z.array(z.enum(['sys_script_include', 'scriptable'])).optional(),
156
+ })
157
+
158
+ /**
159
+ * ARL Pillar Schema for Wildcard Policy
160
+ */
161
+ const ArlPillarSchema = z.object({
162
+ active: z.boolean().default(false),
163
+ arlWildcard: z
164
+ .array(
165
+ z.enum(['scheduled_job_limit', 'event_handler', 'api_transaction_limit', 'interactive_transaction_limit'])
166
+ )
167
+ .optional(),
168
+ })
169
+
170
+ /**
171
+ * Wildcard Policy Schema
172
+ * Defines wildcard/exemption policies for the application (sys_arp_segment_policy)
173
+ * Only one per application scope
174
+ */
175
+ const WildcardPolicySchema = z.object({
176
+ $id: z
177
+ .string()
178
+ .regex(/^[0-9a-f]{32}$/, 'Must be a 32-character lowercase hexadecimal string')
179
+ .optional(),
180
+ active: z.boolean().default(false),
181
+ shortDescription: z.string().max(1024).optional(),
182
+ record: z.boolean().default(false),
183
+ network: NetworkPillarSchema.optional(),
184
+ scripting: ScriptingPillarSchema.optional(),
185
+ arl: ArlPillarSchema.optional(),
186
+ })
187
+
188
+ export type WildcardPolicy = z.infer<typeof WildcardPolicySchema>
189
+
190
+ /**
191
+ * Performance Policy Schema
192
+ * Defines resource limits for the application (sys_app_resource_limit_template)
193
+ * Only one per application scope
194
+ */
195
+ const PerformancePolicySchema = z.object({
196
+ $id: z
197
+ .string()
198
+ .regex(/^[0-9a-f]{32}$/, 'Must be a 32-character lowercase hexadecimal string')
199
+ .optional(),
200
+ name: z.string().max(100),
201
+ scheduledJobLimit: z.number().int().min(1).max(100).optional(),
202
+ eventHandlerLimit: z.number().int().min(1).max(100).optional(),
203
+ apiTransactionLimit: z.number().int().min(1).max(100).optional(),
204
+ interactiveTransactionLimit: z.number().int().min(1).max(100).optional(),
205
+ mode: z.enum(['disabled', 'enforced', 'logOnly']).optional(),
206
+ })
207
+
208
+ export type PerformancePolicy = z.infer<typeof PerformancePolicySchema>
209
+
210
+ // ============================================================================
211
+
26
212
  const NowConfigSchema = z
27
213
  .object({
28
214
  scope: z.string(),
@@ -62,11 +248,16 @@ const NowConfigSchema = z
62
248
  tsconfigPath: z.string().optional(),
63
249
  scripts: z.record(z.string(), z.string()).default({}),
64
250
  tableOutputFormat: z.enum(['bootstrap', 'component']).default('bootstrap'),
65
- tableDefaultLanguage: z
251
+ defaultLanguage: z
66
252
  .string()
67
253
  .regex(/^[a-z]{2,3}(-[a-zA-Z0-9]{2,8})*$/)
68
254
  .default('en'),
69
255
 
256
+ tableDefaultLanguage: z // Deprecated — use defaultLanguage
257
+ .string()
258
+ .regex(/^[a-z]{2,3}(-[a-zA-Z0-9]{2,8})*$/)
259
+ .optional(),
260
+
70
261
  moduleDir: z.string().optional(), // Deprecated
71
262
  compileOutputDir: z.string().optional(), // Deprecated
72
263
  sourceDir: z.string().optional(), // Deprecated
@@ -105,6 +296,12 @@ const NowConfigSchema = z
105
296
  packageResolverVersion: z.string().optional(),
106
297
  type: z.enum(['configuration', 'package']).default('package'),
107
298
  taxonomy,
299
+
300
+ // Application Runtime Policy (ARP) fields
301
+ applicationRuntimePolicy: z.enum(['none', 'tracking', 'enforcing']).default('none'),
302
+ networkPolicies: z.array(NetworkPolicySchema).optional(),
303
+ wildcardPolicy: WildcardPolicySchema.optional(),
304
+ performancePolicy: PerformancePolicySchema.optional(),
108
305
  })
109
306
  .refine((data) => {
110
307
  if (!data.generatedDir.startsWith(data.fluentDir)) {
@@ -136,10 +333,15 @@ const NowConfigSchema = z
136
333
  | 'installedAsDependency'
137
334
  | 'scripts'
138
335
  | 'tableOutputFormat'
336
+ | 'defaultLanguage'
139
337
  | 'tableDefaultLanguage'
140
338
  | 'packageResolverVersion'
141
339
  | 'type'
142
340
  | 'taxonomy'
341
+ | 'applicationRuntimePolicy'
342
+ | 'networkPolicies'
343
+ | 'wildcardPolicy'
344
+ | 'performancePolicy'
143
345
  >
144
346
  > = [
145
347
  'serverModulesDir',
@@ -373,6 +575,7 @@ const DEPRECATED_FIELDS: Record<string, DeprecatedField> = {
373
575
  entitiesDir: { replacedBy: 'fluentDir', transfer: true },
374
576
  transpiledSourceDir: { replacedBy: 'modulePaths' },
375
577
  sourceDir: {},
578
+ tableDefaultLanguage: { replacedBy: 'defaultLanguage', transfer: true },
376
579
  }
377
580
 
378
581
  function replaceDeprecatedFields(config: Record<string, unknown>, logger: Logger) {
@@ -8,3 +8,4 @@ export * from './project'
8
8
  export * from './database'
9
9
  export * from './time'
10
10
  export * from './usage'
11
+ export * from './post-install'
@@ -24,11 +24,16 @@ export type Result<Value = unknown> =
24
24
  | {
25
25
  success: true
26
26
  value: Value
27
+ // Leftover nodes to be removed when a plugin chooses to claim ownership of a record by handling it in a different way
28
+ reclaimedRecords?: DeletedShape[]
27
29
  }
28
30
  | {
29
31
  success: 'partial'
30
32
  value: Value
33
+ // Records owned by a plugin (or descendants of that record) that a plugin has deferred to allow other plugins to handle
31
34
  unhandledRecords: Record[]
35
+ // Leftover nodes to be removed when a plugin chooses to claim ownership of a record by transforming it in a different way
36
+ reclaimedRecords?: DeletedShape[]
32
37
  }
33
38
 
34
39
  export type CommitResult = { success: boolean }
@@ -61,17 +66,8 @@ export type Relationship = {
61
66
  * properties, where each is a tuple: [columnName, regexPattern]. The regex extracts
62
67
  * values from the respective columns using capturing groups, and records are related
63
68
  * if all captured groups match between parent and child
64
- * - Function: Takes the parent and child records as arguments and returns a boolean to
65
- * indicate whether the records are related or not (WARNING: Do not use this
66
- * unless absolutely necessary as it can negatively impact performance and
67
- * cannot be serialized by the relationship resolver, requiring changes to
68
- * the REST API it invokes)
69
69
  */
70
- via:
71
- | string
72
- | { [column: string]: string }
73
- | ((parent: Record, child: Record) => boolean)
74
- | { parent: [string, string]; child: [string, string] }[]
70
+ via: string | { [column: string]: string } | { parent: [string, string]; child: [string, string] }[]
75
71
 
76
72
  /**
77
73
  * When true, the relationship direction is reversed - the parent references the child
@@ -104,12 +100,6 @@ export type PluginApiDoc = {
104
100
  */
105
101
  apiName: string
106
102
 
107
- /**
108
- * Uses module-specifier path to the markdown documentation file for this API relative to the SDK root.
109
- * For example, @servicenow/sdk/docs/BusinessRule/BusinessRule.md
110
- */
111
- docPath: string
112
-
113
103
  /**
114
104
  * Tags for categorizing and organizing the API documentation
115
105
  */
@@ -118,7 +108,6 @@ export type PluginApiDoc = {
118
108
 
119
109
  /**
120
110
  * Manifest structure for API documentation.
121
- * This is the opinionated format returned by getDocsMetadata().
122
111
  */
123
112
  export type DocsManifest = {
124
113
  [apiName: string]: {
@@ -142,7 +131,7 @@ export type PluginConfig<
142
131
  * Documentation for the APIs provided by this plugin. This is used to automatically
143
132
  * generate documentation that can be retrieved via the getDocsMetadata() method.
144
133
  */
145
- docs?: PluginApiDoc[]
134
+ docs: PluginApiDoc[]
146
135
 
147
136
  /**
148
137
  * The TypeScript AST nodes this plugin handles. Plugins that do not introduce new
@@ -643,37 +632,38 @@ export class Plugin {
643
632
 
644
633
  /**
645
634
  * Get documentation metadata for APIs provided by this plugin.
646
- * Returns a manifest object with raw (unresolved) documentation paths.
647
- * Path resolution is handled by PluginRegistry.getDocsMetadata() using the isomorphic resolver.
648
635
  */
649
- getDocsMetadata(): DocsManifest | undefined {
636
+ getDocsMetadata(): { [apiName: string]: { tags: string[] } } | undefined {
650
637
  if (!this.config.docs) {
651
638
  return undefined
652
639
  }
653
640
 
654
- const manifest: DocsManifest = {}
641
+ const manifest: { [apiName: string]: { tags: string[] } } = {}
655
642
 
656
643
  for (const doc of this.config.docs) {
657
- manifest[doc.apiName] = {
658
- docPath: doc.docPath,
659
- tags: doc.tags,
660
- }
644
+ manifest[doc.apiName] = { tags: doc.tags }
661
645
  }
662
646
 
663
647
  return manifest
664
648
  }
665
649
 
666
650
  getDescendants(parent: Record, database: Database): Record[] {
651
+ return this.traverseDescendants(parent, database)
652
+ }
653
+
654
+ private traverseDescendants(parent: Record, database: Database, visited: Set<string> = new Set()): Record[] {
655
+ visited.add(parent.getId().getValue())
656
+
667
657
  const descendantRelationships = Object.entries(this.relationships[parent.getTable()] ?? {}).filter(
668
658
  ([, { descendant }]) => descendant
669
659
  )
670
-
671
660
  const descendants: Record[] = []
672
661
  const push = (children: Record | Record[] | undefined) => {
673
662
  for (const child of [children].flat()) {
674
- if (child && parent !== child) {
663
+ if (child && !visited.has(child.getId().getValue())) {
664
+ visited.add(child.getId().getValue())
675
665
  descendants.push(child)
676
- descendants.push(...this.getDescendants(child, database))
666
+ descendants.push(...this.traverseDescendants(child, database, visited))
677
667
  }
678
668
  }
679
669
  }
@@ -689,8 +679,6 @@ export class Plugin {
689
679
  } else {
690
680
  push(database.query(childTable, { [via]: parent.toString().getValue() }))
691
681
  }
692
- } else if (typeof via === 'function') {
693
- push(database.query(childTable).filter((child) => via(parent, child)))
694
682
  } else if (Array.isArray(via)) {
695
683
  push(
696
684
  database.query(childTable).filter((child) => {
@@ -699,16 +687,26 @@ export class Plugin {
699
687
  parent: [pCol, pReg],
700
688
  child: [cCol, cReg],
701
689
  } = entry
702
- const parentValue = parent
703
- .get(pCol)
704
- .ifString()
705
- ?.asString(`Expected string value for column ${pCol} in regex-based via relationship`)
706
- .getValue()
707
- const childValue = child
708
- .get(cCol)
709
- .ifString()
710
- ?.asString(`Expected string value for column ${cCol} in regex-based via relationship`)
711
- .getValue()
690
+ const parentValue =
691
+ pCol === 'sys_id'
692
+ ? parent.getId().getValue()
693
+ : parent
694
+ .get(pCol)
695
+ .ifString()
696
+ ?.asString(
697
+ `Expected string value for column ${pCol} in regex-based via relationship`
698
+ )
699
+ .getValue()
700
+ const childValue =
701
+ cCol === 'sys_id'
702
+ ? child.getId().getValue()
703
+ : child
704
+ .get(cCol)
705
+ .ifString()
706
+ ?.asString(
707
+ `Expected string value for column ${cCol} in regex-based via relationship`
708
+ )
709
+ .getValue()
712
710
  if (!parentValue || !childValue) {
713
711
  return false
714
712
  }
@@ -959,6 +957,8 @@ export class Plugin {
959
957
  }): Promise<Result<Shape[]>> {
960
958
  let success = false
961
959
  const shapes: Shape[] = []
960
+ const unhandledRecords: Record[] = []
961
+ const unhandledRecordIds: Set<string> = new Set()
962
962
 
963
963
  for (const [table, { toShape }] of Object.entries(this.config.records ?? {})) {
964
964
  if (!toShape) {
@@ -1013,17 +1013,19 @@ export class Plugin {
1013
1013
  descendants: nonDeletedDescendants,
1014
1014
  })
1015
1015
 
1016
- // Track unhandled records to avoid marking them as handled
1017
- const unhandledRecords: Record[] = []
1018
- const unhandledRecordIds: string[] = []
1019
-
1020
1016
  if (result.success === 'partial') {
1021
1017
  for (const unhandledRecord of result.unhandledRecords) {
1022
1018
  unhandledRecords.push(unhandledRecord)
1023
- unhandledRecordIds.push(unhandledRecord.getId().getValue())
1019
+ unhandledRecordIds.add(unhandledRecord.getId().getValue())
1024
1020
  }
1025
1021
  }
1026
1022
 
1023
+ if (result.success !== false && result.reclaimedRecords && result.reclaimedRecords.length > 0) {
1024
+ // These records may have been deferred by the plugin in the past, but now they are handled.
1025
+ // The plugin is choosing to remove the old representation of these records
1026
+ shapes.push(...[result.reclaimedRecords].flat())
1027
+ }
1028
+
1027
1029
  if (result.success) {
1028
1030
  success = true
1029
1031
  handledRecords.insert(record)
@@ -1031,21 +1033,32 @@ export class Plugin {
1031
1033
 
1032
1034
  // Mark all descendants as handled
1033
1035
  for (const descendant of [...deletedDescendants, ...nonDeletedDescendants]) {
1034
- if (!unhandledRecordIds.includes(descendant.getId().getValue())) {
1035
- handledRecords.insert(descendant)
1036
+ if (!unhandledRecordIds.has(descendant.getId().getValue())) {
1037
+ // This descendent was known to the plugin because it is not deleted, and handling was deferred
1038
+ // delete_multiple should also be marked 'handled'
1039
+ if (descendant.getAction() !== 'DELETE') {
1040
+ handledRecords.insert(descendant)
1041
+ } else if (
1042
+ !descendant.getCreator() ||
1043
+ !record.getCreator() ||
1044
+ descendant.getCreator()!.getName() === record.getCreator()!.getName()
1045
+ ) {
1046
+ // This descendant is handled by the same plugin that handled the root record
1047
+ handledRecords.insert(descendant)
1048
+ }
1036
1049
  }
1037
1050
  }
1038
- context.logger.debug(
1039
- `Plugin ${this.getName()} deferred handling of records: ${unhandledRecords.map((r) => r.getTable() + '_' + r.getId().getValue()).join(', ')}`
1040
- )
1041
-
1042
- if (unhandledRecords.length > 0) {
1043
- return { success: 'partial', value: shapes, unhandledRecords }
1044
- }
1045
1051
  }
1046
1052
  }
1047
1053
  }
1048
1054
 
1055
+ if (unhandledRecords.length > 0) {
1056
+ context.logger.debug(
1057
+ `Plugin ${this.getName()} deferred handling of records: ${unhandledRecords.map((r) => r.getTable() + '_' + r.getId().getValue()).join(', ')}`
1058
+ )
1059
+ return { success: 'partial', value: shapes, unhandledRecords }
1060
+ }
1061
+
1049
1062
  return { success, value: shapes }
1050
1063
  }
1051
1064
 
@@ -1284,6 +1297,13 @@ export class Plugins {
1284
1297
  return this.plugins
1285
1298
  }
1286
1299
 
1300
+ remove(name: string): void {
1301
+ const index = this.plugins.findIndex((plugin) => plugin.getName() === name)
1302
+ if (index !== -1) {
1303
+ this.plugins.splice(index, 1)
1304
+ }
1305
+ }
1306
+
1287
1307
  prepend(...plugins: Plugin[]): void {
1288
1308
  this.plugins.unshift(...plugins)
1289
1309
  }
@@ -1343,13 +1363,16 @@ export class Plugins {
1343
1363
  return false
1344
1364
  }
1345
1365
 
1346
- getDocsMetadata(): DocsManifest {
1347
- return this.plugins.reduce((aggregated, plugin) => {
1348
- const pluginManifest = plugin.getDocsMetadata()
1349
- if (pluginManifest) {
1350
- Object.assign(aggregated, pluginManifest)
1351
- }
1352
- return aggregated
1353
- }, {} as DocsManifest)
1366
+ getDocsMetadata(): { [apiName: string]: { tags: string[] } } {
1367
+ return this.plugins.reduce(
1368
+ (aggregated, plugin) => {
1369
+ const pluginManifest = plugin.getDocsMetadata()
1370
+ if (pluginManifest) {
1371
+ Object.assign(aggregated, pluginManifest)
1372
+ }
1373
+ return aggregated
1374
+ },
1375
+ {} as { [apiName: string]: { tags: string[] } }
1376
+ )
1354
1377
  }
1355
1378
  }
@@ -0,0 +1,34 @@
1
+ import type { Logger } from '../logger'
2
+
3
+ export type PostInstallTask = {
4
+ /** Unique name for this task, used in logs and telemetry */
5
+ name: string
6
+
7
+ /** CLI flag name to skip this task (e.g., 'skipFlowActivation' maps to --skip-flow-activation) */
8
+ skipFlag: string
9
+
10
+ /** Human-readable description for the --skip flag help text */
11
+ skipFlagDescription: string
12
+
13
+ /**
14
+ * Controls which project type triggers this task.
15
+ * - `'app'` — only runs after installing a scoped app.
16
+ * - `'configuration'` — only runs after installing a configuration project.
17
+ * - `'both'` — runs after either install path.
18
+ */
19
+ runFor: 'app' | 'configuration' | 'both'
20
+
21
+ /** Execute the post-install task */
22
+ run(context: PostInstallContext): Promise<void>
23
+ }
24
+
25
+ /** Minimal interface for making authenticated requests to a ServiceNow instance */
26
+ export type InstanceClient = {
27
+ fetch(path: string, init?: RequestInit, params?: URLSearchParams): Promise<Response>
28
+ }
29
+
30
+ export type PostInstallContext = {
31
+ instanceClient?: InstanceClient
32
+ logger: Logger
33
+ config: { scopeId: string; scope: string; [key: string]: unknown }
34
+ }
@@ -1170,7 +1170,10 @@ export class ObjectShape extends Shape<globalThis.Record<string, unknown>> {
1170
1170
  }
1171
1171
 
1172
1172
  static quotePropertyNameIfNeeded(name: string): string {
1173
- return name === '' || /[^\w$]/.test(name) ? `'${name}'` : name
1173
+ // Valid JS identifier (e.g. short_description, $id) no quotes needed
1174
+ // Pure numeric literal without leading zeros (e.g. 0, 100, 1.5) — valid as object key without quotes
1175
+ // Everything else (empty string, hyphens, digit-leading non-numeric chars, octal-like 007) — must be quoted
1176
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) || /^(0|[1-9]\d*)(\.\d+)?$/.test(name) ? name : `'${name}'`
1174
1177
  }
1175
1178
  }
1176
1179
 
package/src/taxonomy.ts CHANGED
@@ -103,9 +103,51 @@ const TableNames = {
103
103
  SYS_SG_PARAMETER_SCREEN: 'sys_sg_parameter_screen',
104
104
  SYS_SG_APPLET_LAUNCHER: 'sys_sg_applet_launcher',
105
105
 
106
+ // AIAF tables
107
+ SN_AI_AGENT: 'sn_aia_agent',
108
+ SN_AIA_LTM_CATEGORY_MAPPING: 'sn_aia_ltm_category_mapping',
109
+ SN_AI_AGENT_CONFIG: 'sn_aia_agent_config',
110
+ SN_AI_AGENT_VERSION: 'sn_aia_version',
111
+ SN_AI_AGENT_TOOL: 'sn_aia_tool',
112
+ SN_AI_AGENT_TOOL_M2M: 'sn_aia_agent_tool_m2m',
113
+ SYS_AGENT_ACCESS_ROLE_CONFIG: 'sys_agent_access_role_configuration',
114
+ SN_AIA_TRIGGER_CONFIG: 'sn_aia_trigger_configuration',
115
+ SN_AIA_TRIGGER_MAPPING: 'sn_aia_trigger_agent_usecase_m2m',
116
+ SN_AIA_USECASE: 'sn_aia_usecase',
117
+ SN_AIA_TEAM_MEMBER: 'sn_aia_team_member',
118
+ SN_AIA_TEAM: 'sn_aia_team',
119
+ SN_AIA_USECASE_CONFIG: 'sn_aia_usecase_config_override',
120
+
106
121
  // Natural Language Understanding tables
107
122
  SYS_NLU_MODEL: 'sys_nlu_model',
108
123
 
124
+ // Now Assist tables
125
+ SN_NOWASSIST_SKILL_CONFIG: 'sn_nowassist_skill_config',
126
+ SN_NOWASSIST_SKILL_CONFIG_STATUS: 'sn_nowassist_skill_config_status',
127
+ SN_NOWASSIST_SKILL_CONFIG_VAR_SET: 'sn_nowassist_skill_config_var_set',
128
+ SN_NOWASSIST_SKILL_CONFIG_APPLICABILITY: 'sn_nowassist_skill_config_applicability',
129
+ SN_NOWASSIST_SKILL_CONFIG_UI: 'sn_ns_skill_config_ui',
130
+ SYS_GENERATIVE_AI_CONFIG: 'sys_generative_ai_config',
131
+ SYS_GENERATIVE_AI_PROMPT_CONFIG: 'sys_generative_ai_prompt_config',
132
+ SYS_GENERATIVE_AI_VALIDATOR: 'sys_generative_ai_validator',
133
+ SYS_GENERATIVE_AI_REQUEST_VALIDATOR: 'sys_generative_ai_request_validator',
134
+ SYS_GENERATIVE_AI_RESPONSE_VALIDATOR: 'sys_generative_ai_response_validator',
135
+ SYS_GEN_AI_SKILL: 'sys_gen_ai_skill',
136
+ SYS_GEN_AI_SKILL_APPLICABILITY: 'sys_gen_ai_skill_applicability',
137
+ SYS_GEN_AI_SKILL_CONFIG: 'sys_gen_ai_skill_config',
138
+ SYS_GEN_AI_FEATURE_MAPPING: 'sys_gen_ai_feature_mapping',
139
+ SYS_GEN_AI_STRATEGY_MAPPING: 'sys_gen_ai_strategy_mapping',
140
+ SYS_ONE_EXTEND_CAPABILITY: 'sys_one_extend_capability',
141
+ SYS_ONE_EXTEND_CAPABILITY_DEFINITION: 'sys_one_extend_capability_definition',
142
+ SYS_ONE_EXTEND_DEFINITION_ATTRIBUTE: 'sys_one_extend_definition_attribute',
143
+ SYS_ONE_EXTEND_DEFINITION_CONFIG: 'sys_one_extend_definition_config',
144
+ SYS_ONE_EXTEND_RESOURCE_EDGE: 'sys_one_extend_resource_edge',
145
+ SYS_ONE_EXTEND_RESOURCE_MAPPING: 'sys_one_extend_resource_mapping',
146
+ SYS_ONE_EXTEND_RESOURCE_ATTRIBUTE_MAPPING: 'sys_one_extend_resource_attribute_mapping',
147
+ SYS_ONE_EXTEND_RESOURCE_PARAM_VALUE: 'sys_one_extend_resource_param_value',
148
+ SYS_ONE_EXTEND_EVAL_STRATEGY: 'sys_one_extend_eval_strategy',
149
+ SYS_ONE_EXTEND_EVAL_STRATEGY_METRIC: 'sys_one_extend_eval_strategy_metric',
150
+ SYS_ONE_EXTEND_EVAL_ATTRIBUTE: 'sys_one_extend_eval_attribute',
109
151
  // Properties tables
110
152
  SYS_PROPERTIES: 'sys_properties',
111
153
  SYS_PROPERTIES_CATEGORY: 'sys_properties_category',
@@ -295,6 +337,17 @@ const taxonomyMapping = z
295
337
  // Natural Language Understanding tables
296
338
  [TableNames.SYS_NLU_MODEL]: taxonomyItem.default('natural-language-understanding/nlu-model'),
297
339
 
340
+ // Now Assist - Skill Config
341
+ [TableNames.SN_NOWASSIST_SKILL_CONFIG]: taxonomyItem.default('now-assist/skill'),
342
+ [TableNames.SN_NOWASSIST_SKILL_CONFIG_STATUS]: taxonomyItem.default('now-assist/skill-config'),
343
+ [TableNames.SN_NOWASSIST_SKILL_CONFIG_VAR_SET]: taxonomyItem.default('now-assist/skill-config'),
344
+ [TableNames.SN_NOWASSIST_SKILL_CONFIG_APPLICABILITY]: taxonomyItem.default('now-assist/skill-config'),
345
+ [TableNames.SN_NOWASSIST_SKILL_CONFIG_UI]: taxonomyItem.default('now-assist/skill-config'),
346
+
347
+ // AI Agent and Agentic Workflow
348
+ [TableNames.SN_AI_AGENT]: taxonomyItem.default('ai-agent/agent'),
349
+ [TableNames.SN_AIA_USECASE]: taxonomyItem.default('ai-agent/agentic-workflow'),
350
+
298
351
  // Properties tables
299
352
  [TableNames.SYS_PROPERTIES]: taxonomyItem.default('properties/system-property'),
300
353
  [TableNames.SYS_PROPERTIES_CATEGORY]: taxonomyItem.default('properties/system-property-category'),