odoro 1.0.9 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -0
- package/client.d.ts +90 -16
- package/dist/build-XJIP4GNT.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-DF67LADH.js +735 -0
- package/dist/{chunk-LHZGX5ML.js → chunk-HS7NGZLM.js} +307 -196
- package/dist/chunk-LIKVHWB4.js +231 -0
- package/dist/chunk-QOVBLN7A.js +241 -0
- package/dist/chunk-RTWJ5GFB.js +729 -0
- package/dist/chunk-TONYIKCN.js +57 -0
- package/dist/cli.d.ts +12 -13
- package/dist/cli.js +86 -63
- package/dist/{commands-AHMXBWQQ.js → commands-GYA7NTCX.js} +124 -124
- package/dist/commands-UOWN3IK4.js +181 -0
- package/dist/{create-CYVSVDAD.js → create-B2TLV6XI.js} +198 -160
- package/dist/index.d.ts +603 -151
- package/dist/index.js +6 -6
- package/dist/{package-EIDLKAA6.js → package-3ZJOEXM4.js} +6 -5
- package/dist/preview-3IRG7MHV.js +4 -0
- package/dist/registry/index.d.ts +75 -76
- package/dist/registry/index.js +1 -1
- package/dist/{server-4UGN3SFS.js → server-ZB3P3EGS.js} +3 -3
- package/package.json +6 -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-3SZIN6VG.js +0 -64
- package/dist/chunk-DL3NPC4H.js +0 -113
- package/dist/chunk-TQUJ3MFS.js +0 -268
- package/dist/chunk-ZVL7EXJO.js +0 -66
- package/dist/commands-4JRBD55Z.js +0 -245
- 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-3SZIN6VG.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readFile, writeFile, appendFile } from 'fs/promises';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
|
|
6
|
-
function checkDatabaseUrl(value) {
|
|
7
|
-
const url = value.trim();
|
|
8
|
-
if (url.length === 0) return "L'URL ne peut pas etre vide.";
|
|
9
|
-
if (!/^postgres(ql)?:\/\//i.test(url)) {
|
|
10
|
-
return "Une URL PostgreSQL commence par postgres:// ou postgresql://";
|
|
11
|
-
}
|
|
12
|
-
let parsed;
|
|
13
|
-
try {
|
|
14
|
-
parsed = new URL(url);
|
|
15
|
-
} catch {
|
|
16
|
-
return "Cette chaine n'est pas une URL valide.";
|
|
17
|
-
}
|
|
18
|
-
if (parsed.hostname.length === 0) return "Aucun hote dans cette URL.";
|
|
19
|
-
if (parsed.pathname.replace(/^\//, "").length === 0) {
|
|
20
|
-
return "Aucun nom de base : ajoutez-le apres le port, par exemple /mon_projet";
|
|
21
|
-
}
|
|
22
|
-
if (!/^(localhost|127\.0\.0\.1|\[::1\])$/i.test(parsed.hostname) && parsed.searchParams.get("sslmode") === "disable") {
|
|
23
|
-
return "sslmode=disable sur une base distante : le trafic passerait en clair.";
|
|
24
|
-
}
|
|
25
|
-
return void 0;
|
|
26
|
-
}
|
|
27
|
-
async function writeDatabaseUrl(target, url) {
|
|
28
|
-
const path = join(target, ".env");
|
|
29
|
-
const line = `DATABASE_URL=${url}
|
|
30
|
-
`;
|
|
31
|
-
if (!existsSync(path)) {
|
|
32
|
-
const example = join(target, ".env.example");
|
|
33
|
-
const base = existsSync(example) ? await readFile(example, "utf8") : "";
|
|
34
|
-
await writeFile(path, base.replace(/^DATABASE_URL=.*$/m, line.trimEnd()), "utf8");
|
|
35
|
-
if (!base.includes("DATABASE_URL=")) await appendFile(path, line, "utf8");
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const current = await readFile(path, "utf8");
|
|
39
|
-
await writeFile(
|
|
40
|
-
path,
|
|
41
|
-
current.includes("DATABASE_URL=") ? current.replace(/^DATABASE_URL=.*$/m, line.trimEnd()) : current + line,
|
|
42
|
-
"utf8"
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
async function assertEnvIgnored(target) {
|
|
46
|
-
const path = join(target, ".gitignore");
|
|
47
|
-
if (!existsSync(path)) {
|
|
48
|
-
return "Aucun .gitignore : le fichier .env risque d'etre versionne.";
|
|
49
|
-
}
|
|
50
|
-
const content = await readFile(path, "utf8");
|
|
51
|
-
const ignored = content.split("\n").map((line) => line.trim()).some((line) => line === ".env" || line === ".env*" || line === "*.env");
|
|
52
|
-
return ignored ? void 0 : ".env ne figure pas dans le .gitignore : vos identifiants risquent d etre versionnes.";
|
|
53
|
-
}
|
|
54
|
-
var PROVIDER_PENDING = [
|
|
55
|
-
"Le provisionnement passe par @odoro-cli/cloud-sdk, installe a part :",
|
|
56
|
-
"",
|
|
57
|
-
" npm install --save-dev @odoro-cli/cloud-sdk",
|
|
58
|
-
" odoro db:login",
|
|
59
|
-
" odoro db:create --env production",
|
|
60
|
-
"",
|
|
61
|
-
"Il ne vient pas avec odoro : ce binaire est telecharge a chaque creation de projet, et la plupart emploient leur propre base."
|
|
62
|
-
].join("\n");
|
|
63
|
-
|
|
64
|
-
export { PROVIDER_PENDING, assertEnvIgnored, checkDatabaseUrl, writeDatabaseUrl };
|