@servicenow/sdk-build-plugins 4.6.0 → 4.6.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.
Files changed (45) hide show
  1. package/dist/acl-plugin.js +3 -1
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/atf/test-plugin.js +6 -8
  4. package/dist/atf/test-plugin.js.map +1 -1
  5. package/dist/basic-syntax-plugin.js +10 -3
  6. package/dist/basic-syntax-plugin.js.map +1 -1
  7. package/dist/column-plugin.js +96 -42
  8. package/dist/column-plugin.js.map +1 -1
  9. package/dist/now-config-plugin.js +1 -0
  10. package/dist/now-config-plugin.js.map +1 -1
  11. package/dist/record-plugin.js +3 -2
  12. package/dist/record-plugin.js.map +1 -1
  13. package/dist/script-include-plugin.js +4 -0
  14. package/dist/script-include-plugin.js.map +1 -1
  15. package/dist/service-catalog/catalog-clientscript-plugin.js +2 -2
  16. package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -1
  17. package/dist/service-catalog/catalog-ui-policy-plugin.js +2 -2
  18. package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -1
  19. package/dist/service-catalog/service-catalog-base.d.ts +2 -2
  20. package/dist/service-catalog/service-catalog-base.js +2 -2
  21. package/dist/service-catalog/service-catalog-base.js.map +1 -1
  22. package/dist/service-catalog/utils.js +1 -1
  23. package/dist/service-catalog/utils.js.map +1 -1
  24. package/dist/table-plugin.js +201 -55
  25. package/dist/table-plugin.js.map +1 -1
  26. package/dist/ui-policy-plugin.js +28 -96
  27. package/dist/ui-policy-plugin.js.map +1 -1
  28. package/dist/utils.d.ts +5 -1
  29. package/dist/utils.js +41 -0
  30. package/dist/utils.js.map +1 -1
  31. package/package.json +4 -4
  32. package/src/acl-plugin.ts +3 -1
  33. package/src/atf/test-plugin.ts +6 -9
  34. package/src/basic-syntax-plugin.ts +11 -3
  35. package/src/column-plugin.ts +134 -67
  36. package/src/now-config-plugin.ts +1 -0
  37. package/src/record-plugin.ts +11 -3
  38. package/src/script-include-plugin.ts +8 -0
  39. package/src/service-catalog/catalog-clientscript-plugin.ts +2 -2
  40. package/src/service-catalog/catalog-ui-policy-plugin.ts +2 -2
  41. package/src/service-catalog/service-catalog-base.ts +2 -2
  42. package/src/service-catalog/utils.ts +1 -1
  43. package/src/table-plugin.ts +254 -77
  44. package/src/ui-policy-plugin.ts +32 -128
  45. package/src/utils.ts +52 -0
@@ -26,7 +26,7 @@ import { create } from 'xmlbuilder2'
26
26
  import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'
27
27
  import { addFieldsToColumn } from './column/column-helper'
28
28
  import { getLabelForDefaultLanguage } from './column/column-to-record'
29
- import { createSdkDocEntry, generateDeprecatedDiagnostics } from './utils'
29
+ import { createSdkDocEntry, generateDeprecatedDiagnostics, generateChoiceSetFile } from './utils'
30
30
  import isEqual from 'lodash/isEqual'
31
31
 
32
32
  type GlobalRecord<T extends string | number | symbol, U> = globalThis.Record<T, U>
@@ -80,6 +80,10 @@ const ColumnSchema = z
80
80
  '@_use_dynamic_default': BooleanFromString.optional(),
81
81
  '@_reference': z.string().optional(),
82
82
  '@_virtual': BooleanFromString.optional(),
83
+ '@_formula': z.string().optional(),
84
+ '@_virtual_type': z.string().optional(),
85
+ '@_use_reference_qualifier': z.string().optional(),
86
+ '@_dynamic_ref_qual': z.string().optional(),
83
87
  '@_calculation': z.string().optional(),
84
88
  '@_choice_field': z.string().optional(),
85
89
  '@_function_definition': z.string().optional(),
@@ -168,6 +172,10 @@ type ColumnDefinition = {
168
172
  useDynamicDefault: boolean | undefined
169
173
  reference: string | undefined
170
174
  isVirtual: boolean | undefined
175
+ formula: string | undefined
176
+ virtualType: 'script' | 'formula' | undefined
177
+ useReferenceQualifier: 'simple' | 'dynamic' | 'advanced' | undefined
178
+ dynamicRefQual: string | undefined
171
179
  calculation: string | undefined
172
180
  choiceField: string | undefined
173
181
  functionDefinition: string | undefined
@@ -263,6 +271,10 @@ type SysDictionaryProperties = {
263
271
  use_dynamic_default: boolean | undefined
264
272
  reference: string | undefined
265
273
  virtual: boolean | undefined
274
+ formula: string | undefined
275
+ virtual_type: 'script' | 'formula' | undefined
276
+ use_reference_qualifier: 'simple' | 'dynamic' | 'advanced' | undefined
277
+ dynamic_ref_qual: string | undefined
266
278
  default: string | undefined
267
279
  calculation: string | undefined
268
280
  choice_field: string | undefined
@@ -419,7 +431,11 @@ export const TablePlugin = Plugin.create({
419
431
  await factory.createRecord({
420
432
  source: file,
421
433
  table,
422
- properties: filterUndefinedProperties(rec),
434
+ properties: {
435
+ ...filterUndefinedProperties(rec),
436
+ // Decorate generated sys_db_object
437
+ ...(key === 'sysDbObject' || key === 'sysDictionary' ? { _bootstrap: true } : {}),
438
+ },
423
439
  })
424
440
  )
425
441
  }
@@ -528,29 +544,41 @@ export const TablePlugin = Plugin.create({
528
544
  override.transform(({ $ }) => ({
529
545
  baseTable: $.from('base_table'),
530
546
  default: $.from('default_value_override', 'default_value').map((flag, value) => {
531
- return flag.toBoolean()?.getValue() ? value.ifString()?.getValue() : undefined
547
+ return flag.ifDefined() && flag.toBoolean()?.getValue()
548
+ ? value.ifString()?.getValue()
549
+ : undefined
532
550
  }),
533
551
  calculation: $.from('calculation_override', 'calculation').map((flag, value) => {
534
- return flag.toBoolean()?.getValue() ? value.ifString()?.getValue() : undefined
552
+ return flag.ifDefined() && flag.toBoolean()?.getValue()
553
+ ? value.ifString()?.getValue()
554
+ : undefined
535
555
  }),
536
556
  referenceQualifier: $.from('reference_qual_override', 'reference_qual').map(
537
557
  (flag, value) => {
538
- return flag.toBoolean()?.getValue() ? value.ifString()?.getValue() : undefined
558
+ return flag.ifDefined() && flag.toBoolean()?.getValue()
559
+ ? value.ifString()?.getValue()
560
+ : undefined
539
561
  }
540
562
  ),
541
563
  readOnlyOption: $.from('read_only_option_override', 'read_only_option').map(
542
564
  (flag, value) => {
543
- return flag.toBoolean()?.getValue() ? value.ifString()?.getValue() : undefined
565
+ return flag.ifDefined() && flag.toBoolean()?.getValue()
566
+ ? value.ifString()?.getValue()
567
+ : undefined
544
568
  }
545
569
  ),
546
570
  dependent: $.from('dependent_override', 'dependent').map((flag, value) => {
547
- return flag.toBoolean()?.getValue() ? value.ifString()?.getValue() : undefined
571
+ return flag.ifDefined() && flag.toBoolean()?.getValue()
572
+ ? value.ifString()?.getValue()
573
+ : undefined
548
574
  }),
549
575
  mandatory: $.from('mandatory_override', 'mandatory').map((flag, value) => {
550
- return flag.toBoolean()?.getValue() ? value.toBoolean()?.getValue() : undefined
576
+ return flag.ifDefined() && flag.toBoolean()?.getValue()
577
+ ? value.toBoolean()?.getValue()
578
+ : undefined
551
579
  }),
552
580
  attributes: $.from('attributes_override', 'attributes').map((flag, attrs) => {
553
- if (!flag.toBoolean()?.getValue() || !attrs.isString()) {
581
+ if (!flag.ifDefined() || !flag.toBoolean()?.getValue() || !attrs.isString()) {
554
582
  return undefined
555
583
  }
556
584
  const result: { [key: string]: string | number | boolean } = {}
@@ -603,6 +631,43 @@ export const TablePlugin = Plugin.create({
603
631
  // Avoid replacing call expressions with variable statements
604
632
  const writeAsCallExpression =
605
633
  ts.Node.isNode(originalSource) && originalSource.isKind(ts.SyntaxKind.CallExpression)
634
+
635
+ const tableName = record.get('name').asString().getValue()
636
+ const isBootstrapDbObject = record.get('_bootstrap').ifBoolean()?.getValue() === true
637
+ const nonBootstrapColumns = columns.filter(
638
+ (col) => col.get('_bootstrap').ifBoolean()?.getValue() !== true
639
+ )
640
+
641
+ // Write as augmentation if we have sys_db_object from bootstrap and columns from elsewhere
642
+ const isAugmentation = isBootstrapDbObject && nonBootstrapColumns.length > 0
643
+ if (isAugmentation) {
644
+ const augmentsExpression = new CallExpressionShape({
645
+ source: record,
646
+ callee: 'Table',
647
+ exportName: tableName,
648
+ args: [
649
+ record.transform(({ $ }) => ({
650
+ augments: $.val(tableName),
651
+ schema: $.val(schema),
652
+ })),
653
+ ],
654
+ })
655
+ return {
656
+ success: true,
657
+ value: writeAsCallExpression
658
+ ? augmentsExpression
659
+ : new VariableStatementShape({
660
+ source: record,
661
+ isExported: true,
662
+ variableName: new IdentifierShape({
663
+ source: record,
664
+ name: tableName,
665
+ }),
666
+ initializer: augmentsExpression,
667
+ }),
668
+ }
669
+ }
670
+
606
671
  const callExpression = new CallExpressionShape({
607
672
  source: record,
608
673
  callee: 'Table',
@@ -725,7 +790,7 @@ export const TablePlugin = Plugin.create({
725
790
  ).def([]),
726
791
  label: $.map(
727
792
  (label) =>
728
- label.ifString() ??
793
+ label.ifString()?.ifNotEmpty() ??
729
794
  (tableDocumentation.length &&
730
795
  !isDefaultDocumentation('', tableDocumentation, config.defaultLanguage)
731
796
  ? tableDocumentation.map((doc) =>
@@ -804,12 +869,13 @@ export const TablePlugin = Plugin.create({
804
869
  }
805
870
  },
806
871
  async toFile(record, { descendants, config, transform }) {
807
- if (config.tableOutputFormat !== 'bootstrap' || config.type === 'configuration' || record.isDeleted()) {
808
- // Defer to record plugin
872
+ if (record.isDeleted()) {
809
873
  return { success: false }
810
874
  }
811
875
 
812
- const tableName = record.get('name').asString().getValue()
876
+ const augmentsValue = record.get('augments').ifString()?.getValue()
877
+ const isAugmentation = augmentsValue !== undefined
878
+ const tableName = augmentsValue ?? record.get('name').asString().getValue()
813
879
  const columns = descendants.query('sys_dictionary')
814
880
  const choices = descendants.query('sys_choice')
815
881
  const indexes = descendants.query('sys_index')
@@ -817,6 +883,71 @@ export const TablePlugin = Plugin.create({
817
883
  const licensing = descendants.query('ua_table_licensing_config')
818
884
  const autoNumber = descendants.query('sys_number')
819
885
  const overrides = descendants.query('sys_dictionary_override')
886
+
887
+ const [
888
+ documentationFiles,
889
+ licensingFiles,
890
+ autoNumberFiles,
891
+ overrideFiles,
892
+ sysDictionaryFiles,
893
+ sysDbObjectFiles,
894
+ ] = await Promise.all([
895
+ generateRecordXml(
896
+ documentation.filter(
897
+ (doc) =>
898
+ !isDefaultDocumentation(
899
+ doc.get('element')?.toString().getValue(),
900
+ [doc],
901
+ config.defaultLanguage
902
+ )
903
+ ),
904
+ config,
905
+ transform
906
+ ),
907
+ generateRecordXml(
908
+ licensing.filter((l) => !isDefaultLicenseConfig(tableName, l)),
909
+ config,
910
+ transform
911
+ ),
912
+ generateRecordXml(autoNumber, config, transform),
913
+ generateRecordXml(overrides, config, transform),
914
+ generateRecordXml(columns, config, transform, ['_bootstrap']),
915
+ isAugmentation
916
+ ? Promise.resolve([])
917
+ : generateRecordXml([record], config, transform, ['augments', '_bootstrap']),
918
+ ])
919
+
920
+ if (config.type === 'configuration') {
921
+ // No bootstrap XML for configuration projects, just write independent record XML
922
+ const choiceSets = descendants.query('sys_choice_set')
923
+ const [choiceSetFiles, indexFiles] = await Promise.all([
924
+ Promise.all(
925
+ choiceSets.map((cs) => {
926
+ const csChoices = choices.filter(
927
+ (c) =>
928
+ c.get('name').toString().getValue() === cs.get('name').toString().getValue() &&
929
+ c.get('element').toString().getValue() ===
930
+ cs.get('element').toString().getValue()
931
+ )
932
+ return generateChoiceSetFile(cs, csChoices, config, transform)
933
+ })
934
+ ),
935
+ generateRecordXml(indexes, config, transform),
936
+ ])
937
+ return {
938
+ success: true,
939
+ value: [
940
+ ...sysDictionaryFiles,
941
+ ...choiceSetFiles,
942
+ ...indexFiles,
943
+ ...sysDbObjectFiles,
944
+ ...documentationFiles,
945
+ ...licensingFiles,
946
+ ...autoNumberFiles,
947
+ ...overrideFiles,
948
+ ],
949
+ }
950
+ }
820
951
  let displayColumn: string | undefined
821
952
  let collectionRecord: Record | undefined
822
953
  const elements: XMLBuilder[] = []
@@ -890,6 +1021,23 @@ export const TablePlugin = Plugin.create({
890
1021
  ['hint', column.get('hint').ifString()?.getValue()],
891
1022
  ['help', column.get('help').ifString()?.getValue()],
892
1023
  ['virtual', column.get('virtual').ifBoolean()?.getValue().toString()],
1024
+ ['formula', column.get('formula').ifString()?.getValue() || undefined],
1025
+ [
1026
+ 'virtual_type',
1027
+ column.get('virtual_type').ifString()?.getValue() === 'script'
1028
+ ? undefined
1029
+ : column.get('virtual_type').ifString()?.getValue(),
1030
+ ],
1031
+ [
1032
+ 'use_reference_qualifier',
1033
+ column.get('use_reference_qualifier').ifString()?.getValue() === 'simple'
1034
+ ? undefined
1035
+ : column.get('use_reference_qualifier').ifString()?.getValue(),
1036
+ ],
1037
+ [
1038
+ 'dynamic_ref_qual',
1039
+ column.get('dynamic_ref_qual').ifDefined()?.toString().getValue() || undefined,
1040
+ ],
893
1041
  ['widget', column.get('widget').ifString()?.getValue()],
894
1042
  ['reference_qual', column.get('reference_qual').ifString()?.getValue()],
895
1043
  ['reference_qual_condition', column.get('reference_qual_condition').ifString()?.getValue()],
@@ -982,28 +1130,6 @@ export const TablePlugin = Plugin.create({
982
1130
  ]
983
1131
  ).end({ prettyPrint: true })
984
1132
 
985
- const documentationFiles = await generateRecordXml(
986
- documentation.filter(
987
- (doc) =>
988
- !isDefaultDocumentation(
989
- doc.get('element')?.toString().getValue(),
990
- [doc],
991
- config.defaultLanguage
992
- )
993
- ),
994
- config,
995
- transform
996
- )
997
-
998
- const licensingFiles = await generateRecordXml(
999
- licensing.filter((licensing) => !isDefaultLicenseConfig(tableName, licensing)),
1000
- config,
1001
- transform
1002
- )
1003
-
1004
- const autoNumberFiles = await generateRecordXml(autoNumber, config, transform)
1005
- const overrideFiles = await generateRecordXml(overrides, config, transform)
1006
-
1007
1133
  return {
1008
1134
  success: true,
1009
1135
  value: [
@@ -1017,6 +1143,8 @@ export const TablePlugin = Plugin.create({
1017
1143
  ...licensingFiles,
1018
1144
  ...autoNumberFiles,
1019
1145
  ...overrideFiles,
1146
+ ...sysDictionaryFiles,
1147
+ ...sysDbObjectFiles,
1020
1148
  ],
1021
1149
  }
1022
1150
  },
@@ -1068,7 +1196,9 @@ export const TablePlugin = Plugin.create({
1068
1196
  const table = callExpression.getArgument(0).asObject().withAliasedKeys(tableAliases)
1069
1197
  generateDeprecatedDiagnostics(table, diagnostics)
1070
1198
  const relatedRecords: Record[] = []
1071
- const tableName = table.get('name').asString()
1199
+ const augments = table.get('augments').ifString()
1200
+ const isAugmentation = augments !== undefined
1201
+ const tableName = isAugmentation ? augments! : table.get('name').asString()
1072
1202
  if (!tableName.getValue().match(tableNameRegex)) {
1073
1203
  diagnostics.error(
1074
1204
  table.get('name'),
@@ -1078,16 +1208,28 @@ export const TablePlugin = Plugin.create({
1078
1208
 
1079
1209
  let ignoreColumnNameCheck = false
1080
1210
  const scopeName = config.scope
1081
- const scopeRegex = new RegExp(`^${scopeName}_`)
1082
- const globalRegex = /^u_/
1083
- const tableNameMatch = tableName.getValue().match(scopeRegex)
1084
- if (!tableNameMatch && !isSNScope(scopeName) && scopeName !== 'global') {
1211
+ const scopePrefix = scopeName === 'global' ? 'u_' : `${scopeName}_`
1212
+ const prefixRegex = new RegExp(`^${scopePrefix}`)
1213
+ const tableNameMatch = tableName.getValue().match(prefixRegex)
1214
+ if (isAugmentation) {
1215
+ if (tableNameMatch && scopeName !== 'global') {
1216
+ const nameNode = tableName.getOriginalNode()
1217
+ if (!nameNode?.getParentIfKind(ts.SyntaxKind.AsExpression)) {
1218
+ diagnostics.error(
1219
+ table.get('augments'),
1220
+ `'augments' flag set on in-scope table '${tableName.getValue()}'`
1221
+ )
1222
+ } else {
1223
+ ignoreColumnNameCheck = true
1224
+ }
1225
+ }
1226
+ } else if (!tableNameMatch && !isSNScope(scopeName) && scopeName !== 'global') {
1085
1227
  const nameNode = tableName.getOriginalNode()
1086
1228
  if (nameNode && !nameNode.getParentIfKind(ts.SyntaxKind.AsExpression)) {
1087
1229
  // 'sn' and 'now' scoped apps ignore this validation
1088
1230
  diagnostics.error(
1089
1231
  table.get('name'),
1090
- `'name' property should start with scope prefix '${scopeName}_'`
1232
+ `'name' property should start with scope prefix '${scopePrefix}'`
1091
1233
  )
1092
1234
  } else {
1093
1235
  ignoreColumnNameCheck = true
@@ -1106,13 +1248,11 @@ export const TablePlugin = Plugin.create({
1106
1248
  )
1107
1249
  }
1108
1250
 
1109
- const globalTableNameMatch = tableName.getValue().match(globalRegex)
1110
1251
  let anyNonPrefixedGlobalColumn = false
1111
-
1112
- if (scopeName === 'global' && !globalTableNameMatch) {
1252
+ if (!isAugmentation && scopeName === 'global' && !tableNameMatch) {
1113
1253
  const schema = table.get('schema').asObject()
1114
1254
  for (const [name, _] of schema.entries()) {
1115
- if (!name.match(globalRegex)) {
1255
+ if (!name.match(prefixRegex)) {
1116
1256
  anyNonPrefixedGlobalColumn = true
1117
1257
  break
1118
1258
  }
@@ -1124,7 +1264,7 @@ export const TablePlugin = Plugin.create({
1124
1264
  `Global table 'name' property should start with custom prefix 'u_'`
1125
1265
  )
1126
1266
  }
1127
- } else if (scopeName === 'global') {
1267
+ } else if (scopeName === 'global' && !isAugmentation) {
1128
1268
  // Global table starts with custom prefix `u_`, allow any column name prefix
1129
1269
  ignoreColumnNameCheck = true
1130
1270
  }
@@ -1217,22 +1357,30 @@ export const TablePlugin = Plugin.create({
1217
1357
  relatedRecords.push(overrideRecord)
1218
1358
  } else {
1219
1359
  // Handle regular column - create sys_dictionary record
1220
- if (
1360
+ if (isAugmentation && !tableNameMatch) {
1361
+ if (!isSNScope(scopeName) && !name.match(prefixRegex)) {
1362
+ diagnostics.error(
1363
+ column.getOriginalNode().getParentIfKind(ts.SyntaxKind.PropertyAssignment) ??
1364
+ column,
1365
+ `Column name '${name}' must be prefixed with '${scopePrefix}' when augmenting a table`
1366
+ )
1367
+ }
1368
+ } else if (
1221
1369
  !ignoreColumnNameCheck &&
1222
1370
  !tableNameMatch &&
1223
1371
  !isSNScope(scopeName) &&
1224
1372
  scopeName !== 'global' &&
1225
- !name.match(scopeRegex)
1373
+ !name.match(prefixRegex)
1226
1374
  ) {
1227
1375
  // 'sn' and 'now' scoped apps ignore this validation
1228
1376
  diagnostics.error(
1229
1377
  column.getOriginalNode().getParentIfKind(ts.SyntaxKind.PropertyAssignment) ?? column,
1230
- `Column name should be prefixed with scope '${scopeName}_' if table name does not contain prefix`
1378
+ `Column name should be prefixed with scope '${scopePrefix}' if table name does not contain prefix`
1231
1379
  )
1232
- } else if (scopeName === 'global' && !globalTableNameMatch && !name.match(globalRegex)) {
1380
+ } else if (scopeName === 'global' && !tableNameMatch && !name.match(prefixRegex)) {
1233
1381
  diagnostics.error(
1234
1382
  column.getOriginalNode().getParentIfKind(ts.SyntaxKind.PropertyAssignment) ?? column,
1235
- `Column name should be prefixed with 'u_' custom prefix if table name does not contain this prefix, such as when adding columns to an existing global table`
1383
+ `Column name should be prefixed with '${scopePrefix}' custom prefix if table name does not contain this prefix, such as when adding columns to an existing global table`
1236
1384
  )
1237
1385
  }
1238
1386
  const display = table.get('display').ifString()?.getValue() === name
@@ -1409,29 +1557,32 @@ export const TablePlugin = Plugin.create({
1409
1557
  }
1410
1558
 
1411
1559
  // sys_dictionary (collection)
1412
- relatedRecords.push(
1413
- await factory.createRecord({
1414
- source: statement ?? callExpression,
1415
- table: 'sys_dictionary',
1416
- properties: table.transform(({ $ }) => ({
1417
- name: $,
1418
- element: $.val(undefined),
1419
- internal_type: $.def('collection'),
1420
- attributes: $.map((attributes) => {
1421
- if (!attributes.isObject()) {
1422
- return undefined
1423
- }
1424
- const attributesObj = attributes.asObject().getValue()
1425
- return Object.entries(attributesObj)
1426
- .map(([key, value]) => `${key}=${value}`)
1427
- .join(',')
1428
- }).def(''),
1429
- audit: $.def(false),
1430
- read_only: $.from('readOnly').def(false),
1431
- text_index: $.from('textIndex').def(false),
1432
- })),
1433
- })
1434
- )
1560
+ if (!isAugmentation) {
1561
+ relatedRecords.push(
1562
+ await factory.createRecord({
1563
+ source: statement ?? callExpression,
1564
+ table: 'sys_dictionary',
1565
+ properties: table.transform(({ $ }) => ({
1566
+ name: $,
1567
+ element: $.val(undefined),
1568
+ internal_type: $.def('collection'),
1569
+ active: $.val(true),
1570
+ attributes: $.map((attributes) => {
1571
+ if (!attributes.isObject()) {
1572
+ return undefined
1573
+ }
1574
+ const attributesObj = attributes.asObject().getValue()
1575
+ return Object.entries(attributesObj)
1576
+ .map(([key, value]) => `${key}=${value}`)
1577
+ .join(',')
1578
+ }).def(''),
1579
+ audit: $.def(false),
1580
+ read_only: $.from('readOnly').def(false),
1581
+ text_index: $.from('textIndex').def(false),
1582
+ })),
1583
+ })
1584
+ )
1585
+ }
1435
1586
 
1436
1587
  const hasAction = (actionName: string, actions: Shape): boolean | undefined => {
1437
1588
  return actions
@@ -1481,8 +1632,12 @@ export const TablePlugin = Plugin.create({
1481
1632
  is_extendable: $.from('extensible').toBoolean().def(false),
1482
1633
  label: $.map((label) => getLabelForDefaultLanguage(label, config.defaultLanguage)),
1483
1634
  live_feed_enabled: $.from('liveFeed').toBoolean().def(false),
1484
- name: $,
1635
+ name: $.from('name', 'augments').map(
1636
+ (nameVal, augmentsVal) => augmentsVal.ifString() ?? nameVal
1637
+ ),
1485
1638
  scriptable_table: $.from('scriptableTable').toBoolean().def(false),
1639
+ // Controls toFile output, not written to record XML
1640
+ augments: $.from('augments'),
1486
1641
  })),
1487
1642
  })
1488
1643
 
@@ -1569,6 +1724,18 @@ function parseTableBootstrapXml(xml: unknown): TableDefinition | null {
1569
1724
  useDynamicDefault: column['@_use_dynamic_default'],
1570
1725
  reference: column['@_reference'],
1571
1726
  isVirtual: column['@_virtual'],
1727
+ formula: column['@_formula'],
1728
+ virtualType:
1729
+ column['@_virtual_type'] === 'script' || column['@_virtual_type'] === 'formula'
1730
+ ? column['@_virtual_type']
1731
+ : undefined,
1732
+ useReferenceQualifier:
1733
+ column['@_use_reference_qualifier'] === 'simple' ||
1734
+ column['@_use_reference_qualifier'] === 'dynamic' ||
1735
+ column['@_use_reference_qualifier'] === 'advanced'
1736
+ ? column['@_use_reference_qualifier']
1737
+ : undefined,
1738
+ dynamicRefQual: column['@_dynamic_ref_qual'],
1572
1739
  calculation: column['@_calculation'],
1573
1740
  choiceField: column['@_choice_field'],
1574
1741
  functionDefinition: column['@_function_definition'],
@@ -1712,6 +1879,10 @@ function tableDefToRecordProperties(
1712
1879
  use_dynamic_default: column.useDynamicDefault,
1713
1880
  reference: column.reference,
1714
1881
  virtual: column.isVirtual,
1882
+ formula: column.formula,
1883
+ virtual_type: column.virtualType,
1884
+ use_reference_qualifier: column.useReferenceQualifier,
1885
+ dynamic_ref_qual: column.dynamicRefQual,
1715
1886
  default: column.defaultValue,
1716
1887
  calculation: column.calculation,
1717
1888
  choice_field: column.choiceField,
@@ -1794,6 +1965,10 @@ function tableDefToRecordProperties(
1794
1965
  use_dynamic_default: undefined,
1795
1966
  reference: undefined,
1796
1967
  virtual: undefined,
1968
+ formula: undefined,
1969
+ virtual_type: undefined,
1970
+ use_reference_qualifier: undefined,
1971
+ dynamic_ref_qual: undefined,
1797
1972
  default: undefined,
1798
1973
  calculation: undefined,
1799
1974
  choice_field: undefined,
@@ -1852,7 +2027,7 @@ function addTableToGlobalGeneratedFile(tableArg: ObjectShape, sourceFilePath: st
1852
2027
  >,
1853
2028
  }
1854
2029
  const generatedTableFile: ts.SourceFile | undefined = compiler.getGeneratedTableFile()
1855
- const tableName = tableArg.get('name').asString().getValue()
2030
+ const tableName = tableArg.get('augments').ifString()?.getValue() ?? tableArg.get('name').asString().getValue()
1856
2031
  if (!(tableName.trim().length > 0 && tableName.match(tableNameRegex))) {
1857
2032
  return
1858
2033
  }
@@ -1933,7 +2108,8 @@ function createElement(name: string, attributes: [string, string][] = [], childr
1933
2108
  async function generateRecordXml(
1934
2109
  records: Record[],
1935
2110
  config: { scope: string; scopeId: string },
1936
- transform: Transform
2111
+ transform: Transform,
2112
+ excludeFields: string[] = []
1937
2113
  ): Promise<OutputFile[]> {
1938
2114
  const files: OutputFile[] = []
1939
2115
  for (const record of records) {
@@ -1944,6 +2120,7 @@ async function generateRecordXml(
1944
2120
  record
1945
2121
  .entries()
1946
2122
  .sort(([a], [b]) => a.localeCompare(b))
2123
+ .filter(([prop]) => excludeFields.length === 0 || !excludeFields.includes(prop))
1947
2124
  .forEach(([prop, shape]) => builder.field(prop, shape))
1948
2125
 
1949
2126
  files.push({