@komekoo/astro-cms 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -2
- package/dist/ajv.d.ts +4 -0
- package/dist/ajv.js +23 -0
- package/dist/champs.d.ts +49 -0
- package/dist/champs.js +58 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +70 -0
- package/dist/collections.d.ts +38 -0
- package/dist/collections.js +52 -0
- package/dist/images.d.ts +8 -0
- package/dist/images.js +19 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -0
- package/dist/integration.d.ts +9 -0
- package/dist/integration.js +91 -0
- package/dist/motifs.d.ts +8 -0
- package/dist/motifs.js +9 -0
- package/dist/navigateur.d.ts +5 -0
- package/dist/navigateur.js +5 -0
- package/dist/noyau/charger.d.ts +4 -0
- package/dist/noyau/charger.js +48 -0
- package/dist/noyau/collecter.d.ts +26 -0
- package/dist/noyau/collecter.js +132 -0
- package/dist/noyau/contenus.d.ts +6 -0
- package/dist/noyau/contenus.js +218 -0
- package/dist/noyau/erreurs.d.ts +7 -0
- package/dist/noyau/erreurs.js +14 -0
- package/dist/noyau/frontmatter.d.ts +15 -0
- package/dist/noyau/frontmatter.js +70 -0
- package/dist/noyau/json-schema.d.ts +4 -0
- package/dist/noyau/json-schema.js +58 -0
- package/dist/noyau/manifest.d.ts +15 -0
- package/dist/noyau/manifest.js +136 -0
- package/dist/noyau/markdown.d.ts +11 -0
- package/dist/noyau/markdown.js +95 -0
- package/dist/noyau/operations.d.ts +12 -0
- package/dist/noyau/operations.js +54 -0
- package/dist/noyau/serialiser.d.ts +2 -0
- package/dist/noyau/serialiser.js +25 -0
- package/dist/noyau/version.d.ts +5 -0
- package/dist/noyau/version.js +25 -0
- package/dist/socle/hors-perimetre.d.ts +2 -0
- package/dist/socle/hors-perimetre.js +9 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +10 -0
- package/package.json +53 -3
- package/src/composants/CmsImage.astro +24 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { HORS_PERIMETRE_SOCLE } from '../socle/hors-perimetre.js';
|
|
2
|
+
import { lireMeta } from '../types.js';
|
|
3
|
+
import { deduireEditeur } from './collecter.js';
|
|
4
|
+
const interne = (s) => s._zod;
|
|
5
|
+
const ENVELOPPES = new Set(['optional', 'nullable', 'default', 'prefault', 'nonoptional', 'readonly']);
|
|
6
|
+
function deballer(schema) {
|
|
7
|
+
let courant = schema;
|
|
8
|
+
while (ENVELOPPES.has(interne(courant).def.type) && interne(courant).def.innerType) {
|
|
9
|
+
courant = interne(courant).def.innerType;
|
|
10
|
+
}
|
|
11
|
+
return courant;
|
|
12
|
+
}
|
|
13
|
+
const estFacultatif = (s) => interne(s).def.type === 'optional';
|
|
14
|
+
const TYPES = {
|
|
15
|
+
texte: 'texte court',
|
|
16
|
+
paragraphe: 'texte long',
|
|
17
|
+
richtext: 'texte mis en forme',
|
|
18
|
+
image: 'image',
|
|
19
|
+
date: 'date',
|
|
20
|
+
lien: 'lien',
|
|
21
|
+
nombre: 'nombre',
|
|
22
|
+
booleen: 'oui/non',
|
|
23
|
+
choix: 'choix',
|
|
24
|
+
liste: 'liste',
|
|
25
|
+
groupe: 'groupe',
|
|
26
|
+
};
|
|
27
|
+
const CORPS_MARKDOWN = 'Markdown : intertitres niveaux 2 et 3, gras, italique, liens, listes, citations, images, tableaux simples. Pas de HTML.';
|
|
28
|
+
/** Lit une borne dans les vérifications déclarées (`.min()`/`.max()`), le `bag` étant vide après `.meta()`. */
|
|
29
|
+
function borne(schema, cle) {
|
|
30
|
+
const verifs = interne(schema).def.checks ?? [];
|
|
31
|
+
const noms = cle === 'minimum' ? ['min_length', 'greater_than'] : ['max_length', 'less_than'];
|
|
32
|
+
for (const v of verifs) {
|
|
33
|
+
const d = v._zod.def;
|
|
34
|
+
if (!noms.includes(d.check))
|
|
35
|
+
continue;
|
|
36
|
+
const valeur = d.check === 'min_length' || d.check === 'max_length' ? d.minimum ?? d.maximum : d.value;
|
|
37
|
+
if (typeof valeur === 'number')
|
|
38
|
+
return valeur;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function contraintes(schema, editeur) {
|
|
43
|
+
const bag = deballer(schema);
|
|
44
|
+
const parties = [estFacultatif(schema) ? 'facultatif' : 'obligatoire'];
|
|
45
|
+
if (editeur === 'texte' || editeur === 'paragraphe' || editeur === 'richtext') {
|
|
46
|
+
const max = borne(bag, 'maximum');
|
|
47
|
+
if (max !== undefined)
|
|
48
|
+
parties.push(`${max} caractères max`);
|
|
49
|
+
}
|
|
50
|
+
else if (editeur === 'nombre') {
|
|
51
|
+
const min = borne(bag, 'minimum');
|
|
52
|
+
const max = borne(bag, 'maximum');
|
|
53
|
+
if (min !== undefined && max !== undefined)
|
|
54
|
+
parties.push(`entre ${min} et ${max}`);
|
|
55
|
+
else if (min !== undefined)
|
|
56
|
+
parties.push(`${min} minimum`);
|
|
57
|
+
else if (max !== undefined)
|
|
58
|
+
parties.push(`${max} maximum`);
|
|
59
|
+
const unite = lireMeta(schema)?.unite;
|
|
60
|
+
if (unite !== undefined)
|
|
61
|
+
parties.push(`unité : ${unite}`);
|
|
62
|
+
}
|
|
63
|
+
else if (editeur === 'liste') {
|
|
64
|
+
const min = borne(bag, 'minimum');
|
|
65
|
+
const max = borne(bag, 'maximum');
|
|
66
|
+
if (min !== undefined)
|
|
67
|
+
parties.push(`${min} éléments min`);
|
|
68
|
+
if (max !== undefined)
|
|
69
|
+
parties.push(`${max} éléments max`);
|
|
70
|
+
}
|
|
71
|
+
return parties.join(', ');
|
|
72
|
+
}
|
|
73
|
+
function typeLisible(schema, editeur) {
|
|
74
|
+
if (editeur === 'choix') {
|
|
75
|
+
const options = deballer(schema).options ?? [];
|
|
76
|
+
return `choix parmi : ${options.join(', ')}`;
|
|
77
|
+
}
|
|
78
|
+
return TYPES[editeur];
|
|
79
|
+
}
|
|
80
|
+
function aplatir(cle, schema, chemin, sortie) {
|
|
81
|
+
const editeur = deduireEditeur(schema);
|
|
82
|
+
const entree = {
|
|
83
|
+
chemin,
|
|
84
|
+
libelle: lireMeta(schema)?.libelle ?? cle,
|
|
85
|
+
type: typeLisible(schema, editeur),
|
|
86
|
+
};
|
|
87
|
+
const c = contraintes(schema, editeur);
|
|
88
|
+
if (c !== '')
|
|
89
|
+
entree.contraintes = c;
|
|
90
|
+
sortie.push(entree);
|
|
91
|
+
// L'image est un champ atomique : sa structure interne (src, alt…) n'est pas exposée.
|
|
92
|
+
if (editeur !== 'groupe' && editeur !== 'liste')
|
|
93
|
+
return;
|
|
94
|
+
const d = interne(deballer(schema)).def;
|
|
95
|
+
if (d.type === 'object' && d.shape) {
|
|
96
|
+
for (const [sousCle, sous] of Object.entries(d.shape))
|
|
97
|
+
aplatir(sousCle, sous, `${chemin}.${sousCle}`, sortie);
|
|
98
|
+
}
|
|
99
|
+
else if (d.type === 'array' && d.element) {
|
|
100
|
+
const de = interne(deballer(d.element)).def;
|
|
101
|
+
if (de.type === 'object' && de.shape) {
|
|
102
|
+
for (const [sousCle, sous] of Object.entries(de.shape)) {
|
|
103
|
+
aplatir(sousCle, sous, `${chemin}[].${sousCle}`, sortie);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function champs(schema, ignorer = []) {
|
|
109
|
+
const sortie = [];
|
|
110
|
+
for (const [cle, sous] of Object.entries(interne(schema).def.shape ?? {})) {
|
|
111
|
+
if (ignorer.includes(cle))
|
|
112
|
+
continue;
|
|
113
|
+
aplatir(cle, sous, cle, sortie);
|
|
114
|
+
}
|
|
115
|
+
return sortie;
|
|
116
|
+
}
|
|
117
|
+
/** Vue du modèle pour l'IA : langage naturel, aucun chemin de fichier. */
|
|
118
|
+
export function versManifest(modele, options) {
|
|
119
|
+
return {
|
|
120
|
+
version: 1,
|
|
121
|
+
site: options.site,
|
|
122
|
+
pages: modele.singletons.map((s) => ({ id: s.id, libelle: s.libelle, champs: champs(s.schema) })),
|
|
123
|
+
collections: modele.collections.map((c) => ({
|
|
124
|
+
id: c.id,
|
|
125
|
+
libelle: c.libelle,
|
|
126
|
+
actions: c.ordonnable
|
|
127
|
+
? ['ajouter', 'modifier', 'supprimer', 'reordonner']
|
|
128
|
+
: ['ajouter', 'modifier', 'supprimer'],
|
|
129
|
+
// `ordre` est ajouté automatiquement aux collections ordonnables : couvert par `reordonner`.
|
|
130
|
+
champs: champs(c.schema, c.ordonnable ? ['ordre'] : []),
|
|
131
|
+
...(c.format === 'markdown' ? { corps: CORPS_MARKDOWN } : {}),
|
|
132
|
+
})),
|
|
133
|
+
horsPerimetre: [...new Set([...HORS_PERIMETRE_SOCLE, ...(options.horsPerimetre ?? [])])],
|
|
134
|
+
contact: options.contact,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface Violation {
|
|
2
|
+
fichier?: string;
|
|
3
|
+
ligne?: number;
|
|
4
|
+
motif: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function validerMarkdown(source: string, decalageLigne?: number): Violation[];
|
|
7
|
+
/** Images (``) du source, avec leur ligne (1-based) et leur URL brute. */
|
|
8
|
+
export declare function extraireImages(source: string): {
|
|
9
|
+
url: string;
|
|
10
|
+
ligne?: number;
|
|
11
|
+
}[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import remarkGfm from 'remark-gfm';
|
|
2
|
+
import remarkParse from 'remark-parse';
|
|
3
|
+
import { unified } from 'unified';
|
|
4
|
+
import { MOTIF_LIEN, MOTIF_SRC_IMAGE } from '../motifs.js';
|
|
5
|
+
const analyseur = unified().use(remarkParse).use(remarkGfm);
|
|
6
|
+
const AUTORISES = new Set([
|
|
7
|
+
'root', 'paragraph', 'text', 'heading', 'strong', 'emphasis', 'link', 'list',
|
|
8
|
+
'listItem', 'blockquote', 'image', 'table', 'tableRow', 'tableCell', 'break',
|
|
9
|
+
]);
|
|
10
|
+
const DANS_CELLULE = new Set(['text', 'strong', 'emphasis', 'link']);
|
|
11
|
+
const MOTIFS_FIXES = {
|
|
12
|
+
html: 'HTML interdit',
|
|
13
|
+
code: 'bloc de code interdit',
|
|
14
|
+
inlineCode: 'code en ligne interdit',
|
|
15
|
+
delete: 'texte barré interdit',
|
|
16
|
+
footnoteReference: 'note de bas de page interdite',
|
|
17
|
+
footnoteDefinition: 'note de bas de page interdite',
|
|
18
|
+
thematicBreak: 'ligne horizontale interdite',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Autolien littéral GFM (`www.exemple.fr`, `http://exemple.fr` écrits nus) : un seul enfant texte égal à l'URL,
|
|
22
|
+
* ou à l'URL sans le préfixe `http://` que GFM ajoute aux `www.`. Un lien explicite `[x](http://…)` reste refusé.
|
|
23
|
+
*/
|
|
24
|
+
function estAutolienLitteral(lien) {
|
|
25
|
+
if (!/^https?:\/\//.test(lien.url))
|
|
26
|
+
return false;
|
|
27
|
+
const [enfant, ...reste] = lien.children;
|
|
28
|
+
if (enfant?.type !== 'text' || reste.length > 0)
|
|
29
|
+
return false;
|
|
30
|
+
return enfant.value === lien.url || `http://${enfant.value}` === lien.url;
|
|
31
|
+
}
|
|
32
|
+
// Motif de refus propre au nœud, ou undefined s'il est autorisé hors tableau.
|
|
33
|
+
function motifRefus(noeud) {
|
|
34
|
+
const fixe = MOTIFS_FIXES[noeud.type];
|
|
35
|
+
if (fixe)
|
|
36
|
+
return fixe;
|
|
37
|
+
switch (noeud.type) {
|
|
38
|
+
case 'heading':
|
|
39
|
+
return noeud.depth === 2 || noeud.depth === 3
|
|
40
|
+
? undefined
|
|
41
|
+
: `titre de niveau ${noeud.depth} interdit`;
|
|
42
|
+
case 'listItem':
|
|
43
|
+
return typeof noeud.checked === 'boolean' ? 'case à cocher interdite' : undefined;
|
|
44
|
+
case 'link':
|
|
45
|
+
return MOTIF_LIEN.test(noeud.url) || estAutolienLitteral(noeud) ? undefined : `lien non autorisé : ${noeud.url}`;
|
|
46
|
+
case 'image':
|
|
47
|
+
return MOTIF_SRC_IMAGE.test(noeud.url)
|
|
48
|
+
? undefined
|
|
49
|
+
: `image hors de /uploads/ : ${noeud.url}`;
|
|
50
|
+
default:
|
|
51
|
+
return AUTORISES.has(noeud.type) ? undefined : `élément « ${noeud.type} » interdit`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function validerMarkdown(source, decalageLigne = 0) {
|
|
55
|
+
// Les fins de ligne CRLF sont normalisées : le nombre de lignes est inchangé.
|
|
56
|
+
const arbre = analyseur.parse(source.replace(/\r\n/g, '\n'));
|
|
57
|
+
const violations = [];
|
|
58
|
+
const visiter = (noeud, dansCellule) => {
|
|
59
|
+
let motif = motifRefus(noeud);
|
|
60
|
+
if (!motif && dansCellule && !DANS_CELLULE.has(noeud.type)) {
|
|
61
|
+
motif =
|
|
62
|
+
noeud.type === 'image'
|
|
63
|
+
? 'image interdite dans un tableau'
|
|
64
|
+
: `élément « ${noeud.type} » interdit dans un tableau`;
|
|
65
|
+
}
|
|
66
|
+
if (motif) {
|
|
67
|
+
const ligne = noeud.position?.start.line;
|
|
68
|
+
violations.push(ligne === undefined ? { motif } : { ligne: ligne + decalageLigne, motif });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if ('children' in noeud) {
|
|
72
|
+
const enfantsDansCellule = dansCellule || noeud.type === 'tableCell';
|
|
73
|
+
for (const enfant of noeud.children)
|
|
74
|
+
visiter(enfant, enfantsDansCellule);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
visiter(arbre, false);
|
|
78
|
+
return violations;
|
|
79
|
+
}
|
|
80
|
+
/** Images (``) du source, avec leur ligne (1-based) et leur URL brute. */
|
|
81
|
+
export function extraireImages(source) {
|
|
82
|
+
const arbre = analyseur.parse(source.replace(/\r\n/g, '\n'));
|
|
83
|
+
const images = [];
|
|
84
|
+
const visiter = (noeud) => {
|
|
85
|
+
if (noeud.type === 'image') {
|
|
86
|
+
const ligne = noeud.position?.start.line;
|
|
87
|
+
images.push(ligne === undefined ? { url: noeud.url } : { url: noeud.url, ligne });
|
|
88
|
+
}
|
|
89
|
+
if ('children' in noeud)
|
|
90
|
+
for (const enfant of noeud.children)
|
|
91
|
+
visiter(enfant);
|
|
92
|
+
};
|
|
93
|
+
visiter(arbre);
|
|
94
|
+
return images;
|
|
95
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OptionsCms } from './manifest.js';
|
|
2
|
+
import type { Violation } from './markdown.js';
|
|
3
|
+
type Fichiers = {
|
|
4
|
+
'cms.schema.json': string;
|
|
5
|
+
'ai/manifest.json': string;
|
|
6
|
+
};
|
|
7
|
+
export declare function genererFichiers(racine: string, options: OptionsCms): Promise<Fichiers>;
|
|
8
|
+
/** Écrit les fichiers générés dont le contenu change ; renvoie les chemins (relatifs à `racine`) réécrits. */
|
|
9
|
+
export declare function synchroniser(racine: string, options: OptionsCms): Promise<string[]>;
|
|
10
|
+
/** Dérive des fichiers générés, puis validation des contenus. */
|
|
11
|
+
export declare function verifier(racine: string, options: OptionsCms): Promise<Violation[]>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { promises as fsp } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { chargerCollections } from './charger.js';
|
|
4
|
+
import { collecter } from './collecter.js';
|
|
5
|
+
import { validerContenus } from './contenus.js';
|
|
6
|
+
import { versJsonSchema } from './json-schema.js';
|
|
7
|
+
import { versManifest } from './manifest.js';
|
|
8
|
+
import { serialiser } from './serialiser.js';
|
|
9
|
+
const MOTIF_DERIVE = 'Schéma CMS périmé : lance `pnpm cms:sync` et committe.';
|
|
10
|
+
function fichiersDuModele(modele, options) {
|
|
11
|
+
return {
|
|
12
|
+
'cms.schema.json': serialiser(versJsonSchema(modele, options)),
|
|
13
|
+
'ai/manifest.json': serialiser(versManifest(modele, options)),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function genererFichiers(racine, options) {
|
|
17
|
+
return fichiersDuModele(collecter(await chargerCollections(racine)), options);
|
|
18
|
+
}
|
|
19
|
+
async function lireOuNull(fichier) {
|
|
20
|
+
try {
|
|
21
|
+
return await fsp.readFile(fichier, 'utf8');
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
if (e.code === 'ENOENT')
|
|
25
|
+
return null;
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Écrit les fichiers générés dont le contenu change ; renvoie les chemins (relatifs à `racine`) réécrits. */
|
|
30
|
+
export async function synchroniser(racine, options) {
|
|
31
|
+
const ecrits = [];
|
|
32
|
+
for (const [rel, contenu] of Object.entries(await genererFichiers(racine, options))) {
|
|
33
|
+
const abs = path.join(racine, rel);
|
|
34
|
+
if ((await lireOuNull(abs)) === contenu)
|
|
35
|
+
continue;
|
|
36
|
+
await fsp.mkdir(path.dirname(abs), { recursive: true });
|
|
37
|
+
await fsp.writeFile(abs, contenu);
|
|
38
|
+
ecrits.push(rel);
|
|
39
|
+
}
|
|
40
|
+
return ecrits;
|
|
41
|
+
}
|
|
42
|
+
/** Dérive des fichiers générés, puis validation des contenus. */
|
|
43
|
+
export async function verifier(racine, options) {
|
|
44
|
+
const modele = collecter(await chargerCollections(racine));
|
|
45
|
+
const violations = [];
|
|
46
|
+
for (const [rel, contenu] of Object.entries(fichiersDuModele(modele, options))) {
|
|
47
|
+
// Côté committé, CRLF → LF : un checkout Windows (core.autocrlf) ne compte pas comme une dérive.
|
|
48
|
+
if ((await lireOuNull(path.join(racine, rel)))?.replace(/\r\n/g, '\n') !== contenu) {
|
|
49
|
+
violations.push({ fichier: rel, motif: MOTIF_DERIVE });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
violations.push(...(await validerContenus(racine, modele)));
|
|
53
|
+
return violations;
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function trier(valeur, dansProperties) {
|
|
2
|
+
if (Array.isArray(valeur))
|
|
3
|
+
return valeur.map((v) => trier(v, false));
|
|
4
|
+
if (typeof valeur !== 'object' || valeur === null)
|
|
5
|
+
return valeur;
|
|
6
|
+
const entrees = Object.entries(valeur);
|
|
7
|
+
// Dans `properties`, l'ordre de déclaration est conservé.
|
|
8
|
+
if (!dansProperties)
|
|
9
|
+
entrees.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
10
|
+
const sortie = {};
|
|
11
|
+
for (const [cle, v] of entrees) {
|
|
12
|
+
if (cle === 'required' && Array.isArray(v) && v.every((x) => typeof x === 'string')) {
|
|
13
|
+
sortie[cle] = [...v].sort();
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// Les clés de `properties` sont des noms de champs : leurs valeurs sont des schémas ordinaires.
|
|
17
|
+
sortie[cle] = trier(v, !dansProperties && cle === 'properties');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return sortie;
|
|
21
|
+
}
|
|
22
|
+
/** JSON déterministe : clés triées (sauf `properties`), `required` trié, 2 espaces, saut de ligne final. */
|
|
23
|
+
export function serialiser(valeur) {
|
|
24
|
+
return `${JSON.stringify(trier(valeur, false), null, 2)}\n`;
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Écrit `<dist>/_cms/version.json` et déclare ses en-têtes dans `<dist>/_headers`
|
|
3
|
+
* (créé si absent, contenu existant conservé, règle ajoutée une seule fois).
|
|
4
|
+
*/
|
|
5
|
+
export declare function ecrireVersion(dossierDist: string, commit: string, origineAdmin: string): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { promises as fsp } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const CHEMIN = '/_cms/version.json';
|
|
4
|
+
/**
|
|
5
|
+
* Écrit `<dist>/_cms/version.json` et déclare ses en-têtes dans `<dist>/_headers`
|
|
6
|
+
* (créé si absent, contenu existant conservé, règle ajoutée une seule fois).
|
|
7
|
+
*/
|
|
8
|
+
export async function ecrireVersion(dossierDist, commit, origineAdmin) {
|
|
9
|
+
await fsp.mkdir(path.join(dossierDist, '_cms'), { recursive: true });
|
|
10
|
+
await fsp.writeFile(path.join(dossierDist, '_cms/version.json'), `${JSON.stringify({ commit })}\n`);
|
|
11
|
+
const fichier = path.join(dossierDist, '_headers');
|
|
12
|
+
let existant = '';
|
|
13
|
+
try {
|
|
14
|
+
existant = await fsp.readFile(fichier, 'utf8');
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
if (e.code !== 'ENOENT')
|
|
18
|
+
throw e;
|
|
19
|
+
}
|
|
20
|
+
if (existant.split('\n').some((ligne) => ligne.trim() === CHEMIN))
|
|
21
|
+
return;
|
|
22
|
+
const regle = `${CHEMIN}\n Access-Control-Allow-Origin: ${origineAdmin}\n Cache-Control: no-store\n`;
|
|
23
|
+
const separateur = existant === '' || existant.endsWith('\n') ? '' : '\n';
|
|
24
|
+
await fsp.writeFile(fichier, existant + separateur + regle);
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Ce que le CMS ne permet jamais de faire, quel que soit le site. */
|
|
2
|
+
export const HORS_PERIMETRE_SOCLE = [
|
|
3
|
+
'Modifier le design, les couleurs ou les polices',
|
|
4
|
+
'Modifier la mise en page',
|
|
5
|
+
'Ajouter une page ou une section',
|
|
6
|
+
'Ajouter une fonctionnalité (formulaire, réservation, paiement…)',
|
|
7
|
+
'Modifier le référencement technique',
|
|
8
|
+
'Gérer le nom de domaine ou les adresses e-mail',
|
|
9
|
+
];
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'astro/zod';
|
|
2
|
+
export type Editeur = 'texte' | 'paragraphe' | 'richtext' | 'image' | 'date' | 'lien' | 'nombre' | 'booleen' | 'choix' | 'liste' | 'groupe';
|
|
3
|
+
export interface MetaCms {
|
|
4
|
+
editeur: Editeur;
|
|
5
|
+
libelle: string;
|
|
6
|
+
aide?: string;
|
|
7
|
+
unite?: string;
|
|
8
|
+
onglet?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface OptionsBase {
|
|
11
|
+
libelle: string;
|
|
12
|
+
aide?: string;
|
|
13
|
+
/** `true` par défaut. */
|
|
14
|
+
obligatoire?: boolean;
|
|
15
|
+
/** Onglet de l'admin où ranger le champ (non vide). */
|
|
16
|
+
onglet?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DeclSingleton {
|
|
19
|
+
type: 'singleton';
|
|
20
|
+
id: string;
|
|
21
|
+
libelle: string;
|
|
22
|
+
adresse?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface DeclCollection {
|
|
25
|
+
type: 'collection';
|
|
26
|
+
id: string;
|
|
27
|
+
libelle: string;
|
|
28
|
+
format: 'json' | 'markdown';
|
|
29
|
+
titre: string;
|
|
30
|
+
ordonnable: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Lit la méta CMS d'un schéma, en traversant `ZodOptional`. */
|
|
33
|
+
export declare function lireMeta(schema: z.ZodType): MetaCms | undefined;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'astro/zod';
|
|
2
|
+
/** Lit la méta CMS d'un schéma, en traversant `ZodOptional`. */
|
|
3
|
+
export function lireMeta(schema) {
|
|
4
|
+
let courant = schema;
|
|
5
|
+
while (courant instanceof z.ZodOptional) {
|
|
6
|
+
courant = courant.unwrap();
|
|
7
|
+
}
|
|
8
|
+
const meta = courant.meta();
|
|
9
|
+
return meta?.cms;
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@komekoo/astro-cms",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.23.4"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./integration": {
|
|
15
|
+
"types": "./dist/integration.d.ts",
|
|
16
|
+
"default": "./dist/integration.js"
|
|
17
|
+
},
|
|
18
|
+
"./navigateur": {
|
|
19
|
+
"types": "./dist/navigateur.d.ts",
|
|
20
|
+
"default": "./dist/navigateur.js"
|
|
21
|
+
},
|
|
22
|
+
"./CmsImage.astro": "./src/composants/CmsImage.astro"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"komekoo-cms": "dist/cli.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src/composants"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"astro": "^7.4.2",
|
|
33
|
+
"vite": "^8.3.5"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/mdast": "4.0.4",
|
|
37
|
+
"@types/node": "26.6.5",
|
|
38
|
+
"astro": "7.4.2",
|
|
39
|
+
"typescript": "7.0.2",
|
|
40
|
+
"vite": "8.3.5",
|
|
41
|
+
"vitest": "5.0.5"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"ajv": "8.20.0",
|
|
45
|
+
"ajv-formats": "3.0.1",
|
|
46
|
+
"remark-gfm": "4.0.1",
|
|
47
|
+
"remark-parse": "11.0.0",
|
|
48
|
+
"unified": "11.0.5",
|
|
49
|
+
"yaml": "2.9.1"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
54
|
+
"build": "tsc -p tsconfig.json"
|
|
55
|
+
}
|
|
6
56
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { srcsetDe } from '@komekoo/astro-cms';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
image: { src: string; alt: string; largeur: number; hauteur: number };
|
|
6
|
+
sizes?: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
loading?: 'lazy' | 'eager';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { image, sizes = '100vw', class: classe, loading = 'lazy' } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<img
|
|
15
|
+
src={image.src}
|
|
16
|
+
srcset={srcsetDe(image.src, image.largeur)}
|
|
17
|
+
sizes={sizes}
|
|
18
|
+
alt={image.alt}
|
|
19
|
+
width={image.largeur}
|
|
20
|
+
height={image.hauteur}
|
|
21
|
+
loading={loading}
|
|
22
|
+
decoding="async"
|
|
23
|
+
class={classe}
|
|
24
|
+
/>
|