diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..abf1fbe
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @Coding-Cuddles/slides-maintainers
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..8e8d3e4
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,22 @@
+version: 2
+updates:
+ - package-ecosystem: "npm"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ time: "09:00"
+ timezone: "America/Los_Angeles"
+ versioning-strategy: "increase"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ time: "09:00"
+ timezone: "America/Los_Angeles"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 863ff2e..0111e34 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -6,19 +6,34 @@ on:
- main
pull_request:
-permissions:
- contents: read
- pages: write
- id-token: write
+permissions: {}
jobs:
- build:
+ lint:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
+
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 24
+ cache: npm
+
+ - run: npm ci --ignore-scripts
- - uses: actions/configure-pages@v5
+ - run: make check
+
+ build:
+ needs: lint
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
+ steps:
+ - uses: actions/checkout@v7
- name: Install Pandoc and LaTeX
run: |
@@ -33,12 +48,21 @@ jobs:
run: make -j
- name: Upload artifacts
- uses: actions/upload-pages-artifact@v3
+ uses: actions/upload-pages-artifact@v5
deploy:
needs: build
runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/main' }}
+ permissions:
+ pages: write
+ id-token: write
+
+ # Job-level rather than workflow-level: only the deployment needs
+ # serializing, and a shared group would queue unrelated pull request runs.
+ concurrency:
+ group: pages
+ cancel-in-progress: false
environment:
name: github-pages
@@ -47,4 +71,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v4
+ uses: actions/deploy-pages@v5
diff --git a/.gitignore b/.gitignore
index e983420..ebc4505 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
*.pdf
.DS_Store
+node_modules/
+_site/
diff --git a/.markdownlint.json b/.markdownlint.json
index b5698ae..0709d1e 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -2,6 +2,7 @@
"default": true,
"MD013": { "line_length": 150 },
"MD024": false,
+ "MD025": false,
"MD033": false,
"MD041": false
}
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..57510a2
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1 @@
+_site/
diff --git a/Makefile b/Makefile
index 8486d92..34ca764 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,32 @@
OUTDIR ?= _site
-MARKDOWNS := $(wildcard **/*.md)
+
+# Decks live in per-curriculum subdirectories; -mindepth 2 keeps README.md out.
+MARKDOWNS := $(shell find . -mindepth 2 -name '*.md' \
+ -not -path './node_modules/*' -not -path './$(OUTDIR)/*' | sed 's|^\./||' | sort)
PDFS := $(patsubst %.md,$(OUTDIR)/%.pdf,$(MARKDOWNS))
MDFLAGS := -f markdown -t beamer -s -H include.tex -V aspectratio:169 -V urlcolor:red
+HTMLFLAGS := -f markdown -t html5 -s --template index.template.html --lua-filter index.lua
+
+.PHONY: all check clean format format-check lint
+
+all: $(PDFS) $(OUTDIR)/index.html
+
+check: format-check lint
-all: $(PDFS)
+format:
+ npm run format
+
+format-check:
+ npm run format-check
+
+lint:
+ npm run lint
$(OUTDIR)/%.pdf: %.md
mkdir -p $(dir $@) && pandoc $(MDFLAGS) --output $@ $^
-.PHONY: clean
+$(OUTDIR)/index.html: README.md index.template.html index.lua
+ mkdir -p $(dir $@) && pandoc $(HTMLFLAGS) --output $@ $<
+
clean:
- rm -f $(PDFS)
+ rm -f $(PDFS) $(OUTDIR)/index.html
diff --git a/README.md b/README.md
index 65834f4..fcd6b63 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,22 @@
## Overview
-Here you can find slides for Clean Code conversations or classes.
+This repository contains slide decks for Clean Code conversations and classes.
+
+## Development
+
+Install Node.js 24 or later, Pandoc, and LaTeX. Then install the development dependencies and run
+the validation and rendering steps:
+
+```shell
+npm ci --ignore-scripts
+make check
+make -j
+```
+
+Everything under `_site` is generated. Each deck renders to a PDF, and the published index page is
+built from the Curriculum section below via `index.template.html` and `index.lua`, so the tables
+here are the only place the curriculum is edited.
## Curriculum
@@ -38,19 +53,19 @@ Here you can find slides for Clean Code conversations or classes.
### SOLID (Orange Belt)
-| # | Session Type | Name |
-| --: | ------------ | -------------------------------------------------------------------------------------------- |
-| 1 | Discussion | [Foundations of the SOLID principles](solid/01-foundations.md#warmup) |
-| 2 | Discussion | [The Single Responsibility Principle](solid/02-srp.md#warmup) |
-| 3 | Coding Dojo | Gilded Rose Refactoring Kata ([Python][gilded-rose-python], [C++][gilded-rose-cpp]) |
-| 4 | Discussion | The Dependency Inversion Principle |
-| 5 | Coding Dojo | Smart Home Refactoring Kata ([Python][smart-home-python], [C++][smart-home-cpp]) |
-| 6 | Discussion | The Interface Segregation Principle |
-| 7 | Coding Dojo | Media Player Refactoring Kata ([Python][media-player-python], [C++][media-player-cpp]) |
-| 8 | Discussion | The Open-Closed Principle |
-| 9 | Coding Dojo | Expense Report Refactoring Kata ([Python][expense-report-python], [C++][expense-report-cpp]) |
-| 10 | Discussion | The Liskov Substitution Principle |
-| 11 | Coding Dojo | Game of Life Refactoring Kata ([Python][game-of-life-python], [C++][game-of-life-cpp]) |
+| # | Session Type | Name |
+| --: | ------------ | ---------------------------------------------------------------------------------------------------------------------- |
+| 1 | Discussion | [Foundations of the SOLID principles](solid/01-foundations.md#warmup) ([worksheet](solid/01-foundations-worksheet.md)) |
+| 2 | Discussion | [The Single Responsibility Principle](solid/02-srp.md#warmup) |
+| 3 | Coding Dojo | Gilded Rose Refactoring Kata ([Python][gilded-rose-python], [C++][gilded-rose-cpp]) |
+| 4 | Discussion | The Dependency Inversion Principle |
+| 5 | Coding Dojo | Smart Home Refactoring Kata ([Python][smart-home-python], [C++][smart-home-cpp]) |
+| 6 | Discussion | The Interface Segregation Principle |
+| 7 | Coding Dojo | Media Player Refactoring Kata ([Python][media-player-python], [C++][media-player-cpp]) |
+| 8 | Discussion | The Open-Closed Principle |
+| 9 | Coding Dojo | Expense Report Refactoring Kata ([Python][expense-report-python], [C++][expense-report-cpp]) |
+| 10 | Discussion | The Liskov Substitution Principle |
+| 11 | Coding Dojo | Game of Life Refactoring Kata ([Python][game-of-life-python], [C++][game-of-life-cpp]) |
[gilded-rose-python]: https://github.com/Coding-Cuddles/gilded-rose-refactoring-python-kata
[gilded-rose-cpp]: https://github.com/Coding-Cuddles/gilded-rose-refactoring-cpp-kata
@@ -70,7 +85,7 @@ Here you can find slides for Clean Code conversations or classes.
| 1 | Discussion | [Advanced TDD](advanced-tdd/01-advanced-tdd.md#warmup) |
| 2 | Coding Dojo | Roman Numerals Kata ([Python][roman-numerals-python], [C++][roman-numerals-cpp]) |
| 3 | Discussion | [Clean Tests](advanced-tdd/03-clean-tests.md#warmup) |
-| 4 | Coding Dojo | Mars Rover Kata ([Python][mars-rover-python], [C++][mars-rover-python]) |
+| 4 | Coding Dojo | Mars Rover Kata ([Python][mars-rover-python], [C++][mars-rover-cpp]) |
| 5 | Coding Mob | Mars Rover Kata ([Python][mars-rover-python], [C++][mars-rover-cpp]) |
| 6 | Discussion | Test Design / Test Process |
| 7 | Coding Dojo | Hyper-optimized Telemetry Kata ([Python][hyper-optimized-telemetry-python], [C++][hyper-optimized-telemetry-cpp]) |
@@ -94,15 +109,15 @@ Here you can find slides for Clean Code conversations or classes.
### Legacy Code (Blue Belt)
-| # | Session Type | Name |
-| --: | ------------ | ------------------------------------------------------------------------------------------------------ |
-| 1 | Discussion | [Understanding Legacy Code](legacy-code/01-understanding-legacy.md) |
-| 2 | Kata | [Identifying Legacy Code Kata](legacy-code/02-kata-identifying.md) |
-| 3 | Discussion | [Building a Safety Net](legacy-code/03-safety-net.md) |
-| 4 | Kata | [Characterization Testing Kata](legacy-code/04-kata-characterization-testing.md) |
-| 5 | Discussion | [Safe, Non-Invasive Changes](legacy-code/05-safe-changes.md) |
-| 6 | Kata | [Sprouting & Wrapping Kata](legacy-code/06-kata-sprouting-wrapping.md) |
-| 7 | Discussion | [Core Dependency Breaking Techniques](legacy-code/07-dependency-breaking.md) |
-| 8 | Kata | [Dependency Breaking Kata](legacy-code/08-kata-dependency-breaking.md) |
-| 9 | Discussion | [Advanced Topics & Large-Scale Strategy](legacy-code/09-advanced-topics.md) |
-| 10 | Kata | [Large-Scale Refactoring Kata](legacy-code/10-kata-large-scale-refactoring.md) |
+| # | Session Type | Name |
+| --: | ------------ | -------------------------------------------------------------------------------- |
+| 1 | Discussion | [Understanding Legacy Code](legacy-code/01-understanding-legacy.md) |
+| 2 | Kata | [Identifying Legacy Code Kata](legacy-code/02-kata-identifying.md) |
+| 3 | Discussion | [Building a Safety Net](legacy-code/03-safety-net.md) |
+| 4 | Kata | [Characterization Testing Kata](legacy-code/04-kata-characterization-testing.md) |
+| 5 | Discussion | [Safe, Non-Invasive Changes](legacy-code/05-safe-changes.md) |
+| 6 | Kata | [Sprouting & Wrapping Kata](legacy-code/06-kata-sprouting-wrapping.md) |
+| 7 | Discussion | [Core Dependency Breaking Techniques](legacy-code/07-dependency-breaking.md) |
+| 8 | Kata | [Dependency Breaking Kata](legacy-code/08-kata-dependency-breaking.md) |
+| 9 | Discussion | [Advanced Topics & Large-Scale Strategy](legacy-code/09-advanced-topics.md) |
+| 10 | Kata | [Large-Scale Refactoring Kata](legacy-code/10-kata-large-scale-refactoring.md) |
diff --git a/_site/index.html b/_site/index.html
deleted file mode 100644
index 9c3f589..0000000
--- a/_site/index.html
+++ /dev/null
@@ -1,355 +0,0 @@
-
-
-
-
-
-
-
- Clean Code Slides
-
-
-
-
-
-
-
-
Curriculum
-
Fundamentals (Yellow Belt)
-
-
-
-
- #
- Session Type
- Name
-
-
-
-
- 1
- Discussion
- Introduction
-
-
- 2
- Discussion
- Names++
-
-
- 3
- Coding Dojo
-
- "Change" Refactoring Kata (Python , C++ )
-
-
-
- 4
- Discussion
- Functions
-
-
- 5
- Coding Dojo
-
- FizzBuzz Kata (Python ,
- C++ )
-
-
-
- 6
- Discussion
-
- Function Structure
-
-
-
- 7
- Coding Dojo
-
- Tennis Kata (Python ,
- C++ )
-
-
-
- 8
- Discussion
- Form
-
-
- 9
- Coding Dojo
-
- Bowling Kata (Python ,
- C++ )
-
-
-
- 10
- Discussion
- TDD
-
-
- 11
- Coding Dojo
- Repeat after Uncle Bob
-
-
- 12
- Discussion
-
- "Polly want a message"
-
-
-
- 13
- Coding Dojo
- Participant choice
-
-
-
-
-
SOLID (Orange Belt)
-
-
-
-
- #
- Session Type
- Name
-
-
-
-
- 1
- Discussion
-
- Foundations of the SOLID principles
-
-
-
- 2
- Discussion
-
- The Single Responsibility Principle
-
-
-
- 3
- Coding Dojo
-
- Gilded Rose Refactoring Kata (Python , C++ )
-
-
-
- 4
- Discussion
- The Dependency Inversion Principle
-
-
- 5
- Coding Dojo
-
- Smart Home Refactoring Kata (Python , C++ )
-
-
-
- 6
- Discussion
- The Interface Segregation Principle
-
-
- 7
- Coding Dojo
-
- Media Player Refactoring Kata (Python , C++ )
-
-
-
- 8
- Discussion
- The Open-Closed Principle
-
-
- 9
- Coding Dojo
-
- Expense Report Refactoring Kata (Python , C++ )
-
-
-
- 10
- Discussion
- The Liskov Substitution Principle
-
-
- 11
- Coding Dojo
-
- Game of Life Refactoring Kata (Python , C++ )
-
-
-
-
-
-
Advanced TDD (Green Belt)
-
-
-
-
- #
- Session Type
- Name
-
-
-
-
- 1
- Discussion
- Advanced TDD
-
-
- 2
- Coding Dojo
-
- Roman Numerals Kata (Python ,
- C++ )
-
-
-
- 3
- Discussion
- Clean Tests
-
-
- 4
- Coding Dojo
-
- Mars Rover Kata (Python ,
- C++ )
-
-
-
- 5
- Coding Mob
-
- Mars Rover Kata (Python ,
- C++ )
-
-
-
- 6
- Discussion
- Test Design / Test Process
-
-
- 7
- Coding Dojo
-
- Hyper-optimized Telemetry Kata (Python , C++ )
-
-
-
- 8
- Discussion
- Student Code Demo
-
-
- 9
- Discussion
- Mocking
-
-
- 10
- Coding Dojo
-
- Tire Pressure Monitoring Kata (Python , C++ )
-
-
-
- 11
- Discussion
- Transformation Priority Premise
-
-
- 12
- Coding Dojo
-
- "99 Bottles of Beer" Kata (Python , C++ )
-
-
-
- 13
- Discussion
- Student Code Demo 2
-
-
-
-
-
-
-
diff --git a/index.lua b/index.lua
new file mode 100644
index 0000000..61919d8
--- /dev/null
+++ b/index.lua
@@ -0,0 +1,48 @@
+-- Renders the index page from README.md: keeps the Curriculum section, points
+-- deck links at the rendered PDFs, and restores the Bootstrap table classes.
+
+function Link(link)
+ if link.target:match("^%a[%w+.-]*:") then
+ return link
+ end
+
+ local path = link.target:match("^([^#]+)")
+ if path and path:match("%.md$") then
+ link.target = path:gsub("%.md$", ".pdf")
+ end
+
+ return link
+end
+
+function Table(tbl)
+ tbl.attr.classes = { "table", "table-striped" }
+
+ -- Pipe tables carry column widths derived from the source layout, which
+ -- Bootstrap should decide instead.
+ for i in ipairs(tbl.colspecs) do
+ tbl.colspecs[i] = { pandoc.AlignDefault, nil }
+ end
+
+ return tbl
+end
+
+function Pandoc(doc)
+ local blocks = {}
+ local keeping = false
+
+ for _, block in ipairs(doc.blocks) do
+ if block.t == "Header" and block.level <= 2 then
+ keeping = pandoc.utils.stringify(block.content) == "Curriculum"
+ end
+
+ if keeping then
+ table.insert(blocks, block)
+ end
+ end
+
+ if #blocks == 0 then
+ error("README.md has no Curriculum section")
+ end
+
+ return pandoc.Pandoc(blocks, doc.meta)
+end
diff --git a/index.template.html b/index.template.html
new file mode 100644
index 0000000..2d1be2d
--- /dev/null
+++ b/index.template.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+ Clean Code Slides
+
+
+
+
+
+
+
+ $body$
+
+
+
+
diff --git a/legacy-code/01-understanding-legacy.md b/legacy-code/01-understanding-legacy.md
index f2daa47..68fc94e 100644
--- a/legacy-code/01-understanding-legacy.md
+++ b/legacy-code/01-understanding-legacy.md
@@ -36,19 +36,19 @@
class DataProcessor {
Database db;
Logger logger;
-
+
public:
void process(const std::string& data) {
db.connect("hardcoded:connection:string");
logger.log("Processing started");
-
+
// Complex business logic intertwined with
// database calls and logging
if (data.length() > 0) {
db.insert(data);
logger.log("Data inserted");
}
-
+
db.disconnect();
}
};
@@ -65,12 +65,12 @@ class ReportGenerator:
database="reports_db",
user="admin"
)
-
+
# Business logic mixed with data access
cursor = conn.cursor()
cursor.execute("SELECT * FROM sales")
results = cursor.fetchall()
-
+
# Direct file system access
with open('report.pdf', 'wb') as f:
pdf = PDF()
@@ -97,6 +97,7 @@ class ReportGenerator:
# Next Week
Prepare for hands-on kata:
+
- Identifying legacy code characteristics
- Initial assessment techniques
- Setting up a testing strategy
diff --git a/legacy-code/02-kata-identifying.md b/legacy-code/02-kata-identifying.md
index 7685e22..77a79e8 100644
--- a/legacy-code/02-kata-identifying.md
+++ b/legacy-code/02-kata-identifying.md
@@ -20,7 +20,7 @@ private:
static OrderProcessor* instance;
Database* db;
EmailService* emailService;
-
+
OrderProcessor() {
db = new Database("production_db");
emailService = new EmailService();
@@ -28,7 +28,7 @@ private:
public:
static OrderProcessor* getInstance() {
- if (instance == null) {
+ if (instance == nullptr) {
instance = new OrderProcessor();
}
return instance;
@@ -38,16 +38,16 @@ public:
if (!db->isConnected()) {
db->connect();
}
-
+
bool success = false;
if (order.getTotal() > 0) {
success = db->executeQuery(
- "INSERT INTO orders VALUES (" +
- order.getId() + "," +
+ "INSERT INTO orders VALUES (" +
+ order.getId() + "," +
order.getCustomerId() + "," +
order.getTotal() + ")"
);
-
+
if (success) {
emailService->sendEmail(
order.getCustomerEmail(),
@@ -68,21 +68,21 @@ class ReportGenerator:
def __init__(self):
self.db = Database()
self.template_engine = TemplateEngine()
-
+
def generate_monthly_report(self, month, year):
# Connect to database
self.db.connect()
-
+
# Get data
sales_data = self.db.execute_query(
f"SELECT * FROM sales WHERE MONTH(date) = {month} "
f"AND YEAR(date) = {year}"
)
-
+
# Process data
total = sum(sale['amount'] for sale in sales_data)
avg = total / len(sales_data) if sales_data else 0
-
+
# Generate report
report = self.template_engine.load_template('monthly_report')
report.set_data({
@@ -92,11 +92,11 @@ class ReportGenerator:
'total': total,
'average': avg
})
-
+
# Save to file system
filename = f"report_{month}_{year}.pdf"
report.save_as_pdf(filename)
-
+
# Send email
email = EmailService()
email.send(
@@ -146,6 +146,7 @@ class ReportGenerator:
# Follow-up
Prepare for next week's session on:
+
- Building a safety net
- Writing characterization tests
- Understanding test coverage strategies
diff --git a/legacy-code/03-safety-net.md b/legacy-code/03-safety-net.md
index a9eb977..7cbf486 100644
--- a/legacy-code/03-safety-net.md
+++ b/legacy-code/03-safety-net.md
@@ -11,11 +11,13 @@
# Understanding Characterization Tests
## What are they?
+
- Tests that describe current behavior
- Document "what is" rather than "what should be"
- First line of defense when working with legacy code
## Why do we need them?
+
- Capture existing behavior
- Provide safety for refactoring
- Document system behavior
@@ -60,12 +62,12 @@ TEST_CASE("InvoiceCalculator preserves existing behavior") {
{11, 100.0}, // Bulk discount case
{1, 1001.0} // Large order case
};
-
+
// Capture current behavior
double result = calc.calculateTotal(items);
-
+
// Document the exact current behavior
- REQUIRE(result == Approx(1850.95));
+ REQUIRE(result == Approx(2132.10));
// Note: This might not be correct behavior,
// but it's what the system currently does
}
@@ -74,6 +76,9 @@ TEST_CASE("InvoiceCalculator preserves existing behavior") {
## Python Example: Characterization Testing Technique
```python
+import pytest
+
+
# Original Legacy Code
class TaxCalculator:
def calculate_tax(self, income, state):
@@ -89,33 +94,27 @@ class TaxCalculator:
base_rate += 0.04
else:
base_rate = 0.01
-
+
return income * base_rate
# Characterization Tests
def test_capture_current_tax_behavior():
calc = TaxCalculator()
-
- # Test cases to capture current behavior
+
+ # Exact outputs captured from the current implementation
test_cases = [
- (30000, "NY"),
- (60000, "NY"),
- (120000, "NY"),
- (50000, "CA"),
- (80000, "CA"),
- (45000, "TX")
+ (30000, "NY", 1200),
+ (60000, "NY", 3600),
+ (120000, "NY", 10800),
+ (50000, "CA", 1500),
+ (80000, "CA", 5600),
+ (45000, "TX", 450),
]
-
- # Store current behavior
- results = {
- case: calc.calculate_tax(*case)
- for case in test_cases
- }
-
+
# Verify behavior remains unchanged
- for case in test_cases:
- assert calc.calculate_tax(*case) == results[case], \
- f"Behavior changed for {case}"
+ for income, state, expected in test_cases:
+ result = calc.calculate_tax(income, state)
+ assert result == pytest.approx(expected), f"Behavior changed for {(income, state)}"
```
# Key Techniques
@@ -147,6 +146,7 @@ def test_capture_current_tax_behavior():
# Next Week
Prepare for hands-on kata:
+
- Writing effective characterization tests
- Creating seams in legacy code
- Organizing test suites effectively
diff --git a/legacy-code/04-kata-characterization-testing.md b/legacy-code/04-kata-characterization-testing.md
index 534cf68..f366871 100644
--- a/legacy-code/04-kata-characterization-testing.md
+++ b/legacy-code/04-kata-characterization-testing.md
@@ -77,6 +77,7 @@ class ShippingCostCalculator:
# Follow-up
Prepare for next week's session on:
+
- Safe, non-invasive changes
- Sprouting and wrapping techniques
- Planning incremental improvements
diff --git a/legacy-code/05-safe-changes.md b/legacy-code/05-safe-changes.md
index a0fa8d8..f3bfefa 100644
--- a/legacy-code/05-safe-changes.md
+++ b/legacy-code/05-safe-changes.md
@@ -5,7 +5,7 @@
# Session Timetable
| Activity | Time |
-|-----------------------|--------|
+| --------------------- | ------ |
| Greetings, Warmup | 5 min |
| Sprout Method & Class | 15 min |
| Exercise 1 | 15 min |
@@ -97,7 +97,8 @@ class EVLogger:
# Exercise 1
-- When refactoring legacy code, how do you decide whether to extract a Sprout Method versus creating a Sprout Class? What are the trade-offs in terms of testability, cohesion, and future maintenance?
+- When refactoring legacy code, how do you decide whether to use a Sprout Method or a Sprout Class?
+- What are the trade-offs in testability, cohesion, and future maintenance?
- Time limit: 15 minutes
# Wrap Method
@@ -146,10 +147,13 @@ class Engine:
### Improved Code Example
```python
-class LoggingEngine(Engine):
+class LoggingEngine:
+ def __init__(self, engine):
+ self._engine = engine
+
def calculate_torque(self, rpm, throttle):
print(f"[LOG] Calculating torque: rpm={rpm}, throttle={throttle}")
- torque = super().calculate_torque(rpm, throttle)
+ torque = self._engine.calculate_torque(rpm, throttle)
print(f"[LOG] Torque result: {torque}")
return torque
```
@@ -157,13 +161,14 @@ class LoggingEngine(Engine):
# Usage
```python
-engine = LoggingEngine()
+engine = LoggingEngine(Engine())
engine.calculate_torque(3000, 70)
```
# Exercise 2
-- How do you recognize when legacy behavior should remain within the current class versus when it’s time to sprout a new class for better separation of concerns?
+- How do you decide whether legacy behavior should remain in its current class or move into a Sprout Class?
+- What separation-of-concerns signals inform that choice?
- Time limit: 15 minutes
# Decorators and the Wrap Method
@@ -171,7 +176,7 @@ engine.calculate_torque(3000, 70)
- Decorators are a special form of wrapping that allows for dynamic extension.
- In the context of the Wrap Method, decorators provide an elegant solution for layering additional behavior.
-### Python Decorator Pattern Example
+## Python Decorator Pattern Example
```python
class Vehicle:
@@ -198,19 +203,19 @@ car.drive()
# Comparison & Benefits
-| Technique | Location of Change | Scope | Purpose | Risk | Code Impact |
-|---------------|------------|-----------------|-------------------------------|------|---------------------------|
-| Sprout Method | Same class | One method | Isolate new logic | Low | Add method, call it |
-| Sprout Class | New class | Functionality | Extract cohesive behavior | Low | New class, inject it |
-| Wrap Method | Same class | One method | Insert logic around method| Low | Rename + wrap method |
-| Wrap Class | Subclass | Multiple methods| Modify/extend behavior | Med | New subclass created |
+| Technique | Location of Change | Scope | Purpose | Risk | Code Impact |
+| ------------- | ----------------------- | ---------------- | ------------------------------------ | ---- | ------------------------- |
+| Sprout Method | Same class | One method | Isolate new logic | Low | Add method, call it |
+| Sprout Class | New class | Functionality | Extract cohesive behavior | Low | New class, inject it |
+| Wrap Method | Same class | One method | Insert logic around method | Low | Rename + wrap method |
+| Wrap Class | Wrapper | Multiple methods | Add behavior around legacy class | Med | New wrapper created |
# Summary
- **Sprout** = add new code that old code calls
- **Wrap** = write code that calls into the old code
-- Use *Method* for simple logic, *Class* for complex or stateful logic
-- These give you *safe entry points* into legacy code
+- Use _Method_ for simple logic, _Class_ for complex or stateful logic
+- These give you _safe entry points_ into legacy code
# Final Thought
diff --git a/legacy-code/06-kata-sprouting-wrapping.md b/legacy-code/06-kata-sprouting-wrapping.md
index c9d8cde..a11381e 100644
--- a/legacy-code/06-kata-sprouting-wrapping.md
+++ b/legacy-code/06-kata-sprouting-wrapping.md
@@ -36,15 +36,15 @@ class UserManager:
# Tasks
1. **Sprouting (20 min)**
- - Add a new authentication method that uses a user database or config file
- - Do not modify the original method
+ - Add new authentication logic in a separate method that uses a user database or config file
+ - Make only the minimal change to the original method needed to call the new method
2. **Wrapping (20 min)**
- Create a wrapper that logs authentication attempts
- Ensure the wrapper can be tested independently
3. **Testing (30 min)**
- - Write tests for both the new and legacy authentication methods
+ - Write tests for both the new method and the original authentication entry point
- Validate that wrapping does not change legacy behavior
4. **Review (20 min)**
@@ -67,6 +67,7 @@ class UserManager:
# Follow-up
Prepare for next week's session on:
+
- Breaking dependencies
- Using adapters and seams
- Refactoring for flexibility
diff --git a/legacy-code/07-dependency-breaking.md b/legacy-code/07-dependency-breaking.md
index 68d4a04..0094f43 100644
--- a/legacy-code/07-dependency-breaking.md
+++ b/legacy-code/07-dependency-breaking.md
@@ -11,10 +11,12 @@
# Dependency Breaking Techniques
## Adapters
+
- Introduce new interfaces to decouple code
- Allow substitution of dependencies for testing
## Seams
+
- Places where you can alter behavior without editing code
- Types: Preprocessor, Link, Object, and Parameter seams
@@ -108,6 +110,7 @@ class PaymentProcessor:
# Next Week
Prepare for hands-on kata:
+
- Implementing adapters and seams
- Refactoring for testability
- Managing risk during dependency breaking
diff --git a/legacy-code/08-kata-dependency-breaking.md b/legacy-code/08-kata-dependency-breaking.md
index 7245c54..e4c8990 100644
--- a/legacy-code/08-kata-dependency-breaking.md
+++ b/legacy-code/08-kata-dependency-breaking.md
@@ -73,6 +73,7 @@ class EmailSender:
# Follow-up
Prepare for next week's session on:
+
- Large-scale refactoring
- Managing technical debt
- Strategic improvement planning
diff --git a/legacy-code/09-advanced-topics.md b/legacy-code/09-advanced-topics.md
index e4387a3..5000aeb 100644
--- a/legacy-code/09-advanced-topics.md
+++ b/legacy-code/09-advanced-topics.md
@@ -11,11 +11,13 @@
# Large-Scale Refactoring
## Approaches
+
- Incremental vs. Big Bang
- Strangler Fig Pattern
- Parallel Change (Expand and Contract)
## Managing Technical Debt
+
- Identifying and prioritizing debt
- Communicating debt to stakeholders
- Balancing business needs and code quality
@@ -100,6 +102,7 @@ def calculate_billing(invoice, use_new=False):
# Next Week
Prepare for hands-on kata:
+
- Applying large-scale refactoring
- Managing risk and business continuity
- Measuring success
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..9870ffc
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1385 @@
+{
+ "name": "coding-cuddles-slides",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "coding-cuddles-slides",
+ "devDependencies": {
+ "markdownlint-cli2": "0.23.2",
+ "prettier": "3.9.6"
+ },
+ "engines": {
+ "node": ">=24"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/katex": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz",
+ "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz",
+ "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globby": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.2.tgz",
+ "integrity": "sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^4.0.0",
+ "fast-glob": "^3.3.3",
+ "ignore": "^7.0.5",
+ "is-path-inside": "^4.0.0",
+ "slash": "^5.1.0",
+ "unicorn-magic": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz",
+ "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz",
+ "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.2.tgz",
+ "integrity": "sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.mjs"
+ }
+ },
+ "node_modules/jsonc-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
+ "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsonpointer": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
+ "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/katex": {
+ "version": "0.16.47",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz",
+ "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==",
+ "dev": true,
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/linkify-it": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz",
+ "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/markdown-it"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "uc.micro": "^2.0.0"
+ }
+ },
+ "node_modules/markdown-it": {
+ "version": "14.3.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz",
+ "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/markdown-it"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.5.0",
+ "linkify-it": "^5.0.2",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.mjs"
+ }
+ },
+ "node_modules/markdownlint": {
+ "version": "0.41.1",
+ "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.41.1.tgz",
+ "integrity": "sha512-qHKeU2E1bdyNAT077go2FVTNXvYcktN5IHtF6XyeD1l0PClxzSp2tUApAV14ORI8DGX4H9bNKZEzelZp4qn8IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "micromark": "4.0.2",
+ "micromark-core-commonmark": "2.0.3",
+ "micromark-extension-directive": "4.0.0",
+ "micromark-extension-gfm-autolink-literal": "2.1.0",
+ "micromark-extension-gfm-footnote": "2.1.0",
+ "micromark-extension-gfm-table": "2.1.1",
+ "micromark-extension-math": "3.1.0",
+ "micromark-util-types": "2.0.2",
+ "string-width": "8.2.1"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/DavidAnson"
+ }
+ },
+ "node_modules/markdownlint-cli2": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.23.2.tgz",
+ "integrity": "sha512-eUhcnkSpzURo/o4htSqc7LPDszgOOTknhU4eY/sPHvMCLxnTCYscv1gw1/js/idmaZPisv9ECVEIORcllqjTUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "globby": "16.2.2",
+ "js-yaml": "5.2.2",
+ "jsonc-parser": "3.3.1",
+ "jsonpointer": "5.0.1",
+ "markdown-it": "14.3.0",
+ "markdownlint": "0.41.1",
+ "markdownlint-cli2-formatter-default": "0.0.6",
+ "micromatch": "4.0.8",
+ "smol-toml": "1.7.0"
+ },
+ "bin": {
+ "markdownlint-cli2": "markdownlint-cli2-bin.mjs"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/DavidAnson"
+ }
+ },
+ "node_modules/markdownlint-cli2-formatter-default": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.6.tgz",
+ "integrity": "sha512-VVDGKsq9sgzu378swJ0fcHfSicUnMxnL8gnLm/Q4J/xsNJ4e5bA6lvAz7PCzIl0/No0lHyaWdqVD2jotxOSFMQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/DavidAnson"
+ },
+ "peerDependencies": {
+ "markdownlint-cli2": ">=0.0.4"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz",
+ "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-directive": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz",
+ "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "parse-entities": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-math": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz",
+ "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/katex": "^0.16.0",
+ "devlop": "^1.0.0",
+ "katex": "^0.16.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/parse-entities": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/slash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/smol-toml": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz",
+ "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/cyyynthia"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
+ "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unicorn-magic": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz",
+ "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3a3634c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "coding-cuddles-slides",
+ "private": true,
+ "scripts": {
+ "check": "npm run format-check && npm run lint",
+ "format": "prettier --write .",
+ "format-check": "prettier --check .",
+ "lint": "markdownlint-cli2 \"**/*.md\" \"#node_modules\""
+ },
+ "devDependencies": {
+ "markdownlint-cli2": "0.23.2",
+ "prettier": "3.9.6"
+ },
+ "engines": {
+ "node": ">=24"
+ }
+}