import { tryFn , hasDebug , readJson , hasVerbose , uuidv4 } from './helpers' ; import { shouldTrack } from './shouldTrack' ; import type * as d from '../../declarations' ; import { readConfig , updateConfig , writeConfig } from '../ionic-config' ; import { CoreCompiler } from '../load-compiler' ; import { WWW } from '../../compiler/output-targets/output-utils' ; param sys param config param logger param coreCompiler param result export async function telemetryBuildFinishedAction ( sys , config , logger , coreCompiler , result ) { const tracking = await shouldTrack ( config , sys , config . flags . ci ) ; if ( ! tracking ) { return ; } const component_count = Object . keys ( result . componentGraph ) . length ; const data = await prepareData ( coreCompiler , config , sys , result . duration , component_count ) ; await sendMetric ( sys , config , 'stencil_cli_command' , data ) ; logger . debug ( `${ logger . blue ( 'Telemetry' ) }: ${ logger . gray ( JSON . stringify ( data ) ) }` ) ; } param sys param config param logger param coreCompiler param action returns export async function telemetryAction ( sys , config , logger , coreCompiler , action ? ) { const tracking = await shouldTrack ( config , sys , ! ! config ?. flags ?. ci ) ; let duration = undefined ; let error ; if ( action ) { const start = new Date ( ) ; try { await action ( ) ; } catch ( e ) { error = e ; } const end = new Date ( ) ; duration = end . getTime ( ) - start . getTime ( ) ; } if ( ! tracking || ( config . flags . task == 'build' && ! config . flags . args . includes ( '--watch' ) ) ) { return ; } const data = await prepareData ( coreCompiler , config , sys , duration ) ; await sendMetric ( sys , config , 'stencil_cli_command' , data ) ; logger . debug ( `${ logger . blue ( 'Telemetry' ) }: ${ logger . gray ( JSON . stringify ( data ) ) }` ) ; if ( error ) { throw error ; } } export function hasAppTarget ( config ) { return config . outputTargets . some ( ( target ) => target . type === WWW && ( ! ! target . serviceWorker || ( ! ! target . baseUrl && target . baseUrl !== '/' ) ) ) ; } export function isUsingYarn ( sys ) { return sys . getEnvironmentVar ( 'npm_execpath' ) ?. includes ( 'yarn' ) || false ; } export async function getActiveTargets ( config ) { const result = config . outputTargets . map ( ( t ) => t . type ) ; return Array . from ( new Set ( result ) ) ; } export const prepareData = async ( coreCompiler , config , sys , duration_ms , component_count = undefined ) : Promise < d . TrackableData > => { const { typescript , rollup } = coreCompiler . versions || { typescript : 'unknown' , rollup : 'unknown' } ; const { packages , packagesNoVersions } = await getInstalledPackages ( sys , config ) ; const targets = await getActiveTargets ( config ) ; const yarn = isUsingYarn ( sys ) ; const stencil = coreCompiler . version || 'unknown' ; const system = `${ sys . name } ${ sys . version }` ; const os_name = sys . details . platform ; const os_version = sys . details . release ; const cpu_model = sys . details . cpuModel ; const build = coreCompiler . buildId || 'unknown' ; const has_app_pwa_config = hasAppTarget ( config ) ; return { yarn , duration_ms , component_count , targets , packages , packages_no_versions : packagesNoVersions , arguments : config . flags . args , task : config . flags . task , stencil , system , system_major : getMajorVersion ( system ) , os_name , os_version , cpu_model , build , typescript , rollup , has_app_pwa_config , } ; } ; returns async function getInstalledPackages ( sys , config ) { let packages = [ ] ; let packagesNoVersions = [ ] ; const yarn = isUsingYarn ( sys ) ; try { const appRootDir = sys . getCurrentDirectory ( ) ; const packageJson = await tryFn ( readJson , sys , sys . resolvePath ( appRootDir + '/package.json' ) ) ; if ( ! packageJson ) { return { packages , packagesNoVersions } ; } const rawPackages = Object . entries ( { ... packageJson . devDependencies , ... packageJson . dependencies , } ) ; const ionicPackages = rawPackages . filter ( ( [ k ] ) => k . startsWith ( '@stencil/' ) || k . startsWith ( '@ionic/' ) || k . startsWith ( '@capacitor/' ) ) ; try { packages = yarn ? await yarnPackages ( sys , ionicPackages ) : await npmPackages ( sys , ionicPackages ) ; } catch ( e ) { packages = ionicPackages . map ( ( [ k , v ] ) => `${ k }@${ v . replace ( '^' , '' ) }` ) ; } packagesNoVersions = ionicPackages . map ( ( [ k ] ) => `${ k }` ) ; return { packages , packagesNoVersions } ; } catch ( err ) { hasDebug ( config ) && console . error ( err ) ; return { packages , packagesNoVersions } ; } } param sys param ionicPackages returns async function npmPackages ( sys , ionicPackages ) { const appRootDir = sys . getCurrentDirectory ( ) ; const packageLockJson = await tryFn ( readJson , sys , sys . resolvePath ( appRootDir + '/package-lock.json' ) ) ; return ionicPackages . map ( ( [ k , v ] ) => { let version = packageLockJson ?. dependencies [ k ] ?. version ?? packageLockJson ?. devDependencies [ k ] ?. version ?? v ; version = version . includes ( 'file:' ) ? sanitizeDeclaredVersion ( v ) : version ; return `${ k }@${ version }` ; } ) ; } param sys param ionicPackages returns async function yarnPackages ( sys , ionicPackages ) { const appRootDir = sys . getCurrentDirectory ( ) ; const yarnLock = sys . readFileSync ( sys . resolvePath ( appRootDir + '/yarn.lock' ) ) ; const yarnLockYml = sys . parseYarnLockFile ( yarnLock ) ; return ionicPackages . map ( ( [ k , v ] ) => { const identifiedVersion = `${ k }@${ v }` ; let version = yarnLockYml . object [ identifiedVersion ] ?. version ; version = version . includes ( 'undefined' ) ? sanitizeDeclaredVersion ( identifiedVersion ) : version ; return `${ k }@${ version }` ; } ) ; } param version returns function sanitizeDeclaredVersion ( version ) { return version . replace ( /[*^~]/g , '' ) ; } /**
 * If telemetry is enabled, send a metric via IPC to a forked process for uploading.
 */ export async function sendMetric ( sys , config , name , value ) { const session_id = await getTelemetryToken ( sys ) ; const message = { name , timestamp : new Date ( ) . toISOString ( ) , source : 'stencil_cli' , value , session_id , } ; await sendTelemetry ( sys , config , { type : 'telemetry' , message } ) ; } param sys returns async function getTelemetryToken ( sys ) { const config = await readConfig ( sys ) ; if ( config [ 'tokens.telemetry' ] === undefined ) { config [ 'tokens.telemetry' ] = uuidv4 ( ) ; await writeConfig ( sys , config ) ; } return config [ 'tokens.telemetry' ] ; } param sys param config param data async function sendTelemetry ( sys , config , data ) { try { const now = new Date ( ) . toISOString ( ) ; const body = { metrics : [ data . message ] , sent_at : now , } ; const response = await sys . fetch ( 'https://api.ionicjs.com/events/metrics' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , } , body : JSON . stringify ( body ) , } ) ; hasVerbose ( config ) && console . debug ( '\nSent %O metric to events service (status: %O)' , data . message . name , response . status , '\n' ) ; if ( response . status !== 204 ) { hasVerbose ( config ) && console . debug ( '\nBad response from events service. Request body: %O' , response . body . toString ( ) , '\n' ) ; } } catch ( e ) { hasVerbose ( config ) && console . debug ( 'Telemetry request failed:' , e ) ; } } param sys returns export async function checkTelemetry ( sys ) { const config = await readConfig ( sys ) ; if ( config [ 'telemetry.stencil' ] === undefined ) { config [ 'telemetry.stencil' ] = true ; await writeConfig ( sys , config ) ; } return config [ 'telemetry.stencil' ] ; } param sys returns export async function enableTelemetry ( sys ) { return await updateConfig ( sys , { 'telemetry.stencil' : true } ) ; } param sys returns export async function disableTelemetry ( sys ) { return await updateConfig ( sys , { 'telemetry.stencil' : false } ) ; } param version returns function getMajorVersion ( version ) { const parts = version . split ( '.' ) ; return parts [ 0 ] ; }