@owlmeans/create-app 0.1.10
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 +59 -0
- package/build/args.d.ts +21 -0
- package/build/args.d.ts.map +1 -0
- package/build/args.js +96 -0
- package/build/args.js.map +1 -0
- package/build/bin.d.ts +3 -0
- package/build/bin.d.ts.map +1 -0
- package/build/bin.js +21 -0
- package/build/bin.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/run.d.ts +3 -0
- package/build/run.d.ts.map +1 -0
- package/build/run.js +81 -0
- package/build/run.js.map +1 -0
- package/build/template.d.ts +17 -0
- package/build/template.d.ts.map +1 -0
- package/build/template.js +49 -0
- package/build/template.js.map +1 -0
- package/package.json +43 -0
- package/template/README.md +51 -0
- package/template/_gitignore +7 -0
- package/template/package.json +21 -0
- package/template/sources/api/package.json +23 -0
- package/template/sources/api/src/app/session/add.ts +20 -0
- package/template/sources/api/src/app/session/index.ts +3 -0
- package/template/sources/api/src/app/session/list.ts +15 -0
- package/template/sources/api/src/app/session/remove.ts +18 -0
- package/template/sources/api/src/config.ts +8 -0
- package/template/sources/api/src/consts.ts +2 -0
- package/template/sources/api/src/context.ts +14 -0
- package/template/sources/api/src/index.ts +9 -0
- package/template/sources/api/src/modules.ts +11 -0
- package/template/sources/api/src/types.ts +6 -0
- package/template/sources/api/tsconfig.json +11 -0
- package/template/sources/common/package.json +34 -0
- package/template/sources/common/src/config.ts +24 -0
- package/template/sources/common/src/consts.ts +21 -0
- package/template/sources/common/src/index.ts +5 -0
- package/template/sources/common/src/modules.ts +26 -0
- package/template/sources/common/src/schemas.ts +30 -0
- package/template/sources/common/src/types.ts +19 -0
- package/template/sources/common/tsconfig.json +8 -0
- package/template/sources/web/index.html +15 -0
- package/template/sources/web/package.json +49 -0
- package/template/sources/web/src/components/nav/main.tsx +15 -0
- package/template/sources/web/src/components/ui/alert.tsx +70 -0
- package/template/sources/web/src/components/ui/button.tsx +60 -0
- package/template/sources/web/src/components/ui/card.tsx +93 -0
- package/template/sources/web/src/components/ui/input.tsx +22 -0
- package/template/sources/web/src/components/ui/label.tsx +23 -0
- package/template/sources/web/src/components/ui/progress.tsx +44 -0
- package/template/sources/web/src/config.ts +7 -0
- package/template/sources/web/src/context.ts +7 -0
- package/template/sources/web/src/index.css +77 -0
- package/template/sources/web/src/index.tsx +15 -0
- package/template/sources/web/src/layout/main.tsx +21 -0
- package/template/sources/web/src/lib/utils.ts +6 -0
- package/template/sources/web/src/modules.ts +20 -0
- package/template/sources/web/src/render.tsx +8 -0
- package/template/sources/web/src/screens/home.tsx +24 -0
- package/template/sources/web/src/screens/session.tsx +94 -0
- package/template/sources/web/src/types.ts +5 -0
- package/template/sources/web/tsconfig.json +17 -0
- package/template/sources/web/vite.config.ts +29 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* shadcn / Tailwind v4 design tokens (new-york, neutral). The app owns these — they
|
|
5
|
+
* replace what @owlmeans/owl-theme would otherwise provide. The shadcn primitives in
|
|
6
|
+
* components/ui and the @owlmeans web components consume them via `bg-background`, etc.
|
|
7
|
+
*/
|
|
8
|
+
@theme {
|
|
9
|
+
--color-background: oklch(1 0 0);
|
|
10
|
+
--color-foreground: oklch(0.145 0 0);
|
|
11
|
+
|
|
12
|
+
--color-card: oklch(1 0 0);
|
|
13
|
+
--color-card-foreground: oklch(0.145 0 0);
|
|
14
|
+
|
|
15
|
+
--color-popover: oklch(1 0 0);
|
|
16
|
+
--color-popover-foreground: oklch(0.145 0 0);
|
|
17
|
+
|
|
18
|
+
--color-primary: oklch(0.205 0 0);
|
|
19
|
+
--color-primary-foreground: oklch(0.985 0 0);
|
|
20
|
+
|
|
21
|
+
--color-secondary: oklch(0.97 0 0);
|
|
22
|
+
--color-secondary-foreground: oklch(0.205 0 0);
|
|
23
|
+
|
|
24
|
+
--color-muted: oklch(0.97 0 0);
|
|
25
|
+
--color-muted-foreground: oklch(0.556 0 0);
|
|
26
|
+
|
|
27
|
+
--color-accent: oklch(0.97 0 0);
|
|
28
|
+
--color-accent-foreground: oklch(0.205 0 0);
|
|
29
|
+
|
|
30
|
+
--color-destructive: oklch(0.577 0.245 27.325);
|
|
31
|
+
--color-success: oklch(0.62 0.18 145);
|
|
32
|
+
|
|
33
|
+
--color-border: oklch(0.922 0 0);
|
|
34
|
+
--color-input: oklch(0.922 0 0);
|
|
35
|
+
--color-ring: oklch(0.708 0 0);
|
|
36
|
+
|
|
37
|
+
--radius: 0.625rem;
|
|
38
|
+
|
|
39
|
+
--animate-progress-indeterminate: progress-indeterminate 1.5s linear infinite;
|
|
40
|
+
|
|
41
|
+
@keyframes progress-indeterminate {
|
|
42
|
+
0% { transform: translateX(-100%); }
|
|
43
|
+
100% { transform: translateX(100%); }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dark {
|
|
48
|
+
--color-background: oklch(0.145 0 0);
|
|
49
|
+
--color-foreground: oklch(0.985 0 0);
|
|
50
|
+
--color-card: oklch(0.205 0 0);
|
|
51
|
+
--color-card-foreground: oklch(0.985 0 0);
|
|
52
|
+
--color-popover: oklch(0.205 0 0);
|
|
53
|
+
--color-popover-foreground: oklch(0.985 0 0);
|
|
54
|
+
--color-primary: oklch(0.922 0 0);
|
|
55
|
+
--color-primary-foreground: oklch(0.205 0 0);
|
|
56
|
+
--color-secondary: oklch(0.269 0 0);
|
|
57
|
+
--color-secondary-foreground: oklch(0.985 0 0);
|
|
58
|
+
--color-muted: oklch(0.269 0 0);
|
|
59
|
+
--color-muted-foreground: oklch(0.708 0 0);
|
|
60
|
+
--color-accent: oklch(0.269 0 0);
|
|
61
|
+
--color-accent-foreground: oklch(0.985 0 0);
|
|
62
|
+
--color-destructive: oklch(0.704 0.191 22.216);
|
|
63
|
+
--color-success: oklch(0.7 0.18 145);
|
|
64
|
+
--color-border: oklch(1 0 0 / 10%);
|
|
65
|
+
--color-input: oklch(1 0 0 / 15%);
|
|
66
|
+
--color-ring: oklch(0.556 0 0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@layer base {
|
|
70
|
+
* {
|
|
71
|
+
border-color: var(--color-border);
|
|
72
|
+
}
|
|
73
|
+
body {
|
|
74
|
+
background-color: var(--color-background);
|
|
75
|
+
color: var(--color-foreground);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import './index.css'
|
|
2
|
+
import { APP_API, APP_WEB } from '__APP_SLUG__-common'
|
|
3
|
+
import config from './config.js'
|
|
4
|
+
import { makeContext } from './context.js'
|
|
5
|
+
import { appModules } from './modules.js'
|
|
6
|
+
import { render } from './render.js'
|
|
7
|
+
import type { Config, Context } from './types.js'
|
|
8
|
+
|
|
9
|
+
const context = makeContext<Config, Context>(config)
|
|
10
|
+
context.registerEntrypoints(appModules)
|
|
11
|
+
|
|
12
|
+
context.serviceRoute(APP_WEB, true)
|
|
13
|
+
context.serviceRoute(APP_API, true)
|
|
14
|
+
|
|
15
|
+
render<Config, Context>(context)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FC, PropsWithChildren } from 'react'
|
|
2
|
+
import { HOME, useNavigate } from '@owlmeans/web-panel'
|
|
3
|
+
import { MainNavigation } from '@/components/nav/main'
|
|
4
|
+
|
|
5
|
+
export const MainLayout: FC<PropsWithChildren> = ({ children }) => {
|
|
6
|
+
const nav = useNavigate()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="min-h-screen bg-background text-foreground">
|
|
10
|
+
<header className="border-b">
|
|
11
|
+
<div className="mx-auto flex h-16 max-w-4xl items-center justify-between px-4">
|
|
12
|
+
<a onClick={nav.press(HOME)} className="cursor-pointer text-lg font-semibold">
|
|
13
|
+
__APP_NAME__
|
|
14
|
+
</a>
|
|
15
|
+
<MainNavigation />
|
|
16
|
+
</div>
|
|
17
|
+
</header>
|
|
18
|
+
<main className="mx-auto max-w-4xl px-4 py-8">{children}</main>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BASE, elevate, entrypoint, frontend, handler, HOME, modules as baseModules, route } from '@owlmeans/web-panel'
|
|
2
|
+
import { session, sessionModules, web } from '__APP_SLUG__-common'
|
|
3
|
+
import { MainLayout } from './layout/main.js'
|
|
4
|
+
import { HomeScreen } from './screens/home.js'
|
|
5
|
+
import { SessionScreen } from './screens/session.js'
|
|
6
|
+
|
|
7
|
+
const modules = [...baseModules, ...sessionModules]
|
|
8
|
+
|
|
9
|
+
// Backend entrypoints — elevated without a component so the client can call them.
|
|
10
|
+
elevate(modules, session.base)
|
|
11
|
+
elevate(modules, session.list)
|
|
12
|
+
elevate(modules, session.add)
|
|
13
|
+
elevate(modules, session.remove)
|
|
14
|
+
|
|
15
|
+
// Frontend layout + screens. BASE renders the shared layout; HOME is its default child.
|
|
16
|
+
modules.push(entrypoint(route(BASE, '/', frontend()), handler(MainLayout)))
|
|
17
|
+
modules.push(entrypoint(route(HOME, '/', frontend({ default: true, parent: BASE })), handler(HomeScreen)))
|
|
18
|
+
modules.push(entrypoint(route(web.session, '/session', frontend({ parent: BASE })), handler(SessionScreen)))
|
|
19
|
+
|
|
20
|
+
export const appModules = modules
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppContext } from '@owlmeans/web-client'
|
|
2
|
+
import { render as basicRender, provide } from '@owlmeans/web-client'
|
|
3
|
+
import { PanelApp } from '@owlmeans/web-panel'
|
|
4
|
+
import type { Config, Context } from './types.js'
|
|
5
|
+
|
|
6
|
+
export const render = <C extends Config, T extends Context<C>>(context: T) => {
|
|
7
|
+
basicRender(<PanelApp context={context as unknown as AppContext} provide={provide} />)
|
|
8
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import { useNavigate } from '@owlmeans/web-panel'
|
|
3
|
+
import { web } from '__APP_SLUG__-common'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
6
|
+
|
|
7
|
+
export const HomeScreen: FC = () => {
|
|
8
|
+
const nav = useNavigate()
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Card>
|
|
12
|
+
<CardHeader>
|
|
13
|
+
<CardTitle>Welcome to __APP_NAME__</CardTitle>
|
|
14
|
+
<CardDescription>
|
|
15
|
+
A minimal fullstack OwlMeans Common app — no authentication, with session data
|
|
16
|
+
held in an in-memory resource on the backend.
|
|
17
|
+
</CardDescription>
|
|
18
|
+
</CardHeader>
|
|
19
|
+
<CardContent>
|
|
20
|
+
<Button onClick={nav.press(web.session)}>Open the session demo →</Button>
|
|
21
|
+
</CardContent>
|
|
22
|
+
</Card>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useEffect, useState, type FC } from 'react'
|
|
2
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
3
|
+
import { session, type SessionItem } from '__APP_SLUG__-common'
|
|
4
|
+
import { useContext } from '../context.js'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
7
|
+
import { Input } from '@/components/ui/input'
|
|
8
|
+
|
|
9
|
+
const SID_KEY = '__APP_SLUG__-sid'
|
|
10
|
+
|
|
11
|
+
const sessionId = (): string => {
|
|
12
|
+
let sid = localStorage.getItem(SID_KEY)
|
|
13
|
+
if (sid == null) {
|
|
14
|
+
sid = crypto.randomUUID()
|
|
15
|
+
localStorage.setItem(SID_KEY, sid)
|
|
16
|
+
}
|
|
17
|
+
return sid
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const SessionScreen: FC = () => {
|
|
21
|
+
const ctx = useContext()
|
|
22
|
+
const sid = sessionId()
|
|
23
|
+
const [items, setItems] = useState<SessionItem[]>([])
|
|
24
|
+
const [text, setText] = useState('')
|
|
25
|
+
const [busy, setBusy] = useState(false)
|
|
26
|
+
|
|
27
|
+
const load = async () => {
|
|
28
|
+
const [data] = await ctx
|
|
29
|
+
.entrypoint<ClientEntrypoint<SessionItem[]>>(session.list)
|
|
30
|
+
.call({ params: { sid } })
|
|
31
|
+
setItems(data ?? [])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
useEffect(() => { void load() }, [])
|
|
35
|
+
|
|
36
|
+
const add = async () => {
|
|
37
|
+
if (text.trim() === '') return
|
|
38
|
+
setBusy(true)
|
|
39
|
+
try {
|
|
40
|
+
await ctx
|
|
41
|
+
.entrypoint<ClientEntrypoint<SessionItem>>(session.add)
|
|
42
|
+
.call({ params: { sid }, body: { text } })
|
|
43
|
+
setText('')
|
|
44
|
+
await load()
|
|
45
|
+
} finally {
|
|
46
|
+
setBusy(false)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const remove = async (id: string) => {
|
|
51
|
+
await ctx
|
|
52
|
+
.entrypoint<ClientEntrypoint<{ removed: boolean }>>(session.remove)
|
|
53
|
+
.call({ params: { sid, id } })
|
|
54
|
+
await load()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Card>
|
|
59
|
+
<CardHeader>
|
|
60
|
+
<CardTitle>Session items</CardTitle>
|
|
61
|
+
</CardHeader>
|
|
62
|
+
<CardContent>
|
|
63
|
+
<p className="mb-4 text-sm text-muted-foreground">
|
|
64
|
+
Stored in memory on the API, keyed by your session id <code>{sid.slice(0, 8)}…</code>
|
|
65
|
+
</p>
|
|
66
|
+
<div className="mb-4 flex gap-2">
|
|
67
|
+
<Input
|
|
68
|
+
value={text}
|
|
69
|
+
onChange={e => setText(e.target.value)}
|
|
70
|
+
placeholder="Add an item…"
|
|
71
|
+
onKeyDown={e => { if (e.key === 'Enter') void add() }}
|
|
72
|
+
/>
|
|
73
|
+
<Button onClick={() => void add()} disabled={busy}>Add</Button>
|
|
74
|
+
</div>
|
|
75
|
+
<ul className="flex flex-col gap-2">
|
|
76
|
+
{items.map(item => (
|
|
77
|
+
<li
|
|
78
|
+
key={item.id}
|
|
79
|
+
className="flex items-center justify-between rounded-md border px-3 py-2"
|
|
80
|
+
>
|
|
81
|
+
<span>{item.text}</span>
|
|
82
|
+
<Button variant="ghost" size="sm" onClick={() => void remove(item.id)}>
|
|
83
|
+
Remove
|
|
84
|
+
</Button>
|
|
85
|
+
</li>
|
|
86
|
+
))}
|
|
87
|
+
{items.length === 0 && (
|
|
88
|
+
<li className="text-sm text-muted-foreground">No items yet — add one above.</li>
|
|
89
|
+
)}
|
|
90
|
+
</ul>
|
|
91
|
+
</CardContent>
|
|
92
|
+
</Card>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"@owlmeans/dep-config/tsconfig.base.json",
|
|
4
|
+
"@owlmeans/dep-config/tsconfig.react.json"
|
|
5
|
+
],
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"rootDir": "./src/",
|
|
8
|
+
"outDir": "./build/",
|
|
9
|
+
"moduleResolution": "Bundler",
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"paths": {
|
|
12
|
+
"@/*": ["./src/*"]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"include": ["./src/**/*"],
|
|
16
|
+
"exclude": ["./dist/**/*", "./build/**/*", "./*.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
4
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
|
|
7
|
+
// Packages that must resolve to a single copy so the React context instances created
|
|
8
|
+
// by providers and read by hooks are the same object.
|
|
9
|
+
const owlMeansSingletons = [
|
|
10
|
+
'@owlmeans/client',
|
|
11
|
+
'@owlmeans/client-i18n',
|
|
12
|
+
'@owlmeans/web-client',
|
|
13
|
+
'@owlmeans/web-panel',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
export default defineConfig({
|
|
17
|
+
plugins: [react(), tsconfigPaths(), tailwindcss()],
|
|
18
|
+
server: {
|
|
19
|
+
port: 3001,
|
|
20
|
+
host: '0.0.0.0',
|
|
21
|
+
strictPort: true,
|
|
22
|
+
},
|
|
23
|
+
resolve: {
|
|
24
|
+
alias: [
|
|
25
|
+
{ find: '@', replacement: path.resolve(__dirname, 'src') },
|
|
26
|
+
],
|
|
27
|
+
dedupe: ['react', 'react-dom', 'react-i18next', 'i18next', ...owlMeansSingletons],
|
|
28
|
+
},
|
|
29
|
+
})
|