<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Uladzimir Tsykun</title>
    <description>The latest articles on DEV Community by Uladzimir Tsykun (@vtsykun).</description>
    <link>https://dev.to/vtsykun</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1035119%2Ff0ab9122-1075-425e-ac35-eec95caa0e36.jpeg</url>
      <title>DEV Community: Uladzimir Tsykun</title>
      <link>https://dev.to/vtsykun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vtsykun"/>
    <language>en</language>
    <item>
      <title>Mirroring Composer dependencies with Packeton</title>
      <dc:creator>Uladzimir Tsykun</dc:creator>
      <pubDate>Sun, 05 Mar 2023 17:46:34 +0000</pubDate>
      <link>https://dev.to/vtsykun/mirror-composer-dependencies-with-packeton-1jea</link>
      <guid>https://dev.to/vtsykun/mirror-composer-dependencies-with-packeton-1jea</guid>
      <description>&lt;h2&gt;
  
  
  Introduce
&lt;/h2&gt;

&lt;p&gt;Packeton is an open-source private Composer repository based on Packagist.org and Satis, distributed under the MIT license. Back in 2018, there was no free alternative to the Private Packagist packagist.com with the necessary functionality, but the Satis functionality is very limited. I like the UI of the Packagist.org thought it would be cool to use it for my own private Packagist, so I forked it to make it available for everyone to reuse. The source code is available on &lt;a href="https://github.com/vtsykun/packeton" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mirror dependencies
&lt;/h2&gt;

&lt;p&gt;Mirroring dependencies can be useful for several scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speed-up downloading dependencies &lt;/li&gt;
&lt;li&gt;Sharing access to a private Composer repository, such as Magento, with your team and customers.&lt;/li&gt;
&lt;li&gt;Preventing Dependency Confusion &lt;a href="https://blog.packagist.com/preventing-dependency-hijacking/" rel="noopener noreferrer"&gt;attacks&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Packeton will create a local copy of a remote repository, allowing you to download dependencies and metadata from the local copy instead of the remote composer repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;To get started with Packeton, you'll first need to install the application. You can do this using Docker by creating a &lt;code&gt;docker-compose.yml&lt;/code&gt; file with the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.6'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;packeton&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packeton/packeton:latest&lt;/span&gt;
        &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packeton&lt;/span&gt;
        &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packeton&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;ADMIN_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
            &lt;span class="na"&gt;ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;123456&lt;/span&gt; &lt;span class="c1"&gt;# Default password&lt;/span&gt;
            &lt;span class="na"&gt;ADMIN_EMAIL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin@example.com&lt;/span&gt;
            &lt;span class="na"&gt;TRUSTED_PROXIES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.16.0.0/12&lt;/span&gt;
            &lt;span class="c1"&gt;# Default SQLite&lt;/span&gt;
            &lt;span class="c1"&gt;# DATABASE_URL: "mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8&amp;amp;charset=utf8mb4"&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:8081:80'&lt;/span&gt;
        &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.docker:/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to start the container: &lt;code&gt;docker-compose up -d&lt;/code&gt; The application will then be accessible at &lt;code&gt;http://127.0.0.1:8081&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, you may want to install an Nginx reverse proxy to make the application available at a custom domain name. Here's an example configuration for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen *:443 http2;

    server_name packages.example.org;

    ssl_certificate /etc/nginx/ssl/example.org.crt;
    ssl_certificate_key /etc/nginx/ssl/example.org.key;
    ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4';

    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_session_timeout  5m;

    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 16k;
    gzip_http_version 1.1;
    gzip_min_length 2048;
    gzip_types text/css application/javascript text/javascript application/json;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:8081/;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can login on &lt;code&gt;packages.example.org&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Composer proxy
&lt;/h3&gt;

&lt;p&gt;To configure the Composer proxy, you need to add it on configuration level. For example, for a docker installation, this is a file &lt;code&gt;config.yaml&lt;/code&gt; in a volume. For source code installation put config file with any name the directory &lt;code&gt;{project dir}/config/packages&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;packeton&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mirrors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;orocrm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://satis.oroinc.com/&lt;/span&gt;

            &lt;span class="c1"&gt;# SSH keys for create ZIP archive from Git source.&lt;/span&gt;
            &lt;span class="na"&gt;git_ssh_keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;git@github.com:oroinc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/var/www/.ssh/private_key'&lt;/span&gt;
&lt;span class="c1"&gt;#            logo: 'https://example.com/logo.png'&lt;/span&gt;
&lt;span class="c1"&gt;#            http_basic:&lt;/span&gt;
&lt;span class="c1"&gt;#                username: user&lt;/span&gt;
&lt;span class="c1"&gt;#                password: pass&lt;/span&gt;

            &lt;span class="c1"&gt;# Allow public access, default false&lt;/span&gt;
&lt;span class="c1"&gt;#            public_access: true&lt;/span&gt;
            &lt;span class="c1"&gt;# default false&lt;/span&gt;
&lt;span class="c1"&gt;#            sync_lazy: true&lt;/span&gt;
&lt;span class="c1"&gt;#            enable_dist_mirror: false&lt;/span&gt;

            &lt;span class="c1"&gt;# Additional restriction, but you can restrict it in UI&lt;/span&gt;
&lt;span class="c1"&gt;#            available_package_patterns:&lt;/span&gt;
&lt;span class="c1"&gt;#                - 'vend1/*'&lt;/span&gt;
&lt;span class="c1"&gt;#            available_packages:&lt;/span&gt;
&lt;span class="c1"&gt;#                - 'pack1/name1' # but you can restrict it in UI&lt;/span&gt;
            &lt;span class="c1"&gt;# JSON. auth.json to pass composer opts.&lt;/span&gt;
&lt;span class="c1"&gt;#            composer_auth: '{"auth.json..."}'&lt;/span&gt;

            &lt;span class="c1"&gt;# default auto.&lt;/span&gt;
            &lt;span class="na"&gt;sync_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~&lt;/span&gt;
            &lt;span class="c1"&gt;# Console info message&lt;/span&gt;
            &lt;span class="na"&gt;info_cmd_message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you will need to restart docker container to apply configuration. &lt;/p&gt;

&lt;p&gt;The proxy information now will be shown by url &lt;a href="https://packages.example.com/proxies/orocrm" rel="noopener noreferrer"&gt;https://packages.example.com/proxies/orocrm&lt;/a&gt;&lt;br&gt;
Synchronization of metadata is launched by cron, and in order to update the data after creating a proxy, you need to run the update through the console or through UI. If everything happened without errors, then you will see the result of the synchronization as below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbk6sk7y4fzk5a3fz56at.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbk6sk7y4fzk5a3fz56at.png" alt="Job Result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use this proxy you need to add this line to your composer.json.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.example.org/mirror/orocrm"
    }
  ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, this resource is available only with aurization, so you need enable auth for composer &lt;code&gt;composer config --global --auth http-basic.packages.example.org {username} {apitoken}&lt;/code&gt; Where &lt;code&gt;apitoken&lt;/code&gt; you can see on the profile page.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dependencies Approval.
&lt;/h4&gt;

&lt;p&gt;In Packeton by default dependencies are automatically enabled, when you run the first time &lt;code&gt;composer update&lt;/code&gt;. In the example above, I used the repository &lt;a href="https://satis.oroinc.com" rel="noopener noreferrer"&gt;https://satis.oroinc.com&lt;/a&gt; that has third-party dependencies. Example of the &lt;a href="https://github.com/oroinc/platform-application/blob/5.1.0-rc.2/composer.json#L15" rel="noopener noreferrer"&gt;application&lt;/a&gt; that uses this repo.&lt;/p&gt;

&lt;p&gt;The root file &lt;a href="https://satis.oroinc.com/packages.json" rel="noopener noreferrer"&gt;packages.json&lt;/a&gt; does not contains restrictions by &lt;code&gt;available_package_patterns&lt;/code&gt;. Depending on the satis settings, if &lt;code&gt;require-all&lt;/code&gt; is enabled, the third party vendor can publish a package under any vendor name. For example &lt;code&gt;symfony/process&lt;/code&gt;. So attacker may replace your dependencies, because if a package name is found in this custom package repository, only those versions are loaded at all. &lt;/p&gt;

&lt;p&gt;To select specific packages that you use in a project and remove those you don't need, you can turn on strict mode. Go to proxy settings and select the "Strict mode"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feakg0r9l92aje8e9i6hs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feakg0r9l92aje8e9i6hs.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, using the Mass mirror action, you can enable and approve your packages. Put the composer info output as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kvregbfw8nuzohbtq6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kvregbfw8nuzohbtq6v.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Depending on the User-Agent the metadata is available in two formats for Composer 2 and 1 at once. However, the original repository only provide the metadata for Composer 1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"providers-lazy-url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mirror/orocrm/pkg/%package%.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mirrors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"dist-url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mirror/orocrm/zipball/%package%/%version%/%reference%.%type%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"preferred"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"metadata-url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mirror/orocrm/p2/%package%.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"available-packages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"oro/platform-enterprise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"oro/crm-enterprise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"oro/api-doc-bundle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"oro/crm-pro-ldap-bundle"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If User-Agent header is match with Composer 1, then all packages will be merge into one snapshot &lt;code&gt;includes&lt;/code&gt; for spend up Composer 1 downloads.&lt;/p&gt;

&lt;p&gt;When the User-Agent header matches to Composer1, Packeton will merge all packages into one snapshot &lt;code&gt;includes&lt;/code&gt; to speed up Composer 1 downloads. Because Composer 1 is not able to perform parallel downloads, so by merging all packages into one snapshot, Packeton can reduce the number of requests Composer 1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"includes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"include-packeton/all$6150da03358e44ebc3b99713c643e98dc06a221e.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"sha1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6150da03358e44ebc3b99713c643e98dc06a221e"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mirrors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"dist-url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mirror/orocrm/zipball/%package%/%version%/%reference%.%type%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"preferred"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Packeton source code &lt;a href="https://github.com/vtsykun/packeton" rel="noopener noreferrer"&gt;https://github.com/vtsykun/packeton&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Installation instruction &lt;a href="https://docs.packeton.org/installation.html" rel="noopener noreferrer"&gt;https://docs.packeton.org/installation.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;About Preventing Dependency Confusion with Composer &lt;a href="https://blog.packagist.com/preventing-dependency-hijacking/" rel="noopener noreferrer"&gt;https://blog.packagist.com/preventing-dependency-hijacking/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>composer</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
