diff --git a/content/newsletter/craft-letter-14.md b/content/newsletter/craft-letter-14.md
new file mode 100644
index 0000000..8ad38d7
--- /dev/null
+++ b/content/newsletter/craft-letter-14.md
@@ -0,0 +1,5 @@
+Title: Lettre n°14 — 09 mars 2026
+Date: 2026-03-09 09:00
+Category: Newsletter
+JsonLD:
+
diff --git a/content/pages/index.md b/content/pages/index.md
index 24441d2..2da9fb0 100644
--- a/content/pages/index.md
+++ b/content/pages/index.md
@@ -1,14 +1,14 @@
Title: Accueil
-Date: 2026-03-02 09:00
+Date: 2026-03-09 09:00
URL:
save_as: index.html
Category: Home
-JsonLD: { "@context": "https://schema.org", "@type": "WebPage", "name": "Accueil", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Mar 02 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } }
+JsonLD: { "@context": "https://schema.org", "@type": "WebPage", "name": "Accueil", "description": "Lettre de veille technologique en développement logiciel", "image": [ "https://www.craftletter.fr/images/craftletter.svg" ], "datePublished": "Mon Mar 09 2026 09:00:00 GMT+0200 (Coordinated Universal Time)", "author": { "@type": "Person", "name": "Pascal Le Merrer", "url": "https://www.linkedin.com/in/pascal-le-merrer/" } }
-# La [lettre n°13]({filename}/newsletter/craft-letter-13.md) est parue !
+# La [lettre n°14]({filename}/newsletter/craft-letter-14.md) est parue !
La Craft Letter est une newsletter hebdomadaire dans laquelle je partage des articles
issus de ma veille technologique. Vous y trouverez des articles relatifs au développement logiciel d'une façon générale, qu'il soit front-end, back-end ou autre. Mais aussi des articles consacrés à l'architecture logicielle, la méthodologie, les outils, des projets open source, des conférences...
@@ -37,6 +37,7 @@ Pour savoir qui je suis, ou pourquoi j'écris cette lettre, je vous invite à vo
# Archives
+* [Lettre n°14]({filename}/newsletter/craft-letter-14.md)
* [Lettre n°13]({filename}/newsletter/craft-letter-13.md)
* [Lettre n°12]({filename}/newsletter/craft-letter-12.md)
* [Lettre n°11]({filename}/newsletter/craft-letter-11.md)
diff --git a/justfile b/justfile
index a1319a4..d3d2b83 100644
--- a/justfile
+++ b/justfile
@@ -40,8 +40,8 @@ devserver-global:
pelican -lr content -o output -s "{{CONFFILE}}" {{PELICANOPTS}} -b 0.0.0.0
# generate using production settings
-publish number:
- just format {{number}}
+publish:
+ just format
pelican content -o output -s "{{PUBLISHCONF}}" {{PELICANOPTS}}
rsync -e ssh -av --delete-after /Users/pascal/Documents/craft-letter/output/ craftletter@ssh-craftletter.alwaysdata.net:/home/craftletter/www
@@ -50,15 +50,15 @@ ssh:
ssh craftletter@ssh-craftletter.alwaysdata.net
# Create the skeleton for a new issue of the newsletter, and reference it into the home page
-new number:
- PYTHONPATH=PWD venv/bin/python ./scripts/create_newsletter.py --number={{number}}
+new:
+ PYTHONPATH=PWD venv/bin/python ./scripts/create_newsletter.py
# generate HTML email
-mail number:
- just format {{number}}
- PYTHONPATH=PWD venv/bin/python ./scripts/prepare_email.py --number={{number}}
+mail:
+ just format
+ PYTHONPATH=PWD venv/bin/python ./scripts/prepare_email.py
# Format the content of a given newsletter
-format number:
- PYTHONPATH=PWD venv/bin/python ./scripts/format.py --number={{number}}
+format:
+ PYTHONPATH=PWD venv/bin/python ./scripts/format.py
diff --git a/scripts/create_newsletter.py b/scripts/create_newsletter.py
index ffc7369..817ded4 100644
--- a/scripts/create_newsletter.py
+++ b/scripts/create_newsletter.py
@@ -30,17 +30,18 @@ def set_publication_date_for_seo(text: str, publication_date: datetime) -> str:
return text.replace("{DATE_UTC}", date_utc)
-# Parse the command line arguments
-parser = argparse.ArgumentParser()
-parser.add_argument("-n", "--number", required=True, type=int, help="Newsletter number")
-args = parser.parse_args()
+# Find the number of the letter to create
+destination_path = Path(f"./content/newsletter")
+p = destination_path.glob("craft-letter-*.md")
+files = [x for x in p if x.is_file()]
+letter_number = len(files) + 1
# Load the newsletter template
template = Path("./template/newsletter.md")
content = template.read_text()
-new_content = content.replace("{LETTER_NUMBER}", str(args.number))
+new_content = content.replace("{LETTER_NUMBER}", str(letter_number))
today = datetime.today()
next_monday = get_next_weekday(today, MONDAY)
@@ -52,9 +53,11 @@ new_content = set_publication_date_for_pelican(new_content, next_monday)
new_content = set_publication_date_for_seo(new_content, next_monday)
# Create the new file
-destination = Path(f"./content/newsletter/craft-letter-{args.number}.md")
+destination = destination_path / f"craft-letter-{letter_number}.md"
destination.write_text(new_content)
+print(f"Created letter #{letter_number}: {destination}")
+
# Load the homepage template
template = Path("./template/index.md")
content = template.read_text()
@@ -63,15 +66,15 @@ new_content = set_publication_date_for_pelican(content, next_monday)
new_content = set_publication_date_for_seo(new_content, next_monday)
-link = (
- f"[lettre n°{args.number}]({{filename}}/newsletter/craft-letter-{args.number}.md)"
-)
+link = f"[lettre n°{letter_number}]({{filename}}/newsletter/craft-letter-{letter_number}.md)"
new_content = new_content.replace("{LINK}", link)
-for i in reversed(range(args.number)):
+for i in reversed(range(letter_number)):
link = f"* [Lettre n°{i + 1}]({{filename}}/newsletter/craft-letter-{i + 1}.md)\n"
new_content += link
# Update the index page
destination = Path("./content/pages/index.md")
destination.write_text(new_content)
+
+print(f"Updated index page: {destination}")
diff --git a/scripts/format.py b/scripts/format.py
index 7a61f52..56fbda2 100644
--- a/scripts/format.py
+++ b/scripts/format.py
@@ -1,5 +1,6 @@
import argparse
from pathlib import Path
+from util import get_latest_newsletter_number
REPLACEMENTS = {
" :": " :",
@@ -9,11 +10,12 @@ REPLACEMENTS = {
"'": "’",
}
-parser = argparse.ArgumentParser()
-parser.add_argument("-n", "--number", required=True, type=int, help="Newsletter number")
-args = parser.parse_args()
-file = Path(f"./content/newsletter/craft-letter-{args.number}.md")
+letter_number = get_latest_newsletter_number()
+
+file = Path(f"./content/newsletter/craft-letter-{letter_number}.md")
+
+print(f"Formatting letter #{letter_number}: {file}")
content = file.read_text()
for value, replacement in REPLACEMENTS.items():
diff --git a/scripts/prepare_email.py b/scripts/prepare_email.py
index 81d03d4..475c1ea 100755
--- a/scripts/prepare_email.py
+++ b/scripts/prepare_email.py
@@ -9,14 +9,15 @@ import argparse
import re
from pathlib import Path
+from util import get_latest_newsletter_number
+
MAIL_GENERATOR = "/opt/homebrew/bin/mdtosendy"
-parser = argparse.ArgumentParser()
-parser.add_argument("-n", "--number", required=True, type=int, help="Newsletter number")
-args = parser.parse_args()
+# Find the number of the letter to create
+letter_number = get_latest_newsletter_number()
-source = Path(f"./content/newsletter/craft-letter-{args.number}.md")
+source = Path(f"./content/newsletter/craft-letter-{letter_number}.md")
if not source.is_file():
print(f"ERROR: file not found {source}")
@@ -35,7 +36,7 @@ destination.write_text(output)
subprocess.run([MAIL_GENERATOR, str(destination)])
-generated_mail = Path("mail") / f"craft-letter-{args.number}.html"
+generated_mail = Path("mail") / f"craft-letter-{letter_number}.html"
today = date.today()
mail_content = generated_mail.read_text()
mail_content = mail_content.replace("{{YEAR}}", str(today.year))
diff --git a/scripts/util.py b/scripts/util.py
new file mode 100644
index 0000000..13a8c09
--- /dev/null
+++ b/scripts/util.py
@@ -0,0 +1,10 @@
+from pathlib import Path
+
+
+def get_latest_newsletter_number():
+ # Find the number of the latest newsletter
+ destination_path = Path(f"./content/newsletter")
+ p = destination_path.glob("craft-letter-*.md")
+ files = [x for x in p if x.is_file()]
+ letter_number = len(files)
+ return letter_number