@servicenow/sdk-build-core 4.7.2 → 4.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler.d.ts +7 -0
- package/dist/compiler.js +22 -6
- package/dist/compiler.js.map +1 -1
- package/dist/compression.d.ts +1 -1
- package/dist/compression.js +28 -48
- package/dist/compression.js.map +1 -1
- package/dist/diagnostic.js +17 -7
- package/dist/diagnostic.js.map +1 -1
- package/dist/formatter.js +17 -7
- package/dist/formatter.js.map +1 -1
- package/dist/fs.js +17 -7
- package/dist/fs.js.map +1 -1
- package/dist/glob.d.ts +4 -0
- package/dist/glob.js +23 -0
- package/dist/glob.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json.js +17 -7
- package/dist/json.js.map +1 -1
- package/dist/keys-registry.js +17 -7
- package/dist/keys-registry.js.map +1 -1
- package/dist/now-config-dependencies.js +17 -7
- package/dist/now-config-dependencies.js.map +1 -1
- package/dist/now-config.d.ts +10 -0
- package/dist/now-config.js +19 -7
- package/dist/now-config.js.map +1 -1
- package/dist/package-inventory.js.map +1 -1
- package/dist/plugins/plugin.d.ts +4 -0
- package/dist/plugins/plugin.js +17 -1
- package/dist/plugins/plugin.js.map +1 -1
- package/dist/plugins/post-install.d.ts +7 -0
- package/dist/plugins/shape.d.ts +13 -0
- package/dist/plugins/shape.js +43 -7
- package/dist/plugins/shape.js.map +1 -1
- package/dist/plugins/time.d.ts +5 -0
- package/dist/plugins/time.js +8 -0
- package/dist/plugins/time.js.map +1 -1
- package/dist/taxonomy.d.ts +1169 -6
- package/dist/taxonomy.js +35 -8
- package/dist/taxonomy.js.map +1 -1
- package/dist/telemetry/clients/abstract-client.d.ts +6 -0
- package/dist/telemetry/clients/abstract-client.js +13 -4
- package/dist/telemetry/clients/abstract-client.js.map +1 -1
- package/dist/telemetry/clients/browser-client.d.ts +2 -0
- package/dist/telemetry/clients/browser-client.js +1 -0
- package/dist/telemetry/clients/browser-client.js.map +1 -1
- package/dist/telemetry/clients/node-client.d.ts +1 -0
- package/dist/telemetry/clients/node-client.js +25 -10
- package/dist/telemetry/clients/node-client.js.map +1 -1
- package/dist/telemetry/factory.d.ts +1 -0
- package/dist/telemetry/factory.js.map +1 -1
- package/dist/typescript.js +17 -7
- package/dist/typescript.js.map +1 -1
- package/dist/util/conditional-dir.d.ts +13 -0
- package/dist/util/conditional-dir.js +22 -0
- package/dist/util/conditional-dir.js.map +1 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/index.js.map +1 -1
- package/now.config.schema.json +15 -1
- package/package.json +3 -3
- package/src/compiler.ts +25 -6
- package/src/compression.ts +34 -53
- package/src/glob.ts +20 -0
- package/src/index.ts +1 -0
- package/src/now-config.ts +4 -0
- package/src/package-inventory.ts +1 -1
- package/src/plugins/plugin.ts +19 -2
- package/src/plugins/post-install.ts +8 -0
- package/src/plugins/shape.ts +28 -0
- package/src/plugins/time.ts +8 -0
- package/src/taxonomy.ts +18 -1
- package/src/telemetry/clients/abstract-client.ts +15 -4
- package/src/telemetry/clients/browser-client.ts +2 -0
- package/src/telemetry/clients/node-client.ts +13 -4
- package/src/telemetry/factory.ts +7 -1
- package/src/util/conditional-dir.ts +19 -0
- package/src/util/index.ts +1 -0
package/src/compiler.ts
CHANGED
|
@@ -342,13 +342,17 @@ declare module '*.html' {
|
|
|
342
342
|
return this.generatedTableFilePath === sourcePath
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
//
|
|
346
|
-
// Populated on first call to getTableColumnTypes(); subsequent calls return immediately.
|
|
345
|
+
// Caches populated by resolveDataTypeProperties() on first access per table.
|
|
347
346
|
private tableColumnTypesCache = new Map<string, Map<string, 'boolean' | 'number' | 'array' | 'array-optional'>>()
|
|
347
|
+
private mandatoryColumnsCache = new Map<string, Set<string>>()
|
|
348
348
|
|
|
349
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Resolves Data<T> for the given table and populates both the column-type
|
|
351
|
+
* coercion cache and the mandatory-columns cache in a single pass.
|
|
352
|
+
*/
|
|
353
|
+
private resolveDataTypeProperties(tableName: string): void {
|
|
350
354
|
if (this.tableColumnTypesCache.has(tableName)) {
|
|
351
|
-
return
|
|
355
|
+
return
|
|
352
356
|
}
|
|
353
357
|
|
|
354
358
|
const tempFilePath = path.join(this.rootDir, 'src', '$$TEMP_DATA_RESOLVER$$.ts')
|
|
@@ -362,14 +366,19 @@ declare module '*.html' {
|
|
|
362
366
|
try {
|
|
363
367
|
const typeAlias = tempSource.getTypeAlias('T')
|
|
364
368
|
if (!typeAlias) {
|
|
365
|
-
return
|
|
369
|
+
return
|
|
366
370
|
}
|
|
367
371
|
|
|
368
372
|
const resolvedType = typeAlias.getType()
|
|
369
373
|
const properties = resolvedType.getProperties()
|
|
370
374
|
const columnTypes = new Map<string, 'boolean' | 'number' | 'array' | 'array-optional'>()
|
|
375
|
+
const mandatoryColumns = new Set<string>()
|
|
371
376
|
|
|
372
377
|
properties.forEach((property) => {
|
|
378
|
+
if (!property.isOptional()) {
|
|
379
|
+
mandatoryColumns.add(property.getName())
|
|
380
|
+
}
|
|
381
|
+
|
|
373
382
|
const propType = property.getTypeAtLocation(typeAlias)
|
|
374
383
|
const typeText = propType.getText()
|
|
375
384
|
|
|
@@ -383,12 +392,22 @@ declare module '*.html' {
|
|
|
383
392
|
})
|
|
384
393
|
|
|
385
394
|
this.tableColumnTypesCache.set(tableName, columnTypes)
|
|
386
|
-
|
|
395
|
+
this.mandatoryColumnsCache.set(tableName, mandatoryColumns)
|
|
387
396
|
} finally {
|
|
388
397
|
this.removeSourceFile(tempSource)
|
|
389
398
|
}
|
|
390
399
|
}
|
|
391
400
|
|
|
401
|
+
getTableColumnTypes(tableName: string): Map<string, 'boolean' | 'number' | 'array' | 'array-optional'> | undefined {
|
|
402
|
+
this.resolveDataTypeProperties(tableName)
|
|
403
|
+
return this.tableColumnTypesCache.get(tableName)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
getMandatoryColumns(tableName: string): Set<string> | undefined {
|
|
407
|
+
this.resolveDataTypeProperties(tableName)
|
|
408
|
+
return this.mandatoryColumnsCache.get(tableName)
|
|
409
|
+
}
|
|
410
|
+
|
|
392
411
|
private createModuleProject(fs: FileSystem, tsConfigPath?: string) {
|
|
393
412
|
return new ts.Project({
|
|
394
413
|
fileSystem: new TsMorphFileSystemWrapper(fs),
|
package/src/compression.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { unzipSync, zipSync, gunzip as callbackGunzip, gzipSync, gunzipSync } from 'fflate'
|
|
2
|
+
import type { Zippable } from 'fflate'
|
|
2
3
|
import { FileSystem, TsMorphFileSystemWrapper } from './fs'
|
|
3
4
|
import type { Logger } from './logger'
|
|
4
5
|
import { path } from './path'
|
|
@@ -9,7 +10,8 @@ export const unzipAppPackage = async (
|
|
|
9
10
|
fs: FileSystem,
|
|
10
11
|
zipPath: string,
|
|
11
12
|
targetPath: string,
|
|
12
|
-
logger?: Logger
|
|
13
|
+
logger?: Logger,
|
|
14
|
+
ignore?: (filename: string) => boolean
|
|
13
15
|
): Promise<{ files: string[] }> => {
|
|
14
16
|
const zipArrBuf = new Uint8Array(fs.readFileSync(zipPath))
|
|
15
17
|
const unzipResult = unzipSync(zipArrBuf)
|
|
@@ -31,72 +33,51 @@ export const unzipAppPackage = async (
|
|
|
31
33
|
delete unzipResult[ZIP_STATUS_FILE]
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
const files =
|
|
36
|
+
const files: string[] = []
|
|
37
|
+
for (const [filePath, data] of Object.entries(unzipResult)) {
|
|
38
|
+
const filename = path.basename(filePath)
|
|
39
|
+
|
|
40
|
+
if (ignore?.(filename)) {
|
|
41
|
+
logger?.info(`Skipping file ${filename} due to excludeFilePatterns`)
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
const fullPath = path.join(targetPath, filePath)
|
|
36
46
|
|
|
37
47
|
// Create directories if necessary
|
|
38
|
-
|
|
39
|
-
FileSystem.ensureDirSync(fs, dir)
|
|
48
|
+
FileSystem.ensureDirSync(fs, path.dirname(fullPath))
|
|
40
49
|
|
|
41
50
|
// Write file content to the filesystem
|
|
42
51
|
fs.writeFileSync(fullPath, data)
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
}
|
|
53
|
+
files.push(fullPath)
|
|
54
|
+
}
|
|
46
55
|
|
|
47
56
|
return { files }
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
export const zipDirectory = async (fs: FileSystem, source: string, destination: string): Promise<string> => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
z.ondata = (err, data, final) => {
|
|
58
|
-
if (err) {
|
|
59
|
-
outputStream.destroy()
|
|
60
|
-
reject(err)
|
|
61
|
-
} else {
|
|
62
|
-
outputStream.write(data)
|
|
63
|
-
if (final) {
|
|
64
|
-
outputStream.end()
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
outputStream.on('finish', () => {
|
|
70
|
-
resolve(destination)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
outputStream.on('error', (error) => {
|
|
74
|
-
reject(error)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// Using our own glob for browser compatibility
|
|
78
|
-
const wrappedFs = new TsMorphFileSystemWrapper(fs)
|
|
79
|
-
const files = wrappedFs.globSync(['**'], {
|
|
80
|
-
cwd: source,
|
|
81
|
-
})
|
|
60
|
+
// Using our own glob for browser compatibility
|
|
61
|
+
const wrappedFs = new TsMorphFileSystemWrapper(fs)
|
|
62
|
+
const files = wrappedFs.globSync(['**'], {
|
|
63
|
+
cwd: source,
|
|
64
|
+
})
|
|
82
65
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
66
|
+
if (files.length === 0) {
|
|
67
|
+
throw new Error(`No files found in ${source} directory to create zip.`)
|
|
68
|
+
}
|
|
86
69
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
70
|
+
const fileMap: Zippable = {}
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
const fp = path.normalize(file)
|
|
73
|
+
const data = new Uint8Array(fs.readFileSync(fp))
|
|
74
|
+
const entryName = fp.replace(source, '')
|
|
75
|
+
fileMap[entryName] = [data, { level: 6 }]
|
|
76
|
+
}
|
|
94
77
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
})
|
|
78
|
+
const zipped = zipSync(fileMap)
|
|
79
|
+
fs.writeFileSync(destination, zipped)
|
|
80
|
+
return destination
|
|
100
81
|
}
|
|
101
82
|
|
|
102
83
|
const gunzip = (data: Uint8Array) =>
|
package/src/glob.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a glob pattern to a RegExp.
|
|
3
|
+
* Supports `*` (match within a path segment), `**` (match across segments), and `?` (single character).
|
|
4
|
+
*/
|
|
5
|
+
function globToRegex(pattern: string): RegExp {
|
|
6
|
+
const escaped = pattern
|
|
7
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars (not * or ?)
|
|
8
|
+
.replace(/\*\*/g, '\0') // placeholder for **
|
|
9
|
+
.replace(/\*/g, '[^/]*') // * → match within segment
|
|
10
|
+
.replace(/\0/g, '.*') // ** → match across segments
|
|
11
|
+
.replace(/\?/g, '[^/]') // ? → single char within segment
|
|
12
|
+
return new RegExp(`^${escaped}$`)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns true if `str` matches any of the provided glob patterns.
|
|
17
|
+
*/
|
|
18
|
+
export function matchesGlob(str: string, patterns: string[]): boolean {
|
|
19
|
+
return patterns.some((pattern) => globToRegex(pattern).test(str))
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
package/src/now-config.ts
CHANGED
|
@@ -254,6 +254,7 @@ const NowConfigSchema = z
|
|
|
254
254
|
npmUpdateCheck: z.literal(false).or(z.number()).default(10),
|
|
255
255
|
dependencies: dependenciesSchema,
|
|
256
256
|
ignoreTransformTableList: z.array(z.string()).default([]),
|
|
257
|
+
excludeFilePatterns: z.array(z.string()).default([]),
|
|
257
258
|
trustedModules: z.array(z.string()).default([]),
|
|
258
259
|
tsconfigPath: z.string().optional(),
|
|
259
260
|
scripts: z.record(z.string(), z.string()).default({}),
|
|
@@ -309,6 +310,7 @@ const NowConfigSchema = z
|
|
|
309
310
|
type: z.enum(['configuration', 'package']).default('package'),
|
|
310
311
|
taxonomy,
|
|
311
312
|
hostedPlugins: z.record(z.string(), z.string()).optional(),
|
|
313
|
+
sysCode: z.string().optional(),
|
|
312
314
|
|
|
313
315
|
// Application Runtime Policy (ARP) fields
|
|
314
316
|
applicationRuntimePolicy: z.enum(['none', 'tracking', 'enforcing']).default('none'),
|
|
@@ -329,6 +331,7 @@ const NowConfigSchema = z
|
|
|
329
331
|
| 'npmUpdateCheck'
|
|
330
332
|
| 'dependencies'
|
|
331
333
|
| 'ignoreTransformTableList'
|
|
334
|
+
| 'excludeFilePatterns'
|
|
332
335
|
| 'trustedModules'
|
|
333
336
|
| 'modulePaths'
|
|
334
337
|
| 'serverModulesIncludePatterns'
|
|
@@ -354,6 +357,7 @@ const NowConfigSchema = z
|
|
|
354
357
|
| 'type'
|
|
355
358
|
| 'taxonomy'
|
|
356
359
|
| 'hostedPlugins'
|
|
360
|
+
| 'sysCode'
|
|
357
361
|
| 'applicationRuntimePolicy'
|
|
358
362
|
| 'networkPolicies'
|
|
359
363
|
| 'wildcardPolicy'
|
package/src/package-inventory.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface PackageInventoryConfig {
|
|
|
14
14
|
*/
|
|
15
15
|
export async function sha256(content: string | Uint8Array): Promise<string> {
|
|
16
16
|
const data = typeof content === 'string' ? new TextEncoder().encode(content) : content
|
|
17
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
|
|
17
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data as BufferSource)
|
|
18
18
|
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
|
19
19
|
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
|
|
20
20
|
}
|
package/src/plugins/plugin.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { File, OutputFile } from './file'
|
|
|
19
19
|
import type { Product } from './product'
|
|
20
20
|
import { Database, DiffDatabase } from './database'
|
|
21
21
|
import type { Context as BaseContext, Diagnostics } from './context'
|
|
22
|
-
import { getFileType, isSNScope } from '../util'
|
|
22
|
+
import { getConditionalPluginDir, getFileType, isSNScope } from '../util'
|
|
23
23
|
import { NOW_FILE_EXTENSION, NowConfig } from '..'
|
|
24
24
|
import { path } from '@servicenow/sdk-build-core'
|
|
25
25
|
|
|
@@ -839,6 +839,23 @@ export class Plugin {
|
|
|
839
839
|
return this.traverseDescendants(parent, database)
|
|
840
840
|
}
|
|
841
841
|
|
|
842
|
+
/**
|
|
843
|
+
* Determines whether the conditional directory of a (if/hosted) child matches that of it's parent.
|
|
844
|
+
*/
|
|
845
|
+
private isMixedConditionalChild(parent: Record, child: Record): boolean {
|
|
846
|
+
const parentPlugin = getConditionalPluginDir(parent.getOriginalFilePath())
|
|
847
|
+
const childPlugin = getConditionalPluginDir(child.getOriginalFilePath())
|
|
848
|
+
if (!parentPlugin && !childPlugin) {
|
|
849
|
+
return false
|
|
850
|
+
} else if (!parentPlugin || !childPlugin) {
|
|
851
|
+
return true
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// both conditional - the child belongs to this parent only if they
|
|
855
|
+
// target the same conditional plugin (the if/<plugin> or hosted_plugins/<plugin> segment)
|
|
856
|
+
return parentPlugin !== childPlugin
|
|
857
|
+
}
|
|
858
|
+
|
|
842
859
|
private traverseDescendants(parent: Record, database: Database, visited: Set<string> = new Set()): Record[] {
|
|
843
860
|
visited.add(parent.getId().getValue())
|
|
844
861
|
|
|
@@ -848,7 +865,7 @@ export class Plugin {
|
|
|
848
865
|
const descendants: Record[] = []
|
|
849
866
|
const push = (children: Record | Record[] | undefined) => {
|
|
850
867
|
for (const child of [children].flat()) {
|
|
851
|
-
if (child && !visited.has(child.getId().getValue())) {
|
|
868
|
+
if (child && !visited.has(child.getId().getValue()) && !this.isMixedConditionalChild(parent, child)) {
|
|
852
869
|
visited.add(child.getId().getValue())
|
|
853
870
|
descendants.push(child)
|
|
854
871
|
descendants.push(...this.traverseDescendants(child, database, visited))
|
|
@@ -27,8 +27,16 @@ export type InstanceClient = {
|
|
|
27
27
|
fetch(path: string, init?: RequestInit, params?: URLSearchParams): Promise<Response>
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export type RecordEntry = {
|
|
31
|
+
sys_id: string
|
|
32
|
+
active: string
|
|
33
|
+
state: string
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
export type PostInstallContext = {
|
|
31
37
|
instanceClient?: InstanceClient
|
|
32
38
|
logger: Logger
|
|
33
39
|
config: { scopeId: string; scope: string; [key: string]: unknown }
|
|
40
|
+
/** Map of table name → array of record entries from the project's keys registry */
|
|
41
|
+
recordIds?: Record<string, RecordEntry[]>
|
|
34
42
|
}
|
package/src/plugins/shape.ts
CHANGED
|
@@ -658,6 +658,20 @@ export class TemplateSpanShape extends StringShape {
|
|
|
658
658
|
return `${substitution instanceof Shape ? substitution.getValue() : typeof substitution === 'function' ? substitution(this.expression) : (substitution ?? this.expression.toString().getValue())}${this.literalText}`
|
|
659
659
|
}
|
|
660
660
|
|
|
661
|
+
/**
|
|
662
|
+
* Override equals to compare literal text and expression code instead of
|
|
663
|
+
* rendered values. The base getValue() resolves expressions via
|
|
664
|
+
* toString().getValue() which produces identical strings for structurally
|
|
665
|
+
* different CallExpressionShape instances, causing the commit system to
|
|
666
|
+
* incorrectly skip updates.
|
|
667
|
+
*/
|
|
668
|
+
override equals(other: unknown): boolean {
|
|
669
|
+
if (other instanceof TemplateSpanShape) {
|
|
670
|
+
return this.literalText === other.literalText && this.expression.getCode() === other.expression.getCode()
|
|
671
|
+
}
|
|
672
|
+
return super.equals(other)
|
|
673
|
+
}
|
|
674
|
+
|
|
661
675
|
override getCode(): string {
|
|
662
676
|
return `$\{${this.expression.getCode()}}${StringShape.escapeBackticks(this.literalText)}`
|
|
663
677
|
}
|
|
@@ -697,6 +711,20 @@ export class TemplateExpressionShape extends StringShape {
|
|
|
697
711
|
return `${this.literalText}${this.spans.map((s, i) => s.getValue(typeof substitutions === 'function' ? substitutions : substitutions?.[i])).join('')}`
|
|
698
712
|
}
|
|
699
713
|
|
|
714
|
+
/**
|
|
715
|
+
* Override equals to compare literal text and spans structurally instead of
|
|
716
|
+
* using rendered getValue() strings. See TemplateSpanShape.equals() for details.
|
|
717
|
+
*/
|
|
718
|
+
override equals(other: unknown): boolean {
|
|
719
|
+
if (other instanceof TemplateExpressionShape) {
|
|
720
|
+
if (this.literalText !== other.literalText || this.spans.length !== other.spans.length) {
|
|
721
|
+
return false
|
|
722
|
+
}
|
|
723
|
+
return this.spans.every((span, i) => span.equals(other.spans[i]))
|
|
724
|
+
}
|
|
725
|
+
return super.equals(other)
|
|
726
|
+
}
|
|
727
|
+
|
|
700
728
|
override getCode(): string {
|
|
701
729
|
return `\`${StringShape.escapeBackticks(this.literalText)}${this.spans.map((s) => s.getCode()).join('')}\``
|
|
702
730
|
}
|
package/src/plugins/time.ts
CHANGED
|
@@ -175,6 +175,14 @@ export function parseDateTime(dateTimeStr: string) {
|
|
|
175
175
|
return undefined
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Convert a Duration object to the platform's epoch-relative datetime string.
|
|
180
|
+
* E.g. `{ days: 4, hours: 3, minutes: 2, seconds: 1 }` → `'1970-01-05 03:02:01'`
|
|
181
|
+
*/
|
|
182
|
+
export function formatDuration(duration: Duration): string {
|
|
183
|
+
return formatDateToPlatformFormat(durationFieldToXML(duration))
|
|
184
|
+
}
|
|
185
|
+
|
|
178
186
|
/**
|
|
179
187
|
* Convert a Duration object to a Date for glide_duration fields.
|
|
180
188
|
* Duration fields are timezone-agnostic (they represent a span of time),
|
package/src/taxonomy.ts
CHANGED
|
@@ -37,6 +37,15 @@ const TableNames = {
|
|
|
37
37
|
SYS_FLOW_KNOWLEDGE_TRIGGER: 'sys_flow_knowledge_trigger',
|
|
38
38
|
SYS_FLOW_RT_QUERY_TRIGGER: 'sys_flow_rt_query_trigger',
|
|
39
39
|
SYS_FLOW_TRIGGER: 'sys_flow_trigger',
|
|
40
|
+
SYS_HUB_STEP_INSTANCE: 'sys_hub_step_instance',
|
|
41
|
+
SYS_HUB_STEP_EXT_INPUT: 'sys_hub_step_ext_input',
|
|
42
|
+
SYS_HUB_STEP_EXT_OUTPUT: 'sys_hub_step_ext_output',
|
|
43
|
+
SYS_HUB_ACTION_INPUT: 'sys_hub_action_input',
|
|
44
|
+
SYS_HUB_ACTION_OUTPUT: 'sys_hub_action_output',
|
|
45
|
+
SYS_HUB_ACTION_STATUS_METADATA: 'sys_hub_action_status_metadata',
|
|
46
|
+
SYS_HUB_STATUS_CONDITION: 'sys_hub_status_condition',
|
|
47
|
+
SYS_HUB_ACTION_TYPE_SNAPSHOT: 'sys_hub_action_type_snapshot',
|
|
48
|
+
SYS_HUB_ACTION_PLAN: 'sys_hub_action_plan',
|
|
40
49
|
|
|
41
50
|
// Client Development tables
|
|
42
51
|
DL_U_ASSIGNMENT: 'dl_u_assignment',
|
|
@@ -250,7 +259,6 @@ const taxonomyMapping = z
|
|
|
250
259
|
[TableNames.SYS_FLOW_TRIGGER]: taxonomyItem.default('automation/trigger'),
|
|
251
260
|
[TableNames.SYS_HUB_FLOW]: flowTaxonomyItem,
|
|
252
261
|
[TableNames.SYS_HUB_ACTION_TYPE_DEFINITION]: flowTaxonomyItem,
|
|
253
|
-
[TableNames.SYS_ELEMENT_MAPPING]: flowTaxonomyItem,
|
|
254
262
|
[TableNames.SYS_HUB_FLOW_INPUT]: flowTaxonomyItem,
|
|
255
263
|
[TableNames.SYS_HUB_FLOW_OUTPUT]: flowTaxonomyItem,
|
|
256
264
|
[TableNames.SYS_HUB_ACTION_INPUT_ACTION_INSTANCE]: flowTaxonomyItem,
|
|
@@ -270,6 +278,15 @@ const taxonomyMapping = z
|
|
|
270
278
|
[TableNames.SYS_FLOW_TRIGGER_PLAN]: flowTaxonomyItem,
|
|
271
279
|
[TableNames.SYS_HUB_FLOW_SNAPSHOT]: flowTaxonomyItem,
|
|
272
280
|
[TableNames.SYS_FLOW_SUBFLOW_PLAN]: flowTaxonomyItem,
|
|
281
|
+
[TableNames.SYS_HUB_STEP_INSTANCE]: flowTaxonomyItem,
|
|
282
|
+
[TableNames.SYS_HUB_STEP_EXT_INPUT]: flowTaxonomyItem,
|
|
283
|
+
[TableNames.SYS_HUB_STEP_EXT_OUTPUT]: flowTaxonomyItem,
|
|
284
|
+
[TableNames.SYS_HUB_ACTION_INPUT]: flowTaxonomyItem,
|
|
285
|
+
[TableNames.SYS_HUB_ACTION_OUTPUT]: flowTaxonomyItem,
|
|
286
|
+
[TableNames.SYS_HUB_ACTION_STATUS_METADATA]: flowTaxonomyItem,
|
|
287
|
+
[TableNames.SYS_HUB_STATUS_CONDITION]: flowTaxonomyItem,
|
|
288
|
+
[TableNames.SYS_HUB_ACTION_TYPE_SNAPSHOT]: flowTaxonomyItem,
|
|
289
|
+
[TableNames.SYS_HUB_ACTION_PLAN]: flowTaxonomyItem,
|
|
273
290
|
|
|
274
291
|
// Client Development tables
|
|
275
292
|
[TableNames.DL_U_ASSIGNMENT]: taxonomyItem.default('client-development/assignment-data-lookup'),
|
|
@@ -15,9 +15,12 @@ export abstract class AbstractAppSeeClient implements Telemetry {
|
|
|
15
15
|
protected readonly sdkVersion?: string | undefined
|
|
16
16
|
protected readonly clientName: 'cli' | 'ide' | string
|
|
17
17
|
protected readonly hostname?: string | undefined
|
|
18
|
+
protected readonly codingAgent?: string | undefined
|
|
18
19
|
protected session?: Session
|
|
19
20
|
protected startPromise?: Promise<void>
|
|
20
21
|
protected userId?: string
|
|
22
|
+
private lastEventTimestamp = 0
|
|
23
|
+
private configReceivedTime?: string
|
|
21
24
|
|
|
22
25
|
constructor(
|
|
23
26
|
protected readonly config: InstanceSettings,
|
|
@@ -25,11 +28,19 @@ export abstract class AbstractAppSeeClient implements Telemetry {
|
|
|
25
28
|
sdkVersion?: string
|
|
26
29
|
clientName: 'cli' | 'ide' | string
|
|
27
30
|
hostname?: string
|
|
31
|
+
codingAgent?: string
|
|
28
32
|
}
|
|
29
33
|
) {
|
|
30
34
|
this.sdkVersion = telemetryAttributes.sdkVersion
|
|
31
35
|
this.clientName = telemetryAttributes.clientName
|
|
32
36
|
this.hostname = telemetryAttributes.hostname
|
|
37
|
+
this.codingAgent = telemetryAttributes.codingAgent
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private nextTimestamp(): number {
|
|
41
|
+
const now = Date.now()
|
|
42
|
+
this.lastEventTimestamp = now > this.lastEventTimestamp ? now : this.lastEventTimestamp + 1
|
|
43
|
+
return this.lastEventTimestamp
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
startTimerEvent(name: TelemetryEvent['name']): TimerMetric {
|
|
@@ -77,19 +88,18 @@ export abstract class AbstractAppSeeClient implements Telemetry {
|
|
|
77
88
|
|
|
78
89
|
const defaultValues = this.getDefaultDataValues()
|
|
79
90
|
const eventData = Array.isArray(events) ? events : [events]
|
|
80
|
-
const now = Date.now()
|
|
81
91
|
const hostname = this.hostname ? { hostname: this.hostname } : {}
|
|
82
92
|
|
|
83
93
|
const dataPoints = eventData.map<Datapoint>((d) => ({
|
|
84
94
|
t: DatapointType.Event,
|
|
85
|
-
d:
|
|
95
|
+
d: this.nextTimestamp(),
|
|
86
96
|
n: d.name,
|
|
87
97
|
p: { scopeId: d.appInfo?.scopeId, ...hostname, ...defaultValues, ...d.data },
|
|
88
98
|
}))
|
|
89
99
|
|
|
90
100
|
dataPoints.push({
|
|
91
101
|
t: DatapointType.User,
|
|
92
|
-
d:
|
|
102
|
+
d: this.nextTimestamp(),
|
|
93
103
|
n: this.userId ?? 'unknown',
|
|
94
104
|
p: this.getUserProps(hostname),
|
|
95
105
|
})
|
|
@@ -99,7 +109,7 @@ export abstract class AbstractAppSeeClient implements Telemetry {
|
|
|
99
109
|
DataPoints: dataPoints,
|
|
100
110
|
TabId: this.session.TabId || '0',
|
|
101
111
|
ClientTime: new Date().toISOString(),
|
|
102
|
-
ConfigReceivedTime: new Date().toISOString(),
|
|
112
|
+
ConfigReceivedTime: this.configReceivedTime ?? new Date().toISOString(),
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
fetch(new URL('/web/heartbeat', this.config.BaseUrl), {
|
|
@@ -150,6 +160,7 @@ export abstract class AbstractAppSeeClient implements Telemetry {
|
|
|
150
160
|
const bodyResponse = await response.json()
|
|
151
161
|
if (response.ok) {
|
|
152
162
|
this.session = { ...bodyResponse, TabId: tabId }
|
|
163
|
+
this.configReceivedTime = new Date().toISOString()
|
|
153
164
|
} else {
|
|
154
165
|
throw new Error(`Failed to start session with status ${response.status}: ${bodyResponse.Error}`)
|
|
155
166
|
}
|
|
@@ -9,6 +9,7 @@ export class BrowserTelemetryClient extends AbstractAppSeeClient {
|
|
|
9
9
|
clientName?: 'ide' | string
|
|
10
10
|
hostname?: string
|
|
11
11
|
ideVersion?: string
|
|
12
|
+
codingAgent?: string
|
|
12
13
|
}
|
|
13
14
|
) {
|
|
14
15
|
const { ideVersion, ...baseAttributes } = telemetryAttributes ?? {}
|
|
@@ -68,6 +69,7 @@ export class BrowserTelemetryClient extends AbstractAppSeeClient {
|
|
|
68
69
|
version: this.sdkVersion || 'unknown',
|
|
69
70
|
clientName: this.clientName,
|
|
70
71
|
ideVersion: this.ideVersion || 'unknown',
|
|
72
|
+
...(this.codingAgent ? { codingAgent: this.codingAgent } : {}),
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -7,7 +7,12 @@ import { detectCodingAgent, detectIde } from './detect-agent'
|
|
|
7
7
|
export class NodeTelemetryClient extends AbstractAppSeeClient {
|
|
8
8
|
constructor(
|
|
9
9
|
config: InstanceSettings,
|
|
10
|
-
telemetryAttributes?: {
|
|
10
|
+
telemetryAttributes?: {
|
|
11
|
+
sdkVersion?: string
|
|
12
|
+
clientName?: 'cli' | string
|
|
13
|
+
hostname?: string
|
|
14
|
+
codingAgent?: string
|
|
15
|
+
}
|
|
11
16
|
) {
|
|
12
17
|
super(config, {
|
|
13
18
|
...(telemetryAttributes ?? {}),
|
|
@@ -44,9 +49,13 @@ export class NodeTelemetryClient extends AbstractAppSeeClient {
|
|
|
44
49
|
const env = process.env
|
|
45
50
|
const agentData: TelemetryEventData<{ codingAgent?: string; ide?: string }> = {}
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
if (this.codingAgent) {
|
|
53
|
+
agentData.codingAgent = this.codingAgent
|
|
54
|
+
} else {
|
|
55
|
+
const codingAgent = detectCodingAgent(env)
|
|
56
|
+
if (codingAgent) {
|
|
57
|
+
agentData.codingAgent = codingAgent
|
|
58
|
+
}
|
|
50
59
|
}
|
|
51
60
|
const ide = detectIde(env, process.platform)
|
|
52
61
|
if (ide) {
|
package/src/telemetry/factory.ts
CHANGED
|
@@ -8,7 +8,13 @@ import { getAppSeeConfig } from './config'
|
|
|
8
8
|
export class TelemetryFactory {
|
|
9
9
|
static create(options?: {
|
|
10
10
|
type?: 'node' | 'browser'
|
|
11
|
-
attributes?: {
|
|
11
|
+
attributes?: {
|
|
12
|
+
sdkVersion?: string
|
|
13
|
+
clientName?: string
|
|
14
|
+
hostname?: string
|
|
15
|
+
ideVersion?: string
|
|
16
|
+
codingAgent?: string
|
|
17
|
+
}
|
|
12
18
|
}): Telemetry {
|
|
13
19
|
if (process.env['NODE_ENV'] === 'test' || !options || !options.type) {
|
|
14
20
|
return new NoOpTelemetry()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const CONDITIONAL_MARKERS = ['if', 'hosted_plugins'] as const
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the conditional plugin directory from a file path — the `if` or
|
|
5
|
+
* `hosted_plugins` marker plus the plugin segment that follows it (e.g.
|
|
6
|
+
* `if/com.plugin` from `.../if/com.plugin/update/x.xml`, or
|
|
7
|
+
* `hosted_plugins/com.plugin` from `.../hosted_plugins/com.plugin/...`).
|
|
8
|
+
*
|
|
9
|
+
* Two conditional records belong to the same app only when this value matches,
|
|
10
|
+
* so it is used both for grouping descendants and for detecting conditional
|
|
11
|
+
* conflicts. Returns undefined when the path is not under a conditional directory.
|
|
12
|
+
*
|
|
13
|
+
* Matches either path separator so it behaves the same on POSIX and Windows.
|
|
14
|
+
*/
|
|
15
|
+
export function getConditionalPluginDir(filePath: string): string | undefined {
|
|
16
|
+
const marker = `(${CONDITIONAL_MARKERS.join('|')})`
|
|
17
|
+
const match = filePath.match(new RegExp(`[/\\\\]${marker}[/\\\\]([^/\\\\]+)`))
|
|
18
|
+
return match ? `${match[1]}/${match[2]}` : undefined
|
|
19
|
+
}
|
package/src/util/index.ts
CHANGED