@starterlib/cli 0.11.2 → 0.11.4
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 +5 -0
- package/matrice-compatibilite.md +18 -17
- package/package.json +1 -1
- package/src/construction/construire.mjs +31 -3
- package/src/construction/prerendu.mjs +202 -0
- package/src/construction/sprite.mjs +7 -2
- package/src/etapes/i18n.mjs +27 -14
- package/src/etapes/securite.mjs +7 -1
- package/src/projet.mjs +9 -0
package/README.md
CHANGED
|
@@ -95,6 +95,11 @@ par un point d'entree vide dans la configuration `production`).
|
|
|
95
95
|
`starter-web construire` n'y ecrit pas de manifeste, et pose `--stats-json` ; `securite` y
|
|
96
96
|
neutralise UNE lecture de cookie du cadre (`DomAdapter.getCookie`, dans `main` faute de morceau
|
|
97
97
|
partage), toute autre restant un defaut ;
|
|
98
|
+
- pour une application autonome PRERENDUE (0.11.4), `starterWeb.prerendu: true` : un site statique servi a la
|
|
99
|
+
racine de son domaine (`outputMode: "static"`), que le navigateur ne reprend pas. `starter-web construire` sort
|
|
100
|
+
de chaque page les feuilles des composants et les attributs `style` dans `composants-<empreinte>.css` (une
|
|
101
|
+
politique de securite stricte les refuserait en ligne) et y precharge la police ; son sprite est servi a
|
|
102
|
+
`/assets/icones/` (`src/construction/prerendu.mjs`) ;
|
|
98
103
|
- pour l'hote, dans `starterWeb` : `premierEcran.entrees` (les sources de ses gabarits et de sa page d'arrivee,
|
|
99
104
|
`["src/app/gabarits/coquille.ts", "src/app/pages/accueil.ts"]`), cherchees dans `browser-stats.json` que la
|
|
100
105
|
cible esbuild d'`angular.json` ecrit avec `statsJson: true` ; sans elles, le premier ecran compte toutes les pages
|
package/matrice-compatibilite.md
CHANGED
|
@@ -16,23 +16,24 @@ meme corrective, attend : au 2026-10-07, `axe-core` 4.14.0 (2026-10-05), `typesc
|
|
|
16
16
|
|
|
17
17
|
## Train `2026.1`
|
|
18
18
|
|
|
19
|
-
| Element | Version | Pourquoi cette version exactement
|
|
20
|
-
| -------------------------------------------------------------------------------------------- | ------------------ |
|
|
21
|
-
| `@angular/core`, `common`, `compiler`, `compiler-cli`, `forms`, `platform-browser`, `router` | `22.2.1` | Dernier correctif de la ligne 22.2, publie le 2026-09-30 (plus de trois jours au 2026-10-07)
|
|
22
|
-
| `@angular/cdk`, `aria` | `22.2.1` | Le paquet `aria` est stable depuis la 22 ; il porte les primitives sans style. `@angular/material` est retire : aucun composant du socle ne l'importe
|
|
23
|
-
| `@angular/elements` | `22.2.1` | Les custom elements de `@starterlib/elements` seulement (lot 299, decision du proprietaire du 2026-10-01) ; son pair exige le numero exact de `@angular/core`, d'ou le meme 22.2.1
|
|
24
|
-
| `@angular/
|
|
25
|
-
| `@angular
|
|
26
|
-
|
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `playwright`, `@
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
19
|
+
| Element | Version | Pourquoi cette version exactement |
|
|
20
|
+
| -------------------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| `@angular/core`, `common`, `compiler`, `compiler-cli`, `forms`, `platform-browser`, `router` | `22.2.1` | Dernier correctif de la ligne 22.2, publie le 2026-09-30 (plus de trois jours au 2026-10-07) |
|
|
22
|
+
| `@angular/cdk`, `aria` | `22.2.1` | Le paquet `aria` est stable depuis la 22 ; il porte les primitives sans style. `@angular/material` est retire : aucun composant du socle ne l'importe |
|
|
23
|
+
| `@angular/elements` | `22.2.1` | Les custom elements de `@starterlib/elements` seulement (lot 299, decision du proprietaire du 2026-10-01) ; son pair exige le numero exact de `@angular/core`, d'ou le meme 22.2.1 |
|
|
24
|
+
| `@angular/ssr`, `platform-server` | `22.2.1` | Le prerendu d'une application autonome (`starterWeb.prerendu`, 0.11.4 : le site public d'Aplomb) : la construction seule s'en sert, aucun serveur n'est livre. Le meme numero que `@angular/core`, dont leurs pairs exigent l'exact |
|
|
25
|
+
| `@angular/build`, `@angular/cli` | `22.2.1` | ⚠️ **Cadence differente de `core`.** L'outillage recoit des correctifs que le coeur ne recoit pas. Une matrice qui ne porterait qu'un seul numero mentirait des le premier correctif |
|
|
26
|
+
| `@angular-devkit/schematics` | `22.2.1` | Le moteur des schematics du CLI d'Angular, au meme numero : `@starterlib/schematics` (lot 285) n'en importe rien a l'execution, ses essais le lancent (`SchematicTestRunner`) |
|
|
27
|
+
| `ng-packagr` | `22.2.4` | Derniere stable de la ligne 22.2 (2026-10-01) |
|
|
28
|
+
| `typescript` | `6.0.3` | ⚠️ **Piege.** La 7.0.2 est publiee, mais `@angular/compiler-cli@22.2.1` exige `>=6.0 <6.1`. Installer la derniere casse la compilation |
|
|
29
|
+
| `node` | `>=24.15.0` | 24.x LTS, declare dans `engines` ; le meme plancher que la construction Maven des SPA (`frontend.node.version`) |
|
|
30
|
+
| `pnpm` | `11.27.1` | Declare dans `packageManager` ; reglages dans `pnpm-workspace.yaml` (`allowBuilds`, `minimumReleaseAge`). Le meme pour la construction Maven des SPA (`frontend.pnpm.version` de `platform-parent`) |
|
|
31
|
+
| `vitest`, `@vitest/browser`, `@vitest/browser-playwright`, `@vitest/coverage-v8` | `5.0.3` | Lanceur officiel du constructeur `@angular/build:unit-test` ; un meme numero pour toute la famille (des numeros meles chargent deux copies du lanceur) |
|
|
32
|
+
| `playwright`, `@playwright/test` | `1.63.0` | Le navigateur des essais : un correctif change un rendu mesure au pixel |
|
|
33
|
+
| `eslint` | `10.12.0` | `angular-eslint@22.5.0` accepte les majeurs 9 et 10 |
|
|
34
|
+
| `angular-eslint` | `22.5.0` | Suit le majeur d'Angular |
|
|
35
|
+
| `stylelint`, `postcss-scss` | `17.16.0`, `4.0.9` | Les feuilles du socle et des SPA (lot 178, `@starterlib/eslint-config/stylelint`) ; `postcss-scss` lit le SCSS |
|
|
36
|
+
| `jsdom`, `axe-core` | `30.1.1`, `4.13.0` | Le DOM des essais unitaires et le controle d'accessibilite : un correctif change ce qu'un essai voit |
|
|
36
37
|
|
|
37
38
|
## Prochain train : `2026.2` (prevu, non applique)
|
|
38
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starterlib/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
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",
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 1. le sprite hache et sa constante (le code l'importe) ;
|
|
5
5
|
* 2. `ng build` dans la configuration demandee ;
|
|
6
|
-
* 3. l'hote
|
|
6
|
+
* 3. l'hote et l'application autonome : le prechargement de la police du premier ecran ; une application
|
|
7
|
+
* PRERENDUE (`starterWeb.prerendu`, 0.11.4) le pose dans chaque page, et sort de ses pages les feuilles des
|
|
8
|
+
* composants et les attributs `style` (`prerendu.mjs`) ;
|
|
7
9
|
* 4. le manifeste (il renomme les catalogues i18n) ;
|
|
8
10
|
* 5. la precompression (apres le renommage, sans quoi des variantes resteraient orphelines).
|
|
9
11
|
*/
|
|
@@ -12,6 +14,11 @@ import { join } from 'node:path';
|
|
|
12
14
|
import { executer } from '../commun.mjs';
|
|
13
15
|
import { ecrireLeManifeste } from './manifeste.mjs';
|
|
14
16
|
import { juger, precompresser } from './precompresser.mjs';
|
|
17
|
+
import {
|
|
18
|
+
externaliserLesStyles,
|
|
19
|
+
prechargerLaPoliceDansLesPages,
|
|
20
|
+
resumerLExternalisation,
|
|
21
|
+
} from './prerendu.mjs';
|
|
15
22
|
import { poserLeSprite } from './sprite.mjs';
|
|
16
23
|
|
|
17
24
|
/** Inter latin, graisse variable, style normal : le nom que `polices.css` du socle donne. */
|
|
@@ -44,6 +51,16 @@ export function prechargerLaPolice(sortie) {
|
|
|
44
51
|
return polices[0];
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Une construction LIVREE : celle de production. Une autre configuration (`development`, pour l'hote de developpement)
|
|
56
|
+
* ecrit dans son propre dossier (son `outputPath`), ses actifs sous `ui/<nom>/` : ni police prechargee, ni manifeste,
|
|
57
|
+
* ni precompression, que ces etapes chercheraient dans le dossier de production (`pnpm build:dev` d'un remote echouait
|
|
58
|
+
* sur une copie neuve : « remoteEntry.json absent », constate le 2026-10-10).
|
|
59
|
+
*/
|
|
60
|
+
export function estLivree(configuration) {
|
|
61
|
+
return configuration === 'production';
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
/** Construit ; rend 0, ou le code de l'etape qui a echoue. `journal` : `{ info, erreur }`. */
|
|
48
65
|
export function construire(
|
|
49
66
|
projet,
|
|
@@ -62,9 +79,20 @@ export function construire(
|
|
|
62
79
|
journal.erreur(` ng build --configuration ${configuration} : code ${String(code)}.`);
|
|
63
80
|
return code;
|
|
64
81
|
}
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
if (!estLivree(configuration)) {
|
|
83
|
+
journal.info(
|
|
84
|
+
` ${configuration} : construction non livree, sans police, manifeste ni precompression.`,
|
|
85
|
+
);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
// L'hote et l'application autonome peignent un premier ecran : la police s'y precharge. Une application prerendue
|
|
89
|
+
// a une page par adresse : chacune la precharge, et ses styles sortent de la page (politique stricte).
|
|
90
|
+
if (projet.prerendu) {
|
|
91
|
+
journal.info(` police prechargee : ${prechargerLaPoliceDansLesPages(projet.sortie)}`);
|
|
92
|
+
journal.info(` prerendu : ${resumerLExternalisation(externaliserLesStyles(projet.sortie))}`);
|
|
93
|
+
} else if (projet.role === 'hote' || projet.role === 'autonome') {
|
|
67
94
|
journal.info(` police prechargee : ${prechargerLaPolice(projet.sortie)}`);
|
|
95
|
+
}
|
|
68
96
|
// Une application autonome n'est pas assemblee : aucun manifeste (lot 240).
|
|
69
97
|
if (projet.role !== 'autonome') {
|
|
70
98
|
const manifeste = ecrireLeManifeste(projet);
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* La sortie d'une application autonome PRERENDUE (`starterWeb.prerendu`, 0.11.4) : un site statique, une page HTML par
|
|
3
|
+
* adresse (le site public d'Aplomb, P09-28), servi a la racine de son domaine par un hebergeur statique, sous une
|
|
4
|
+
* politique de securite du contenu STRICTE : ni `unsafe-inline`, ni `unsafe-hashes`.
|
|
5
|
+
*
|
|
6
|
+
* POURQUOI CETTE ETAPE EXISTE
|
|
7
|
+
*
|
|
8
|
+
* Le prerendu d'Angular (`outputMode: "static"`) ecrit les feuilles des composants en `<style ng-app-id>` dans la tete
|
|
9
|
+
* de chaque page, et les liaisons de style d'un composant (`st-logo` : sa hauteur, son rapport) en attributs `style`.
|
|
10
|
+
* Une politique stricte refuse les deux, et l'etape `securite` refuse les premieres : la page partirait sans les
|
|
11
|
+
* styles du socle. L'etape les sort de la page, apres `ng build` et avant la precompression :
|
|
12
|
+
*
|
|
13
|
+
* - les feuilles des composants de TOUTES les pages, dedoublonnees dans l'ordre de leur premiere apparition, dans
|
|
14
|
+
* UNE feuille hachee `composants-<empreinte>.css` (immuable, partagee par les pages), liee par chaque page la ou
|
|
15
|
+
* etait sa premiere feuille (l'ordre de la cascade ne change pas : apres les feuilles globales) ;
|
|
16
|
+
* - chaque attribut `style` en une regle de cette feuille, sur `data-st-style="<empreinte>"`, ses declarations en
|
|
17
|
+
* `!important` : la priorite d'un style en ligne, qu'aucune feuille de composant ne contredit ;
|
|
18
|
+
* - le prechargement de la police du premier ecran dans CHAQUE page, en adresse absolue (une page vit dans un
|
|
19
|
+
* sous-dossier : `media/` relatif viserait `/en/media/`).
|
|
20
|
+
*
|
|
21
|
+
* Le navigateur ne reprend pas les pages (ni hydratation ni rendu) : Angular y recreerait des `<style>` que la
|
|
22
|
+
* politique refuserait. Une page qui veut un comportement l'ecrit en amelioration progressive (`src/main.ts`).
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { empreinteCourte, fichiers, relatif } from '../commun.mjs';
|
|
27
|
+
|
|
28
|
+
/** Une feuille de composant ecrite par le prerendu, et le blanc qui la suit. */
|
|
29
|
+
const FEUILLE_DE_COMPOSANT = /<style ng-app-id="[^"]*">([\s\S]*?)<\/style>[ \t]*\n?/g;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Ce que le prerendu peut ecrire hors d'une balise et qu'il ne faut pas lire comme elle : un commentaire, le contenu
|
|
33
|
+
* d'un `script` ou d'un `style` ; puis une balise ouvrante, attributs entre guillemets compris (le prerendu n'ecrit
|
|
34
|
+
* jamais de guillemet nu dans une valeur : il l'echappe en `"`).
|
|
35
|
+
*/
|
|
36
|
+
const SEGMENT =
|
|
37
|
+
/<!--[\s\S]*?-->|<(script|style)\b[^>]*>[\s\S]*?<\/\1>|<[a-zA-Z][^\s/>]*(?:\s+[^\s"'=<>/]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*\/?>/g;
|
|
38
|
+
|
|
39
|
+
/** L'attribut `style` d'une balise ouvrante. */
|
|
40
|
+
const ATTRIBUT_STYLE = /\sstyle="([^"]*)"/;
|
|
41
|
+
|
|
42
|
+
const CHARSET = /@charset\s+"[^"]*";\s*/gi;
|
|
43
|
+
|
|
44
|
+
/** La police du premier ecran, nommee par la recette `polices` du socle (voir `construire.mjs`). */
|
|
45
|
+
const POLICE = /^inter-latin-wght-normal-[A-Za-z0-9]+\.woff2$/;
|
|
46
|
+
|
|
47
|
+
/** Les pages du site : chaque `.html` du dossier livre, dans un ordre stable. */
|
|
48
|
+
export function pagesPrerendues(sortie) {
|
|
49
|
+
return fichiers(sortie).filter((chemin) => chemin.endsWith('.html'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Les entites qu'une valeur d'attribut peut porter, rendues a leur caractere. */
|
|
53
|
+
function decoder(valeur) {
|
|
54
|
+
return valeur
|
|
55
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCodePoint(Number.parseInt(code, 16)))
|
|
56
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number.parseInt(code, 10)))
|
|
57
|
+
.replace(/"/g, '"')
|
|
58
|
+
.replace(/'/g, "'")
|
|
59
|
+
.replace(/</g, '<')
|
|
60
|
+
.replace(/>/g, '>')
|
|
61
|
+
.replace(/ /g, ' ')
|
|
62
|
+
.replace(/&/g, '&');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Les declarations d'un attribut `style`, chacune en `!important` ; un `;` entre parentheses ou guillemets reste. */
|
|
66
|
+
export function declarationsImportantes(style) {
|
|
67
|
+
const declarations = [];
|
|
68
|
+
let courante = '';
|
|
69
|
+
let profondeur = 0;
|
|
70
|
+
let guillemet = null;
|
|
71
|
+
for (const caractere of style) {
|
|
72
|
+
if (guillemet !== null) {
|
|
73
|
+
if (caractere === guillemet) guillemet = null;
|
|
74
|
+
} else if (caractere === '"' || caractere === "'") {
|
|
75
|
+
guillemet = caractere;
|
|
76
|
+
} else if (caractere === '(') {
|
|
77
|
+
profondeur += 1;
|
|
78
|
+
} else if (caractere === ')') {
|
|
79
|
+
profondeur = Math.max(0, profondeur - 1);
|
|
80
|
+
} else if (caractere === ';' && profondeur === 0) {
|
|
81
|
+
declarations.push(courante);
|
|
82
|
+
courante = '';
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
courante += caractere;
|
|
86
|
+
}
|
|
87
|
+
declarations.push(courante);
|
|
88
|
+
return declarations
|
|
89
|
+
.map((declaration) => declaration.trim())
|
|
90
|
+
.filter((declaration) => declaration.includes(':'))
|
|
91
|
+
.map((declaration) =>
|
|
92
|
+
/!\s*important$/i.test(declaration) ? declaration : `${declaration} !important`,
|
|
93
|
+
)
|
|
94
|
+
.join(';');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Sort les feuilles des composants et les attributs `style` de chaque page dans une feuille hachee. Rend
|
|
99
|
+
* `{ pages, feuilles, styles, feuille }` : les pages reecrites, les feuilles et les attributs distincts sortis, le nom
|
|
100
|
+
* de la feuille ecrite (`null` si rien n'etait a sortir : une seconde passe ne change rien).
|
|
101
|
+
*/
|
|
102
|
+
export function externaliserLesStyles(sortie) {
|
|
103
|
+
const pages = pagesPrerendues(sortie).map((chemin) => ({
|
|
104
|
+
chemin,
|
|
105
|
+
contenu: readFileSync(chemin, 'utf8'),
|
|
106
|
+
}));
|
|
107
|
+
const feuilles = [];
|
|
108
|
+
const vues = new Set();
|
|
109
|
+
const regles = new Map();
|
|
110
|
+
|
|
111
|
+
for (const page of pages) {
|
|
112
|
+
for (const [, feuille] of page.contenu.matchAll(FEUILLE_DE_COMPOSANT)) {
|
|
113
|
+
const propre = feuille.replace(CHARSET, '').trim();
|
|
114
|
+
if (propre !== '' && !vues.has(propre)) {
|
|
115
|
+
vues.add(propre);
|
|
116
|
+
feuilles.push(propre);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
page.contenu = page.contenu.replace(SEGMENT, (segment, rawText) => {
|
|
120
|
+
if (rawText !== undefined || segment.startsWith('<!--')) return segment;
|
|
121
|
+
const trouve = ATTRIBUT_STYLE.exec(segment);
|
|
122
|
+
if (trouve === null) return segment;
|
|
123
|
+
const declarations = declarationsImportantes(decoder(trouve[1]));
|
|
124
|
+
if (declarations === '') return segment.replace(ATTRIBUT_STYLE, '');
|
|
125
|
+
const empreinte = empreinteCourte(declarations).toLowerCase();
|
|
126
|
+
regles.set(empreinte, declarations);
|
|
127
|
+
return segment.replace(ATTRIBUT_STYLE, ` data-st-style="${empreinte}"`);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (feuilles.length === 0 && regles.size === 0) {
|
|
132
|
+
return { pages: 0, feuilles: 0, styles: 0, feuille: null };
|
|
133
|
+
}
|
|
134
|
+
const contenu =
|
|
135
|
+
'@charset "UTF-8";\n' +
|
|
136
|
+
[
|
|
137
|
+
...feuilles,
|
|
138
|
+
...[...regles].map(
|
|
139
|
+
([empreinte, declarations]) => `[data-st-style="${empreinte}"]{${declarations}}`,
|
|
140
|
+
),
|
|
141
|
+
].join('\n') +
|
|
142
|
+
'\n';
|
|
143
|
+
const feuille = `composants-${empreinteCourte(contenu)}.css`;
|
|
144
|
+
writeFileSync(join(sortie, feuille), contenu, 'utf8');
|
|
145
|
+
const lien = `<link rel="stylesheet" href="/${feuille}">`;
|
|
146
|
+
|
|
147
|
+
let reecrites = 0;
|
|
148
|
+
for (const page of pages) {
|
|
149
|
+
let premiere = true;
|
|
150
|
+
let contenuDeLaPage = page.contenu.replace(FEUILLE_DE_COMPOSANT, () => {
|
|
151
|
+
if (!premiere) return '';
|
|
152
|
+
premiere = false;
|
|
153
|
+
return lien;
|
|
154
|
+
});
|
|
155
|
+
// Une page sans feuille de composant, mais dont un attribut `style` est sorti, lie la feuille avant `</head>`.
|
|
156
|
+
if (
|
|
157
|
+
premiere &&
|
|
158
|
+
contenuDeLaPage.includes('data-st-style="') &&
|
|
159
|
+
contenuDeLaPage.includes('</head>')
|
|
160
|
+
) {
|
|
161
|
+
contenuDeLaPage = contenuDeLaPage.replace('</head>', `${lien}</head>`);
|
|
162
|
+
}
|
|
163
|
+
if (contenuDeLaPage !== readFileSync(page.chemin, 'utf8')) {
|
|
164
|
+
writeFileSync(page.chemin, contenuDeLaPage, 'utf8');
|
|
165
|
+
reecrites += 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { pages: reecrites, feuilles: feuilles.length, styles: regles.size, feuille };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Pose dans CHAQUE page le prechargement de la police du premier ecran, en adresse absolue (`/media/...`). Rend le nom
|
|
173
|
+
* de la police. La construction sans la police echoue : le premier rendu partirait dans la police du systeme.
|
|
174
|
+
*/
|
|
175
|
+
export function prechargerLaPoliceDansLesPages(sortie) {
|
|
176
|
+
const media = join(sortie, 'media');
|
|
177
|
+
const polices = existsSync(media) ? readdirSync(media).filter((nom) => POLICE.test(nom)) : [];
|
|
178
|
+
if (polices.length !== 1) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`prechargement de la police : ${String(polices.length)} fichier(s) Inter latin dans media/ ` +
|
|
181
|
+
'(un attendu). La feuille des polices du socle est-elle dans angular.json ?',
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const lien = `<link rel="preload" href="/media/${polices[0]}" as="font" type="font/woff2" crossorigin>`;
|
|
185
|
+
for (const chemin of pagesPrerendues(sortie)) {
|
|
186
|
+
const page = readFileSync(chemin, 'utf8');
|
|
187
|
+
if (page.includes(`href="/media/${polices[0]}"`)) continue;
|
|
188
|
+
if (!page.includes('</head>')) {
|
|
189
|
+
throw new Error(`prechargement de la police : ${relatif(sortie, chemin)} sans </head>.`);
|
|
190
|
+
}
|
|
191
|
+
writeFileSync(chemin, page.replace('</head>', ` ${lien}\n</head>`), 'utf8');
|
|
192
|
+
}
|
|
193
|
+
return polices[0];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Le resume d'une externalisation, pour le journal de la construction. */
|
|
197
|
+
export function resumerLExternalisation({ pages, feuilles, styles, feuille }) {
|
|
198
|
+
return feuille === null
|
|
199
|
+
? 'aucune feuille de composant ni attribut style a sortir.'
|
|
200
|
+
: `${String(feuilles)} feuille(s) de composant et ${String(styles)} attribut(s) style sortis dans ` +
|
|
201
|
+
`${feuille}, ${String(pages)} page(s) reecrite(s).`;
|
|
202
|
+
}
|
|
@@ -22,7 +22,9 @@ const SPRITE_HACHE = /^sprite-[A-Z0-9]{8}\.svg$/;
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Les chemins du sprite selon le role. Une application autonome (lot 240) peint le sprite du SOCLE,
|
|
25
|
-
* comme l'hote : elle n'a ni remote ni sprite metier.
|
|
25
|
+
* comme l'hote : elle n'a ni remote ni sprite metier. Une application PRERENDUE (0.11.4) est servie a
|
|
26
|
+
* la racine de son domaine par un hebergeur statique, et non par le BFF sous `/ui/<nom>/` : son
|
|
27
|
+
* sprite est a `/assets/icones/`.
|
|
26
28
|
*/
|
|
27
29
|
const duSocle = (projet) => projet.role === 'hote' || projet.role === 'autonome';
|
|
28
30
|
|
|
@@ -39,7 +41,10 @@ function reglages(projet) {
|
|
|
39
41
|
'sprite.svg',
|
|
40
42
|
),
|
|
41
43
|
dossier: join(projet.racine, 'public', 'assets', 'icones'),
|
|
42
|
-
adresse: (fichier) =>
|
|
44
|
+
adresse: (fichier) =>
|
|
45
|
+
projet.prerendu === true
|
|
46
|
+
? `/assets/icones/${fichier}`
|
|
47
|
+
: `/ui/${projet.nom}/assets/icones/${fichier}`,
|
|
43
48
|
genere: join(projet.racine, 'src', 'app', 'sprite-genere.ts'),
|
|
44
49
|
constante: 'CHEMIN_DU_SPRITE',
|
|
45
50
|
};
|
package/src/etapes/i18n.mjs
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* `<perimetre>.pages.<page>`, et la cle soeur `<perimetre>.aide.<page>` existe (le texte de
|
|
17
17
|
* l'aide que `st-page-header` pose a cote du titre) ; une aide sans page est une faute de frappe ;
|
|
18
18
|
* - l'hote porte les cles que `@starterlib/core` traduit par son catalogue
|
|
19
|
-
* (`schemas/cles-exigees-du-socle.json`, constat A0837)
|
|
19
|
+
* (`schemas/cles-exigees-du-socle.json`, constat A0837), et l'application autonome qui pose le noyau
|
|
20
|
+
* comme lui ; une application prerendue, que le navigateur ne reprend pas, n'en porte aucune (0.11.4) ;
|
|
20
21
|
* - le type des cles engendre (`src/app/i18n/cles.ts`, `starter-web i18n:types`) est a jour : une cle
|
|
21
22
|
* inconnue dans `t()` est alors une erreur de compilation ;
|
|
22
23
|
* - aucun texte ne porte une formulation que l'application interdit
|
|
@@ -258,6 +259,20 @@ export function jugerLesAides(catalogues, routes) {
|
|
|
258
259
|
return defauts;
|
|
259
260
|
}
|
|
260
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Les cles que le noyau (`@starterlib/core` installe) traduit par le catalogue de l'application : celles que l'hote
|
|
264
|
+
* porte, et l'application autonome qui pose le noyau comme lui (constat A0837). Une application PRERENDUE
|
|
265
|
+
* (`starterWeb.prerendu`, 0.11.4) n'execute pas le noyau dans le navigateur, qui ne reprend pas ses pages : aucune.
|
|
266
|
+
*/
|
|
267
|
+
export function clesExigeesDuNoyau(projet) {
|
|
268
|
+
if (projet.role !== 'hote' && projet.role !== 'autonome') return [];
|
|
269
|
+
if (projet.prerendu === true) return [];
|
|
270
|
+
const dossier = dossierInstalle(projet.racine, '@starterlib/core');
|
|
271
|
+
const exigees =
|
|
272
|
+
dossier === null ? null : lireJson(join(dossier, 'schemas', 'cles-exigees-du-socle.json'));
|
|
273
|
+
return exigees?.cles ?? [];
|
|
274
|
+
}
|
|
275
|
+
|
|
261
276
|
function lire(chemin) {
|
|
262
277
|
if (!existsSync(chemin)) return null;
|
|
263
278
|
try {
|
|
@@ -298,12 +313,15 @@ export function rechercherLesFormulations(projet, catalogues) {
|
|
|
298
313
|
|
|
299
314
|
/**
|
|
300
315
|
* Les perimetres de catalogues d'un projet : ceux du manifeste (remote), ceux de
|
|
301
|
-
* `starterWeb.i18n.perimetres` (application autonome, lot 240), `socle` pour l'hote
|
|
316
|
+
* `starterWeb.i18n.perimetres` (application autonome, lot 240), `socle` pour l'hote, plus ceux qu'il
|
|
317
|
+
* declare dans `starterWeb.i18n.perimetres` (un catalogue charge apres le premier ecran : `aplomb`,
|
|
318
|
+
* remarque d'Aplomb, P09-04) ; `socle` reste juge le premier, sans doublon.
|
|
302
319
|
*/
|
|
303
320
|
export function perimetresDuProjet(projet) {
|
|
304
321
|
if (projet.role === 'remote') return projet.source?.i18n?.perimetres ?? [];
|
|
305
322
|
if (projet.role === 'autonome') return projet.paquet.starterWeb?.i18n?.perimetres ?? ['socle'];
|
|
306
|
-
|
|
323
|
+
const declares = projet.paquet?.starterWeb?.i18n?.perimetres ?? [];
|
|
324
|
+
return ['socle', ...declares.filter((perimetre) => perimetre !== 'socle')];
|
|
307
325
|
}
|
|
308
326
|
|
|
309
327
|
export default {
|
|
@@ -348,17 +366,12 @@ export default {
|
|
|
348
366
|
|
|
349
367
|
// L'hote, et l'application autonome qui pose le noyau comme lui, portent les cles que le noyau
|
|
350
368
|
// traduit par son catalogue.
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (!Object.hasOwn(catalogues.socle?.[langue] ?? {}, cle)) {
|
|
358
|
-
defauts.push(
|
|
359
|
-
`socle.${langue}.json : cle "${cle}", exigee par @starterlib/core, absente.`,
|
|
360
|
-
);
|
|
361
|
-
}
|
|
369
|
+
for (const cle of clesExigeesDuNoyau(projet)) {
|
|
370
|
+
for (const langue of LANGUES) {
|
|
371
|
+
if (!Object.hasOwn(catalogues.socle?.[langue] ?? {}, cle)) {
|
|
372
|
+
defauts.push(
|
|
373
|
+
`socle.${langue}.json : cle "${cle}", exigee par @starterlib/core, absente.`,
|
|
374
|
+
);
|
|
362
375
|
}
|
|
363
376
|
}
|
|
364
377
|
}
|
package/src/etapes/securite.mjs
CHANGED
|
@@ -145,9 +145,15 @@ export const LECTURE_DE_COOKIE_DU_CADRE =
|
|
|
145
145
|
* navigateur. De cette forme exacte, chacune est neutralisee UNE fois dans tout le livrable ; tout
|
|
146
146
|
* autre acces au stockage, n'importe ou, reste un defaut (ADR 0014). Constat de la montee d'Aplomb
|
|
147
147
|
* a la 0.11.0, 2026-10-07.
|
|
148
|
+
*
|
|
149
|
+
* Le motif ne part que du DEBUT d'un identifiant (`(?<![\w$])`) : sans cet ancrage, chaque position
|
|
150
|
+
* d'une longue suite de lettres et de chiffres (une donnee en base 64 du livrable) relancait la
|
|
151
|
+
* recherche, en temps carre ; l'etape tenait plus de sept minutes sur l'ecran anonyme d'Aplomb
|
|
152
|
+
* (2026-10-08). Les correspondances sont les memes : la premiere qui reussit part toujours du debut
|
|
153
|
+
* de l'identifiant.
|
|
148
154
|
*/
|
|
149
155
|
export const STOCKAGES_DE_L_ORCHESTRATEUR =
|
|
150
|
-
/([\w$]+)=([\w$]+)=>\(([\w$]+),([\w$]+)\)=>\{let ([\w$]+)=\{get\(\)\{let ([\w$]+)=(localStorage|sessionStorage)\.getItem\(`\$\{\2\}\.\$\{String\(\3\)\}`\);return \6\?JSON\.parse\(\6\):[\w$]+\(\3,\4\)\},set\(([\w$]+)\)\{return \7\.setItem\(`\$\{\2\}\.\$\{String\(\3\)\}`,JSON\.stringify\(\8\)\),\5\},clear\(\)\{return \7\.setItem\(`\$\{\2\}\.\$\{String\(\3\)\}`,JSON\.stringify\(\4\)\),this\}\};return \5\};\1\.type="\7";Object\.freeze\(\1\);/g;
|
|
156
|
+
/(?<![\w$])([\w$]+)=([\w$]+)=>\(([\w$]+),([\w$]+)\)=>\{let ([\w$]+)=\{get\(\)\{let ([\w$]+)=(localStorage|sessionStorage)\.getItem\(`\$\{\2\}\.\$\{String\(\3\)\}`\);return \6\?JSON\.parse\(\6\):[\w$]+\(\3,\4\)\},set\(([\w$]+)\)\{return \7\.setItem\(`\$\{\2\}\.\$\{String\(\3\)\}`,JSON\.stringify\(\8\)\),\5\},clear\(\)\{return \7\.setItem\(`\$\{\2\}\.\$\{String\(\3\)\}`,JSON\.stringify\(\4\)\),this\}\};return \5\};\1\.type="\7";Object\.freeze\(\1\);/g;
|
|
151
157
|
|
|
152
158
|
/** Les stockages qu'une fabrique de l'orchestrateur nomme, dans l'ordre ou le livrable les porte. */
|
|
153
159
|
export function stockagesDeLOrchestrateur(contenu) {
|
package/src/projet.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* des qu'un `manifest.source.json` existe ;
|
|
11
11
|
* - le nom et `pleinePage` : `manifest.source.json` (un remote), `socle` pour l'hote ;
|
|
12
12
|
* - le prefixe : `prefix` du projet d'`angular.json`, ou `starterWeb.prefixe` ;
|
|
13
|
+
* - le prerendu : `starterWeb.prerendu` (une application autonome servie en pages statiques, 0.11.4) ;
|
|
13
14
|
* - le dossier livre : `outputPath` de la cible `@angular/build:application`, plus `browser`.
|
|
14
15
|
*
|
|
15
16
|
* Les options de la ligne de commande (`--nom`, `--prefixe`, `--pleine-page`) les remplacent : ce
|
|
@@ -68,6 +69,13 @@ export function lireLeProjet(racine, options = {}) {
|
|
|
68
69
|
`starterWeb.role : "remote", "hote" ou "autonome", recu "${String(role)}".`,
|
|
69
70
|
);
|
|
70
71
|
}
|
|
72
|
+
// Une application prerendue (0.11.4) : un site statique, une page par adresse, que le navigateur ne reprend pas.
|
|
73
|
+
const prerendu = reglages.prerendu === true;
|
|
74
|
+
if (prerendu && role !== 'autonome') {
|
|
75
|
+
throw new ProjetIllisible(
|
|
76
|
+
`starterWeb.prerendu : reserve a une application autonome, pas a un role "${role}".`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
71
79
|
const source = role === 'remote' ? lireJson(cheminSource) : null;
|
|
72
80
|
if (role === 'remote' && source === null) {
|
|
73
81
|
throw new ProjetIllisible('manifest.source.json absent ou illisible : un remote en a un.');
|
|
@@ -91,6 +99,7 @@ export function lireLeProjet(racine, options = {}) {
|
|
|
91
99
|
nom,
|
|
92
100
|
prefixe: options.prefixe ?? reglages.prefixe ?? projet.prefix ?? null,
|
|
93
101
|
pleinePage: options.pleinePage ?? source?.pleinePage === true,
|
|
102
|
+
prerendu,
|
|
94
103
|
source,
|
|
95
104
|
nomDuProjetAngular: nomDuProjet,
|
|
96
105
|
sortieRelative: sortie.split('\\').join('/'),
|