Marketplace


Анализ интеграции IDEA c кастомным Marketplace

Цель - реализация собственного маркетплейса для Intellij IDEA взамен использования https://plugins.jetbrains.com/ (далее - JB Marketplace).

В ходе анализа сравниваются два подхода:

Ответы на поставленные вопросы

  • Убедиться в плохом решение через uploadPlugin.xml
    Реализация с помощью Marketplace API более целесообразна, т.к. предоставляет большую гибкость и позволяет заместить обращения к JB Marketplace.

  • Понять можем ли мы поставлять плагины из кастомного маркетплейса через JB marketplace api
    Можем, при условии реализации endpoint-ов, используемых IDEA.

  • Примерно оценить трудозатраты со стороны IDEA (client)
    Перенаправить IDEA на кастомный маркетплейс можно с помощью параметра конфигурации.
    Изменения со стороны IDEA могут потребоваться, если нужно изменить поведение и настройки по умолчанию, а также убрать логику, специфичную для ultimate и других JB аддонов.

  • Можем ли мы использовать одновременно плагины из JB marketplace и нашего кастомного?
    Объединение данных об аддонах из кастомного маркетплейса и из JB Marketplace можно реализовать на уровне сервера.

Использование Custom plugin repositories

Для использования механизма Custom plugin repositories достаточно реализовать endpoint, возвращающий данные плагинов в поддерживаемом формате.

В запросе к репозиторию передаётся параметр build - версия IDEA.

Ограничения:

  • IDEA отображает Custom plugin repositories в отдельных группах в Settings | Plugins | Marketplace
  • IDEA продолжит обращаться к хосту маркетплейса (JB Marketplace по умолчанию)
  • Касательно release channel - каналы не используются в IDEA. API endpoint /channels/list помечен как deprecated. Штатный способ установки плагина из канала на https://plugins.jetbrains.com/ - установка URL конкретного канала в качестве Custom plugin repository. Например, single plugin - https://plugins.jetbrains.com/plugins/nightly/10081, all plugins - https://plugins.jetbrains.com/plugins/nightly/list.

Полезные property

  • intellij.plugins.custom.built.in.repository.url
    Позволяет указать встроенный кастомный репозиторий плагинов.

Формат данных

Custom plugin repositories поддерживает два формата данных:

  • updates.xml
  • plugins.jetbrains.com

updates.xml

Note: В примере приведён не полный список возможных полей.

<?xml version="1.0" encoding="UTF-8"?>
<plugins>
  <plugin
          id="fully.qualified.id.of.this.plugin"
          url="https://mycompany.example.com/my_repo/my_plugin.jar"
          version="major.minor.update">
    <idea-version since-build="181.3" until-build="191.*"/>
  </plugin>
  <plugin
          id="id.of.different.plugin"
          url="https://othercompany.example.com/other_repo/other_plugin.jar"
          version="major.minor">
    <idea-version since-build="181.3" until-build="191.*"/>
  </plugin>
</plugins>

plugins.jetbrains.com

<?xml version='1.0' encoding='UTF-8'?>
<plugin-repository>
    <ff>"Editor"</ff>
    <category name="Editor">
        <idea-plugin downloads="238085" size="67900" date="1719996938000" updatedDate="1719996938000" url="">
            <name>IdeaVim-EasyMotion</name>
            <id>org.jetbrains.IdeaVim-EasyMotion</id>
            <description>
                <![CDATA[EasyMotion plugin emulation for IdeaVim]]>
            </description>
            <version>1.15</version>
            <vendor email="support@jetbrains.com" url="https://www.jetbrains.com/">JetBrains</vendor>
            <rating>4.2</rating>
            <change-notes>
                <![CDATA[Fix https://github.com/AlexPl292/IdeaVim-EasyMotion/issues/56]]>
            </change-notes>
            <idea-version min="n/a" max="n/a" since-build="241.15989.150"/>
            <depends>AceJump</depends>
            <depends>IdeaVIM</depends>
            <depends>com.intellij.modules.platform</depends>
            <tags>Editor</tags>
        </idea-plugin>
        <idea-plugin downloads="16479367" size="5616586" date="1724073309000" updatedDate="1724073309000" url="https://plugins.jetbrains.com/plugin/164">
            <name>IdeaVim</name>
            <id>IdeaVIM</id>
            <description>
                <![CDATA[<p>Vim engine for JetBrains IDEs</p>]]>
            </description>
            <version>2.16.0</version>
            <vendor email="support@jetbrains.com" url="https://www.jetbrains.com/">JetBrains</vendor>
            <rating>4.5</rating>
            <change-notes>
                <![CDATA[Undo in IdeaVim now works like in Vim]]>
            </change-notes>
            <idea-version min="n/a" max="n/a" since-build="241.15989"/>
            <depends>com.intellij.modules.platform</depends>
            <tags>Editor</tags>
            <tags>Keymap</tags>
        </idea-plugin>
    </category>
</plugin-repository>

Properties

Name update.xml plugins.jetbrains.com
Category N/A /plugin-repository/category/@name
Id /plugins/plugin/@id /plugin-repository/category/idea-plugin/id
Downloads N/A /plugin-repository/category/idea-plugin/@downloads
Size N/A /plugin-repository/category/idea-plugin/@size
Url N/A /plugin-repository/category/idea-plugin/@url
IDEA Version - since build /plugins/plugin/idea-version/@since-build /plugin-repository/category/idea-plugin/idea-version/@since-build
IDEA Version - until build /plugins/plugin/idea-version/@since-build /plugin-repository/category/idea-plugin/idea-version/@until-build
Download URL /plugins/plugin/@url /plugin-repository/category/idea-plugin/download-url
Version /plugins/plugin/@version /plugin-repository/category/idea-plugin/version
Date N/A /plugin-repository/category/idea-plugin/@date
Updated date N/A /plugin-repository/category/idea-plugin/@updatedDate
Vendor Email N/A (?) /plugin-repository/category/idea-plugin/vendor/@email
Vendor URL N/A (?) /plugin-repository/category/idea-plugin/vendor/@url
Change notes /plugins/plugin/change-notes /plugin-repository/category/idea-plugin/change-notes
Description /plugins/plugin/description /plugin-repository/category/idea-plugin/description
Name /plugins/plugin/name /plugin-repository/category/idea-plugin/name
Depends N/A (?) /plugin-repository/category/idea-plugin/depends
Tags N/A (?) /plugin-repository/category/idea-plugin/tags
Product code N/A (?) /plugin-repository/category/idea-plugin/productCode
Rating N/A (?) /plugin-repository/category/idea-plugin/rating

IDEA Marketplace API

Для интеграции через API маркетплейса IDEA требуется реализовать endpoint-ы, используемые в Marketplace plugin.

Base URLs

  • URL of plugins repository instance
    Default value: https://plugins.jetbrains.com

    fun getPluginManagerUrl() = MarketplaceCustomizationService.getInstance().getPluginManagerUrl().trimEnd('/')
    
    • Можно переопределить в собственной реализации MarketplaceCustomizationService
    • Настраивается с помощью property: idea.plugins.host
  • Plugin download base URL
    Default value: ${pluginManagerUrl}/pluginManager/

    private fun getDownloadUrl() = MarketplaceCustomizationService.getInstance().getPluginDownloadUrl().trimEnd('/')
    
    • Можно переопределить в собственной реализации MarketplaceCustomizationService

Endpoint URLs

  • Plugin download URL
    Value: ${pluginDownloadUrl}?id=${pluginId}&build=${IDE_BUILD_FOR_REQUEST}&uuid=${marketplaceDownloadsUUID}&updatedFrom=${currentVersion}&channel=${channel}
    Params:

    • build
    • marketplaceDownloadsUUID - уникальный идентификатор машины, генерируется с помощью DeviceIdManager
    • currentVersion
    • channel
    private fun getDownloadUrl() = MarketplaceCustomizationService.getInstance().getPluginDownloadUrl().trimEnd('/')
    
    **
     * Download  the latest compatible update for plugin [ProductFamily.INTELLIJ] by IDE Version.
     * @param xmlId plugin XML id.
     * @param ideBuild IDE version. Example: "IC-145.184"
     * @param channel plugin channel. Default value is "stable" plugin channel.
     */
    
    • Из plugin-repository-rest-client также передаётся параметр action=download
  • Plugin metadata URL
    Value: ${pluginManagerUrl}/files/${externalPluginId}/meta.json
    Params:

    • externalPluginId
  • Plugin update metadata URL
    Value: ${pluginManagerUrl}/files/${externalPluginId}/${externalUpdateId}/meta.json

    /**
     * Get plugin update metadata.
     *
     * Supported for [ProductFamily.INTELLIJ] only.
     */
    
  • All available JetBrains plugins IDs URL
    Value: ${pluginManagerUrl}/files/jbPluginsXMLIds.json

  • All Intellij plugins IDs URL
    Value: ${pluginManagerUrl}/files/pluginsXMLIds.json

    /**
     * Get All [ProductFamily.INTELLIJ] plugins IDs.
     */
    
  • Broken plugins JSON URL
    Value: ${pluginManagerUrl}/files/brokenPlugins.json

  • Supported IDE extensions JSON URL
    Value: ${pluginManagerUrl}/files/IDE/extensions.json?build=${IDE_BUILD_FOR_REQUEST}
    Params:

    • build
  • Marketplace features URL
    Value: ${pluginManagerUrl}/feature/getImplementations?featureType=${featureType}&implementationName=${implementationName}&build=${IDE_BUILD_FOR_REQUEST}
    Params:

    • featureType
    • implementationName
    • build
  • Search aggregation URL
    Value: ${pluginManagerUrl}/api/search/aggregation/${field}?build=${IDE_BUILD_FOR_REQUEST}
    Params:

    • field - принимаемые значения: tags, organizations
    • build
  • Search last compatible update URL
    Value: ${pluginManagerUrl}/api/search/compatibleUpdates

    /**
     * Search last compatible update for each ID` from [xmlIds] or by declared [module]
     * Supported for [ProductFamily.INTELLIJ].
     * @return the list of last compatible updates [UpdateBean] for plugins from [xmlIds].
     */
    
  • Search nearest update URL
    Value: ${pluginManagerUrl}/api/search/updates/nearest

  • Search plugins URL
    Value: ${pluginManagerUrl}/api/search/plugins?${query}&build=${IDE_BUILD_FOR_REQUEST}&max=${count}&all=${includeIncompatible}
    Params:

    • query
    • build
    • max
    • all
  • Plugin reviews URL
    Value: ${pluginManagerUrl}/api/products/intellij/plugins/${pluginId}/comments${page}
    Params:

    • pluginId
    • page
  • Plugin homepage URL
    Default value: ${pluginManagerUrl}/plugin/index?xmlId=${pluginId}
    Params:

    • pluginId
    fun getPluginHomepage(pluginId: PluginId): String? = MarketplaceCustomizationService.getInstance().getPluginHomepageUrl(pluginId)
    
    • Можно переопределить в собственной реализации MarketplaceCustomizationService
  • Plugin reviews policy URL
    Value: ${pluginManagerUrl}/docs/marketplace/reviews-policy.html

  • Plugin write review URL
    Value: ${pluginManagerUrl}/intellij/${pluginId}/review/new?build=${IDE_BUILD_FOR_REQUEST}&version=${version}

Deprecated

  • Channels list URL
    Default value: ${getPluginManagerUrl()}/channels/list/

    /**
     * @deprecated is not used by the platform anymore
     */
    
  • Plugins list URL
    Default value: ${getPluginManagerUrl()}/plugins/list/?uuid=${marketplaceDownloadsUUID}
    Params:

    • marketplaceDownloadsUUID - уникальный идентификатор машины, генерируется с помощью DeviceIdManager
    /**
     * List of plugins compatible with [ideBuild]
     * @deprecated use [searchCompatibleUpdates] for getting compatible update IDs and [PluginUpdateManager.getIntellijUpdateMetadata] for getting information
     */
    @Deprecated("Will be removed for performance reasons")
    
    • Можно переопределить в собственной реализации MarketplaceCustomizationService

Другие полезные источники

  • Extension backup file
    Value: ${PathManager.getTempPath()}/pluginsFeatures.json

Приоритет реализации endpoint-ов

Name URL Priority
Plugin download /pluginManager/ A
Plugin metadata /files/$/meta.json A
Plugin update metadata /files/$/$/meta.json A
All available JetBrains plugins IDs /files/jbPluginsXMLIds.json B
All Intellij plugins IDs /files/pluginsXMLIds.json A
Broken plugins JSON /files/brokenPlugins.json B
Supported IDE extensions JSON /files/IDE/extensions.json B
Marketplace features /feature/getImplementations B
Search aggregation /api/search/aggregation/$ A
Search last compatible update /api/search/compatibleUpdates A
Search nearest update /api/search/updates/nearest A
Search plugins /api/search/plugins A
Plugin reviews /api/products/intellij/plugins/$/comments$ C
Plugin homepage /plugin/index B
Plugin reviews policy /docs/marketplace/reviews-policy.html C
Plugin write review /intellij/$/review/new C