@starterlib/cli 0.11.1 → 0.11.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.
- package/README.md +8 -2
- package/package.json +1 -1
- package/src/etapes/budget.mjs +67 -8
- package/src/etapes/i18n.mjs +5 -2
- package/src/etapes/securite.mjs +27 -10
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Codes de sortie : `0` conforme ; `1` une etape a echoue ; `2` la commande ne peu
|
|
|
66
66
|
| `manifeste` | les deux | `validerLeManifesteDeRemote` et le schema de `@starterlib/core` INSTALLE ; le nom partout le meme |
|
|
67
67
|
| `federation` | les deux | Partage en singleton strict a plage explicite, Angular et le socle partages, aucun import nu orphelin |
|
|
68
68
|
| `entree` | hote | Aucun paquet partage importe statiquement avant `initFederation` |
|
|
69
|
-
| `budget` | les deux | En brotli. Remote : chaque page (60 Ko), le total (250 Ko), les catalogues. Hote : premier ecran
|
|
69
|
+
| `budget` | les deux | En brotli. Remote : chaque page (60 Ko), le total (250 Ko), les catalogues. Hote : charge initiale et premier ecran (ses entrees, `starterWeb.premierEcran.entrees`, D171) |
|
|
70
70
|
| `autonome` | autonome | Aucune federation livree (`remoteEntry`, carte d'importation, `es-module-shims`) ; versions d'Angular et du socle egales a celles du jumeau (`starterWeb.jumeau`) ; poids de l'ecran (`starterWeb.ecran` : entree et seuil en Ko br), lu dans la carte de la construction (lot 240) |
|
|
71
71
|
| `amorcage` | hote | Aucun pair lourd (CDK, aria, formulaires, surfaces) dans le premier ecran |
|
|
72
72
|
| `securite` | tous | Puits de l'ADR 0029 dans le code de la SPA, politiques Trusted Types exactes, cartes, SVG, meta CSP |
|
|
@@ -89,10 +89,16 @@ par un point d'entree vide dans la configuration `production`).
|
|
|
89
89
|
qu'un `manifest.source.json` existe ;
|
|
90
90
|
- pour une application autonome, dans `starterWeb` : `nom` (le projet d'`angular.json`),
|
|
91
91
|
`i18n.perimetres` (ses catalogues), `jumeau` (le dossier du remote du meme service, dont elle
|
|
92
|
-
partage les versions)
|
|
92
|
+
partage les versions), `ecran` (`{ "entree": "src/app/pages/connexion.ts", "seuilKo": 155 }`) et, s'il en
|
|
93
|
+
cree, `trustedTypes` (les politiques Trusted Types qu'elle cree en plus de celles du BFF, que sa page doit porter
|
|
94
|
+
nommement : `["verification-humaine"]`) ;
|
|
93
95
|
`starter-web construire` n'y ecrit pas de manifeste, et pose `--stats-json` ; `securite` y
|
|
94
96
|
neutralise UNE lecture de cookie du cadre (`DomAdapter.getCookie`, dans `main` faute de morceau
|
|
95
97
|
partage), toute autre restant un defaut ;
|
|
98
|
+
- pour l'hote, dans `starterWeb` : `premierEcran.entrees` (les sources de ses gabarits et de sa page d'arrivee,
|
|
99
|
+
`["src/app/gabarits/coquille.ts", "src/app/pages/accueil.ts"]`), cherchees dans `browser-stats.json` que la
|
|
100
|
+
cible esbuild d'`angular.json` ecrit avec `statsJson: true` ; sans elles, le premier ecran compte toutes les pages
|
|
101
|
+
de route (D171) ;
|
|
96
102
|
- le nom et `pleinePage` : `manifest.source.json` (un remote), `socle` pour l'hote ;
|
|
97
103
|
- les formulations que l'application interdit dans ses textes : `starterWeb.formulationsInterdites`,
|
|
98
104
|
chemin d'un fichier JSON relatif a sa racine, qui liste des `formulations` (un `id`, des `textes`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starterlib/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.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",
|
package/src/etapes/budget.mjs
CHANGED
|
@@ -15,18 +15,19 @@
|
|
|
15
15
|
* partages resolus par la carte d'importation.
|
|
16
16
|
*/
|
|
17
17
|
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
18
|
-
import { join } from 'node:path';
|
|
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
21
|
import { fichiers, ko, lireJson, relatif } from '../commun.mjs';
|
|
22
22
|
import { natureDesFichiers } from '../livraison.mjs';
|
|
23
|
+
import { morceauDeLEntree } from './autonome.mjs';
|
|
23
24
|
|
|
24
25
|
/** Les budgets d'un remote, en Ko brotli (plan SPA §2.8). */
|
|
25
26
|
export const BUDGETS_DU_REMOTE = { pageKo: 60, totalKo: 250, catalogueKo: 30 };
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* Les seuils de l'hote, en Ko brotli (ADR 0031) :
|
|
29
|
-
* charge initiale (ADR web 0005, D168). Leur histoire : ramenes au socle 0.10.0 (releves dans
|
|
29
|
+
* Les seuils de l'hote, en Ko brotli (ADR 0031) : 312 pour le premier ecran reel, mesure sur les
|
|
30
|
+
* entrees que l'hote declare, 289 pour la charge initiale (ADR web 0005, D168 et D171). Leur histoire : ramenes au socle 0.10.0 (releves dans
|
|
30
31
|
* l'ADR 0031) ; la charge initiale passee de 280 a 281 le 2026-09-29, decision du proprietaire :
|
|
31
32
|
* 280,4 Ko mesures avec le socle 0.10.9, dont les controles d'empreinte S05 du lot 272 tournent au
|
|
32
33
|
* demarrage. Puis, le meme jour, 282 et 301 (decision de l'orchestrateur sur autorisation du
|
|
@@ -38,13 +39,20 @@ export const BUDGETS_DU_REMOTE = { pageKo: 60, totalKo: 250, catalogueKo: 30 };
|
|
|
38
39
|
* 287,4 et 308,3 Ko avec sa feuille de marque et la feuille de sa police de titre (+0,44 Ko). Puis
|
|
39
40
|
* 289 et 313 le 2026-10-07 : la coquille d'Aplomb refaite sur ses maquettes (menu en haut, barre
|
|
40
41
|
* inferieure, bandeau du compte simule, pied, accueil de simulation), mesuree contre la 0.11.1
|
|
41
|
-
* construite, avant publication, 288,2 et 312,2 Ko (tronc a 47 410 octets, D168).
|
|
42
|
+
* construite, avant publication, 288,2 et 312,2 Ko (tronc a 47 410 octets, D168). Puis 314 pour le
|
|
43
|
+
* premier ecran le 2026-10-07 (D169) : la meme coquille avec la garde du parcours d'ouverture (lecture
|
|
44
|
+
* du parcours avant l'accueil, client engendre compris), le gabarit pleine page refait et la page de
|
|
45
|
+
* repli du parcours, mesuree contre la 0.11.1 publiee et le tronc de la 0.11.2, 288,8 et 313,7 Ko.
|
|
46
|
+
* Puis 312 le 2026-10-08 (D171, avant la publication de la 0.11.2) : le premier ecran se mesure sur les
|
|
47
|
+
* ENTREES que l'hote declare (ses gabarits, sa page d'arrivee) et non plus sur toutes ses pages de route ;
|
|
48
|
+
* la coquille d'Aplomb avec le cablage du parcours, le contexte de compte, l'etat du bot (en `@defer`) et
|
|
49
|
+
* le bouton d'arret permanent, 311,3 Ko.
|
|
42
50
|
*
|
|
43
51
|
* ⚠️ Ce ne sont pas la cible du plan SPA §2.8 (170 Ko). L'ecart restant est STRUCTUREL : un paquet
|
|
44
52
|
* partage en singleton part ENTIER, parce que n'importe quel remote peut en avoir besoin. Un seuil
|
|
45
53
|
* se baisse ; il ne se leve jamais sans mesure ecrite et sans date.
|
|
46
54
|
*/
|
|
47
|
-
export const BUDGETS_DE_L_HOTE = { premierEcranKo:
|
|
55
|
+
export const BUDGETS_DE_L_HOTE = { premierEcranKo: 312, chargeInitialeKo: 289, cibleDuPlanKo: 170 };
|
|
48
56
|
|
|
49
57
|
/** Les imports DYNAMIQUES d'un module : les pages qu'il charge a la demande. */
|
|
50
58
|
const IMPORT_DYNAMIQUE = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
@@ -161,25 +169,76 @@ function budgetDuRemote(projet) {
|
|
|
161
169
|
};
|
|
162
170
|
}
|
|
163
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Le premier ecran de l'hote (D171) : la charge initiale (cloture de profondeur 1 depuis la page) et les
|
|
174
|
+
* ENTREES que l'hote declare (ses gabarits, sa page d'arrivee), chacune avec ses imports STATIQUES ; ni
|
|
175
|
+
* leurs imports dynamiques (un `@defer`, une page chargee a la demande), ni les autres pages de route.
|
|
176
|
+
*
|
|
177
|
+
* @param morceaux les fichiers des entrees, lus dans la carte de la construction ; `null` sans entrees
|
|
178
|
+
* declarees : la mesure reste alors la cloture de profondeur 2 (toutes les pages de route)
|
|
179
|
+
*/
|
|
180
|
+
export function premierEcranDeLHote(sortie, depart, carte, morceaux) {
|
|
181
|
+
if (morceaux === null) return cloture(sortie, depart, carte, 2);
|
|
182
|
+
const ecran = cloture(sortie, depart, carte, 1);
|
|
183
|
+
for (const [chemin, taille] of cloture(sortie, morceaux, carte, 0)) {
|
|
184
|
+
if (!ecran.has(chemin)) ecran.set(chemin, taille);
|
|
185
|
+
}
|
|
186
|
+
return ecran;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Les fichiers des entrees du premier ecran (`starterWeb.premierEcran.entrees` : les sources, comme
|
|
191
|
+
* `starterWeb.ecran` d'une application autonome), lus dans la carte de la construction
|
|
192
|
+
* (`browser-stats.json`, que la cible esbuild d'`angular.json` ecrit avec `statsJson: true` : le
|
|
193
|
+
* builder de Native Federation refuse `--stats-json`). `null` sans entrees declarees. Une carte absente
|
|
194
|
+
* ou une entree introuvable est un defaut, jamais un repli silencieux sur l'ancienne mesure.
|
|
195
|
+
*/
|
|
196
|
+
function entreesDuPremierEcran(projet, defauts) {
|
|
197
|
+
const entrees = projet.paquet.starterWeb?.premierEcran?.entrees;
|
|
198
|
+
if (!Array.isArray(entrees) || entrees.length === 0) return null;
|
|
199
|
+
const stats = lireJson(join(dirname(projet.sortie), 'browser-stats.json'));
|
|
200
|
+
if (stats === null) {
|
|
201
|
+
defauts.push(
|
|
202
|
+
'starterWeb.premierEcran : browser-stats.json absent (poser statsJson: true sur la cible ' +
|
|
203
|
+
"esbuild d'angular.json, puis construire).",
|
|
204
|
+
);
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
const morceaux = [];
|
|
208
|
+
for (const entree of entrees) {
|
|
209
|
+
const morceau = morceauDeLEntree(stats, entree);
|
|
210
|
+
if (morceau === null) {
|
|
211
|
+
defauts.push(
|
|
212
|
+
`starterWeb.premierEcran : l'entree ${String(entree)} est introuvable dans browser-stats.json.`,
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
morceaux.push(morceau);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return morceaux;
|
|
219
|
+
}
|
|
220
|
+
|
|
164
221
|
function budgetDeLHote(projet) {
|
|
165
222
|
const index = join(projet.sortie, 'index.html');
|
|
166
223
|
if (!existsSync(index)) {
|
|
167
224
|
return { defauts: [`${projet.sortieRelative}/index.html absent : construire d'abord.`] };
|
|
168
225
|
}
|
|
226
|
+
const defauts = [];
|
|
169
227
|
const carte = lireJson(join(projet.sortie, 'importmap.json'))?.imports ?? {};
|
|
170
228
|
const depart = departDeLaPage(readFileSync(index, 'utf8'));
|
|
171
229
|
const initiale = totaux(cloture(projet.sortie, depart, carte, 1));
|
|
172
|
-
const
|
|
230
|
+
const morceaux = entreesDuPremierEcran(projet, defauts);
|
|
231
|
+
const premierEcran = premierEcranDeLHote(projet.sortie, depart, carte, morceaux);
|
|
173
232
|
const ecran = totaux(premierEcran);
|
|
174
233
|
const details = [
|
|
175
234
|
`charge initiale ${String(ko(initiale.brotli))} Ko br (${String(initiale.fichiers)} fichiers)`,
|
|
176
|
-
`premier ecran reel ${String(ko(ecran.brotli))} Ko br (${String(ecran.fichiers)} fichiers
|
|
235
|
+
`premier ecran reel ${String(ko(ecran.brotli))} Ko br (${String(ecran.fichiers)} fichiers, ` +
|
|
236
|
+
`${morceaux === null ? 'toutes les pages de route' : 'entrees declarees'})`,
|
|
177
237
|
...[...premierEcran.entries()]
|
|
178
238
|
.sort((a, b) => b[1].brotli - a[1].brotli)
|
|
179
239
|
.slice(0, 5)
|
|
180
240
|
.map(([chemin, taille]) => ` ${String(ko(taille.brotli))} Ko br ${chemin}`),
|
|
181
241
|
];
|
|
182
|
-
const defauts = [];
|
|
183
242
|
if (ko(initiale.brotli) > BUDGETS_DE_L_HOTE.chargeInitialeKo) {
|
|
184
243
|
defauts.push(
|
|
185
244
|
`charge initiale : ${String(ko(initiale.brotli))} Ko br > ${String(BUDGETS_DE_L_HOTE.chargeInitialeKo)} Ko.`,
|
package/src/etapes/i18n.mjs
CHANGED
|
@@ -298,12 +298,15 @@ export function rechercherLesFormulations(projet, catalogues) {
|
|
|
298
298
|
|
|
299
299
|
/**
|
|
300
300
|
* 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
|
|
301
|
+
* `starterWeb.i18n.perimetres` (application autonome, lot 240), `socle` pour l'hote, plus ceux qu'il
|
|
302
|
+
* declare dans `starterWeb.i18n.perimetres` (un catalogue charge apres le premier ecran : `aplomb`,
|
|
303
|
+
* remarque d'Aplomb, P09-04) ; `socle` reste juge le premier, sans doublon.
|
|
302
304
|
*/
|
|
303
305
|
export function perimetresDuProjet(projet) {
|
|
304
306
|
if (projet.role === 'remote') return projet.source?.i18n?.perimetres ?? [];
|
|
305
307
|
if (projet.role === 'autonome') return projet.paquet.starterWeb?.i18n?.perimetres ?? ['socle'];
|
|
306
|
-
|
|
308
|
+
const declares = projet.paquet?.starterWeb?.i18n?.perimetres ?? [];
|
|
309
|
+
return ['socle', ...declares.filter((perimetre) => perimetre !== 'socle')];
|
|
307
310
|
}
|
|
308
311
|
|
|
309
312
|
export default {
|
package/src/etapes/securite.mjs
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Pour tout fichier livre, cadre compris :
|
|
7
7
|
* - eval, new Function, stockage du navigateur, lecture de cookie ;
|
|
8
|
-
* - politiques Trusted Types comparees a la liste EXACTE du BFF
|
|
8
|
+
* - politiques Trusted Types comparees a la liste EXACTE du BFF, plus celles qu'une application declare
|
|
9
|
+
* (un remote dans `manifest.source.json#trustedTypes`, lot 270 ; une application autonome dans
|
|
10
|
+
* `package.json#starterWeb.trustedTypes`, 0.11.2)
|
|
9
11
|
* (`platform.web.headers.csp.trusted-types-policies`), augmentee de celles que le remote
|
|
10
12
|
* declare (`trustedTypes` de `manifest.source.json`, lot 270) ;
|
|
11
13
|
* - cartes de source (et leurs variantes compressees), script dans un SVG ;
|
|
@@ -143,9 +145,15 @@ export const LECTURE_DE_COOKIE_DU_CADRE =
|
|
|
143
145
|
* navigateur. De cette forme exacte, chacune est neutralisee UNE fois dans tout le livrable ; tout
|
|
144
146
|
* autre acces au stockage, n'importe ou, reste un defaut (ADR 0014). Constat de la montee d'Aplomb
|
|
145
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.
|
|
146
154
|
*/
|
|
147
155
|
export const STOCKAGES_DE_L_ORCHESTRATEUR =
|
|
148
|
-
/([\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;
|
|
149
157
|
|
|
150
158
|
/** Les stockages qu'une fabrique de l'orchestrateur nomme, dans l'ordre ou le livrable les porte. */
|
|
151
159
|
export function stockagesDeLOrchestrateur(contenu) {
|
|
@@ -241,7 +249,8 @@ export function jugerUnFichierLivre(
|
|
|
241
249
|
defauts.push(
|
|
242
250
|
`${nom} : politique Trusted Types hors liste ("${politique}" ; admises : ` +
|
|
243
251
|
`${[...POLITIQUES_ADMISES, ...declarees].join(', ')}). La declarer dans ` +
|
|
244
|
-
'manifest.source.json (trustedTypes)
|
|
252
|
+
`${role === 'autonome' ? 'package.json (starterWeb.trustedTypes)' : 'manifest.source.json (trustedTypes)'} ` +
|
|
253
|
+
'et a la CSP du BFF avant livraison.',
|
|
245
254
|
);
|
|
246
255
|
}
|
|
247
256
|
}
|
|
@@ -271,15 +280,23 @@ export default {
|
|
|
271
280
|
};
|
|
272
281
|
}
|
|
273
282
|
const { partages } = natureDesFichiers(lireJson(join(projet.sortie, 'remoteEntry.json')));
|
|
274
|
-
// Les politiques que
|
|
275
|
-
// CSP n'admet pas
|
|
276
|
-
|
|
283
|
+
// Les politiques que l'application declare : un remote (lot 270), que le BFF retire s'il en exige une
|
|
284
|
+
// que sa CSP n'admet pas ; une application autonome (0.11.2), dont la page doit les porter nommement
|
|
285
|
+
// (la page d'ui-auth d'Aplomb et le widget de verification humaine). L'etape le dit des la construction.
|
|
286
|
+
const declarees =
|
|
287
|
+
projet.role === 'remote'
|
|
288
|
+
? (projet.source?.trustedTypes ?? [])
|
|
289
|
+
: projet.role === 'autonome'
|
|
290
|
+
? (projet.paquet.starterWeb?.trustedTypes ?? [])
|
|
291
|
+
: [];
|
|
277
292
|
const avertissements = declarees
|
|
278
293
|
.filter((politique) => !POLITIQUES_ADMISES.includes(politique))
|
|
279
|
-
.map(
|
|
280
|
-
|
|
281
|
-
`trustedTypes : "${politique}" absente de la liste par defaut du BFF ` +
|
|
282
|
-
|
|
294
|
+
.map((politique) =>
|
|
295
|
+
projet.role === 'autonome'
|
|
296
|
+
? `trustedTypes : "${politique}" absente de la liste par defaut du BFF ` +
|
|
297
|
+
'(PLATFORM_WEB_CSP_TRUSTED_TYPES) : la page de l application doit la porter nommement.'
|
|
298
|
+
: `trustedTypes : "${politique}" absente de la liste par defaut du BFF ` +
|
|
299
|
+
'(PLATFORM_WEB_CSP_TRUSTED_TYPES) : le BFF retire le remote tant qu elle n y est pas.',
|
|
283
300
|
);
|
|
284
301
|
const defauts = [];
|
|
285
302
|
// Application autonome : la lecture de cookie du cadre, une seule fois dans tout le livrable.
|