diff --git a/husky.config.js b/husky.config.js index 18e3ade..58fba0b 100644 --- a/husky.config.js +++ b/husky.config.js @@ -1 +1,9 @@ -module.exports = require('@jupiterone/integration-sdk-dev-tools/config/husky'); +module.exports = { + ...require('@jupiterone/integration-sdk-dev-tools/config/husky'), + // TODO: This override is to make sure the hooks use yarn. It can be removed once this integration is using npm. + hooks: { + 'pre-commit': + 'yarn j1-integration document && git add docs/jupiterone.md && lint-staged', + 'pre-push': 'yarn prepush', + }, +}; diff --git a/package.json b/package.json index 0d8c355..f1f0261 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jupiterone/graph-airwatch", - "version": "0.6.7", + "version": "0.6.8", "description": "A graph conversion tool for https://www.air-watch.com/.", "repository": { "type": "git", @@ -28,12 +28,12 @@ "prepack": "yarn build" }, "peerDependencies": { - "@jupiterone/integration-sdk-core": "^11.1.0" + "@jupiterone/integration-sdk-core": "^13.2.0" }, "devDependencies": { - "@jupiterone/integration-sdk-core": "^11.1.0", - "@jupiterone/integration-sdk-dev-tools": "^11.1.0", - "@jupiterone/integration-sdk-testing": "^11.1.0", + "@jupiterone/integration-sdk-core": "^13.2.0", + "@jupiterone/integration-sdk-dev-tools": "^13.2.0", + "@jupiterone/integration-sdk-testing": "^13.2.0", "@types/node-fetch": "^2.5.7" }, "dependencies": { diff --git a/src/client/types.ts b/src/client/types.ts index 052cb82..745c91d 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -27,6 +27,14 @@ export interface AirWatchAdmin { firstName: string; lastName: string; email: string; + LocationGroup: string; + LocationGroupId: string; + LastLoginTimeStamp: string; + timeZone: string; + locale: string; + messageTemplateId: string; + messageTemplateUuid: string; + initialLandingPage: string; } export interface AirWatchDevice { @@ -65,6 +73,10 @@ export interface AirWatchOrganizationGroup { Country: string; WebLink: string; CreatedOn: string; + Locale: string; + Users: number; + Admins: number; + Devices: number; /** * Organization groups that refer to this group as their diff --git a/src/entities.ts b/src/entities.ts new file mode 100644 index 0000000..53d67c5 --- /dev/null +++ b/src/entities.ts @@ -0,0 +1,154 @@ +import { SchemaType } from '@jupiterone/integration-sdk-core'; +import { + createEntityType, + createEntityMetadata, + createMultiClassEntityMetadata, +} from './helpers'; +import { typeboxClassSchemaMap } from '@jupiterone/data-model'; + +type PlatformValues = + | 'other' + | 'darwin' + | 'linux' + | 'unix' + | 'windows' + | 'android' + | 'ios' + | 'embedded'; + +// https://resources.workspaceone.com/view/zv5cgwjrcv972rd6fmml/en --> chapter 17 for platform values +// Will not use until cutover strategy is defined +export const platformConverter = (apiPlatformValue: string): PlatformValues => { + const lowerCasePlatform = apiPlatformValue.toLowerCase(); + if (lowerCasePlatform.includes('android')) { + return 'android'; + } else if (lowerCasePlatform === 'apple') { + return 'ios'; + } else if (lowerCasePlatform.includes('windows')) { + return 'windows'; + } else { + return 'other'; + } +}; + +export const [AccountEntityMetadata, createAccountAssignEntity] = + createEntityMetadata({ + resourceName: 'Account', + _class: ['Account'], + _type: createEntityType('account'), + description: 'Airwatch Account', + schema: SchemaType.Object({ + name: SchemaType.String(), + }), + }); + +export const [AdminEntityMetadata, createAdminAssignEntity] = + createEntityMetadata({ + resourceName: 'Admin', + _class: ['User'], + _type: createEntityType('user'), + description: 'Airwatch Admin', + schema: SchemaType.Object({ + admin: SchemaType.Boolean(), + uuid: SchemaType.Optional(SchemaType.String()), + organizationGroupUuid: SchemaType.Optional(SchemaType.String()), + username: SchemaType.Optional(SchemaType.String()), + firstName: SchemaType.Optional(SchemaType.String()), + lastName: SchemaType.Optional(SchemaType.String()), + email: SchemaType.Optional(SchemaType.String()), + initialLandingPage: SchemaType.Optional(SchemaType.String()), + lastLoginTimeStamp: SchemaType.Optional(SchemaType.Number()), + locale: SchemaType.Optional(SchemaType.String()), + locationGroup: SchemaType.Optional(SchemaType.String()), + locationGroupId: SchemaType.Optional(SchemaType.String()), + messageTemplateId: SchemaType.Optional(SchemaType.String()), + messageTemplateUuid: SchemaType.Optional(SchemaType.String()), + timeZone: SchemaType.Optional(SchemaType.String()), + }), + }); + +export const [ + OrganizationGroupEntityMetadata, + createOrganizationGroupAssignEntity, +] = createMultiClassEntityMetadata({ + resourceName: 'Organization Group', + _class: [typeboxClassSchemaMap['Group'], typeboxClassSchemaMap['UserGroup']], + _type: createEntityType('group'), + description: 'Airwatch Group', + schema: SchemaType.Object({ + uuid: SchemaType.Optional(SchemaType.String()), + groupId: SchemaType.Optional(SchemaType.String()), + locationGroupType: SchemaType.Optional( + SchemaType.String({ + description: 'Type of organization group', + examples: ['Global', 'Customer', 'Partner'], + }), + ), + country: SchemaType.Optional(SchemaType.String()), + admins: SchemaType.Optional( + SchemaType.Number({ + description: 'Number of console admin users in the organization group', + }), + ), + devices: SchemaType.Optional( + SchemaType.Number({ + description: + 'Number of enrolled/unenrolled devices present in the organization group', + }), + ), + users: SchemaType.Optional( + SchemaType.Number({ + description: 'Number of enrollment users in the organization group', + }), + ), + locale: SchemaType.Optional(SchemaType.String()), + }), +}); + +export const [UserEndpointEntityMetadata, createUserEndpointAssignEntity] = + createMultiClassEntityMetadata({ + resourceName: 'Device', + _class: [typeboxClassSchemaMap['Host'], typeboxClassSchemaMap['Device']], + _type: 'user_endpoint', + description: 'Airwatch User Endpoint', + schema: SchemaType.Object({ + username: SchemaType.Optional(SchemaType.String()), + email: SchemaType.Optional(SchemaType.String()), + uuid: SchemaType.Optional(SchemaType.String()), + serialNumber: SchemaType.Optional(SchemaType.String()), + imei: SchemaType.Optional(SchemaType.String()), + deviceFriendlyName: SchemaType.Optional(SchemaType.String()), + ownerId: SchemaType.Optional(SchemaType.String()), + assetNumber: SchemaType.Optional(SchemaType.String()), + hostName: SchemaType.Optional(SchemaType.String()), + wifiSsid: SchemaType.Optional(SchemaType.String()), + isSupervised: SchemaType.Optional(SchemaType.Boolean()), + userEmailAddress: SchemaType.Optional(SchemaType.String()), + airwatchPlatform: SchemaType.Optional(SchemaType.String()), + operatingSystem: SchemaType.Optional(SchemaType.String()), + }), + }); + +export const [DeviceUserEntityMetadata, createDeviceUserAssignEntity] = + createEntityMetadata({ + resourceName: 'Device User', + _class: ['User'], + _type: 'device_user', + description: 'Airwatch Device User', + schema: SchemaType.Object({ + uuid: SchemaType.Optional(SchemaType.String()), + }), + }); + +export const [ProfileEntityMetadata, createProfileAssignEntity] = + createEntityMetadata({ + resourceName: 'Profile', + _class: ['Configuration'], + _type: createEntityType('profile'), + description: 'Airwatch Profile', + schema: SchemaType.Object({ + platform: SchemaType.Optional(SchemaType.String()), + managedBy: SchemaType.Optional(SchemaType.String()), + payloads: SchemaType.Optional(SchemaType.Array(SchemaType.String())), + }), + }); diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..ade0288 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,11 @@ +import { createIntegrationHelpers } from '@jupiterone/integration-sdk-core'; +import { typeboxClassSchemaMap } from '@jupiterone/data-model'; + +export const { + createEntityType, + createEntityMetadata, + createMultiClassEntityMetadata, +} = createIntegrationHelpers({ + integrationName: 'airwatch', + classSchemaMap: typeboxClassSchemaMap, +}); diff --git a/src/steps/__snapshots__/index.test.ts.snap b/src/steps/__snapshots__/index.test.ts.snap index b96cb4d..3df9776 100644 --- a/src/steps/__snapshots__/index.test.ts.snap +++ b/src/steps/__snapshots__/index.test.ts.snap @@ -21,6 +21,7 @@ exports[`should collect data 1`] = ` "createdOn": undefined, "displayName": "as1985.awmdm.com", "name": "as1985.awmdm.com", + "vendor": "AirWatch", "webLink": "https://as1985.awmdm.com", }, { @@ -50,18 +51,17 @@ exports[`should collect data 1`] = ` }, ], "_type": "airwatch_group", - "admins": "4", + "admins": 4, "country": "Argentina", "createdOn": 1698375953, - "devices": "0", + "devices": 0, "displayName": "M104252336", "groupId": "M1044590", "id": "1112", - "lgLevel": 0, "locale": "English (United States)", "locationGroupType": "Customer", "name": "M104252336", - "users": "2", + "users": 2, "uuid": "71b1202a-fc21-4239-bacb-5e74e35a820e", "webLink": "https://as1985.awmdm.com/AirWatch/#/AirWatch/OrganizationGroup/Details/Index/1112", }, @@ -109,7 +109,7 @@ exports[`should collect data 1`] = ` "email": "erica.nagle@jupiterone.com", "firstName": "Erica", "initialLandingPage": "/Device/Dashboard", - "lastLoginTimeStamp": "2023-10-27T19:33:33.163", + "lastLoginTimeStamp": 1698435213163, "lastName": "Nagle", "locale": "en-US", "locationGroup": "M104252336", @@ -167,7 +167,7 @@ exports[`should collect data 1`] = ` "email": "J1@J1.com", "firstName": "J1", "initialLandingPage": "/Device/Dashboard", - "lastLoginTimeStamp": "2023-10-27T18:06:03.293", + "lastLoginTimeStamp": 1698429963293, "lastName": "J1", "locale": "en-US", "locationGroup": "M104252336", diff --git a/src/steps/access.ts b/src/steps/access.ts index 97c57c7..f31ef54 100644 --- a/src/steps/access.ts +++ b/src/steps/access.ts @@ -14,7 +14,6 @@ import { ACCOUNT_ORGANIZATION_GROUP_RELATIONSHIP_CLASS, Entities, ORGANIZATION_GROUP_ADMIN_RELATIONSHIP_CLASS, - ORGANIZATION_GROUP_ENTITY_TYPE, ORGANIZATION_GROUP_RELATIONSHIP_CLASS, Relationships, STEP_FETCH_ACCOUNT, @@ -22,6 +21,7 @@ import { STEP_FETCH_ORGANIZATION_GROUPS, } from './constants'; import { createAdminEntity, createOrganizationGroupEntity } from './converters'; +import { OrganizationGroupEntityMetadata } from '../entities'; export async function fetchOrganizationGroups({ instance, @@ -51,7 +51,7 @@ export async function fetchOrganizationGroups({ }); await jobState.iterateEntities( - { _type: ORGANIZATION_GROUP_ENTITY_TYPE }, + { _type: OrganizationGroupEntityMetadata._type }, async (groupEntity) => { const groupData = getRawData(groupEntity) as AirWatchOrganizationGroup; for (const childGroup of groupData?.Children || []) { @@ -60,9 +60,9 @@ export async function fetchOrganizationGroups({ await jobState.addRelationship( createDirectRelationship({ _class: ORGANIZATION_GROUP_RELATIONSHIP_CLASS, - fromType: ORGANIZATION_GROUP_ENTITY_TYPE, + fromType: OrganizationGroupEntityMetadata._type, fromKey: groupEntity._key, - toType: ORGANIZATION_GROUP_ENTITY_TYPE, + toType: OrganizationGroupEntityMetadata._type, toKey: childEntityKey, }), ); diff --git a/src/steps/account.ts b/src/steps/account.ts index 5b20332..a52b966 100644 --- a/src/steps/account.ts +++ b/src/steps/account.ts @@ -5,11 +5,8 @@ import { } from '@jupiterone/integration-sdk-core'; import { IntegrationConfig } from '../types'; -import { - ACCOUNT_ENTITY_CLASS, - ACCOUNT_ENTITY_TYPE, - STEP_FETCH_ACCOUNT, -} from './constants'; +import { Entities, STEP_FETCH_ACCOUNT } from './constants'; +import { createAccountAssignEntity } from '../entities'; export const ACCOUNT_ENTITY_KEY = 'entity:account'; @@ -24,13 +21,13 @@ export async function fetchAccountDetails({ host: instance.config.airwatchHost, username: instance.config.airwatchUsername, }, - assign: { - _class: ACCOUNT_ENTITY_CLASS, - _type: ACCOUNT_ENTITY_TYPE, + assign: createAccountAssignEntity({ _key: `airwatch-${instance.id}`, name: instance.config.airwatchHost, + displayName: instance.config.airwatchHost, webLink: `https://${instance.config.airwatchHost}`, - }, + vendor: 'AirWatch', + }), }, }), ); @@ -42,13 +39,7 @@ export const accountSteps: IntegrationStep[] = [ { id: STEP_FETCH_ACCOUNT, name: 'Fetch Account Details', - entities: [ - { - resourceName: 'Account', - _type: ACCOUNT_ENTITY_TYPE, - _class: ACCOUNT_ENTITY_CLASS, - }, - ], + entities: [Entities.ACCOUNT], relationships: [], dependsOn: [], executionHandler: fetchAccountDetails, diff --git a/src/steps/constants.ts b/src/steps/constants.ts index 695dfa0..bcc17db 100644 --- a/src/steps/constants.ts +++ b/src/steps/constants.ts @@ -2,6 +2,14 @@ import { generateRelationshipType, RelationshipClass, } from '@jupiterone/integration-sdk-core'; +import { + AccountEntityMetadata, + AdminEntityMetadata, + DeviceUserEntityMetadata, + OrganizationGroupEntityMetadata, + ProfileEntityMetadata, + UserEndpointEntityMetadata, +} from '../entities'; export const STEP_FETCH_ACCOUNT = 'fetch-account'; export const STEP_FETCH_ORGANIZATION_GROUPS = 'fetch-org-groups'; @@ -10,38 +18,20 @@ export const STEP_FETCH_DEVICES = 'fetch-devices'; export const STEP_FETCH_PROFILES = 'fetch-profiles'; export const STEP_BUILD_PROFILE_TO_DEVICE = 'build-profile-device-relationship'; -export const ACCOUNT_ENTITY_TYPE = 'airwatch_account'; -export const ACCOUNT_ENTITY_CLASS = 'Account'; - -export const ADMIN_ENTITY_TYPE = 'airwatch_user'; -export const ADMIN_ENTITY_CLASS = 'User'; - -export const DEVICE_ENTITY_TYPE = 'user_endpoint'; -export const DEVICE_ENTITY_CLASS = ['Host', 'Device']; - -export const DEVICE_USER_ENTITY_TYPE = 'device_user'; -export const DEVICE_USER_ENTITY_CLASS = 'User'; - -export const ORGANIZATION_GROUP_ENTITY_TYPE = 'airwatch_group'; -export const ORGANIZATION_GROUP_ENTITY_CLASS = ['Group', 'UserGroup']; - -export const PROFILE_ENTITY_TYPE = 'airwatch_profile'; -export const PROFILE_ENTITY_CLASS = ['Configuration']; - export const ACCOUNT_ORGANIZATION_GROUP_RELATIONSHIP_CLASS = RelationshipClass.HAS; export const ACCOUNT_ORGANIZATION_GROUP_RELATIONSHIP_TYPE = generateRelationshipType( ACCOUNT_ORGANIZATION_GROUP_RELATIONSHIP_CLASS, - ACCOUNT_ENTITY_TYPE, - ORGANIZATION_GROUP_ENTITY_TYPE, + AccountEntityMetadata._type, + OrganizationGroupEntityMetadata._type, ); export const ACCOUNT_DEVICE_RELATIONSHIP_CLASS = RelationshipClass.MANAGES; export const ACCOUNT_DEVICE_RELATIONSHIP_TYPE = generateRelationshipType( ACCOUNT_DEVICE_RELATIONSHIP_CLASS, - ACCOUNT_ENTITY_TYPE, - DEVICE_ENTITY_TYPE, + AccountEntityMetadata._type, + UserEndpointEntityMetadata._type, ); export const ORGANIZATION_GROUP_ADMIN_RELATIONSHIP_CLASS = @@ -49,15 +39,15 @@ export const ORGANIZATION_GROUP_ADMIN_RELATIONSHIP_CLASS = export const ORGANIZATION_GROUP_ADMIN_RELATIONSHIP_TYPE = generateRelationshipType( ORGANIZATION_GROUP_ADMIN_RELATIONSHIP_CLASS, - ORGANIZATION_GROUP_ENTITY_TYPE, - ADMIN_ENTITY_TYPE, + OrganizationGroupEntityMetadata._type, + AdminEntityMetadata._type, ); export const ORGANIZATION_GROUP_RELATIONSHIP_CLASS = RelationshipClass.HAS; export const ORGANIZATION_GROUP_RELATIONSHIP_TYPE = generateRelationshipType( ORGANIZATION_GROUP_RELATIONSHIP_CLASS, - ORGANIZATION_GROUP_ENTITY_TYPE, - ORGANIZATION_GROUP_ENTITY_TYPE, + OrganizationGroupEntityMetadata._type, + OrganizationGroupEntityMetadata._type, ); export const USER_ENDPOINT_DEVICE_USER_RELATIONSHIP_CLASS = @@ -65,84 +55,60 @@ export const USER_ENDPOINT_DEVICE_USER_RELATIONSHIP_CLASS = export const USER_ENDPOINT_DEVICE_USER_RELATIONSHIP_TYPE = generateRelationshipType( USER_ENDPOINT_DEVICE_USER_RELATIONSHIP_CLASS, - DEVICE_ENTITY_TYPE, - DEVICE_USER_ENTITY_TYPE, + UserEndpointEntityMetadata._type, + DeviceUserEntityMetadata._type, ); export const DEVICE_PROFILE_REATIONSHIP_CLASS = RelationshipClass.INSTALLED; export const DEVICE_PROFILE_REATIONSHIP_TYPE = generateRelationshipType( DEVICE_PROFILE_REATIONSHIP_CLASS, - DEVICE_ENTITY_TYPE, - PROFILE_ENTITY_TYPE, + UserEndpointEntityMetadata._type, + ProfileEntityMetadata._type, ); export const Entities = { - ACCOUNT: { - _type: ACCOUNT_ENTITY_TYPE, - _class: ACCOUNT_ENTITY_CLASS, - resourceName: 'Account', - }, - ADMIN: { - _type: ADMIN_ENTITY_TYPE, - _class: ADMIN_ENTITY_CLASS, - resourceName: 'Admin', - }, - ORGANIZATION_GROUP: { - _type: ORGANIZATION_GROUP_ENTITY_TYPE, - _class: ORGANIZATION_GROUP_ENTITY_CLASS, - resourceName: 'Organization Group', - }, - USER_ENDPOINT: { - _type: DEVICE_ENTITY_TYPE, - _class: DEVICE_ENTITY_CLASS, - resourceName: 'Device', - }, - DEVICE_USER: { - _type: DEVICE_USER_ENTITY_TYPE, - _class: DEVICE_USER_ENTITY_CLASS, - resourceName: 'Device User', - }, - PROFILE: { - _type: PROFILE_ENTITY_TYPE, - _class_: PROFILE_ENTITY_CLASS, - resourceName: 'Profile', - }, + ACCOUNT: AccountEntityMetadata, + ADMIN: AdminEntityMetadata, + ORGANIZATION_GROUP: OrganizationGroupEntityMetadata, + USER_ENDPOINT: UserEndpointEntityMetadata, + DEVICE_USER: DeviceUserEntityMetadata, + PROFILE: ProfileEntityMetadata, }; export const Relationships = { ACCOUNT_ORGANIZATION_GROUP: { _type: ACCOUNT_ORGANIZATION_GROUP_RELATIONSHIP_TYPE, _class: ACCOUNT_ORGANIZATION_GROUP_RELATIONSHIP_CLASS, - sourceType: ACCOUNT_ENTITY_TYPE, - targetType: ORGANIZATION_GROUP_ENTITY_TYPE, + sourceType: AccountEntityMetadata._type, + targetType: OrganizationGroupEntityMetadata._type, }, ACCOUNT_DEVICE: { _type: ACCOUNT_DEVICE_RELATIONSHIP_TYPE, _class: ACCOUNT_DEVICE_RELATIONSHIP_CLASS, - sourceType: ACCOUNT_ENTITY_TYPE, - targetType: DEVICE_ENTITY_TYPE, + sourceType: AccountEntityMetadata._type, + targetType: UserEndpointEntityMetadata._type, }, ORGANIZATION_GROUP_ADMIN: { _type: ORGANIZATION_GROUP_ADMIN_RELATIONSHIP_TYPE, _class: ORGANIZATION_GROUP_ADMIN_RELATIONSHIP_CLASS, - sourceType: ORGANIZATION_GROUP_ENTITY_TYPE, - targetType: ADMIN_ENTITY_TYPE, + sourceType: OrganizationGroupEntityMetadata._type, + targetType: AdminEntityMetadata._type, }, ORGANIZATION_GROUP_GROUP: { _type: ORGANIZATION_GROUP_RELATIONSHIP_TYPE, _class: ORGANIZATION_GROUP_RELATIONSHIP_CLASS, - sourceType: ORGANIZATION_GROUP_ENTITY_TYPE, - targetType: ORGANIZATION_GROUP_ENTITY_TYPE, + sourceType: OrganizationGroupEntityMetadata._type, + targetType: OrganizationGroupEntityMetadata._type, }, USER_ENDPOINT_DEVICE_USER: { _type: USER_ENDPOINT_DEVICE_USER_RELATIONSHIP_TYPE, _class: USER_ENDPOINT_DEVICE_USER_RELATIONSHIP_CLASS, - sourceType: DEVICE_ENTITY_TYPE, - targetType: DEVICE_USER_ENTITY_TYPE, + sourceType: UserEndpointEntityMetadata._type, + targetType: DeviceUserEntityMetadata._type, }, DEVICE_PROFILE: { _type: DEVICE_PROFILE_REATIONSHIP_TYPE, _class: DEVICE_PROFILE_REATIONSHIP_CLASS, - sourceType: DEVICE_ENTITY_TYPE, - targetType: PROFILE_ENTITY_TYPE, + sourceType: UserEndpointEntityMetadata._type, + targetType: ProfileEntityMetadata._type, }, }; diff --git a/src/steps/converters.test.ts b/src/steps/converters.test.ts index 14fe150..8acdfa2 100644 --- a/src/steps/converters.test.ts +++ b/src/steps/converters.test.ts @@ -5,15 +5,11 @@ import { AirWatchOrganizationGroup, } from '../client/types'; import { - ADMIN_ENTITY_CLASS, - ADMIN_ENTITY_TYPE, - DEVICE_ENTITY_CLASS, - DEVICE_ENTITY_TYPE, - DEVICE_USER_ENTITY_CLASS, - DEVICE_USER_ENTITY_TYPE, - ORGANIZATION_GROUP_ENTITY_CLASS, - ORGANIZATION_GROUP_ENTITY_TYPE, -} from './constants'; + AdminEntityMetadata, + DeviceUserEntityMetadata, + OrganizationGroupEntityMetadata, + UserEndpointEntityMetadata, +} from '../entities'; import { createAdminEntity, createDeviceEntity, @@ -28,6 +24,14 @@ const admin: AirWatchAdmin = { firstName: 'admin.firstName', lastName: 'admin.lastName', email: 'admin@myairwatch.test', + LocationGroup: '', + LocationGroupId: '', + LastLoginTimeStamp: '', + timeZone: '', + locale: '', + messageTemplateId: '', + messageTemplateUuid: '', + initialLandingPage: '', }; const organizationGroup: AirWatchOrganizationGroup = { @@ -39,6 +43,10 @@ const organizationGroup: AirWatchOrganizationGroup = { Country: 'group.Country', WebLink: 'group.WebLink', CreatedOn: '3/19/2020 12:19:56 AM', + Locale: 'en-US', + Users: 0, + Admins: 0, + Devices: 0, }; const device: AirWatchDevice = { @@ -79,9 +87,9 @@ const deviceUser: AirWatchDeviceUser = { describe('createAdminEntity', () => { test('all data', () => { expect(createAdminEntity('host', admin)).toMatchObject({ - _class: [ADMIN_ENTITY_CLASS], + _class: AdminEntityMetadata._class, _key: 'admin.uuid', - _type: ADMIN_ENTITY_TYPE, + _type: AdminEntityMetadata._type, _rawData: [ { name: 'default', @@ -110,8 +118,8 @@ describe('createAdminEntity', () => { expect(createAdminEntity('host', adminNoName)).toMatchObject({ _key: 'admin.uuid', - _type: ADMIN_ENTITY_TYPE, - _class: [ADMIN_ENTITY_CLASS], + _type: AdminEntityMetadata._type, + _class: AdminEntityMetadata._class, _rawData: [{ name: 'default', rawData: adminNoName }], uuid: 'admin.uuid', organizationGroupUuid: 'group.Uuid', @@ -128,9 +136,9 @@ describe('createAdminEntity', () => { test('createDeviceEntity', () => { expect(createDeviceEntity('host', device)).toMatchObject({ - _class: DEVICE_ENTITY_CLASS, + _class: UserEndpointEntityMetadata._class, _key: 'device.Uuid', - _type: DEVICE_ENTITY_TYPE, + _type: UserEndpointEntityMetadata._type, _rawData: [ { name: 'default', @@ -163,9 +171,9 @@ test('createOrganizationGroupEntity', () => { expect( createOrganizationGroupEntity('as1300.awmdm.com', organizationGroup), ).toMatchObject({ - _class: ORGANIZATION_GROUP_ENTITY_CLASS, + _class: OrganizationGroupEntityMetadata._class, _key: 'group.Uuid', - _type: ORGANIZATION_GROUP_ENTITY_TYPE, + _type: OrganizationGroupEntityMetadata._type, _rawData: [ { name: 'default', @@ -187,9 +195,9 @@ test('createOrganizationGroupEntity', () => { test('createUserEntity', () => { expect(createUserEntity('host', deviceUser)).toMatchObject({ - _class: [DEVICE_USER_ENTITY_CLASS], + _class: DeviceUserEntityMetadata._class, _key: 'device.UserId.Uuid', - _type: DEVICE_USER_ENTITY_TYPE, + _type: DeviceUserEntityMetadata._type, _rawData: [ { name: 'default', diff --git a/src/steps/converters.ts b/src/steps/converters.ts index c72ed4c..c39f4eb 100644 --- a/src/steps/converters.ts +++ b/src/steps/converters.ts @@ -1,4 +1,4 @@ -import moment from 'moment'; +import { utc } from 'moment'; import { convertProperties, @@ -15,17 +15,12 @@ import { AirwatchProfile, } from '../client/types'; import { - ADMIN_ENTITY_CLASS, - ADMIN_ENTITY_TYPE, - DEVICE_ENTITY_CLASS, - DEVICE_ENTITY_TYPE, - DEVICE_USER_ENTITY_CLASS, - DEVICE_USER_ENTITY_TYPE, - ORGANIZATION_GROUP_ENTITY_CLASS, - ORGANIZATION_GROUP_ENTITY_TYPE, - PROFILE_ENTITY_CLASS, - PROFILE_ENTITY_TYPE, -} from './constants'; + createAdminAssignEntity, + createDeviceUserAssignEntity, + createOrganizationGroupAssignEntity, + createProfileAssignEntity, + createUserEndpointAssignEntity, +} from '../entities'; export function createAdminEntity(host: string, data: AirWatchAdmin): Entity { const name = @@ -36,15 +31,26 @@ export function createAdminEntity(host: string, data: AirWatchAdmin): Entity { return createIntegrationEntity({ entityData: { source: data, - assign: { - _class: ADMIN_ENTITY_CLASS, - _type: ADMIN_ENTITY_TYPE, + assign: createAdminAssignEntity({ _key: data.uuid, - ...convertProperties(data), name, admin: true, webLink: `https://${host}/AirWatch/#/Admin/List`, - }, + uuid: data.uuid, + organizationGroupUuid: data.organizationGroupUuid, + username: data.username, + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + initialLandingPage: data.initialLandingPage, + lastLoginTimeStamp: parseTimePropertyValue(data.LastLoginTimeStamp), + locale: data.locale, + locationGroup: data.LocationGroup, + locationGroupId: data.LocationGroupId, + messageTemplateId: data.messageTemplateId, + messageTemplateUuid: data.messageTemplateUuid, + timeZone: data.timeZone, + }), }, }); } @@ -58,36 +64,51 @@ export function createDeviceEntity( macAddress && macAddress.length == 12 ? macAddress.replace(/(.{2})(?=.)/g, '$1:').toLowerCase() : undefined; + const name = + data.DeviceFriendlyName || + `${data.UserName || 'Unknown User'}'s ${data.Model || 'Device'}`; return createIntegrationEntity({ entityData: { source: data, - assign: { - _class: DEVICE_ENTITY_CLASS, - _type: DEVICE_ENTITY_TYPE, + assign: createUserEndpointAssignEntity({ _key: data.Uuid, - ...convertProperties(data), // TODO: Explicitly pull out properties instead of using convertProperties. + // TODO: Explicitly pull out properties instead of using convertProperties. ...convertProperties(securityDetails ? securityDetails : {}), username: data.UserName, + owner: data.UserId?.Name, email: data.UserEmailAddress?.toLowerCase(), webLink: `https://${host}/AirWatch/#/AirWatch/Device/Details/Summary/${data.Id.Value}`, - name: - data.DeviceFriendlyName || - `${data.UserName || 'Unknown User'}'s ${data.Model || 'Device'}`, + name, + displayName: name, hostname: data.HostName, complianceStatus: data.ComplianceStatus === 'Compliant' ? 1 : undefined, - platform: String(data.Platform).toLowerCase(), + // todo: use platformConverter once rollout strategy is implemented + platform: String(data.Platform).toLowerCase() as any, make: data.Model, model: data.Model, serial: data.SerialNumber, deviceId: data.Id.Value?.toString(), macAddress: formatMacAddress(data.MacAddress), category: 'endpoint', - lastSeenOn: parseTimePropertyValue( - !data.LastSeen || data.LastSeen?.endsWith('Z') - ? data.LastSeen - : data.LastSeen + 'Z', - ), // LastSeen is not correctly formatted as an ISO string in AirWatch so we have to do it manually. - }, + lastSeenOn: + parseTimePropertyValue( + !data.LastSeen || data.LastSeen?.endsWith('Z') + ? data.LastSeen + : data.LastSeen + 'Z', + ) ?? null, // LastSeen is not correctly formatted as an ISO string in AirWatch so we have to do it manually. + uuid: data.Uuid, + serialNumber: data.SerialNumber, + imei: data.Imei, + deviceFriendlyName: data.DeviceFriendlyName, + ownerId: data.OwnerId, + assetNumber: data.AssetNumber, + hostName: data.HostName, + osName: data.OperatingSystem, + wifiSsid: data.WifiSsid, + isSupervised: data.IsSupervised, + userEmailAddress: data.UserEmailAddress, + operatingSystem: data.OperatingSystem, + }), }, }); } @@ -99,15 +120,22 @@ export function createOrganizationGroupEntity( return createIntegrationEntity({ entityData: { source: data, - assign: { - _class: ORGANIZATION_GROUP_ENTITY_CLASS, + assign: createOrganizationGroupAssignEntity({ _key: data.Uuid, - _type: ORGANIZATION_GROUP_ENTITY_TYPE, - ...convertProperties(data), + displayName: data.Name, + name: data.Name, id: String(data.Id), webLink: `https://${host}/AirWatch/#/AirWatch/OrganizationGroup/Details/Index/${data.Id}`, createdOn: parseDatetime(data.CreatedOn), - }, + uuid: data.Uuid, + groupId: data.GroupId, + locationGroupType: data.LocationGroupType, + country: data.Country, + admins: Number(data.Admins), + devices: Number(data.Devices), + users: Number(data.Users), + locale: data.Locale, + }), }, }); } @@ -119,14 +147,14 @@ export function createUserEntity( return createIntegrationEntity({ entityData: { source: data, - assign: { - _class: DEVICE_USER_ENTITY_CLASS, - _type: DEVICE_USER_ENTITY_TYPE, + assign: createDeviceUserAssignEntity({ _key: data.Uuid, - ...convertProperties(data), + name: data.Name, + displayName: data.Name, + uuid: data.Uuid, username: data.Name, webLink: `https://${host}/AirWatch/#/AirWatch/User/Details/Summary/${data.Id.Value}`, - }, + }), }, }); } @@ -138,9 +166,7 @@ export function createProfileEntity( return createIntegrationEntity({ entityData: { source: data, - assign: { - _class: PROFILE_ENTITY_CLASS, - _type: PROFILE_ENTITY_TYPE, + assign: createProfileAssignEntity({ _key: data.uuid, name: data.name, platform: data.platform, @@ -148,11 +174,11 @@ export function createProfileEntity( managedBy: data.managed_by, payloads: data.configured_payload.map((entry) => entry.name), webLink: `https://${host}/AirWatch/#/Profile/List/`, - }, + }), }, }); } function parseDatetime(time: string): number { - return moment.utc(time, 'M/D/YYYY h:m:ss').unix(); + return utc(time, 'M/D/YYYY h:m:ss').unix(); } diff --git a/src/steps/index.test.ts b/src/steps/index.test.ts index eff829a..0d0e61f 100644 --- a/src/steps/index.test.ts +++ b/src/steps/index.test.ts @@ -9,6 +9,7 @@ import validateInvocation from '../validateInvocation'; import { fetchAdmins, fetchOrganizationGroups } from './access'; import { fetchAccountDetails } from './account'; import { fetchDevices } from './devices'; +import { AccountEntityMetadata } from '../entities'; const integrationConfig: IntegrationConfig = { airwatchHost: process.env.AIRWATCH_HOST || 'as1985.awmdm.com', @@ -56,16 +57,7 @@ test('should collect data', async () => { expect(accounts.length).toBeGreaterThan(0); expect(accounts).toMatchGraphObjectSchema({ _class: ['Account'], - schema: { - additionalProperties: false, - properties: { - _type: { const: 'airwatch_account' }, - _rawData: { - type: 'array', - items: { type: 'object' }, - }, - }, - }, + schema: AccountEntityMetadata.schema, }); const users = context.jobState.collectedEntities.filter( diff --git a/src/steps/profiles.ts b/src/steps/profiles.ts index 6dc4c2d..2829b02 100644 --- a/src/steps/profiles.ts +++ b/src/steps/profiles.ts @@ -6,11 +6,8 @@ import { import { IntegrationConfig } from '../types'; import { - DEVICE_ENTITY_TYPE, DEVICE_PROFILE_REATIONSHIP_CLASS, - ORGANIZATION_GROUP_ENTITY_TYPE, - PROFILE_ENTITY_CLASS, - PROFILE_ENTITY_TYPE, + Entities, Relationships, STEP_BUILD_PROFILE_TO_DEVICE, STEP_FETCH_DEVICES, @@ -19,6 +16,11 @@ import { } from './constants'; import { createAPIClient } from '../client'; import { createProfileEntity } from './converters'; +import { + OrganizationGroupEntityMetadata, + ProfileEntityMetadata, + UserEndpointEntityMetadata, +} from '../entities'; export async function fetchProfiles({ instance, @@ -27,7 +29,7 @@ export async function fetchProfiles({ }: IntegrationStepExecutionContext) { const apiClient = createAPIClient(instance.config); await jobState.iterateEntities( - { _type: ORGANIZATION_GROUP_ENTITY_TYPE }, + { _type: OrganizationGroupEntityMetadata._type }, async (groupEntity) => { await apiClient.iterateOrganizationGroupProfiles(async (profile) => { const profileEntity = createProfileEntity(apiClient.host, profile); @@ -51,7 +53,7 @@ export async function buildDeviceProfileRelationships({ }: IntegrationStepExecutionContext) { const apiClient = createAPIClient(instance.config); await jobState.iterateEntities( - { _type: PROFILE_ENTITY_TYPE }, + { _type: ProfileEntityMetadata._type }, async (profileEntity) => { try { const response = await apiClient.fetchDevicesForProfile( @@ -70,9 +72,9 @@ export async function buildDeviceProfileRelationships({ createDirectRelationship({ _class: DEVICE_PROFILE_REATIONSHIP_CLASS, fromKey: device.uuid, - fromType: DEVICE_ENTITY_TYPE, + fromType: UserEndpointEntityMetadata._type, toKey: profileEntity._key, - toType: PROFILE_ENTITY_TYPE, + toType: ProfileEntityMetadata._type, }), ); } else { @@ -96,13 +98,7 @@ export const profileSteps: IntegrationStep[] = [ { id: STEP_FETCH_PROFILES, name: 'Fetch Profile Details', - entities: [ - { - resourceName: 'Profile', - _type: PROFILE_ENTITY_TYPE, - _class: PROFILE_ENTITY_CLASS, - }, - ], + entities: [Entities.PROFILE], relationships: [], dependsOn: [STEP_FETCH_ORGANIZATION_GROUPS], executionHandler: fetchProfiles, diff --git a/yarn.lock b/yarn.lock index 7bf287e..803e559 100644 --- a/yarn.lock +++ b/yarn.lock @@ -914,6 +914,13 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d" integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w== +"@ewoudenberg/difflib@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@ewoudenberg/difflib/-/difflib-0.1.0.tgz#a2ae5d3321ffa7c1b47691cf0db189d1264aaaa4" + integrity sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A== + dependencies: + heap ">= 0.2.0" + "@humanwhocodes/config-array@^0.11.13": version "0.11.13" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" @@ -1173,29 +1180,31 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jupiterone/data-model@^0.54.0": - version "0.54.0" - resolved "https://registry.yarnpkg.com/@jupiterone/data-model/-/data-model-0.54.0.tgz#904b2629e2606b9fe334fa480472caa8c242bd20" - integrity sha512-cBFJT/q/UH40yOQsqAr1O75v64rnwC3bywDO81wWK+B0YbP+RCotzz/VFqgwNAhVb/QMCFuXhJk/ewaz704CoQ== +"@jupiterone/data-model@^0.61.9": + version "0.61.9" + resolved "https://registry.yarnpkg.com/@jupiterone/data-model/-/data-model-0.61.9.tgz#4052f6d30210cb17aac78e246aa2fe71d0cbc2b2" + integrity sha512-5hFbsej5kXAjXGL2UIyGdtB9PXGkfp86c8SQ07wvLQeaVvH+9wv9GgJzNoaPxifo34VwN5z4RSycZ5sgFWRgvw== dependencies: + "@sinclair/typebox" "^0.32.30" ajv "^8.0.0" ajv-formats "^2.0.0" -"@jupiterone/integration-sdk-cli@^11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-cli/-/integration-sdk-cli-11.1.0.tgz#dd13cbc26b493511e7e04673f3adbb06b36b3a6b" - integrity sha512-G0gYN2FXI2aA99a/St80Utgyp4SChJoyiID9kX8vz0Bs+SJtj3buhQNTgzW4Tr27k4Bc0KC+Bg4V6/F7O61U+Q== +"@jupiterone/integration-sdk-cli@^13.2.0": + version "13.2.0" + resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-cli/-/integration-sdk-cli-13.2.0.tgz#8ab1c12dcc5ad9766c67581e6a7d08607d7148a3" + integrity sha512-9FRCRb8CG/oFSzkyQPWiWF6U75VsENjxt4DKOEXF+tDs1L8XLUICyIc5+Av/UK3r8ecOku/D6p80/SWu9AIkbw== dependencies: - "@jupiterone/data-model" "^0.54.0" - "@jupiterone/integration-sdk-core" "^11.1.0" - "@jupiterone/integration-sdk-runtime" "^11.1.0" + "@jupiterone/data-model" "^0.61.9" + "@jupiterone/integration-sdk-core" "^13.2.0" + "@jupiterone/integration-sdk-runtime" "^13.2.0" chalk "^4" commander "^9.4.0" + ejs "^3.1.9" fs-extra "^10.1.0" globby "^11.0.0" inquirer-checkbox-plus-prompt "^1.4.2" js-yaml "^4.1.0" - json-diff "^0.5.4" + json-diff "^1.0.6" lodash "^4.17.19" markdown-table "^2.0.0" neo4j-driver "^4.3.3" @@ -1204,21 +1213,23 @@ upath "^1.2.0" url-exists "^1.0.3" -"@jupiterone/integration-sdk-core@^11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-core/-/integration-sdk-core-11.1.0.tgz#1c8043fafed4f234bc3bb5b20c765cb0583e98fa" - integrity sha512-001QwOFPM+sKIrYkBDL306h+Nhen4qCMyxgcnw3ZmxdbNpZo8qtwdmVYrwYOyaWGguidqYer7hU9130Hk5FPwQ== +"@jupiterone/integration-sdk-core@^13.2.0": + version "13.2.0" + resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-core/-/integration-sdk-core-13.2.0.tgz#00ba41ca3af367e64f1552084622600afadda714" + integrity sha512-CfMWvS136NPMhucDWEwOPpFjEhR1Gk0/QC1M1TjpORBvXP0+tEwMeG5m40vzM8BOYummCxyHvkv3DX7hxda3SA== dependencies: - "@jupiterone/data-model" "^0.54.0" + "@jupiterone/data-model" "^0.61.9" + "@jupiterone/integration-sdk-entity-validator" "^13.2.0" + "@sinclair/typebox" "^0.32.30" lodash "^4.17.21" -"@jupiterone/integration-sdk-dev-tools@^11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-dev-tools/-/integration-sdk-dev-tools-11.1.0.tgz#263099536825d5f9d20b6d1a99568e91f8a571dc" - integrity sha512-6ZO1amDDs+SJFRjI8tAiocUtjBJoX7Px4imGHuphQenEPSd12yh13ooR3xYmOTYZdGXpKAlboOjWVcqc90j4wg== +"@jupiterone/integration-sdk-dev-tools@^13.2.0": + version "13.2.0" + resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-dev-tools/-/integration-sdk-dev-tools-13.2.0.tgz#d7ad1be18d3cda6ed75062a1263fc6dca15cb7ee" + integrity sha512-S6/zioIXuA9hyqZZsM3cc2sQzrJD9FHPCsTL00lDr6tnCo0RzCUILEyetn+ZhQ8pt67USpoHn6S5FZ0tqzi/QA== dependencies: - "@jupiterone/integration-sdk-cli" "^11.1.0" - "@jupiterone/integration-sdk-testing" "^11.1.0" + "@jupiterone/integration-sdk-cli" "^13.2.0" + "@jupiterone/integration-sdk-testing" "^13.2.0" "@types/jest" "^29.5.3" "@types/node" "^18" "@typescript-eslint/eslint-plugin" "^6.2.1" @@ -1233,14 +1244,23 @@ prettier "^3.0.0" ts-jest "^29.1.1" ts-node "^9.1.1" - typescript "^5.1.6" + typescript "^5.5.2" -"@jupiterone/integration-sdk-runtime@^11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-runtime/-/integration-sdk-runtime-11.1.0.tgz#2cec03fa15aec1fc28d365936d7f512e66850037" - integrity sha512-jO6MlFQ6DzgcLzmAzHAiWmbtMEi2dmdVJJ2l20zlYLXENqEqDBbD97+XpLKWHWgNoCJDs+ozj3gESVZhsRaIrQ== +"@jupiterone/integration-sdk-entity-validator@^13.2.0": + version "13.2.0" + resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-entity-validator/-/integration-sdk-entity-validator-13.2.0.tgz#88b43e1b091bc17e65894c1e3378a1587a8194ea" + integrity sha512-uDr3einGneDJJIEI5LB4XEdlPS/4ZpT/ctBhO/17v+TdLuF+HTK5YkqziHnsugEUYzV93V6Rp2KCEQxCS09UtQ== + dependencies: + ajv "^8.12.0" + ajv-formats "^3.0.1" + prettier "^3.2.5" + +"@jupiterone/integration-sdk-runtime@^13.2.0": + version "13.2.0" + resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-runtime/-/integration-sdk-runtime-13.2.0.tgz#ec96a48968a666b53fdc5db54b71b842f4c8d7f3" + integrity sha512-t5b5Vdid8DKeQpEHVWVESXYlei1b20ulD/jSXK8WJPNgrK9db17cfWqEzGkRC/zDKfI0qsFhSa7Svon7THOEWA== dependencies: - "@jupiterone/integration-sdk-core" "^11.1.0" + "@jupiterone/integration-sdk-core" "^13.2.0" "@lifeomic/alpha" "^5.2.0" "@lifeomic/attempt" "^3.0.3" async-sema "^3.1.0" @@ -1256,13 +1276,13 @@ p-queue "^6.3.0" rimraf "^3.0.2" -"@jupiterone/integration-sdk-testing@^11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-testing/-/integration-sdk-testing-11.1.0.tgz#1ff2588e5c54d5faefb37261b29836078d7143b7" - integrity sha512-cl0y0+kFijB/b1XxEN8S0YRTEdCV8YwCkxTbzmubES0g0j9wff0it5X7CpxsVn1tGBeV3jm48C/12/Udqm70BA== +"@jupiterone/integration-sdk-testing@^13.2.0": + version "13.2.0" + resolved "https://registry.yarnpkg.com/@jupiterone/integration-sdk-testing/-/integration-sdk-testing-13.2.0.tgz#68aab49fc45a0ec6466b3ca9a0a0baf1922eb41c" + integrity sha512-huubIaruglKaURwt+imJP80Xx3sMsjlNK3Pyy0jtpz7JrBTriVkLxcEM/pXXFl+mllKlhVzN0luSp5iaX93PUA== dependencies: - "@jupiterone/integration-sdk-core" "^11.1.0" - "@jupiterone/integration-sdk-runtime" "^11.1.0" + "@jupiterone/integration-sdk-core" "^13.2.0" + "@jupiterone/integration-sdk-runtime" "^13.2.0" "@pollyjs/adapter-node-http" "^6.0.5" "@pollyjs/core" "^6.0.5" "@pollyjs/persister-fs" "^6.0.5" @@ -1415,6 +1435,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.32.30": + version "0.32.34" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.32.34.tgz#a1c59d4df30982263cc7aa64c2c853878050838d" + integrity sha512-a3Z3ytYl6R/+7ldxx04PO1semkwWlX/8pTqxsPw4quIcIXDFPZhOc1Wx8azWmkU26ccK3mHwcWenn0avNgAKQg== + "@sindresorhus/fnv1a@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@sindresorhus/fnv1a/-/fnv1a-2.0.1.tgz#2aefdfa7eb5b7f29a7936978218e986c70c603fc" @@ -2296,6 +2321,13 @@ ajv-formats@^2.0.0: dependencies: ajv "^8.0.0" +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -2316,6 +2348,16 @@ ajv@^8.0.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^8.12.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.16.0.tgz#22e2a92b94f005f7e0f9c9d39652ef0b8f6f0cb4" + integrity sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw== + dependencies: + fast-deep-equal "^3.1.3" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.4.1" + ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -2439,6 +2481,11 @@ async-sema@^3.1.0: resolved "https://registry.yarnpkg.com/async-sema/-/async-sema-3.1.0.tgz#3a813beb261e4cc58b19213916a48e931e21d21e" integrity sha512-+JpRq3r0zjpRLDruS6q/nC4V5tzsaiu07521677Mdi5i+AkaU/aNJH38rYHJVQ4zvz+SSkjgc8FUI7qIZrR+3g== +async@^3.2.3: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2598,6 +2645,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.1, braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2725,7 +2779,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@4.1.2, chalk@^4, chalk@^4.1.1: +chalk@4.1.2, chalk@^4, chalk@^4.0.2, chalk@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2803,13 +2857,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-color@~0.1.6: - version "0.1.7" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-0.1.7.tgz#adc3200fa471cc211b0da7f566b71e98b9d67347" - integrity sha512-xNaQxWYgI6DD4xIJLn8GY2zDZVbrN0vsU1fEbDNAHZRyceWhpj7A08mYcG1AY92q1Aw0geYkVfiAcEYIZtuTSg== - dependencies: - es5-ext "0.8.x" - cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -2890,6 +2937,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3132,13 +3184,6 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -difflib@~0.2.1: - version "0.2.4" - resolved "https://registry.yarnpkg.com/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e" - integrity sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w== - dependencies: - heap ">= 0.2.0" - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -3176,10 +3221,10 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -dreamopt@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/dreamopt/-/dreamopt-0.6.0.tgz#d813ccdac8d39d8ad526775514a13dda664d6b4b" - integrity sha512-KRJa47iBEK0y6ZtgCgy2ykuvMT8c9gj3ua9Dv7vCkclFJJeH2FjhGY2xO5qBoWGahsjCGMlk4Cq9wJYeWxuYhQ== +dreamopt@~0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/dreamopt/-/dreamopt-0.8.0.tgz#5bcc80be7097e45fc489c342405ab68140a8c1d9" + integrity sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg== dependencies: wordwrap ">=0.0.2" @@ -3203,6 +3248,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +ejs@^3.1.9: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + electron-to-chromium@^1.4.535: version "1.4.583" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.583.tgz#7b0ac4f36388da4b5485788adb92cd7dd0abffc4" @@ -3244,11 +3296,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es5-ext@0.8.x: - version "0.8.2" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.8.2.tgz#aba8d9e1943a895ac96837a62a39b3f55ecd94ab" - integrity sha512-H19ompyhnKiBdjHR1DPHvf5RHgHPmJaY9JNzFGbMbPgdsUkvnUCN1Ke8J4Y0IMyTwFM2M9l4h2GoHwzwpSmXbA== - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3599,6 +3646,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -4432,6 +4486,16 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jake@^10.8.5: + version "10.9.1" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.1.tgz#8dc96b7fcc41cb19aa502af506da4e1d56f5e62b" + integrity sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + jest-changed-files@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" @@ -4825,14 +4889,14 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-diff@^0.5.4: - version "0.5.5" - resolved "https://registry.yarnpkg.com/json-diff/-/json-diff-0.5.5.tgz#24658ad200dbdd64ae8a56baf4d87b2b33d7196e" - integrity sha512-B2RSfPv8Y5iqm6/9aKC3cOhXPzjYupKDpGuqT5py9NRulL8J0UoB/zKXUo70xBsuxPcIFgtsGgEdXLrNp0GL7w== +json-diff@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/json-diff/-/json-diff-1.0.6.tgz#63690f695469b6437efaed5bb3d51f011f7cac28" + integrity sha512-tcFIPRdlc35YkYdGxcamJjllUhXWv4n2rK9oJ2RsAzV4FBkuV4ojKEDgcZ+kpKxDmJKv+PFK65+1tVVOnSeEqA== dependencies: - cli-color "~0.1.6" - difflib "~0.2.1" - dreamopt "~0.6.0" + "@ewoudenberg/difflib" "0.1.0" + colors "^1.4.0" + dreamopt "~0.8.0" json-parse-even-better-errors@^2.3.0: version "2.3.1" @@ -5190,6 +5254,13 @@ minimatch@^3.0.5, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -5747,6 +5818,11 @@ prettier@^3.0.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.0.tgz#c6d16474a5f764ea1a4a373c593b779697744d5e" integrity sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw== +prettier@^3.2.5: + version "3.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" + integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -6556,10 +6632,10 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typescript@^5.1.6: - version "5.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +typescript@^5.5.2: + version "5.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" + integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== uglify-js@^3.1.4: version "3.17.4" @@ -6620,6 +6696,13 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +uri-js@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + url-exists@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/url-exists/-/url-exists-1.0.3.tgz#05b2875baf04950e27ef51e9846f1eff04716893"