odoro 0.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 (59) hide show
  1. package/LICENSE +12 -0
  2. package/client.d.ts +84 -0
  3. package/dist/build-SNTGJH2J.js +4 -0
  4. package/dist/chunk-2YDI5NKV.js +55 -0
  5. package/dist/chunk-G2WW7N4C.js +1872 -0
  6. package/dist/chunk-G5QXVBYT.js +1139 -0
  7. package/dist/chunk-JMEHF3KN.js +35 -0
  8. package/dist/chunk-PEMYUK2D.js +64 -0
  9. package/dist/chunk-T42X2NJN.js +98 -0
  10. package/dist/chunk-T6RLHSCW.js +123 -0
  11. package/dist/chunk-UGRSODHU.js +154 -0
  12. package/dist/cli.d.ts +40 -0
  13. package/dist/cli.js +236 -0
  14. package/dist/commands-FVAHVVVN.js +793 -0
  15. package/dist/commands-V44Y5G4F.js +245 -0
  16. package/dist/create-SH2M722Y.js +288 -0
  17. package/dist/index.d.ts +354 -0
  18. package/dist/index.js +7 -0
  19. package/dist/package-AUINPBEX.js +62 -0
  20. package/dist/preview-LOAO5Y6V.js +3 -0
  21. package/dist/registry/index.d.ts +316 -0
  22. package/dist/registry/index.js +2 -0
  23. package/dist/server-MZ76LPAG.js +3 -0
  24. package/package.json +57 -0
  25. package/templates/react-ts/README.md +52 -0
  26. package/templates/react-ts/_gitignore +8 -0
  27. package/templates/react-ts/index.html +13 -0
  28. package/templates/react-ts/odoro.config.ts +10 -0
  29. package/templates/react-ts/package.json +23 -0
  30. package/templates/react-ts/public/favicon.svg +4 -0
  31. package/templates/react-ts/src/App.tsx +66 -0
  32. package/templates/react-ts/src/main.tsx +18 -0
  33. package/templates/react-ts/src/odoro-env.d.ts +1 -0
  34. package/templates/react-ts/src/routes/About.tsx +19 -0
  35. package/templates/react-ts/src/routes/Home.tsx +80 -0
  36. package/templates/react-ts/src/routes/NotFound.tsx +14 -0
  37. package/templates/react-ts/src/styles.css +13 -0
  38. package/templates/react-ts/tsconfig.json +25 -0
  39. package/templates/react-ts-server/Dockerfile +51 -0
  40. package/templates/react-ts-server/README.md +87 -0
  41. package/templates/react-ts-server/_dockerignore +8 -0
  42. package/templates/react-ts-server/_env.example +78 -0
  43. package/templates/react-ts-server/_gitignore +8 -0
  44. package/templates/react-ts-server/client/index.html +13 -0
  45. package/templates/react-ts-server/client/public/favicon.svg +4 -0
  46. package/templates/react-ts-server/client/src/App.tsx +66 -0
  47. package/templates/react-ts-server/client/src/main.tsx +18 -0
  48. package/templates/react-ts-server/client/src/odoro-env.d.ts +1 -0
  49. package/templates/react-ts-server/client/src/routes/About.tsx +19 -0
  50. package/templates/react-ts-server/client/src/routes/Home.tsx +148 -0
  51. package/templates/react-ts-server/client/src/routes/NotFound.tsx +14 -0
  52. package/templates/react-ts-server/client/src/styles.css +13 -0
  53. package/templates/react-ts-server/odoro.config.ts +21 -0
  54. package/templates/react-ts-server/package.json +33 -0
  55. package/templates/react-ts-server/scripts/dev.mjs +74 -0
  56. package/templates/react-ts-server/server/src/main.ts +131 -0
  57. package/templates/react-ts-server/server/src/modules/health/index.ts +144 -0
  58. package/templates/react-ts-server/server/tsconfig.json +23 -0
  59. package/templates/react-ts-server/tsconfig.json +27 -0
@@ -0,0 +1,316 @@
1
+ import * as z from 'zod/mini';
2
+
3
+ /**
4
+ * Format d'une entree de registre.
5
+ *
6
+ * ## Pourquoi le schema vit ici
7
+ *
8
+ * Le format est un **contrat** entre le registre qui publie et le client qui
9
+ * telecharge. Le placer du cote client n'est pas arbitraire : c'est la ou la
10
+ * validation compte le plus. Le registre valide ce qu'il produit avant de le
11
+ * publier ; le client, lui, valide ce qu'il recoit d'un serveur qu'il ne
12
+ * controle pas, juste avant d'ecrire des fichiers dans le projet de
13
+ * l'utilisateur.
14
+ *
15
+ * Une seule definition, donc, employee aux deux bouts.
16
+ *
17
+ * ## Le poids de la validation
18
+ *
19
+ * Deux decisions, mesurees plutot que supposees.
20
+ *
21
+ * La bibliotheque est **integree a la compilation** plutot que declaree en
22
+ * dependance : installee, elle pese pres de six megaoctets, alors que la
23
+ * surface reellement employee en represente une fraction.
24
+ *
25
+ * Et c'est sa variante concue pour le decoupage qui est employee, non son API
26
+ * usuelle : la premiere produit treize kilo-octets minifies, la seconde quatre
27
+ * cent vingt-sept. Un facteur trente-trois pour la meme validation. L'ecriture
28
+ * y est plus verbeuse — les controles sont des fonctions plutot que des
29
+ * methodes chainees — mais plaider le poids pour le moteur graphique et
30
+ * l'ignorer ici serait incoherent.
31
+ *
32
+ * @module
33
+ */
34
+
35
+ /** Categories de composants du registre. */
36
+ declare const CATEGORIES: readonly ["text", "background", "effect", "hero", "image", "ui", "section", "hooks"];
37
+ /** Niveaux de cout d'un composant. */
38
+ declare const PERF_TIERS: readonly ["light", "medium", "heavy"];
39
+ /** Backends graphiques declarables. */
40
+ declare const GL_BACKENDS: readonly ["ogl", "three"];
41
+ /** Nature du repli visuel d'un composant. */
42
+ declare const FALLBACKS: readonly ["poster", "gradient", "static", "none"];
43
+ /** Forme d'une entree, avant les regles qui croisent plusieurs champs. */
44
+ declare const baseSchema: z.ZodMiniObject<{
45
+ name: z.ZodMiniString<string>;
46
+ category: z.ZodMiniEnum<{
47
+ text: "text";
48
+ background: "background";
49
+ effect: "effect";
50
+ hero: "hero";
51
+ image: "image";
52
+ ui: "ui";
53
+ section: "section";
54
+ hooks: "hooks";
55
+ }>;
56
+ title: z.ZodMiniString<string>;
57
+ description: z.ZodMiniString<string>;
58
+ engine: z.ZodMiniDefault<z.ZodMiniObject<{
59
+ gsap: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniString<string>>>;
60
+ gl: z.ZodMiniDefault<z.ZodMiniUnion<readonly [z.ZodMiniLiteral<false>, z.ZodMiniEnum<{
61
+ ogl: "ogl";
62
+ three: "three";
63
+ }>]>>;
64
+ }, z.core.$strip>>;
65
+ files: z.ZodMiniArray<z.ZodMiniObject<{
66
+ path: z.ZodMiniString<string>;
67
+ target: z.ZodMiniString<string>;
68
+ }, z.core.$strip>>;
69
+ dependencies: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniString<string>>>;
70
+ registryDependencies: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniString<string>>>;
71
+ tokens: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniString<string>>>;
72
+ props: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniObject<{
73
+ name: z.ZodMiniString<string>;
74
+ type: z.ZodMiniString<string>;
75
+ required: z.ZodMiniDefault<z.ZodMiniBoolean<boolean>>;
76
+ default: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniString<string>, z.ZodMiniNumber<number>, z.ZodMiniBoolean<boolean>]>>;
77
+ unit: z.ZodMiniOptional<z.ZodMiniString<string>>;
78
+ description: z.ZodMiniOptional<z.ZodMiniString<string>>;
79
+ min: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
80
+ max: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
81
+ step: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
82
+ options: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
83
+ }, z.core.$strip>>>;
84
+ perf: z.ZodMiniObject<{
85
+ tier: z.ZodMiniEnum<{
86
+ light: "light";
87
+ medium: "medium";
88
+ heavy: "heavy";
89
+ }>;
90
+ backend: z.ZodMiniDefault<z.ZodMiniUnion<readonly [z.ZodMiniLiteral<false>, z.ZodMiniEnum<{
91
+ ogl: "ogl";
92
+ three: "three";
93
+ }>]>>;
94
+ notes: z.ZodMiniOptional<z.ZodMiniString<string>>;
95
+ fallback: z.ZodMiniOptional<z.ZodMiniEnum<{
96
+ static: "static";
97
+ poster: "poster";
98
+ gradient: "gradient";
99
+ none: "none";
100
+ }>>;
101
+ }, z.core.$strip>;
102
+ }, z.core.$strip>;
103
+ /** Une entree telle qu'elle sort de la validation. */
104
+ type RegistryMeta = z.infer<typeof baseSchema>;
105
+ /** Entree telle qu'elle est ecrite dans un `meta.json`, avant valeurs par defaut. */
106
+ type RegistryMetaInput = z.input<typeof baseSchema>;
107
+ /**
108
+ * Description complete d'une entree de registre.
109
+ *
110
+ * Les regles qui croisent plusieurs champs sont verifiees ici plutot que dans
111
+ * le script de validation : elles font partie du format, et un registre tiers
112
+ * qui reutiliserait ce schema doit les subir aussi.
113
+ */
114
+ declare const metaSchema: z.ZodMiniObject<{
115
+ name: z.ZodMiniString<string>;
116
+ category: z.ZodMiniEnum<{
117
+ text: "text";
118
+ background: "background";
119
+ effect: "effect";
120
+ hero: "hero";
121
+ image: "image";
122
+ ui: "ui";
123
+ section: "section";
124
+ hooks: "hooks";
125
+ }>;
126
+ title: z.ZodMiniString<string>;
127
+ description: z.ZodMiniString<string>;
128
+ engine: z.ZodMiniDefault<z.ZodMiniObject<{
129
+ gsap: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniString<string>>>;
130
+ gl: z.ZodMiniDefault<z.ZodMiniUnion<readonly [z.ZodMiniLiteral<false>, z.ZodMiniEnum<{
131
+ ogl: "ogl";
132
+ three: "three";
133
+ }>]>>;
134
+ }, z.core.$strip>>;
135
+ files: z.ZodMiniArray<z.ZodMiniObject<{
136
+ path: z.ZodMiniString<string>;
137
+ target: z.ZodMiniString<string>;
138
+ }, z.core.$strip>>;
139
+ dependencies: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniString<string>>>;
140
+ registryDependencies: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniString<string>>>;
141
+ tokens: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniString<string>>>;
142
+ props: z.ZodMiniDefault<z.ZodMiniArray<z.ZodMiniObject<{
143
+ name: z.ZodMiniString<string>;
144
+ type: z.ZodMiniString<string>;
145
+ required: z.ZodMiniDefault<z.ZodMiniBoolean<boolean>>;
146
+ default: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniString<string>, z.ZodMiniNumber<number>, z.ZodMiniBoolean<boolean>]>>;
147
+ unit: z.ZodMiniOptional<z.ZodMiniString<string>>;
148
+ description: z.ZodMiniOptional<z.ZodMiniString<string>>;
149
+ min: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
150
+ max: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
151
+ step: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
152
+ options: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
153
+ }, z.core.$strip>>>;
154
+ perf: z.ZodMiniObject<{
155
+ tier: z.ZodMiniEnum<{
156
+ light: "light";
157
+ medium: "medium";
158
+ heavy: "heavy";
159
+ }>;
160
+ backend: z.ZodMiniDefault<z.ZodMiniUnion<readonly [z.ZodMiniLiteral<false>, z.ZodMiniEnum<{
161
+ ogl: "ogl";
162
+ three: "three";
163
+ }>]>>;
164
+ notes: z.ZodMiniOptional<z.ZodMiniString<string>>;
165
+ fallback: z.ZodMiniOptional<z.ZodMiniEnum<{
166
+ static: "static";
167
+ poster: "poster";
168
+ gradient: "gradient";
169
+ none: "none";
170
+ }>>;
171
+ }, z.core.$strip>;
172
+ }, z.core.$strip>;
173
+ /** Identifiant complet d'une entree, sous la forme `categorie/nom`. */
174
+ declare function entryId(meta: Pick<RegistryMeta, 'category' | 'name'>): string;
175
+ /** Une entree publiee, code source inline. */
176
+ interface PublishedEntry extends RegistryMeta {
177
+ /** Identifiant complet. */
178
+ readonly id: string;
179
+ /** Contenu des fichiers, indexe par leur chemin dans le composant. */
180
+ readonly sources: Readonly<Record<string, string>>;
181
+ }
182
+ /** Resume d'une entree, tel qu'il figure dans l'index. */
183
+ interface IndexEntry {
184
+ readonly id: string;
185
+ readonly name: string;
186
+ readonly category: (typeof CATEGORIES)[number];
187
+ readonly title: string;
188
+ readonly description: string;
189
+ readonly tier: (typeof PERF_TIERS)[number];
190
+ readonly backend: false | (typeof GL_BACKENDS)[number];
191
+ readonly registryDependencies: readonly string[];
192
+ }
193
+ /** Index du registre, servi a la racine. */
194
+ interface RegistryIndex {
195
+ /** Version du format, pour que le client sache s'il sait lire. */
196
+ readonly version: 1;
197
+ /** Date de generation, en ISO 8601. */
198
+ readonly generatedAt: string;
199
+ /** Resume de chaque entree, sans le code source. */
200
+ readonly entries: readonly IndexEntry[];
201
+ }
202
+ /**
203
+ * Valide une entree et rend des messages lisibles en cas d'echec.
204
+ *
205
+ * Les messages bruts sont exacts mais arides : ils sont reformates en chemin
206
+ * plus explication, pour qu'un auteur de composant sache quoi corriger sans
207
+ * avoir a lire le schema.
208
+ *
209
+ * @example
210
+ * const result = parseMeta(JSON.parse(raw), 'text/split-reveal')
211
+ * if (!result.ok) console.error(result.problems.join('\n'))
212
+ */
213
+ declare function parseMeta(value: unknown, origin: string): {
214
+ ok: true;
215
+ meta: RegistryMeta;
216
+ } | {
217
+ ok: false;
218
+ problems: string[];
219
+ };
220
+
221
+ /**
222
+ * Resolution du graphe de dependances du registre.
223
+ *
224
+ * Une entree peut en reclamer d'autres — un hook partage, un utilitaire. Il
225
+ * faut donc, avant d'ecrire quoi que ce soit chez l'utilisateur, savoir
226
+ * exactement quelles entrees installer et dans quel ordre.
227
+ *
228
+ * ## Les cycles
229
+ *
230
+ * Rien n'interdit structurellement a deux entrees de se reclamer l'une
231
+ * l'autre, et un parcours naif s'y perdrait indefiniment. Le cycle est donc
232
+ * detecte, et **signale avec le chemin qui le compose** : une erreur qui dit
233
+ * seulement « cycle detecte » oblige a le chercher a la main.
234
+ *
235
+ * ## L'ordre
236
+ *
237
+ * Les dependances sont installees avant ce qui les reclame. Cela n'a pas
238
+ * d'importance pour l'ecriture de fichiers, qui est independante, mais cela en
239
+ * a pour ce que l'utilisateur voit defiler : une liste ou les dependances
240
+ * apparaissent apres leur consommateur se lit comme une erreur.
241
+ *
242
+ * @module
243
+ */
244
+
245
+ /** Ce qu'il faut savoir d'une entree pour resoudre le graphe. */
246
+ interface ResolvableEntry {
247
+ /** Identifiant complet, sous la forme `categorie/nom`. */
248
+ readonly id: string;
249
+ /** Entrees reclamees par celle-ci. */
250
+ readonly registryDependencies: readonly string[];
251
+ }
252
+ /** Resultat d'une resolution reussie. */
253
+ interface ResolvedGraph {
254
+ /** Entrees a installer, dependances d'abord. */
255
+ readonly order: readonly string[];
256
+ /** Entrees ajoutees qui n'avaient pas ete demandees. */
257
+ readonly implied: readonly string[];
258
+ }
259
+ /** Ce qui a empeche la resolution. */
260
+ type ResolutionProblem = {
261
+ readonly kind: 'introuvable';
262
+ readonly id: string;
263
+ readonly requiredBy: string | null;
264
+ } | {
265
+ readonly kind: 'cycle';
266
+ readonly path: readonly string[];
267
+ };
268
+ /** Resultat d'une resolution. */
269
+ type ResolutionResult = {
270
+ readonly ok: true;
271
+ readonly graph: ResolvedGraph;
272
+ } | {
273
+ readonly ok: false;
274
+ readonly problems: readonly ResolutionProblem[];
275
+ };
276
+ /**
277
+ * Met un probleme de resolution en phrase lisible.
278
+ *
279
+ * @example
280
+ * describeProblem({ kind: 'cycle', path: ['a', 'b', 'a'] })
281
+ * // 'Cycle de dependances : a → b → a'
282
+ */
283
+ declare function describeProblem(problem: ResolutionProblem): string;
284
+ /**
285
+ * Resout les dependances d'un ensemble d'entrees demandees.
286
+ *
287
+ * @param requested Identifiants demandes par l'utilisateur.
288
+ * @param available Toutes les entrees connues, indexees par identifiant.
289
+ *
290
+ * @example
291
+ * const result = resolveGraph(['hero/canopy'], catalogue)
292
+ * if (result.ok) install(result.graph.order)
293
+ */
294
+ declare function resolveGraph(requested: readonly string[], available: ReadonlyMap<string, ResolvableEntry>): ResolutionResult;
295
+ /**
296
+ * Verifie l'integrite d'un catalogue entier.
297
+ *
298
+ * Emploi typique : la validation du registre avant publication, ou toutes les
299
+ * entrees sont resolues d'un coup plutot qu'une par une.
300
+ *
301
+ * @example
302
+ * const problems = validateCatalogue(catalogue)
303
+ * if (problems.length > 0) process.exit(1)
304
+ */
305
+ declare function validateCatalogue(available: ReadonlyMap<string, ResolvableEntry>): readonly ResolutionProblem[];
306
+ /**
307
+ * Construit un catalogue a partir d'entrees completes.
308
+ *
309
+ * @example
310
+ * const catalogue = toCatalogue(metas)
311
+ */
312
+ declare function toCatalogue(entries: readonly (RegistryMeta & {
313
+ id: string;
314
+ })[]): Map<string, ResolvableEntry>;
315
+
316
+ export { CATEGORIES, FALLBACKS, GL_BACKENDS, type IndexEntry, PERF_TIERS, type PublishedEntry, type RegistryIndex, type RegistryMeta, type RegistryMetaInput, type ResolutionProblem, type ResolutionResult, type ResolvableEntry, type ResolvedGraph, describeProblem, entryId, metaSchema, parseMeta, resolveGraph, toCatalogue, validateCatalogue };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { CATEGORIES, FALLBACKS, GL_BACKENDS, PERF_TIERS, describeProblem, entryId, metaSchema, parseMeta, resolveGraph, toCatalogue, validateCatalogue } from '../chunk-G2WW7N4C.js';
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export { extractEntries, injectClient, startDevServer } from './chunk-G5QXVBYT.js';
3
+ import './chunk-JMEHF3KN.js';
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "odoro",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "UNLICENSED",
6
+ "author": "BouBouw",
7
+ "description": "Moteur de developpement et echafaudage Odoro : odoro create, dev, build, preview.",
8
+ "engines": {
9
+ "node": ">=20"
10
+ },
11
+ "bin": {
12
+ "odoro": "./dist/cli.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "templates",
17
+ "client.d.ts"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ },
24
+ "./package.json": "./package.json",
25
+ "./client": {
26
+ "types": "./client.d.ts"
27
+ },
28
+ "./cli": {
29
+ "types": "./dist/cli.d.ts",
30
+ "import": "./dist/cli.js"
31
+ },
32
+ "./registry": {
33
+ "types": "./dist/registry/index.d.ts",
34
+ "import": "./dist/registry/index.js"
35
+ }
36
+ },
37
+ "dependencies": {
38
+ "@babel/core": "^8.0.1",
39
+ "@clack/prompts": "^0.11.0",
40
+ "esbuild": "^0.28.2",
41
+ "picocolors": "^1.1.1",
42
+ "react-refresh": "^0.18.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.18.8",
46
+ "tsup": "^8.5.0",
47
+ "typescript": "^5.9.3",
48
+ "vitest": "^3.2.4",
49
+ "zod": "^4.5.1"
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "dev": "tsup --watch",
54
+ "test": "vitest run",
55
+ "typecheck": "tsc --noEmit"
56
+ }
57
+ }
@@ -0,0 +1,52 @@
1
+ # Odoro — application monopage
2
+
3
+ Projet genere par `odoro create`. Le routeur, le moteur d'animation, le systeme
4
+ de style et les composants d'interface viennent tous de `@odoro-cli/libs` ; le
5
+ serveur de developpement et la compilation viennent du moteur `odoro`.
6
+
7
+ ## Structure
8
+
9
+ ```
10
+ index.html Document et point d'entree : la balise <script type="module">
11
+ designe le fichier a compiler.
12
+ odoro.config.ts Configuration du moteur : alias, port, proxy, build.
13
+ public/ Fichiers copies tels quels a la racine du site.
14
+ src/
15
+ main.tsx Montage de React et imports de feuilles de style.
16
+ App.tsx Declaration des routes et enveloppe commune.
17
+ routes/ Une page par route.
18
+ styles.css Styles propres au projet et surcharges de tokens.
19
+ odoro-env.d.ts Types ambiants (import.meta.env, imports de ressources).
20
+ ```
21
+
22
+ ## Scripts
23
+
24
+ | Commande | Effet |
25
+ | ----------- | --------------------------------------------------- |
26
+ | `dev` | Serveur de developpement avec rechargement a chaud. |
27
+ | `build` | Compilation de production dans `dist/`. |
28
+ | `preview` | Sert `dist/` comme le ferait un hebergeur statique. |
29
+ | `typecheck` | Verification des types, sans emission. |
30
+
31
+ ## Personnalisation
32
+
33
+ Toutes les valeurs visuelles passent par des variables CSS. Surcharger
34
+ `--o-palette-brand-600` dans `src/styles.css` retheme l'application **et** les
35
+ composants de la librairie, sans toucher a leur code.
36
+
37
+ Deux feuilles de style sont disponibles :
38
+
39
+ - `@odoro-cli/libs/styles.css` — structure et couleurs semantiques ;
40
+ - `@odoro-cli/libs/styles.full.css` — la meme, plus les utilitaires de couleur sur
41
+ la palette complete.
42
+
43
+ Importer l'une **ou** l'autre, jamais les deux.
44
+
45
+ ## Rechargement a chaud
46
+
47
+ Modifier un composant remplace son code sans demonter l'arbre : la valeur d'un
48
+ compteur, le texte saisi dans un champ, l'onglet ouvert survivent a l'edition.
49
+ Une feuille de style est echangee sans rechargement.
50
+
51
+ Un module qui n'exporte pas que des composants recharge la page, et c'est
52
+ correct : rien ne permettrait d'en propager le changement sans risque.
@@ -0,0 +1,8 @@
1
+ node_modules/
2
+ dist/
3
+ *.local
4
+ .DS_Store
5
+ *.log
6
+ .env
7
+ .env.*
8
+ !.env.example
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
+ <title>Odoro</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'odoro'
2
+
3
+ export default defineConfig({
4
+ alias: {
5
+ '@': 'src',
6
+ },
7
+ server: {
8
+ port: 5180,
9
+ },
10
+ })
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "odoro-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "odoro dev",
8
+ "build": "odoro build",
9
+ "preview": "odoro preview",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@odoro-cli/libs": "^0.0.0",
14
+ "react": "^19.2.0",
15
+ "react-dom": "^19.2.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^19.2.2",
19
+ "@types/react-dom": "^19.2.1",
20
+ "odoro": "^0.0.0",
21
+ "typescript": "^5.9.3"
22
+ }
23
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="8" fill="#5b62f4" />
3
+ <circle cx="16" cy="16" r="7" fill="none" stroke="#fff" stroke-width="3" />
4
+ </svg>
@@ -0,0 +1,66 @@
1
+ import { Link, Outlet, Route, Router, Routes, useLocation } from '@odoro-cli/libs/router'
2
+
3
+ import { About } from '@/routes/About'
4
+ import { Home } from '@/routes/Home'
5
+ import { NotFound } from '@/routes/NotFound'
6
+
7
+ /** Barre de navigation, avec mise en valeur de la route courante. */
8
+ function Nav() {
9
+ const { pathname } = useLocation()
10
+
11
+ const links = [
12
+ { to: '/', label: 'Accueil' },
13
+ { to: '/a-propos', label: 'A propos' },
14
+ ]
15
+
16
+ return (
17
+ <nav className="o-flex o-items-center o-gap-4 o-px-6 o-py-4 o-border-b o-border-zinc-200 dark:o-border-zinc-800">
18
+ <span className="o-font-semibold o-text-brand-600 dark:o-text-brand-400">
19
+ Odoro
20
+ </span>
21
+ {links.map((link) => (
22
+ <Link
23
+ key={link.to}
24
+ to={link.to}
25
+ className={
26
+ pathname === link.to
27
+ ? 'o-text-zinc-900 dark:o-text-zinc-50 o-font-medium o-no-underline'
28
+ : 'o-text-zinc-500 dark:o-text-zinc-400 o-no-underline hover:o-text-zinc-900 dark:hover:o-text-zinc-50 o-transition'
29
+ }
30
+ >
31
+ {link.label}
32
+ </Link>
33
+ ))}
34
+ </nav>
35
+ )
36
+ }
37
+
38
+ /** Enveloppe commune a toutes les pages. */
39
+ function Layout() {
40
+ return (
41
+ <div className="app-shell">
42
+ <Nav />
43
+ <main className="o-view-transition-page o-mx-auto o-w-full o-max-w-3xl o-px-6 o-py-12">
44
+ <Outlet />
45
+ </main>
46
+ <footer className="o-px-6 o-py-6 o-text-sm o-text-zinc-500 dark:o-text-zinc-400 o-border-t o-border-zinc-200 dark:o-border-zinc-800">
47
+ Construit avec Odoro.
48
+ </footer>
49
+ </div>
50
+ )
51
+ }
52
+
53
+ /** Racine de l'application. */
54
+ export function App() {
55
+ return (
56
+ <Router>
57
+ <Routes>
58
+ <Route path="/" element={<Layout />}>
59
+ <Route index element={<Home />} />
60
+ <Route path="a-propos" element={<About />} />
61
+ <Route path="*" element={<NotFound />} />
62
+ </Route>
63
+ </Routes>
64
+ </Router>
65
+ )
66
+ }
@@ -0,0 +1,18 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+
4
+ import { App } from '@/App'
5
+
6
+ import '@odoro-cli/libs/styles.css'
7
+ import '@/styles.css'
8
+
9
+ const container = document.getElementById('root')
10
+ if (container === null) {
11
+ throw new Error('Element racine "#root" introuvable dans index.html.')
12
+ }
13
+
14
+ createRoot(container).render(
15
+ <StrictMode>
16
+ <App />
17
+ </StrictMode>,
18
+ )
@@ -0,0 +1 @@
1
+ /// <reference types="odoro/client" />
@@ -0,0 +1,19 @@
1
+ import { Reveal } from '@odoro-cli/libs/motion'
2
+ import { Link } from '@odoro-cli/libs/router'
3
+
4
+ /** Page « a propos », qui demontre la navigation entre routes. */
5
+ export function About() {
6
+ return (
7
+ <Reveal className="o-flex o-flex-col o-gap-6">
8
+ <h1 className="o-text-3xl o-font-bold o-tracking-tight">A propos</h1>
9
+ <p className="o-text-zinc-500 dark:o-text-zinc-400 o-max-w-prose">
10
+ Cette page est servie par le routeur d Odoro. La navigation ne recharge pas le
11
+ document : elle passe par l historique du navigateur, restaure la position de
12
+ defilement et joue une transition de page quand le navigateur la prend en charge.
13
+ </p>
14
+ <p>
15
+ <Link to="/">Retour a l accueil</Link>
16
+ </p>
17
+ </Reveal>
18
+ )
19
+ }