@servicenow/sdk-build-plugins 2.0.1 → 2.1.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 (133) hide show
  1. package/dist/AttachmentPlugin.d.ts +8 -6
  2. package/dist/AttachmentPlugin.js +4 -2
  3. package/dist/AttachmentPlugin.js.map +1 -1
  4. package/dist/BusinessRulePlugin.d.ts +5 -31
  5. package/dist/BusinessRulePlugin.js +45 -72
  6. package/dist/BusinessRulePlugin.js.map +1 -1
  7. package/dist/CrossScopePrivilegePlugin.d.ts +14 -1
  8. package/dist/CrossScopePrivilegePlugin.js +36 -1
  9. package/dist/CrossScopePrivilegePlugin.js.map +1 -1
  10. package/dist/DefaultPlugin.d.ts +60 -57
  11. package/dist/DefaultPlugin.js +151 -146
  12. package/dist/DefaultPlugin.js.map +1 -1
  13. package/dist/HtmlTemplatePlugin.d.ts +21 -0
  14. package/dist/HtmlTemplatePlugin.js +52 -0
  15. package/dist/HtmlTemplatePlugin.js.map +1 -0
  16. package/dist/IdPlugin.d.ts +18 -8
  17. package/dist/IdPlugin.js +55 -28
  18. package/dist/IdPlugin.js.map +1 -1
  19. package/dist/JsonPlugin.d.ts +23 -0
  20. package/dist/JsonPlugin.js +74 -0
  21. package/dist/JsonPlugin.js.map +1 -0
  22. package/dist/ListPlugin.d.ts +7 -13
  23. package/dist/ListPlugin.js +32 -15
  24. package/dist/ListPlugin.js.map +1 -1
  25. package/dist/NowConfigPlugin.d.ts +45 -0
  26. package/dist/NowConfigPlugin.js +90 -0
  27. package/dist/NowConfigPlugin.js.map +1 -0
  28. package/dist/PackageJsonPlugin.d.ts +20 -0
  29. package/dist/PackageJsonPlugin.js +48 -0
  30. package/dist/PackageJsonPlugin.js.map +1 -0
  31. package/dist/PropertyPlugin.d.ts +9 -35
  32. package/dist/PropertyPlugin.js +53 -32
  33. package/dist/PropertyPlugin.js.map +1 -1
  34. package/dist/ScriptTemplatePlugin.d.ts +3 -20
  35. package/dist/ScriptTemplatePlugin.js +20 -138
  36. package/dist/ScriptTemplatePlugin.js.map +1 -1
  37. package/dist/ServerModulePlugin.d.ts +62 -0
  38. package/dist/ServerModulePlugin.js +231 -0
  39. package/dist/ServerModulePlugin.js.map +1 -0
  40. package/dist/UserPreferencePlugin.d.ts +4 -1
  41. package/dist/UserPreferencePlugin.js +12 -10
  42. package/dist/UserPreferencePlugin.js.map +1 -1
  43. package/dist/aclAndRole/AclPlugin.d.ts +41 -79
  44. package/dist/aclAndRole/AclPlugin.js +50 -72
  45. package/dist/aclAndRole/AclPlugin.js.map +1 -1
  46. package/dist/aclAndRole/RolePlugin.d.ts +27 -18
  47. package/dist/aclAndRole/RolePlugin.js +66 -41
  48. package/dist/aclAndRole/RolePlugin.js.map +1 -1
  49. package/dist/app/ApplicationMenuPlugin.d.ts +4 -2
  50. package/dist/app/ApplicationMenuPlugin.js +12 -7
  51. package/dist/app/ApplicationMenuPlugin.js.map +1 -1
  52. package/dist/atf/ATFComposer.js +324 -324
  53. package/dist/atf/TestPlugin.d.ts +12 -10
  54. package/dist/atf/TestPlugin.js +14 -12
  55. package/dist/atf/TestPlugin.js.map +1 -1
  56. package/dist/db/ColumnPlugins.d.ts +559 -97
  57. package/dist/db/ColumnPlugins.js +28 -24
  58. package/dist/db/ColumnPlugins.js.map +1 -1
  59. package/dist/db/DBUtils.d.ts +2 -0
  60. package/dist/db/DBUtils.js +27 -0
  61. package/dist/db/DBUtils.js.map +1 -0
  62. package/dist/db/DocumentationPlugin.d.ts +58 -0
  63. package/dist/db/DocumentationPlugin.js +248 -0
  64. package/dist/db/DocumentationPlugin.js.map +1 -0
  65. package/dist/db/RecordPlugin.d.ts +15 -13
  66. package/dist/db/RecordPlugin.js +74 -79
  67. package/dist/db/RecordPlugin.js.map +1 -1
  68. package/dist/db/TablePlugin.d.ts +38 -105
  69. package/dist/db/TablePlugin.js +205 -111
  70. package/dist/db/TablePlugin.js.map +1 -1
  71. package/dist/db/index.d.ts +2 -0
  72. package/dist/db/index.js +4 -1
  73. package/dist/db/index.js.map +1 -1
  74. package/dist/index.d.ts +7 -2
  75. package/dist/index.js +12 -2
  76. package/dist/index.js.map +1 -1
  77. package/dist/scriptedRESTAPI/RESTDeserializationUtils.js +8 -6
  78. package/dist/scriptedRESTAPI/RESTDeserializationUtils.js.map +1 -1
  79. package/dist/scriptedRESTAPI/RESTSerializationUtils.js +7 -6
  80. package/dist/scriptedRESTAPI/RESTSerializationUtils.js.map +1 -1
  81. package/dist/scriptedRESTAPI/RestApiPlugin.d.ts +74 -85
  82. package/dist/scriptedRESTAPI/RestApiPlugin.js +71 -54
  83. package/dist/scriptedRESTAPI/RestApiPlugin.js.map +1 -1
  84. package/dist/scriptedRESTAPI/RestUtils.d.ts +2 -0
  85. package/dist/scriptedRESTAPI/RestUtils.js +37 -9
  86. package/dist/scriptedRESTAPI/RestUtils.js.map +1 -1
  87. package/dist/scripts/ClientScriptPlugin.d.ts +18 -16
  88. package/dist/scripts/ClientScriptPlugin.js +48 -45
  89. package/dist/scripts/ClientScriptPlugin.js.map +1 -1
  90. package/dist/scripts/scriptUtils.d.ts +0 -14
  91. package/dist/scripts/scriptUtils.js +0 -74
  92. package/dist/scripts/scriptUtils.js.map +1 -1
  93. package/dist/uxf/ExperiencePlugin.d.ts +31 -3
  94. package/dist/uxf/ExperiencePlugin.js +10 -4
  95. package/dist/uxf/ExperiencePlugin.js.map +1 -1
  96. package/dist/uxf/RoutesPlugin.d.ts +10 -3
  97. package/dist/uxf/RoutesPlugin.js +20 -14
  98. package/dist/uxf/RoutesPlugin.js.map +1 -1
  99. package/package.json +6 -6
  100. package/src/AttachmentPlugin.ts +10 -2
  101. package/src/BusinessRulePlugin.ts +79 -104
  102. package/src/CrossScopePrivilegePlugin.ts +65 -5
  103. package/src/DefaultPlugin.ts +181 -173
  104. package/src/HtmlTemplatePlugin.ts +31 -0
  105. package/src/IdPlugin.ts +59 -28
  106. package/src/JsonPlugin.ts +81 -0
  107. package/src/ListPlugin.ts +38 -19
  108. package/src/NowConfigPlugin.ts +72 -0
  109. package/src/PackageJsonPlugin.ts +24 -0
  110. package/src/PropertyPlugin.ts +60 -38
  111. package/src/ScriptTemplatePlugin.ts +22 -179
  112. package/src/ServerModulePlugin.ts +267 -0
  113. package/src/UserPreferencePlugin.ts +28 -19
  114. package/src/aclAndRole/AclPlugin.ts +78 -109
  115. package/src/aclAndRole/RolePlugin.ts +85 -58
  116. package/src/app/ApplicationMenuPlugin.ts +15 -11
  117. package/src/atf/ATFComposer.ts +1 -1
  118. package/src/atf/TestPlugin.ts +14 -12
  119. package/src/db/ColumnPlugins.ts +36 -37
  120. package/src/db/DBUtils.ts +36 -0
  121. package/src/db/DocumentationPlugin.ts +282 -0
  122. package/src/db/RecordPlugin.ts +96 -98
  123. package/src/db/TablePlugin.ts +278 -152
  124. package/src/db/index.ts +2 -0
  125. package/src/index.ts +7 -2
  126. package/src/scriptedRESTAPI/RESTDeserializationUtils.ts +15 -7
  127. package/src/scriptedRESTAPI/RESTSerializationUtils.ts +7 -6
  128. package/src/scriptedRESTAPI/RestApiPlugin.ts +85 -75
  129. package/src/scriptedRESTAPI/RestUtils.ts +44 -11
  130. package/src/scripts/ClientScriptPlugin.ts +64 -68
  131. package/src/scripts/scriptUtils.ts +0 -76
  132. package/src/uxf/ExperiencePlugin.ts +14 -13
  133. package/src/uxf/RoutesPlugin.ts +22 -27
@@ -32,6 +32,7 @@ import {
32
32
  UserRolesColumn,
33
33
  VersionColumn,
34
34
  StringColumn,
35
+ Documentation,
35
36
  } from '@servicenow/sdk-core/runtime/db'
36
37
  import {
37
38
  Context,
@@ -53,20 +54,36 @@ import {
53
54
  generateCallExpressionExport,
54
55
  generateCallExpression,
55
56
  createPropertyIdentifier,
56
- XMLJsonBuilder,
57
- XMLJsonElement,
58
57
  ObjectData,
58
+ isSNScope,
59
+ GUID,
59
60
  } from '@servicenow/sdk-build-core'
60
- import { XMLBuilder } from 'fast-xml-parser'
61
- import { isArray, isEmpty, isObject, isString, parseInt } from 'lodash'
61
+ import { create } from 'xmlbuilder2'
62
+ import { isArray, isEmpty, isObject, parseInt } from 'lodash'
62
63
  import { z } from 'zod'
63
64
  import { RecordPlugin, TextBooleanSchema, TextNumberSchema, TextStringSchema } from './RecordPlugin'
64
- import { ScriptInfo, buildScriptImport, scriptInfo } from '../scripts/scriptUtils'
65
- import { SyntaxKind, Node, ts, ObjectLiteralExpression, CallExpression, Structure, SourceFile } from 'ts-morph'
65
+ import {
66
+ SyntaxKind,
67
+ Node,
68
+ ts,
69
+ ObjectLiteralExpression,
70
+ CallExpression,
71
+ Structure,
72
+ SourceFile,
73
+ ImportDeclarationStructure,
74
+ PropertySignatureStructure,
75
+ InterfaceDeclarationStructure,
76
+ ImportSpecifierStructure,
77
+ OptionalKind,
78
+ ImportDeclaration,
79
+ } from 'ts-morph'
66
80
  import { Diagnostic } from '@servicenow/sdk-project'
67
81
  import { formatScript } from '../ScriptTemplatePlugin'
68
82
  import * as os from 'os'
69
83
  import * as path from 'path'
84
+ import { XMLBuilder } from 'xmlbuilder2/lib/interfaces'
85
+ import { getLabelDiagnostics } from './DBUtils'
86
+ import { ModuleFunctionData } from '../ServerModulePlugin'
70
87
 
71
88
  /**
72
89
  * The access levels are mapped to the following values on the platform
@@ -92,7 +109,7 @@ export const TableSchemaFull = z.object({
92
109
  * if table definition not present locally */
93
110
  extends: z.string().optional(),
94
111
  /** Will match name if not provided, cannot exceed 80 characters */
95
- label: z.string().max(80, 'Cannot exceed 80 characters').optional(),
112
+ label: z.string().max(80, 'Cannot exceed 80 characters').or(z.array(z.any())).optional(),
96
113
  /** Must match column name */
97
114
  display: z.string().optional(),
98
115
 
@@ -158,17 +175,18 @@ type IndexType = {
158
175
 
159
176
  // Everything which goes into a element with type='string/int/etc' in bootstrap.xml
160
177
  type ColumnSchema = z.infer<typeof ColumnSchema>
161
- export const ColumnSchema = z.object({
178
+ const ColumnSchema = z.object({
162
179
  entityKind: z.string().optional(),
163
180
  column_type: z.string().optional(),
164
181
  audit: z.boolean().optional().default(false),
165
- label: z.string().optional(),
182
+ label: z.string().or(z.array(z.any())).optional(),
166
183
  maxLength: z.number().optional().default(40),
167
184
  mandatory: z.boolean().optional().default(false),
168
185
  read_only: z.boolean().optional().default(false),
169
186
  default: z.any().optional(),
170
187
  attributes: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
171
188
  referenceTable: z.string().optional(),
189
+ cascadeRule: z.string().optional(),
172
190
  dropdown: z.enum(choiceDropdown).default('none').optional(),
173
191
  choices: z.record(z.any()).or(z.array(z.any())).optional(),
174
192
  choice_elements: z.record(z.any()).or(z.array(z.any())).optional(),
@@ -232,7 +250,7 @@ const DBObjectIncomingAttributes = z.object({
232
250
  export const TableSchemaBootstrap = z.object({
233
251
  name: z.string(),
234
252
  extends: z.string().optional(),
235
- label: z.string().max(80, 'Cannot exceed 80 characters').optional(),
253
+ label: z.string().max(80, 'Cannot exceed 80 characters').or(z.array(z.any())).optional(),
236
254
 
237
255
  // Controls
238
256
  extensible: z.boolean().optional().default(false),
@@ -306,7 +324,7 @@ const DBObjectIncoming = z.object({
306
324
  sys_db_object: z.record(z.any()),
307
325
  })
308
326
 
309
- export const Documentation = z.object({
327
+ export const SysDocumentation = z.object({
310
328
  sys_documentation: z.object({
311
329
  element: TextStringSchema.optional(),
312
330
  label: TextStringSchema.optional(),
@@ -315,6 +333,7 @@ export const Documentation = z.object({
315
333
  help: TextStringSchema.optional(),
316
334
  name: TextStringSchema.optional(),
317
335
  plural: TextStringSchema.optional(),
336
+ sys_id: TextStringSchema.optional(),
318
337
  url: TextStringSchema.optional(),
319
338
  url_target: TextStringSchema.optional(),
320
339
  }),
@@ -326,7 +345,7 @@ export const TableSchemaBootstrapAttributes = z
326
345
  '@_name': z.string(),
327
346
  '@_extends': z.string().optional(),
328
347
  '@_label': z.string().optional(),
329
- '@_max_length': z.coerce.number().optional(),
348
+ '@_max_length': z.any().optional(),
330
349
 
331
350
  // From sys_dictionary
332
351
  '@_audit': BooleanFromString,
@@ -415,10 +434,6 @@ type ComposedTableData = {
415
434
  }
416
435
  }
417
436
 
418
- function isSNScope(scopeName: string) {
419
- return scopeName.startsWith('sn') || scopeName.startsWith('now')
420
- }
421
-
422
437
  export default Plugin({
423
438
  name: 'Table',
424
439
  ownedTables: {
@@ -426,92 +441,94 @@ export default Plugin({
426
441
  },
427
442
  extractors: {
428
443
  entity: {
429
- // TODO: Cast to `any` is temporary workaround for the circular nature of Table entities
430
- CallExpression: (node, context) => {
431
- const result = extractCallExpression(Table, 'table', node, context, (table) => table.name)
432
- if (!result.handled || !(0 in result.data)) {
433
- return result
434
- }
444
+ fluent: {
445
+ // TODO: Cast to `any` is temporary workaround for the circular nature of Table entities
446
+ CallExpression: (node, context) => {
447
+ const result = extractCallExpression(Table, 'table', node, context, (table) => table.name)
448
+ if (!result.handled || !(0 in result.data)) {
449
+ return result
450
+ }
435
451
 
436
- const data = result.data[0]
437
- const diagnostics = result.diagnostics
438
- const scopeName = context.app.config.scope
439
- const scopeRegex = new RegExp(`^${scopeName}_`)
440
- const nameRegex = new RegExp(`^[a-z_][a-z0-9_]`)
441
- const tableName = data.getProperty('name')
442
- const scopeMatches = tableName.getValue().match(scopeRegex)
443
-
444
- if (!scopeMatches) {
445
- const nameNode = tableName.getNode().asKindOrThrow(SyntaxKind.StringLiteral)
446
- if (nameNode.getParent().getKindName() !== 'AsExpression') {
452
+ const data = result.data[0]
453
+ const diagnostics = result.diagnostics
454
+ const scopeName = context.app.config.scope
455
+ const scopeRegex = new RegExp(`^${scopeName}_`)
456
+ const columnNameRegex = new RegExp(`^[a-z_][a-z0-9_]*$`)
457
+ const tableNameRegex = new RegExp(`^[a-z_][a-z0-9_]*[a-z0-9]$`)
458
+ const tableName = data.getProperty('name')
459
+ const scopeMatches = tableName.getValue().match(scopeRegex)
460
+ let ignoreColumnNameCheck = false
461
+
462
+ if (!tableName.getValue().match(tableNameRegex)) {
447
463
  diagnostics.push(
448
464
  new FluentDiagnostic(
449
465
  tableName.getNode(),
450
- `'name' property must start with scope prefix '${scopeName}'`
466
+ `Table name must only contain lowercase letters, numbers, and underscores and end with a letter or number`
451
467
  )
452
468
  )
453
469
  }
454
- }
455
470
 
456
- const schema = data.getProperty('schema')
457
- const schemaNode = schema.getNode().asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
458
- for (const columnName in schema.getValue()) {
459
- const columnNode = schemaNode.getPropertyOrThrow(columnName)
460
- if (!scopeMatches && !isSNScope(scopeName) && !columnName.match(scopeRegex)) {
461
- diagnostics.push(
462
- new FluentDiagnostic(
463
- columnNode,
464
- `Column name must be prefixed with scope '${scopeName}' if table name does not contain prefix`
471
+ if (!scopeMatches) {
472
+ const nameNode = tableName.getNode().asKindOrThrow(SyntaxKind.StringLiteral)
473
+ if (nameNode.getParent().getKindName() !== 'AsExpression') {
474
+ diagnostics.push(
475
+ new FluentDiagnostic(
476
+ tableName.getNode(),
477
+ `'name' property must start with scope prefix '${scopeName}'`
478
+ )
465
479
  )
466
- )
480
+ } else {
481
+ ignoreColumnNameCheck = true
482
+ }
467
483
  }
468
484
 
469
- if (!columnName.match(nameRegex)) {
485
+ const schema = data.getProperty('schema')
486
+ const schemaNode = schema.getNode().asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
487
+ for (const columnName in schema.getValue()) {
488
+ const columnNode = schemaNode.getPropertyOrThrow(columnName)
489
+ if (
490
+ !ignoreColumnNameCheck &&
491
+ !scopeMatches &&
492
+ !isSNScope(scopeName) &&
493
+ !columnName.match(scopeRegex)
494
+ ) {
495
+ diagnostics.push(
496
+ new FluentDiagnostic(
497
+ columnNode,
498
+ `Column name must be prefixed with scope '${scopeName}' if table name does not contain prefix`
499
+ )
500
+ )
501
+ }
502
+
503
+ if (!columnName.match(columnNameRegex)) {
504
+ diagnostics.push(
505
+ new FluentDiagnostic(
506
+ columnNode,
507
+ `Column name must only contain lowercase letters, numbers, and underscores`
508
+ )
509
+ )
510
+ }
511
+ }
512
+
513
+ const declaration = node.getParentIfKind(SyntaxKind.VariableDeclaration)
514
+ if (
515
+ !declaration ||
516
+ !declaration.isExported() ||
517
+ !declaration.isNamedExport() ||
518
+ declaration.getName() !== tableName.getValue()
519
+ ) {
470
520
  diagnostics.push(
471
521
  new FluentDiagnostic(
472
- columnNode,
473
- `Column name must only contain lowercase letters, numbers, and underscores`
522
+ node,
523
+ `Table definition should be exported as a named export with the name '${tableName.getValue()}'`,
524
+ { level: Diagnostic.Level.Warn }
474
525
  )
475
526
  )
476
527
  }
477
- }
478
-
479
- const declaration = node.getParentIfKind(SyntaxKind.VariableDeclaration)
480
- if (
481
- !declaration ||
482
- !declaration.isExported() ||
483
- !declaration.isNamedExport() ||
484
- declaration.getName() !== tableName.getValue()
485
- ) {
486
- diagnostics.push(
487
- new FluentDiagnostic(
488
- node,
489
- `Table definition should be exported as a named export with the name '${tableName.getValue()}'`,
490
- { level: Diagnostic.Level.Warn }
491
- )
492
- )
493
- }
494
528
 
495
- return { handled: true, data: [data], diagnostics }
496
- },
497
- },
498
- raw: {
499
- FunctionDeclaration(node, context) {
500
- const info = scriptInfo(node, context, Table.name)
501
- if (!info) {
502
- return { handled: false }
503
- }
504
-
505
- return { handled: true, diagnostics: [], data: [info] }
506
- },
507
-
508
- FunctionExpression(node, context) {
509
- const info = scriptInfo(node, context, Table.name)
510
- if (!info) {
511
- return { handled: false }
512
- }
513
-
514
- return { handled: true, diagnostics: [], data: [info] }
529
+ diagnostics.push(...getLabelDiagnostics(data))
530
+ return { handled: true, data: [data], diagnostics }
531
+ },
515
532
  },
516
533
  },
517
534
  xml(xml, context) {
@@ -551,16 +568,16 @@ export default Plugin({
551
568
  }
552
569
 
553
570
  if (table === 'sys_documentation') {
554
- const data = Documentation.parse(record_update[table])
571
+ const data = SysDocumentation.parse(record_update[table]).sys_documentation
555
572
  return new XmlData(
556
573
  {
557
574
  table: table,
558
- id: record_update[table][table]['sys_id']['#text'],
559
- data: data.sys_documentation,
575
+ id: record_update[table][table]['sys_id']?.['#text'] ?? GUID(),
576
+ data,
560
577
  },
561
578
  xml.filePath,
562
- 'record',
563
- record_update[table][table]['@_action']
579
+ 'sys_documentation',
580
+ record_update[table]['@_action']
564
581
  )
565
582
  }
566
583
 
@@ -623,12 +640,15 @@ export default Plugin({
623
640
  const table = TableSchemaFull.parse(data)
624
641
 
625
642
  const composeData = compose(table)
626
- composeData['columns'] = composeColumns(
643
+ const { columnData, documentation } = composeColumns(
627
644
  table.schema as Record<string, ColumnSchema>,
628
645
  context,
646
+ table.name,
629
647
  table.display
630
648
  )
631
649
 
650
+ composeData['columns'] = columnData
651
+
632
652
  const entities: LinkedDocument[] = []
633
653
  if (table.auto_number) {
634
654
  const data = table.auto_number
@@ -650,6 +670,25 @@ export default Plugin({
650
670
  composeData['number_ref'] = composedNumber.guid
651
671
  }
652
672
 
673
+ documentation.forEach((doc) => {
674
+ entities.push({
675
+ kind: 'sys_documentation',
676
+ node: node,
677
+ data: doc,
678
+ guid: doc.$id as string,
679
+ })
680
+ })
681
+
682
+ const docs = composeDocumentation('', table.name, table.label)
683
+ docs.forEach((doc) => {
684
+ entities.push({
685
+ kind: 'sys_documentation',
686
+ node: node,
687
+ data: doc,
688
+ guid: doc.$id as string,
689
+ })
690
+ })
691
+
653
692
  composeData['index'] = table.index
654
693
 
655
694
  entities.push({
@@ -734,42 +773,23 @@ export default Plugin({
734
773
  attributes['live_feed'] = true
735
774
  }
736
775
 
737
- const builder = new XMLJsonBuilder('1.0')
738
- const databaseObj = builder.createRoot('database', undefined, undefined)
776
+ const root = create({ version: '1.0' }).ele('database')
739
777
  const composedAttributes = composeAttributes(attributes)
740
- const collectionObj = databaseObj.addJsonObj(
741
- 'element',
742
- undefined,
743
- { ...bootstrapData, attributes: composedAttributes }!
744
- )
778
+ const collection = root.ele('element', { ...bootstrapData, attributes: composedAttributes }!)
745
779
  for (const column of Object.values(columns)) {
746
780
  const { choice_elements, attributes, ...parsedColumn } = column as ColumnSchema
747
781
  const columnAttributes = composeAttributes(attributes)
748
- const elementObj = collectionObj.addJsonObj(
749
- 'element',
750
- undefined,
751
- {
752
- ...(parsedColumn as any),
753
- attributes: columnAttributes,
754
- }!
755
- )
782
+ const element = collection.ele('element', { ...parsedColumn, attributes: columnAttributes }!)
756
783
 
757
- buildChoices(column as ColumnSchema, elementObj)
784
+ buildChoices(column as ColumnSchema, element)
758
785
  }
759
786
 
760
- buildIndexes(index, collectionObj)
761
-
762
- const bootstrapXmlJson = new XMLBuilder({
763
- ignoreAttributes: false,
764
- format: true,
765
- suppressBooleanAttributes: false,
766
- suppressEmptyNode: true,
767
- }).build(builder.buildJsonObj())
787
+ buildIndexes(index, collection)
768
788
 
769
789
  return {
770
790
  name: `${bootstrapData.name}.xml`,
771
791
  directory: 'dictionary',
772
- content: bootstrapXmlJson,
792
+ content: root.end({ prettyPrint: true }),
773
793
  usedIds: [],
774
794
  }
775
795
  },
@@ -863,13 +883,72 @@ export default Plugin({
863
883
  },
864
884
  postProcessors: {
865
885
  entities(entities, context) {
886
+ const { interfaces, properties, imports, namedImports } = {
887
+ interfaces: [] as OptionalKind<InterfaceDeclarationStructure>[],
888
+ properties: [] as OptionalKind<PropertySignatureStructure>[],
889
+ imports: {} as Record<string, OptionalKind<ImportDeclarationStructure>>,
890
+ namedImports: [] as (OptionalKind<ImportSpecifierStructure> & {
891
+ existingImport: ImportDeclaration
892
+ })[],
893
+ }
894
+ const generatedTableFile: SourceFile | undefined = context.compiler.getGeneratedTableFile()
866
895
  entities
867
896
  .filter((entity) => entity.getKind() === 'table')
868
897
  .forEach((table) => {
869
898
  const tableName = table.getProperty('name')!.getValue() as string
870
- const originalSourceFilePath = path.normalize(table.getNode().getSourceFile().getFilePath())
871
- context.compiler.addTableInterfaceToGlobalDeclaration(originalSourceFilePath, tableName)
899
+ const extendsTable = table.getProperty('extends')?.getValue() as string
900
+ if (tableName && tableName.trim().length > 0) {
901
+ if (!context.compiler.interfaceExistsInGlobalDeclaration(tableName)) {
902
+ interfaces.push({
903
+ name: tableName,
904
+ extends: [`Helper<typeof ${tableName}>`],
905
+ })
906
+ }
907
+ if (!context.compiler.propertyExistsInGlobalDeclaration(tableName)) {
908
+ let interfaceType = `Table<TableSchemas.${tableName}>`
909
+ if (extendsTable) {
910
+ interfaceType = `Table<TableSchemas.${tableName}, "${extendsTable}">`
911
+ }
912
+ properties.push({
913
+ name: tableName,
914
+ type: interfaceType,
915
+ })
916
+ }
917
+
918
+ const filePath = path.normalize(table.getNode().getSourceFile().getFilePath())
919
+ const resolvedModuleSpecifier = generatedTableFile?.getRelativePathAsModuleSpecifierTo(
920
+ filePath
921
+ ) as string
922
+ const importExists = context.compiler.importExistsInGlobalDeclaration(tableName)
923
+
924
+ if (!importExists) {
925
+ if (imports[filePath]) {
926
+ ;(imports[filePath].namedImports! as string[]).push(tableName)
927
+ } else {
928
+ imports[filePath] = {
929
+ namedImports: [tableName],
930
+ moduleSpecifier: resolvedModuleSpecifier,
931
+ }
932
+ }
933
+ } else {
934
+ const namedImportExists = importExists
935
+ ?.getNamedImports()
936
+ .find((value) => value.getName() === tableName)
937
+ if (!namedImportExists) {
938
+ namedImports.push({
939
+ existingImport: importExists,
940
+ name: tableName,
941
+ })
942
+ }
943
+ }
944
+ }
872
945
  })
946
+ context.compiler.addTableInterfacesToGlobalDeclaration({
947
+ interfaces,
948
+ properties,
949
+ imports,
950
+ namedImports,
951
+ })
873
952
  },
874
953
  },
875
954
  })
@@ -908,19 +987,52 @@ function updateDisplayValue(node: CallExpression, columns: ColumnSchema, incomin
908
987
  }
909
988
  }
910
989
 
911
- function composeColumns(columns: Record<string, ColumnSchema>, context: Context, display?: string) {
990
+ function composeDocumentation(columnName: string, tableName: string, labels?: string | Documentation[]): NowRecord[] {
991
+ const labelData: NowRecord[] = []
992
+
993
+ if (!labels || !isArray(labels)) {
994
+ return labelData
995
+ }
996
+
997
+ labels.forEach((label) => {
998
+ const labelValue = label.label ?? columnName !== '' ? columnName : tableName
999
+ labelData.push(
1000
+ NowRecord({
1001
+ table: 'sys_documentation',
1002
+ $id: `${tableName}_${columnName ? columnName + '_' : ''}${label.language}`,
1003
+ data: { label: labelValue, element: columnName, name: tableName, ...label },
1004
+ })
1005
+ )
1006
+ })
1007
+ return labelData
1008
+ }
1009
+
1010
+ function getLabel(label?: string | Documentation[]): string | undefined {
1011
+ if (!label || isArray(label)) {
1012
+ // use column name if no label specified
1013
+ return
1014
+ }
1015
+
1016
+ // label is an explicit string value
1017
+ return label
1018
+ }
1019
+
1020
+ function composeColumns(columns: Record<string, ColumnSchema>, _context: Context, tableName: string, display?: string) {
912
1021
  const composedColumns = {}
1022
+ const documentation: NowRecord[] = []
913
1023
  for (const [columnName, columnData] of Object.entries(columns)) {
914
- const column = ColumnSchema.parse(columnData)
1024
+ const { maxLength, ...rest } = columnData
1025
+ const column = ColumnSchema.parse(rest)
915
1026
  const requiresDropdown = column.dynamic_value_definitions || column.choices
916
1027
  const element = {
917
1028
  name: columnName,
918
1029
  type: column.entityKind === 'generic' ? column.column_type : column.entityKind,
919
1030
  attributes: column.attributes ?? undefined,
920
1031
  reference: column.referenceTable,
1032
+ reference_cascade_rule: column.referenceTable ? column.cascadeRule : undefined,
921
1033
  audit: column.audit,
922
- label: column.label ?? columnName,
923
- max_length: column.maxLength,
1034
+ label: getLabel(column.label) ?? columnName,
1035
+ max_length: maxLength ?? column.maxLength,
924
1036
  mandatory: column.mandatory,
925
1037
  read_only: column.read_only,
926
1038
  choice: choiceDropdown.indexOf(column.dropdown ?? requiresDropdown ? 'dropdown_with_none' : 'none'),
@@ -930,7 +1042,10 @@ function composeColumns(columns: Record<string, ColumnSchema>, context: Context,
930
1042
  virtual: column.dynamic_value_definitions?.type === 'calculated_value' ? true : undefined,
931
1043
  calculation:
932
1044
  column.dynamic_value_definitions?.type === 'calculated_value'
933
- ? getScriptInfo(column.dynamic_value_definitions!['calculated_value'], context)
1045
+ ? ModuleFunctionData.getGlueCode(
1046
+ column.dynamic_value_definitions!['calculated_value'],
1047
+ (n) => `${n}(current)`
1048
+ )
934
1049
  : undefined,
935
1050
  choice_table:
936
1051
  column.dynamic_value_definitions?.type === 'choices_from_other_table'
@@ -961,6 +1076,8 @@ function composeColumns(columns: Record<string, ColumnSchema>, context: Context,
961
1076
  function_field: column.function_definition ? true : false,
962
1077
  }
963
1078
 
1079
+ documentation.push(...composeDocumentation(columnName, tableName, column.label))
1080
+
964
1081
  if (column.choices) {
965
1082
  Object.keys(column.choices).forEach((key) => {
966
1083
  if (isObject(column.choices![key])) {
@@ -974,7 +1091,7 @@ function composeColumns(columns: Record<string, ColumnSchema>, context: Context,
974
1091
 
975
1092
  composedColumns[columnName] = element
976
1093
  }
977
- return composedColumns
1094
+ return { columnData: composedColumns, documentation }
978
1095
  }
979
1096
 
980
1097
  function compose(bootstrapData: z.infer<typeof TableSchemaBootstrap>) {
@@ -991,7 +1108,7 @@ function compose(bootstrapData: z.infer<typeof TableSchemaBootstrap>) {
991
1108
  name: bootstrapData.name,
992
1109
  type: 'collection',
993
1110
  extends: bootstrapData.extends,
994
- label: bootstrapData.label ?? bootstrapData.name,
1111
+ label: getLabel(bootstrapData.label) ?? bootstrapData.name,
995
1112
 
996
1113
  // Controls
997
1114
  is_extendable: bootstrapData.extensible,
@@ -1095,27 +1212,30 @@ function getActionsArray(data) {
1095
1212
  return actions
1096
1213
  }
1097
1214
 
1098
- function getDynamicDefault(val: string) {
1215
+ function getDynamicDefault(val: string | undefined) {
1216
+ if (!val) {
1217
+ return
1218
+ }
1099
1219
  return val.startsWith('javascript:') ? val : `javascript:${dynamic_value_mapping[val]}`
1100
1220
  }
1101
1221
 
1102
- function buildIndexes(indexes: IndexType[] = [], collection: XMLJsonElement) {
1222
+ function buildIndexes(indexes: IndexType[] = [], collection: XMLBuilder) {
1103
1223
  indexes.forEach((index) => {
1104
- const indexElement = collection.addJsonObj('index', undefined, { name: index.name, unique: index.unique })
1105
- indexElement.addJsonObj('element', undefined, { name: index.element })
1224
+ const indexElement = collection.ele('index', { name: index.name, unique: index.unique })
1225
+ indexElement.ele('element', { name: index.element })
1106
1226
  })
1107
1227
  }
1108
1228
 
1109
- function buildChoices(column: ColumnSchema, element: XMLJsonElement) {
1229
+ function buildChoices(column: ColumnSchema, element: XMLBuilder) {
1110
1230
  if (!column.choice_elements) {
1111
1231
  return
1112
1232
  }
1113
1233
 
1114
- const choices = element.addJsonObj('choice', undefined, undefined)
1234
+ const choices = element.ele('choice')
1115
1235
  if (Array.isArray(column.choices) && column.choices.length > 0) {
1116
1236
  // Formatted - choices: [1, 2, 3, 4]
1117
1237
  for (const choice in column.choices) {
1118
- choices.addJsonObj('element', undefined, {
1238
+ choices.ele('element', {
1119
1239
  value: column.choices[choice],
1120
1240
  label: column.choices[choice],
1121
1241
  })
@@ -1124,12 +1244,12 @@ function buildChoices(column: ColumnSchema, element: XMLJsonElement) {
1124
1244
  // Formatted - choices: { 1: { label: 'label' }, 2: { lable: label 2}, ...}
1125
1245
  Object.entries(column.choice_elements).forEach((value) => {
1126
1246
  if (isObject(value[1])) {
1127
- choices.addJsonObj('element', undefined, {
1247
+ choices.ele('element', {
1128
1248
  value: value[0],
1129
1249
  ...(value[1] as ChoiceConfig),
1130
1250
  })
1131
1251
  } else {
1132
- choices.addJsonObj('element', undefined, {
1252
+ choices.ele('element', {
1133
1253
  value: value[1],
1134
1254
  label: value[0],
1135
1255
  })
@@ -1157,11 +1277,12 @@ export function transformColumnData(col: any, callExpFromType: any) {
1157
1277
  default: def,
1158
1278
  max_length,
1159
1279
  reference,
1280
+ reference_cascade_rule,
1160
1281
  function_field,
1161
1282
  ...column
1162
1283
  } = col
1163
1284
 
1164
- if (max_length) {
1285
+ if (max_length !== undefined) {
1165
1286
  column.maxLength = max_length
1166
1287
  }
1167
1288
 
@@ -1175,6 +1296,10 @@ export function transformColumnData(col: any, callExpFromType: any) {
1175
1296
 
1176
1297
  if (reference) {
1177
1298
  column.referenceTable = reference
1299
+
1300
+ if (reference_cascade_rule) {
1301
+ column.cascadeRule = reference_cascade_rule
1302
+ }
1178
1303
  }
1179
1304
 
1180
1305
  // Handle dynamic values
@@ -1226,21 +1351,6 @@ function isDefaultEdgeEncryptionAttribute(attributes: any) {
1226
1351
  )
1227
1352
  }
1228
1353
 
1229
- function getScriptInfo(scriptInfo: ScriptInfo | string, context: Context) {
1230
- if (isString(scriptInfo)) {
1231
- return scriptInfo
1232
- }
1233
- const { filePath, functionName, isDefault } = scriptInfo
1234
- const script = buildScriptImport(
1235
- filePath,
1236
- functionName,
1237
- isDefault,
1238
- context,
1239
- (funcName: string) => `;${funcName}(current)`
1240
- )
1241
- return script.replaceAll('\n', '')
1242
- }
1243
-
1244
1354
  function removeAttributeTag(obj: any) {
1245
1355
  Object.entries(obj).forEach(([key, value]) => {
1246
1356
  if (key.startsWith('@_')) {
@@ -1330,6 +1440,21 @@ export function createObjectExpression(firstArg: object): ts.Expression {
1330
1440
  ts.factory.createNoSubstitutionTemplateLiteral(calVal, calVal)
1331
1441
  )
1332
1442
  )
1443
+ } else if (key === 'maxLength') {
1444
+ if (isNaN(+value)) {
1445
+ return ts.factory.createPropertyAssignment(
1446
+ ts.factory.createIdentifier(key),
1447
+ ts.factory.createAsExpression(
1448
+ ts.factory.createStringLiteral(value),
1449
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)
1450
+ )
1451
+ )
1452
+ }
1453
+
1454
+ return ts.factory.createPropertyAssignment(
1455
+ ts.factory.createIdentifier(key),
1456
+ ts.factory.createNumericLiteral(value)
1457
+ )
1333
1458
  }
1334
1459
  return ts.factory.createPropertyAssignment(
1335
1460
  createPropertyIdentifier(key),
@@ -1388,6 +1513,9 @@ type Undefined<T> = {
1388
1513
  }
1389
1514
  export function getCallExpressionFromType(internal_type: string) {
1390
1515
  switch (internal_type) {
1516
+ case undefined:
1517
+ case 'string':
1518
+ return StringColumn
1391
1519
  case 'boolean':
1392
1520
  return BooleanColumn
1393
1521
  case 'choice':
@@ -1440,8 +1568,6 @@ export function getCallExpressionFromType(internal_type: string) {
1440
1568
  return UserRolesColumn
1441
1569
  case 'version':
1442
1570
  return VersionColumn
1443
- case 'string':
1444
- return StringColumn
1445
1571
  case 'int':
1446
1572
  case 'string_types':
1447
1573
  case 'approval_rules':