odoro 0.3.0 → 0.3.4

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.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ export { buildProject, reportBuild } from './chunk-CJIUP7A3.js';
3
+ import './chunk-R6PMAMXA.js';
4
+ import './chunk-JMEHF3KN.js';
@@ -15,7 +15,10 @@ var MIME = {
15
15
  ".webp": "image/webp",
16
16
  ".ico": "image/x-icon",
17
17
  ".woff2": "font/woff2",
18
- ".map": "application/json; charset=utf-8"
18
+ ".map": "application/json; charset=utf-8",
19
+ ".txt": "text/plain; charset=utf-8",
20
+ ".md": "text/markdown; charset=utf-8",
21
+ ".markdown": "text/markdown; charset=utf-8"
19
22
  };
20
23
  async function startPreviewServer(config, port = config.server.port + 1) {
21
24
  if (!existsSync(config.outDir)) {
@@ -25,7 +28,8 @@ async function startPreviewServer(config, port = config.server.port + 1) {
25
28
  const path = (incoming.url ?? "/").split("?")[0] ?? "/";
26
29
  const relativePath = normalize(decodeURIComponent(path)).split(/[\\/]/).filter(Boolean).join("/");
27
30
  const candidate = join(config.outDir, relativePath);
28
- const file = existsSync(candidate) && statSync(candidate).isFile() ? candidate : join(config.outDir, "index.html");
31
+ const dansLeDossier = ["index.html", "index.md"].map((nom) => join(candidate, nom)).find((chemin) => existsSync(chemin) && statSync(chemin).isFile());
32
+ const file = existsSync(candidate) && statSync(candidate).isFile() ? candidate : dansLeDossier ?? join(config.outDir, "index.html");
29
33
  if (!file.startsWith(config.outDir)) {
30
34
  response.writeHead(403, { "Content-Type": "text/plain" });
31
35
  response.end("Interdit");
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { extractEntries, applyAlias, isBareSpecifier } from './chunk-G5QXVBYT.js';
2
+ import { extractEntries, applyAlias, isBareSpecifier, fournisseurDe } from './chunk-R6PMAMXA.js';
3
3
  import { info, colors, size, success, duration } from './chunk-JMEHF3KN.js';
4
4
  import { existsSync } from 'fs';
5
5
  import { readFile, rm, mkdir, writeFile, cp, stat } from 'fs/promises';
6
- import { join, basename, resolve, relative, sep, dirname } from 'path';
6
+ import { join, basename, resolve, relative, sep } from 'path';
7
7
  import { build } from 'esbuild';
8
- import { createRequire } from 'module';
9
- import { pathToFileURL } from 'url';
10
8
 
11
9
  // src/build/elaguer.ts
12
10
  var GROUPES = /* @__PURE__ */ new Set(["media", "supports", "layer", "container", "scope"]);
@@ -187,28 +185,6 @@ function decouperSelecteurs(selecteur) {
187
185
  parts.push(selecteur.slice(debut).trim());
188
186
  return parts.filter((p) => p.length > 0);
189
187
  }
190
- var PAQUET = "@odoro-cli/libs";
191
- var SOUS_CHEMIN = "./generateur";
192
- function fichierDe(entree) {
193
- if (entree === void 0) return void 0;
194
- if (typeof entree === "string") return entree;
195
- return entree.import ?? entree.default;
196
- }
197
- async function fournisseurDe(root) {
198
- try {
199
- const exiger = createRequire(join(root, "package.json"));
200
- const manifeste = exiger.resolve(`${PAQUET}/package.json`);
201
- const dossier = dirname(manifeste);
202
- const contenu = JSON.parse(await readFile(manifeste, "utf8"));
203
- const relatif = fichierDe(contenu.exports?.[SOUS_CHEMIN]);
204
- if (relatif === void 0) return void 0;
205
- const module_ = await import(pathToFileURL(resolve(dossier, relatif)).href);
206
- if (typeof module_.renderUtilitairesPour !== "function") return void 0;
207
- return { renderUtilitairesPour: module_.renderUtilitairesPour };
208
- } catch {
209
- return void 0;
210
- }
211
- }
212
188
 
213
189
  // src/build/build.ts
214
190
  function toPosix(path) {
@@ -24,7 +24,18 @@ async function importConfigFile(file, root) {
24
24
  write: false,
25
25
  // Seul le code du projet est inline ; ses dependances restent externes,
26
26
  // sans quoi il faudrait resoudre tout node_modules pour lire trois lignes.
27
- packages: "external"
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"]
28
39
  });
29
40
  const code = result.outputFiles[0]?.text;
30
41
  if (code === void 0) {
@@ -3,14 +3,44 @@ import { info, error, success, duration, colors, warn } from './chunk-JMEHF3KN.j
3
3
  import { existsSync, readFileSync, statSync, watch, createReadStream } from 'fs';
4
4
  import { readFile, rm, mkdir, writeFile } from 'fs/promises';
5
5
  import { createServer, request } from 'http';
6
- import { resolve, join, dirname, extname, relative } from 'path';
7
- import { createHash } from 'crypto';
6
+ import { join, dirname, resolve, extname, relative } from 'path';
8
7
  import { createRequire } from 'module';
9
- import { fileURLToPath, pathToFileURL } from 'url';
8
+ import { pathToFileURL, fileURLToPath } from 'url';
9
+ import { createHash } from 'crypto';
10
10
  import { build } from 'esbuild';
11
11
  import { transformAsync } from '@babel/core';
12
12
  import reactRefreshPlugin from 'react-refresh/babel';
13
13
 
14
+ var PAQUET = "@odoro-cli/libs";
15
+ var SOUS_CHEMIN = "./generateur";
16
+ function fichierDe(entree) {
17
+ if (entree === void 0) return void 0;
18
+ if (typeof entree === "string") return entree;
19
+ return entree.import ?? entree.default;
20
+ }
21
+ async function fournisseurDe(root) {
22
+ try {
23
+ const exiger = createRequire(join(root, "package.json"));
24
+ const manifeste = exiger.resolve(`${PAQUET}/package.json`);
25
+ const dossier = dirname(manifeste);
26
+ const contenu = JSON.parse(await readFile(manifeste, "utf8"));
27
+ const relatif = fichierDe(contenu.exports?.[SOUS_CHEMIN]);
28
+ if (relatif === void 0) return void 0;
29
+ const module_ = await import(pathToFileURL(resolve(dossier, relatif)).href);
30
+ if (typeof module_.renderUtilitairesPour !== "function") return void 0;
31
+ const generate = module_.generate;
32
+ return {
33
+ renderUtilitairesPour: module_.renderUtilitairesPour,
34
+ // `generate` enumere les classes du palier courant. Absent d'un paquet
35
+ // plus ancien : le champ reste alors indefini, et le developpement
36
+ // retombe sur la feuille telle qu'elle est livree.
37
+ ...typeof generate === "function" ? { classesConnues: () => new Set(generate("core").classNames) } : {}
38
+ };
39
+ } catch {
40
+ return void 0;
41
+ }
42
+ }
43
+
14
44
  // src/dev/client.ts
15
45
  var HMR_STREAM_PATH = "/@odoro/hmr";
16
46
  var HMR_CLIENT_PATH = "/@odoro/client";
@@ -266,6 +296,7 @@ function renderInteropProxy(info2) {
266
296
  }
267
297
  var DEPS_PREFIX = "/@deps/";
268
298
  var INTERNAL_PREFIX = "/@odoro/";
299
+ var SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
269
300
  var STYLE_EXTENSIONS = [".css"];
270
301
  var ASSET_EXTENSIONS = [
271
302
  ".svg",
@@ -827,6 +858,7 @@ async function bundleRefreshRuntime() {
827
858
  }
828
859
 
829
860
  // src/dev/server.ts
861
+ var UTILITAIRES_PATH = `${INTERNAL_PREFIX}utilitaires.css`;
830
862
  var MIME = {
831
863
  ".html": "text/html; charset=utf-8",
832
864
  ".js": "text/javascript; charset=utf-8",
@@ -845,7 +877,9 @@ var MIME = {
845
877
  ".woff2": "font/woff2",
846
878
  ".mp4": "video/mp4",
847
879
  ".webm": "video/webm",
848
- ".txt": "text/plain; charset=utf-8"
880
+ ".txt": "text/plain; charset=utf-8",
881
+ ".md": "text/markdown; charset=utf-8",
882
+ ".markdown": "text/markdown; charset=utf-8"
849
883
  };
850
884
  var SCRIPT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
851
885
  function cleanUrl(url) {
@@ -874,8 +908,11 @@ function extractEntries(html, root) {
874
908
  }
875
909
  return entries;
876
910
  }
877
- function injectClient(html) {
911
+ function injectClient(html, utilitaires = false) {
878
912
  const tags = [
913
+ // La feuille vient avant tout le reste : le socle et les styles de
914
+ // l'application, importes ensuite, doivent pouvoir la surcharger.
915
+ ...utilitaires ? [`<link rel="stylesheet" href="${UTILITAIRES_PATH}">`] : [],
879
916
  REFRESH_HTML_TAG,
880
917
  `<script type="module" src="${HMR_CLIENT_PATH}"></script>`
881
918
  ].join("\n ");
@@ -894,6 +931,11 @@ async function startDevServer(config) {
894
931
  throw new Error(`[odoro] Aucun "index.html" a la racine du projet (${config.root}).`);
895
932
  }
896
933
  const refreshRuntime = await bundleRefreshRuntime();
934
+ const fournisseur = await fournisseurDe(config.root);
935
+ const utilitaires = fournisseur?.classesConnues === void 0 ? void 0 : fournisseur.renderUtilitairesPour(fournisseur.classesConnues());
936
+ if (utilitaires !== void 0) {
937
+ info(`utilitaires de developpement : ${String(Math.round(utilitaires.length / 1024))} Ko`);
938
+ }
897
939
  const entries = extractEntries(await readFile(indexFile, "utf8"), config.root);
898
940
  const specifiers = await scanDependencies(config, entries);
899
941
  const deps = await optimizeDeps(config, specifiers);
@@ -948,6 +990,14 @@ async function startDevServer(config) {
948
990
  node.code = hotPreamble(url2) + wrapStyle(url2, css);
949
991
  send(response, node.code, MIME[".js"]);
950
992
  };
993
+ const indexDeDossier = (dossier) => {
994
+ if (!existsSync(dossier) || !statSync(dossier).isDirectory()) return void 0;
995
+ for (const nom of ["index.html", "index.md"]) {
996
+ const candidat = join(dossier, nom);
997
+ if (existsSync(candidat) && statSync(candidat).isFile()) return candidat;
998
+ }
999
+ return void 0;
1000
+ };
951
1001
  const serveFile = (response, file) => {
952
1002
  const type = MIME[extname(file).toLowerCase()] ?? "application/octet-stream";
953
1003
  response.writeHead(200, { "Content-Type": type, "Cache-Control": "no-cache" });
@@ -955,7 +1005,7 @@ async function startDevServer(config) {
955
1005
  };
956
1006
  const serveHtml = async (response) => {
957
1007
  const html = await readFile(indexFile, "utf8");
958
- send(response, injectClient(html), MIME[".html"]);
1008
+ send(response, injectClient(html, utilitaires !== void 0), MIME[".html"]);
959
1009
  };
960
1010
  const forward = (incoming, response, target) => {
961
1011
  const url2 = new URL(incoming.url ?? "/", target);
@@ -994,6 +1044,14 @@ async function startDevServer(config) {
994
1044
  send(response, refreshRuntime, MIME[".js"] ?? "text/javascript");
995
1045
  return;
996
1046
  }
1047
+ if (path === UTILITAIRES_PATH) {
1048
+ if (utilitaires === void 0) {
1049
+ send(response, "Introuvable", "text/plain", 404);
1050
+ return;
1051
+ }
1052
+ send(response, utilitaires, MIME[".css"] ?? "text/css");
1053
+ return;
1054
+ }
997
1055
  if (path === HMR_CLIENT_PATH) {
998
1056
  send(response, HMR_CLIENT_SOURCE, MIME[".js"] ?? "text/javascript");
999
1057
  return;
@@ -1063,6 +1121,11 @@ async function startDevServer(config) {
1063
1121
  serveFile(response, publicFile);
1064
1122
  return;
1065
1123
  }
1124
+ const index = indexDeDossier(publicFile);
1125
+ if (index !== void 0) {
1126
+ serveFile(response, index);
1127
+ return;
1128
+ }
1066
1129
  if (!extname(path)) {
1067
1130
  await serveHtml(response);
1068
1131
  return;
@@ -1098,7 +1161,12 @@ async function startDevServer(config) {
1098
1161
  }
1099
1162
  const boundaries = graph.invalidate(file);
1100
1163
  if (boundaries.length === 0) {
1101
- if (graph.get(file) !== void 0) reload = true;
1164
+ if (graph.get(file) !== void 0) {
1165
+ reload = true;
1166
+ } else if (SOURCE_EXTENSIONS.some((ext) => file.endsWith(ext))) {
1167
+ graph.clear();
1168
+ reload = true;
1169
+ }
1102
1170
  continue;
1103
1171
  }
1104
1172
  for (const boundary of boundaries) {
@@ -1136,4 +1204,4 @@ async function startDevServer(config) {
1136
1204
  };
1137
1205
  }
1138
1206
 
1139
- export { ModuleGraph, applyAlias, depFileName, detectSelfAccepting, extractEntries, injectClient, isBareSpecifier, optimizeDeps, scanDependencies, startDevServer };
1207
+ export { ModuleGraph, applyAlias, depFileName, detectSelfAccepting, extractEntries, fournisseurDe, injectClient, isBareSpecifier, optimizeDeps, scanDependencies, startDevServer };
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { loadConfig } from './chunk-Q35CGY2Z.js';
2
+ import { loadConfig } from './chunk-DL3NPC4H.js';
3
3
  import './chunk-T6RLHSCW.js';
4
4
  import { error } from './chunk-JMEHF3KN.js';
5
5
  import { realpathSync } from 'fs';
@@ -79,6 +79,7 @@ ${colors.bold("Options du registre")}
79
79
 
80
80
  ${colors.bold("Options de create")}
81
81
  --template <nom> Template a utiliser
82
+ --modules <liste> libs,router,icons,engine,registre \u2014 ou "aucun"
82
83
  --pm <nom> Gestionnaire de paquets (pnpm, npm, yarn, bun)
83
84
  --no-git N'initialise pas de depot git
84
85
  --no-install N'installe pas les dependances
@@ -130,7 +131,7 @@ function rootFrom(flags, positional) {
130
131
  async function run(argv) {
131
132
  const { command, positional, flags } = parseArgs(argv);
132
133
  if (flags["version"] === true) {
133
- const manifest = await import('./package-U7R7SC4H.js');
134
+ const manifest = await import('./package-Q2FV4K3H.js');
134
135
  console.log(manifest.default.version);
135
136
  return 0;
136
137
  }
@@ -141,11 +142,12 @@ async function run(argv) {
141
142
  switch (command) {
142
143
  case "create":
143
144
  case "new": {
144
- const { createCommand } = await import('./create-XJM3TUAQ.js');
145
+ const { createCommand } = await import('./create-IOFAPDBO.js');
145
146
  const options = {};
146
147
  if (positional[0] !== void 0) options.name = positional[0];
147
148
  if (typeof flags["template"] === "string") options.template = flags["template"];
148
149
  if (typeof flags["pm"] === "string") options.pm = flags["pm"];
150
+ if (typeof flags["modules"] === "string") options.modules = flags["modules"];
149
151
  if (typeof flags["git"] === "boolean") options.git = flags["git"];
150
152
  if (typeof flags["install"] === "boolean") options.install = flags["install"];
151
153
  if (flags["yes"] === true) options.yes = true;
@@ -154,20 +156,20 @@ async function run(argv) {
154
156
  return createCommand(options);
155
157
  }
156
158
  case "dev": {
157
- const { startDevServer } = await import('./server-MZ76LPAG.js');
159
+ const { startDevServer } = await import('./server-ATGFRVSW.js');
158
160
  const config = await loadConfig(rootFrom(flags, positional), overridesFrom(flags));
159
161
  await startDevServer(config);
160
162
  return new Promise(() => void 0);
161
163
  }
162
164
  case "build": {
163
- const { buildProject, reportBuild } = await import('./build-3SPCMZXZ.js');
165
+ const { buildProject, reportBuild } = await import('./build-GIPMPYJ3.js');
164
166
  const config = await loadConfig(rootFrom(flags, positional), overridesFrom(flags));
165
167
  const output = await buildProject(config);
166
168
  reportBuild(output, process.cwd());
167
169
  return 0;
168
170
  }
169
171
  case "preview": {
170
- const { startPreviewServer } = await import('./preview-LOAO5Y6V.js');
172
+ const { startPreviewServer } = await import('./preview-3K46Y23N.js');
171
173
  const config = await loadConfig(rootFrom(flags, positional), overridesFrom(flags));
172
174
  const port = numberFlag(flags, "port");
173
175
  await startPreviewServer(config, port);
@@ -8,6 +8,89 @@ import colors from 'picocolors';
8
8
  import { readdir, rm, mkdir, copyFile, readFile, writeFile } from 'fs/promises';
9
9
  import { fileURLToPath } from 'url';
10
10
 
11
+ // src/scaffold/modules.ts
12
+ var MODULES = [
13
+ {
14
+ id: "libs",
15
+ label: "Bibliotheques",
16
+ hint: "style, jetons, interface et animations",
17
+ defaut: true,
18
+ paquet: "@odoro-cli/libs"
19
+ },
20
+ {
21
+ id: "router",
22
+ label: "Routeur",
23
+ hint: "vient avec les bibliotheques \u2014 cable les pages",
24
+ defaut: true
25
+ },
26
+ {
27
+ id: "icons",
28
+ label: "Icones",
29
+ hint: "cinq familles, importables une a une",
30
+ defaut: true,
31
+ paquet: "@odoro-cli/icons"
32
+ },
33
+ {
34
+ id: "engine",
35
+ label: "Moteur",
36
+ hint: "WebGL, surfaces et politique de mouvement",
37
+ defaut: false,
38
+ paquet: "@odoro-cli/engine"
39
+ },
40
+ {
41
+ id: "registre",
42
+ label: "Registre de composants",
43
+ hint: "copies par `odoro add` \u2014 entraine le moteur",
44
+ defaut: false
45
+ }
46
+ ];
47
+ var MODULE_IDS = MODULES.map((m) => m.id);
48
+ var MODULES_PAR_DEFAUT = MODULES.filter(
49
+ (m) => m.defaut
50
+ ).map((m) => m.id);
51
+ function resoudre(selection) {
52
+ const choisis = new Set(selection);
53
+ const avertissements = [];
54
+ if (choisis.has("router") && !choisis.has("libs")) {
55
+ choisis.delete("router");
56
+ avertissements.push(
57
+ "Le routeur vient des bibliotheques (@odoro-cli/libs/router) : sans elles, il est retire."
58
+ );
59
+ }
60
+ if (choisis.has("registre") && !choisis.has("engine")) {
61
+ choisis.add("engine");
62
+ avertissements.push(
63
+ "Presque toutes les entrees du registre importent @odoro-cli/engine : le moteur est ajoute."
64
+ );
65
+ }
66
+ return { modules: MODULE_IDS.filter((id) => choisis.has(id)), avertissements };
67
+ }
68
+ function paquetsDe(modules) {
69
+ const choisis = new Set(modules);
70
+ return MODULES.filter((m) => m.paquet !== void 0 && choisis.has(m.id)).map(
71
+ (m) => m.paquet
72
+ );
73
+ }
74
+ function varianteDe(modules) {
75
+ const choisis = new Set(modules);
76
+ if (!choisis.has("libs")) return "sans-libs";
77
+ if (!choisis.has("router")) return "sans-routeur";
78
+ return void 0;
79
+ }
80
+ function gardeLesRoutes(modules) {
81
+ return modules.includes("router");
82
+ }
83
+ function lireModules(valeur) {
84
+ const brut = valeur.split(",").map((part) => part.trim()).filter((part) => part !== "");
85
+ if (brut.length === 1 && brut[0] === "aucun") return { modules: [] };
86
+ const inconnu = brut.find((part) => !MODULE_IDS.includes(part));
87
+ if (inconnu !== void 0) {
88
+ return {
89
+ erreur: `Module inconnu : "${inconnu}". Disponibles : ${MODULE_IDS.join(", ")}, ou "aucun".`
90
+ };
91
+ }
92
+ return { modules: brut };
93
+ }
11
94
  var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
12
95
  function detectPackageManager(userAgent = process.env["npm_config_user_agent"]) {
13
96
  if (userAgent === void 0) return "npm";
@@ -56,6 +139,8 @@ function availableTemplates(root = templatesRoot()) {
56
139
  }
57
140
 
58
141
  // src/scaffold/scaffold.ts
142
+ var DOSSIER_VARIANTES = "_variantes";
143
+ var PAQUETS_OPTIONNELS = ["@odoro-cli/libs", "@odoro-cli/icons", "@odoro-cli/engine"];
59
144
  function cliVersion() {
60
145
  try {
61
146
  const manifeste = join(dirname(templatesRoot()), "package.json");
@@ -68,6 +153,7 @@ function cliVersion() {
68
153
  async function copyDirectory(from, to, written, prefix = "") {
69
154
  await mkdir(to, { recursive: true });
70
155
  for (const entry of await readdir(from, { withFileTypes: true })) {
156
+ if (prefix === "" && entry.name === DOSSIER_VARIANTES) continue;
71
157
  const source = join(from, entry.name);
72
158
  const name = targetFileName(entry.name);
73
159
  const destination = join(to, name);
@@ -96,11 +182,35 @@ function alignOdoroVersions(manifest, version) {
96
182
  }
97
183
  return aligned;
98
184
  }
99
- async function renamePackage(target, packageName, version) {
185
+ function ajusterModules(manifest, modules) {
186
+ const voulus = new Set(paquetsDe(modules));
187
+ const deps = { ...manifest["dependencies"] ?? {} };
188
+ for (const paquet of PAQUETS_OPTIONNELS) {
189
+ if (voulus.has(paquet)) deps[paquet] ??= "latest";
190
+ else delete deps[paquet];
191
+ }
192
+ return {
193
+ ...manifest,
194
+ dependencies: Object.fromEntries(
195
+ Object.entries(deps).sort(([a], [b]) => a.localeCompare(b))
196
+ )
197
+ };
198
+ }
199
+ async function poserVariante(source, target, nom) {
200
+ const racine = join(source, DOSSIER_VARIANTES, nom);
201
+ if (!existsSync(racine)) {
202
+ throw new Error(`[odoro] Variante de gabarit introuvable : "${nom}".`);
203
+ }
204
+ const poses = [];
205
+ await copyDirectory(racine, target, poses);
206
+ return poses;
207
+ }
208
+ async function renamePackage(target, packageName, version, modules) {
100
209
  const file = join(target, "package.json");
101
210
  if (!existsSync(file)) return;
102
211
  const manifest = JSON.parse(await readFile(file, "utf8"));
103
- const renamed = alignOdoroVersions({ ...manifest, name: packageName }, version);
212
+ const ajuste = ajusterModules({ ...manifest, name: packageName }, modules);
213
+ const renamed = alignOdoroVersions(ajuste, version);
104
214
  await writeFile(file, `${JSON.stringify(renamed, null, 2)}
105
215
  `, "utf8");
106
216
  }
@@ -116,14 +226,29 @@ async function scaffold(options) {
116
226
  await rm(join(options.target, entry), { recursive: true, force: true });
117
227
  }
118
228
  }
229
+ const modules = options.modules ?? MODULES_PAR_DEFAUT;
119
230
  const files = [];
120
231
  await copyDirectory(source, options.target, files);
232
+ if (!gardeLesRoutes(modules)) {
233
+ for (const dossier of ["src/routes", "client/src/routes"]) {
234
+ await rm(join(options.target, dossier), { recursive: true, force: true });
235
+ }
236
+ }
237
+ const variante = varianteDe(modules);
238
+ const poses = variante === void 0 ? [] : await poserVariante(source, options.target, variante);
121
239
  await renamePackage(
122
240
  options.target,
123
241
  options.packageName,
124
- options.version ?? cliVersion()
242
+ options.version ?? cliVersion(),
243
+ modules
125
244
  );
126
- return { files };
245
+ const listes = /* @__PURE__ */ new Set([
246
+ ...files.filter(
247
+ (f) => !f.startsWith("src/routes/") && !f.startsWith("client/src/routes/")
248
+ ),
249
+ ...poses
250
+ ]);
251
+ return { files: [...listes].sort() };
127
252
  }
128
253
 
129
254
  // src/commands/create.ts
@@ -176,6 +301,30 @@ async function askDatabase() {
176
301
  note: "Forme de l URL verifiee. La connexion sera etablie au premier demarrage."
177
302
  };
178
303
  }
304
+ async function askModules(options) {
305
+ if (options.modules !== void 0) {
306
+ const lu = lireModules(options.modules);
307
+ if (lu.erreur !== void 0) {
308
+ prompts.cancel(lu.erreur);
309
+ return void 0;
310
+ }
311
+ return annoncer(resoudre(lu.modules));
312
+ }
313
+ if (options.yes === true) return MODULES_PAR_DEFAUT;
314
+ const choisis = ensure(
315
+ await prompts.multiselect({
316
+ message: "Que met-on dans le projet ?",
317
+ initialValues: [...MODULES_PAR_DEFAUT],
318
+ required: false,
319
+ options: MODULES.map((m) => ({ value: m.id, label: m.label, hint: m.hint }))
320
+ })
321
+ );
322
+ return annoncer(resoudre(choisis));
323
+ }
324
+ function annoncer(resolution) {
325
+ for (const mot of resolution.avertissements) prompts.log.warn(mot);
326
+ return resolution.modules;
327
+ }
179
328
  function ensure(value) {
180
329
  if (prompts.isCancel(value)) {
181
330
  prompts.cancel("Creation annulee.");
@@ -245,6 +394,8 @@ async function createCommand(options) {
245
394
  );
246
395
  return 1;
247
396
  }
397
+ const modules = await askModules(options);
398
+ if (modules === void 0) return 1;
248
399
  const detected = detectPackageManager();
249
400
  const manager = options.pm ?? (options.yes === true ? detected : ensure(
250
401
  await prompts.select({
@@ -277,6 +428,7 @@ async function createCommand(options) {
277
428
  root
278
429
  };
279
430
  if (overwrite !== void 0) scaffoldOptions.overwrite = overwrite;
431
+ scaffoldOptions.modules = modules;
280
432
  const { files } = await scaffold(scaffoldOptions);
281
433
  spinner2.stop(`${files.length} fichiers ecrits dans ${colors.cyan(basename(target))}`);
282
434
  if (withGit && !existsSync(resolve(target, ".git"))) {
@@ -294,6 +446,15 @@ async function createCommand(options) {
294
446
  if (risque !== void 0) prompts.log.warn(risque);
295
447
  }
296
448
  if (database !== void 0) prompts.log.info(database.note);
449
+ if (modules.includes("registre")) {
450
+ const { initCommand } = await import('./commands-PT3NS65O.js');
451
+ const code = await initCommand({ root: target, yes: true });
452
+ if (code !== 0) {
453
+ prompts.log.warn(
454
+ "Le registre n a pas pu etre configure. Relancez `odoro init` dans le projet."
455
+ );
456
+ }
457
+ }
297
458
  if (withInstall) {
298
459
  const install = prompts.spinner();
299
460
  install.start(`Installation avec ${manager}`);
@@ -307,6 +468,9 @@ async function createCommand(options) {
307
468
  const steps = [
308
469
  `cd ${basename(target)}`,
309
470
  ...withInstall ? [] : [installCommand(manager)],
471
+ // `odoro.json` vient d'etre ecrit : ce qui reste a montrer, c'est la
472
+ // commande qui s'en sert.
473
+ ...modules.includes("registre") ? ["odoro add text/count-up"] : [],
310
474
  runCommand(manager, "dev")
311
475
  ];
312
476
  prompts.note(steps.join("\n"), "Prochaines etapes");
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- export { defineConfig, loadConfig } from './chunk-Q35CGY2Z.js';
2
+ export { startPreviewServer } from './chunk-34ES2ICQ.js';
3
+ export { defineConfig, loadConfig } from './chunk-DL3NPC4H.js';
3
4
  import './chunk-T6RLHSCW.js';
4
- export { buildProject, reportBuild } from './chunk-HCU53TIK.js';
5
- export { ModuleGraph, depFileName, detectSelfAccepting, optimizeDeps, scanDependencies, startDevServer } from './chunk-G5QXVBYT.js';
6
- export { startPreviewServer } from './chunk-2YDI5NKV.js';
5
+ export { buildProject, reportBuild } from './chunk-CJIUP7A3.js';
6
+ export { ModuleGraph, depFileName, detectSelfAccepting, optimizeDeps, scanDependencies, startDevServer } from './chunk-R6PMAMXA.js';
7
7
  import './chunk-JMEHF3KN.js';
@@ -2,7 +2,7 @@
2
2
  // package.json
3
3
  var package_default = {
4
4
  name: "odoro",
5
- version: "0.3.0",
5
+ version: "0.3.4",
6
6
  type: "module",
7
7
  license: "UNLICENSED",
8
8
  author: "BouBouw",
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export { startPreviewServer } from './chunk-34ES2ICQ.js';
3
+ import './chunk-JMEHF3KN.js';
@@ -94,9 +94,9 @@ declare const baseSchema: z.ZodMiniObject<{
94
94
  }>]>>;
95
95
  notes: z.ZodMiniOptional<z.ZodMiniString<string>>;
96
96
  fallback: z.ZodMiniOptional<z.ZodMiniEnum<{
97
- static: "static";
98
97
  poster: "poster";
99
98
  gradient: "gradient";
99
+ static: "static";
100
100
  none: "none";
101
101
  }>>;
102
102
  }, z.core.$strip>;
@@ -165,9 +165,9 @@ declare const metaSchema: z.ZodMiniObject<{
165
165
  }>]>>;
166
166
  notes: z.ZodMiniOptional<z.ZodMiniString<string>>;
167
167
  fallback: z.ZodMiniOptional<z.ZodMiniEnum<{
168
- static: "static";
169
168
  poster: "poster";
170
169
  gradient: "gradient";
170
+ static: "static";
171
171
  none: "none";
172
172
  }>>;
173
173
  }, z.core.$strip>;
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- export { extractEntries, injectClient, startDevServer } from './chunk-G5QXVBYT.js';
2
+ export { extractEntries, injectClient, startDevServer } from './chunk-R6PMAMXA.js';
3
3
  import './chunk-JMEHF3KN.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odoro",
3
- "version": "0.3.0",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "author": "BouBouw",
@@ -0,0 +1,71 @@
1
+ import { useState } from 'react'
2
+
3
+ /** Trois arguments mis en avant. */
4
+ const FEATURES = [
5
+ {
6
+ title: 'Compilation',
7
+ body: 'Un serveur de developpement a rechargement a chaud, et une compilation de production.',
8
+ },
9
+ {
10
+ title: 'TypeScript',
11
+ body: 'Types stricts, alias de chemins, et verification sans emission.',
12
+ },
13
+ {
14
+ title: 'A vous de jouer',
15
+ body: 'Aucun systeme de style impose : le votre prend toute la place.',
16
+ },
17
+ ]
18
+
19
+ /**
20
+ * Racine de l'application.
21
+ *
22
+ * Les bibliotheques Odoro n'ont pas ete retenues a la creation : pas de
23
+ * classes `o-*`, pas de jetons, pas de routeur. Les styles de cette page sont
24
+ * dans `styles.css`, en CSS ordinaire.
25
+ *
26
+ * Pour les ajouter plus tard : `npm i @odoro-cli/libs`, puis importer
27
+ * `@odoro-cli/libs/styles.css` dans `main.tsx`.
28
+ */
29
+ export function App() {
30
+ const [count, setCount] = useState(0)
31
+
32
+ return (
33
+ <div className="app-shell">
34
+ <nav className="nav">
35
+ <span className="marque">Odoro</span>
36
+ </nav>
37
+
38
+ <main className="contenu">
39
+ <h1>Un point de depart maitrise.</h1>
40
+ <p className="chapeau">
41
+ Ce projet a ete genere par <code>odoro create</code>. Le moteur de
42
+ developpement et la compilation viennent de <code>odoro</code>.
43
+ </p>
44
+
45
+ <div className="grille">
46
+ {FEATURES.map((feature) => (
47
+ <article key={feature.title} className="carte">
48
+ <h2>{feature.title}</h2>
49
+ <p>{feature.body}</p>
50
+ </article>
51
+ ))}
52
+ </div>
53
+
54
+ <div className="ligne">
55
+ <button
56
+ type="button"
57
+ className="bouton"
58
+ onClick={() => {
59
+ setCount((value) => value + 1)
60
+ }}
61
+ >
62
+ Compter
63
+ </button>
64
+ <span className="compteur">{count}</span>
65
+ </div>
66
+ </main>
67
+
68
+ <footer className="pied">Construit avec Odoro.</footer>
69
+ </div>
70
+ )
71
+ }
@@ -0,0 +1,17 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+
4
+ import { App } from '@/App'
5
+
6
+ import '@/styles.css'
7
+
8
+ const container = document.getElementById('root')
9
+ if (container === null) {
10
+ throw new Error('Element racine "#root" introuvable dans index.html.')
11
+ }
12
+
13
+ createRoot(container).render(
14
+ <StrictMode>
15
+ <App />
16
+ </StrictMode>,
17
+ )
@@ -0,0 +1,138 @@
1
+ /* Styles de l'application.
2
+
3
+ Les bibliotheques Odoro n'ont pas ete retenues a la creation : il n'y a donc
4
+ ni jetons ni classes utilitaires, et cette feuille porte tout. C'est du CSS
5
+ ordinaire, a remplacer par ce que vous voulez.
6
+
7
+ Pour passer aux jetons plus tard : `npm i @odoro-cli/libs`, puis importer
8
+ `@odoro-cli/libs/styles.css` dans `main.tsx` avant cette feuille. */
9
+
10
+ :root {
11
+ --fond: #ffffff;
12
+ --encre: #18181b;
13
+ --doux: #71717a;
14
+ --filet: #e4e4e7;
15
+ --accent: #4f46e5;
16
+ color-scheme: light dark;
17
+ }
18
+
19
+ /* La preference du systeme, faute d'un selecteur de theme : c'est ce que les
20
+ jetons apporteraient, et l'absence de bibliotheque ne doit pas se payer d'un
21
+ fond blanc au milieu de la nuit. */
22
+ @media (prefers-color-scheme: dark) {
23
+ :root {
24
+ --fond: #09090b;
25
+ --encre: #fafafa;
26
+ --doux: #a1a1aa;
27
+ --filet: #27272a;
28
+ --accent: #818cf8;
29
+ }
30
+ }
31
+
32
+ * {
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ body {
37
+ margin: 0;
38
+ background: var(--fond);
39
+ color: var(--encre);
40
+ font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
41
+ line-height: 1.6;
42
+ }
43
+
44
+ .app-shell {
45
+ min-height: 100dvh;
46
+ display: grid;
47
+ grid-template-rows: auto 1fr auto;
48
+ }
49
+
50
+ .nav,
51
+ .pied {
52
+ padding: 1rem 1.5rem;
53
+ }
54
+
55
+ .nav {
56
+ border-bottom: 1px solid var(--filet);
57
+ }
58
+
59
+ .pied {
60
+ border-top: 1px solid var(--filet);
61
+ color: var(--doux);
62
+ font-size: 0.875rem;
63
+ }
64
+
65
+ .marque {
66
+ font-weight: 600;
67
+ color: var(--accent);
68
+ }
69
+
70
+ .contenu {
71
+ width: 100%;
72
+ max-width: 48rem;
73
+ margin: 0 auto;
74
+ padding: 3rem 1.5rem;
75
+ }
76
+
77
+ h1 {
78
+ font-size: 2.25rem;
79
+ letter-spacing: -0.02em;
80
+ margin: 0;
81
+ }
82
+
83
+ .chapeau {
84
+ color: var(--doux);
85
+ font-size: 1.125rem;
86
+ max-width: 60ch;
87
+ }
88
+
89
+ code {
90
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
91
+ font-size: 0.9em;
92
+ }
93
+
94
+ .grille {
95
+ display: grid;
96
+ gap: 1rem;
97
+ grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
98
+ margin: 3rem 0;
99
+ }
100
+
101
+ .carte {
102
+ border: 1px solid var(--filet);
103
+ border-radius: 0.5rem;
104
+ padding: 1.25rem;
105
+ }
106
+
107
+ .carte h2 {
108
+ font-size: 1rem;
109
+ margin: 0;
110
+ }
111
+
112
+ .carte p {
113
+ color: var(--doux);
114
+ font-size: 0.875rem;
115
+ margin: 0.5rem 0 0;
116
+ }
117
+
118
+ .ligne {
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 1rem;
122
+ }
123
+
124
+ .bouton {
125
+ background: var(--accent);
126
+ color: var(--fond);
127
+ border: 0;
128
+ border-radius: 0.5rem;
129
+ padding: 0.5rem 1rem;
130
+ font: inherit;
131
+ font-weight: 500;
132
+ cursor: pointer;
133
+ }
134
+
135
+ .compteur {
136
+ font-size: 1.125rem;
137
+ font-variant-numeric: tabular-nums;
138
+ }
@@ -0,0 +1,93 @@
1
+ import { useState } from 'react'
2
+
3
+ import { Reveal, Stagger, useAnimate } from '@odoro-cli/libs/motion'
4
+ import { Button } from '@odoro-cli/libs/ui'
5
+
6
+ /** Trois arguments mis en avant. */
7
+ const FEATURES = [
8
+ {
9
+ title: 'Animations',
10
+ body: "Une couche mince sur le moteur du navigateur. Les animations reduites sont respectees d'office.",
11
+ },
12
+ {
13
+ title: 'Styles',
14
+ body: 'Des tokens en source de verite, une feuille statique, aucun scan a l execution.',
15
+ },
16
+ {
17
+ title: 'Interface',
18
+ body: 'Des composants qui lisent les memes tokens que votre code : les rethemer suffit.',
19
+ },
20
+ ]
21
+
22
+ /**
23
+ * Racine de l'application.
24
+ *
25
+ * Une seule page : le routeur n'a pas ete retenu a la creation. Pour en
26
+ * ajouter un plus tard, `@odoro-cli/libs/router` est deja installe — il vient
27
+ * avec les bibliotheques.
28
+ */
29
+ export function App() {
30
+ const [ref, controls] = useAnimate<HTMLSpanElement>()
31
+ const [count, setCount] = useState(0)
32
+
33
+ return (
34
+ <div className="app-shell">
35
+ <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">
36
+ <span className="o-font-semibold o-text-brand-600 dark:o-text-brand-400">
37
+ Odoro
38
+ </span>
39
+ </nav>
40
+
41
+ <main className="o-mx-auto o-w-full o-max-w-3xl o-px-6 o-py-12 o-flex o-flex-col o-gap-12">
42
+ <Reveal>
43
+ <h1 className="o-text-4xl o-font-bold o-tracking-tight">
44
+ Un point de depart maitrise.
45
+ </h1>
46
+ <p className="o-mt-4 o-text-lg o-text-zinc-500 dark:o-text-zinc-400 o-max-w-prose">
47
+ Ce projet a ete genere par <code className="o-font-mono">odoro create</code>.
48
+ Le moteur d animation et le systeme de style viennent de la meme librairie.
49
+ </p>
50
+ </Reveal>
51
+
52
+ <Stagger step={80} className="o-grid o-grid-cols-1 md:o-grid-cols-3 o-gap-4">
53
+ {FEATURES.map((feature) => (
54
+ <article
55
+ key={feature.title}
56
+ className="o-rounded-lg o-border-w-1 o-border-zinc-200 dark:o-border-zinc-800 o-bg-white dark:o-bg-zinc-900 o-p-5"
57
+ >
58
+ <h2 className="o-font-semibold">{feature.title}</h2>
59
+ <p className="o-mt-2 o-text-sm o-text-zinc-500 dark:o-text-zinc-400">
60
+ {feature.body}
61
+ </p>
62
+ </article>
63
+ ))}
64
+ </Stagger>
65
+
66
+ <div className="o-flex o-items-center o-gap-4">
67
+ <Button
68
+ onClick={() => {
69
+ setCount((value) => value + 1)
70
+ void controls.play(
71
+ [
72
+ { transform: 'translateY(0)' },
73
+ { transform: 'translateY(-0.4rem)' },
74
+ { transform: 'translateY(0)' },
75
+ ],
76
+ { duration: 'fast', easing: 'emphasized' },
77
+ )
78
+ }}
79
+ >
80
+ Compter
81
+ </Button>
82
+ <span ref={ref} className="o-inline-block o-text-lg o-tabular-nums">
83
+ {count}
84
+ </span>
85
+ </div>
86
+ </main>
87
+
88
+ <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">
89
+ Construit avec Odoro.
90
+ </footer>
91
+ </div>
92
+ )
93
+ }
@@ -18,8 +18,7 @@
18
18
  "skipLibCheck": true,
19
19
  "paths": {
20
20
  "@/*": ["./src/*"]
21
- },
22
- "baseUrl": "."
21
+ }
23
22
  },
24
23
  "include": ["src", "odoro.config.ts"]
25
24
  }
@@ -0,0 +1,71 @@
1
+ import { useState } from 'react'
2
+
3
+ /** Trois arguments mis en avant. */
4
+ const FEATURES = [
5
+ {
6
+ title: 'Compilation',
7
+ body: 'Un serveur de developpement a rechargement a chaud, et une compilation de production.',
8
+ },
9
+ {
10
+ title: 'TypeScript',
11
+ body: 'Types stricts, alias de chemins, et verification sans emission.',
12
+ },
13
+ {
14
+ title: 'A vous de jouer',
15
+ body: 'Aucun systeme de style impose : le votre prend toute la place.',
16
+ },
17
+ ]
18
+
19
+ /**
20
+ * Racine de l'application.
21
+ *
22
+ * Les bibliotheques Odoro n'ont pas ete retenues a la creation : pas de
23
+ * classes `o-*`, pas de jetons, pas de routeur. Les styles de cette page sont
24
+ * dans `styles.css`, en CSS ordinaire.
25
+ *
26
+ * Pour les ajouter plus tard : `npm i @odoro-cli/libs`, puis importer
27
+ * `@odoro-cli/libs/styles.css` dans `main.tsx`.
28
+ */
29
+ export function App() {
30
+ const [count, setCount] = useState(0)
31
+
32
+ return (
33
+ <div className="app-shell">
34
+ <nav className="nav">
35
+ <span className="marque">Odoro</span>
36
+ </nav>
37
+
38
+ <main className="contenu">
39
+ <h1>Un point de depart maitrise.</h1>
40
+ <p className="chapeau">
41
+ Ce projet a ete genere par <code>odoro create</code>. Le moteur de
42
+ developpement et la compilation viennent de <code>odoro</code>.
43
+ </p>
44
+
45
+ <div className="grille">
46
+ {FEATURES.map((feature) => (
47
+ <article key={feature.title} className="carte">
48
+ <h2>{feature.title}</h2>
49
+ <p>{feature.body}</p>
50
+ </article>
51
+ ))}
52
+ </div>
53
+
54
+ <div className="ligne">
55
+ <button
56
+ type="button"
57
+ className="bouton"
58
+ onClick={() => {
59
+ setCount((value) => value + 1)
60
+ }}
61
+ >
62
+ Compter
63
+ </button>
64
+ <span className="compteur">{count}</span>
65
+ </div>
66
+ </main>
67
+
68
+ <footer className="pied">Construit avec Odoro.</footer>
69
+ </div>
70
+ )
71
+ }
@@ -0,0 +1,17 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+
4
+ import { App } from '@/App'
5
+
6
+ import '@/styles.css'
7
+
8
+ const container = document.getElementById('root')
9
+ if (container === null) {
10
+ throw new Error('Element racine "#root" introuvable dans index.html.')
11
+ }
12
+
13
+ createRoot(container).render(
14
+ <StrictMode>
15
+ <App />
16
+ </StrictMode>,
17
+ )
@@ -0,0 +1,138 @@
1
+ /* Styles de l'application.
2
+
3
+ Les bibliotheques Odoro n'ont pas ete retenues a la creation : il n'y a donc
4
+ ni jetons ni classes utilitaires, et cette feuille porte tout. C'est du CSS
5
+ ordinaire, a remplacer par ce que vous voulez.
6
+
7
+ Pour passer aux jetons plus tard : `npm i @odoro-cli/libs`, puis importer
8
+ `@odoro-cli/libs/styles.css` dans `main.tsx` avant cette feuille. */
9
+
10
+ :root {
11
+ --fond: #ffffff;
12
+ --encre: #18181b;
13
+ --doux: #71717a;
14
+ --filet: #e4e4e7;
15
+ --accent: #4f46e5;
16
+ color-scheme: light dark;
17
+ }
18
+
19
+ /* La preference du systeme, faute d'un selecteur de theme : c'est ce que les
20
+ jetons apporteraient, et l'absence de bibliotheque ne doit pas se payer d'un
21
+ fond blanc au milieu de la nuit. */
22
+ @media (prefers-color-scheme: dark) {
23
+ :root {
24
+ --fond: #09090b;
25
+ --encre: #fafafa;
26
+ --doux: #a1a1aa;
27
+ --filet: #27272a;
28
+ --accent: #818cf8;
29
+ }
30
+ }
31
+
32
+ * {
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ body {
37
+ margin: 0;
38
+ background: var(--fond);
39
+ color: var(--encre);
40
+ font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
41
+ line-height: 1.6;
42
+ }
43
+
44
+ .app-shell {
45
+ min-height: 100dvh;
46
+ display: grid;
47
+ grid-template-rows: auto 1fr auto;
48
+ }
49
+
50
+ .nav,
51
+ .pied {
52
+ padding: 1rem 1.5rem;
53
+ }
54
+
55
+ .nav {
56
+ border-bottom: 1px solid var(--filet);
57
+ }
58
+
59
+ .pied {
60
+ border-top: 1px solid var(--filet);
61
+ color: var(--doux);
62
+ font-size: 0.875rem;
63
+ }
64
+
65
+ .marque {
66
+ font-weight: 600;
67
+ color: var(--accent);
68
+ }
69
+
70
+ .contenu {
71
+ width: 100%;
72
+ max-width: 48rem;
73
+ margin: 0 auto;
74
+ padding: 3rem 1.5rem;
75
+ }
76
+
77
+ h1 {
78
+ font-size: 2.25rem;
79
+ letter-spacing: -0.02em;
80
+ margin: 0;
81
+ }
82
+
83
+ .chapeau {
84
+ color: var(--doux);
85
+ font-size: 1.125rem;
86
+ max-width: 60ch;
87
+ }
88
+
89
+ code {
90
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
91
+ font-size: 0.9em;
92
+ }
93
+
94
+ .grille {
95
+ display: grid;
96
+ gap: 1rem;
97
+ grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
98
+ margin: 3rem 0;
99
+ }
100
+
101
+ .carte {
102
+ border: 1px solid var(--filet);
103
+ border-radius: 0.5rem;
104
+ padding: 1.25rem;
105
+ }
106
+
107
+ .carte h2 {
108
+ font-size: 1rem;
109
+ margin: 0;
110
+ }
111
+
112
+ .carte p {
113
+ color: var(--doux);
114
+ font-size: 0.875rem;
115
+ margin: 0.5rem 0 0;
116
+ }
117
+
118
+ .ligne {
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 1rem;
122
+ }
123
+
124
+ .bouton {
125
+ background: var(--accent);
126
+ color: var(--fond);
127
+ border: 0;
128
+ border-radius: 0.5rem;
129
+ padding: 0.5rem 1rem;
130
+ font: inherit;
131
+ font-weight: 500;
132
+ cursor: pointer;
133
+ }
134
+
135
+ .compteur {
136
+ font-size: 1.125rem;
137
+ font-variant-numeric: tabular-nums;
138
+ }
@@ -0,0 +1,93 @@
1
+ import { useState } from 'react'
2
+
3
+ import { Reveal, Stagger, useAnimate } from '@odoro-cli/libs/motion'
4
+ import { Button } from '@odoro-cli/libs/ui'
5
+
6
+ /** Trois arguments mis en avant. */
7
+ const FEATURES = [
8
+ {
9
+ title: 'Animations',
10
+ body: "Une couche mince sur le moteur du navigateur. Les animations reduites sont respectees d'office.",
11
+ },
12
+ {
13
+ title: 'Styles',
14
+ body: 'Des tokens en source de verite, une feuille statique, aucun scan a l execution.',
15
+ },
16
+ {
17
+ title: 'Interface',
18
+ body: 'Des composants qui lisent les memes tokens que votre code : les rethemer suffit.',
19
+ },
20
+ ]
21
+
22
+ /**
23
+ * Racine de l'application.
24
+ *
25
+ * Une seule page : le routeur n'a pas ete retenu a la creation. Pour en
26
+ * ajouter un plus tard, `@odoro-cli/libs/router` est deja installe — il vient
27
+ * avec les bibliotheques.
28
+ */
29
+ export function App() {
30
+ const [ref, controls] = useAnimate<HTMLSpanElement>()
31
+ const [count, setCount] = useState(0)
32
+
33
+ return (
34
+ <div className="app-shell">
35
+ <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">
36
+ <span className="o-font-semibold o-text-brand-600 dark:o-text-brand-400">
37
+ Odoro
38
+ </span>
39
+ </nav>
40
+
41
+ <main className="o-mx-auto o-w-full o-max-w-3xl o-px-6 o-py-12 o-flex o-flex-col o-gap-12">
42
+ <Reveal>
43
+ <h1 className="o-text-4xl o-font-bold o-tracking-tight">
44
+ Un point de depart maitrise.
45
+ </h1>
46
+ <p className="o-mt-4 o-text-lg o-text-zinc-500 dark:o-text-zinc-400 o-max-w-prose">
47
+ Ce projet a ete genere par <code className="o-font-mono">odoro create</code>.
48
+ Le moteur d animation et le systeme de style viennent de la meme librairie.
49
+ </p>
50
+ </Reveal>
51
+
52
+ <Stagger step={80} className="o-grid o-grid-cols-1 md:o-grid-cols-3 o-gap-4">
53
+ {FEATURES.map((feature) => (
54
+ <article
55
+ key={feature.title}
56
+ className="o-rounded-lg o-border-w-1 o-border-zinc-200 dark:o-border-zinc-800 o-bg-white dark:o-bg-zinc-900 o-p-5"
57
+ >
58
+ <h2 className="o-font-semibold">{feature.title}</h2>
59
+ <p className="o-mt-2 o-text-sm o-text-zinc-500 dark:o-text-zinc-400">
60
+ {feature.body}
61
+ </p>
62
+ </article>
63
+ ))}
64
+ </Stagger>
65
+
66
+ <div className="o-flex o-items-center o-gap-4">
67
+ <Button
68
+ onClick={() => {
69
+ setCount((value) => value + 1)
70
+ void controls.play(
71
+ [
72
+ { transform: 'translateY(0)' },
73
+ { transform: 'translateY(-0.4rem)' },
74
+ { transform: 'translateY(0)' },
75
+ ],
76
+ { duration: 'fast', easing: 'emphasized' },
77
+ )
78
+ }}
79
+ >
80
+ Compter
81
+ </Button>
82
+ <span ref={ref} className="o-inline-block o-text-lg o-tabular-nums">
83
+ {count}
84
+ </span>
85
+ </div>
86
+ </main>
87
+
88
+ <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">
89
+ Construit avec Odoro.
90
+ </footer>
91
+ </div>
92
+ )
93
+ }
@@ -18,7 +18,6 @@
18
18
  "noFallthroughCasesInSwitch": true,
19
19
 
20
20
  "skipLibCheck": true,
21
- "baseUrl": ".",
22
21
  "paths": {
23
22
  "@/*": ["./client/src/*"]
24
23
  }
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- export { buildProject, reportBuild } from './chunk-HCU53TIK.js';
3
- import './chunk-G5QXVBYT.js';
4
- import './chunk-JMEHF3KN.js';
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export { startPreviewServer } from './chunk-2YDI5NKV.js';
3
- import './chunk-JMEHF3KN.js';