odoro 1.0.8 → 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.
Files changed (85) hide show
  1. package/README.md +94 -0
  2. package/client.d.ts +90 -16
  3. package/dist/build-LZAUDJST.js +5 -0
  4. package/dist/{chunk-34RGOZFA.js → chunk-4D56Z7G3.js} +49 -50
  5. package/dist/{chunk-JMEHF3KN.js → chunk-7ZB5MAM6.js} +4 -4
  6. package/dist/{chunk-LHZGX5ML.js → chunk-OMHEQVIW.js} +295 -160
  7. package/dist/{chunk-3SZIN6VG.js → chunk-P3DJITWE.js} +10 -10
  8. package/dist/{chunk-ZVL7EXJO.js → chunk-PRWVZ2SM.js} +22 -14
  9. package/dist/chunk-Q2KCMTB5.js +741 -0
  10. package/dist/chunk-QOVBLN7A.js +241 -0
  11. package/dist/chunk-WWTDEV4Y.js +604 -0
  12. package/dist/cli.d.ts +12 -13
  13. package/dist/cli.js +84 -63
  14. package/dist/{commands-4JRBD55Z.js → commands-EUGVLQWL.js} +44 -45
  15. package/dist/{commands-AHMXBWQQ.js → commands-GYA7NTCX.js} +124 -124
  16. package/dist/{create-CDKS7KZ3.js → create-QX5LEWMS.js} +158 -154
  17. package/dist/index.d.ts +603 -151
  18. package/dist/index.js +6 -6
  19. package/dist/{package-NBP5NDX6.js → package-T4Z7OBOH.js} +5 -5
  20. package/dist/preview-P4WY5GEY.js +4 -0
  21. package/dist/registry/index.d.ts +75 -76
  22. package/dist/registry/index.js +1 -1
  23. package/dist/{server-4UGN3SFS.js → server-2WP562EV.js} +3 -3
  24. package/package.json +5 -5
  25. package/templates/react-ts/README.md +71 -35
  26. package/templates/react-ts/_env.example +9 -0
  27. package/templates/react-ts/_variants/with-engine/src/background.tsx +118 -0
  28. package/templates/react-ts/_variants/without-libs/src/App.tsx +380 -0
  29. package/templates/react-ts/_variants/without-libs/src/background.tsx +24 -0
  30. package/templates/react-ts/_variants/without-libs/src/entry-server.tsx +39 -0
  31. package/templates/react-ts/_variants/without-libs/src/main.tsx +26 -0
  32. package/templates/{react-ts-server/_variantes/sans-libs/client → react-ts/_variants/without-libs}/src/styles.css +137 -137
  33. package/templates/react-ts/{_variantes/sans-routeur → _variants/without-router}/src/App.tsx +141 -141
  34. package/templates/react-ts/_variants/without-router/src/entry-server.tsx +39 -0
  35. package/templates/react-ts/index.html +4 -4
  36. package/templates/react-ts/odoro.config.ts +5 -0
  37. package/templates/react-ts/src/App.tsx +219 -209
  38. package/templates/react-ts/src/background.tsx +66 -0
  39. package/templates/react-ts/src/entry-server.tsx +85 -0
  40. package/templates/react-ts/src/main.tsx +13 -4
  41. package/templates/react-ts/src/router.tsx +65 -45
  42. package/templates/react-ts/src/styles.css +5 -5
  43. package/templates/react-ts-server/Dockerfile +9 -9
  44. package/templates/react-ts-server/README.md +97 -45
  45. package/templates/react-ts-server/_env.example +31 -33
  46. package/templates/react-ts-server/_variants/with-engine/client/src/background.tsx +118 -0
  47. package/templates/react-ts-server/_variants/without-libs/client/src/App.tsx +380 -0
  48. package/templates/react-ts-server/_variants/without-libs/client/src/background.tsx +24 -0
  49. package/templates/react-ts-server/_variants/without-libs/client/src/entry-server.tsx +39 -0
  50. package/templates/react-ts-server/_variants/without-libs/client/src/main.tsx +26 -0
  51. package/templates/{react-ts/_variantes/sans-libs → react-ts-server/_variants/without-libs/client}/src/styles.css +137 -137
  52. package/templates/react-ts-server/{_variantes/sans-routeur → _variants/without-router}/client/src/App.tsx +141 -141
  53. package/templates/react-ts-server/_variants/without-router/client/src/entry-server.tsx +39 -0
  54. package/templates/react-ts-server/client/index.html +4 -4
  55. package/templates/react-ts-server/client/src/App.tsx +228 -210
  56. package/templates/react-ts-server/client/src/account.tsx +267 -0
  57. package/templates/react-ts-server/client/src/auth.tsx +139 -0
  58. package/templates/react-ts-server/client/src/background.tsx +66 -0
  59. package/templates/react-ts-server/client/src/entry-server.tsx +92 -0
  60. package/templates/react-ts-server/client/src/main.tsx +17 -5
  61. package/templates/react-ts-server/client/src/router.tsx +77 -45
  62. package/templates/react-ts-server/client/src/styles.css +5 -5
  63. package/templates/react-ts-server/odoro.config.ts +7 -4
  64. package/templates/react-ts-server/package.json +2 -0
  65. package/templates/react-ts-server/scripts/dev.mjs +14 -14
  66. package/templates/react-ts-server/server/src/main.ts +119 -45
  67. package/templates/react-ts-server/server/src/modules/auth/index.ts +238 -0
  68. package/templates/react-ts-server/server/src/modules/auth/password.ts +122 -0
  69. package/templates/react-ts-server/server/src/modules/auth/store.ts +193 -0
  70. package/templates/react-ts-server/server/src/modules/health/index.ts +45 -46
  71. package/dist/build-JFQHODAT.js +0 -5
  72. package/dist/chunk-22KJTV2R.js +0 -380
  73. package/dist/chunk-DL3NPC4H.js +0 -113
  74. package/dist/chunk-TQUJ3MFS.js +0 -268
  75. package/dist/preview-7DKEAQOQ.js +0 -4
  76. package/templates/react-ts/_variantes/avec-moteur/src/fond.tsx +0 -118
  77. package/templates/react-ts/_variantes/sans-libs/src/App.tsx +0 -382
  78. package/templates/react-ts/_variantes/sans-libs/src/fond.tsx +0 -23
  79. package/templates/react-ts/_variantes/sans-libs/src/main.tsx +0 -17
  80. package/templates/react-ts/src/fond.tsx +0 -42
  81. package/templates/react-ts-server/_variantes/avec-moteur/client/src/fond.tsx +0 -118
  82. package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +0 -382
  83. package/templates/react-ts-server/_variantes/sans-libs/client/src/fond.tsx +0 -23
  84. package/templates/react-ts-server/_variantes/sans-libs/client/src/main.tsx +0 -17
  85. package/templates/react-ts-server/client/src/fond.tsx +0 -42
@@ -0,0 +1,380 @@
1
+ /**
2
+ * The whole page, in one file.
3
+ *
4
+ * ## Without the libraries
5
+ *
6
+ * They were not picked at creation: no `o-*` classes, no tokens, no router.
7
+ * The design is that of the full version — same pill bar, same title, same
8
+ * cards — carried by `styles.css` in plain CSS.
9
+ *
10
+ * To add them later: `npm i @odoro-cli/libs`, then import
11
+ * `@odoro-cli/libs/styles.css` in `main.tsx` before this stylesheet.
12
+ *
13
+ * @module
14
+ */
15
+
16
+ import { useEffect, useState, type ReactElement } from 'react'
17
+
18
+ import { Background } from '@/background'
19
+
20
+ /* -------------------------------------------------------------------------- */
21
+ /* Brand */
22
+ /* -------------------------------------------------------------------------- */
23
+
24
+ /** The brand mark, at the requested size. */
25
+ function Mark({ size = '1.75rem' }: { readonly size?: string }): ReactElement {
26
+ return (
27
+ <svg
28
+ viewBox="0 0 100 100"
29
+ fill="none"
30
+ aria-hidden="true"
31
+ style={{ width: size, height: size, flexShrink: 0 }}
32
+ >
33
+ <path
34
+ d="M20.25 20.25H50a29.75 29.75 0 1 1-29.75 29.75Z"
35
+ stroke="currentColor"
36
+ strokeWidth="10.5"
37
+ strokeLinejoin="miter"
38
+ />
39
+ </svg>
40
+ )
41
+ }
42
+
43
+ /* -------------------------------------------------------------------------- */
44
+ /* Navigation bar */
45
+ /* -------------------------------------------------------------------------- */
46
+
47
+ /** The bar: the brand in a pill, and the link to the site. */
48
+ function Nav(): ReactElement {
49
+ return (
50
+ <header className="nav">
51
+ <div className="nav-content">
52
+ <span className="pill brand">
53
+ <span className="brand-mark">
54
+ <Mark />
55
+ </span>
56
+ <span className="brand-word">ODORO</span>
57
+ </span>
58
+
59
+ <div className="nav-end">
60
+ <a
61
+ href="https://odoro.dev"
62
+ target="_blank"
63
+ rel="noreferrer"
64
+ className="pill pill-link"
65
+ >
66
+ odoro.dev
67
+ </a>
68
+ <ThemeToggle />
69
+ </div>
70
+ </div>
71
+ </header>
72
+ )
73
+ }
74
+
75
+ /* -------------------------------------------------------------------------- */
76
+ /* Theme */
77
+ /* -------------------------------------------------------------------------- */
78
+
79
+ /** The three states of the toggle. */
80
+ type Theme = 'system' | 'light' | 'dark'
81
+
82
+ /** Where the choice is stored. The script in `index.html` reads the same key. */
83
+ const THEME_KEY = 'odoro-theme'
84
+
85
+ /** The order of the cycle, on click. */
86
+ const CYCLE: readonly Theme[] = ['system', 'light', 'dark']
87
+
88
+ /**
89
+ * Applies the theme to the root of the document.
90
+ *
91
+ * `system` removes the attribute rather than setting a third value: the
92
+ * stylesheet listens to `data-theme`, and its absence hands control back to
93
+ * the browser preference.
94
+ */
95
+ function applyTheme(theme: Theme): void {
96
+ if (theme === 'system') delete document.documentElement.dataset.theme
97
+ else document.documentElement.dataset.theme = theme
98
+ }
99
+
100
+ /** The stored theme, or `system`. */
101
+ function storedTheme(): Theme {
102
+ try {
103
+ const value = localStorage.getItem(THEME_KEY)
104
+ if (value === 'light') return 'light'
105
+ if (value === 'dark') return 'dark'
106
+ return 'system'
107
+ } catch {
108
+ // Storage refused — private window, cookies blocked. The system theme is
109
+ // still a valid answer.
110
+ return 'system'
111
+ }
112
+ }
113
+
114
+ /** The three glyphs, drawn inline. */
115
+ function Glyph({ theme }: { readonly theme: Theme }): ReactElement {
116
+ const common = {
117
+ viewBox: '0 0 24 24',
118
+ fill: 'none',
119
+ stroke: 'currentColor',
120
+ strokeWidth: 1.8,
121
+ strokeLinecap: 'round' as const,
122
+ strokeLinejoin: 'round' as const,
123
+ 'aria-hidden': true,
124
+ style: { width: '1.15rem', height: '1.15rem' },
125
+ }
126
+
127
+ if (theme === 'light') {
128
+ return (
129
+ <svg {...common}>
130
+ <circle cx="12" cy="12" r="4" />
131
+ <path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
132
+ </svg>
133
+ )
134
+ }
135
+
136
+ if (theme === 'dark') {
137
+ return (
138
+ <svg {...common}>
139
+ <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" />
140
+ </svg>
141
+ )
142
+ }
143
+
144
+ return (
145
+ <svg {...common}>
146
+ <rect x="2" y="4" width="20" height="13" rx="2" />
147
+ <path d="M8 21h8M12 17v4" />
148
+ </svg>
149
+ )
150
+ }
151
+
152
+ /** The theme toggle: system, light, dark. */
153
+ function ThemeToggle(): ReactElement {
154
+ const [theme, setTheme] = useState<Theme>(() => storedTheme())
155
+
156
+ useEffect(() => {
157
+ applyTheme(theme)
158
+ try {
159
+ if (theme === 'system') localStorage.removeItem(THEME_KEY)
160
+ else localStorage.setItem(THEME_KEY, theme)
161
+ } catch {
162
+ // The theme stays applied for the session, even without storage.
163
+ }
164
+ }, [theme])
165
+
166
+ return (
167
+ <button
168
+ type="button"
169
+ aria-label={`Theme: ${theme}`}
170
+ title={`Theme: ${theme}`}
171
+ className="pill toggle"
172
+ onClick={() => {
173
+ setTheme((current) => CYCLE[(CYCLE.indexOf(current) + 1) % CYCLE.length] ?? 'system')
174
+ }}
175
+ >
176
+ <Glyph theme={theme} />
177
+ </button>
178
+ )
179
+ }
180
+
181
+ /* -------------------------------------------------------------------------- */
182
+ /* Sections */
183
+ /* -------------------------------------------------------------------------- */
184
+
185
+ /** Three facts, not three slogans. */
186
+ const FACTS = ['Zero configuration', 'Strict TypeScript', 'Hot reloading']
187
+
188
+ /** What the product window shows. */
189
+ const SNIPPET = `export function App() {
190
+ return (
191
+ <h1 className="hero-title">
192
+ Your first page.
193
+ </h1>
194
+ )
195
+ }`
196
+
197
+ /** A miniature browser window. */
198
+ function Window(): ReactElement {
199
+ return (
200
+ <div className="window">
201
+ <div className="window-bar">
202
+ <span className="window-dots" aria-hidden="true">
203
+ <span />
204
+ <span />
205
+ <span />
206
+ </span>
207
+ <span className="window-path">src/App.tsx</span>
208
+ </div>
209
+ <pre className="window-code">{SNIPPET}</pre>
210
+ </div>
211
+ )
212
+ }
213
+
214
+ /** The hero: the promise, the actions, and the product window. */
215
+ function Hero(): ReactElement {
216
+ return (
217
+ <section className="hero">
218
+ <div className="content">
219
+ <span className="brand-dot">
220
+ <Mark size="0.9rem" />
221
+ Generated by odoro create
222
+ </span>
223
+
224
+ <h1 className="hero-title">
225
+ Your first page, <span className="accent">already alive</span>.
226
+ </h1>
227
+
228
+ <p className="hero-eyebrow">
229
+ The development server, the build and the hot reloading all come from{' '}
230
+ <code>odoro</code>. The style system is yours.
231
+ </p>
232
+
233
+ <div className="hero-actions">
234
+ <a href="#pillars" className="button button-primary">
235
+ See what is included
236
+ </a>
237
+ <a
238
+ href="https://odoro.dev/docs"
239
+ target="_blank"
240
+ rel="noreferrer"
241
+ className="button button-secondary"
242
+ >
243
+ Documentation
244
+ </a>
245
+ </div>
246
+
247
+ <div className="facts">
248
+ {FACTS.map((fact) => (
249
+ <span key={fact} className="fact">
250
+ {fact}
251
+ </span>
252
+ ))}
253
+ </div>
254
+
255
+ <div className="hero-window">
256
+ <Window />
257
+ </div>
258
+ </div>
259
+ </section>
260
+ )
261
+ }
262
+
263
+ /** What the project already has at hand. */
264
+ const PILLARS = [
265
+ {
266
+ title: 'Build',
267
+ text: 'A development server with hot reloading, and a production build.',
268
+ },
269
+ {
270
+ title: 'TypeScript',
271
+ text: 'Strict types, path aliases, and checking without emitting.',
272
+ },
273
+ {
274
+ title: 'Over to you',
275
+ text: 'No style system imposed: yours takes all the room.',
276
+ },
277
+ ]
278
+
279
+ /** The pillars section. */
280
+ function Pillars(): ReactElement {
281
+ return (
282
+ <section id="pillars" className="section">
283
+ <div className="content">
284
+ <h2 className="overline">What is already here</h2>
285
+ <div className="grid">
286
+ {PILLARS.map((pillar, index) => (
287
+ <article key={pillar.title} className="card">
288
+ <span className="card-number">{String(index + 1).padStart(2, '0')}</span>
289
+ <h3>{pillar.title}</h3>
290
+ <p>{pillar.text}</p>
291
+ </article>
292
+ ))}
293
+ </div>
294
+ </div>
295
+ </section>
296
+ )
297
+ }
298
+
299
+ /** The command people copy the most. */
300
+ const COMMAND = 'npm i @odoro-cli/libs'
301
+
302
+ /** The closing: one command, and the door to the documentation. */
303
+ function Closing(): ReactElement {
304
+ const [copied, setCopied] = useState(false)
305
+
306
+ return (
307
+ <section className="section section-centered">
308
+ <div className="content-narrow">
309
+ <h2 className="closing-title">Add the style system whenever you like.</h2>
310
+ <p className="closing-eyebrow">
311
+ The tokens, the utilities and the components live in a separate package. This
312
+ project runs without them, and takes them in without breaking anything.
313
+ </p>
314
+
315
+ <div className="command">
316
+ <code>{COMMAND}</code>
317
+ <button
318
+ type="button"
319
+ className="button button-ghost"
320
+ onClick={() => {
321
+ // The clipboard can be refused — outside a secure context, or
322
+ // permission withdrawn. The failure does not break the page: the
323
+ // label simply does not change.
324
+ void navigator.clipboard
325
+ ?.writeText(COMMAND)
326
+ .then(() => {
327
+ setCopied(true)
328
+ setTimeout(() => {
329
+ setCopied(false)
330
+ }, 1600)
331
+ })
332
+ .catch(() => undefined)
333
+ }}
334
+ >
335
+ {copied ? 'Copied' : 'Copy'}
336
+ </button>
337
+ </div>
338
+
339
+ <a
340
+ href="https://odoro.dev/docs/installation"
341
+ target="_blank"
342
+ rel="noreferrer"
343
+ className="quiet-link"
344
+ >
345
+ Read the walkthrough
346
+ </a>
347
+ </div>
348
+ </section>
349
+ )
350
+ }
351
+
352
+ /* -------------------------------------------------------------------------- */
353
+ /* Root */
354
+ /* -------------------------------------------------------------------------- */
355
+
356
+ /** Root of the application. */
357
+ export function App(): ReactElement {
358
+ return (
359
+ // The background sits here and not in the content: anchored lower, it
360
+ // started below the bar and left a darker strip behind it.
361
+ <div className="app-shell app-shell-background">
362
+ <Background />
363
+ <Nav />
364
+ <main className="main">
365
+ <Hero />
366
+ <Pillars />
367
+ <Closing />
368
+ </main>
369
+ <footer className="footer">
370
+ <span className="brand">
371
+ <span className="brand-mark">
372
+ <Mark size="1.1rem" />
373
+ </span>
374
+ <span className="brand-word">ODORO</span>
375
+ </span>
376
+ <span>Built with Odoro.</span>
377
+ </footer>
378
+ </div>
379
+ )
380
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The decorative background of the page.
3
+ *
4
+ * Two glows in the brand colour, heavily diluted. Nothing moves: it is a
5
+ * gradient, not an animation.
6
+ *
7
+ * If the engine was picked, this file is replaced by a version that opens an
8
+ * animated WebGL surface. `App.tsx` places `<Background />` and asks for
9
+ * nothing more.
10
+ *
11
+ * @module
12
+ */
13
+
14
+ import type { ReactElement } from 'react'
15
+
16
+ /** Two brand glows behind the top of the page. */
17
+ export function Background(): ReactElement {
18
+ return (
19
+ <div className="background" aria-hidden="true">
20
+ <div className="background-glow background-glow-top" />
21
+ <div className="background-glow background-glow-right" />
22
+ </div>
23
+ )
24
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The prerendering entry point.
3
+ *
4
+ * Without a router there is only one page: the list of routes comes down to
5
+ * the root. The rest is identical — `odoro build` renders this HTML at build
6
+ * time, and `src/main.tsx` hydrates it on load.
7
+ *
8
+ * To do without: remove `prerender` from `odoro.config.ts`, then this file.
9
+ *
10
+ * @module
11
+ */
12
+
13
+ import { StrictMode } from 'react'
14
+ import { renderToString } from 'react-dom/server'
15
+
16
+ import { App } from '@/App'
17
+
18
+ /** The routes rendered at build time. */
19
+ export const routes = ['/']
20
+
21
+ /**
22
+ * Renders the page.
23
+ *
24
+ * @returns The HTML of the container, and the tags to add to the head.
25
+ */
26
+ export function render(): { html: string; head: string } {
27
+ return {
28
+ // `StrictMode` on both sides: a tree rendered without it here and with it
29
+ // in the browser does not always produce the same markup, and hydration
30
+ // would report a difference that is not one.
31
+ html: renderToString(
32
+ <StrictMode>
33
+ <App />
34
+ </StrictMode>,
35
+ ),
36
+ head:
37
+ '<meta name="description" content="An Odoro project: in-house engine, generated styles.">',
38
+ }
39
+ }
@@ -0,0 +1,26 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot, hydrateRoot } 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('Root element "#root" not found in index.html.')
11
+ }
12
+
13
+ const tree = (
14
+ <StrictMode>
15
+ <App />
16
+ </StrictMode>
17
+ )
18
+
19
+ // A container that is already filled comes from prerendering: it has to be
20
+ // **hydrated**, that is, taken over as it stands with the events attached.
21
+ // Rebuilding it would throw away the page the visitor already sees only to draw
22
+ // it again identically — a flicker, and the whole benefit of prerendering lost.
23
+ //
24
+ // Empty, it is an ordinary render.
25
+ if (container.firstElementChild === null) createRoot(container).render(tree)
26
+ else hydrateRoot(container, tree)