@servicenow/sdk-build-plugins 4.3.0 → 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acl-plugin.js +2 -0
- package/dist/acl-plugin.js.map +1 -1
- package/dist/column/column-to-record.d.ts +10 -3
- package/dist/column/column-to-record.js +44 -7
- package/dist/column/column-to-record.js.map +1 -1
- package/dist/column-plugin.d.ts +3 -1
- package/dist/column-plugin.js +11 -11
- package/dist/column-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-instance-plugin.js +285 -10
- package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-trigger-instance-plugin.js +21 -7
- package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -1
- package/dist/flow/utils/flow-constants.d.ts +7 -0
- package/dist/flow/utils/flow-constants.js +12 -5
- package/dist/flow/utils/flow-constants.js.map +1 -1
- package/dist/flow/utils/service-catalog.d.ts +47 -0
- package/dist/flow/utils/service-catalog.js +137 -0
- package/dist/flow/utils/service-catalog.js.map +1 -0
- package/dist/index.js.map +1 -1
- package/dist/now-attach-plugin.js +7 -10
- package/dist/now-attach-plugin.js.map +1 -1
- package/dist/now-ref-plugin.js +1 -1
- package/dist/now-ref-plugin.js.map +1 -1
- package/dist/server-module-plugin/index.d.ts +10 -0
- package/dist/server-module-plugin/index.js +45 -55
- package/dist/server-module-plugin/index.js.map +1 -1
- package/dist/service-catalog/sc-record-producer-plugin.js +1 -0
- package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -1
- package/dist/service-catalog/service-catalog-base.js +2 -2
- package/dist/service-catalog/service-catalog-base.js.map +1 -1
- package/dist/service-catalog/service-catalog-diagnostics.js +4 -1
- package/dist/service-catalog/service-catalog-diagnostics.js.map +1 -1
- package/dist/service-catalog/shape-to-record.d.ts +1 -0
- package/dist/service-catalog/shape-to-record.js +4 -1
- package/dist/service-catalog/shape-to-record.js.map +1 -1
- package/dist/service-catalog/utils.d.ts +10 -0
- package/dist/service-catalog/utils.js +72 -0
- package/dist/service-catalog/utils.js.map +1 -1
- package/dist/static-content-plugin.js +25 -2
- package/dist/static-content-plugin.js.map +1 -1
- package/dist/table-plugin.js +16 -13
- package/dist/table-plugin.js.map +1 -1
- package/dist/ui-page-plugin.js +832 -19
- package/dist/ui-page-plugin.js.map +1 -1
- package/package.json +5 -5
- package/src/acl-plugin.ts +2 -0
- package/src/column/column-to-record.ts +54 -8
- package/src/column-plugin.ts +28 -12
- package/src/flow/plugins/flow-instance-plugin.ts +364 -13
- package/src/flow/plugins/flow-trigger-instance-plugin.ts +25 -7
- package/src/flow/utils/flow-constants.ts +13 -4
- package/src/flow/utils/service-catalog.ts +174 -0
- package/src/index.ts +0 -1
- package/src/now-attach-plugin.ts +10 -11
- package/src/now-ref-plugin.ts +1 -1
- package/src/server-module-plugin/index.ts +59 -69
- package/src/service-catalog/sc-record-producer-plugin.ts +1 -1
- package/src/service-catalog/service-catalog-base.ts +2 -2
- package/src/service-catalog/service-catalog-diagnostics.ts +4 -1
- package/src/service-catalog/shape-to-record.ts +6 -2
- package/src/service-catalog/utils.ts +93 -0
- package/src/static-content-plugin.ts +25 -2
- package/src/table-plugin.ts +30 -14
- package/src/ui-page-plugin.ts +1063 -20
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { Database, Record, Shape, Source } from '@servicenow/sdk-build-core'
|
|
2
|
+
import {
|
|
3
|
+
ArrayShape,
|
|
4
|
+
IdentifierShape,
|
|
5
|
+
PropertyAccessShape,
|
|
6
|
+
TemplateExpressionShape,
|
|
7
|
+
TemplateSpanShape,
|
|
8
|
+
isGUID,
|
|
9
|
+
} from '@servicenow/sdk-build-core'
|
|
10
|
+
import { getIdentifierFromRecord } from './utils'
|
|
11
|
+
import {
|
|
12
|
+
CATALOG_VARIABLE_ACTIONS,
|
|
13
|
+
CORE_ACTIONS_SYS_ID_NAME_MAP,
|
|
14
|
+
CATALOG_ITEM_TABLE,
|
|
15
|
+
CATALOG_VARIABLE_TABLE,
|
|
16
|
+
} from './flow-constants'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Service Catalog Utility Functions
|
|
20
|
+
*
|
|
21
|
+
* This module contains utilities for transforming catalog item and catalog variable
|
|
22
|
+
* data between XML and Fluent formats, including:
|
|
23
|
+
* - Checking if an action is catalog-related
|
|
24
|
+
* - Finding catalog items by sys_id
|
|
25
|
+
* - Creating template expressions for catalog item references
|
|
26
|
+
* - Converting slushbucket format to catalog variable arrays
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if an action definition is a catalog-related action (getCatalogVariables or createCatalogTask).
|
|
31
|
+
* @param actionDefinition - The action definition shape
|
|
32
|
+
* @returns true if the action is getCatalogVariables or createCatalogTask, false otherwise
|
|
33
|
+
*/
|
|
34
|
+
export function isCatalogAction(actionDefinition: Shape | undefined): boolean {
|
|
35
|
+
const actionName = CORE_ACTIONS_SYS_ID_NAME_MAP[actionDefinition?.getValue() as string]
|
|
36
|
+
return actionName ? CATALOG_VARIABLE_ACTIONS.includes(actionName) : false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Helper function to find catalog item record by sys_id from database.
|
|
41
|
+
* @param sysId - The sys_id of the catalog item (must be 32 characters)
|
|
42
|
+
* @param database - Database instance to query
|
|
43
|
+
* @returns Catalog item record or undefined if not found
|
|
44
|
+
*/
|
|
45
|
+
export function findCatalogItemBySysId(sysId: string | undefined, database: Database | undefined): Record | undefined {
|
|
46
|
+
if (!database || !sysId || !isGUID(sysId)) {
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
return database.get(CATALOG_ITEM_TABLE, sysId)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Helper function to create a template expression from an identifier.
|
|
54
|
+
* Creates: `${identifierName}`
|
|
55
|
+
*/
|
|
56
|
+
export function createTemplateExpressionFromIdentifier(
|
|
57
|
+
identifierName: string,
|
|
58
|
+
source: Source
|
|
59
|
+
): TemplateExpressionShape {
|
|
60
|
+
return new TemplateExpressionShape({
|
|
61
|
+
source,
|
|
62
|
+
literalText: '',
|
|
63
|
+
spans: [
|
|
64
|
+
new TemplateSpanShape({
|
|
65
|
+
source,
|
|
66
|
+
expression: new IdentifierShape({ source, name: identifierName }),
|
|
67
|
+
literalText: '',
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Helper function to create a PropertyAccessShape for a catalog variable reference.
|
|
75
|
+
* Creates: catalogItemIdentifierName.variables.varName
|
|
76
|
+
*/
|
|
77
|
+
export function createCatalogVariablePropertyAccess(
|
|
78
|
+
catalogItemRecord: Record,
|
|
79
|
+
catalogItemIdentifierName: string,
|
|
80
|
+
varName: string,
|
|
81
|
+
source: Source
|
|
82
|
+
): PropertyAccessShape {
|
|
83
|
+
const catalogItemIdentifier = new IdentifierShape({
|
|
84
|
+
source: catalogItemRecord.getOriginalNode() || source,
|
|
85
|
+
name: catalogItemIdentifierName,
|
|
86
|
+
value: catalogItemRecord,
|
|
87
|
+
})
|
|
88
|
+
const variablesIdentifier = new IdentifierShape({
|
|
89
|
+
source,
|
|
90
|
+
name: 'variables',
|
|
91
|
+
})
|
|
92
|
+
const varNameIdentifier = new IdentifierShape({
|
|
93
|
+
source,
|
|
94
|
+
name: varName,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return new PropertyAccessShape({
|
|
98
|
+
source,
|
|
99
|
+
elements: [catalogItemIdentifier, variablesIdentifier, varNameIdentifier],
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Convert slushbucket format (comma-separated sys_id:name pairs) to an array of catalog variable references.
|
|
105
|
+
* Example: "abc123:email,def456:html" → [catalogItem.variables.email, catalogItem.variables.html]
|
|
106
|
+
*
|
|
107
|
+
* @param slushBucketValue - Slushbucket string from XML
|
|
108
|
+
* @param catalogItemRecord - Catalog item record to look up variables
|
|
109
|
+
* @param source - Source for creating shapes
|
|
110
|
+
* @param actionInstanceUUID - UUID of the action instance (for label_cache lookup)
|
|
111
|
+
* @param flowRecord - Flow record containing label_cache
|
|
112
|
+
* @returns ArrayShape of PropertyAccessShape elements or undefined if conversion fails
|
|
113
|
+
*/
|
|
114
|
+
export function convertSlushBucketToCatalogVariableArray(
|
|
115
|
+
slushBucketValue: string,
|
|
116
|
+
catalogItemRecord: Record | undefined,
|
|
117
|
+
source: Source
|
|
118
|
+
): ArrayShape | undefined {
|
|
119
|
+
if (!catalogItemRecord) {
|
|
120
|
+
return undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const variableShapes: PropertyAccessShape[] = []
|
|
124
|
+
const catalogItemIdentifierName = getIdentifierFromRecord(catalogItemRecord) || 'catalogItem'
|
|
125
|
+
|
|
126
|
+
//use slushbucket sys_ids to look up variable names
|
|
127
|
+
if (!slushBucketValue || !slushBucketValue.trim()) {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const entries = slushBucketValue
|
|
132
|
+
.split(',')
|
|
133
|
+
.map((entry) => entry.trim())
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
|
|
136
|
+
// Build a map of sysId → record by iterating allRecords once
|
|
137
|
+
const variableRecordBySysId = new Map<string, Record>()
|
|
138
|
+
for (const rec of catalogItemRecord.flat()) {
|
|
139
|
+
if (rec.getTable() === CATALOG_VARIABLE_TABLE) {
|
|
140
|
+
variableRecordBySysId.set(rec.getId().getValue(), rec)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const [sysId] = entry.split(':')
|
|
146
|
+
if (!sysId || !sysId.trim()) {
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
const variableRecord = variableRecordBySysId.get(sysId)
|
|
150
|
+
|
|
151
|
+
if (variableRecord) {
|
|
152
|
+
const varName = variableRecord.get('name')?.asString()?.getValue()
|
|
153
|
+
if (varName) {
|
|
154
|
+
// Create PropertyAccessShape: catalogItemIdentifierName.variables.varName
|
|
155
|
+
const propertyAccess = createCatalogVariablePropertyAccess(
|
|
156
|
+
catalogItemRecord,
|
|
157
|
+
catalogItemIdentifierName,
|
|
158
|
+
varName,
|
|
159
|
+
source
|
|
160
|
+
)
|
|
161
|
+
variableShapes.push(propertyAccess)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (variableShapes.length === 0) {
|
|
167
|
+
return undefined
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return new ArrayShape({
|
|
171
|
+
source,
|
|
172
|
+
elements: variableShapes,
|
|
173
|
+
})
|
|
174
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -58,7 +58,6 @@ export * from './ux-list-menu-config-plugin'
|
|
|
58
58
|
export * from './workspace-plugin'
|
|
59
59
|
export * from './dashboard/dashboard-plugin'
|
|
60
60
|
export * from './applicability-plugin'
|
|
61
|
-
|
|
62
61
|
// non-plugins
|
|
63
62
|
export * from './atf/step-configs'
|
|
64
63
|
export { REPACK_OUTPUT_DIR, checkModuleExists, resolveModule } from './repack'
|
package/src/now-attach-plugin.ts
CHANGED
|
@@ -214,17 +214,23 @@ export const NowAttachPlugin = Plugin.create({
|
|
|
214
214
|
records: {
|
|
215
215
|
'*': {
|
|
216
216
|
async toFile(record, { config, database, factory, transform }) {
|
|
217
|
+
const entries = record.entries()
|
|
218
|
+
const hasAttachments =
|
|
219
|
+
entries.some(([, shape]) => isLazyValue(shape)) ||
|
|
220
|
+
Object.keys(record.properties()).some((field) => hasAssociatedRecords(record.get(field)))
|
|
221
|
+
|
|
222
|
+
if (!hasAttachments) {
|
|
223
|
+
return { success: false }
|
|
224
|
+
}
|
|
225
|
+
|
|
217
226
|
const recordBuilder = unloadBuilder(config)
|
|
218
227
|
const updateName = await transform.getUpdateName(record)
|
|
219
228
|
const builder = recordBuilder.record(record, updateName)
|
|
220
|
-
let attachmentsProcessed = false
|
|
221
229
|
|
|
222
|
-
|
|
223
|
-
.entries()
|
|
230
|
+
entries
|
|
224
231
|
.sort(([a], [b]) => a.localeCompare(b)) // Sort keys to make outputs more deterministic
|
|
225
232
|
.forEach(([prop, shape]) => {
|
|
226
233
|
if (isLazyValue(shape)) {
|
|
227
|
-
attachmentsProcessed = true
|
|
228
234
|
builder.field(prop, shape.evaluate(record, prop))
|
|
229
235
|
} else {
|
|
230
236
|
builder.field(prop, shape)
|
|
@@ -234,7 +240,6 @@ export const NowAttachPlugin = Plugin.create({
|
|
|
234
240
|
for (const field in record.properties()) {
|
|
235
241
|
const shape = record.get(field)
|
|
236
242
|
if (hasAssociatedRecords(shape)) {
|
|
237
|
-
attachmentsProcessed = true
|
|
238
243
|
for (const rec of await shape.createAssociatedRecords({
|
|
239
244
|
parentRecord: record,
|
|
240
245
|
factory,
|
|
@@ -249,12 +254,6 @@ export const NowAttachPlugin = Plugin.create({
|
|
|
249
254
|
}
|
|
250
255
|
}
|
|
251
256
|
|
|
252
|
-
if (!attachmentsProcessed) {
|
|
253
|
-
return {
|
|
254
|
-
success: false,
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
257
|
const claims = database
|
|
259
258
|
.query('sys_claim')
|
|
260
259
|
.filter((claim) => claim.get('metadata_update_name').equals(updateName))
|
package/src/now-ref-plugin.ts
CHANGED
|
@@ -24,7 +24,7 @@ export const NowRefPlugin = Plugin.create({
|
|
|
24
24
|
.getArgument(1)
|
|
25
25
|
.as(
|
|
26
26
|
[StringShape, ObjectShape],
|
|
27
|
-
'The second argument to Now.ref() must be a GUID or a coalesce keys object'
|
|
27
|
+
'The second argument to Now.ref() must be a GUID, a Now ID string, or a coalesce keys object'
|
|
28
28
|
)
|
|
29
29
|
const maybeKeys = callExpression.hasArgument(2)
|
|
30
30
|
? callExpression
|
|
@@ -19,7 +19,6 @@ import { SBOMBuilder } from './sbom-builder'
|
|
|
19
19
|
import isEqual from 'lodash/isEqual'
|
|
20
20
|
import { INVALID_XML_CHARACTERS, applyPathMappings } from '../utils'
|
|
21
21
|
import zip from 'lodash/zip'
|
|
22
|
-
import type { DependencyNode } from '@servicenow/sdk-repack'
|
|
23
22
|
|
|
24
23
|
const GLUE_CODE_PREFIX = '// @fluent-module'
|
|
25
24
|
const GLUE_CODE_META_REGEX = new RegExp(`^${GLUE_CODE_PREFIX} (.*);(true|false);(.*)`) // name;isDefault;path
|
|
@@ -32,7 +31,7 @@ const GLUE_CODE_WARNING = `
|
|
|
32
31
|
// SDK from regenerating it. However, you will then be responsible for the
|
|
33
32
|
// management of this code in your Fluent file.`
|
|
34
33
|
|
|
35
|
-
const NODE_MODULES = 'node_modules'
|
|
34
|
+
export const NODE_MODULES = 'node_modules'
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
37
|
* Check if a module matches any trusted module patterns.
|
|
@@ -250,41 +249,7 @@ function isValidRequireCall(callExpression: ts.CallExpression, requirePath: ts.S
|
|
|
250
249
|
return isRequire && !isRelativePath
|
|
251
250
|
}
|
|
252
251
|
|
|
253
|
-
|
|
254
|
-
const importerMap: { [key: string]: string[] } = {}
|
|
255
|
-
for (const node of dependencyNodes) {
|
|
256
|
-
let parent = node.parentPackage
|
|
257
|
-
|
|
258
|
-
while (parent) {
|
|
259
|
-
const importerMapKey = `${node.pkgName}@${node.updatedManifest.version}`
|
|
260
|
-
if (!importerMap[importerMapKey]) {
|
|
261
|
-
importerMap[importerMapKey] = []
|
|
262
|
-
}
|
|
263
|
-
if (parent.pkgName) {
|
|
264
|
-
importerMap[importerMapKey].push(parent.pkgName)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const newParent = dependencyNodes.find((potentialParent) => {
|
|
268
|
-
if (!parent || !parent.pkgName) {
|
|
269
|
-
return false
|
|
270
|
-
}
|
|
271
|
-
if (potentialParent.updatedManifest.dependencies[parent!.pkgName]) {
|
|
272
|
-
return potentialParent
|
|
273
|
-
}
|
|
274
|
-
return false
|
|
275
|
-
})
|
|
276
|
-
parent = newParent
|
|
277
|
-
? {
|
|
278
|
-
pkgName: newParent.updatedManifest.name,
|
|
279
|
-
version: newParent.updatedManifest.version,
|
|
280
|
-
}
|
|
281
|
-
: undefined
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return importerMap
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
class ModuleDependencyShape extends Shape {
|
|
252
|
+
export class ModuleDependencyShape extends Shape {
|
|
288
253
|
private readonly moduleName: string
|
|
289
254
|
|
|
290
255
|
constructor({
|
|
@@ -369,34 +334,16 @@ function generateSBOMContent(context: RecordContext) {
|
|
|
369
334
|
|
|
370
335
|
function getModuleDependencyPath(
|
|
371
336
|
config: NowConfig,
|
|
372
|
-
module: { name: string; file: string; version: string; packageJson: Package;
|
|
337
|
+
module: { name: string; file: string; version: string; packageJson: Package; modulePath?: string }
|
|
373
338
|
) {
|
|
374
|
-
const { name, file, packageJson,
|
|
339
|
+
const { name, file, packageJson, modulePath, version } = module
|
|
375
340
|
|
|
376
341
|
if (NowConfig.legacyPackageResolution(config)) {
|
|
377
342
|
return NowConfig.moduleResolutionPath(config, packageJson, true, name, version, file)
|
|
378
343
|
}
|
|
379
344
|
|
|
380
|
-
if (
|
|
381
|
-
return NowConfig.moduleResolutionPath(config, packageJson, true,
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (importerPath && importerPath.length > 0) {
|
|
385
|
-
const pathSegments = [...importerPath].reverse()
|
|
386
|
-
const pathSegmentsWithNodeModules: string[] = []
|
|
387
|
-
pathSegments.forEach((segment) => {
|
|
388
|
-
pathSegmentsWithNodeModules.push(NODE_MODULES)
|
|
389
|
-
pathSegmentsWithNodeModules.push(segment)
|
|
390
|
-
})
|
|
391
|
-
return NowConfig.moduleResolutionPath(
|
|
392
|
-
config,
|
|
393
|
-
packageJson,
|
|
394
|
-
true,
|
|
395
|
-
...pathSegmentsWithNodeModules,
|
|
396
|
-
NODE_MODULES,
|
|
397
|
-
name,
|
|
398
|
-
file
|
|
399
|
-
)
|
|
345
|
+
if (modulePath) {
|
|
346
|
+
return NowConfig.moduleResolutionPath(config, packageJson, true, modulePath, file)
|
|
400
347
|
}
|
|
401
348
|
|
|
402
349
|
return NowConfig.moduleResolutionPath(config, packageJson, true, NODE_MODULES, name, file)
|
|
@@ -511,7 +458,8 @@ export const ServerModulePlugin = Plugin.create({
|
|
|
511
458
|
shape: ModuleDependencyShape,
|
|
512
459
|
fileTypes: ['module'],
|
|
513
460
|
// TODO: When managed cache is provided to plugins, cache dependencies that were already handled to avoid reprocessing
|
|
514
|
-
async toRecord(shape,
|
|
461
|
+
async toRecord(shape, context) {
|
|
462
|
+
const { packageJson, diagnostics, fs, logger, project, factory, config } = context
|
|
515
463
|
if (config.type === 'configuration') {
|
|
516
464
|
throw new Error(`Modules cannot be used in a configuration project`)
|
|
517
465
|
}
|
|
@@ -531,22 +479,24 @@ export const ServerModulePlugin = Plugin.create({
|
|
|
531
479
|
const dependencyNodes = await repack.execute({
|
|
532
480
|
id,
|
|
533
481
|
entry: entry ? [entry] : ['.'],
|
|
482
|
+
legacyPackageResolution: NowConfig.legacyPackageResolution(config),
|
|
534
483
|
})
|
|
535
484
|
if (!dependencyNodes) {
|
|
536
485
|
throw new Error(`Failed to build dependency ${id}`)
|
|
537
486
|
}
|
|
538
487
|
|
|
539
|
-
let importerMap: { [key: string]: string[] } = {}
|
|
540
|
-
if (!config.hoistDependencies) {
|
|
541
|
-
importerMap = buildParentPathMap(dependencyNodes)
|
|
542
|
-
}
|
|
543
|
-
|
|
544
488
|
const modules: { id: string; path: string; content: string }[] = []
|
|
545
489
|
const { Lint } = await import('../repack/lint/index.js')
|
|
546
490
|
for (const node of dependencyNodes) {
|
|
547
|
-
const { packagePath, files, updatedManifest,
|
|
491
|
+
const { packagePath, files, updatedManifest, originalPath } = node
|
|
548
492
|
const { name, version } = updatedManifest
|
|
549
493
|
|
|
494
|
+
const { modulePath, idPath } = buildDependencyPackagePath(
|
|
495
|
+
project.getRootDir(),
|
|
496
|
+
name,
|
|
497
|
+
version,
|
|
498
|
+
originalPath
|
|
499
|
+
)
|
|
550
500
|
for (const file of files) {
|
|
551
501
|
const fileContent = fs.readFileSync(pathModule.join(packagePath, file)).toString('utf-8')
|
|
552
502
|
if (/(.js|.cjs|.mjs)$/.test(pathModule.extname(file))) {
|
|
@@ -557,15 +507,16 @@ export const ServerModulePlugin = Plugin.create({
|
|
|
557
507
|
}
|
|
558
508
|
}
|
|
559
509
|
|
|
560
|
-
const importerPath = importerMap[`${pkgName}@${version}`]
|
|
561
510
|
modules.push({
|
|
562
|
-
id:
|
|
511
|
+
id: NowConfig.legacyPackageResolution(config)
|
|
512
|
+
? `${name}@${version}/${file}`
|
|
513
|
+
: `${idPath}@${version}/${file}`,
|
|
563
514
|
path: getModuleDependencyPath(config, {
|
|
564
515
|
name,
|
|
565
516
|
file,
|
|
566
517
|
version,
|
|
567
518
|
packageJson,
|
|
568
|
-
|
|
519
|
+
modulePath,
|
|
569
520
|
}),
|
|
570
521
|
content: fileContent,
|
|
571
522
|
})
|
|
@@ -635,3 +586,42 @@ export const ServerModulePlugin = Plugin.create({
|
|
|
635
586
|
},
|
|
636
587
|
],
|
|
637
588
|
})
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Builds a relative dependency package path from an absolute path.
|
|
592
|
+
* Handles special cases for monorepo dependencies and pnpm-style paths.
|
|
593
|
+
*
|
|
594
|
+
* @param rootDir - The project root directory
|
|
595
|
+
* @param name - The package name
|
|
596
|
+
* @param version - The package version
|
|
597
|
+
* @param path - The absolute path to the package (optional)
|
|
598
|
+
* @returns Relative path suitable for module resolution
|
|
599
|
+
*/
|
|
600
|
+
function buildDependencyPackagePath(
|
|
601
|
+
rootDir: string,
|
|
602
|
+
name: string,
|
|
603
|
+
version: string,
|
|
604
|
+
path?: string
|
|
605
|
+
): {
|
|
606
|
+
modulePath: string
|
|
607
|
+
idPath: string
|
|
608
|
+
} {
|
|
609
|
+
let relative = pathModule.relative(rootDir, path ?? '')
|
|
610
|
+
const isMonorepoDependency = relative.includes(`../`)
|
|
611
|
+
const isPnpm = relative.includes('.pnpm')
|
|
612
|
+
if (isMonorepoDependency) {
|
|
613
|
+
// Treat monorepo node_modules as if they are inside app node_modules
|
|
614
|
+
relative = relative.replaceAll('../', '').replaceAll('+', '/')
|
|
615
|
+
}
|
|
616
|
+
if (isPnpm) {
|
|
617
|
+
// Do our best to turn this pnpm path into an npm path
|
|
618
|
+
relative = relative.replaceAll('+', '/').replaceAll(`node_modules/.pnpm/${name}@${version}/`, '')
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// IDs will resemble the legacy paths and share sys_ids if possible
|
|
622
|
+
const idPath = relative.replaceAll('node_modules/', '').replaceAll('.pnpm/', '')
|
|
623
|
+
return {
|
|
624
|
+
modulePath: relative,
|
|
625
|
+
idPath,
|
|
626
|
+
}
|
|
627
|
+
}
|
|
@@ -162,7 +162,6 @@ export const CatalogItemRecordProducerPlugin = Plugin.create({
|
|
|
162
162
|
if (!validateVariableNameConflicts(arg, diagnostics, 'RecordProducer')) {
|
|
163
163
|
return { success: false }
|
|
164
164
|
}
|
|
165
|
-
|
|
166
165
|
const m2mRecords = await createSharedM2MRecords(callExpression, arg, recordProducer, factory)
|
|
167
166
|
|
|
168
167
|
if (arg.get('variables').isDefined()) {
|
|
@@ -172,6 +171,7 @@ export const CatalogItemRecordProducerPlugin = Plugin.create({
|
|
|
172
171
|
variablesConfig,
|
|
173
172
|
factory,
|
|
174
173
|
parent: recordProducer,
|
|
174
|
+
parentArg: arg,
|
|
175
175
|
diagnostics,
|
|
176
176
|
})
|
|
177
177
|
|
|
@@ -446,8 +446,8 @@ export function transformCatalogItemSpecificFieldsToRecord(arg: ObjectShape, $:
|
|
|
446
446
|
.toCdata()
|
|
447
447
|
.def(''),
|
|
448
448
|
request_method: $.from('requestMethod')
|
|
449
|
-
.map((v: Shape) => (v.ifString()?.getValue() === '' ?
|
|
450
|
-
.def('
|
|
449
|
+
.map((v: Shape) => (v.ifString()?.getValue() === 'order' ? '' : v.getValue()))
|
|
450
|
+
.def(''),
|
|
451
451
|
no_cart_v2: $.from('hideAddToCart').def(false),
|
|
452
452
|
no_quantity_v2: $.from('hideQuantitySelector').def(false),
|
|
453
453
|
no_delivery_time_v2: $.from('hideDeliveryTime').def(false),
|
|
@@ -7,7 +7,10 @@ import type { Diagnostics, ObjectShape } from '@servicenow/sdk-build-core'
|
|
|
7
7
|
*/
|
|
8
8
|
export function validateFulfillmentProcessExclusivity(arg: ObjectShape, diagnostics: Diagnostics): boolean {
|
|
9
9
|
const fields = ['executionPlan', 'flow', 'workflow'] as const
|
|
10
|
-
const defined = fields.filter((f) =>
|
|
10
|
+
const defined = fields.filter((f) => {
|
|
11
|
+
const val = arg.get(f)
|
|
12
|
+
return val.isDefined() && (!!val.ifRecordId() || !val.ifString()?.isEmpty())
|
|
13
|
+
})
|
|
11
14
|
|
|
12
15
|
if (defined.length > 1) {
|
|
13
16
|
diagnostics.error(
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from '@servicenow/sdk-build-core'
|
|
8
8
|
import { getVariableTypeFromName } from './variable-helper'
|
|
9
9
|
import { VariableTypeName } from './variable-helper'
|
|
10
|
-
import { convertRolesToString, getVisibilityId } from './utils'
|
|
10
|
+
import { convertRolesToString, getVisibilityId, validateFieldNameBelongsToTable } from './utils'
|
|
11
11
|
import {
|
|
12
12
|
validateMapToFieldRequiresField,
|
|
13
13
|
validateMandatoryReadOnlyHidden,
|
|
@@ -20,8 +20,9 @@ export async function buildVariableRecords(options: {
|
|
|
20
20
|
diagnostics: Diagnostics
|
|
21
21
|
variablesConfig: ObjectShape
|
|
22
22
|
parent: Record
|
|
23
|
+
parentArg?: ObjectShape
|
|
23
24
|
}): Promise<Record[]> {
|
|
24
|
-
const { variablesConfig, factory, parent, diagnostics } = options
|
|
25
|
+
const { variablesConfig, factory, parent, diagnostics, parentArg } = options
|
|
25
26
|
const records: Record[] = []
|
|
26
27
|
let catItemRecord: Record
|
|
27
28
|
let variableSetRecord: Record
|
|
@@ -31,6 +32,9 @@ export async function buildVariableRecords(options: {
|
|
|
31
32
|
if (parent?.getTable() === 'item_option_new_set') {
|
|
32
33
|
variableSetRecord = parent
|
|
33
34
|
}
|
|
35
|
+
if (parent?.getTable() === 'sc_cat_item_producer' && parentArg) {
|
|
36
|
+
validateFieldNameBelongsToTable(parentArg, diagnostics, 'RecordProducer')
|
|
37
|
+
}
|
|
34
38
|
|
|
35
39
|
// Convert entries to array to use for-of loop
|
|
36
40
|
const entries = Array.from(variablesConfig.entries())
|
|
@@ -1360,3 +1360,96 @@ export function resolveAndValidateVariableId(
|
|
|
1360
1360
|
|
|
1361
1361
|
return variableId
|
|
1362
1362
|
}
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* Validates that each variable's 'field' value (when 'mapToField' is true) belongs to the record producer's target table.
|
|
1366
|
+
* Resolves the table record from arg.get('table'), collects field names from sys_documentation descendants via .flat(),
|
|
1367
|
+
* and checks each variable's 'field' against those keys.
|
|
1368
|
+
* @param arg - The ObjectShape containing the record producer configuration
|
|
1369
|
+
* @param diagnostics - Diagnostics instance for reporting errors
|
|
1370
|
+
* @param context - Context where the validation is being performed ('RecordProducer')
|
|
1371
|
+
* @returns True if all mapped fields belong to the table, false otherwise
|
|
1372
|
+
*/
|
|
1373
|
+
export function validateFieldNameBelongsToTable(arg: ObjectShape, diagnostics: Diagnostics, context: string): boolean {
|
|
1374
|
+
if (!arg.get('variables').isDefined()) {
|
|
1375
|
+
return true
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// Resolve the table record from arg.get('table')
|
|
1379
|
+
// table can be a direct record reference (IdentifierShape) or a plain string table name
|
|
1380
|
+
const tableShape = arg.get('table')
|
|
1381
|
+
|
|
1382
|
+
let tableRecord: Record | undefined
|
|
1383
|
+
|
|
1384
|
+
if (tableShape.isRecord()) {
|
|
1385
|
+
tableRecord = tableShape.asRecord()
|
|
1386
|
+
} else if (tableShape.is(IdentifierShape)) {
|
|
1387
|
+
const resolved = tableShape.as(IdentifierShape).resolve(true)
|
|
1388
|
+
if (resolved?.isRecord()) {
|
|
1389
|
+
tableRecord = resolved.asRecord()
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Collect field names from sys_documentation descendants via .flat() (only when table resolves to a record)
|
|
1394
|
+
let tableFieldSet: Set<string> | undefined
|
|
1395
|
+
|
|
1396
|
+
if (tableRecord) {
|
|
1397
|
+
const tableFields = tableRecord
|
|
1398
|
+
.flat()
|
|
1399
|
+
.filter((r: Record) => r.getTable() === 'sys_dictionary')
|
|
1400
|
+
.map((r: Record) => r.get('element')?.ifString()?.getValue())
|
|
1401
|
+
.filter((element): element is string => !!element)
|
|
1402
|
+
|
|
1403
|
+
if (tableFields.length > 0) {
|
|
1404
|
+
tableFieldSet = new Set(tableFields)
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Iterate over variables and validate field names for those with mapToField: true
|
|
1409
|
+
const variablesConfig = arg.get('variables').asObject()
|
|
1410
|
+
const entries = Array.from(variablesConfig.entries())
|
|
1411
|
+
|
|
1412
|
+
// Track which fields have already been mapped to detect duplicates (always enforced)
|
|
1413
|
+
const mappedFields = new Map<string, string>() // field -> first variableKey that mapped it
|
|
1414
|
+
|
|
1415
|
+
for (const [variableKey, value] of entries) {
|
|
1416
|
+
const callExpr = value.as(CallExpressionShape)
|
|
1417
|
+
const config = callExpr.getArgument(0).asObject()
|
|
1418
|
+
|
|
1419
|
+
const mapToField = config.get('mapToField')
|
|
1420
|
+
const isMapToFieldTrue = mapToField.isDefined() && mapToField.ifBoolean()?.getValue() === true
|
|
1421
|
+
|
|
1422
|
+
if (!isMapToFieldTrue) {
|
|
1423
|
+
continue
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
const fieldShape = config.get('field')
|
|
1427
|
+
const fieldValue = fieldShape.isDefined() ? fieldShape.ifString()?.getValue() : undefined
|
|
1428
|
+
|
|
1429
|
+
if (!fieldValue) {
|
|
1430
|
+
continue
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// Check field belongs to table (only when table resolved to a record with sys_documentation)
|
|
1434
|
+
if (tableFieldSet && !tableFieldSet.has(fieldValue)) {
|
|
1435
|
+
diagnostics.error(
|
|
1436
|
+
fieldShape,
|
|
1437
|
+
`${context} variable '${variableKey}': field '${fieldValue}' does not belong to the target table. Valid fields are: ${[...tableFieldSet].join(', ')}.`
|
|
1438
|
+
)
|
|
1439
|
+
return false
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Check if this field is already mapped by another variable (always enforced)
|
|
1443
|
+
const existingVariable = mappedFields.get(fieldValue)
|
|
1444
|
+
if (existingVariable) {
|
|
1445
|
+
diagnostics.error(
|
|
1446
|
+
fieldShape,
|
|
1447
|
+
`${context} variable '${variableKey}': field '${fieldValue}' is already mapped by variable '${existingVariable}'. Each table field can only be mapped by one variable.`
|
|
1448
|
+
)
|
|
1449
|
+
return false
|
|
1450
|
+
}
|
|
1451
|
+
mappedFields.set(fieldValue, variableKey)
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
return true
|
|
1455
|
+
}
|
|
@@ -71,6 +71,20 @@ const attachmentRelationships = {
|
|
|
71
71
|
},
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
const sourceArtifactRelationships = {
|
|
75
|
+
sn_glider_source_artifact_m2m: {
|
|
76
|
+
via: 'application_file',
|
|
77
|
+
descendant: true,
|
|
78
|
+
relationships: {
|
|
79
|
+
sn_glider_source_artifact: {
|
|
80
|
+
via: 'source_artifact',
|
|
81
|
+
inverse: true,
|
|
82
|
+
descendant: true,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
const toNoOpShape = (record: Record) => {
|
|
75
89
|
return { success: true, value: Shape.noOp(record) }
|
|
76
90
|
}
|
|
@@ -87,9 +101,14 @@ export const StaticContentPlugin = Plugin.create({
|
|
|
87
101
|
toShape: toNoOpShape,
|
|
88
102
|
},
|
|
89
103
|
sys_ux_lib_asset: {
|
|
90
|
-
|
|
104
|
+
coalesce: ['name'],
|
|
105
|
+
relationships: { ...attachmentRelationships, ...sourceArtifactRelationships },
|
|
91
106
|
toShape: toNoOpShape,
|
|
92
|
-
toFile:
|
|
107
|
+
toFile: async (mainRecord, context) => {
|
|
108
|
+
const existingRelated = mainRecord.flat().slice(1)
|
|
109
|
+
const m2mRecords = context.descendants.query('sn_glider_source_artifact_m2m')
|
|
110
|
+
return multipleRecordsToFile(mainRecord.with(...existingRelated, ...m2mRecords), context)
|
|
111
|
+
},
|
|
93
112
|
},
|
|
94
113
|
db_image: {
|
|
95
114
|
relationships: attachmentRelationships,
|
|
@@ -142,6 +161,10 @@ export const StaticContentPlugin = Plugin.create({
|
|
|
142
161
|
})
|
|
143
162
|
if (mimeType === 'text/html') {
|
|
144
163
|
// This content will be handled by the UiPage referencing it
|
|
164
|
+
} else if (relativePath.endsWith('.ui-source-manifest.json')) {
|
|
165
|
+
// Build-time manifest produced by the uiPageSourceManifest rollup plugin.
|
|
166
|
+
// Consumed by UiPage during build to determine which source files to include
|
|
167
|
+
// in the source artifact record. Not a deployable asset.
|
|
145
168
|
} else if (mimeType === 'application/javascript') {
|
|
146
169
|
tableName = 'sys_ux_lib_asset'
|
|
147
170
|
assetName = pathModule.join(
|