@servicenow/sdk-build-core 4.6.0 → 4.7.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 (59) hide show
  1. package/dist/compiler.d.ts +2 -0
  2. package/dist/compiler.js +13 -7
  3. package/dist/compiler.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/now-config.d.ts +51 -3
  8. package/dist/now-config.js +44 -1
  9. package/dist/now-config.js.map +1 -1
  10. package/dist/package-inventory.d.ts +15 -0
  11. package/dist/package-inventory.js +59 -0
  12. package/dist/package-inventory.js.map +1 -0
  13. package/dist/plugins/context.d.ts +2 -2
  14. package/dist/plugins/file.d.ts +1 -0
  15. package/dist/plugins/index.d.ts +0 -1
  16. package/dist/plugins/index.js +0 -1
  17. package/dist/plugins/index.js.map +1 -1
  18. package/dist/plugins/plugin.d.ts +41 -53
  19. package/dist/plugins/plugin.js +517 -162
  20. package/dist/plugins/plugin.js.map +1 -1
  21. package/dist/plugins/shape.d.ts +13 -2
  22. package/dist/plugins/shape.js +96 -15
  23. package/dist/plugins/shape.js.map +1 -1
  24. package/dist/taxonomy.js +7 -2
  25. package/dist/taxonomy.js.map +1 -1
  26. package/dist/telemetry/clients/detect-agent.d.ts +4 -0
  27. package/dist/telemetry/clients/detect-agent.js +84 -0
  28. package/dist/telemetry/clients/detect-agent.js.map +1 -0
  29. package/dist/telemetry/clients/node-client.d.ts +2 -0
  30. package/dist/telemetry/clients/node-client.js +10 -9
  31. package/dist/telemetry/clients/node-client.js.map +1 -1
  32. package/dist/telemetry/index.d.ts +1 -1
  33. package/dist/xml.d.ts +2 -2
  34. package/dist/xml.js +2 -2
  35. package/dist/xml.js.map +1 -1
  36. package/now.config.schema.json +54 -0
  37. package/package.json +9 -5
  38. package/src/compiler.ts +14 -7
  39. package/src/index.ts +1 -0
  40. package/src/now-config.ts +56 -1
  41. package/src/package-inventory.ts +75 -0
  42. package/src/plugins/context.ts +2 -2
  43. package/src/plugins/file.ts +1 -0
  44. package/src/plugins/index.ts +0 -1
  45. package/src/plugins/plugin.ts +704 -231
  46. package/src/plugins/shape.ts +115 -24
  47. package/src/taxonomy.ts +8 -2
  48. package/src/telemetry/clients/detect-agent.ts +88 -0
  49. package/src/telemetry/clients/node-client.ts +12 -8
  50. package/src/telemetry/index.ts +1 -1
  51. package/src/xml.ts +11 -2
  52. package/dist/plugins/cache.d.ts +0 -15
  53. package/dist/plugins/cache.js +0 -22
  54. package/dist/plugins/cache.js.map +0 -1
  55. package/dist/plugins/usage.d.ts +0 -11
  56. package/dist/plugins/usage.js +0 -26
  57. package/dist/plugins/usage.js.map +0 -1
  58. package/src/plugins/cache.ts +0 -23
  59. package/src/plugins/usage.ts +0 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicenow/sdk-build-core",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "ServiceNow SDK Build System Core Libraries",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -10,7 +10,11 @@
10
10
  "now.config.schema.json"
11
11
  ],
12
12
  "exports": {
13
- ".": "./dist/index.js"
13
+ ".": "./dist/index.js",
14
+ "./telemetry": {
15
+ "types": "./dist/telemetry/index.d.ts",
16
+ "default": "./dist/telemetry/index.js"
17
+ }
14
18
  },
15
19
  "dependencies": {
16
20
  "ci-info": "4.4.0",
@@ -18,7 +22,7 @@
18
22
  "fflate": "0.8.2",
19
23
  "json5": "2.2.3",
20
24
  "jsonschema": "1.5.0",
21
- "lodash": "4.17.23",
25
+ "lodash": "4.18.1",
22
26
  "pathe": "1.1.2",
23
27
  "prettier": "3.6.1",
24
28
  "readable-stream": "4.5.2",
@@ -27,7 +31,7 @@
27
31
  "xml-js": "1.6.11",
28
32
  "xmlbuilder2": "3.1.1",
29
33
  "zod": "3.23.8",
30
- "@servicenow/sdk-core": "4.6.0"
34
+ "@servicenow/sdk-core": "4.7.0"
31
35
  },
32
36
  "overrides": {
33
37
  "micromatch": "4.0.7"
@@ -47,6 +51,6 @@
47
51
  "bundle:production": "tsc -b && tsx esbuild.config.ts",
48
52
  "clean": "tsc -b --clean",
49
53
  "watch": "tsc -b -w",
50
- "test": "export NODE_OPTIONS=\"--max-old-space-size=8192 --experimental-vm-modules\" && jest --colors --reporters=default --no-cache --logHeapUsage"
54
+ "test": "export NODE_OPTIONS=\"--max-old-space-size=8192 --experimental-vm-modules\" && jest --colors --no-cache --logHeapUsage"
51
55
  }
52
56
  }
package/src/compiler.ts CHANGED
@@ -40,6 +40,7 @@ export class Compiler extends ts.Project {
40
40
  private readonly rootDir: string
41
41
  private readonly sourceFileToRelativeModuleSpecifier: Record<string, string> = {}
42
42
  private readonly generatedTableFilePath: string
43
+ private readonly generatedBootstrappedTablesFilePath: string
43
44
  private moduleProject: ts.Project | undefined
44
45
 
45
46
  constructor(
@@ -63,6 +64,7 @@ export class Compiler extends ts.Project {
63
64
 
64
65
  this.rootDir = rootDir
65
66
  this.generatedTableFilePath = path.join(this.rootDir, '$$GENERATED$$_common_table.ts')
67
+ this.generatedBootstrappedTablesFilePath = path.join(this.rootDir, '$$GENERATED$$_bootstrapped_tables.ts')
66
68
  this.addGlobalTableDefinitionFile()
67
69
  if (typesDir) {
68
70
  this.addTypesTableDeclaration(typesDir)
@@ -89,7 +91,7 @@ export class Compiler extends ts.Project {
89
91
  }
90
92
 
91
93
  addGlobalTableDefinitionFile() {
92
- const globalTableDefinitionContent = `
94
+ const tableDefinitionContent = `
93
95
  import '@servicenow/sdk/global'
94
96
  import { Table } from '@servicenow/sdk/core'
95
97
 
@@ -105,10 +107,18 @@ declare global {
105
107
  }
106
108
  }
107
109
  `
108
- this.createSourceFile(this.generatedTableFilePath, globalTableDefinitionContent, {
110
+ this.createSourceFile(this.generatedTableFilePath, tableDefinitionContent, {
109
111
  overwrite: true,
110
112
  scriptKind: ts.ScriptKind.TS,
111
113
  })
114
+ this.createSourceFile(this.generatedBootstrappedTablesFilePath, tableDefinitionContent, {
115
+ overwrite: true,
116
+ scriptKind: ts.ScriptKind.TS,
117
+ })
118
+ }
119
+
120
+ getGeneratedBootstrappedTablesFile(): ts.SourceFile | undefined {
121
+ return this.getSourceFile(this.generatedBootstrappedTablesFilePath)
112
122
  }
113
123
 
114
124
  /**
@@ -332,16 +342,15 @@ declare module '*.html' {
332
342
  return this.generatedTableFilePath === sourcePath
333
343
  }
334
344
 
335
- // Cache for table column types that need coercion (boolean/number only)
345
+ // Cache for table column types that need coercion (boolean/number only).
346
+ // Populated on first call to getTableColumnTypes(); subsequent calls return immediately.
336
347
  private tableColumnTypesCache = new Map<string, Map<string, 'boolean' | 'number' | 'array' | 'array-optional'>>()
337
348
 
338
349
  getTableColumnTypes(tableName: string): Map<string, 'boolean' | 'number' | 'array' | 'array-optional'> | undefined {
339
- // Return cached value if available
340
350
  if (this.tableColumnTypesCache.has(tableName)) {
341
351
  return this.tableColumnTypesCache.get(tableName)
342
352
  }
343
353
 
344
- // Lazily resolve and cache on first access
345
354
  const tempFilePath = path.join(this.rootDir, 'src', '$$TEMP_DATA_RESOLVER$$.ts')
346
355
  const typeDeclaration = `type T = import('@servicenow/sdk/core').Data<'${tableName}'>`
347
356
 
@@ -367,10 +376,8 @@ declare module '*.html' {
367
376
  if (typeText === 'boolean' || typeText === 'number') {
368
377
  columnTypes.set(property.getName(), typeText)
369
378
  } else if (typeText.match(/^\(.*\)\[\]$/)) {
370
- // Matches `(...)[]` -> this field should always be an array
371
379
  columnTypes.set(property.getName(), 'array')
372
380
  } else if (typeText.match(/^.*\[\]$/)) {
373
- // Matches `[]` -> this field may be an array or a single value
374
381
  columnTypes.set(property.getName(), 'array-optional')
375
382
  }
376
383
  })
package/src/index.ts CHANGED
@@ -17,6 +17,7 @@ export * from './path'
17
17
  export * from './typescript'
18
18
  export * from './crypto'
19
19
  export * from './compression'
20
+ export * from './package-inventory'
20
21
  export * from './formatter'
21
22
  export * from './telemetry'
22
23
  export * from './claims'
package/src/now-config.ts CHANGED
@@ -9,6 +9,7 @@ import { Compiler } from './compiler'
9
9
  import { GUID } from './guid'
10
10
  import { taxonomy } from './taxonomy'
11
11
  import { dependenciesSchema } from './now-config-dependencies'
12
+ import { isSNScope } from './util'
12
13
 
13
14
  export type NowConfig = z.output<typeof NowConfigSchema>
14
15
  export type NowConfigOptions = z.input<typeof NowConfigSchema>
@@ -135,6 +136,15 @@ const NetworkPolicySchema = z
135
136
 
136
137
  export type NetworkPolicy = z.infer<typeof NetworkPolicySchema>
137
138
 
139
+ /**
140
+ * Schema for Linter configuration options
141
+ */
142
+ const LinterSchema = z
143
+ .object({
144
+ module: z.object({ enabled: z.boolean().default(true) }),
145
+ })
146
+ .default({ module: { enabled: true } })
147
+
138
148
  /**
139
149
  * Network Pillar Schema for Wildcard Policy
140
150
  */
@@ -247,7 +257,8 @@ const NowConfigSchema = z
247
257
  trustedModules: z.array(z.string()).default([]),
248
258
  tsconfigPath: z.string().optional(),
249
259
  scripts: z.record(z.string(), z.string()).default({}),
250
- tableOutputFormat: z.enum(['bootstrap', 'component']).default('bootstrap'),
260
+ linter: LinterSchema,
261
+ tableOutputFormat: z.enum(['bootstrap', 'component']).default('bootstrap').optional(),
251
262
  defaultLanguage: z
252
263
  .string()
253
264
  .regex(/^[a-z]{2,3}(-[a-zA-Z0-9]{2,8})*$/)
@@ -296,6 +307,7 @@ const NowConfigSchema = z
296
307
  packageResolverVersion: z.string().optional(),
297
308
  type: z.enum(['configuration', 'package']).default('package'),
298
309
  taxonomy,
310
+ hostedPlugins: z.record(z.string(), z.string()).optional(),
299
311
 
300
312
  // Application Runtime Policy (ARP) fields
301
313
  applicationRuntimePolicy: z.enum(['none', 'tracking', 'enforcing']).default('none'),
@@ -332,12 +344,14 @@ const NowConfigSchema = z
332
344
  | 'description'
333
345
  | 'installedAsDependency'
334
346
  | 'scripts'
347
+ | 'linter'
335
348
  | 'tableOutputFormat'
336
349
  | 'defaultLanguage'
337
350
  | 'tableDefaultLanguage'
338
351
  | 'packageResolverVersion'
339
352
  | 'type'
340
353
  | 'taxonomy'
354
+ | 'hostedPlugins'
341
355
  | 'applicationRuntimePolicy'
342
356
  | 'networkPolicies'
343
357
  | 'wildcardPolicy'
@@ -491,6 +505,46 @@ export namespace NowConfig {
491
505
 
492
506
  return true
493
507
  }
508
+
509
+ export function getRecordScope(config: NowConfig, filePath: string): { scope?: string; scopeId: string } {
510
+ if (!isSNScope(config.scope) || !config.hostedPlugins) {
511
+ return { scope: config.scope, scopeId: config.scopeId }
512
+ }
513
+
514
+ const matchingDir = Object.keys(config.hostedPlugins).find((pluginDirectory) =>
515
+ filePath.includes(path.join(config.fluentDir, 'hosted_plugins', pluginDirectory))
516
+ )
517
+ if (!matchingDir) {
518
+ return { scope: config.scope, scopeId: config.scopeId }
519
+ }
520
+
521
+ return { scopeId: config.hostedPlugins[matchingDir]! }
522
+ }
523
+
524
+ export function getHostedDirectory(config: NowConfig, filePath: string): string | undefined {
525
+ if (!isSNScope(config.scope) || !config.hostedPlugins) {
526
+ return
527
+ }
528
+
529
+ const normalizedPath = path.normalize(filePath)
530
+ const match = Object.keys(config.hostedPlugins).find((pluginDirectory) =>
531
+ normalizedPath.includes(path.join('hosted_plugins', pluginDirectory))
532
+ )
533
+ if (!match) {
534
+ return
535
+ }
536
+
537
+ return path.join('hosted_plugins', match)
538
+ }
539
+
540
+ export function getConditionalDirectory(config: NowConfig, filePath: string): string | undefined {
541
+ if (!isSNScope(config.scope)) {
542
+ return
543
+ }
544
+
545
+ const match = filePath.match(/[/\\]if[/\\][^/\\]+/)
546
+ return match ? match[0] : undefined
547
+ }
494
548
  }
495
549
 
496
550
  const NOW_CONFIG_SCHEMA = require('../now.config.schema.json')
@@ -576,6 +630,7 @@ const DEPRECATED_FIELDS: Record<string, DeprecatedField> = {
576
630
  transpiledSourceDir: { replacedBy: 'modulePaths' },
577
631
  sourceDir: {},
578
632
  tableDefaultLanguage: { replacedBy: 'defaultLanguage', transfer: true },
633
+ tableOutputFormat: {},
579
634
  }
580
635
 
581
636
  function replaceDeprecatedFields(config: Record<string, unknown>, logger: Logger) {
@@ -0,0 +1,75 @@
1
+ import { crypto } from './crypto'
2
+ import { FileSystem } from './fs'
3
+ import { path } from './path'
4
+
5
+ export interface PackageInventoryConfig {
6
+ build: string
7
+ type: 'scoped' | 'global'
8
+ appVersion: string
9
+ version?: string
10
+ }
11
+
12
+ /**
13
+ * Generate SHA-256 hash for file content (matches ServiceNow's package_inventory.csv format)
14
+ */
15
+ export async function sha256(content: string | Uint8Array): Promise<string> {
16
+ const data = typeof content === 'string' ? new TextEncoder().encode(content) : content
17
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data)
18
+ const hashArray = Array.from(new Uint8Array(hashBuffer))
19
+ return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
20
+ }
21
+
22
+ /**
23
+ * Recursively find all files in a directory
24
+ */
25
+ function findAllFiles(fs: FileSystem, dir: string, baseDir: string): string[] {
26
+ const files: string[] = []
27
+
28
+ if (!FileSystem.existsSync(fs, dir)) {
29
+ return files
30
+ }
31
+
32
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
33
+ const fullPath = path.join(dir, entry.name)
34
+ if (entry.isDirectory()) {
35
+ files.push(...findAllFiles(fs, fullPath, baseDir))
36
+ } else {
37
+ files.push(path.relative(baseDir, fullPath))
38
+ }
39
+ }
40
+
41
+ return files
42
+ }
43
+
44
+ /**
45
+ * Generate package_inventory.csv from files in a directory on disk (for pack command)
46
+ */
47
+ export async function generatePackageInventoryFromDirectory(
48
+ fs: FileSystem,
49
+ appOutputDir: string,
50
+ config: PackageInventoryConfig
51
+ ): Promise<string> {
52
+ const headerLines: string[] = [
53
+ `#build=${config.build}`,
54
+ `#appVersion=${config.appVersion}`,
55
+ `#type=${config.type}`,
56
+ `#version=${config.version ?? '1.0.0'}`,
57
+ ]
58
+
59
+ // Find all files in directory
60
+ const filePaths = findAllFiles(fs, appOutputDir, appOutputDir)
61
+ .filter((f) => !f.endsWith('package_inventory.csv'))
62
+ .sort()
63
+
64
+ // Hash all files in parallel for better performance
65
+ const hashResults = await Promise.all(
66
+ filePaths.map(async (relativePath) => {
67
+ const fullPath = path.join(appOutputDir, relativePath)
68
+ const content = fs.readFileSync(fullPath)
69
+ const hash = await sha256(content)
70
+ return `${relativePath};${hash}`
71
+ })
72
+ )
73
+
74
+ return `${[...headerLines, ...hashResults].join('\n')}\n`
75
+ }
@@ -43,12 +43,12 @@ export interface Diagnostics {
43
43
 
44
44
  export interface Transform {
45
45
  toShape(node: ts.Node, ...plugins: Plugin[]): Promise<Result<Shape>>
46
- recordToShape(record: Record, database: Database, ...plugins: Plugin[]): Promise<Result<Shape>>
47
- toSubclass<const S extends Shape>(shape: S, ...plugins: Plugin[]): Promise<Result<S>>
46
+ toSubclass<const S extends Shape>(shape: S, ...plugins: Plugin[]): Promise<S>
48
47
  toRecord(node: ts.Node, ...plugins: Plugin[]): Promise<Result<Record>>
49
48
  toRecord(shape: Shape, ...plugins: Plugin[]): Promise<Result<Record>>
50
49
  toRecord(file: File, ...plugins: Plugin[]): Promise<Result<Record>>
51
50
  toRecord(source: ts.Node | Shape | File, ...plugins: Plugin[]): Promise<Result<Record>>
51
+ recordToShape(record: Record, database: Database, ...plugins: Plugin[]): Promise<Result<Shape>>
52
52
  getUpdateName(record: Record, ...plugins: Plugin[]): Promise<string>
53
53
  }
54
54
 
@@ -22,4 +22,5 @@ export type OutputFile = {
22
22
  category: InstallCategory
23
23
  content: string
24
24
  ifDirectoryPackage?: string | undefined
25
+ hostedPluginDir?: string | undefined
25
26
  }
@@ -7,5 +7,4 @@ export * from './product'
7
7
  export * from './project'
8
8
  export * from './database'
9
9
  export * from './time'
10
- export * from './usage'
11
10
  export * from './post-install'