odoro 0.1.2 → 0.1.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.
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- export { buildProject, reportBuild } from './chunk-UGRSODHU.js';
2
+ export { buildProject, reportBuild } from './chunk-JGXJBVI7.js';
3
3
  import './chunk-G5QXVBYT.js';
4
4
  import './chunk-JMEHF3KN.js';
@@ -52,9 +52,9 @@ async function assertEnvIgnored(target) {
52
52
  return ignored ? void 0 : ".env ne figure pas dans le .gitignore : vos identifiants risquent d etre versionnes.";
53
53
  }
54
54
  var PROVIDER_PENDING = [
55
- "Le provisionnement passe par @odoro/cloud-sdk, installe a part :",
55
+ "Le provisionnement passe par @odoro-cli/cloud-sdk, installe a part :",
56
56
  "",
57
- " npm install --save-dev @odoro/cloud-sdk",
57
+ " npm install --save-dev @odoro-cli/cloud-sdk",
58
58
  " odoro db:login",
59
59
  " odoro db:create --env production",
60
60
  "",
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env node
2
+ import { extractEntries, applyAlias, isBareSpecifier } from './chunk-G5QXVBYT.js';
3
+ import { info, colors, size, success, duration } from './chunk-JMEHF3KN.js';
4
+ import { existsSync } from 'fs';
5
+ import { readFile, rm, mkdir, writeFile, cp, stat } from 'fs/promises';
6
+ import { join, basename, resolve, relative, sep } from 'path';
7
+ import { build } from 'esbuild';
8
+ import { createRequire } from 'module';
9
+ import { pathToFileURL } from 'url';
10
+
11
+ // src/build/elaguer.ts
12
+ var GROUPES = /* @__PURE__ */ new Set(["media", "supports", "layer", "container", "scope"]);
13
+ function analyser(css) {
14
+ const noeuds = [];
15
+ let i = 0;
16
+ let debut = 0;
17
+ function avancer() {
18
+ const c = css[i];
19
+ if (c === "\\") {
20
+ i += 2;
21
+ return;
22
+ }
23
+ if (c === "/" && css[i + 1] === "*") {
24
+ const fin = css.indexOf("*/", i + 2);
25
+ i = fin === -1 ? css.length : fin + 2;
26
+ return;
27
+ }
28
+ if (c === '"' || c === "'") {
29
+ i += 1;
30
+ while (i < css.length && css[i] !== c) {
31
+ i += css[i] === "\\" ? 2 : 1;
32
+ }
33
+ i += 1;
34
+ return;
35
+ }
36
+ i += 1;
37
+ }
38
+ while (i < css.length) {
39
+ const c = css[i];
40
+ if (c === "{") {
41
+ const prelude = css.slice(debut, i).trim();
42
+ let profondeur = 1;
43
+ i += 1;
44
+ const debutCorps = i;
45
+ while (i < css.length && profondeur > 0) {
46
+ const d = css[i];
47
+ if (d === "{") {
48
+ profondeur += 1;
49
+ i += 1;
50
+ } else if (d === "}") {
51
+ profondeur -= 1;
52
+ i += 1;
53
+ } else {
54
+ avancer();
55
+ }
56
+ }
57
+ const corps = css.slice(debutCorps, i - 1);
58
+ if (prelude.startsWith("@")) {
59
+ const nom = /^@([a-zA-Z-]+)/.exec(prelude)?.[1] ?? "";
60
+ noeuds.push(
61
+ GROUPES.has(nom) ? { sorte: "groupe", prelude, enfants: analyser(corps) } : (
62
+ // `@keyframes`, `@font-face`, `@property` : recopies tels quels.
63
+ { sorte: "brut", texte: `${prelude}{${corps}}` }
64
+ )
65
+ );
66
+ } else if (prelude.length > 0) {
67
+ noeuds.push({ sorte: "regle", selecteur: prelude, corps });
68
+ }
69
+ debut = i;
70
+ continue;
71
+ }
72
+ if (c === ";" && css.slice(debut, i).trim().startsWith("@")) {
73
+ noeuds.push({ sorte: "brut", texte: `${css.slice(debut, i).trim()};` });
74
+ i += 1;
75
+ debut = i;
76
+ continue;
77
+ }
78
+ avancer();
79
+ }
80
+ return noeuds;
81
+ }
82
+ function ecrire(noeuds) {
83
+ return noeuds.map((n) => {
84
+ if (n.sorte === "brut") return n.texte;
85
+ if (n.sorte === "regle") return `${n.selecteur}{${n.corps}}`;
86
+ return `${n.prelude}{${ecrire(n.enfants)}}`;
87
+ }).join("\n");
88
+ }
89
+ function classesDe(selecteur) {
90
+ const classes = [];
91
+ const motif = /(?<!\\)\.((?:\\.|[\w-])+)/g;
92
+ for (const trouve of selecteur.matchAll(motif)) {
93
+ classes.push(trouve[1].replaceAll(/\\(.)/g, "$1"));
94
+ }
95
+ return classes;
96
+ }
97
+ function motsDe(texte) {
98
+ const mots = /* @__PURE__ */ new Set();
99
+ const motif = /[A-Za-z0-9_][A-Za-z0-9_:/.[\]%@-]*/g;
100
+ for (const trouve of texte.matchAll(motif)) mots.add(trouve[0]);
101
+ return mots;
102
+ }
103
+ function estUtilitaire(classe, prefixe) {
104
+ const nue = classe.slice(classe.lastIndexOf(":") + 1);
105
+ return nue.startsWith(prefixe);
106
+ }
107
+ function gardee(classe, employes, sauvegarde) {
108
+ if (employes.has(classe)) return true;
109
+ for (const regle of sauvegarde) {
110
+ if (typeof regle === "string" ? regle === classe : regle.test(classe)) return true;
111
+ }
112
+ return false;
113
+ }
114
+ function elaguer(css, sources, options = {}) {
115
+ const prefixe = options.prefixe ?? "o-";
116
+ const sauvegarde = options.sauvegarde ?? [];
117
+ const employes = /* @__PURE__ */ new Set();
118
+ for (const source of sources) {
119
+ for (const mot of motsDe(source)) employes.add(mot);
120
+ }
121
+ const vues = /* @__PURE__ */ new Set();
122
+ const survivantes = /* @__PURE__ */ new Set();
123
+ function filtrerSelecteur(selecteur) {
124
+ const retenus = decouperSelecteurs(selecteur).filter((un) => {
125
+ const utilitaires = classesDe(un).filter((c) => estUtilitaire(c, prefixe));
126
+ if (utilitaires.length === 0) return true;
127
+ for (const classe of utilitaires) vues.add(classe);
128
+ const survit = utilitaires.every((c) => gardee(c, employes, sauvegarde));
129
+ if (survit) for (const classe of utilitaires) survivantes.add(classe);
130
+ return survit;
131
+ });
132
+ return retenus.length === 0 ? void 0 : retenus.join(",");
133
+ }
134
+ function filtrer(noeuds) {
135
+ const retenus = [];
136
+ for (const noeud of noeuds) {
137
+ if (noeud.sorte === "brut") {
138
+ retenus.push(noeud);
139
+ continue;
140
+ }
141
+ if (noeud.sorte === "regle") {
142
+ const selecteur = filtrerSelecteur(noeud.selecteur);
143
+ if (selecteur !== void 0) retenus.push({ ...noeud, selecteur });
144
+ continue;
145
+ }
146
+ const enfants = filtrer(noeud.enfants);
147
+ if (enfants.length > 0) retenus.push({ ...noeud, enfants });
148
+ }
149
+ return retenus;
150
+ }
151
+ const elaguee = ecrire(filtrer(analyser(css)));
152
+ return {
153
+ css: elaguee,
154
+ octetsAvant: Buffer.byteLength(css),
155
+ octetsApres: Buffer.byteLength(elaguee),
156
+ gardees: survivantes.size,
157
+ retirees: vues.size - survivantes.size
158
+ };
159
+ }
160
+ function decouperSelecteurs(selecteur) {
161
+ const parts = [];
162
+ let profondeur = 0;
163
+ let debut = 0;
164
+ let i = 0;
165
+ while (i < selecteur.length) {
166
+ const c = selecteur[i];
167
+ if (c === "\\") {
168
+ i += 2;
169
+ continue;
170
+ }
171
+ if (c === '"' || c === "'") {
172
+ i += 1;
173
+ while (i < selecteur.length && selecteur[i] !== c) {
174
+ i += selecteur[i] === "\\" ? 2 : 1;
175
+ }
176
+ i += 1;
177
+ continue;
178
+ }
179
+ if (c === "(" || c === "[") profondeur += 1;
180
+ else if (c === ")" || c === "]") profondeur -= 1;
181
+ else if (c === "," && profondeur === 0) {
182
+ parts.push(selecteur.slice(debut, i).trim());
183
+ debut = i + 1;
184
+ }
185
+ i += 1;
186
+ }
187
+ parts.push(selecteur.slice(debut).trim());
188
+ return parts.filter((p) => p.length > 0);
189
+ }
190
+ var FOURNISSEUR = "@odoro-cli/libs/generateur";
191
+ async function fournisseurDe(root) {
192
+ try {
193
+ const exiger = createRequire(join(root, "package.json"));
194
+ const chemin = exiger.resolve(FOURNISSEUR);
195
+ const module_ = await import(pathToFileURL(chemin).href);
196
+ if (typeof module_.renderUtilitairesPour !== "function") return void 0;
197
+ return { renderUtilitairesPour: module_.renderUtilitairesPour };
198
+ } catch {
199
+ return void 0;
200
+ }
201
+ }
202
+
203
+ // src/build/build.ts
204
+ function toPosix(path) {
205
+ return path.split(sep).join("/");
206
+ }
207
+ function buildEnv(config) {
208
+ const env = {
209
+ MODE: "production",
210
+ DEV: false,
211
+ PROD: true,
212
+ BASE_URL: config.base
213
+ };
214
+ for (const [key, value] of Object.entries(process.env)) {
215
+ if (key.startsWith(config.envPrefix) && value !== void 0) env[key] = value;
216
+ }
217
+ return env;
218
+ }
219
+ function outputsForEntry(result, entry, outDir, root) {
220
+ const toRelative = (file) => toPosix(relative(outDir, resolve(root, file)));
221
+ for (const [file, meta] of Object.entries(result.metafile.outputs)) {
222
+ if (meta.entryPoint === void 0) continue;
223
+ if (resolve(root, meta.entryPoint) !== resolve(entry)) continue;
224
+ if (!file.endsWith(".js")) continue;
225
+ return {
226
+ script: toRelative(file),
227
+ styles: meta.cssBundle === void 0 ? [] : [toRelative(meta.cssBundle)]
228
+ };
229
+ }
230
+ return { script: void 0, styles: [] };
231
+ }
232
+ async function taillerFeuilles(result, config, html) {
233
+ const produits = Object.keys(result.metafile.outputs).map((f) => resolve(config.root, f));
234
+ const feuilles = produits.filter((f) => f.endsWith(".css"));
235
+ if (feuilles.length === 0) return;
236
+ const sources = [html];
237
+ for (const fichier of produits) {
238
+ if (fichier.endsWith(".js")) sources.push(await readFile(fichier, "utf8"));
239
+ }
240
+ const fournisseur = await fournisseurDe(config.root);
241
+ const employes = /* @__PURE__ */ new Set();
242
+ if (fournisseur !== void 0) {
243
+ for (const source of sources) for (const mot of motsDe(source)) employes.add(mot);
244
+ for (const garde of config.build.safelist) {
245
+ if (typeof garde === "string") employes.add(garde);
246
+ }
247
+ }
248
+ for (const feuille of feuilles) {
249
+ const avant = await readFile(feuille, "utf8");
250
+ if (fournisseur === void 0) {
251
+ const rapport = elaguer(avant, sources, { sauvegarde: config.build.safelist });
252
+ await writeFile(feuille, rapport.css, "utf8");
253
+ info(
254
+ ` ${colors.dim("elagage")} ${basename(feuille)} ${size(rapport.octetsAvant)} \u2192 ${size(rapport.octetsApres)} ${colors.dim(`${String(rapport.gardees)} classes gardees`)}`
255
+ );
256
+ continue;
257
+ }
258
+ const socle = elaguer(avant, [], {}).css;
259
+ const utilitaires = fournisseur.renderUtilitairesPour(employes);
260
+ const apres = `${socle}
261
+ ${utilitaires}`;
262
+ await writeFile(feuille, apres, "utf8");
263
+ info(
264
+ ` ${colors.dim("generation")} ${basename(feuille)} ${size(Buffer.byteLength(avant))} \u2192 ${size(Buffer.byteLength(apres))}`
265
+ );
266
+ }
267
+ }
268
+ async function buildProject(config) {
269
+ const started = Date.now();
270
+ const indexFile = join(config.root, "index.html");
271
+ if (!existsSync(indexFile)) {
272
+ throw new Error(`[odoro] Aucun "index.html" a la racine du projet (${config.root}).`);
273
+ }
274
+ const html = await readFile(indexFile, "utf8");
275
+ const entries = extractEntries(html, config.root);
276
+ if (entries.length === 0) {
277
+ throw new Error(
278
+ `[odoro] Aucun point d'entree : "index.html" doit contenir un <script type="module" src="...">.`
279
+ );
280
+ }
281
+ await rm(config.outDir, { recursive: true, force: true });
282
+ await mkdir(config.outDir, { recursive: true });
283
+ const result = await build({
284
+ entryPoints: [...entries],
285
+ bundle: true,
286
+ format: "esm",
287
+ platform: "browser",
288
+ target: config.build.target,
289
+ splitting: true,
290
+ minify: config.build.minify,
291
+ sourcemap: config.build.sourcemap,
292
+ metafile: true,
293
+ outdir: join(config.outDir, "assets"),
294
+ absWorkingDir: config.root,
295
+ publicPath: `${config.base}assets`,
296
+ // Les empreintes rendent les fichiers immuables : ils peuvent etre mis en
297
+ // cache indefiniment, et un deploiement n'invalide que ce qui a change.
298
+ entryNames: "[name]-[hash]",
299
+ chunkNames: "chunk-[hash]",
300
+ assetNames: "[name]-[hash]",
301
+ jsx: "automatic",
302
+ logLevel: "silent",
303
+ define: {
304
+ "import.meta.env": JSON.stringify(buildEnv(config)),
305
+ "process.env.NODE_ENV": JSON.stringify("production"),
306
+ ...config.define
307
+ },
308
+ loader: {
309
+ ".svg": "file",
310
+ ".png": "file",
311
+ ".jpg": "file",
312
+ ".jpeg": "file",
313
+ ".gif": "file",
314
+ ".webp": "file",
315
+ ".avif": "file",
316
+ ".ico": "file",
317
+ ".woff": "file",
318
+ ".woff2": "file",
319
+ ".mp4": "file",
320
+ ".webm": "file"
321
+ },
322
+ plugins: [
323
+ {
324
+ name: "odoro-alias",
325
+ setup(builder) {
326
+ builder.onResolve({ filter: /.*/ }, (args) => {
327
+ if (args.kind === "entry-point") return null;
328
+ const aliased = applyAlias(args.path, config);
329
+ if (aliased === args.path || isBareSpecifier(aliased)) return null;
330
+ return builder.resolve(aliased, {
331
+ kind: "import-statement",
332
+ resolveDir: args.resolveDir,
333
+ importer: args.importer,
334
+ pluginData: { aliased: true }
335
+ });
336
+ });
337
+ }
338
+ }
339
+ ]
340
+ });
341
+ if (config.build.elaguer) await taillerFeuilles(result, config, html);
342
+ let output = html;
343
+ for (const entry of entries) {
344
+ const { script, styles } = outputsForEntry(
345
+ result,
346
+ entry,
347
+ join(config.outDir, "assets"),
348
+ config.root
349
+ );
350
+ if (script === void 0) continue;
351
+ const original = new RegExp(
352
+ `<script[^>]*type=["']module["'][^>]*src=["'][^"']*${basename(entry)}["'][^>]*></script>`,
353
+ "i"
354
+ );
355
+ const tags = [
356
+ ...styles.map(
357
+ (style) => `<link rel="stylesheet" href="${config.base}assets/${style}">`
358
+ ),
359
+ `<script type="module" crossorigin src="${config.base}assets/${script}"></script>`
360
+ ].join("\n ");
361
+ output = output.replace(original, tags);
362
+ }
363
+ await writeFile(join(config.outDir, "index.html"), output, "utf8");
364
+ if (existsSync(config.publicDir)) {
365
+ await cp(config.publicDir, config.outDir, { recursive: true });
366
+ }
367
+ const files = (await Promise.all(
368
+ Object.keys(result.metafile.outputs).map(async (file) => {
369
+ const chemin = resolve(config.root, file);
370
+ return {
371
+ path: toPosix(relative(config.outDir, chemin)),
372
+ bytes: (await stat(chemin)).size
373
+ };
374
+ })
375
+ )).sort((a, b) => b.bytes - a.bytes);
376
+ return { outDir: config.outDir, files, elapsed: Date.now() - started };
377
+ }
378
+ function reportBuild(output, root) {
379
+ const directory = toPosix(relative(root, output.outDir)) || ".";
380
+ let total = 0;
381
+ for (const file of output.files) {
382
+ total += file.bytes;
383
+ if (file.path.endsWith(".map")) continue;
384
+ info(
385
+ ` ${colors.dim(`${directory}/`)}${file.path} ${colors.dim(size(file.bytes))}`
386
+ );
387
+ }
388
+ success(`compile en ${duration(output.elapsed)} \u2014 ${size(total)} au total`);
389
+ }
390
+
391
+ export { buildProject, reportBuild };
@@ -78,7 +78,11 @@ async function loadConfig(root, overrides = {}) {
78
78
  outDir: merged.build?.outDir ?? "dist",
79
79
  minify: merged.build?.minify ?? true,
80
80
  sourcemap: merged.build?.sourcemap ?? true,
81
- target: merged.build?.target ?? "es2022"
81
+ target: merged.build?.target ?? "es2022",
82
+ // Actif par defaut : une feuille qui contient toutes les classes
83
+ // possibles est un accident de generation, pas une intention.
84
+ elaguer: merged.build?.elaguer ?? true,
85
+ safelist: merged.build?.safelist ?? []
82
86
  },
83
87
  // Les alias declares dans `tsconfig.json` sont repris d'office. Sans
84
88
  // cela, un projet qui suit `odoro init` — lequel deduit son prefixe du
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { loadConfig } from './chunk-T42X2NJN.js';
2
+ import { loadConfig } from './chunk-Q35CGY2Z.js';
3
3
  import './chunk-T6RLHSCW.js';
4
4
  import { error } from './chunk-JMEHF3KN.js';
5
5
  import { realpathSync } from 'fs';
@@ -69,7 +69,7 @@ ${colors.bold("Base de donnees")}
69
69
  db:status Liste les bases du projet
70
70
  db:create Provisionne une base et ecrit .env
71
71
  db:branch Cree une previsualisation par branche
72
- ${colors.dim("Ces commandes demandent @odoro/cloud-sdk, installe a part :")}
72
+ ${colors.dim("Ces commandes demandent @odoro-cli/cloud-sdk, installe a part :")}
73
73
  ${colors.dim("il ne vient pas avec, ce binaire etant telecharge a chaque")}
74
74
  ${colors.dim("creation de projet.")}
75
75
 
@@ -130,7 +130,7 @@ function rootFrom(flags, positional) {
130
130
  async function run(argv) {
131
131
  const { command, positional, flags } = parseArgs(argv);
132
132
  if (flags["version"] === true) {
133
- const manifest = await import('./package-E26GKHFT.js');
133
+ const manifest = await import('./package-VRJRDQY3.js');
134
134
  console.log(manifest.default.version);
135
135
  return 0;
136
136
  }
@@ -141,7 +141,7 @@ async function run(argv) {
141
141
  switch (command) {
142
142
  case "create":
143
143
  case "new": {
144
- const { createCommand } = await import('./create-Z3TSGBE6.js');
144
+ const { createCommand } = await import('./create-XJM3TUAQ.js');
145
145
  const options = {};
146
146
  if (positional[0] !== void 0) options.name = positional[0];
147
147
  if (typeof flags["template"] === "string") options.template = flags["template"];
@@ -160,7 +160,7 @@ async function run(argv) {
160
160
  return new Promise(() => void 0);
161
161
  }
162
162
  case "build": {
163
- const { buildProject, reportBuild } = await import('./build-SNTGJH2J.js');
163
+ const { buildProject, reportBuild } = await import('./build-J2J3NBV5.js');
164
164
  const config = await loadConfig(rootFrom(flags, positional), overridesFrom(flags));
165
165
  const output = await buildProject(config);
166
166
  reportBuild(output, process.cwd());
@@ -194,7 +194,7 @@ async function run(argv) {
194
194
  case "db:status":
195
195
  case "db:create":
196
196
  case "db:branch": {
197
- const db = await import('./commands-V44Y5G4F.js');
197
+ const db = await import('./commands-4JRBD55Z.js');
198
198
  const options = {
199
199
  root: typeof flags["root"] === "string" ? flags["root"] : process.cwd(),
200
200
  ...typeof flags["api"] === "string" ? { apiUrl: flags["api"] } : {},
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { writeDatabaseUrl, assertEnvIgnored } from './chunk-PEMYUK2D.js';
2
+ import { writeDatabaseUrl, assertEnvIgnored } from './chunk-3SZIN6VG.js';
3
3
  import { info, success, colors, warn, error } from './chunk-JMEHF3KN.js';
4
4
  import { randomBytes } from 'crypto';
5
5
  import { readFile, mkdir, writeFile, chmod } from 'fs/promises';
@@ -58,7 +58,7 @@ async function findToken(apiUrl, path = configPath(), env = processEnv()) {
58
58
  }
59
59
 
60
60
  // src/db/sdk.ts
61
- var SDK_PACKAGE = "@odoro/cloud-sdk";
61
+ var SDK_PACKAGE = "@odoro-cli/cloud-sdk";
62
62
  async function loadSdk() {
63
63
  try {
64
64
  const specifier = SDK_PACKAGE;
@@ -78,7 +78,7 @@ creation de projet, et la plupart n'emploient pas la plateforme.`
78
78
  }
79
79
 
80
80
  // src/db/commands.ts
81
- var DEFAULT_API_URL = "https://api.odoro.dev";
81
+ var DEFAULT_API_URL = "https://db.odoro.dev";
82
82
  function idempotencyKey() {
83
83
  return randomBytes(24).toString("hex");
84
84
  }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { writeDatabaseUrl, assertEnvIgnored, PROVIDER_PENDING, checkDatabaseUrl } from './chunk-PEMYUK2D.js';
2
+ import { writeDatabaseUrl, assertEnvIgnored, PROVIDER_PENDING, checkDatabaseUrl } from './chunk-3SZIN6VG.js';
3
3
  import { execSync } from 'child_process';
4
4
  import { existsSync, statSync, readdirSync, readFileSync } from 'fs';
5
5
  import { resolve, basename, dirname, join } from 'path';
package/dist/index.d.ts CHANGED
@@ -31,6 +31,31 @@ interface BuildConfig {
31
31
  sourcemap?: boolean;
32
32
  /** Cible de compilation. @defaultValue 'es2022' */
33
33
  target?: string;
34
+ /**
35
+ * Retire de la feuille de style les classes utilitaires que rien n'emploie.
36
+ *
37
+ * La bibliotheque livre une feuille pre-generee qui les contient toutes ;
38
+ * une application donnee en emploie une fraction. L'elagage lit le code
39
+ * **produit** — donc les composants de bibliotheque autant que la source de
40
+ * l'application — et ne garde que ce qui est atteignable.
41
+ *
42
+ * Une classe assemblee a l'execution (`o-text-${couleur}`) n'existe nulle
43
+ * part sous sa forme finale et disparaitra : la declarer dans
44
+ * `safelist` est le seul moyen de la garder.
45
+ *
46
+ * @defaultValue true
47
+ */
48
+ elaguer?: boolean;
49
+ /**
50
+ * Les classes gardees quoi qu'il arrive, malgre l'elagage.
51
+ *
52
+ * Une chaine garde une classe ; une expression reguliere garde toutes celles
53
+ * qu'elle reconnait.
54
+ *
55
+ * @example
56
+ * { safelist: [/^o-text-/, 'o-animate-spin'] }
57
+ */
58
+ safelist?: readonly (string | RegExp)[];
34
59
  }
35
60
  /** Configuration d'un projet Odoro. */
36
61
  interface OdoroConfig {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- export { defineConfig, loadConfig } from './chunk-T42X2NJN.js';
2
+ export { defineConfig, loadConfig } from './chunk-Q35CGY2Z.js';
3
3
  import './chunk-T6RLHSCW.js';
4
- export { buildProject, reportBuild } from './chunk-UGRSODHU.js';
4
+ export { buildProject, reportBuild } from './chunk-JGXJBVI7.js';
5
5
  export { ModuleGraph, depFileName, detectSelfAccepting, optimizeDeps, scanDependencies, startDevServer } from './chunk-G5QXVBYT.js';
6
6
  export { startPreviewServer } from './chunk-2YDI5NKV.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.1.2",
5
+ version: "0.1.4",
6
6
  type: "module",
7
7
  license: "UNLICENSED",
8
8
  author: "BouBouw",
@@ -56,6 +56,15 @@ var package_default = {
56
56
  typescript: "^5.9.3",
57
57
  vitest: "^3.2.4",
58
58
  zod: "^4.5.1"
59
+ },
60
+ homepage: "https://odoro.dev",
61
+ repository: {
62
+ type: "git",
63
+ url: "git+https://github.com/ODORO-CLI/OdoroKit.git",
64
+ directory: "packages/odoro"
65
+ },
66
+ bugs: {
67
+ url: "https://github.com/ODORO-CLI/OdoroKit/issues"
59
68
  }
60
69
  };
61
70
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odoro",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "author": "BouBouw",
@@ -48,6 +48,15 @@
48
48
  "vitest": "^3.2.4",
49
49
  "zod": "^4.5.1"
50
50
  },
51
+ "homepage": "https://odoro.dev",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/ODORO-CLI/OdoroKit.git",
55
+ "directory": "packages/odoro"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/ODORO-CLI/OdoroKit/issues"
59
+ },
51
60
  "scripts": {
52
61
  "build": "tsup",
53
62
  "dev": "tsup --watch",
@@ -34,13 +34,12 @@ Toutes les valeurs visuelles passent par des variables CSS. Surcharger
34
34
  `--o-palette-brand-600` dans `src/styles.css` retheme l'application **et** les
35
35
  composants de la librairie, sans toucher a leur code.
36
36
 
37
- Deux feuilles de style sont disponibles :
37
+ Une seule feuille de style : `@odoro-cli/libs/styles.css`, a importer une fois
38
+ a la racine de l'application.
38
39
 
39
- - `@odoro-cli/libs/styles.css` structure et couleurs semantiques ;
40
- - `@odoro-cli/libs/styles.full.css` la meme, plus les utilitaires de couleur sur
41
- la palette complete.
42
-
43
- Importer l'une **ou** l'autre, jamais les deux.
40
+ Elle pese 1,7 Mo sur le disque, mais ce n'est pas ce que vos visiteurs
41
+ telechargent : la compilation n'en garde que les classes que votre code
42
+ emploie reellement — quelques dizaines de kilooctets en pratique.
44
43
 
45
44
  ## Rechargement a chaud
46
45
 
@@ -1,154 +0,0 @@
1
- #!/usr/bin/env node
2
- import { extractEntries, applyAlias, isBareSpecifier } from './chunk-G5QXVBYT.js';
3
- import { info, colors, size, success, duration } from './chunk-JMEHF3KN.js';
4
- import { existsSync } from 'fs';
5
- import { readFile, rm, mkdir, writeFile, cp } from 'fs/promises';
6
- import { join, basename, relative, resolve, sep } from 'path';
7
- import { build } from 'esbuild';
8
-
9
- function toPosix(path) {
10
- return path.split(sep).join("/");
11
- }
12
- function buildEnv(config) {
13
- const env = {
14
- MODE: "production",
15
- DEV: false,
16
- PROD: true,
17
- BASE_URL: config.base
18
- };
19
- for (const [key, value] of Object.entries(process.env)) {
20
- if (key.startsWith(config.envPrefix) && value !== void 0) env[key] = value;
21
- }
22
- return env;
23
- }
24
- function outputsForEntry(result, entry, outDir, root) {
25
- const toRelative = (file) => toPosix(relative(outDir, resolve(root, file)));
26
- for (const [file, meta] of Object.entries(result.metafile.outputs)) {
27
- if (meta.entryPoint === void 0) continue;
28
- if (resolve(root, meta.entryPoint) !== resolve(entry)) continue;
29
- if (!file.endsWith(".js")) continue;
30
- return {
31
- script: toRelative(file),
32
- styles: meta.cssBundle === void 0 ? [] : [toRelative(meta.cssBundle)]
33
- };
34
- }
35
- return { script: void 0, styles: [] };
36
- }
37
- async function buildProject(config) {
38
- const started = Date.now();
39
- const indexFile = join(config.root, "index.html");
40
- if (!existsSync(indexFile)) {
41
- throw new Error(`[odoro] Aucun "index.html" a la racine du projet (${config.root}).`);
42
- }
43
- const html = await readFile(indexFile, "utf8");
44
- const entries = extractEntries(html, config.root);
45
- if (entries.length === 0) {
46
- throw new Error(
47
- `[odoro] Aucun point d'entree : "index.html" doit contenir un <script type="module" src="...">.`
48
- );
49
- }
50
- await rm(config.outDir, { recursive: true, force: true });
51
- await mkdir(config.outDir, { recursive: true });
52
- const result = await build({
53
- entryPoints: [...entries],
54
- bundle: true,
55
- format: "esm",
56
- platform: "browser",
57
- target: config.build.target,
58
- splitting: true,
59
- minify: config.build.minify,
60
- sourcemap: config.build.sourcemap,
61
- metafile: true,
62
- outdir: join(config.outDir, "assets"),
63
- absWorkingDir: config.root,
64
- publicPath: `${config.base}assets`,
65
- // Les empreintes rendent les fichiers immuables : ils peuvent etre mis en
66
- // cache indefiniment, et un deploiement n'invalide que ce qui a change.
67
- entryNames: "[name]-[hash]",
68
- chunkNames: "chunk-[hash]",
69
- assetNames: "[name]-[hash]",
70
- jsx: "automatic",
71
- logLevel: "silent",
72
- define: {
73
- "import.meta.env": JSON.stringify(buildEnv(config)),
74
- "process.env.NODE_ENV": JSON.stringify("production"),
75
- ...config.define
76
- },
77
- loader: {
78
- ".svg": "file",
79
- ".png": "file",
80
- ".jpg": "file",
81
- ".jpeg": "file",
82
- ".gif": "file",
83
- ".webp": "file",
84
- ".avif": "file",
85
- ".ico": "file",
86
- ".woff": "file",
87
- ".woff2": "file",
88
- ".mp4": "file",
89
- ".webm": "file"
90
- },
91
- plugins: [
92
- {
93
- name: "odoro-alias",
94
- setup(builder) {
95
- builder.onResolve({ filter: /.*/ }, (args) => {
96
- if (args.kind === "entry-point") return null;
97
- const aliased = applyAlias(args.path, config);
98
- if (aliased === args.path || isBareSpecifier(aliased)) return null;
99
- return builder.resolve(aliased, {
100
- kind: "import-statement",
101
- resolveDir: args.resolveDir,
102
- importer: args.importer,
103
- pluginData: { aliased: true }
104
- });
105
- });
106
- }
107
- }
108
- ]
109
- });
110
- let output = html;
111
- for (const entry of entries) {
112
- const { script, styles } = outputsForEntry(
113
- result,
114
- entry,
115
- join(config.outDir, "assets"),
116
- config.root
117
- );
118
- if (script === void 0) continue;
119
- const original = new RegExp(
120
- `<script[^>]*type=["']module["'][^>]*src=["'][^"']*${basename(entry)}["'][^>]*></script>`,
121
- "i"
122
- );
123
- const tags = [
124
- ...styles.map(
125
- (style) => `<link rel="stylesheet" href="${config.base}assets/${style}">`
126
- ),
127
- `<script type="module" crossorigin src="${config.base}assets/${script}"></script>`
128
- ].join("\n ");
129
- output = output.replace(original, tags);
130
- }
131
- await writeFile(join(config.outDir, "index.html"), output, "utf8");
132
- if (existsSync(config.publicDir)) {
133
- await cp(config.publicDir, config.outDir, { recursive: true });
134
- }
135
- const files = Object.entries(result.metafile.outputs).map(([file, meta]) => ({
136
- path: toPosix(relative(config.outDir, resolve(config.root, file))),
137
- bytes: meta.bytes
138
- })).sort((a, b) => b.bytes - a.bytes);
139
- return { outDir: config.outDir, files, elapsed: Date.now() - started };
140
- }
141
- function reportBuild(output, root) {
142
- const directory = toPosix(relative(root, output.outDir)) || ".";
143
- let total = 0;
144
- for (const file of output.files) {
145
- total += file.bytes;
146
- if (file.path.endsWith(".map")) continue;
147
- info(
148
- ` ${colors.dim(`${directory}/`)}${file.path} ${colors.dim(size(file.bytes))}`
149
- );
150
- }
151
- success(`compile en ${duration(output.elapsed)} \u2014 ${size(total)} au total`);
152
- }
153
-
154
- export { buildProject, reportBuild };