@starterlib/cli 0.10.2 → 0.10.3

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.
@@ -90,5 +90,6 @@ partout d'un coup.
90
90
  `transloco` entrera ici quand le premier depot de SPA l'emploiera ; il declare
91
91
  une plage de pairs tres large (`>=16`), qui ne vaut pas compatibilite testee
92
92
  avec la 22. `orval` y est depuis l'ADR web 0008, a la version relevee le
93
- 2026-09-25 : la compatibilite avec la 22 se prouvera au premier client engendre
94
- (lot 175).
93
+ 2026-09-25 : sa compatibilite avec la 22 est prouvee depuis le lot 175 (2026-09-28), par les
94
+ clients engendres de la coquille, du temoin, de catalog et d'identity, construits et essayes
95
+ contre Angular 22.2.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starterlib/cli",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "Chaine de verification et de construction des SPA du socle frontal starter : starter-web verify, construire, sprite, manifeste, precompresser. Jamais en dependance de production.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -128,13 +128,18 @@ export function ecrireLeManifeste(projet) {
128
128
  `${prefixe}/i18n/${hacherLeCatalogue(join(projet.sortie, 'i18n'), nom, langue)}`;
129
129
  }
130
130
 
131
- const icones = join(projet.sortie, 'icones');
132
- const sprite = existsSync(icones)
133
- ? readdirSync(icones).find((fichier) => /^sprite-[A-Z0-9]{8}\.svg$/.test(fichier))
134
- : undefined;
135
- if (sprite === undefined) {
131
+ // Le sprite annonce est celui de la SOURCE, nomme par son empreinte (lot 194) : jamais le premier
132
+ // fichier venu d'un dossier ou deux sprites haches pouvaient coexister (le temoin y avait porte
133
+ // son sprite metier et une copie de celui du socle, et le manifeste pouvait annoncer l'autre).
134
+ const spriteSource = join(projet.racine, 'src', 'icones', 'sprite.svg');
135
+ if (!existsSync(spriteSource)) {
136
+ throw new Error('sprite metier absent : src/icones/sprite.svg.');
137
+ }
138
+ const sprite = `sprite-${empreinteCourte(readFileSync(spriteSource))}.svg`;
139
+ if (!existsSync(join(projet.sortie, 'icones', sprite))) {
136
140
  throw new Error(
137
- `sprite metier absent de ${projet.sortieRelative}/icones : starter-web sprite d'abord.`,
141
+ `${projet.sortieRelative}/icones/${sprite} absent (le sprite de src/icones/sprite.svg) : ` +
142
+ "starter-web sprite, puis construire, d'abord.",
138
143
  );
139
144
  }
140
145
 
@@ -13,6 +13,7 @@
13
13
  * Une ancienne empreinte est retiree : deux sprites livres, et le manifeste pourrait annoncer le
14
14
  * mauvais.
15
15
  */
16
+ import { createHash } from 'node:crypto';
16
17
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
17
18
  import { join } from 'node:path';
18
19
  import { empreinteCourte } from '../commun.mjs';
@@ -47,6 +48,27 @@ function reglages(projet) {
47
48
  };
48
49
  }
49
50
 
51
+ /**
52
+ * L'hote livre le sprite du socle tel que `@starterlib/ui` l'a publie (lot 194) : le
53
+ * `catalogue.json` publie a cote en porte l'empreinte SHA-256. Un sprite recopie a la main, tronque
54
+ * ou d'une autre version que son catalogue arrete la construction, au lieu de livrer des icones que
55
+ * le catalogue ne decrit pas.
56
+ */
57
+ function verifierLeSpriteDuSocle(source, contenu) {
58
+ const chemin = join(source, '..', 'catalogue.json');
59
+ if (!existsSync(chemin)) {
60
+ throw new Error(`catalogue du sprite du socle absent : ${chemin}.`);
61
+ }
62
+ const annonce = JSON.parse(readFileSync(chemin, 'utf8')).sprite ?? {};
63
+ const lue = createHash('sha256').update(contenu).digest('hex');
64
+ if (annonce.sha256 !== lue) {
65
+ throw new Error(
66
+ `sprite du socle altere : empreinte ${empreinteCourte(contenu)}, le catalogue de ` +
67
+ `@starterlib/ui annonce ${String(annonce.empreinte)} (${chemin}).`,
68
+ );
69
+ }
70
+ }
71
+
50
72
  /** Pose le sprite hache et sa constante ; rend l'adresse servie. */
51
73
  export function poserLeSprite(projet) {
52
74
  const { source, dossier, adresse, genere, constante } = reglages(projet);
@@ -56,6 +78,7 @@ export function poserLeSprite(projet) {
56
78
  );
57
79
  }
58
80
  const contenu = readFileSync(source);
81
+ if (projet.role === 'hote') verifierLeSpriteDuSocle(source, contenu);
59
82
  const fichier = `sprite-${empreinteCourte(contenu)}.svg`;
60
83
  mkdirSync(dossier, { recursive: true });
61
84
  for (const existant of readdirSync(dossier)) {
@@ -34,16 +34,22 @@ export function typesDeCles(perimetres, catalogues) {
34
34
  for (const perimetre of perimetres) {
35
35
  const cles = Object.keys(catalogues[perimetre]?.fr ?? {}).sort();
36
36
  lignes.push('', `/** Les cles du perimetre ${perimetre}. */`);
37
- const enLigne = `export type ${nomDuType(perimetre)} = ${
38
- cles.length === 0 ? 'never' : cles.map((cle) => `'${cle}'`).join(' | ')
39
- };`;
40
- // La forme que Prettier (largeur 100) donnerait : une ligne si elle tient, une cle par ligne
41
- // sinon. Le fichier engendre passe ainsi `prettier --check` sans etre ecarte.
37
+ const union = cles.length === 0 ? 'never' : cles.map((cle) => `'${cle}'`).join(' | ');
38
+ const enLigne = `export type ${nomDuType(perimetre)} = ${union};`;
39
+ // La forme que Prettier (largeur 100) donne, dans ses trois cas : une ligne si elle tient ;
40
+ // sinon l'union sur sa propre ligne indentee, si elle y tient (0.10.3 : la 0.10.2 sautait ce
41
+ // cas, et un petit catalogue, le squelette de new-spa.sh pour un nom de cinq lettres, sortait
42
+ // en echec de `prettier --check`) ; sinon une cle par ligne. Le fichier engendre passe ainsi
43
+ // `prettier --check` sans etre ecarte (test/i18n.test.mjs le compare a Prettier).
42
44
  if (enLigne.length <= 100) {
43
45
  lignes.push(enLigne);
44
46
  continue;
45
47
  }
46
48
  lignes.push(`export type ${nomDuType(perimetre)} =`);
49
+ if (` ${union};`.length <= 100 || cles.length === 1) {
50
+ lignes.push(` ${union};`);
51
+ continue;
52
+ }
47
53
  cles.forEach((cle, indice) => {
48
54
  lignes.push(` | '${cle}'${indice === cles.length - 1 ? ';' : ''}`);
49
55
  });
@@ -100,17 +100,52 @@ export const test = {
100
100
  executer: (projet) => deleguer(projet, ['exec', 'ng', 'test', '--no-watch'], 'ng test conforme.'),
101
101
  };
102
102
 
103
+ /** Ou vit le client engendre (ADR web 0008). */
104
+ export const DOSSIER_DU_CLIENT_ENGENDRE = join('src', 'app', 'donnees', 'genere');
105
+
106
+ /** Un motif du contrat, tel qu'orval le recopie en commentaire du client engendre. */
107
+ const MOTIF_ENGENDRE = /@pattern\s+(\S.*?)\s*$/gm;
108
+
109
+ /**
110
+ * Les motifs du client engendre qui ne compilent pas en ECMA-262 (lot 175, report du lot 81) : un
111
+ * motif Java (`\p{Cntrl}`, quantificateur possessif, groupe atomique) recopie par le contrat. Il
112
+ * refuserait chaque saisie legitime, ou ne compilerait pas, le jour ou un formulaire ou un schema
113
+ * de validation le reprendrait. Lu avec le drapeau `u`, celui d'un motif HTML.
114
+ */
115
+ export function motifsNonPortables(racine) {
116
+ const defauts = [];
117
+ for (const chemin of fichiers(join(racine, DOSSIER_DU_CLIENT_ENGENDRE))) {
118
+ if (!chemin.endsWith('.ts')) continue;
119
+ for (const [, motif] of readFileSync(chemin, 'utf8').matchAll(MOTIF_ENGENDRE)) {
120
+ try {
121
+ new RegExp(motif, 'u');
122
+ } catch (erreur) {
123
+ defauts.push(
124
+ `${relatif(racine, chemin)} : motif ${motif} hors ECMA-262 (${erreur instanceof Error ? erreur.message : String(erreur)}) ; le corriger dans le contrat du service.`,
125
+ );
126
+ }
127
+ }
128
+ }
129
+ return defauts;
130
+ }
131
+
103
132
  /**
104
- * `contrat` : le client d'API engendre depuis le contrat du service, sans difference (lot 175).
105
- * Tant que la SPA n'a pas de script `contrat:check`, l'etape est sautee, et le dit.
133
+ * `contrat` : le client d'API engendre depuis le contrat du service, sans difference (lot 175), et
134
+ * sans motif qu'un navigateur ne sait pas lire. Tant que la SPA n'a pas de script `contrat:check`,
135
+ * l'etape est sautee, et le dit.
106
136
  */
107
137
  export const contrat = {
108
138
  nom: 'contrat',
109
139
  roles: ['remote', 'hote'],
110
140
  executer(projet) {
111
141
  if (projet.paquet.scripts?.['contrat:check'] === undefined) {
112
- return { sautee: 'aucun script contrat:check (client engendre : lot 175).' };
142
+ return { sautee: 'aucun script contrat:check : pas de client engendre (ADR web 0008).' };
113
143
  }
114
- return deleguer(projet, ['run', 'contrat:check'], 'client engendre sans difference.');
144
+ const resultat = deleguer(projet, ['run', 'contrat:check'], 'client engendre sans difference.');
145
+ if (resultat.defauts.length > 0) return resultat;
146
+ const motifs = motifsNonPortables(projet.racine);
147
+ return motifs.length === 0
148
+ ? { defauts: [], resume: 'client engendre sans difference, motifs ECMA-262.' }
149
+ : { defauts: motifs };
115
150
  },
116
151
  };
package/src/verifier.mjs CHANGED
@@ -75,12 +75,14 @@ export async function verifier(
75
75
 
76
76
  for (const [indice, etape] of retenues.entries()) {
77
77
  journal.info(`[${String(indice + 1)}/${String(retenues.length)}] ${etape.nom}`);
78
- if (etape.preparation === 'sprite' && !spritePose) {
79
- journal.info(` sprite : ${poserLeSprite(projet)}`);
80
- spritePose = true;
81
- }
82
78
  let resultat;
83
79
  try {
80
+ // Un sprite qui ne se pose pas (absent, altere) est un defaut de l'etape qui le lit, nomme
81
+ // comme les autres, et non une commande qui ne peut pas s'executer.
82
+ if (etape.preparation === 'sprite' && !spritePose) {
83
+ journal.info(` sprite : ${poserLeSprite(projet)}`);
84
+ spritePose = true;
85
+ }
84
86
  resultat = await etape.executer(projet, contexte);
85
87
  } catch (erreur) {
86
88
  resultat = { defauts: [erreur instanceof Error ? erreur.message : String(erreur)] };