@komekoo/astro-cms 0.0.0-stage → 0.2.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 +144 -2
- package/dist/ajv.d.ts +4 -0
- package/dist/ajv.js +24 -0
- package/dist/apercu/arbre.d.ts +3 -0
- package/dist/apercu/arbre.js +64 -0
- package/dist/apercu/demarrage.d.ts +1 -0
- package/dist/apercu/demarrage.js +5 -0
- package/dist/apercu/index.d.ts +2 -0
- package/dist/apercu/index.js +2 -0
- package/dist/apercu/protocole.d.ts +67 -0
- package/dist/apercu/protocole.js +176 -0
- package/dist/apercu/script.d.ts +8 -0
- package/dist/apercu/script.js +321 -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 +39 -0
- package/dist/collections.js +57 -0
- package/dist/images.d.ts +8 -0
- package/dist/images.js +19 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +10 -0
- package/dist/integration.d.ts +9 -0
- package/dist/integration.js +113 -0
- package/dist/marqueur.d.ts +4 -0
- package/dist/marqueur.js +8 -0
- package/dist/motifs.d.ts +10 -0
- package/dist/motifs.js +11 -0
- package/dist/navigateur.d.ts +6 -0
- package/dist/navigateur.js +6 -0
- package/dist/noyau/apercu-build.d.ts +12 -0
- package/dist/noyau/apercu-build.js +74 -0
- package/dist/noyau/charger.d.ts +4 -0
- package/dist/noyau/charger.js +48 -0
- package/dist/noyau/collecter.d.ts +27 -0
- package/dist/noyau/collecter.js +133 -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 +61 -0
- package/dist/noyau/manifest.d.ts +16 -0
- package/dist/noyau/manifest.js +136 -0
- package/dist/noyau/markdown.d.ts +12 -0
- package/dist/noyau/markdown.js +95 -0
- package/dist/noyau/marqueurs.d.ts +11 -0
- package/dist/noyau/marqueurs.js +57 -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 +34 -0
- package/dist/types.js +10 -0
- package/package.json +62 -3
- package/src/composants/CmsImage.astro +26 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { promises as fsp } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const MARQUEUR = /<(\w+)\b[^>]*\sdata-cms="([^"]*)"/g;
|
|
4
|
+
const NUMERIQUE = /^\d+$/;
|
|
5
|
+
function resoudre(chemin, schema) {
|
|
6
|
+
const [id = '', ...suite] = chemin.split('.');
|
|
7
|
+
if (!Object.hasOwn(schema, id))
|
|
8
|
+
return 'page inconnue';
|
|
9
|
+
let noeud = schema[id];
|
|
10
|
+
for (const segment of suite) {
|
|
11
|
+
const enfant = NUMERIQUE.test(segment) && noeud.items !== undefined
|
|
12
|
+
? noeud.items
|
|
13
|
+
: noeud.properties !== undefined && Object.hasOwn(noeud.properties, segment)
|
|
14
|
+
? noeud.properties[segment]
|
|
15
|
+
: undefined;
|
|
16
|
+
if (enfant === undefined)
|
|
17
|
+
return 'champ inconnu';
|
|
18
|
+
noeud = enfant;
|
|
19
|
+
}
|
|
20
|
+
return noeud;
|
|
21
|
+
}
|
|
22
|
+
/** Contrôle les marqueurs `data-cms` d'une page HTML produite. */
|
|
23
|
+
export function verifierMarqueursHtml(html, page, schema) {
|
|
24
|
+
const erreurs = [];
|
|
25
|
+
for (const [, balise, chemin] of html.matchAll(MARQUEUR)) {
|
|
26
|
+
const noeud = resoudre(chemin, schema);
|
|
27
|
+
const motif = typeof noeud === 'string'
|
|
28
|
+
? noeud
|
|
29
|
+
: noeud['x-cms']?.editeur === 'image' && balise.toLowerCase() !== 'img'
|
|
30
|
+
? "image hors d'une balise <img>"
|
|
31
|
+
: noeud['x-cms']?.editeur === 'lien' && balise.toLowerCase() !== 'a'
|
|
32
|
+
? "lien hors d'une balise <a>"
|
|
33
|
+
: undefined;
|
|
34
|
+
if (motif !== undefined)
|
|
35
|
+
erreurs.push({ page, chemin: chemin, motif });
|
|
36
|
+
}
|
|
37
|
+
return erreurs;
|
|
38
|
+
}
|
|
39
|
+
async function listerHtml(dossier, prefixe = '') {
|
|
40
|
+
const entrees = await fsp.readdir(path.join(dossier, prefixe), { withFileTypes: true });
|
|
41
|
+
const fichiers = await Promise.all(entrees.map(async (e) => {
|
|
42
|
+
const relatif = prefixe === '' ? e.name : `${prefixe}/${e.name}`;
|
|
43
|
+
if (e.isDirectory())
|
|
44
|
+
return listerHtml(dossier, relatif);
|
|
45
|
+
return e.name.endsWith('.html') ? [relatif] : [];
|
|
46
|
+
}));
|
|
47
|
+
return fichiers.flat().sort();
|
|
48
|
+
}
|
|
49
|
+
/** Contrôle toutes les pages HTML de `dossierDist` ; `page` est relative à ce dossier (séparateur `/`). */
|
|
50
|
+
export async function verifierMarqueurs(dossierDist, schema) {
|
|
51
|
+
const erreurs = [];
|
|
52
|
+
for (const page of await listerHtml(dossierDist)) {
|
|
53
|
+
const html = await fsp.readFile(path.join(dossierDist, page), 'utf8');
|
|
54
|
+
erreurs.push(...verifierMarqueursHtml(html, page, schema));
|
|
55
|
+
}
|
|
56
|
+
return erreurs;
|
|
57
|
+
}
|
|
@@ -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,34 @@
|
|
|
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
|
+
apercu?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface DeclCollection {
|
|
26
|
+
type: 'collection';
|
|
27
|
+
id: string;
|
|
28
|
+
libelle: string;
|
|
29
|
+
format: 'json' | 'markdown';
|
|
30
|
+
titre: string;
|
|
31
|
+
ordonnable: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Lit la méta CMS d'un schéma, en traversant `ZodOptional`. */
|
|
34
|
+
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,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@komekoo/astro-cms",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.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
|
+
"./apercu": {
|
|
23
|
+
"types": "./dist/apercu/index.d.ts",
|
|
24
|
+
"default": "./dist/apercu/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./apercu/arbre": {
|
|
27
|
+
"types": "./dist/apercu/arbre.d.ts",
|
|
28
|
+
"default": "./dist/apercu/arbre.js"
|
|
29
|
+
},
|
|
30
|
+
"./CmsImage.astro": "./src/composants/CmsImage.astro"
|
|
31
|
+
},
|
|
32
|
+
"bin": {
|
|
33
|
+
"komekoo-cms": "dist/cli.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src/composants"
|
|
38
|
+
],
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"astro": "^7.4.2",
|
|
41
|
+
"vite": "^8.3.5"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/mdast": "4.0.4",
|
|
45
|
+
"@types/node": "26.6.5",
|
|
46
|
+
"astro": "7.4.2",
|
|
47
|
+
"happy-dom": "21.0.2",
|
|
48
|
+
"typescript": "7.0.2",
|
|
49
|
+
"vite": "8.3.5",
|
|
50
|
+
"vitest": "5.0.5"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"ajv": "8.20.0",
|
|
54
|
+
"ajv-formats": "3.0.1",
|
|
55
|
+
"remark-gfm": "4.0.1",
|
|
56
|
+
"remark-parse": "11.0.0",
|
|
57
|
+
"unified": "11.0.5",
|
|
58
|
+
"yaml": "2.9.1"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
63
|
+
"build": "tsc -p tsconfig.json"
|
|
64
|
+
}
|
|
6
65
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
cms?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { image, sizes = '100vw', class: classe, loading = 'lazy', cms } = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<img
|
|
16
|
+
src={image.src}
|
|
17
|
+
srcset={srcsetDe(image.src, image.largeur)}
|
|
18
|
+
sizes={sizes}
|
|
19
|
+
alt={image.alt}
|
|
20
|
+
width={image.largeur}
|
|
21
|
+
height={image.hauteur}
|
|
22
|
+
loading={loading}
|
|
23
|
+
decoding="async"
|
|
24
|
+
class={classe}
|
|
25
|
+
data-cms={cms}
|
|
26
|
+
/>
|