<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/feed.xsl"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:webfeeds="http://webfeeds.org/rss/1.0" version="2.0">
  <channel>
    <title>PhiPhi’s Bizarre Aventure (fr)</title>
    <link>https://normco.re/</link>
    <atom:link href="https://normco.re/fr/rss.xml" rel="self" type="application/rss+xml"/>
    <description>Blog personnel de Phiphi, basé à Chengdu, en Chine.</description>
    <lastBuildDate>Sat, 11 Apr 2026 07:57:44 GMT</lastBuildDate>
    <language>fr</language>
    <item>
      <title>Comment ce blog est déployé sur Alibaba Cloud OSS et CDN</title>
      <link>https://normco.re/fr/posts/alibaba-cloud-oss-cdn-deployment/</link>
      <guid isPermaLink="false">https://normco.re/fr/posts/alibaba-cloud-oss-cdn-deployment/</guid>
      <description>
        Présentation pratique de ma pipeline GitHub Actions : build Lume réchauffé, prise de rôle OIDC, synchronisation OSS, refresh CDN et nettoyage automatique.
      </description>
      <content:encoded>
        <![CDATA[<p>Ce site est construit avec Lume puis déployé sur Alibaba Cloud OSS, avec Alibaba
Cloud CDN en frontal. La chaîne de déploiement reste volontairement compacte :
un workflow GitHub, Deno disponible sur le runner, et une action personnalisée
qui restaure un cache local de build, exécute le build, synchronise OSS et
nettoie :
<a href="https://github.com/frenchvandal/aliyun-oss-cdn-sync-action"><code>frenchvandal/aliyun-oss-cdn-sync-action</code></a>.</p>
<p>Je cherche désormais cinq propriétés en même temps : des identifiants à durée de
vie courte, des builds réchauffés sur des runners neufs, des uploads
prévisibles, des en-têtes de cache cohérents avec les fichiers livrés, et une
cohérence de cache automatique. Cette configuration me donne les cinq sans
ajouter de scripts de déploiement propres à chaque dépôt.</p>
<h2>Pipeline en un coup d’œil</h2>
<p>En régime de production nominal, le workflow de ce dépôt fait maintenant trois
choses :</p>
<ol>
<li>Récupère le dépôt.</li>
<li>Installe Deno avec la version verrouillée dans <code>.tool-versions</code> et active le
cache outillage du runner.</li>
<li>Exécute l’action de synchronisation OSS/CDN, qui restaure <code>_cache</code>, lance
<code>deno task build</code>, synchronise <code>_site</code>, rafraîchit le CDN et nettoie.</li>
</ol>
<p>À ce stade, ce build fingerprint aussi la feuille partagée et les feuilles
critiques par route, retire les source maps et supprime les fichiers optionnels
de Pagefind qui ne sont plus référencés par le HTML final avant la synchro de
<code>_site</code>.</p>
<pre><code class="language-yaml">name: Deploy static content to OSS

on:
  push:
    branches: [&quot;master&quot;]
  workflow_dispatch:

permissions:
  contents: read
  id-token: write

concurrency:
  group: &quot;oss-deploy&quot;
  cancel-in-progress: true

jobs:
  deploy:
    environment:
      name: development
    runs-on: macos-26
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup Deno environment
        uses: denoland/setup-deno@v2
        with:
          deno-version-file: .tool-versions
          cache: true

      - name: Deploy to Alibaba Cloud OSS
        uses: frenchvandal/aliyun-oss-cdn-sync-action@master
        with:
          role-oidc-arn: ${{ secrets.ALIBABA_CLOUD_ROLE_ARN }}
          oidc-provider-arn: ${{ secrets.ALIBABA_CLOUD_OIDC_PROVIDER_ARN }}
          role-session-name: ${{ github.run_id }}
          role-session-expiration: 3600
          cache-enabled: true
          cache-key: &gt;-
            lume-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('deno.lock') }}-${{ hashFiles('_cache/**/*') || 'empty' }}
          cache-restore-keys: |
            lume-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('deno.lock') }}-
            lume-cache-${{ runner.os }}-${{ runner.arch }}-
          input-dir: _site
          bucket: ${{ secrets.OSS_BUCKET }}
          region: ${{ secrets.OSS_REGION }}
          audience: ${{ github.repository_id }}
          cdn-enabled: true
          cdn-base-url: ${{ secrets.OSS_CDN_BASE_URL }}
          cdn-actions: refresh
          build-command: deno task build
</code></pre>
<p>Dans ce dépôt, j’utilise maintenant <code>cdn-actions: refresh</code>. L’action supporte
toujours <code>refresh,preload</code>, mais le workflow de ce site n’active plus le preload
à chaque déploiement.</p>
<h2>Pourquoi OIDC plutôt que des clés d’accès</h2>
<p>L’action utilise GitHub OIDC pour endosser un rôle RAM Alibaba Cloud à
l’exécution. Aucune clé d’accès longue durée n’est stockée dans le dépôt. Le
workflow n’a besoin que de <code>id-token: write</code>, de l’ARN du rôle RAM et de l’ARN
du fournisseur OIDC.</p>
<p>L’authentification est désormais strictement OIDC : l’action ne retombe pas sur
des access keys statiques passées en entrée ou via l’environnement si la prise
de rôle échoue.</p>
<p>La configuration actuelle transmet aussi
<code>audience: ${{ github.repository_id }}</code>. GitHub peut émettre un ID token avec
une audience personnalisée, et Alibaba Cloud RAM peut valider cette valeur <code>aud</code>
par rapport aux Client ID configurés sur le fournisseur d’identité OIDC et dans
la trust policy du rôle. La frontière de confiance est donc plus resserrée
qu’avec l’audience par défaut seule.</p>
<h2>Fonctionnement interne de l’action</h2>
<p>L’action est découpée en trois phases :</p>
<ul>
<li><strong>pre</strong> : endosse le rôle RAM via OIDC et stocke des identifiants temporaires
dans l’état de l’action.</li>
<li><strong>main</strong> : restaure éventuellement le dossier local <code>_cache</code>, exécute
<code>build-command</code>, upload les fichiers locaux sur OSS, applique les en-têtes de
cache et déclenche les actions CDN si elles sont activées.</li>
<li><strong>post</strong> : compare les objets distants sous le préfixe cible avec les fichiers
locaux produits par le build, supprime les orphelins côté distant, rafraîchit
au besoin les URL supprimées, écrit des résumés d’exécution CDN et peut
sauvegarder <code>_cache</code> si <code>cache-key</code> est configuré.</li>
</ul>
<p>La phase de nettoyage s’exécute avec <code>post-if: always()</code>, donc elle se lance
même si des étapes précédentes échouent. Le nettoyage, les appels CDN et la
restauration ou sauvegarde du cache sont volontairement non bloquants : les
avertissements sont journalisés, mais des incidents CDN ou cache transitoires
n’interrompent pas le déploiement.</p>
<h2>Upload, cache, quota et contrôle de dérive</h2>
<p>Quelques détails d’implémentation comptent pour la fiabilité :</p>
<ul>
<li><code>build-command</code> s’exécute dans l’action avant tout upload OSS ou appel CDN.</li>
<li>Si <code>cache-enabled</code> vaut <code>true</code> et que <code>cache-key</code> est configuré, <code>_cache</code> est
restauré avant le build et un nouvel instantané peut être sauvegardé pendant
<code>post</code>.</li>
<li><code>cache-enabled</code> ne contrôle que la restauration : je peux donc neutraliser le
warm cache temporairement sans perdre la sauvegarde de cache en post-step.</li>
<li>Les uploads sont parallélisés via <code>max-concurrency</code>.</li>
<li>Un throttling global d’API est appliqué avec <code>api-rps-limit</code>.</li>
<li>Chaque upload de fichier est retenté jusqu’à trois fois avant d’être marqué en
échec.</li>
<li>Les échecs partiels d’upload remontent via <code>failed-count</code> et le résumé du job
GitHub Actions au lieu d’être noyés dans les logs.</li>
<li>L’étape d’upload écrit désormais automatiquement des en-têtes
<code>Cache-Control</code> : les fichiers HTML et <code>sw.js</code> sont revalidés de manière
agressive, les assets hashés sont envoyés comme immuables, et les ressources
statiques courantes reçoivent des fenêtres de revalidation plus courtes au
lieu d’une politique unique pour tout.</li>
<li><code>cdn-actions</code> n’accepte que <code>refresh</code> ou <code>refresh,preload</code> ; si le CDN est
activé et que la valeur est vide ou invalide, l’action retombe sur <code>refresh</code>.</li>
<li>L’action vérifie le quota CDN restant avant de soumettre des lots de refresh
ou de preload.</li>
<li>La suppression d’objets OSS peut déclencher un refresh CDN des URL supprimées,
ce qui réduit les fenêtres de cache obsolète et limite la dérive des objets
dans le temps.</li>
<li>L’action écrit aussi un résumé GitHub Actions pour le déploiement, le
nettoyage et l’état des tâches CDN.</li>
</ul>
<p>Ce dernier point est facile à sous-estimer : l’upload seul ne suffit pas pour un
site statique qui évolue. Il faut aussi gérer la suppression et l’invalidation
de cache des objets qui ne devraient plus exister.</p>
<h2>Permissions RAM minimales</h2>
<p>Au niveau des politiques, le rôle a besoin de permissions OSS sur le scope du
bucket cible pour lister, uploader et supprimer les objets. Côté CDN, ce
workflow a besoin des permissions de refresh ainsi que des API de lecture
utilisées pour les contrôles de quota et le suivi des tâches. Si le preload est
activé, il faut y ajouter la permission correspondante. La trust policy doit
autoriser le fournisseur OIDC GitHub à endosser le rôle de déploiement.</p>
<p>Je conserve ce rôle comme rôle de déploiement dédié, sans le mélanger à des
permissions opérationnelles plus larges.</p>
<h2>Réutiliser l’action dans d’autres dépôts</h2>
<p>L’action est publiée et réutilisable :
<a href="https://github.com/frenchvandal/aliyun-oss-cdn-sync-action">github.com/frenchvandal/aliyun-oss-cdn-sync-action</a>.</p>
<p>Ce dépôt suit actuellement <code>@master</code> parce que je fais évoluer le site et
l’action ensemble. Dans un autre dépôt, j’épinglerais plutôt <code>@v1</code>, voire un SHA
de commit complet. Je laisse maintenant l’action piloter le build et le cycle de
vie de <code>_cache</code>, ce qui raccourcit encore le workflow consommateur.</p>
<p>Pour moi, le résultat clé reste le même : des déploiements prévisibles.
Restauration du cache, build, sync, refresh, cleanup, terminé.</p>
]]>
      </content:encoded>
      <pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Lorem ipsum et l’art du texte de remplissage</title>
      <link>https://normco.re/fr/posts/lorem-ipsum/</link>
      <guid isPermaLink="false">https://normco.re/fr/posts/lorem-ipsum/</guid>
      <description>
        Une réflexion sur le faux texte, son histoire et les raisons pour lesquelles il reste utile dans les workflows de design modernes.
      </description>
      <content:encoded>
        <![CDATA[<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Tout le monde connaît la formule, mais peu de personnes en connaissent
l’origine. Il s’agit d’un extrait brouillé du <em>de Finibus Bonorum et Malorum</em> de
Cicéron, un traité philosophique rédigé en 45 av. J.-C. Ce texte est utilisé
comme faux contenu depuis le XVIe siècle, lorsqu’un imprimeur inconnu a mélangé
une casse de caractères pour créer un spécimen typographique.</p>
<p><img src="https://normco.re/posts/lorem-ipsum/images/amol-srivastava-uOYc6OlgpUI-unsplash.jpg" alt=""></p>
<h2>Pourquoi le texte de remplissage compte</h2>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Quand vous concevez une mise en page avec de vrais mots, vous concevez pour ces
mots précis. Le faux texte vous oblige à concevoir pour la structure, pas pour
le sens. C’est une contrainte utile : elle révèle si votre design peut absorber
l’imprévu : un titre sur trois lignes, un paragraphe sans respiration naturelle,
un mot trop long pour son conteneur.</p>
<h2>Une question de fidélité</h2>
<p>Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit
laboriosam ? En design, il existe une tension entre la fidélité au contenu réel
et la liberté qu’autorise l’abstraction.</p>
<p>Les prototypes haute fidélité avec du vrai contenu exigent que ce contenu existe
déjà. Les wireframes basse fidélité, eux, communiquent la structure sans la
distraction du sens. Les deux approches ont leur place. L’essentiel est de
savoir quel outil sert le moment.</p>
<pre><code class="language-ts">// A small utility to generate repeating text blocks.
function lorem(words: number): string {
  const base = &quot;lorem ipsum dolor sit amet consectetur adipiscing elit&quot;;
  const tokens = base.split(&quot; &quot;);
  const result: string[] = [];
  for (let i = 0; i &lt; words; i++) {
    result.push(tokens[i % tokens.length] ?? &quot;&quot;);
  }
  return result.join(&quot; &quot;);
}
</code></pre>
<p>Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed
quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
<p><img src="https://normco.re/posts/lorem-ipsum/images/scott-rodgerson-z0MDyylvY1k-unsplash.jpg" alt=""></p>
<h2>Conclusion</h2>
<p>Le lorem ipsum est plus qu’un simple remplissage. C’est un miroir tendu au
design : il reflète la structure dépouillée de sens, la forme sans contenu.
C’est précisément pour cela qu’il traverse les époques.</p>
]]>
      </content:encoded>
      <pubDate>Wed, 18 Feb 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Vestibulum Ante : seuils et nouveaux départs</title>
      <link>https://normco.re/fr/posts/vestibulum-ante/</link>
      <guid isPermaLink="false">https://normco.re/fr/posts/vestibulum-ante/</guid>
      <description>Réflexions sur les transitions, les espaces intermédiaires de la vie et ce que signifie se tenir sur un seuil.</description>
      <content:encoded>
        <![CDATA[<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
curae. Le mot latin <em>vestibulum</em> — une antichambre, une entrée, l’espace devant
la porte — porte un poids que son descendant français « vestibule » ne restitue
pas toujours.</p>
<p>Un seuil n’est ni une fin, ni un commencement. C’est l’espace liminal entre les
deux : le souffle retenu avant le premier mot, la pause après le tour de clé.
C’est l’endroit où quelque chose s’achève et où autre chose n’a pas encore
commencé.</p>
<p><img src="https://normco.re/posts/vestibulum-ante/images/bruno-martins-4cwf-iW6I1Q-unsplash.jpg" alt=""></p>
<h2>La valeur de l’entre-deux</h2>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac
turpis egestas. Nous vivons dans une culture qui dévalue la transition. Nous
voulons des résultats, des livrables, l’objet fini. Le milieu brouillon, les
brouillons successifs, le doute, l’accumulation lente de compréhension, tout
cela reste peu documenté et rarement célébré.</p>
<p>Pourtant, l’entre-deux est l’endroit où se déroule l’essentiel de la vie. Les
trajets. Les salles d’attente. Le temps entre l’envoi d’un message et la lecture
de la réponse. Les années entre le moment où l’on sait ce que l’on veut et celui
où l’on sait comment l’obtenir.</p>
<h2>Se tenir sur le pas de la porte</h2>
<p>Fusce suscipit varius mi. Cum sociis natoque penatibus et magnis dis parturient
montes, nascetur ridiculus mus. Phasellus viverra nulla ut metus varius laoreet.</p>
<p>J’ai déménagé à Chengdu un mardi du début d’automne. La ville était chaude d’une
manière qui m’a surpris, non pas la chaleur de l’été, mais la chaleur d’un lieu
qui avait déjà décidé qu’il vous appréciait. Les rues sentaient l’huile pimentée
et l’osmanthe, et tout semblait légèrement, agréablement familier et nouveau à
la fois.</p>
<p>C’est cela, un seuil, je crois. Ni hostile, ni accueillant. Simplement ouvert,
en attente de ce que vous en ferez.</p>
<h2>Coda</h2>
<p>Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi,
condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit
eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim
ac dui.</p>
<p>Chaque texte possède son vestibule, la page de titre, l’introduction, le premier
paragraphe. C’est l’espace qui prépare la lectrice ou le lecteur à ce qui vient
ensuite. Il faut l’écrire avec soin.</p>
]]>
      </content:encoded>
      <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Proin Facilisis : rendre les choses plus simples</title>
      <link>https://normco.re/fr/posts/proin-facilisis/</link>
      <guid isPermaLink="false">https://normco.re/fr/posts/proin-facilisis/</guid>
      <description>Sur la philosophie de la réduction des frictions, dans le code, le design et la vie quotidienne.</description>
      <content:encoded>
        <![CDATA[<p><em>Proin facilisis</em> — en latin, « favoriser l’aisance ». L’expression apparaît
dans d’anciens textes botaniques pour décrire une plante qui facilite la
digestion, adoucit un passage, lève une obstruction. Comme philosophie de
conception logicielle, la formule se transpose étonnamment bien.</p>
<p>Un bon logiciel réduit les frictions. Il anticipe l’étape suivante de
l’utilisateur. Il propose la bonne affordance au bon moment. Il s’efface.</p>
<p><img src="https://normco.re/posts/proin-facilisis/images/brett-jordan-92-mTYj5oGs-unsplash.jpg" alt=""></p>
<h2>L’inventaire des frictions</h2>
<p>Proin in tellus sit amet nibh dignissim sagittis. La première étape pour réduire
les frictions est de les cartographier. Où l’utilisateur ralentit-il ? Où
l’attention monte-t-elle ? Où les erreurs se concentrent-elles ?</p>
<p>Dans une application web classique, les moments de forte friction sont
prévisibles : onboarding, envoi de formulaire, récupération après erreur et
états de chargement. Ce sont les vestibules du produit, les seuils que
l’utilisateur doit franchir pour atteindre la valeur.</p>
<pre><code class="language-ts">// Friction shows up in code too.
// Compare these two approaches to handling a missing value:

// High friction — the caller must always check:
function getUser(id: string): User | undefined {/* … */}

// Lower friction — the error is explicit and handled at the boundary:
function getUser(id: string): User {
  const user = db.find(id);
  if (user === undefined) {
    throw new Error(`User not found: ${id}`);
  }
  return user;
}
</code></pre>
<h2>Le paradoxe de la simplicité</h2>
<p>Vivamus pretium aliquet erat. Il y a un paradoxe au cœur du « rendre simple » :
c’est difficile. Éliminer les frictions demande une compréhension fine de
l’utilisateur, du contexte et des modes d’échec. Cela exige plus de travail côté
concepteur pour en demander moins côté utilisateur.</p>
<p>C’est pour cela que la simplicité est une forme de générosité. Chaque étape
inutile retirée du parcours rend du temps à l’utilisateur, du temps qu’il peut
consacrer à ce qui compte vraiment.</p>
<h2>Facilisis en pratique</h2>
<p>Donec aliquet metus ut erat semper, et tincidunt nulla luctus. Quelques
principes vers lesquels je reviens régulièrement :</p>
<ul>
<li>Les valeurs par défaut doivent convenir à la majorité des usages.</li>
<li>Les messages d’erreur doivent expliquer le problème et la correction.</li>
<li>Le chemin nominal ne devrait demander aucun effort mental.</li>
<li>La configuration doit être possible, jamais obligatoire.</li>
<li>La documentation fait partie intégrante du produit.</li>
</ul>
<p>Nulla facilisi. Phasellus blandit leo ut odio. Nam sed nulla non diam tincidunt
tempus. Le nom même de ce principe — <em>nulla facilisi</em>, « rien de facile » —
rappelle que la simplicité, bien comprise, n’est jamais accidentelle.</p>
]]>
      </content:encoded>
      <pubDate>Sat, 12 Apr 2025 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Comment installer ce thème ?</title>
      <link>https://normco.re/fr/posts/instructions/</link>
      <guid isPermaLink="false">https://normco.re/fr/posts/instructions/</guid>
      <description>Un guide rapide pour configurer le thème Simple Blog avec Lume.</description>
      <content:encoded>
        <![CDATA[<p><strong>Simple blog</strong> est un thème de blog épuré et minimal pour Lume, avec prise en
charge des tags et des auteurs. Il permet de créer votre blog <strong>en quelques
secondes</strong>, et fournit des flux Atom et JSON pour vos abonnés.</p>
<p>La manière <strong>la plus simple et la plus rapide</strong> de configurer ce thème est
d’utiliser la <a href="https://deno.land/x/lume_init">commande Lume init</a>, que vous
pouvez aussi copier facilement depuis la
<a href="https://lume.land/theme/simple-blog/">page du thème Simple Blog</a>. En lançant :</p>
<pre><code class="language-bash">deno run -A https://lume.land/init.ts --theme=simple-blog
</code></pre>
<p>vous créez un nouveau projet avec Simple Blog déjà configuré. Modifiez ensuite
le fichier <code>_data.yml</code> à la racine du blog pour personnaliser le titre, la
description et les métadonnées du site.</p>
<p>Les articles doivent être enregistrés dans le dossier <code>posts</code>. Par exemple :
<code>posts/my-first-post.md</code>.</p>
<h2>Installer en tant que thème distant</h2>
<p>Pour ajouter ce thème à un projet Lume existant, importez-le dans votre fichier
<code>_config.ts</code> comme module distant. Pour le mettre à jour, changez le numéro de
version dans l’URL d’import :</p>
<pre><code class="language-ts">import lume from &quot;lume/mod.ts&quot;;
import blog from &quot;https://deno.land/x/lume_theme_simple_blog@v0.15.6/mod.ts&quot;;

const site = lume();

site.use(blog());

export default site;
</code></pre>
<p>Copiez ensuite le fichier
<a href="https://github.com/lumeland/theme-simple-blog/blob/main/src/_data.yml"><code>_data.yml</code></a>
à la racine de votre blog et adaptez-le avec vos informations.</p>
<h2>Personnalisation</h2>
<p>Vous pouvez utiliser <a href="https://lume.land/cms">lumeCMS</a> pour personnaliser le blog
et ajouter du contenu facilement.</p>
]]>
      </content:encoded>
      <pubDate>Sun, 12 Jun 2022 00:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>