@starterlib/cli 0.11.3 → 0.11.5
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 +31 -26
- package/package.json +1 -1
- package/src/commun.mjs +13 -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/autonome.mjs +8 -7
- package/src/etapes/budget.mjs +24 -23
- package/src/etapes/i18n.mjs +22 -12
- package/src/navigateurs.mjs +6 -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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Matrice de compatibilite du socle frontal
|
|
2
2
|
|
|
3
|
-
Verifiee le 2026-10-
|
|
3
|
+
Verifiee le 2026-10-11 contre le registre npm (montee au correctif 22.2.2 d'Angular, 0.11.5 ; avant elle, 22.2.1 le
|
|
4
|
+
2026-10-07 et lot 154). Toute montee de version se fait
|
|
4
5
|
**ligne par ligne, dans le meme commit**, et `pnpm versions:check` echoue si le
|
|
5
6
|
`package.json` s'en ecarte (version non exacte, autre que celle de la matrice, paquet Angular absent
|
|
6
7
|
de la matrice, greffon de federation d'une autre majeure.mineure qu'Angular, autre pnpm, Node sous le
|
|
@@ -9,30 +10,32 @@ lit CE document, publie avec lui, et ses essais le comparent a la matrice de `ve
|
|
|
9
10
|
|
|
10
11
|
⚠️ **Trois jours d'anciennete au moins** : `minimumReleaseAge: 4320` (`pnpm-workspace.yaml`) tient hors
|
|
11
12
|
du verrou toute version publiee depuis moins de trois jours (constat A1405). Une version plus recente,
|
|
12
|
-
meme corrective, attend : au 2026-10-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
meme corrective, attend : au 2026-10-11, `angular-eslint` 22.6.0 (2026-10-11), `prettier` 3.9.10 (2026-10-10)
|
|
14
|
+
et `@types/node` 24.19.2 (2026-10-09) n'entrent pas ; la version precedente de chacun reste. Angular monte en
|
|
15
|
+
22.2.2 le 2026-10-11 (correctif publie le 2026-10-08 a 17 h 55 UTC, entre au verrou trois jours plus tard ; son
|
|
16
|
+
outillage, publie le 2026-10-07, le precede). `axe-core` 4.14.0 (2026-10-05) attend `@axe-core/playwright` 4.14 :
|
|
17
|
+
les deux portent le meme numero (section des depots de SPA), et le second n'existe qu'en 4.13.0.
|
|
16
18
|
|
|
17
19
|
## Train `2026.1`
|
|
18
20
|
|
|
19
|
-
| Element | Version | Pourquoi cette version exactement
|
|
20
|
-
| -------------------------------------------------------------------------------------------- | ------------------ |
|
|
21
|
-
| `@angular/core`, `common`, `compiler`, `compiler-cli`, `forms`, `platform-browser`, `router` | `22.2.
|
|
22
|
-
| `@angular/cdk`, `aria` | `22.2.
|
|
23
|
-
| `@angular/elements` | `22.2.
|
|
24
|
-
| `@angular/
|
|
25
|
-
| `@angular
|
|
26
|
-
|
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `playwright`, `@
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
21
|
+
| Element | Version | Pourquoi cette version exactement |
|
|
22
|
+
| -------------------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `@angular/core`, `common`, `compiler`, `compiler-cli`, `forms`, `platform-browser`, `router` | `22.2.2` | Dernier correctif de la ligne 22.2, publie le 2026-10-08 (trois jours revolus le 2026-10-11) |
|
|
24
|
+
| `@angular/cdk`, `aria` | `22.2.2` | 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 |
|
|
25
|
+
| `@angular/elements` | `22.2.2` | 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.2 |
|
|
26
|
+
| `@angular/ssr`, `platform-server` | `22.2.2` | 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 |
|
|
27
|
+
| `@angular/build`, `@angular/cli` | `22.2.2` | ⚠️ **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 |
|
|
28
|
+
| `@angular-devkit/schematics` | `22.2.2` | 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`) |
|
|
29
|
+
| `ng-packagr` | `22.2.4` | Derniere stable de la ligne 22.2 (2026-10-01) |
|
|
30
|
+
| `typescript` | `6.0.3` | ⚠️ **Piege.** La 7.0.2 est publiee, mais `@angular/compiler-cli@22.2.2` exige `>=6.0 <6.1`. Installer la derniere casse la compilation |
|
|
31
|
+
| `node` | `>=24.15.0` | 24.x LTS, declare dans `engines` ; le meme plancher que la construction Maven des SPA (`frontend.node.version`) |
|
|
32
|
+
| `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`) |
|
|
33
|
+
| `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) |
|
|
34
|
+
| `playwright`, `@playwright/test` | `1.64.0` | Le navigateur des essais : un correctif change un rendu mesure au pixel |
|
|
35
|
+
| `eslint` | `10.12.0` | `angular-eslint@22.5.0` accepte les majeurs 9 et 10 |
|
|
36
|
+
| `angular-eslint` | `22.5.0` | Suit le majeur d'Angular |
|
|
37
|
+
| `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 |
|
|
38
|
+
| `jsdom`, `axe-core` | `30.1.2`, `4.13.0` | Le DOM des essais unitaires et le controle d'accessibilite : un correctif change ce qu'un essai voit |
|
|
36
39
|
|
|
37
40
|
## Prochain train : `2026.2` (prevu, non applique)
|
|
38
41
|
|
|
@@ -51,7 +54,7 @@ interfaces), avec `pnpm verify` et `banc-d-assemblage.sh --construire` verts :
|
|
|
51
54
|
| Element | Version | Avertissement |
|
|
52
55
|
| --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
56
|
| `@angular-architects/native-federation` | `22.2.2` | ⚠️ **A epingler a l'exact, jamais par etiquette.** Le 2026-09-12, l'etiquette `latest` du paquet pointait sur la **21.2.6**, publiee apres la 22.1.2. Un `pnpm add` sans version ramene donc un majeur de retard, en silence, et l'echec se manifeste au chargement d'un remote, loin de la cause. Version mesuree au spike du 2026-09-16. Sa majeure.mineure est celle d'Angular (22.2) : `versions:check` refuse un couple disjoint, et les deux montent dans le meme commit (235a) |
|
|
54
|
-
| `orval` | `8.
|
|
57
|
+
| `orval` | `8.41.0` | Client d'API engendre depuis le contrat du service (ADR web 0008) : services `HttpClient` pour les ecritures, `httpResource` pour les lectures, aucun schema zod, donc aucune dependance a l'execution. Derniere stable de plus de trois jours au 2026-10-07 (8.39.0, publiee le 2026-09-30) ; a epingler a l'exact, en dependance de developpement |
|
|
55
58
|
| `es-module-shims` | `2.8.4` | Dependance du greffon, et pas un detail : c'est ELLE qui cree la politique de types de confiance `es-module-shims`. Toute montee doit verifier que le nom de la politique n'a pas change, sinon la page devient blanche sans erreur serveur |
|
|
56
59
|
| `@axe-core/playwright` | `4.13.0` | Les essais d'accessibilite de bout en bout de la coquille (Playwright) ; le meme numero qu'`axe-core`, qui fait le controle : deux moteurs d'une version differente ne signaleraient pas les memes violations |
|
|
57
60
|
|
|
@@ -78,9 +81,11 @@ Ce plancher est plus haut que celui d'Angular 22.2 (« baseline widely available
|
|
|
78
81
|
Edge 119, Firefox 119, Safari et iOS 17) : la construction ne vise pas plus bas que le socle n'en a besoin.
|
|
79
82
|
|
|
80
83
|
**Liste partagee (lot 276, 0.10.14).** La liste browserslist de chaque SPA DERIVE de la ligne du plancher
|
|
81
|
-
ci-dessus, que `@starterlib/cli` lit dans ce document publie avec lui : Chrome, Edge
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
ci-dessus, que `@starterlib/cli` lit dans ce document publie avec lui : Chrome, Edge et Chrome Android au
|
|
85
|
+
plancher de Chrome, Firefox et Firefox Android a celui de Firefox, Safari et iOS a celui de Safari. La WebView
|
|
86
|
+
d'Android, qui suit Chromium, n'a pas de ligne a elle depuis la 0.11.5 : browserslist ne la connait qu'a sa
|
|
87
|
+
derniere version et la politique d'Angular (« baseline widely available ») ne la nomme pas, si bien que chaque
|
|
88
|
+
construction avertissait d'un navigateur hors de sa prise en charge ; la cible produite ne change pas. `starter-web navigateurs` l'ecrit dans `.browserslistrc` ; l'etape `navigateurs` de
|
|
84
89
|
`starter-web verify` refuse une liste absente, plus basse ou plus large. Sous le plancher, l'hote montre
|
|
85
90
|
la page « navigateur non pris en charge » de `@starterlib/core/navigateur` au lieu de demarrer
|
|
86
91
|
(detection par fonction, jamais par l'agent utilisateur).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starterlib/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
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",
|
package/src/commun.mjs
CHANGED
|
@@ -32,9 +32,21 @@ export function lireJson(chemin) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
/** Des octets en kilo-octets, au dixieme. */
|
|
35
|
+
/** Des octets en kilo-octets, au dixieme : pour LIRE une mesure, jamais pour la juger (voir `depasse`). */
|
|
36
36
|
export const ko = (octets) => Math.round((octets / 1024) * 10) / 10;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Vrai quand `octets` depassent `seuilKo` kilo-octets (de 1 024 octets), compare en OCTETS exacts (0.11.5). Juge sur
|
|
40
|
+
* `ko`, un depassement de moins de 52 octets se lisait au seuil et passait : le 2026-10-11, l'ecran de connexion
|
|
41
|
+
* d'Aplomb pesait 166 951 octets pour un seuil de 163 Ko (166 912 octets), lu « 163 Ko br », et l'etape le
|
|
42
|
+
* declarait conforme.
|
|
43
|
+
*/
|
|
44
|
+
export const depasse = (octets, seuilKo) => octets > seuilKo * 1024;
|
|
45
|
+
|
|
46
|
+
/** Une mesure jugee contre son seuil, lisible et exacte : `163 Ko br (166951 o) > 163 Ko br (166912 o)`. */
|
|
47
|
+
export const mesureEtSeuil = (octets, seuilKo) =>
|
|
48
|
+
`${String(ko(octets))} Ko br (${String(octets)} o) > ${String(seuilKo)} Ko br (${String(seuilKo * 1024)} o)`;
|
|
49
|
+
|
|
38
50
|
/**
|
|
39
51
|
* Empreinte courte d'un contenu, dans la forme que le socle Java reconnait comme « hachee » (huit
|
|
40
52
|
* caracteres alphanumeriques apres un tiret) : un actif ainsi nomme est servi immuable un an.
|
|
@@ -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/autonome.mjs
CHANGED
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
* 3. LE POIDS DE SON ECRAN (`starterWeb.ecran` : `{ "entree": "src/app/pages/connexion.ts",
|
|
11
11
|
* "seuilKo": 155 }`) : le chargement initial et la route de l'ecran, tout ce qu'ils importent
|
|
12
12
|
* statiquement, en brotli, lus dans la carte de la construction (`ng build --stats-json`, que
|
|
13
|
-
* `starter-web construire` pose pour ce role)
|
|
14
|
-
* mesure ecrite et datee (ADR 0031).
|
|
13
|
+
* `starter-web construire` pose pour ce role), compares au seuil en octets exacts (0.11.5). Un seuil
|
|
14
|
+
* se baisse ; il ne se leve jamais sans mesure ecrite et datee (ADR 0031).
|
|
15
15
|
*
|
|
16
16
|
* Remplace `verification/application-autonome.mjs` que l'ecran anonyme d'identity tenait seul.
|
|
17
17
|
*/
|
|
18
18
|
import { existsSync, readFileSync } from 'node:fs';
|
|
19
19
|
import { dirname, join, resolve } from 'node:path';
|
|
20
20
|
import { cloture, departDeLaPage, totaux } from '../cloture.mjs';
|
|
21
|
-
import { fichiers, ko, lireJson, relatif } from '../commun.mjs';
|
|
21
|
+
import { depasse, fichiers, ko, lireJson, mesureEtSeuil, relatif } from '../commun.mjs';
|
|
22
22
|
|
|
23
23
|
/** Ce qui trahit un moteur de federation dans un fichier livre. */
|
|
24
24
|
const MOTEUR_DE_FEDERATION =
|
|
@@ -111,12 +111,13 @@ export default {
|
|
|
111
111
|
} else {
|
|
112
112
|
const { initial, ecran: poids } = poidsDeLEcran(projet.sortie, morceau);
|
|
113
113
|
details.push(
|
|
114
|
-
`chargement initial ${String(ko(initial.brotli))} Ko br (${String(initial.fichiers)} fichiers)`,
|
|
115
|
-
`ecran ${String(ecran.entree)} ${String(ko(poids.brotli))} Ko br (${String(poids.
|
|
114
|
+
`chargement initial ${String(ko(initial.brotli))} Ko br (${String(initial.brotli)} o, ${String(initial.fichiers)} fichiers)`,
|
|
115
|
+
`ecran ${String(ecran.entree)} ${String(ko(poids.brotli))} Ko br (${String(poids.brotli)} o, ${String(poids.fichiers)} fichiers, ` +
|
|
116
|
+
`seuil ${String(ecran.seuilKo)} Ko br${typeof ecran.seuilKo === 'number' ? `, ${String(ecran.seuilKo * 1024)} o` : ''})`,
|
|
116
117
|
);
|
|
117
|
-
if (typeof ecran.seuilKo === 'number' &&
|
|
118
|
+
if (typeof ecran.seuilKo === 'number' && depasse(poids.brotli, ecran.seuilKo)) {
|
|
118
119
|
defauts.push(
|
|
119
|
-
`ecran : ${
|
|
120
|
+
`ecran : ${mesureEtSeuil(poids.brotli, ecran.seuilKo)} (un seuil ne se ` +
|
|
120
121
|
'leve jamais sans mesure ecrite et datee, ADR 0031).',
|
|
121
122
|
);
|
|
122
123
|
}
|
package/src/etapes/budget.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
|
18
18
|
import { dirname, join } from 'node:path';
|
|
19
19
|
import { brotliCompressSync, constants } from 'node:zlib';
|
|
20
20
|
import { cloture, departDeLaPage, totaux } from '../cloture.mjs';
|
|
21
|
-
import { fichiers, ko, lireJson, relatif } from '../commun.mjs';
|
|
21
|
+
import { depasse, fichiers, ko, lireJson, mesureEtSeuil, relatif } from '../commun.mjs';
|
|
22
22
|
import { natureDesFichiers } from '../livraison.mjs';
|
|
23
23
|
import { morceauDeLEntree } from './autonome.mjs';
|
|
24
24
|
|
|
@@ -48,6 +48,10 @@ export const BUDGETS_DU_REMOTE = { pageKo: 60, totalKo: 250, catalogueKo: 30 };
|
|
|
48
48
|
* la coquille d'Aplomb avec le cablage du parcours, le contexte de compte, l'etat du bot (en `@defer`) et
|
|
49
49
|
* le bouton d'arret permanent, 311,3 Ko.
|
|
50
50
|
*
|
|
51
|
+
* Depuis la 0.11.5, chaque seuil se juge en OCTETS exacts (`depasse`) : arrondie au dixieme de Ko, une mesure
|
|
52
|
+
* qui le depassait de moins de 52 octets se lisait au seuil et passait. Mesure du 2026-10-11, coquille d'Aplomb
|
|
53
|
+
* sur la 0.11.4 : charge initiale 295 668 octets (seuil 295 936), premier ecran 319 479 (seuil 319 488).
|
|
54
|
+
*
|
|
51
55
|
* ⚠️ Ce ne sont pas la cible du plan SPA §2.8 (170 Ko). L'ecart restant est STRUCTUREL : un paquet
|
|
52
56
|
* partage en singleton part ENTIER, parce que n'importe quel remote peut en avoir besoin. Un seuil
|
|
53
57
|
* se baisse ; il ne se leve jamais sans mesure ecrite et sans date.
|
|
@@ -137,17 +141,15 @@ function budgetDuRemote(projet) {
|
|
|
137
141
|
details.push(
|
|
138
142
|
`page ${nom} : ${String(ko(page.brotli))} Ko br (${String(page.fichiers)} fichiers)`,
|
|
139
143
|
);
|
|
140
|
-
if (
|
|
144
|
+
if (depasse(page.brotli, BUDGETS_DU_REMOTE.pageKo)) {
|
|
141
145
|
defauts.push(
|
|
142
|
-
`page ${nom} (${fichier}) : ${
|
|
146
|
+
`page ${nom} (${fichier}) : ${mesureEtSeuil(page.brotli, BUDGETS_DU_REMOTE.pageKo)}.`,
|
|
143
147
|
);
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
const total = totaux(charges);
|
|
147
|
-
if (
|
|
148
|
-
defauts.push(
|
|
149
|
-
`total charge : ${String(ko(total.brotli))} Ko br > ${String(BUDGETS_DU_REMOTE.totalKo)} Ko.`,
|
|
150
|
-
);
|
|
151
|
+
if (depasse(total.brotli, BUDGETS_DU_REMOTE.totalKo)) {
|
|
152
|
+
defauts.push(`total charge : ${mesureEtSeuil(total.brotli, BUDGETS_DU_REMOTE.totalKo)}.`);
|
|
151
153
|
}
|
|
152
154
|
const dossierI18n = join(projet.sortie, 'i18n');
|
|
153
155
|
const catalogues = existsSync(dossierI18n)
|
|
@@ -156,10 +158,8 @@ function budgetDuRemote(projet) {
|
|
|
156
158
|
for (const catalogue of catalogues) {
|
|
157
159
|
const taille = brotliDe(join(dossierI18n, catalogue));
|
|
158
160
|
details.push(`i18n/${catalogue} : ${String(ko(taille))} Ko br`);
|
|
159
|
-
if (
|
|
160
|
-
defauts.push(
|
|
161
|
-
`i18n/${catalogue} : ${String(ko(taille))} Ko br > ${String(BUDGETS_DU_REMOTE.catalogueKo)} Ko.`,
|
|
162
|
-
);
|
|
161
|
+
if (depasse(taille, BUDGETS_DU_REMOTE.catalogueKo)) {
|
|
162
|
+
defauts.push(`i18n/${catalogue} : ${mesureEtSeuil(taille, BUDGETS_DU_REMOTE.catalogueKo)}.`);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
return {
|
|
@@ -231,30 +231,31 @@ function budgetDeLHote(projet) {
|
|
|
231
231
|
const premierEcran = premierEcranDeLHote(projet.sortie, depart, carte, morceaux);
|
|
232
232
|
const ecran = totaux(premierEcran);
|
|
233
233
|
const details = [
|
|
234
|
-
`charge initiale ${String(ko(initiale.brotli))} Ko br (${String(initiale.
|
|
235
|
-
|
|
234
|
+
`charge initiale ${String(ko(initiale.brotli))} Ko br (${String(initiale.brotli)} o, ` +
|
|
235
|
+
`${String(initiale.fichiers)} fichiers, seuil ${String(BUDGETS_DE_L_HOTE.chargeInitialeKo * 1024)} o)`,
|
|
236
|
+
`premier ecran reel ${String(ko(ecran.brotli))} Ko br (${String(ecran.brotli)} o, ` +
|
|
237
|
+
`${String(ecran.fichiers)} fichiers, seuil ${String(BUDGETS_DE_L_HOTE.premierEcranKo * 1024)} o, ` +
|
|
236
238
|
`${morceaux === null ? 'toutes les pages de route' : 'entrees declarees'})`,
|
|
237
239
|
...[...premierEcran.entries()]
|
|
238
240
|
.sort((a, b) => b[1].brotli - a[1].brotli)
|
|
239
241
|
.slice(0, 5)
|
|
240
242
|
.map(([chemin, taille]) => ` ${String(ko(taille.brotli))} Ko br ${chemin}`),
|
|
241
243
|
];
|
|
242
|
-
if (
|
|
244
|
+
if (depasse(initiale.brotli, BUDGETS_DE_L_HOTE.chargeInitialeKo)) {
|
|
243
245
|
defauts.push(
|
|
244
|
-
`charge initiale : ${
|
|
246
|
+
`charge initiale : ${mesureEtSeuil(initiale.brotli, BUDGETS_DE_L_HOTE.chargeInitialeKo)}.`,
|
|
245
247
|
);
|
|
246
248
|
}
|
|
247
|
-
if (
|
|
249
|
+
if (depasse(ecran.brotli, BUDGETS_DE_L_HOTE.premierEcranKo)) {
|
|
248
250
|
defauts.push(
|
|
249
|
-
`premier ecran : ${
|
|
251
|
+
`premier ecran : ${mesureEtSeuil(ecran.brotli, BUDGETS_DE_L_HOTE.premierEcranKo)} (garde de non-regression).`,
|
|
250
252
|
);
|
|
251
253
|
}
|
|
252
|
-
const avertissements =
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
: [];
|
|
254
|
+
const avertissements = depasse(ecran.brotli, BUDGETS_DE_L_HOTE.cibleDuPlanKo)
|
|
255
|
+
? [
|
|
256
|
+
`cible du plan SPA (section 2.8) : ${String(BUDGETS_DE_L_HOTE.cibleDuPlanKo)} Ko, ecart assume (ADR 0031).`,
|
|
257
|
+
]
|
|
258
|
+
: [];
|
|
258
259
|
return { defauts, avertissements, details, resume: 'premier ecran dans ses seuils.' };
|
|
259
260
|
}
|
|
260
261
|
|
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 {
|
|
@@ -351,17 +366,12 @@ export default {
|
|
|
351
366
|
|
|
352
367
|
// L'hote, et l'application autonome qui pose le noyau comme lui, portent les cles que le noyau
|
|
353
368
|
// traduit par son catalogue.
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (!Object.hasOwn(catalogues.socle?.[langue] ?? {}, cle)) {
|
|
361
|
-
defauts.push(
|
|
362
|
-
`socle.${langue}.json : cle "${cle}", exigee par @starterlib/core, absente.`,
|
|
363
|
-
);
|
|
364
|
-
}
|
|
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
|
+
);
|
|
365
375
|
}
|
|
366
376
|
}
|
|
367
377
|
}
|
package/src/navigateurs.mjs
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Edge suit Chrome ; la WebView d'Android suit Chromium et WKWebView le Safari du systeme (ADR
|
|
11
11
|
* 0043 du socle `api/starter`) : memes planchers.
|
|
12
|
+
*
|
|
13
|
+
* La WebView d'Android n'a pas de ligne a elle (0.11.5) : browserslist ne la connait qu'a sa derniere
|
|
14
|
+
* version (`Android >= 123` donne `android 152`), et la politique de prise en charge d'Angular (« baseline
|
|
15
|
+
* widely available ») ne la nomme pas ; chaque construction d'une SPA avertissait donc d'un navigateur
|
|
16
|
+
* « hors de la prise en charge d'Angular ». Elle suit Chromium : `Chrome` et `ChromeAndroid` au plancher
|
|
17
|
+
* de Chrome disent deja ce que la construction vise pour elle, et la cible produite ne change pas.
|
|
12
18
|
*/
|
|
13
19
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
14
20
|
import { join } from 'node:path';
|
|
@@ -48,7 +54,6 @@ export function requetesDuPlancher({ chrome, safari, firefox }) {
|
|
|
48
54
|
`Safari >= ${safari}`,
|
|
49
55
|
`iOS >= ${safari}`,
|
|
50
56
|
`ChromeAndroid >= ${chrome}`,
|
|
51
|
-
`Android >= ${chrome}`,
|
|
52
57
|
`FirefoxAndroid >= ${firefox}`,
|
|
53
58
|
];
|
|
54
59
|
}
|
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('/'),
|