odoro 1.0.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +94 -0
  2. package/client.d.ts +90 -16
  3. package/dist/build-LZAUDJST.js +5 -0
  4. package/dist/{chunk-34RGOZFA.js → chunk-4D56Z7G3.js} +49 -50
  5. package/dist/{chunk-JMEHF3KN.js → chunk-7ZB5MAM6.js} +4 -4
  6. package/dist/{chunk-LHZGX5ML.js → chunk-OMHEQVIW.js} +295 -160
  7. package/dist/{chunk-3SZIN6VG.js → chunk-P3DJITWE.js} +10 -10
  8. package/dist/{chunk-ZVL7EXJO.js → chunk-PRWVZ2SM.js} +22 -14
  9. package/dist/chunk-Q2KCMTB5.js +741 -0
  10. package/dist/chunk-QOVBLN7A.js +241 -0
  11. package/dist/chunk-WWTDEV4Y.js +604 -0
  12. package/dist/cli.d.ts +12 -13
  13. package/dist/cli.js +84 -63
  14. package/dist/{commands-4JRBD55Z.js → commands-EUGVLQWL.js} +44 -45
  15. package/dist/{commands-AHMXBWQQ.js → commands-GYA7NTCX.js} +124 -124
  16. package/dist/{create-CYVSVDAD.js → create-QX5LEWMS.js} +158 -154
  17. package/dist/index.d.ts +603 -151
  18. package/dist/index.js +6 -6
  19. package/dist/{package-EIDLKAA6.js → package-T4Z7OBOH.js} +5 -5
  20. package/dist/preview-P4WY5GEY.js +4 -0
  21. package/dist/registry/index.d.ts +75 -76
  22. package/dist/registry/index.js +1 -1
  23. package/dist/{server-4UGN3SFS.js → server-2WP562EV.js} +3 -3
  24. package/package.json +5 -5
  25. package/templates/react-ts/README.md +71 -35
  26. package/templates/react-ts/_env.example +9 -0
  27. package/templates/react-ts/_variants/with-engine/src/background.tsx +118 -0
  28. package/templates/react-ts/_variants/without-libs/src/App.tsx +380 -0
  29. package/templates/react-ts/_variants/without-libs/src/background.tsx +24 -0
  30. package/templates/react-ts/_variants/without-libs/src/entry-server.tsx +39 -0
  31. package/templates/react-ts/_variants/without-libs/src/main.tsx +26 -0
  32. package/templates/{react-ts-server/_variantes/sans-libs/client → react-ts/_variants/without-libs}/src/styles.css +137 -137
  33. package/templates/react-ts/{_variantes/sans-routeur → _variants/without-router}/src/App.tsx +141 -141
  34. package/templates/react-ts/_variants/without-router/src/entry-server.tsx +39 -0
  35. package/templates/react-ts/index.html +4 -4
  36. package/templates/react-ts/odoro.config.ts +5 -0
  37. package/templates/react-ts/src/App.tsx +219 -209
  38. package/templates/react-ts/src/background.tsx +66 -0
  39. package/templates/react-ts/src/entry-server.tsx +85 -0
  40. package/templates/react-ts/src/main.tsx +13 -4
  41. package/templates/react-ts/src/router.tsx +65 -45
  42. package/templates/react-ts/src/styles.css +5 -5
  43. package/templates/react-ts-server/Dockerfile +9 -9
  44. package/templates/react-ts-server/README.md +97 -45
  45. package/templates/react-ts-server/_env.example +31 -33
  46. package/templates/react-ts-server/_variants/with-engine/client/src/background.tsx +118 -0
  47. package/templates/react-ts-server/_variants/without-libs/client/src/App.tsx +380 -0
  48. package/templates/react-ts-server/_variants/without-libs/client/src/background.tsx +24 -0
  49. package/templates/react-ts-server/_variants/without-libs/client/src/entry-server.tsx +39 -0
  50. package/templates/react-ts-server/_variants/without-libs/client/src/main.tsx +26 -0
  51. package/templates/{react-ts/_variantes/sans-libs → react-ts-server/_variants/without-libs/client}/src/styles.css +137 -137
  52. package/templates/react-ts-server/{_variantes/sans-routeur → _variants/without-router}/client/src/App.tsx +141 -141
  53. package/templates/react-ts-server/_variants/without-router/client/src/entry-server.tsx +39 -0
  54. package/templates/react-ts-server/client/index.html +4 -4
  55. package/templates/react-ts-server/client/src/App.tsx +228 -210
  56. package/templates/react-ts-server/client/src/account.tsx +267 -0
  57. package/templates/react-ts-server/client/src/auth.tsx +139 -0
  58. package/templates/react-ts-server/client/src/background.tsx +66 -0
  59. package/templates/react-ts-server/client/src/entry-server.tsx +92 -0
  60. package/templates/react-ts-server/client/src/main.tsx +17 -5
  61. package/templates/react-ts-server/client/src/router.tsx +77 -45
  62. package/templates/react-ts-server/client/src/styles.css +5 -5
  63. package/templates/react-ts-server/odoro.config.ts +7 -4
  64. package/templates/react-ts-server/package.json +2 -0
  65. package/templates/react-ts-server/scripts/dev.mjs +14 -14
  66. package/templates/react-ts-server/server/src/main.ts +119 -45
  67. package/templates/react-ts-server/server/src/modules/auth/index.ts +238 -0
  68. package/templates/react-ts-server/server/src/modules/auth/password.ts +122 -0
  69. package/templates/react-ts-server/server/src/modules/auth/store.ts +193 -0
  70. package/templates/react-ts-server/server/src/modules/health/index.ts +45 -46
  71. package/dist/build-JFQHODAT.js +0 -5
  72. package/dist/chunk-22KJTV2R.js +0 -380
  73. package/dist/chunk-DL3NPC4H.js +0 -113
  74. package/dist/chunk-TQUJ3MFS.js +0 -268
  75. package/dist/preview-7DKEAQOQ.js +0 -4
  76. package/templates/react-ts/_variantes/avec-moteur/src/fond.tsx +0 -118
  77. package/templates/react-ts/_variantes/sans-libs/src/App.tsx +0 -382
  78. package/templates/react-ts/_variantes/sans-libs/src/fond.tsx +0 -23
  79. package/templates/react-ts/_variantes/sans-libs/src/main.tsx +0 -17
  80. package/templates/react-ts/src/fond.tsx +0 -66
  81. package/templates/react-ts-server/_variantes/avec-moteur/client/src/fond.tsx +0 -118
  82. package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +0 -382
  83. package/templates/react-ts-server/_variantes/sans-libs/client/src/fond.tsx +0 -23
  84. package/templates/react-ts-server/_variantes/sans-libs/client/src/main.tsx +0 -17
  85. package/templates/react-ts-server/client/src/fond.tsx +0 -66
@@ -1,40 +1,39 @@
1
1
  /**
2
- * Module de sante — et exemple de ce a quoi ressemble un module.
2
+ * Health module — and an example of what a module looks like.
3
3
  *
4
- * C'est le seul module de ce template, et il est ecrit ici plutot que fourni
5
- * par `@odoro-cli/server` pour une raison : ecrire un module est la premiere chose
6
- * qu'on fait sur ce socle, et un exemple qu'on peut ouvrir vaut mieux qu'une
7
- * page de documentation.
4
+ * It is the only module of this template, and it is written here rather than
5
+ * provided by `@odoro-cli/server` for a reason: writing a module is the first
6
+ * thing you do on this foundation, and an example you can open beats a page of
7
+ * documentation.
8
8
  *
9
- * ## Un module est une fonction de ce dont il a besoin
9
+ * ## A module is a function of what it needs
10
10
  *
11
- * Celui-ci recoit la configuration en parametre plutot que de lire
12
- * `process.env`. C'est la regle du socle : l'environnement est valide une fois,
13
- * au demarrage, et tout le reste consomme le resultat. Une lecture directe
14
- * echappe a cette validation et se manifeste a la centieme requete.
11
+ * This one receives the configuration as a parameter rather than reading
12
+ * `process.env`. That is the rule of the foundation: the environment is
13
+ * validated once, at startup, and everything else consumes the result. A
14
+ * direct read escapes that validation and shows up on the hundredth request.
15
15
  *
16
- * ## Deux points de controle, et pourquoi ils different
16
+ * ## Two health endpoints, and why they differ
17
17
  *
18
- * `/api/health` repond que **le processus vit**. Il ne teste rien d'autre, et
19
- * doit repondre meme quand tout le reste est casse : c'est ce qu'un
20
- * orchestrateur interroge pour decider s'il faut redemarrer le conteneur. Le
21
- * faire dependre de la base ferait redemarrer un serveur parfaitement sain
22
- * chaque fois que la base hoquette — et un redemarrage ne repare pas une base.
18
+ * `/api/health` answers that **the process is alive**. It tests nothing else,
19
+ * and must answer even when everything else is broken: that is what an
20
+ * orchestrator asks to decide whether to restart the container. Making it
21
+ * depend on the database would restart a perfectly healthy server every time
22
+ * the database hiccups — and a restart does not repair a database.
23
23
  *
24
- * `/api/ready` repond que **le service peut travailler**, et rend 503 tant
25
- * qu'il manque quelque chose : c'est ce qu'un repartiteur interroge pour
26
- * decider s'il peut envoyer du trafic.
24
+ * `/api/ready` answers that **the service can do its work**, and returns 503
25
+ * for as long as something is missing: that is what a load balancer asks to
26
+ * decide whether it can send traffic.
27
27
  *
28
- * Confondre les deux donne l'un des deux defauts : un service qui redemarre en
29
- * boucle pendant un incident de base, ou un repartiteur qui envoie du trafic a
30
- * un service incapable de repondre.
28
+ * Confusing the two gives one of two faults: a service restarting in a loop
29
+ * during a database incident, or a load balancer sending traffic to a service
30
+ * that cannot answer.
31
31
  *
32
- * ## Pourquoi `/ready` echoue au premier demarrage
32
+ * ## Why `/ready` fails on the first start
33
33
  *
34
- * Un projet fraichement echafaude n'a pas encore de `DATABASE_URL`. Le client
35
- * demarre, l'interface s'affiche, et `/api/ready` dit ce qui manque. On voit
36
- * donc quelque chose des la premiere minute, et on sait exactement ce qu'il
37
- * reste a faire.
34
+ * A freshly scaffolded project has no `DATABASE_URL` yet. The client starts,
35
+ * the interface shows, and `/api/ready` says what is missing. So you see
36
+ * something in the first minute, and you know exactly what is left to do.
38
37
  *
39
38
  * @module
40
39
  */
@@ -47,20 +46,20 @@ import {
47
46
  } from '@odoro-cli/server'
48
47
  import { z } from 'zod'
49
48
 
50
- /** Ce que rend le controle de vie. */
49
+ /** What the liveness check returns. */
51
50
  const liveness = z.object({
52
51
  status: z.literal('ok'),
53
52
  environment: z.string(),
54
53
  uptime: z.number(),
55
54
  })
56
55
 
57
- /** Ce que rend le controle de disponibilite, quand tout repond. */
56
+ /** What the readiness check returns, when everything answers. */
58
57
  const readiness = z.object({
59
58
  ready: z.literal(true),
60
59
  dependencies: z.array(z.object({ name: z.string(), detail: z.string() })),
61
60
  })
62
61
 
63
- /** Une dependance et son etat. */
62
+ /** A dependency and its state. */
64
63
  interface Dependency {
65
64
  readonly name: string
66
65
  readonly ready: boolean
@@ -68,13 +67,13 @@ interface Dependency {
68
67
  }
69
68
 
70
69
  /**
71
- * Etat des dependances.
70
+ * State of the dependencies.
72
71
  *
73
- * La base n'est pas encore interrogee — le socle n'a pas sa couche de
74
- * persistance. Ce qui est verifie ici est sa **configuration**, ce qui suffit
75
- * a distinguer un projet qui n'a jamais recu d'URL d'un projet configure.
76
- * Quand la persistance arrivera, c'est cette fonction qui apprendra a ouvrir
77
- * une connexion, et rien d'autre ne changera.
72
+ * The database is not queried yet — the foundation has no persistence layer.
73
+ * What is checked here is its **configuration**, which is enough to tell a
74
+ * project that never received a URL from a configured one. When persistence
75
+ * arrives, this function is the one that will learn to open a connection, and
76
+ * nothing else will change.
78
77
  */
79
78
  function inspect(config: KernelConfig): readonly Dependency[] {
80
79
  const url = config.DATABASE_URL.trim()
@@ -84,14 +83,14 @@ function inspect(config: KernelConfig): readonly Dependency[] {
84
83
  ready: url.length > 0,
85
84
  detail:
86
85
  url.length > 0
87
- ? 'URL configuree'
88
- : 'DATABASE_URL absente — voir .env.example, ou lancer `odoro db:create`',
86
+ ? 'URL configured'
87
+ : 'DATABASE_URL missing — see .env.example, or run `odoro db:create`',
89
88
  },
90
89
  ]
91
90
  }
92
91
 
93
92
  /**
94
- * Construit le module.
93
+ * Builds the module.
95
94
  *
96
95
  * @example
97
96
  * createApp({ modules: [createHealthModule(config)], … })
@@ -105,7 +104,7 @@ export function createHealthModule(config: KernelConfig) {
105
104
  method: 'GET',
106
105
  path: '/api/health',
107
106
  auth: 'public',
108
- summary: 'Le processus repond.',
107
+ summary: 'The process answers.',
109
108
  output: liveness,
110
109
  handler: () => ({
111
110
  status: 'ok' as const,
@@ -119,17 +118,17 @@ export function createHealthModule(config: KernelConfig) {
119
118
  method: 'GET',
120
119
  path: '/api/ready',
121
120
  auth: 'public',
122
- summary: 'Les dependances repondent.',
121
+ summary: 'The dependencies answer.',
123
122
  output: readiness,
124
123
  handler: () => {
125
124
  const dependencies = inspect(config)
126
- const manquantes = dependencies.filter((d) => !d.ready)
125
+ const missing = dependencies.filter((d) => !d.ready)
127
126
 
128
- if (manquantes.length > 0) {
129
- // 503 et non 500 : la demande etait valide, c'est le service qui
130
- // ne peut pas encore y repondre.
127
+ if (missing.length > 0) {
128
+ // 503 and not 500: the request was valid, it is the service that
129
+ // cannot answer it yet.
131
130
  throw new ServiceUnavailableError(
132
- manquantes.map((d) => `${d.name} — ${d.detail}`).join(' ; '),
131
+ missing.map((d) => `${d.name} — ${d.detail}`).join(' ; '),
133
132
  )
134
133
  }
135
134
 
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- export { buildProject, reportBuild } from './chunk-22KJTV2R.js';
3
- import './chunk-LHZGX5ML.js';
4
- import './chunk-TQUJ3MFS.js';
5
- import './chunk-JMEHF3KN.js';
@@ -1,380 +0,0 @@
1
- #!/usr/bin/env node
2
- import { extractEntries, fournisseurDe } from './chunk-LHZGX5ML.js';
3
- import { applyAlias, isBareSpecifier } from './chunk-TQUJ3MFS.js';
4
- import { info, colors, size, success, duration } from './chunk-JMEHF3KN.js';
5
- import { existsSync } from 'fs';
6
- import { readFile, rm, mkdir, writeFile, cp, stat } from 'fs/promises';
7
- import { join, basename, resolve, relative, sep } from 'path';
8
- import { build } from 'esbuild';
9
-
10
- // src/build/elaguer.ts
11
- var GROUPES = /* @__PURE__ */ new Set(["media", "supports", "layer", "container", "scope"]);
12
- function analyser(css) {
13
- const noeuds = [];
14
- let i = 0;
15
- let debut = 0;
16
- function avancer() {
17
- const c = css[i];
18
- if (c === "\\") {
19
- i += 2;
20
- return;
21
- }
22
- if (c === "/" && css[i + 1] === "*") {
23
- const fin = css.indexOf("*/", i + 2);
24
- i = fin === -1 ? css.length : fin + 2;
25
- return;
26
- }
27
- if (c === '"' || c === "'") {
28
- i += 1;
29
- while (i < css.length && css[i] !== c) {
30
- i += css[i] === "\\" ? 2 : 1;
31
- }
32
- i += 1;
33
- return;
34
- }
35
- i += 1;
36
- }
37
- while (i < css.length) {
38
- const c = css[i];
39
- if (c === "{") {
40
- const prelude = css.slice(debut, i).trim();
41
- let profondeur = 1;
42
- i += 1;
43
- const debutCorps = i;
44
- while (i < css.length && profondeur > 0) {
45
- const d = css[i];
46
- if (d === "{") {
47
- profondeur += 1;
48
- i += 1;
49
- } else if (d === "}") {
50
- profondeur -= 1;
51
- i += 1;
52
- } else {
53
- avancer();
54
- }
55
- }
56
- const corps = css.slice(debutCorps, i - 1);
57
- if (prelude.startsWith("@")) {
58
- const nom = /^@([a-zA-Z-]+)/.exec(prelude)?.[1] ?? "";
59
- noeuds.push(
60
- GROUPES.has(nom) ? { sorte: "groupe", prelude, enfants: analyser(corps) } : (
61
- // `@keyframes`, `@font-face`, `@property` : recopies tels quels.
62
- { sorte: "brut", texte: `${prelude}{${corps}}` }
63
- )
64
- );
65
- } else if (prelude.length > 0) {
66
- noeuds.push({ sorte: "regle", selecteur: prelude, corps });
67
- }
68
- debut = i;
69
- continue;
70
- }
71
- if (c === ";" && css.slice(debut, i).trim().startsWith("@")) {
72
- noeuds.push({ sorte: "brut", texte: `${css.slice(debut, i).trim()};` });
73
- i += 1;
74
- debut = i;
75
- continue;
76
- }
77
- avancer();
78
- }
79
- return noeuds;
80
- }
81
- function ecrire(noeuds) {
82
- return noeuds.map((n) => {
83
- if (n.sorte === "brut") return n.texte;
84
- if (n.sorte === "regle") return `${n.selecteur}{${n.corps}}`;
85
- return `${n.prelude}{${ecrire(n.enfants)}}`;
86
- }).join("\n");
87
- }
88
- function classesDe(selecteur) {
89
- const classes = [];
90
- const motif = /(?<!\\)\.((?:\\.|[\w-])+)/g;
91
- for (const trouve of selecteur.matchAll(motif)) {
92
- classes.push(trouve[1].replaceAll(/\\(.)/g, "$1"));
93
- }
94
- return classes;
95
- }
96
- function motsDe(texte) {
97
- const mots = /* @__PURE__ */ new Set();
98
- const motif = /[A-Za-z0-9_][A-Za-z0-9_:/.[\]%@-]*/g;
99
- for (const trouve of texte.matchAll(motif)) mots.add(trouve[0]);
100
- return mots;
101
- }
102
- function estUtilitaire(classe, prefixe) {
103
- const nue = classe.slice(classe.lastIndexOf(":") + 1);
104
- return nue.startsWith(prefixe);
105
- }
106
- function gardee(classe, employes, sauvegarde) {
107
- if (employes.has(classe)) return true;
108
- for (const regle of sauvegarde) {
109
- if (typeof regle === "string" ? regle === classe : regle.test(classe)) return true;
110
- }
111
- return false;
112
- }
113
- function elaguer(css, sources, options = {}) {
114
- const prefixe = options.prefixe ?? "o-";
115
- const sauvegarde = options.sauvegarde ?? [];
116
- const employes = /* @__PURE__ */ new Set();
117
- for (const source of sources) {
118
- for (const mot of motsDe(source)) employes.add(mot);
119
- }
120
- const vues = /* @__PURE__ */ new Set();
121
- const survivantes = /* @__PURE__ */ new Set();
122
- function filtrerSelecteur(selecteur) {
123
- const retenus = decouperSelecteurs(selecteur).filter((un) => {
124
- const utilitaires = classesDe(un).filter((c) => estUtilitaire(c, prefixe));
125
- if (utilitaires.length === 0) return true;
126
- for (const classe of utilitaires) vues.add(classe);
127
- const survit = utilitaires.every((c) => gardee(c, employes, sauvegarde));
128
- if (survit) for (const classe of utilitaires) survivantes.add(classe);
129
- return survit;
130
- });
131
- return retenus.length === 0 ? void 0 : retenus.join(",");
132
- }
133
- function filtrer(noeuds) {
134
- const retenus = [];
135
- for (const noeud of noeuds) {
136
- if (noeud.sorte === "brut") {
137
- retenus.push(noeud);
138
- continue;
139
- }
140
- if (noeud.sorte === "regle") {
141
- const selecteur = filtrerSelecteur(noeud.selecteur);
142
- if (selecteur !== void 0) retenus.push({ ...noeud, selecteur });
143
- continue;
144
- }
145
- const enfants = filtrer(noeud.enfants);
146
- if (enfants.length > 0) retenus.push({ ...noeud, enfants });
147
- }
148
- return retenus;
149
- }
150
- const elaguee = ecrire(filtrer(analyser(css)));
151
- return {
152
- css: elaguee,
153
- octetsAvant: Buffer.byteLength(css),
154
- octetsApres: Buffer.byteLength(elaguee),
155
- gardees: survivantes.size,
156
- retirees: vues.size - survivantes.size
157
- };
158
- }
159
- function decouperSelecteurs(selecteur) {
160
- const parts = [];
161
- let profondeur = 0;
162
- let debut = 0;
163
- let i = 0;
164
- while (i < selecteur.length) {
165
- const c = selecteur[i];
166
- if (c === "\\") {
167
- i += 2;
168
- continue;
169
- }
170
- if (c === '"' || c === "'") {
171
- i += 1;
172
- while (i < selecteur.length && selecteur[i] !== c) {
173
- i += selecteur[i] === "\\" ? 2 : 1;
174
- }
175
- i += 1;
176
- continue;
177
- }
178
- if (c === "(" || c === "[") profondeur += 1;
179
- else if (c === ")" || c === "]") profondeur -= 1;
180
- else if (c === "," && profondeur === 0) {
181
- parts.push(selecteur.slice(debut, i).trim());
182
- debut = i + 1;
183
- }
184
- i += 1;
185
- }
186
- parts.push(selecteur.slice(debut).trim());
187
- return parts.filter((p) => p.length > 0);
188
- }
189
-
190
- // src/build/build.ts
191
- function toPosix(path) {
192
- return path.split(sep).join("/");
193
- }
194
- function buildEnv(config) {
195
- const env = {
196
- MODE: "production",
197
- DEV: false,
198
- PROD: true,
199
- BASE_URL: config.base
200
- };
201
- for (const [key, value] of Object.entries(process.env)) {
202
- if (key.startsWith(config.envPrefix) && value !== void 0) env[key] = value;
203
- }
204
- return env;
205
- }
206
- function outputsForEntry(result, entry, outDir, root) {
207
- const toRelative = (file) => toPosix(relative(outDir, resolve(root, file)));
208
- for (const [file, meta] of Object.entries(result.metafile.outputs)) {
209
- if (meta.entryPoint === void 0) continue;
210
- if (resolve(root, meta.entryPoint) !== resolve(entry)) continue;
211
- if (!file.endsWith(".js")) continue;
212
- return {
213
- script: toRelative(file),
214
- styles: meta.cssBundle === void 0 ? [] : [toRelative(meta.cssBundle)]
215
- };
216
- }
217
- return { script: void 0, styles: [] };
218
- }
219
- async function taillerFeuilles(result, config, html) {
220
- const produits = Object.keys(result.metafile.outputs).map(
221
- (f) => resolve(config.root, f)
222
- );
223
- const feuilles = produits.filter((f) => f.endsWith(".css"));
224
- if (feuilles.length === 0) return;
225
- const sources = [html];
226
- for (const fichier of produits) {
227
- if (fichier.endsWith(".js")) sources.push(await readFile(fichier, "utf8"));
228
- }
229
- const fournisseur = await fournisseurDe(config.root);
230
- const employes = /* @__PURE__ */ new Set();
231
- if (fournisseur !== void 0) {
232
- for (const source of sources) for (const mot of motsDe(source)) employes.add(mot);
233
- for (const garde of config.build.safelist) {
234
- if (typeof garde === "string") employes.add(garde);
235
- }
236
- }
237
- for (const feuille of feuilles) {
238
- const avant = await readFile(feuille, "utf8");
239
- if (fournisseur === void 0) {
240
- const rapport = elaguer(avant, sources, { sauvegarde: config.build.safelist });
241
- await writeFile(feuille, rapport.css, "utf8");
242
- info(
243
- ` ${colors.dim("elagage")} ${basename(feuille)} ${size(rapport.octetsAvant)} \u2192 ${size(rapport.octetsApres)} ${colors.dim(`${String(rapport.gardees)} classes gardees`)}`
244
- );
245
- continue;
246
- }
247
- const socle = elaguer(avant, [], {}).css;
248
- const utilitaires = fournisseur.renderUtilitairesPour(employes);
249
- const apres = `${socle}
250
- ${utilitaires}`;
251
- await writeFile(feuille, apres, "utf8");
252
- info(
253
- ` ${colors.dim("generation")} ${basename(feuille)} ${size(Buffer.byteLength(avant))} \u2192 ${size(Buffer.byteLength(apres))}`
254
- );
255
- }
256
- }
257
- async function buildProject(config) {
258
- const started = Date.now();
259
- const indexFile = join(config.root, "index.html");
260
- if (!existsSync(indexFile)) {
261
- throw new Error(`[odoro] Aucun "index.html" a la racine du projet (${config.root}).`);
262
- }
263
- const html = await readFile(indexFile, "utf8");
264
- const entries = extractEntries(html, config.root);
265
- if (entries.length === 0) {
266
- throw new Error(
267
- `[odoro] Aucun point d'entree : "index.html" doit contenir un <script type="module" src="...">.`
268
- );
269
- }
270
- await rm(config.outDir, { recursive: true, force: true });
271
- await mkdir(config.outDir, { recursive: true });
272
- const result = await build({
273
- entryPoints: [...entries],
274
- bundle: true,
275
- format: "esm",
276
- platform: "browser",
277
- target: config.build.target,
278
- splitting: true,
279
- minify: config.build.minify,
280
- sourcemap: config.build.sourcemap,
281
- metafile: true,
282
- outdir: join(config.outDir, "assets"),
283
- absWorkingDir: config.root,
284
- publicPath: `${config.base}assets`,
285
- // Les empreintes rendent les fichiers immuables : ils peuvent etre mis en
286
- // cache indefiniment, et un deploiement n'invalide que ce qui a change.
287
- entryNames: "[name]-[hash]",
288
- chunkNames: "chunk-[hash]",
289
- assetNames: "[name]-[hash]",
290
- jsx: "automatic",
291
- logLevel: "silent",
292
- define: {
293
- "import.meta.env": JSON.stringify(buildEnv(config)),
294
- "process.env.NODE_ENV": JSON.stringify("production"),
295
- ...config.define
296
- },
297
- loader: {
298
- ".svg": "file",
299
- ".png": "file",
300
- ".jpg": "file",
301
- ".jpeg": "file",
302
- ".gif": "file",
303
- ".webp": "file",
304
- ".avif": "file",
305
- ".ico": "file",
306
- ".woff": "file",
307
- ".woff2": "file",
308
- ".mp4": "file",
309
- ".webm": "file"
310
- },
311
- plugins: [
312
- {
313
- name: "odoro-alias",
314
- setup(builder) {
315
- builder.onResolve({ filter: /.*/ }, (args) => {
316
- if (args.kind === "entry-point") return null;
317
- const aliased = applyAlias(args.path, config);
318
- if (aliased === args.path || isBareSpecifier(aliased)) return null;
319
- return builder.resolve(aliased, {
320
- kind: "import-statement",
321
- resolveDir: args.resolveDir,
322
- importer: args.importer,
323
- pluginData: { aliased: true }
324
- });
325
- });
326
- }
327
- }
328
- ]
329
- });
330
- if (config.build.elaguer) await taillerFeuilles(result, config, html);
331
- let output = html;
332
- for (const entry of entries) {
333
- const { script, styles } = outputsForEntry(
334
- result,
335
- entry,
336
- join(config.outDir, "assets"),
337
- config.root
338
- );
339
- if (script === void 0) continue;
340
- const original = new RegExp(
341
- `<script[^>]*type=["']module["'][^>]*src=["'][^"']*${basename(entry)}["'][^>]*></script>`,
342
- "i"
343
- );
344
- const tags = [
345
- ...styles.map(
346
- (style) => `<link rel="stylesheet" href="${config.base}assets/${style}">`
347
- ),
348
- `<script type="module" crossorigin src="${config.base}assets/${script}"></script>`
349
- ].join("\n ");
350
- output = output.replace(original, tags);
351
- }
352
- await writeFile(join(config.outDir, "index.html"), output, "utf8");
353
- if (existsSync(config.publicDir)) {
354
- await cp(config.publicDir, config.outDir, { recursive: true });
355
- }
356
- const files = (await Promise.all(
357
- Object.keys(result.metafile.outputs).map(async (file) => {
358
- const chemin = resolve(config.root, file);
359
- return {
360
- path: toPosix(relative(config.outDir, chemin)),
361
- bytes: (await stat(chemin)).size
362
- };
363
- })
364
- )).sort((a, b) => b.bytes - a.bytes);
365
- return { outDir: config.outDir, files, elapsed: Date.now() - started };
366
- }
367
- function reportBuild(output, root) {
368
- const directory = toPosix(relative(root, output.outDir)) || ".";
369
- let total = 0;
370
- for (const file of output.files) {
371
- total += file.bytes;
372
- if (file.path.endsWith(".map")) continue;
373
- info(
374
- ` ${colors.dim(`${directory}/`)}${file.path} ${colors.dim(size(file.bytes))}`
375
- );
376
- }
377
- success(`compile en ${duration(output.elapsed)} \u2014 ${size(total)} au total`);
378
- }
379
-
380
- export { buildProject, reportBuild };
@@ -1,113 +0,0 @@
1
- #!/usr/bin/env node
2
- import { guessAliasPaths } from './chunk-T6RLHSCW.js';
3
- import { mkdir, writeFile, rm } from 'fs/promises';
4
- import { existsSync } from 'fs';
5
- import { resolve, join } from 'path';
6
- import { pathToFileURL } from 'url';
7
- import { build } from 'esbuild';
8
-
9
- function defineConfig(config) {
10
- return config;
11
- }
12
- var CONFIG_FILES = ["odoro.config.ts", "odoro.config.js", "odoro.config.mjs"];
13
- async function importConfigFile(file, root) {
14
- const directory = join(root, "node_modules", ".odoro");
15
- const output = join(directory, `config.${Date.now().toString(36)}.mjs`);
16
- await mkdir(directory, { recursive: true });
17
- try {
18
- const result = await build({
19
- entryPoints: [file],
20
- bundle: true,
21
- format: "esm",
22
- platform: "node",
23
- target: "node20",
24
- write: false,
25
- // Seul le code du projet est inline ; ses dependances restent externes,
26
- // sans quoi il faudrait resoudre tout node_modules pour lire trois lignes.
27
- packages: "external",
28
- // `packages: 'external'` ne suffit pas : un `baseUrl` dans le
29
- // `tsconfig.json` fait chercher les imports nus depuis la racine du
30
- // projet, ou `odoro.json` — le fichier du registre — est trouve avant le
31
- // paquet. L'import cesse alors d'etre un import de paquet, la regle
32
- // ci-dessus ne s'applique plus, et la compilation echoue sur un
33
- // `defineConfig` introuvable dans un fichier de configuration.
34
- //
35
- // Le nommer explicitement le met hors d'atteinte de cette resolution. La
36
- // liste vaut pour les projets deja crees : eux portent encore un
37
- // `baseUrl`, et on ne peut pas reecrire leur tsconfig.
38
- external: ["odoro"]
39
- });
40
- const code = result.outputFiles[0]?.text;
41
- if (code === void 0) {
42
- throw new Error(`[odoro] La configuration "${file}" n'a produit aucun code.`);
43
- }
44
- await writeFile(output, code, "utf8");
45
- const module = await import(pathToFileURL(output).href);
46
- if (module.default === void 0) {
47
- throw new Error(
48
- `[odoro] La configuration "${file}" doit avoir un export par defaut.`
49
- );
50
- }
51
- return module.default;
52
- } finally {
53
- await rm(output, { force: true });
54
- }
55
- }
56
- async function loadConfig(root, overrides = {}) {
57
- const absoluteRoot = resolve(root);
58
- let file;
59
- let loaded = {};
60
- for (const candidate of CONFIG_FILES) {
61
- const path = join(absoluteRoot, candidate);
62
- if (existsSync(path)) {
63
- file = path;
64
- loaded = await importConfigFile(path, absoluteRoot);
65
- break;
66
- }
67
- }
68
- const merged = {
69
- ...loaded,
70
- ...overrides,
71
- server: { ...loaded.server, ...overrides.server },
72
- build: { ...loaded.build, ...overrides.build },
73
- alias: { ...loaded.alias, ...overrides.alias },
74
- define: { ...loaded.define, ...overrides.define }
75
- };
76
- const base = merged.base ?? "/";
77
- const projectRoot = merged.root === void 0 ? absoluteRoot : resolve(absoluteRoot, merged.root);
78
- return {
79
- root: projectRoot,
80
- base: base.endsWith("/") ? base : `${base}/`,
81
- publicDir: resolve(projectRoot, merged.publicDir ?? "public"),
82
- outDir: resolve(projectRoot, merged.build?.outDir ?? "dist"),
83
- server: {
84
- port: merged.server?.port ?? 5180,
85
- host: merged.server?.host ?? "localhost",
86
- proxy: merged.server?.proxy ?? {}
87
- },
88
- build: {
89
- outDir: merged.build?.outDir ?? "dist",
90
- minify: merged.build?.minify ?? true,
91
- sourcemap: merged.build?.sourcemap ?? true,
92
- target: merged.build?.target ?? "es2022",
93
- // Actif par defaut : une feuille qui contient toutes les classes
94
- // possibles est un accident de generation, pas une intention.
95
- elaguer: merged.build?.elaguer ?? true,
96
- safelist: merged.build?.safelist ?? []
97
- },
98
- // Les alias declares dans `tsconfig.json` sont repris d'office. Sans
99
- // cela, un projet qui suit `odoro init` — lequel deduit son prefixe du
100
- // tsconfig — aurait a redeclarer le meme alias ici pour que le serveur
101
- // sache le resoudre. Deux endroits pour la meme verite, et une erreur
102
- // qui n'apparait qu'au premier import.
103
- //
104
- // La configuration l'emporte : c'est elle qu'on ecrit pour corriger un
105
- // cas que la deduction n'attrape pas.
106
- alias: { ...await guessAliasPaths(projectRoot), ...merged.alias },
107
- define: merged.define ?? {},
108
- envPrefix: merged.envPrefix ?? "ODORO_",
109
- configFile: file
110
- };
111
- }
112
-
113
- export { defineConfig, loadConfig };