odoro 1.0.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -0
- package/client.d.ts +90 -16
- package/dist/build-LZAUDJST.js +5 -0
- package/dist/{chunk-34RGOZFA.js → chunk-4D56Z7G3.js} +49 -50
- package/dist/{chunk-JMEHF3KN.js → chunk-7ZB5MAM6.js} +4 -4
- package/dist/{chunk-LHZGX5ML.js → chunk-OMHEQVIW.js} +295 -160
- package/dist/{chunk-3SZIN6VG.js → chunk-P3DJITWE.js} +10 -10
- package/dist/{chunk-ZVL7EXJO.js → chunk-PRWVZ2SM.js} +22 -14
- package/dist/chunk-Q2KCMTB5.js +741 -0
- package/dist/chunk-QOVBLN7A.js +241 -0
- package/dist/chunk-WWTDEV4Y.js +604 -0
- package/dist/cli.d.ts +12 -13
- package/dist/cli.js +84 -63
- package/dist/{commands-4JRBD55Z.js → commands-EUGVLQWL.js} +44 -45
- package/dist/{commands-AHMXBWQQ.js → commands-GYA7NTCX.js} +124 -124
- package/dist/{create-CYVSVDAD.js → create-QX5LEWMS.js} +158 -154
- package/dist/index.d.ts +603 -151
- package/dist/index.js +6 -6
- package/dist/{package-EIDLKAA6.js → package-T4Z7OBOH.js} +5 -5
- package/dist/preview-P4WY5GEY.js +4 -0
- package/dist/registry/index.d.ts +75 -76
- package/dist/registry/index.js +1 -1
- package/dist/{server-4UGN3SFS.js → server-2WP562EV.js} +3 -3
- package/package.json +5 -5
- package/templates/react-ts/README.md +71 -35
- package/templates/react-ts/_env.example +9 -0
- package/templates/react-ts/_variants/with-engine/src/background.tsx +118 -0
- package/templates/react-ts/_variants/without-libs/src/App.tsx +380 -0
- package/templates/react-ts/_variants/without-libs/src/background.tsx +24 -0
- package/templates/react-ts/_variants/without-libs/src/entry-server.tsx +39 -0
- package/templates/react-ts/_variants/without-libs/src/main.tsx +26 -0
- package/templates/{react-ts-server/_variantes/sans-libs/client → react-ts/_variants/without-libs}/src/styles.css +137 -137
- package/templates/react-ts/{_variantes/sans-routeur → _variants/without-router}/src/App.tsx +141 -141
- package/templates/react-ts/_variants/without-router/src/entry-server.tsx +39 -0
- package/templates/react-ts/index.html +4 -4
- package/templates/react-ts/odoro.config.ts +5 -0
- package/templates/react-ts/src/App.tsx +219 -209
- package/templates/react-ts/src/background.tsx +66 -0
- package/templates/react-ts/src/entry-server.tsx +85 -0
- package/templates/react-ts/src/main.tsx +13 -4
- package/templates/react-ts/src/router.tsx +65 -45
- package/templates/react-ts/src/styles.css +5 -5
- package/templates/react-ts-server/Dockerfile +9 -9
- package/templates/react-ts-server/README.md +97 -45
- package/templates/react-ts-server/_env.example +31 -33
- package/templates/react-ts-server/_variants/with-engine/client/src/background.tsx +118 -0
- package/templates/react-ts-server/_variants/without-libs/client/src/App.tsx +380 -0
- package/templates/react-ts-server/_variants/without-libs/client/src/background.tsx +24 -0
- package/templates/react-ts-server/_variants/without-libs/client/src/entry-server.tsx +39 -0
- package/templates/react-ts-server/_variants/without-libs/client/src/main.tsx +26 -0
- package/templates/{react-ts/_variantes/sans-libs → react-ts-server/_variants/without-libs/client}/src/styles.css +137 -137
- package/templates/react-ts-server/{_variantes/sans-routeur → _variants/without-router}/client/src/App.tsx +141 -141
- package/templates/react-ts-server/_variants/without-router/client/src/entry-server.tsx +39 -0
- package/templates/react-ts-server/client/index.html +4 -4
- package/templates/react-ts-server/client/src/App.tsx +228 -210
- package/templates/react-ts-server/client/src/account.tsx +267 -0
- package/templates/react-ts-server/client/src/auth.tsx +139 -0
- package/templates/react-ts-server/client/src/background.tsx +66 -0
- package/templates/react-ts-server/client/src/entry-server.tsx +92 -0
- package/templates/react-ts-server/client/src/main.tsx +17 -5
- package/templates/react-ts-server/client/src/router.tsx +77 -45
- package/templates/react-ts-server/client/src/styles.css +5 -5
- package/templates/react-ts-server/odoro.config.ts +7 -4
- package/templates/react-ts-server/package.json +2 -0
- package/templates/react-ts-server/scripts/dev.mjs +14 -14
- package/templates/react-ts-server/server/src/main.ts +119 -45
- package/templates/react-ts-server/server/src/modules/auth/index.ts +238 -0
- package/templates/react-ts-server/server/src/modules/auth/password.ts +122 -0
- package/templates/react-ts-server/server/src/modules/auth/store.ts +193 -0
- package/templates/react-ts-server/server/src/modules/health/index.ts +45 -46
- package/dist/build-JFQHODAT.js +0 -5
- package/dist/chunk-22KJTV2R.js +0 -380
- package/dist/chunk-DL3NPC4H.js +0 -113
- package/dist/chunk-TQUJ3MFS.js +0 -268
- package/dist/preview-7DKEAQOQ.js +0 -4
- package/templates/react-ts/_variantes/avec-moteur/src/fond.tsx +0 -118
- package/templates/react-ts/_variantes/sans-libs/src/App.tsx +0 -382
- package/templates/react-ts/_variantes/sans-libs/src/fond.tsx +0 -23
- package/templates/react-ts/_variantes/sans-libs/src/main.tsx +0 -17
- package/templates/react-ts/src/fond.tsx +0 -66
- package/templates/react-ts-server/_variantes/avec-moteur/client/src/fond.tsx +0 -118
- package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +0 -382
- package/templates/react-ts-server/_variantes/sans-libs/client/src/fond.tsx +0 -23
- package/templates/react-ts-server/_variantes/sans-libs/client/src/main.tsx +0 -17
- package/templates/react-ts-server/client/src/fond.tsx +0 -66
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Health module — and an example of what a module looks like.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* It is the only module of this template, and it is written here rather than
|
|
5
|
+
* provided by `@odoro-cli/server` for a reason: writing a module is the first
|
|
6
|
+
* thing you do on this foundation, and an example you can open beats a page of
|
|
7
|
+
* documentation.
|
|
8
8
|
*
|
|
9
|
-
* ##
|
|
9
|
+
* ## A module is a function of what it needs
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* `process.env`.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* This one receives the configuration as a parameter rather than reading
|
|
12
|
+
* `process.env`. That is the rule of the foundation: the environment is
|
|
13
|
+
* validated once, at startup, and everything else consumes the result. A
|
|
14
|
+
* direct read escapes that validation and shows up on the hundredth request.
|
|
15
15
|
*
|
|
16
|
-
* ##
|
|
16
|
+
* ## Two health endpoints, and why they differ
|
|
17
17
|
*
|
|
18
|
-
* `/api/health`
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* `/api/health` answers that **the process is alive**. It tests nothing else,
|
|
19
|
+
* and must answer even when everything else is broken: that is what an
|
|
20
|
+
* orchestrator asks to decide whether to restart the container. Making it
|
|
21
|
+
* depend on the database would restart a perfectly healthy server every time
|
|
22
|
+
* the database hiccups — and a restart does not repair a database.
|
|
23
23
|
*
|
|
24
|
-
* `/api/ready`
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* `/api/ready` answers that **the service can do its work**, and returns 503
|
|
25
|
+
* for as long as something is missing: that is what a load balancer asks to
|
|
26
|
+
* decide whether it can send traffic.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Confusing the two gives one of two faults: a service restarting in a loop
|
|
29
|
+
* during a database incident, or a load balancer sending traffic to a service
|
|
30
|
+
* that cannot answer.
|
|
31
31
|
*
|
|
32
|
-
* ##
|
|
32
|
+
* ## Why `/ready` fails on the first start
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* reste a faire.
|
|
34
|
+
* A freshly scaffolded project has no `DATABASE_URL` yet. The client starts,
|
|
35
|
+
* the interface shows, and `/api/ready` says what is missing. So you see
|
|
36
|
+
* something in the first minute, and you know exactly what is left to do.
|
|
38
37
|
*
|
|
39
38
|
* @module
|
|
40
39
|
*/
|
|
@@ -47,20 +46,20 @@ import {
|
|
|
47
46
|
} from '@odoro-cli/server'
|
|
48
47
|
import { z } from 'zod'
|
|
49
48
|
|
|
50
|
-
/**
|
|
49
|
+
/** What the liveness check returns. */
|
|
51
50
|
const liveness = z.object({
|
|
52
51
|
status: z.literal('ok'),
|
|
53
52
|
environment: z.string(),
|
|
54
53
|
uptime: z.number(),
|
|
55
54
|
})
|
|
56
55
|
|
|
57
|
-
/**
|
|
56
|
+
/** What the readiness check returns, when everything answers. */
|
|
58
57
|
const readiness = z.object({
|
|
59
58
|
ready: z.literal(true),
|
|
60
59
|
dependencies: z.array(z.object({ name: z.string(), detail: z.string() })),
|
|
61
60
|
})
|
|
62
61
|
|
|
63
|
-
/**
|
|
62
|
+
/** A dependency and its state. */
|
|
64
63
|
interface Dependency {
|
|
65
64
|
readonly name: string
|
|
66
65
|
readonly ready: boolean
|
|
@@ -68,13 +67,13 @@ interface Dependency {
|
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
/**
|
|
71
|
-
*
|
|
70
|
+
* State of the dependencies.
|
|
72
71
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
72
|
+
* The database is not queried yet — the foundation has no persistence layer.
|
|
73
|
+
* What is checked here is its **configuration**, which is enough to tell a
|
|
74
|
+
* project that never received a URL from a configured one. When persistence
|
|
75
|
+
* arrives, this function is the one that will learn to open a connection, and
|
|
76
|
+
* nothing else will change.
|
|
78
77
|
*/
|
|
79
78
|
function inspect(config: KernelConfig): readonly Dependency[] {
|
|
80
79
|
const url = config.DATABASE_URL.trim()
|
|
@@ -84,14 +83,14 @@ function inspect(config: KernelConfig): readonly Dependency[] {
|
|
|
84
83
|
ready: url.length > 0,
|
|
85
84
|
detail:
|
|
86
85
|
url.length > 0
|
|
87
|
-
? 'URL
|
|
88
|
-
: 'DATABASE_URL
|
|
86
|
+
? 'URL configured'
|
|
87
|
+
: 'DATABASE_URL missing — see .env.example, or run `odoro db:create`',
|
|
89
88
|
},
|
|
90
89
|
]
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
/**
|
|
94
|
-
*
|
|
93
|
+
* Builds the module.
|
|
95
94
|
*
|
|
96
95
|
* @example
|
|
97
96
|
* createApp({ modules: [createHealthModule(config)], … })
|
|
@@ -105,7 +104,7 @@ export function createHealthModule(config: KernelConfig) {
|
|
|
105
104
|
method: 'GET',
|
|
106
105
|
path: '/api/health',
|
|
107
106
|
auth: 'public',
|
|
108
|
-
summary: '
|
|
107
|
+
summary: 'The process answers.',
|
|
109
108
|
output: liveness,
|
|
110
109
|
handler: () => ({
|
|
111
110
|
status: 'ok' as const,
|
|
@@ -119,17 +118,17 @@ export function createHealthModule(config: KernelConfig) {
|
|
|
119
118
|
method: 'GET',
|
|
120
119
|
path: '/api/ready',
|
|
121
120
|
auth: 'public',
|
|
122
|
-
summary: '
|
|
121
|
+
summary: 'The dependencies answer.',
|
|
123
122
|
output: readiness,
|
|
124
123
|
handler: () => {
|
|
125
124
|
const dependencies = inspect(config)
|
|
126
|
-
const
|
|
125
|
+
const missing = dependencies.filter((d) => !d.ready)
|
|
127
126
|
|
|
128
|
-
if (
|
|
129
|
-
// 503
|
|
130
|
-
//
|
|
127
|
+
if (missing.length > 0) {
|
|
128
|
+
// 503 and not 500: the request was valid, it is the service that
|
|
129
|
+
// cannot answer it yet.
|
|
131
130
|
throw new ServiceUnavailableError(
|
|
132
|
-
|
|
131
|
+
missing.map((d) => `${d.name} — ${d.detail}`).join(' ; '),
|
|
133
132
|
)
|
|
134
133
|
}
|
|
135
134
|
|
package/dist/build-JFQHODAT.js
DELETED
package/dist/chunk-22KJTV2R.js
DELETED
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { extractEntries, fournisseurDe } from './chunk-LHZGX5ML.js';
|
|
3
|
-
import { applyAlias, isBareSpecifier } from './chunk-TQUJ3MFS.js';
|
|
4
|
-
import { info, colors, size, success, duration } from './chunk-JMEHF3KN.js';
|
|
5
|
-
import { existsSync } from 'fs';
|
|
6
|
-
import { readFile, rm, mkdir, writeFile, cp, stat } from 'fs/promises';
|
|
7
|
-
import { join, basename, resolve, relative, sep } from 'path';
|
|
8
|
-
import { build } from 'esbuild';
|
|
9
|
-
|
|
10
|
-
// src/build/elaguer.ts
|
|
11
|
-
var GROUPES = /* @__PURE__ */ new Set(["media", "supports", "layer", "container", "scope"]);
|
|
12
|
-
function analyser(css) {
|
|
13
|
-
const noeuds = [];
|
|
14
|
-
let i = 0;
|
|
15
|
-
let debut = 0;
|
|
16
|
-
function avancer() {
|
|
17
|
-
const c = css[i];
|
|
18
|
-
if (c === "\\") {
|
|
19
|
-
i += 2;
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (c === "/" && css[i + 1] === "*") {
|
|
23
|
-
const fin = css.indexOf("*/", i + 2);
|
|
24
|
-
i = fin === -1 ? css.length : fin + 2;
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (c === '"' || c === "'") {
|
|
28
|
-
i += 1;
|
|
29
|
-
while (i < css.length && css[i] !== c) {
|
|
30
|
-
i += css[i] === "\\" ? 2 : 1;
|
|
31
|
-
}
|
|
32
|
-
i += 1;
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
i += 1;
|
|
36
|
-
}
|
|
37
|
-
while (i < css.length) {
|
|
38
|
-
const c = css[i];
|
|
39
|
-
if (c === "{") {
|
|
40
|
-
const prelude = css.slice(debut, i).trim();
|
|
41
|
-
let profondeur = 1;
|
|
42
|
-
i += 1;
|
|
43
|
-
const debutCorps = i;
|
|
44
|
-
while (i < css.length && profondeur > 0) {
|
|
45
|
-
const d = css[i];
|
|
46
|
-
if (d === "{") {
|
|
47
|
-
profondeur += 1;
|
|
48
|
-
i += 1;
|
|
49
|
-
} else if (d === "}") {
|
|
50
|
-
profondeur -= 1;
|
|
51
|
-
i += 1;
|
|
52
|
-
} else {
|
|
53
|
-
avancer();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const corps = css.slice(debutCorps, i - 1);
|
|
57
|
-
if (prelude.startsWith("@")) {
|
|
58
|
-
const nom = /^@([a-zA-Z-]+)/.exec(prelude)?.[1] ?? "";
|
|
59
|
-
noeuds.push(
|
|
60
|
-
GROUPES.has(nom) ? { sorte: "groupe", prelude, enfants: analyser(corps) } : (
|
|
61
|
-
// `@keyframes`, `@font-face`, `@property` : recopies tels quels.
|
|
62
|
-
{ sorte: "brut", texte: `${prelude}{${corps}}` }
|
|
63
|
-
)
|
|
64
|
-
);
|
|
65
|
-
} else if (prelude.length > 0) {
|
|
66
|
-
noeuds.push({ sorte: "regle", selecteur: prelude, corps });
|
|
67
|
-
}
|
|
68
|
-
debut = i;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (c === ";" && css.slice(debut, i).trim().startsWith("@")) {
|
|
72
|
-
noeuds.push({ sorte: "brut", texte: `${css.slice(debut, i).trim()};` });
|
|
73
|
-
i += 1;
|
|
74
|
-
debut = i;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
avancer();
|
|
78
|
-
}
|
|
79
|
-
return noeuds;
|
|
80
|
-
}
|
|
81
|
-
function ecrire(noeuds) {
|
|
82
|
-
return noeuds.map((n) => {
|
|
83
|
-
if (n.sorte === "brut") return n.texte;
|
|
84
|
-
if (n.sorte === "regle") return `${n.selecteur}{${n.corps}}`;
|
|
85
|
-
return `${n.prelude}{${ecrire(n.enfants)}}`;
|
|
86
|
-
}).join("\n");
|
|
87
|
-
}
|
|
88
|
-
function classesDe(selecteur) {
|
|
89
|
-
const classes = [];
|
|
90
|
-
const motif = /(?<!\\)\.((?:\\.|[\w-])+)/g;
|
|
91
|
-
for (const trouve of selecteur.matchAll(motif)) {
|
|
92
|
-
classes.push(trouve[1].replaceAll(/\\(.)/g, "$1"));
|
|
93
|
-
}
|
|
94
|
-
return classes;
|
|
95
|
-
}
|
|
96
|
-
function motsDe(texte) {
|
|
97
|
-
const mots = /* @__PURE__ */ new Set();
|
|
98
|
-
const motif = /[A-Za-z0-9_][A-Za-z0-9_:/.[\]%@-]*/g;
|
|
99
|
-
for (const trouve of texte.matchAll(motif)) mots.add(trouve[0]);
|
|
100
|
-
return mots;
|
|
101
|
-
}
|
|
102
|
-
function estUtilitaire(classe, prefixe) {
|
|
103
|
-
const nue = classe.slice(classe.lastIndexOf(":") + 1);
|
|
104
|
-
return nue.startsWith(prefixe);
|
|
105
|
-
}
|
|
106
|
-
function gardee(classe, employes, sauvegarde) {
|
|
107
|
-
if (employes.has(classe)) return true;
|
|
108
|
-
for (const regle of sauvegarde) {
|
|
109
|
-
if (typeof regle === "string" ? regle === classe : regle.test(classe)) return true;
|
|
110
|
-
}
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
function elaguer(css, sources, options = {}) {
|
|
114
|
-
const prefixe = options.prefixe ?? "o-";
|
|
115
|
-
const sauvegarde = options.sauvegarde ?? [];
|
|
116
|
-
const employes = /* @__PURE__ */ new Set();
|
|
117
|
-
for (const source of sources) {
|
|
118
|
-
for (const mot of motsDe(source)) employes.add(mot);
|
|
119
|
-
}
|
|
120
|
-
const vues = /* @__PURE__ */ new Set();
|
|
121
|
-
const survivantes = /* @__PURE__ */ new Set();
|
|
122
|
-
function filtrerSelecteur(selecteur) {
|
|
123
|
-
const retenus = decouperSelecteurs(selecteur).filter((un) => {
|
|
124
|
-
const utilitaires = classesDe(un).filter((c) => estUtilitaire(c, prefixe));
|
|
125
|
-
if (utilitaires.length === 0) return true;
|
|
126
|
-
for (const classe of utilitaires) vues.add(classe);
|
|
127
|
-
const survit = utilitaires.every((c) => gardee(c, employes, sauvegarde));
|
|
128
|
-
if (survit) for (const classe of utilitaires) survivantes.add(classe);
|
|
129
|
-
return survit;
|
|
130
|
-
});
|
|
131
|
-
return retenus.length === 0 ? void 0 : retenus.join(",");
|
|
132
|
-
}
|
|
133
|
-
function filtrer(noeuds) {
|
|
134
|
-
const retenus = [];
|
|
135
|
-
for (const noeud of noeuds) {
|
|
136
|
-
if (noeud.sorte === "brut") {
|
|
137
|
-
retenus.push(noeud);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (noeud.sorte === "regle") {
|
|
141
|
-
const selecteur = filtrerSelecteur(noeud.selecteur);
|
|
142
|
-
if (selecteur !== void 0) retenus.push({ ...noeud, selecteur });
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
const enfants = filtrer(noeud.enfants);
|
|
146
|
-
if (enfants.length > 0) retenus.push({ ...noeud, enfants });
|
|
147
|
-
}
|
|
148
|
-
return retenus;
|
|
149
|
-
}
|
|
150
|
-
const elaguee = ecrire(filtrer(analyser(css)));
|
|
151
|
-
return {
|
|
152
|
-
css: elaguee,
|
|
153
|
-
octetsAvant: Buffer.byteLength(css),
|
|
154
|
-
octetsApres: Buffer.byteLength(elaguee),
|
|
155
|
-
gardees: survivantes.size,
|
|
156
|
-
retirees: vues.size - survivantes.size
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
function decouperSelecteurs(selecteur) {
|
|
160
|
-
const parts = [];
|
|
161
|
-
let profondeur = 0;
|
|
162
|
-
let debut = 0;
|
|
163
|
-
let i = 0;
|
|
164
|
-
while (i < selecteur.length) {
|
|
165
|
-
const c = selecteur[i];
|
|
166
|
-
if (c === "\\") {
|
|
167
|
-
i += 2;
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
if (c === '"' || c === "'") {
|
|
171
|
-
i += 1;
|
|
172
|
-
while (i < selecteur.length && selecteur[i] !== c) {
|
|
173
|
-
i += selecteur[i] === "\\" ? 2 : 1;
|
|
174
|
-
}
|
|
175
|
-
i += 1;
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
if (c === "(" || c === "[") profondeur += 1;
|
|
179
|
-
else if (c === ")" || c === "]") profondeur -= 1;
|
|
180
|
-
else if (c === "," && profondeur === 0) {
|
|
181
|
-
parts.push(selecteur.slice(debut, i).trim());
|
|
182
|
-
debut = i + 1;
|
|
183
|
-
}
|
|
184
|
-
i += 1;
|
|
185
|
-
}
|
|
186
|
-
parts.push(selecteur.slice(debut).trim());
|
|
187
|
-
return parts.filter((p) => p.length > 0);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// src/build/build.ts
|
|
191
|
-
function toPosix(path) {
|
|
192
|
-
return path.split(sep).join("/");
|
|
193
|
-
}
|
|
194
|
-
function buildEnv(config) {
|
|
195
|
-
const env = {
|
|
196
|
-
MODE: "production",
|
|
197
|
-
DEV: false,
|
|
198
|
-
PROD: true,
|
|
199
|
-
BASE_URL: config.base
|
|
200
|
-
};
|
|
201
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
202
|
-
if (key.startsWith(config.envPrefix) && value !== void 0) env[key] = value;
|
|
203
|
-
}
|
|
204
|
-
return env;
|
|
205
|
-
}
|
|
206
|
-
function outputsForEntry(result, entry, outDir, root) {
|
|
207
|
-
const toRelative = (file) => toPosix(relative(outDir, resolve(root, file)));
|
|
208
|
-
for (const [file, meta] of Object.entries(result.metafile.outputs)) {
|
|
209
|
-
if (meta.entryPoint === void 0) continue;
|
|
210
|
-
if (resolve(root, meta.entryPoint) !== resolve(entry)) continue;
|
|
211
|
-
if (!file.endsWith(".js")) continue;
|
|
212
|
-
return {
|
|
213
|
-
script: toRelative(file),
|
|
214
|
-
styles: meta.cssBundle === void 0 ? [] : [toRelative(meta.cssBundle)]
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
return { script: void 0, styles: [] };
|
|
218
|
-
}
|
|
219
|
-
async function taillerFeuilles(result, config, html) {
|
|
220
|
-
const produits = Object.keys(result.metafile.outputs).map(
|
|
221
|
-
(f) => resolve(config.root, f)
|
|
222
|
-
);
|
|
223
|
-
const feuilles = produits.filter((f) => f.endsWith(".css"));
|
|
224
|
-
if (feuilles.length === 0) return;
|
|
225
|
-
const sources = [html];
|
|
226
|
-
for (const fichier of produits) {
|
|
227
|
-
if (fichier.endsWith(".js")) sources.push(await readFile(fichier, "utf8"));
|
|
228
|
-
}
|
|
229
|
-
const fournisseur = await fournisseurDe(config.root);
|
|
230
|
-
const employes = /* @__PURE__ */ new Set();
|
|
231
|
-
if (fournisseur !== void 0) {
|
|
232
|
-
for (const source of sources) for (const mot of motsDe(source)) employes.add(mot);
|
|
233
|
-
for (const garde of config.build.safelist) {
|
|
234
|
-
if (typeof garde === "string") employes.add(garde);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
for (const feuille of feuilles) {
|
|
238
|
-
const avant = await readFile(feuille, "utf8");
|
|
239
|
-
if (fournisseur === void 0) {
|
|
240
|
-
const rapport = elaguer(avant, sources, { sauvegarde: config.build.safelist });
|
|
241
|
-
await writeFile(feuille, rapport.css, "utf8");
|
|
242
|
-
info(
|
|
243
|
-
` ${colors.dim("elagage")} ${basename(feuille)} ${size(rapport.octetsAvant)} \u2192 ${size(rapport.octetsApres)} ${colors.dim(`${String(rapport.gardees)} classes gardees`)}`
|
|
244
|
-
);
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
const socle = elaguer(avant, [], {}).css;
|
|
248
|
-
const utilitaires = fournisseur.renderUtilitairesPour(employes);
|
|
249
|
-
const apres = `${socle}
|
|
250
|
-
${utilitaires}`;
|
|
251
|
-
await writeFile(feuille, apres, "utf8");
|
|
252
|
-
info(
|
|
253
|
-
` ${colors.dim("generation")} ${basename(feuille)} ${size(Buffer.byteLength(avant))} \u2192 ${size(Buffer.byteLength(apres))}`
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
async function buildProject(config) {
|
|
258
|
-
const started = Date.now();
|
|
259
|
-
const indexFile = join(config.root, "index.html");
|
|
260
|
-
if (!existsSync(indexFile)) {
|
|
261
|
-
throw new Error(`[odoro] Aucun "index.html" a la racine du projet (${config.root}).`);
|
|
262
|
-
}
|
|
263
|
-
const html = await readFile(indexFile, "utf8");
|
|
264
|
-
const entries = extractEntries(html, config.root);
|
|
265
|
-
if (entries.length === 0) {
|
|
266
|
-
throw new Error(
|
|
267
|
-
`[odoro] Aucun point d'entree : "index.html" doit contenir un <script type="module" src="...">.`
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
await rm(config.outDir, { recursive: true, force: true });
|
|
271
|
-
await mkdir(config.outDir, { recursive: true });
|
|
272
|
-
const result = await build({
|
|
273
|
-
entryPoints: [...entries],
|
|
274
|
-
bundle: true,
|
|
275
|
-
format: "esm",
|
|
276
|
-
platform: "browser",
|
|
277
|
-
target: config.build.target,
|
|
278
|
-
splitting: true,
|
|
279
|
-
minify: config.build.minify,
|
|
280
|
-
sourcemap: config.build.sourcemap,
|
|
281
|
-
metafile: true,
|
|
282
|
-
outdir: join(config.outDir, "assets"),
|
|
283
|
-
absWorkingDir: config.root,
|
|
284
|
-
publicPath: `${config.base}assets`,
|
|
285
|
-
// Les empreintes rendent les fichiers immuables : ils peuvent etre mis en
|
|
286
|
-
// cache indefiniment, et un deploiement n'invalide que ce qui a change.
|
|
287
|
-
entryNames: "[name]-[hash]",
|
|
288
|
-
chunkNames: "chunk-[hash]",
|
|
289
|
-
assetNames: "[name]-[hash]",
|
|
290
|
-
jsx: "automatic",
|
|
291
|
-
logLevel: "silent",
|
|
292
|
-
define: {
|
|
293
|
-
"import.meta.env": JSON.stringify(buildEnv(config)),
|
|
294
|
-
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
295
|
-
...config.define
|
|
296
|
-
},
|
|
297
|
-
loader: {
|
|
298
|
-
".svg": "file",
|
|
299
|
-
".png": "file",
|
|
300
|
-
".jpg": "file",
|
|
301
|
-
".jpeg": "file",
|
|
302
|
-
".gif": "file",
|
|
303
|
-
".webp": "file",
|
|
304
|
-
".avif": "file",
|
|
305
|
-
".ico": "file",
|
|
306
|
-
".woff": "file",
|
|
307
|
-
".woff2": "file",
|
|
308
|
-
".mp4": "file",
|
|
309
|
-
".webm": "file"
|
|
310
|
-
},
|
|
311
|
-
plugins: [
|
|
312
|
-
{
|
|
313
|
-
name: "odoro-alias",
|
|
314
|
-
setup(builder) {
|
|
315
|
-
builder.onResolve({ filter: /.*/ }, (args) => {
|
|
316
|
-
if (args.kind === "entry-point") return null;
|
|
317
|
-
const aliased = applyAlias(args.path, config);
|
|
318
|
-
if (aliased === args.path || isBareSpecifier(aliased)) return null;
|
|
319
|
-
return builder.resolve(aliased, {
|
|
320
|
-
kind: "import-statement",
|
|
321
|
-
resolveDir: args.resolveDir,
|
|
322
|
-
importer: args.importer,
|
|
323
|
-
pluginData: { aliased: true }
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
]
|
|
329
|
-
});
|
|
330
|
-
if (config.build.elaguer) await taillerFeuilles(result, config, html);
|
|
331
|
-
let output = html;
|
|
332
|
-
for (const entry of entries) {
|
|
333
|
-
const { script, styles } = outputsForEntry(
|
|
334
|
-
result,
|
|
335
|
-
entry,
|
|
336
|
-
join(config.outDir, "assets"),
|
|
337
|
-
config.root
|
|
338
|
-
);
|
|
339
|
-
if (script === void 0) continue;
|
|
340
|
-
const original = new RegExp(
|
|
341
|
-
`<script[^>]*type=["']module["'][^>]*src=["'][^"']*${basename(entry)}["'][^>]*></script>`,
|
|
342
|
-
"i"
|
|
343
|
-
);
|
|
344
|
-
const tags = [
|
|
345
|
-
...styles.map(
|
|
346
|
-
(style) => `<link rel="stylesheet" href="${config.base}assets/${style}">`
|
|
347
|
-
),
|
|
348
|
-
`<script type="module" crossorigin src="${config.base}assets/${script}"></script>`
|
|
349
|
-
].join("\n ");
|
|
350
|
-
output = output.replace(original, tags);
|
|
351
|
-
}
|
|
352
|
-
await writeFile(join(config.outDir, "index.html"), output, "utf8");
|
|
353
|
-
if (existsSync(config.publicDir)) {
|
|
354
|
-
await cp(config.publicDir, config.outDir, { recursive: true });
|
|
355
|
-
}
|
|
356
|
-
const files = (await Promise.all(
|
|
357
|
-
Object.keys(result.metafile.outputs).map(async (file) => {
|
|
358
|
-
const chemin = resolve(config.root, file);
|
|
359
|
-
return {
|
|
360
|
-
path: toPosix(relative(config.outDir, chemin)),
|
|
361
|
-
bytes: (await stat(chemin)).size
|
|
362
|
-
};
|
|
363
|
-
})
|
|
364
|
-
)).sort((a, b) => b.bytes - a.bytes);
|
|
365
|
-
return { outDir: config.outDir, files, elapsed: Date.now() - started };
|
|
366
|
-
}
|
|
367
|
-
function reportBuild(output, root) {
|
|
368
|
-
const directory = toPosix(relative(root, output.outDir)) || ".";
|
|
369
|
-
let total = 0;
|
|
370
|
-
for (const file of output.files) {
|
|
371
|
-
total += file.bytes;
|
|
372
|
-
if (file.path.endsWith(".map")) continue;
|
|
373
|
-
info(
|
|
374
|
-
` ${colors.dim(`${directory}/`)}${file.path} ${colors.dim(size(file.bytes))}`
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
success(`compile en ${duration(output.elapsed)} \u2014 ${size(total)} au total`);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export { buildProject, reportBuild };
|
package/dist/chunk-DL3NPC4H.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { guessAliasPaths } from './chunk-T6RLHSCW.js';
|
|
3
|
-
import { mkdir, writeFile, rm } from 'fs/promises';
|
|
4
|
-
import { existsSync } from 'fs';
|
|
5
|
-
import { resolve, join } from 'path';
|
|
6
|
-
import { pathToFileURL } from 'url';
|
|
7
|
-
import { build } from 'esbuild';
|
|
8
|
-
|
|
9
|
-
function defineConfig(config) {
|
|
10
|
-
return config;
|
|
11
|
-
}
|
|
12
|
-
var CONFIG_FILES = ["odoro.config.ts", "odoro.config.js", "odoro.config.mjs"];
|
|
13
|
-
async function importConfigFile(file, root) {
|
|
14
|
-
const directory = join(root, "node_modules", ".odoro");
|
|
15
|
-
const output = join(directory, `config.${Date.now().toString(36)}.mjs`);
|
|
16
|
-
await mkdir(directory, { recursive: true });
|
|
17
|
-
try {
|
|
18
|
-
const result = await build({
|
|
19
|
-
entryPoints: [file],
|
|
20
|
-
bundle: true,
|
|
21
|
-
format: "esm",
|
|
22
|
-
platform: "node",
|
|
23
|
-
target: "node20",
|
|
24
|
-
write: false,
|
|
25
|
-
// Seul le code du projet est inline ; ses dependances restent externes,
|
|
26
|
-
// sans quoi il faudrait resoudre tout node_modules pour lire trois lignes.
|
|
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"]
|
|
39
|
-
});
|
|
40
|
-
const code = result.outputFiles[0]?.text;
|
|
41
|
-
if (code === void 0) {
|
|
42
|
-
throw new Error(`[odoro] La configuration "${file}" n'a produit aucun code.`);
|
|
43
|
-
}
|
|
44
|
-
await writeFile(output, code, "utf8");
|
|
45
|
-
const module = await import(pathToFileURL(output).href);
|
|
46
|
-
if (module.default === void 0) {
|
|
47
|
-
throw new Error(
|
|
48
|
-
`[odoro] La configuration "${file}" doit avoir un export par defaut.`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
return module.default;
|
|
52
|
-
} finally {
|
|
53
|
-
await rm(output, { force: true });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async function loadConfig(root, overrides = {}) {
|
|
57
|
-
const absoluteRoot = resolve(root);
|
|
58
|
-
let file;
|
|
59
|
-
let loaded = {};
|
|
60
|
-
for (const candidate of CONFIG_FILES) {
|
|
61
|
-
const path = join(absoluteRoot, candidate);
|
|
62
|
-
if (existsSync(path)) {
|
|
63
|
-
file = path;
|
|
64
|
-
loaded = await importConfigFile(path, absoluteRoot);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const merged = {
|
|
69
|
-
...loaded,
|
|
70
|
-
...overrides,
|
|
71
|
-
server: { ...loaded.server, ...overrides.server },
|
|
72
|
-
build: { ...loaded.build, ...overrides.build },
|
|
73
|
-
alias: { ...loaded.alias, ...overrides.alias },
|
|
74
|
-
define: { ...loaded.define, ...overrides.define }
|
|
75
|
-
};
|
|
76
|
-
const base = merged.base ?? "/";
|
|
77
|
-
const projectRoot = merged.root === void 0 ? absoluteRoot : resolve(absoluteRoot, merged.root);
|
|
78
|
-
return {
|
|
79
|
-
root: projectRoot,
|
|
80
|
-
base: base.endsWith("/") ? base : `${base}/`,
|
|
81
|
-
publicDir: resolve(projectRoot, merged.publicDir ?? "public"),
|
|
82
|
-
outDir: resolve(projectRoot, merged.build?.outDir ?? "dist"),
|
|
83
|
-
server: {
|
|
84
|
-
port: merged.server?.port ?? 5180,
|
|
85
|
-
host: merged.server?.host ?? "localhost",
|
|
86
|
-
proxy: merged.server?.proxy ?? {}
|
|
87
|
-
},
|
|
88
|
-
build: {
|
|
89
|
-
outDir: merged.build?.outDir ?? "dist",
|
|
90
|
-
minify: merged.build?.minify ?? true,
|
|
91
|
-
sourcemap: merged.build?.sourcemap ?? true,
|
|
92
|
-
target: merged.build?.target ?? "es2022",
|
|
93
|
-
// Actif par defaut : une feuille qui contient toutes les classes
|
|
94
|
-
// possibles est un accident de generation, pas une intention.
|
|
95
|
-
elaguer: merged.build?.elaguer ?? true,
|
|
96
|
-
safelist: merged.build?.safelist ?? []
|
|
97
|
-
},
|
|
98
|
-
// Les alias declares dans `tsconfig.json` sont repris d'office. Sans
|
|
99
|
-
// cela, un projet qui suit `odoro init` — lequel deduit son prefixe du
|
|
100
|
-
// tsconfig — aurait a redeclarer le meme alias ici pour que le serveur
|
|
101
|
-
// sache le resoudre. Deux endroits pour la meme verite, et une erreur
|
|
102
|
-
// qui n'apparait qu'au premier import.
|
|
103
|
-
//
|
|
104
|
-
// La configuration l'emporte : c'est elle qu'on ecrit pour corriger un
|
|
105
|
-
// cas que la deduction n'attrape pas.
|
|
106
|
-
alias: { ...await guessAliasPaths(projectRoot), ...merged.alias },
|
|
107
|
-
define: merged.define ?? {},
|
|
108
|
-
envPrefix: merged.envPrefix ?? "ODORO_",
|
|
109
|
-
configFile: file
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export { defineConfig, loadConfig };
|