Add Makefile, package.json, and initial configuration for VitePress and Slidev integration

- Introduced a Makefile for streamlined commands related to building, running, and managing presentations.
- Created package.json to define project metadata, dependencies, and scripts for development and testing.
- Added .gitignore entries to exclude build artifacts and temporary files from version control.
- Included playwright.config.mjs for end-to-end testing setup with Playwright.
- Added README.md for project documentation, outlining setup instructions and development guidelines.
This commit is contained in:
Le Prévost-Corvellec Arnault
2026-04-11 15:59:20 +02:00
parent 10f7798afb
commit 354c8cd9ef
70 changed files with 16388 additions and 2469 deletions
+90
View File
@@ -0,0 +1,90 @@
/**
* Invariant du dépôt : chaque slides/<slug>/meta.json a une entrée et des fichiers générés.
* Exécuter après `npm run sync` (la CI lance sync avant les tests).
*/
import { test, describe, before } from 'node:test'
import assert from 'node:assert'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { spawnSync } from 'node:child_process'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const repoRoot = path.resolve(__dirname, '..')
before(() => {
const r = spawnSync(process.execPath, [path.join(repoRoot, 'tools', 'sync-portal.mjs')], {
cwd: repoRoot,
encoding: 'utf8',
})
assert.strictEqual(r.status, 0, r.stderr || r.stdout)
})
describe('Cohérence slides ↔ portail (après sync)', () => {
test('presentations.json existe et liste les dossiers slides', () => {
const jsonPath = path.join(repoRoot, 'portal', '.vitepress', 'data', 'presentations.json')
assert.ok(fs.existsSync(jsonPath), 'presentations.json manquant')
const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
assert.ok(Array.isArray(data))
assert.ok(data.length >= 1, 'au moins une présentation attendue dans ce dépôt')
const slideDirs = fs
.readdirSync(path.join(repoRoot, 'slides'), { withFileTypes: true })
.filter((d) => d.isDirectory() && !d.name.startsWith('_'))
.map((d) => d.name)
.filter((name) => fs.existsSync(path.join(repoRoot, 'slides', name, 'meta.json')))
assert.strictEqual(
data.length,
slideDirs.length,
'nombre d’entrées JSON = nombre de dossiers slides avec meta.json',
)
const slugs = new Set(data.map((p) => p.slug))
for (const dir of slideDirs) {
assert.ok(slugs.has(dir), `slug manquant pour le dossier slides/${dir}`)
}
})
test('chaque entrée a title, description, slug', () => {
const jsonPath = path.join(repoRoot, 'portal', '.vitepress', 'data', 'presentations.json')
const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
for (const p of data) {
assert.ok(typeof p.title === 'string' && p.title.length > 0, `title requis: ${p.slug}`)
assert.ok(typeof p.description === 'string', `description requise: ${p.slug}`)
assert.ok(typeof p.slug === 'string' && p.slug.length > 0)
}
})
test('chaque deck a un global-bottom.vue (pied de page portail)', () => {
const data = JSON.parse(
fs.readFileSync(path.join(repoRoot, 'portal', '.vitepress', 'data', 'presentations.json'), 'utf8'),
)
for (const p of data) {
const gb = path.join(repoRoot, 'slides', p.slug, 'global-bottom.vue')
assert.ok(fs.existsSync(gb), `slides/${p.slug}/global-bottom.vue manquant (pied de page)`)
const raw = fs.readFileSync(gb, 'utf8')
assert.ok(
raw.includes('_shared/DeckFooter'),
`global-bottom doit importer le pied de page partagé: ${p.slug}`,
)
}
})
test('fiche et liste générées pour chaque slug', () => {
const data = JSON.parse(
fs.readFileSync(path.join(repoRoot, 'portal', '.vitepress', 'data', 'presentations.json'), 'utf8'),
)
const listMd = path.join(repoRoot, 'portal', 'presentations', 'index.md')
const listContent = fs.readFileSync(listMd, 'utf8')
for (const p of data) {
const detail = path.join(repoRoot, 'portal', 'presentations', p.slug, 'index.md')
assert.ok(fs.existsSync(detail), `fiche manquante: presentations/${p.slug}/index.md`)
const body = fs.readFileSync(detail, 'utf8')
assert.match(body, new RegExp(`slides/${p.slug}/`, ''), 'lien deck dans la fiche')
assert.match(body, /<QrEncode path="/, 'composant QR présent')
assert.ok(listContent.includes(p.title), 'titre présent dans la liste')
}
})
})