@servicenow/sdk-build-plugins 4.0.2 → 4.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 (111) hide show
  1. package/dist/acl-plugin.js +23 -16
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/application-menu-plugin.js +15 -14
  4. package/dist/application-menu-plugin.js.map +1 -1
  5. package/dist/atf/test-plugin.js +37 -34
  6. package/dist/atf/test-plugin.js.map +1 -1
  7. package/dist/basic-syntax-plugin.js +50 -1
  8. package/dist/basic-syntax-plugin.js.map +1 -1
  9. package/dist/business-rule-plugin.js +12 -10
  10. package/dist/business-rule-plugin.js.map +1 -1
  11. package/dist/claims-plugin.d.ts +3 -0
  12. package/dist/claims-plugin.js +95 -0
  13. package/dist/claims-plugin.js.map +1 -0
  14. package/dist/client-script-plugin.js +1 -1
  15. package/dist/client-script-plugin.js.map +1 -1
  16. package/dist/column/column-to-record.d.ts +3 -3
  17. package/dist/column/column-to-record.js +17 -16
  18. package/dist/column/column-to-record.js.map +1 -1
  19. package/dist/column-plugin.js +86 -8
  20. package/dist/column-plugin.js.map +1 -1
  21. package/dist/cross-scope-privilege-plugin.js +17 -3
  22. package/dist/cross-scope-privilege-plugin.js.map +1 -1
  23. package/dist/html-import-plugin.js +8 -1
  24. package/dist/html-import-plugin.js.map +1 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +3 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/json-plugin.d.ts +7 -2
  29. package/dist/json-plugin.js +28 -12
  30. package/dist/json-plugin.js.map +1 -1
  31. package/dist/list-plugin.js +29 -17
  32. package/dist/list-plugin.js.map +1 -1
  33. package/dist/now-config-plugin.js +14 -5
  34. package/dist/now-config-plugin.js.map +1 -1
  35. package/dist/now-include-plugin.d.ts +6 -7
  36. package/dist/now-include-plugin.js +59 -26
  37. package/dist/now-include-plugin.js.map +1 -1
  38. package/dist/now-ref-plugin.js +1 -1
  39. package/dist/now-ref-plugin.js.map +1 -1
  40. package/dist/package-json-plugin.js +5 -7
  41. package/dist/package-json-plugin.js.map +1 -1
  42. package/dist/property-plugin.js +1 -1
  43. package/dist/property-plugin.js.map +1 -1
  44. package/dist/record-plugin.js +23 -9
  45. package/dist/record-plugin.js.map +1 -1
  46. package/dist/repack/index.d.ts +2 -0
  47. package/dist/repack/index.js +8 -0
  48. package/dist/repack/index.js.map +1 -1
  49. package/dist/rest-api-plugin.js +12 -12
  50. package/dist/rest-api-plugin.js.map +1 -1
  51. package/dist/role-plugin.js +18 -16
  52. package/dist/role-plugin.js.map +1 -1
  53. package/dist/script-action-plugin.js +1 -1
  54. package/dist/script-action-plugin.js.map +1 -1
  55. package/dist/script-include-plugin.js +6 -4
  56. package/dist/script-include-plugin.js.map +1 -1
  57. package/dist/server-module-plugin/index.d.ts +3 -1
  58. package/dist/server-module-plugin/index.js +110 -28
  59. package/dist/server-module-plugin/index.js.map +1 -1
  60. package/dist/service-portal/angular-provider-plugin.js +3 -3
  61. package/dist/service-portal/angular-provider-plugin.js.map +1 -1
  62. package/dist/service-portal/dependency-plugin.js +9 -12
  63. package/dist/service-portal/dependency-plugin.js.map +1 -1
  64. package/dist/service-portal/widget-plugin.js +8 -8
  65. package/dist/service-portal/widget-plugin.js.map +1 -1
  66. package/dist/static-content-plugin.d.ts +2 -0
  67. package/dist/static-content-plugin.js +16 -13
  68. package/dist/static-content-plugin.js.map +1 -1
  69. package/dist/table-plugin.js +55 -33
  70. package/dist/table-plugin.js.map +1 -1
  71. package/dist/ui-action-plugin.js +29 -19
  72. package/dist/ui-action-plugin.js.map +1 -1
  73. package/dist/ui-page-plugin.js +6 -3
  74. package/dist/ui-page-plugin.js.map +1 -1
  75. package/dist/user-preference-plugin.js +2 -2
  76. package/dist/user-preference-plugin.js.map +1 -1
  77. package/package.json +5 -5
  78. package/src/acl-plugin.ts +31 -27
  79. package/src/application-menu-plugin.ts +39 -39
  80. package/src/atf/test-plugin.ts +82 -81
  81. package/src/basic-syntax-plugin.ts +54 -1
  82. package/src/business-rule-plugin.ts +74 -73
  83. package/src/claims-plugin.ts +116 -0
  84. package/src/client-script-plugin.ts +1 -1
  85. package/src/column/column-to-record.ts +59 -53
  86. package/src/column-plugin.ts +93 -8
  87. package/src/cross-scope-privilege-plugin.ts +18 -3
  88. package/src/html-import-plugin.ts +12 -2
  89. package/src/index.ts +2 -1
  90. package/src/json-plugin.ts +35 -15
  91. package/src/list-plugin.ts +39 -30
  92. package/src/now-config-plugin.ts +19 -8
  93. package/src/now-include-plugin.ts +64 -30
  94. package/src/now-ref-plugin.ts +1 -1
  95. package/src/package-json-plugin.ts +7 -6
  96. package/src/property-plugin.ts +1 -1
  97. package/src/record-plugin.ts +25 -9
  98. package/src/repack/index.ts +14 -0
  99. package/src/rest-api-plugin.ts +15 -15
  100. package/src/role-plugin.ts +15 -15
  101. package/src/script-action-plugin.ts +1 -1
  102. package/src/script-include-plugin.ts +7 -4
  103. package/src/server-module-plugin/index.ts +158 -43
  104. package/src/service-portal/angular-provider-plugin.ts +12 -10
  105. package/src/service-portal/dependency-plugin.ts +12 -13
  106. package/src/service-portal/widget-plugin.ts +36 -30
  107. package/src/static-content-plugin.ts +12 -10
  108. package/src/table-plugin.ts +71 -56
  109. package/src/ui-action-plugin.ts +49 -33
  110. package/src/ui-page-plugin.ts +6 -3
  111. package/src/user-preference-plugin.ts +2 -2
@@ -0,0 +1,116 @@
1
+ import { Plugin, type Project, type NowConfig, type Record } from '@servicenow/sdk-build-core'
2
+ import { JsonFileShape } from './json-plugin'
3
+ export const CLAIMS_FILE = '.claims.json'
4
+
5
+ function getClaimsPath(project: Project, config: NowConfig): string {
6
+ return project.resolvePath(config.generatedDir, CLAIMS_FILE)
7
+ }
8
+
9
+ export const ClaimsPlugin = Plugin.create({
10
+ name: 'ClaimsPlugin',
11
+ files: [
12
+ {
13
+ entryPoint: true,
14
+ matcher: (path, { project, config }) => path === getClaimsPath(project, config),
15
+ },
16
+ ],
17
+ records: {
18
+ sys_claim: {
19
+ coalesce: ['claim_owner_scope', 'previous_claim_scope', 'metadata_update_name'],
20
+ toFile() {
21
+ // Claims don't get their own file, they're appended to the corresponding record's payload
22
+ return { success: true, value: [] }
23
+ },
24
+ toShape(root, { descendants, database, project, config }) {
25
+ descendants.insert(...database.query('sys_claim')) // Adding them as descendants will mark them as handled so they don't get reprocessed
26
+ const unsorted: globalThis.Record<string, string[]> = {}
27
+ for (const claim of descendants) {
28
+ const updateName = claim.get('metadata_update_name').asString().getValue().trim()
29
+ const timestamp = claim.get('claim_timestamp').asString().getValue().trim()
30
+ const ownerScope = claim.get('claim_owner_scope').asString().getValue().trim()
31
+ const previousScope = claim.get('previous_claim_scope').asString().getValue().trim()
32
+ const previousVersion = claim.get('previous_claim_app_version').ifString()?.getValue().trim() ?? ''
33
+
34
+ const array = (unsorted[updateName] ??= [])
35
+ array.push(`${timestamp};${ownerScope};${previousScope};${previousVersion}`)
36
+ }
37
+
38
+ const sorted = Object.fromEntries(
39
+ Object.entries(unsorted).map(([updateName, claims]) => [updateName, claims.sort()])
40
+ )
41
+
42
+ return {
43
+ success: true,
44
+ value: new JsonFileShape({
45
+ source: root,
46
+ path: getClaimsPath(project, config),
47
+ content: JSON.stringify(sorted),
48
+ json: sorted,
49
+ }),
50
+ }
51
+ },
52
+ },
53
+ },
54
+ shapes: [
55
+ {
56
+ shape: JsonFileShape,
57
+ async toRecord(file, { project, config, factory }) {
58
+ if (file.getPath() !== getClaimsPath(project, config)) {
59
+ return { success: false }
60
+ }
61
+
62
+ const allClaims = file.getJson()
63
+ const records: Record[] = []
64
+ for (const [updateName, claims] of allClaims.entries()) {
65
+ for (const claim of claims
66
+ .asArray(
67
+ `Claims entry for ${updateName} is malformed. Expected array, but received: ${claims.getDescription()}`
68
+ )
69
+ .getElements()) {
70
+ const [timestamp, ownerScope, previousScope, previousVersion] = claim
71
+ .asString(
72
+ `Claim for ${updateName} is malformed. Expected string, but received: ${claim.getDescription()}`
73
+ )
74
+ .split(';')
75
+ .map((part) => part.trim())
76
+
77
+ if (!timestamp) {
78
+ throw new Error(
79
+ `Claim for ${updateName} is malformed. Expected timestamp at first position.`
80
+ )
81
+ }
82
+
83
+ if (!ownerScope) {
84
+ throw new Error(
85
+ `Claim for ${updateName} is malformed. Expected owner scope at second position.`
86
+ )
87
+ }
88
+
89
+ if (!previousScope) {
90
+ throw new Error(
91
+ `Claim for ${updateName} is malformed. Expected previous scope at third position.`
92
+ )
93
+ }
94
+
95
+ records.push(
96
+ await factory.createRecord({
97
+ source: file,
98
+ table: 'sys_claim',
99
+ properties: {
100
+ metadata_update_name: updateName,
101
+ claim_timestamp: timestamp,
102
+ claim_owner_scope: ownerScope,
103
+ previous_claim_scope: previousScope,
104
+ previous_claim_app_version: previousVersion || undefined,
105
+ },
106
+ })
107
+ )
108
+ }
109
+ }
110
+
111
+ const [first, ...rest] = records
112
+ return first ? { success: true, value: first.with(...rest) } : { success: false }
113
+ },
114
+ },
115
+ ],
116
+ })
@@ -119,7 +119,7 @@ export const ClientScriptPlugin = Plugin.create({
119
119
 
120
120
  return {
121
121
  success: true,
122
- value: factory.createRecord({
122
+ value: await factory.createRecord({
123
123
  source: callExpression,
124
124
  table: 'sys_script_client',
125
125
  explicitId: clientScript.get('$id'),
@@ -3,12 +3,13 @@ import {
3
3
  type CallExpressionShape,
4
4
  type Diagnostics,
5
5
  type Factory,
6
+ type Record,
6
7
  StringShape,
7
8
  } from '@servicenow/sdk-build-core'
8
9
  import { generateDeprecatedDiagnostics } from '../utils'
9
10
  import { generatePlural } from '../column-plugin'
10
11
 
11
- export function getDocumentationRecords(
12
+ export async function getDocumentationRecords(
12
13
  callExpression: CallExpressionShape,
13
14
  aliasedKeys: globalThis.Record<string, string[]>,
14
15
  factory: Factory,
@@ -20,7 +21,7 @@ export function getDocumentationRecords(
20
21
  const columnName = column.get('name')
21
22
  if (!documentation.if([ArrayShape, StringShape])) {
22
23
  return [
23
- factory.createRecord({
24
+ await factory.createRecord({
24
25
  source: callExpression,
25
26
  table: 'sys_documentation',
26
27
  properties: column.transform(({ $ }) => ({
@@ -39,30 +40,30 @@ export function getDocumentationRecords(
39
40
  }
40
41
 
41
42
  if (documentation.ifArray()) {
42
- return (
43
- documentation
44
- .asArray()
45
- .getElements()
46
- .map((documentation) => {
47
- const docObject = documentation.asObject().withAliasedKeys(aliasedKeys)
48
- generateDeprecatedDiagnostics(docObject, diagnostics)
49
- return factory.createRecord({
50
- source: callExpression,
51
- table: 'sys_documentation',
52
- properties: docObject.transform(({ $ }) => ({
53
- name: $.val(column.get('table')),
54
- element: $.val(column.get('name')),
55
- language: $,
56
- hint: $,
57
- help: $,
58
- label: $,
59
- plural: $,
60
- url: $,
61
- url_target: $.from('urlTarget'),
62
- })),
63
- })
64
- }) ?? []
65
- )
43
+ const docRecords: Record[] = []
44
+ for (const doc of documentation.asArray().getElements()) {
45
+ const docObject = doc.asObject().withAliasedKeys(aliasedKeys)
46
+ generateDeprecatedDiagnostics(docObject, diagnostics)
47
+ docRecords.push(
48
+ await factory.createRecord({
49
+ source: callExpression,
50
+ table: 'sys_documentation',
51
+ properties: docObject.transform(({ $ }) => ({
52
+ name: $.val(column.get('table')),
53
+ element: $.val(column.get('name')),
54
+ language: $,
55
+ hint: $,
56
+ help: $,
57
+ label: $,
58
+ plural: $,
59
+ url: $,
60
+ url_target: $.from('urlTarget'),
61
+ })),
62
+ })
63
+ )
64
+ }
65
+
66
+ return docRecords
66
67
  }
67
68
 
68
69
  if (!generateDefaultDoc) {
@@ -70,7 +71,7 @@ export function getDocumentationRecords(
70
71
  }
71
72
 
72
73
  return [
73
- factory.createRecord({
74
+ await factory.createRecord({
74
75
  source: callExpression,
75
76
  table: 'sys_documentation',
76
77
  properties: column.transform(({ $ }) => ({
@@ -86,7 +87,7 @@ export function getDocumentationRecords(
86
87
  ]
87
88
  }
88
89
 
89
- export function getChoiceRecords(
90
+ export async function getChoiceRecords(
90
91
  callExpression: CallExpressionShape,
91
92
  aliasedKeys: globalThis.Record<string, string[]>,
92
93
  factory: Factory,
@@ -98,34 +99,39 @@ export function getChoiceRecords(
98
99
  return []
99
100
  }
100
101
 
101
- return Object.keys(choiceArg.getValue()).map((choiceValue) => {
102
+ const records: Record[] = []
103
+ for (const choiceValue of Object.keys(choiceArg.getValue())) {
102
104
  // TODO Should have proper type here
103
105
  const choice = choiceArg.get(choiceValue)
104
106
  if (choice.isObject()) {
105
107
  generateDeprecatedDiagnostics(choice.withAliasedKeys(aliasedKeys), diagnostics)
106
108
  }
107
- return factory.createRecord({
108
- source: callExpression,
109
- table: 'sys_choice',
110
- properties: choice.isString()
111
- ? {
112
- name: column.get('table').asString().getValue(),
113
- element: column.get('name').asString().getValue(),
114
- label: choice,
115
- value: choiceValue,
116
- }
117
- : choice.asObject().transform(({ $ }) => ({
118
- name: $.val(column.get('table')),
119
- element: $.val(column.get('name')),
120
- label: $,
121
- value: $.val(choiceValue),
122
- sequence: $,
123
- dependent_value: $.from('dependentValue'),
124
- hint: $,
125
- inactive: $.toBoolean().def(false),
126
- inactive_on_update: $.from('inactiveOnUpdate').toBoolean().def(false),
127
- language: $.def('en'),
128
- })),
129
- })
130
- })
109
+ records.push(
110
+ await factory.createRecord({
111
+ source: callExpression,
112
+ table: 'sys_choice',
113
+ properties: choice.isString()
114
+ ? {
115
+ name: column.get('table').asString().getValue(),
116
+ element: column.get('name').asString().getValue(),
117
+ label: choice,
118
+ value: choiceValue,
119
+ }
120
+ : choice.asObject().transform(({ $ }) => ({
121
+ name: $.val(column.get('table')),
122
+ element: $.val(column.get('name')),
123
+ label: $,
124
+ value: $.val(choiceValue),
125
+ sequence: $,
126
+ dependent_value: $.from('dependentValue'),
127
+ hint: $,
128
+ inactive: $.toBoolean().def(false),
129
+ inactive_on_update: $.from('inactiveOnUpdate').toBoolean().def(false),
130
+ language: $.def('en'),
131
+ })),
132
+ })
133
+ )
134
+ }
135
+
136
+ return records
131
137
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from './column/column-helper'
11
11
  import { ModuleFunctionShape } from './server-module-plugin'
12
12
  import { generateDeprecatedDiagnostics } from './utils'
13
+ import { create } from 'xmlbuilder2'
13
14
 
14
15
  const PLURAL_EQUALS_LABEL = [
15
16
  's',
@@ -63,8 +64,80 @@ const documentationAliases = {
63
64
  export const ColumnPlugin = Plugin.create({
64
65
  name: 'ColumnPlugin',
65
66
  records: {
66
- sys_choice: { coalesce: ['name', 'element', 'value'] },
67
- sys_documentation: { coalesce: ['name', 'element', 'language'] },
67
+ sys_choice: {
68
+ coalesce: ['name', 'element', 'value'],
69
+ getUpdateName: (record) => ({
70
+ success: true,
71
+ value: `sys_choice_${record.get('name').getValue()}_${record.get('element').getValue()}`,
72
+ }),
73
+ },
74
+ sys_choice_set: {
75
+ coalesce: ['name', 'element'],
76
+ relationships: {
77
+ sys_choice: {
78
+ via: {
79
+ name: 'name',
80
+ element: 'element',
81
+ },
82
+ descendant: true,
83
+ },
84
+ },
85
+ getUpdateName: (record) => ({
86
+ success: true,
87
+ // Seems odd, but according to UpdateName.java, sys_choice and sys_choice_set share the same update naming logic
88
+ value: `sys_choice_${record.get('name').getValue()}_${record.get('element').getValue()}`,
89
+ }),
90
+ async toFile(choiceSet, { config, descendants, transform }) {
91
+ if (config.tableOutputFormat === 'bootstrap') {
92
+ // Defer to table plugin
93
+ return { success: false }
94
+ }
95
+
96
+ const tableName = choiceSet.get('name').asString().getValue()
97
+ const elementName = choiceSet.get('element').asString().getValue()
98
+ const xml = create().ele('record_update')
99
+ const root = xml.ele('sys_choice_set', { table: tableName })
100
+ descendants.query('sys_choice').forEach((choice) => {
101
+ const child = root.ele('sys_choice', { action: choice.getAction() })
102
+ child.ele('sys_id').txt(choice.getId().getValue())
103
+ child.ele('name').txt(tableName)
104
+ child.ele('element').txt(elementName)
105
+
106
+ for (const prop of [
107
+ 'label',
108
+ 'value',
109
+ 'sequence',
110
+ 'dependent_value',
111
+ 'hint',
112
+ 'inactive',
113
+ 'inactive_on_update',
114
+ 'language',
115
+ ]) {
116
+ choice
117
+ .get(prop)
118
+ .ifDefined()
119
+ ?.toString()
120
+ .pipe((p) => child.ele(prop).txt(p.getValue()))
121
+ }
122
+ })
123
+
124
+ const child = root.ele('sys_choice_set', { action: choiceSet.getAction() })
125
+ child.ele('sys_id').txt(choiceSet.getId().getValue())
126
+ child.ele('sys_scope', { display_value: config.scope }).txt(config.scopeId)
127
+ child.ele('name').txt(tableName)
128
+ child.ele('element').txt(elementName)
129
+
130
+ return {
131
+ success: true,
132
+ value: {
133
+ source: choiceSet,
134
+ name: `${await transform.getUpdateName(choiceSet)}.xml`,
135
+ category: choiceSet.getInstallCategory(),
136
+ content: xml.end({ prettyPrint: true }),
137
+ },
138
+ }
139
+ },
140
+ },
68
141
  },
69
142
  shapes: [
70
143
  {
@@ -82,20 +155,32 @@ export const ColumnPlugin = Plugin.create({
82
155
  return { success: false }
83
156
  }
84
157
 
85
- const documentationRecords = getDocumentationRecords(
158
+ const documentationRecords = await getDocumentationRecords(
86
159
  callExpression,
87
160
  documentationAliases,
88
161
  factory,
89
162
  diagnostics,
90
163
  true
91
164
  )
92
- const choiceRecords = getChoiceRecords(callExpression, choiceAliases, factory, diagnostics)
93
-
165
+ const choiceRecords = await getChoiceRecords(callExpression, choiceAliases, factory, diagnostics)
166
+ const choiceSetRecords: Record[] = []
167
+ if (choiceRecords.length > 0) {
168
+ choiceSetRecords.push(
169
+ await factory.createRecord({
170
+ source: callExpression,
171
+ table: 'sys_choice_set',
172
+ properties: {
173
+ name: column.get('table').asString().getValue(),
174
+ element: column.get('name').asString().getValue(),
175
+ },
176
+ })
177
+ )
178
+ }
94
179
  const columnType = COLUMN_API_TO_TYPE[callee] ?? column.get('columnType').asString().getValue()
95
180
  return {
96
181
  success: true,
97
- value: factory
98
- .createRecord({
182
+ value: (
183
+ await factory.createRecord({
99
184
  source: callExpression,
100
185
  table: 'sys_dictionary',
101
186
  properties: column.transform(({ $ }) => ({
@@ -241,7 +326,7 @@ export const ColumnPlugin = Plugin.create({
241
326
  xml_view: $.from('xmlView').toBoolean().def(false),
242
327
  })),
243
328
  })
244
- .with(...documentationRecords, ...choiceRecords),
329
+ ).with(...documentationRecords, ...choiceRecords, ...choiceSetRecords),
245
330
  }
246
331
  },
247
332
  },
@@ -25,6 +25,12 @@ export const CrossScopePrivilegePlugin = Plugin.create({
25
25
  name: 'CrossScopePrivilegePlugin',
26
26
  records: {
27
27
  sys_scope_privilege: {
28
+ relationships: {
29
+ sys_scope: {
30
+ via: 'target_scope',
31
+ inverse: true,
32
+ },
33
+ },
28
34
  toShape(record) {
29
35
  return {
30
36
  success: true,
@@ -52,7 +58,7 @@ export const CrossScopePrivilegePlugin = Plugin.create({
52
58
  {
53
59
  shape: CallExpressionShape,
54
60
  fileTypes: ['fluent'],
55
- toRecord(callExpression, { factory, config, diagnostics }) {
61
+ async toRecord(callExpression, { factory, config, diagnostics }) {
56
62
  if (callExpression.getCallee() !== 'CrossScopePrivilege') {
57
63
  return { success: false }
58
64
  }
@@ -60,9 +66,18 @@ export const CrossScopePrivilegePlugin = Plugin.create({
60
66
  const csp = callExpression.getArgument(0).asObject().withAliasedKeys(crossScopeAliases)
61
67
  generateDeprecatedDiagnostics(csp, diagnostics)
62
68
 
69
+ const targetScope = csp.get('targetScope')
70
+ const targetScopeRef = targetScope.isString()
71
+ ? await factory.createReference({
72
+ source: targetScope,
73
+ table: 'sys_scope',
74
+ guid: targetScope,
75
+ })
76
+ : undefined
77
+
63
78
  return {
64
79
  success: true,
65
- value: factory.createRecord({
80
+ value: await factory.createRecord({
66
81
  source: callExpression,
67
82
  table: 'sys_scope_privilege',
68
83
  explicitId: csp.get('$id'),
@@ -70,7 +85,7 @@ export const CrossScopePrivilegePlugin = Plugin.create({
70
85
  operation: $,
71
86
  status: $,
72
87
  target_name: $.from('targetName'),
73
- target_scope: $.from('targetScope'),
88
+ target_scope: $.val(targetScopeRef),
74
89
  target_type: $.from('targetType'),
75
90
  source_scope: $.val(config.scopeId),
76
91
  })),
@@ -1,4 +1,4 @@
1
- import { path as pathModule, Plugin, Shape, StringShape, ts } from '@servicenow/sdk-build-core'
1
+ import { path as pathModule, Plugin, type ProjectFile, Shape, StringShape, ts } from '@servicenow/sdk-build-core'
2
2
  import { applyPathMappings } from './utils'
3
3
 
4
4
  const HTML_IMPORT_PREFIX = `<!-- @fluent-import-html`
@@ -66,7 +66,17 @@ export const HtmlImportPlugin = Plugin.create({
66
66
  }
67
67
 
68
68
  const mappedPath = applyPathMappings(projectRelativePath, config.staticContentPaths)
69
- const htmlFile = project.addFile(mappedPath, { resolveDependencies: false, excludeFromCompiler: true })
69
+
70
+ let htmlFile: ProjectFile
71
+ try {
72
+ htmlFile = project.addFile(mappedPath, { resolveDependencies: false, excludeFromCompiler: true })
73
+ } catch (e) {
74
+ diagnostics.error(
75
+ importDeclaration,
76
+ `failed to add HTML file: ${e instanceof Error ? e.message : String(e)}`
77
+ )
78
+ return { success: false }
79
+ }
70
80
 
71
81
  return {
72
82
  success: true,
package/src/index.ts CHANGED
@@ -33,7 +33,8 @@ export * from './rest-api-plugin'
33
33
  export * from './html-import-plugin'
34
34
  export * from './static-content-plugin'
35
35
  export * from './script-action-plugin'
36
+ export * from './claims-plugin'
36
37
 
37
38
  // non-plugins
38
39
  export * from './atf/step-configs'
39
- export { REPACK_OUTPUT_DIR } from './repack'
40
+ export { REPACK_OUTPUT_DIR, checkModuleExists } from './repack'
@@ -27,9 +27,14 @@ export class JsonStringifyShape extends CallExpressionShape {
27
27
  export class JsonFileShape extends SourceFileShape {
28
28
  private readonly json: ObjectShape
29
29
 
30
- constructor(file: SourceFileShape, json: ObjectShape) {
31
- super({ file: file.getSource() })
32
- this.json = json
30
+ constructor({
31
+ source,
32
+ json,
33
+ path,
34
+ content,
35
+ }: { source: Source; json: unknown; path: string | Shape; content: string | Shape }) {
36
+ super({ source, path, content })
37
+ this.json = Shape.from(source, json).asObject('Expected JSON file content to be a JSON object')
33
38
  }
34
39
 
35
40
  getJson(): ObjectShape {
@@ -43,23 +48,38 @@ export const JsonPlugin = Plugin.create({
43
48
  {
44
49
  shape: SourceFileShape,
45
50
  fileTypes: ['json'],
46
- toSubclass(file) {
47
- const rawContent = file.getContent().trim()
48
- const jsonContent = rawContent || '{}'
51
+ async toSubclass(file, { compiler, transform }) {
52
+ const actualFile = compiler.getSourceFile(file.getPath())
53
+ if (!actualFile) {
54
+ return {
55
+ success: true,
56
+ value: new JsonFileShape({
57
+ source: file,
58
+ path: file.getPath(),
59
+ content: file.getContent(),
60
+ json: JSON5.parse(file.getContent()),
61
+ }),
62
+ }
63
+ }
49
64
 
50
- let json: ObjectShape
51
- try {
52
- json = Shape.from(
53
- file.getSource().getFirstDescendantByKind(ts.SyntaxKind.ObjectLiteralExpression) ?? file,
54
- JSON5.parse(jsonContent)
55
- ).asObject('Expected JSON file content to be a JSON object')
56
- } catch (e) {
57
- throw new Error(`Failed to parse JSON content from file (${file.getPath()})`, { cause: e })
65
+ const root = actualFile.getFirstDescendantByKind(ts.SyntaxKind.ObjectLiteralExpression)
66
+ if (!root) {
67
+ return { success: false }
68
+ }
69
+
70
+ const json = await transform.toShape(root)
71
+ if (!json.success) {
72
+ return { success: false }
58
73
  }
59
74
 
60
75
  return {
61
76
  success: true,
62
- value: new JsonFileShape(file, json),
77
+ value: new JsonFileShape({
78
+ source: file,
79
+ json: json.value,
80
+ path: file.getPath(),
81
+ content: file.getContent(),
82
+ }),
63
83
  }
64
84
  },
65
85
  },