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.
- package/dist/build-GIPMPYJ3.js +4 -0
- package/dist/{chunk-2YDI5NKV.js → chunk-34ES2ICQ.js} +6 -2
- package/dist/{chunk-HCU53TIK.js → chunk-CJIUP7A3.js} +2 -26
- package/dist/{chunk-Q35CGY2Z.js → chunk-DL3NPC4H.js} +12 -1
- package/dist/{chunk-G5QXVBYT.js → chunk-R6PMAMXA.js} +76 -8
- package/dist/cli.js +8 -6
- package/dist/{create-XJM3TUAQ.js → create-IOFAPDBO.js} +168 -4
- package/dist/index.js +4 -4
- package/dist/{package-U7R7SC4H.js → package-Q2FV4K3H.js} +1 -1
- package/dist/preview-3K46Y23N.js +3 -0
- package/dist/registry/index.d.ts +2 -2
- package/dist/{server-MZ76LPAG.js → server-ATGFRVSW.js} +1 -1
- package/package.json +1 -1
- package/templates/react-ts/_variantes/sans-libs/src/App.tsx +71 -0
- package/templates/react-ts/_variantes/sans-libs/src/main.tsx +17 -0
- package/templates/react-ts/_variantes/sans-libs/src/styles.css +138 -0
- package/templates/react-ts/_variantes/sans-routeur/src/App.tsx +93 -0
- package/templates/react-ts/tsconfig.json +1 -2
- package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +71 -0
- package/templates/react-ts-server/_variantes/sans-libs/client/src/main.tsx +17 -0
- package/templates/react-ts-server/_variantes/sans-libs/client/src/styles.css +138 -0
- package/templates/react-ts-server/_variantes/sans-routeur/client/src/App.tsx +93 -0
- package/templates/react-ts-server/tsconfig.json +0 -1
- package/dist/build-3SPCMZXZ.js +0 -4
- package/dist/preview-LOAO5Y6V.js +0 -3
|
@@ -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
|
|
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-
|
|
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
|
|
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 {
|
|
7
|
-
import { createHash } from 'crypto';
|
|
6
|
+
import { join, dirname, resolve, extname, relative } from 'path';
|
|
8
7
|
import { createRequire } from 'module';
|
|
9
|
-
import {
|
|
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)
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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-
|
|
5
|
-
export { ModuleGraph, depFileName, detectSelfAccepting, optimizeDeps, scanDependencies, startDevServer } from './chunk-
|
|
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';
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -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>;
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/build-3SPCMZXZ.js
DELETED
package/dist/preview-LOAO5Y6V.js
DELETED