@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.
- package/dist/compression.d.ts +2 -1
- package/dist/compression.js +5 -2
- package/dist/compression.js.map +1 -1
- package/dist/keys-registry.d.ts +2 -1
- package/dist/keys-registry.js +15 -15
- package/dist/keys-registry.js.map +1 -1
- package/dist/now-config.d.ts +476 -5
- package/dist/now-config.js +165 -1
- package/dist/now-config.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/plugin.d.ts +17 -18
- package/dist/plugins/plugin.js +52 -31
- package/dist/plugins/plugin.js.map +1 -1
- package/dist/plugins/post-install.d.ts +31 -0
- package/dist/plugins/post-install.js +3 -0
- package/dist/plugins/post-install.js.map +1 -0
- package/dist/plugins/shape.js +4 -1
- package/dist/plugins/shape.js.map +1 -1
- package/dist/taxonomy.js +50 -0
- package/dist/taxonomy.js.map +1 -1
- package/dist/telemetry/clients/abstract-client.d.ts +23 -2
- package/dist/telemetry/clients/abstract-client.js +80 -0
- package/dist/telemetry/clients/abstract-client.js.map +1 -1
- package/dist/telemetry/clients/browser-client.d.ts +15 -9
- package/dist/telemetry/clients/browser-client.js +8 -82
- package/dist/telemetry/clients/browser-client.js.map +1 -1
- package/dist/telemetry/clients/node-client.d.ts +28 -6
- package/dist/telemetry/clients/node-client.js +37 -101
- package/dist/telemetry/clients/node-client.js.map +1 -1
- package/dist/telemetry/factory.js +4 -6
- package/dist/telemetry/factory.js.map +1 -1
- package/dist/telemetry/types.d.ts +1 -1
- package/dist/typescript.d.ts +8 -0
- package/dist/typescript.js +12 -1
- package/dist/typescript.js.map +1 -1
- package/dist/xml.js +3 -1
- package/dist/xml.js.map +1 -1
- package/now.config.schema.json +386 -2
- package/package.json +5 -6
- package/src/compression.ts +7 -2
- package/src/keys-registry.ts +14 -12
- package/src/now-config.ts +204 -1
- package/src/plugins/index.ts +1 -0
- package/src/plugins/plugin.ts +86 -63
- package/src/plugins/post-install.ts +34 -0
- package/src/plugins/shape.ts +4 -1
- package/src/taxonomy.ts +53 -0
- package/src/telemetry/clients/abstract-client.ts +104 -3
- package/src/telemetry/clients/browser-client.ts +12 -95
- package/src/telemetry/clients/node-client.ts +39 -114
- package/src/telemetry/factory.ts +5 -8
- package/src/telemetry/types.ts +2 -1
- package/src/typescript.ts +12 -1
- 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
|
-
|
|
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) {
|
package/src/plugins/index.ts
CHANGED
package/src/plugins/plugin.ts
CHANGED
|
@@ -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
|
|
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():
|
|
636
|
+
getDocsMetadata(): { [apiName: string]: { tags: string[] } } | undefined {
|
|
650
637
|
if (!this.config.docs) {
|
|
651
638
|
return undefined
|
|
652
639
|
}
|
|
653
640
|
|
|
654
|
-
const manifest:
|
|
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 &&
|
|
663
|
+
if (child && !visited.has(child.getId().getValue())) {
|
|
664
|
+
visited.add(child.getId().getValue())
|
|
675
665
|
descendants.push(child)
|
|
676
|
-
descendants.push(...this.
|
|
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 =
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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.
|
|
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.
|
|
1035
|
-
|
|
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():
|
|
1347
|
-
return this.plugins.reduce(
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
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
|
+
}
|
package/src/plugins/shape.ts
CHANGED
|
@@ -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
|
-
|
|
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'),
|