@servicenow/sdk-build-core 4.7.1 → 4.8.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.
- 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 +15 -0
- package/dist/now-config.js +20 -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/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 +1106 -6
- package/dist/taxonomy.js +17 -8
- package/dist/taxonomy.js.map +1 -1
- package/dist/telemetry/clients/abstract-client.d.ts +3 -0
- package/dist/telemetry/clients/abstract-client.js +2 -0
- 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 +2 -2
- 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 +6 -0
- package/src/package-inventory.ts +1 -1
- package/src/plugins/plugin.ts +19 -2
- package/src/plugins/shape.ts +28 -0
- package/src/plugins/time.ts +8 -0
- package/src/taxonomy.ts +0 -1
- package/src/telemetry/clients/abstract-client.ts +3 -0
- 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/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,11 +254,13 @@ 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({}),
|
|
260
261
|
linter: LinterSchema,
|
|
261
262
|
tableOutputFormat: z.enum(['bootstrap', 'component']).default('bootstrap').optional(),
|
|
263
|
+
emitDictionary: z.boolean().default(true),
|
|
262
264
|
defaultLanguage: z
|
|
263
265
|
.string()
|
|
264
266
|
.regex(/^[a-z]{2,3}(-[a-zA-Z0-9]{2,8})*$/)
|
|
@@ -308,6 +310,7 @@ const NowConfigSchema = z
|
|
|
308
310
|
type: z.enum(['configuration', 'package']).default('package'),
|
|
309
311
|
taxonomy,
|
|
310
312
|
hostedPlugins: z.record(z.string(), z.string()).optional(),
|
|
313
|
+
sysCode: z.string().optional(),
|
|
311
314
|
|
|
312
315
|
// Application Runtime Policy (ARP) fields
|
|
313
316
|
applicationRuntimePolicy: z.enum(['none', 'tracking', 'enforcing']).default('none'),
|
|
@@ -328,6 +331,7 @@ const NowConfigSchema = z
|
|
|
328
331
|
| 'npmUpdateCheck'
|
|
329
332
|
| 'dependencies'
|
|
330
333
|
| 'ignoreTransformTableList'
|
|
334
|
+
| 'excludeFilePatterns'
|
|
331
335
|
| 'trustedModules'
|
|
332
336
|
| 'modulePaths'
|
|
333
337
|
| 'serverModulesIncludePatterns'
|
|
@@ -346,12 +350,14 @@ const NowConfigSchema = z
|
|
|
346
350
|
| 'scripts'
|
|
347
351
|
| 'linter'
|
|
348
352
|
| 'tableOutputFormat'
|
|
353
|
+
| 'emitDictionary'
|
|
349
354
|
| 'defaultLanguage'
|
|
350
355
|
| 'tableDefaultLanguage'
|
|
351
356
|
| 'packageResolverVersion'
|
|
352
357
|
| 'type'
|
|
353
358
|
| 'taxonomy'
|
|
354
359
|
| 'hostedPlugins'
|
|
360
|
+
| 'sysCode'
|
|
355
361
|
| 'applicationRuntimePolicy'
|
|
356
362
|
| 'networkPolicies'
|
|
357
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))
|
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
|
@@ -250,7 +250,6 @@ const taxonomyMapping = z
|
|
|
250
250
|
[TableNames.SYS_FLOW_TRIGGER]: taxonomyItem.default('automation/trigger'),
|
|
251
251
|
[TableNames.SYS_HUB_FLOW]: flowTaxonomyItem,
|
|
252
252
|
[TableNames.SYS_HUB_ACTION_TYPE_DEFINITION]: flowTaxonomyItem,
|
|
253
|
-
[TableNames.SYS_ELEMENT_MAPPING]: flowTaxonomyItem,
|
|
254
253
|
[TableNames.SYS_HUB_FLOW_INPUT]: flowTaxonomyItem,
|
|
255
254
|
[TableNames.SYS_HUB_FLOW_OUTPUT]: flowTaxonomyItem,
|
|
256
255
|
[TableNames.SYS_HUB_ACTION_INPUT_ACTION_INSTANCE]: flowTaxonomyItem,
|
|
@@ -15,6 +15,7 @@ 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
|
|
@@ -25,11 +26,13 @@ export abstract class AbstractAppSeeClient implements Telemetry {
|
|
|
25
26
|
sdkVersion?: string
|
|
26
27
|
clientName: 'cli' | 'ide' | string
|
|
27
28
|
hostname?: string
|
|
29
|
+
codingAgent?: string
|
|
28
30
|
}
|
|
29
31
|
) {
|
|
30
32
|
this.sdkVersion = telemetryAttributes.sdkVersion
|
|
31
33
|
this.clientName = telemetryAttributes.clientName
|
|
32
34
|
this.hostname = telemetryAttributes.hostname
|
|
35
|
+
this.codingAgent = telemetryAttributes.codingAgent
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
startTimerEvent(name: TelemetryEvent['name']): TimerMetric {
|
|
@@ -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