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.
- package/LICENSE +12 -0
- package/client.d.ts +84 -0
- package/dist/build-SNTGJH2J.js +4 -0
- package/dist/chunk-2YDI5NKV.js +55 -0
- package/dist/chunk-G2WW7N4C.js +1872 -0
- package/dist/chunk-G5QXVBYT.js +1139 -0
- package/dist/chunk-JMEHF3KN.js +35 -0
- package/dist/chunk-PEMYUK2D.js +64 -0
- package/dist/chunk-T42X2NJN.js +98 -0
- package/dist/chunk-T6RLHSCW.js +123 -0
- package/dist/chunk-UGRSODHU.js +154 -0
- package/dist/cli.d.ts +40 -0
- package/dist/cli.js +236 -0
- package/dist/commands-FVAHVVVN.js +793 -0
- package/dist/commands-V44Y5G4F.js +245 -0
- package/dist/create-SH2M722Y.js +288 -0
- package/dist/index.d.ts +354 -0
- package/dist/index.js +7 -0
- package/dist/package-AUINPBEX.js +62 -0
- package/dist/preview-LOAO5Y6V.js +3 -0
- package/dist/registry/index.d.ts +316 -0
- package/dist/registry/index.js +2 -0
- package/dist/server-MZ76LPAG.js +3 -0
- package/package.json +57 -0
- package/templates/react-ts/README.md +52 -0
- package/templates/react-ts/_gitignore +8 -0
- package/templates/react-ts/index.html +13 -0
- package/templates/react-ts/odoro.config.ts +10 -0
- package/templates/react-ts/package.json +23 -0
- package/templates/react-ts/public/favicon.svg +4 -0
- package/templates/react-ts/src/App.tsx +66 -0
- package/templates/react-ts/src/main.tsx +18 -0
- package/templates/react-ts/src/odoro-env.d.ts +1 -0
- package/templates/react-ts/src/routes/About.tsx +19 -0
- package/templates/react-ts/src/routes/Home.tsx +80 -0
- package/templates/react-ts/src/routes/NotFound.tsx +14 -0
- package/templates/react-ts/src/styles.css +13 -0
- package/templates/react-ts/tsconfig.json +25 -0
- package/templates/react-ts-server/Dockerfile +51 -0
- package/templates/react-ts-server/README.md +87 -0
- package/templates/react-ts-server/_dockerignore +8 -0
- package/templates/react-ts-server/_env.example +78 -0
- package/templates/react-ts-server/_gitignore +8 -0
- package/templates/react-ts-server/client/index.html +13 -0
- package/templates/react-ts-server/client/public/favicon.svg +4 -0
- package/templates/react-ts-server/client/src/App.tsx +66 -0
- package/templates/react-ts-server/client/src/main.tsx +18 -0
- package/templates/react-ts-server/client/src/odoro-env.d.ts +1 -0
- package/templates/react-ts-server/client/src/routes/About.tsx +19 -0
- package/templates/react-ts-server/client/src/routes/Home.tsx +148 -0
- package/templates/react-ts-server/client/src/routes/NotFound.tsx +14 -0
- package/templates/react-ts-server/client/src/styles.css +13 -0
- package/templates/react-ts-server/odoro.config.ts +21 -0
- package/templates/react-ts-server/package.json +33 -0
- package/templates/react-ts-server/scripts/dev.mjs +74 -0
- package/templates/react-ts-server/server/src/main.ts +131 -0
- package/templates/react-ts-server/server/src/modules/health/index.ts +144 -0
- package/templates/react-ts-server/server/tsconfig.json +23 -0
- 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 };
|
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,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,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,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
|
+
}
|