odoro 1.0.9 → 2.1.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 (88) hide show
  1. package/README.md +94 -0
  2. package/client.d.ts +90 -16
  3. package/dist/build-XJIP4GNT.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-DF67LADH.js +735 -0
  7. package/dist/{chunk-LHZGX5ML.js → chunk-HS7NGZLM.js} +307 -196
  8. package/dist/chunk-LIKVHWB4.js +231 -0
  9. package/dist/chunk-QOVBLN7A.js +241 -0
  10. package/dist/chunk-RTWJ5GFB.js +729 -0
  11. package/dist/chunk-TONYIKCN.js +57 -0
  12. package/dist/cli.d.ts +12 -13
  13. package/dist/cli.js +86 -63
  14. package/dist/{commands-AHMXBWQQ.js → commands-GYA7NTCX.js} +124 -124
  15. package/dist/commands-UOWN3IK4.js +181 -0
  16. package/dist/{create-CYVSVDAD.js → create-B2TLV6XI.js} +198 -160
  17. package/dist/index.d.ts +603 -151
  18. package/dist/index.js +6 -6
  19. package/dist/{package-EIDLKAA6.js → package-3ZJOEXM4.js} +6 -5
  20. package/dist/preview-3IRG7MHV.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-ZB3P3EGS.js} +3 -3
  24. package/package.json +6 -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-3SZIN6VG.js +0 -64
  74. package/dist/chunk-DL3NPC4H.js +0 -113
  75. package/dist/chunk-TQUJ3MFS.js +0 -268
  76. package/dist/chunk-ZVL7EXJO.js +0 -66
  77. package/dist/commands-4JRBD55Z.js +0 -245
  78. package/dist/preview-7DKEAQOQ.js +0 -4
  79. package/templates/react-ts/_variantes/avec-moteur/src/fond.tsx +0 -118
  80. package/templates/react-ts/_variantes/sans-libs/src/App.tsx +0 -382
  81. package/templates/react-ts/_variantes/sans-libs/src/fond.tsx +0 -23
  82. package/templates/react-ts/_variantes/sans-libs/src/main.tsx +0 -17
  83. package/templates/react-ts/src/fond.tsx +0 -66
  84. package/templates/react-ts-server/_variantes/avec-moteur/client/src/fond.tsx +0 -118
  85. package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +0 -382
  86. package/templates/react-ts-server/_variantes/sans-libs/client/src/fond.tsx +0 -23
  87. package/templates/react-ts-server/_variantes/sans-libs/client/src/main.tsx +0 -17
  88. 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,64 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFile, writeFile, appendFile } from 'fs/promises';
3
- import { existsSync } from 'fs';
4
- import { join } from 'path';
5
-
6
- function checkDatabaseUrl(value) {
7
- const url = value.trim();
8
- if (url.length === 0) return "L'URL ne peut pas etre vide.";
9
- if (!/^postgres(ql)?:\/\//i.test(url)) {
10
- return "Une URL PostgreSQL commence par postgres:// ou postgresql://";
11
- }
12
- let parsed;
13
- try {
14
- parsed = new URL(url);
15
- } catch {
16
- return "Cette chaine n'est pas une URL valide.";
17
- }
18
- if (parsed.hostname.length === 0) return "Aucun hote dans cette URL.";
19
- if (parsed.pathname.replace(/^\//, "").length === 0) {
20
- return "Aucun nom de base : ajoutez-le apres le port, par exemple /mon_projet";
21
- }
22
- if (!/^(localhost|127\.0\.0\.1|\[::1\])$/i.test(parsed.hostname) && parsed.searchParams.get("sslmode") === "disable") {
23
- return "sslmode=disable sur une base distante : le trafic passerait en clair.";
24
- }
25
- return void 0;
26
- }
27
- async function writeDatabaseUrl(target, url) {
28
- const path = join(target, ".env");
29
- const line = `DATABASE_URL=${url}
30
- `;
31
- if (!existsSync(path)) {
32
- const example = join(target, ".env.example");
33
- const base = existsSync(example) ? await readFile(example, "utf8") : "";
34
- await writeFile(path, base.replace(/^DATABASE_URL=.*$/m, line.trimEnd()), "utf8");
35
- if (!base.includes("DATABASE_URL=")) await appendFile(path, line, "utf8");
36
- return;
37
- }
38
- const current = await readFile(path, "utf8");
39
- await writeFile(
40
- path,
41
- current.includes("DATABASE_URL=") ? current.replace(/^DATABASE_URL=.*$/m, line.trimEnd()) : current + line,
42
- "utf8"
43
- );
44
- }
45
- async function assertEnvIgnored(target) {
46
- const path = join(target, ".gitignore");
47
- if (!existsSync(path)) {
48
- return "Aucun .gitignore : le fichier .env risque d'etre versionne.";
49
- }
50
- const content = await readFile(path, "utf8");
51
- const ignored = content.split("\n").map((line) => line.trim()).some((line) => line === ".env" || line === ".env*" || line === "*.env");
52
- return ignored ? void 0 : ".env ne figure pas dans le .gitignore : vos identifiants risquent d etre versionnes.";
53
- }
54
- var PROVIDER_PENDING = [
55
- "Le provisionnement passe par @odoro-cli/cloud-sdk, installe a part :",
56
- "",
57
- " npm install --save-dev @odoro-cli/cloud-sdk",
58
- " odoro db:login",
59
- " odoro db:create --env production",
60
- "",
61
- "Il ne vient pas avec odoro : ce binaire est telecharge a chaque creation de projet, et la plupart emploient leur propre base."
62
- ].join("\n");
63
-
64
- export { PROVIDER_PENDING, assertEnvIgnored, checkDatabaseUrl, writeDatabaseUrl };