@ossy/app 0.9.0 → 0.10.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 +63 -1
- package/cli/build.js +170 -11
- package/cli/default-app.jsx +10 -0
- package/cli/default-config.js +5 -0
- package/cli/dev.js +51 -11
- package/cli/proxy-internal.js +1 -1
- package/cli/server.js +8 -1
- package/package.json +13 -6
- package/src/index.js +2 -0
- package/tsconfig.json +19 -0
- package/cli/index.js +0 -21
package/README.md
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
# `@ossy/app`
|
|
2
2
|
|
|
3
|
-
Server-side rendering runtime for Ossy apps.
|
|
3
|
+
Server-side rendering runtime and build tooling for Ossy apps. Use with `@ossy/cli` for the convention-based setup (`npx @ossy/cli dev`).
|
|
4
|
+
|
|
5
|
+
For custom setups (Next.js, Vite, etc.), use `@ossy/connected-components` directly — see the [root README](../README.md#when-to-use-what).
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Create `*.page.jsx` files in `src/`:
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
// src/home.page.jsx
|
|
13
|
+
import React from 'react'
|
|
14
|
+
export default () => <h1>Welcome</h1>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Each file becomes a route: `home.page.jsx` → `/`, `about.page.jsx` → `/about`. Optionally export `metadata` for custom id/path or multi-language:
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
export const metadata = { path: { en: '/about', sv: '/om' } }
|
|
21
|
+
export default () => <h1>About</h1>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For a single-file setup, use `src/pages.jsx` (legacy).
|
|
25
|
+
|
|
26
|
+
Add `src/config.js` for workspace and theme:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import { CloudLight } from '@ossy/themes'
|
|
30
|
+
|
|
31
|
+
export default {
|
|
32
|
+
workspaceId: 'your-workspace-id',
|
|
33
|
+
theme: CloudLight, // or 'light' | 'dark' | CloudDark
|
|
34
|
+
apiUrl: 'https://api.ossy.se/api/v0', // optional
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Config is loaded at build time and merged with request-time settings (e.g. user theme preference from cookies). The server passes `workspaceId`, `apiUrl`, and `theme` to the App component.
|
|
39
|
+
|
|
40
|
+
Run `npx @ossy/cli dev` or `npx @ossy/cli build`.
|
|
41
|
+
|
|
42
|
+
## API routes
|
|
43
|
+
|
|
44
|
+
Create `src/api.js` to define custom API endpoints. Each route must have `id`, `path`, and a `handle(req, res)` function:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
export default [
|
|
48
|
+
{
|
|
49
|
+
id: 'health',
|
|
50
|
+
path: '/api/health',
|
|
51
|
+
handle(req, res) {
|
|
52
|
+
res.json({ status: 'ok' })
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'users',
|
|
57
|
+
path: '/api/users',
|
|
58
|
+
handle(req, res) {
|
|
59
|
+
res.json({ users: [] })
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
API routes are matched before the app is rendered. The router supports dynamic segments (e.g. `path: '/api/users/:id'`); extract params from `req.originalUrl` if needed. Use paths that don't conflict with `/@ossy/*` (reserved for the internal proxy).
|
|
4
66
|
|
|
5
67
|
## Port configuration
|
|
6
68
|
|
package/cli/build.js
CHANGED
|
@@ -16,12 +16,143 @@ import remove from 'rollup-plugin-delete';
|
|
|
16
16
|
import arg from 'arg'
|
|
17
17
|
// import inject from '@rollup/plugin-inject'
|
|
18
18
|
|
|
19
|
+
const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
|
|
20
|
+
|
|
21
|
+
export function discoverPageFiles(srcDir) {
|
|
22
|
+
const dir = path.resolve(srcDir)
|
|
23
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
const files = []
|
|
27
|
+
const walk = (d) => {
|
|
28
|
+
const entries = fs.readdirSync(d, { withFileTypes: true })
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
const full = path.join(d, e.name)
|
|
31
|
+
if (e.isDirectory()) walk(full)
|
|
32
|
+
else if (PAGE_FILE_PATTERN.test(e.name)) files.push(full)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
walk(dir)
|
|
36
|
+
return files.sort()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function filePathToRoute(filePath, srcDir) {
|
|
40
|
+
const rel = path.relative(srcDir, filePath).replace(/\\/g, '/')
|
|
41
|
+
let pathPart = rel.replace(PAGE_FILE_PATTERN, '').replace(/\/index$/, '').replace(/\/home$/, '') || 'home'
|
|
42
|
+
if (pathPart === 'index' || pathPart === 'home') pathPart = 'home'
|
|
43
|
+
const id = pathPart === 'home' ? 'home' : pathPart.replace(/\//g, '-')
|
|
44
|
+
const routePath = pathPart === 'home' ? '/' : '/' + pathPart
|
|
45
|
+
return { id, path: routePath }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function generatePagesModule(pageFiles, cwd, srcDir = 'src') {
|
|
49
|
+
const resolvedSrc = path.resolve(cwd, srcDir)
|
|
50
|
+
const lines = [
|
|
51
|
+
"import React from 'react'",
|
|
52
|
+
...pageFiles.map((f, i) => {
|
|
53
|
+
const rel = path.relative(cwd, f).replace(/\\/g, '/')
|
|
54
|
+
return `import * as _page${i} from './${rel}'`
|
|
55
|
+
}),
|
|
56
|
+
'',
|
|
57
|
+
'function toPage(mod, derived) {',
|
|
58
|
+
' const meta = mod?.metadata || {}',
|
|
59
|
+
" const def = mod?.default",
|
|
60
|
+
" if (typeof def === 'function') {",
|
|
61
|
+
" return { ...derived, ...meta, element: React.createElement(def) }",
|
|
62
|
+
' }',
|
|
63
|
+
" return { ...derived, ...meta, ...(def || {}) }",
|
|
64
|
+
'}',
|
|
65
|
+
'',
|
|
66
|
+
'export default [',
|
|
67
|
+
...pageFiles.map((f, i) => {
|
|
68
|
+
const { id, path: defaultPath } = filePathToRoute(f, resolvedSrc)
|
|
69
|
+
const pathStr = typeof defaultPath === 'string' ? defaultPath : JSON.stringify(defaultPath)
|
|
70
|
+
return ` toPage(_page${i}, { id: '${id}', path: ${pathStr} }),`
|
|
71
|
+
}),
|
|
72
|
+
']',
|
|
73
|
+
]
|
|
74
|
+
return lines.join('\n')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function parsePagesFromSource(filePath) {
|
|
78
|
+
try {
|
|
79
|
+
const content = fs.readFileSync(filePath, 'utf8')
|
|
80
|
+
const items = []
|
|
81
|
+
// Match { id: 'x', path: '/y' } or { id: "x", path: "/y" }
|
|
82
|
+
const idPathPattern = /\{\s*id\s*:\s*['"]([^'"]*)['"]\s*,\s*path\s*:\s*['"]([^'"]*)['"]/g
|
|
83
|
+
// Match { path: '/y', element: ... } (path-first)
|
|
84
|
+
const pathElementPattern = /\{\s*path\s*:\s*['"]([^'"]*)['"]\s*,\s*element\s*:/g
|
|
85
|
+
// Match { path: { en: '/x', sv: '/y' }, ... }
|
|
86
|
+
const pathObjPattern = /\{\s*path\s*:\s*\{\s*([^}]+)\}/g
|
|
87
|
+
let m
|
|
88
|
+
while ((m = idPathPattern.exec(content)) !== null) {
|
|
89
|
+
items.push({ id: m[1], path: m[2] })
|
|
90
|
+
}
|
|
91
|
+
if (items.length === 0) {
|
|
92
|
+
while ((m = pathElementPattern.exec(content)) !== null) {
|
|
93
|
+
const p = m[1]
|
|
94
|
+
const id = p === '/' ? 'home' : p.replace(/^\//, '').replace(/\/$/, '').replace(/\//g, '-') || 'page'
|
|
95
|
+
items.push({ id, path: p })
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (items.length === 0) {
|
|
99
|
+
while ((m = pathObjPattern.exec(content)) !== null) {
|
|
100
|
+
const pathStr = m[1].replace(/\s/g, '').replace(/:/g, ': ')
|
|
101
|
+
items.push({ id: 'page', path: pathStr })
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return items
|
|
105
|
+
} catch {
|
|
106
|
+
return []
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function printBuildOverview({ pagesSourcePath, apiSourcePath, configPath, isPageFiles, pageFiles }) {
|
|
111
|
+
const rel = (p) => path.relative(process.cwd(), p)
|
|
112
|
+
const cwd = process.cwd()
|
|
113
|
+
const srcDir = path.resolve(cwd, 'src')
|
|
114
|
+
console.log('\n \x1b[1mBuild overview\x1b[0m')
|
|
115
|
+
console.log(' ' + '─'.repeat(50))
|
|
116
|
+
console.log(` \x1b[36mPages:\x1b[0m ${rel(pagesSourcePath)}`)
|
|
117
|
+
if (fs.existsSync(configPath)) {
|
|
118
|
+
console.log(` \x1b[36mConfig:\x1b[0m ${rel(configPath)}`)
|
|
119
|
+
}
|
|
120
|
+
console.log(' ' + '─'.repeat(50))
|
|
121
|
+
|
|
122
|
+
const pages = isPageFiles && pageFiles?.length
|
|
123
|
+
? pageFiles.map((f) => filePathToRoute(f, srcDir))
|
|
124
|
+
: parsePagesFromSource(pagesSourcePath)
|
|
125
|
+
if (pages.length > 0) {
|
|
126
|
+
console.log(' \x1b[36mRoutes:\x1b[0m')
|
|
127
|
+
const maxId = Math.max(6, ...pages.map((p) => String(p.id).length))
|
|
128
|
+
const maxPath = Math.max(6, ...pages.map((p) => String(p.path).length))
|
|
129
|
+
pages.forEach((p) => {
|
|
130
|
+
const id = String(p.id).padEnd(maxId)
|
|
131
|
+
const pathStr = String(p.path).padEnd(maxPath)
|
|
132
|
+
console.log(` ${id} ${pathStr}`)
|
|
133
|
+
})
|
|
134
|
+
} else {
|
|
135
|
+
console.log(' \x1b[33mRoutes:\x1b[0m (could not parse or empty)')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (fs.existsSync(apiSourcePath)) {
|
|
139
|
+
const apiRoutes = parsePagesFromSource(apiSourcePath)
|
|
140
|
+
if (apiRoutes.length > 0) {
|
|
141
|
+
console.log(' \x1b[36mAPI routes:\x1b[0m')
|
|
142
|
+
apiRoutes.forEach((r) => {
|
|
143
|
+
console.log(` ${r.id} ${r.path}`)
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
console.log(' ' + '─'.repeat(50) + '\n')
|
|
148
|
+
}
|
|
149
|
+
|
|
19
150
|
export const build = async (cliArgs) => {
|
|
20
151
|
console.log('[@ossy/app][build] Starting...')
|
|
21
152
|
|
|
22
153
|
const options = arg({
|
|
23
|
-
'--
|
|
24
|
-
'--
|
|
154
|
+
'--pages': String,
|
|
155
|
+
'--p': '--pages',
|
|
25
156
|
|
|
26
157
|
'--destination': String,
|
|
27
158
|
'--d': '--destination',
|
|
@@ -31,22 +162,40 @@ export const build = async (cliArgs) => {
|
|
|
31
162
|
}, { argv: cliArgs })
|
|
32
163
|
|
|
33
164
|
|
|
34
|
-
const
|
|
165
|
+
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
166
|
+
const cwd = process.cwd()
|
|
167
|
+
const pagesOpt = options['--pages'] || 'src'
|
|
168
|
+
const srcDir = path.resolve(pagesOpt)
|
|
169
|
+
const pageFiles = discoverPageFiles(srcDir)
|
|
170
|
+
const pagesJsxPath = path.resolve('src/pages.jsx')
|
|
171
|
+
const hasPagesJsx = fs.existsSync(pagesJsxPath)
|
|
172
|
+
|
|
173
|
+
let effectivePagesSource
|
|
174
|
+
let isPageFiles = false
|
|
175
|
+
if (pageFiles.length > 0) {
|
|
176
|
+
const generatedPath = path.join(cwd, '.ossy-pages.generated.jsx')
|
|
177
|
+
fs.writeFileSync(generatedPath, generatePagesModule(pageFiles, cwd, pagesOpt))
|
|
178
|
+
effectivePagesSource = generatedPath
|
|
179
|
+
isPageFiles = true
|
|
180
|
+
} else if (hasPagesJsx) {
|
|
181
|
+
effectivePagesSource = pagesJsxPath
|
|
182
|
+
} else {
|
|
183
|
+
throw new Error(`[@ossy/app][build] No pages found. Create *.page.jsx files in src/, or src/pages.jsx`);
|
|
184
|
+
}
|
|
185
|
+
|
|
35
186
|
let apiSourcePath = path.resolve(options['--api-source'] || 'src/api.js');
|
|
36
187
|
let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/middleware.js');
|
|
37
188
|
const configPath = path.resolve(options['--config'] || 'src/config.js');
|
|
38
189
|
const buildPath = path.resolve(options['--destination'] || 'build');
|
|
39
190
|
const publicDir = path.resolve('public')
|
|
40
191
|
|
|
41
|
-
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
42
192
|
const inputClient = path.resolve(scriptDir, 'client.js')
|
|
43
193
|
const inputServer = path.resolve(scriptDir, 'server.js')
|
|
44
194
|
|
|
45
195
|
const inputFiles = [inputClient, inputServer]
|
|
46
196
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
197
|
+
const appEntryPath = path.resolve(scriptDir, 'default-app.jsx')
|
|
198
|
+
printBuildOverview({ pagesSourcePath: effectivePagesSource, apiSourcePath, configPath, isPageFiles, pageFiles: isPageFiles ? pageFiles : [] });
|
|
50
199
|
|
|
51
200
|
if (!fs.existsSync(apiSourcePath)) {
|
|
52
201
|
apiSourcePath = path.resolve(scriptDir, 'api.js')
|
|
@@ -56,9 +205,9 @@ export const build = async (cliArgs) => {
|
|
|
56
205
|
middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
|
|
57
206
|
}
|
|
58
207
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
208
|
+
const configSourcePath = fs.existsSync(configPath)
|
|
209
|
+
? configPath
|
|
210
|
+
: path.resolve(scriptDir, 'default-config.js')
|
|
62
211
|
|
|
63
212
|
const inputOptions = {
|
|
64
213
|
input: inputFiles,
|
|
@@ -68,7 +217,12 @@ export const build = async (cliArgs) => {
|
|
|
68
217
|
replace({
|
|
69
218
|
preventAssignment: true,
|
|
70
219
|
delimiters: ['%%', '%%'],
|
|
71
|
-
'@ossy/app/source-file':
|
|
220
|
+
'@ossy/app/source-file': appEntryPath,
|
|
221
|
+
}),
|
|
222
|
+
replace({
|
|
223
|
+
preventAssignment: true,
|
|
224
|
+
delimiters: ['%%', '%%'],
|
|
225
|
+
'@ossy/pages/source-file': effectivePagesSource,
|
|
72
226
|
}),
|
|
73
227
|
replace({
|
|
74
228
|
preventAssignment: true,
|
|
@@ -80,6 +234,11 @@ export const build = async (cliArgs) => {
|
|
|
80
234
|
delimiters: ['%%', '%%'],
|
|
81
235
|
'@ossy/middleware/source-file': middlewareSourcePath,
|
|
82
236
|
}),
|
|
237
|
+
replace({
|
|
238
|
+
preventAssignment: true,
|
|
239
|
+
delimiters: ['%%', '%%'],
|
|
240
|
+
'@ossy/config/source-file': configSourcePath,
|
|
241
|
+
}),
|
|
83
242
|
replace({
|
|
84
243
|
preventAssignment: true,
|
|
85
244
|
'process.env.NODE_ENV': JSON.stringify('production')
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { App } from '@ossy/connected-components'
|
|
3
|
+
import pages from '%%@ossy/pages/source-file%%'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* App entry. Uses App from @ossy/connected-components with pages from src/pages.jsx.
|
|
7
|
+
*/
|
|
8
|
+
export default function DefaultApp(config) {
|
|
9
|
+
return React.createElement(App, { ...config, pages })
|
|
10
|
+
}
|
package/cli/dev.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import url from 'url';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
+
import { printBuildOverview, discoverPageFiles, generatePagesModule } from './build.js';
|
|
4
5
|
import { watch } from 'rollup';
|
|
5
6
|
import babel from '@rollup/plugin-babel';
|
|
6
7
|
import { nodeResolve as resolveDependencies } from '@rollup/plugin-node-resolve'
|
|
@@ -21,8 +22,8 @@ export const dev = async (cliArgs) => {
|
|
|
21
22
|
console.log('[@ossy/app][dev] Starting...')
|
|
22
23
|
|
|
23
24
|
const options = arg({
|
|
24
|
-
'--
|
|
25
|
-
'--
|
|
25
|
+
'--pages': String,
|
|
26
|
+
'--p': '--pages',
|
|
26
27
|
|
|
27
28
|
'--destination': String,
|
|
28
29
|
'--d': '--destination',
|
|
@@ -32,22 +33,39 @@ export const dev = async (cliArgs) => {
|
|
|
32
33
|
}, { argv: cliArgs, permissive: true })
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
const
|
|
36
|
+
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
37
|
+
const cwd = process.cwd()
|
|
38
|
+
const pagesOpt = options['--pages'] || 'src'
|
|
39
|
+
const srcDir = path.resolve(pagesOpt)
|
|
40
|
+
const pageFiles = discoverPageFiles(srcDir)
|
|
41
|
+
const pagesJsxPath = path.resolve('src/pages.jsx')
|
|
42
|
+
const hasPagesJsx = fs.existsSync(pagesJsxPath)
|
|
43
|
+
|
|
44
|
+
let effectivePagesSource
|
|
45
|
+
let isPageFiles = false
|
|
46
|
+
if (pageFiles.length > 0) {
|
|
47
|
+
const generatedPath = path.join(cwd, '.ossy-pages.generated.jsx')
|
|
48
|
+
fs.writeFileSync(generatedPath, generatePagesModule(pageFiles, cwd, pagesOpt))
|
|
49
|
+
effectivePagesSource = generatedPath
|
|
50
|
+
isPageFiles = true
|
|
51
|
+
} else if (hasPagesJsx) {
|
|
52
|
+
effectivePagesSource = pagesJsxPath
|
|
53
|
+
} else {
|
|
54
|
+
throw new Error(`[@ossy/app][dev] No pages found. Create *.page.jsx files in src/, or src/pages.jsx`);
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
let apiSourcePath = path.resolve(options['--api-source'] || 'src/api.js');
|
|
37
58
|
let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/middleware.js');
|
|
38
59
|
const configPath = path.resolve(options['--config'] || 'src/config.js');
|
|
39
60
|
const buildPath = path.resolve(options['--destination'] || 'build');
|
|
40
61
|
const publicDir = path.resolve('public')
|
|
41
62
|
|
|
42
|
-
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
43
63
|
const inputClient = path.resolve(scriptDir, 'client.js')
|
|
44
64
|
const inputServer = path.resolve(scriptDir, 'server.js')
|
|
45
65
|
|
|
46
66
|
const inputFiles = [inputClient, inputServer]
|
|
47
67
|
|
|
48
|
-
|
|
49
|
-
throw new Error(`[@ossy/app][build] Source path does not exist: ${appSourcePath}`);
|
|
50
|
-
}
|
|
68
|
+
printBuildOverview({ pagesSourcePath: effectivePagesSource, apiSourcePath, configPath, isPageFiles, pageFiles: isPageFiles ? pageFiles : [] });
|
|
51
69
|
|
|
52
70
|
if (!fs.existsSync(apiSourcePath)) {
|
|
53
71
|
apiSourcePath = path.resolve(scriptDir, 'api.js')
|
|
@@ -57,9 +75,9 @@ export const dev = async (cliArgs) => {
|
|
|
57
75
|
middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
const configSourcePath = fs.existsSync(configPath)
|
|
79
|
+
? configPath
|
|
80
|
+
: path.resolve(scriptDir, 'default-config.js')
|
|
63
81
|
|
|
64
82
|
const inputOptions = {
|
|
65
83
|
input: inputFiles,
|
|
@@ -69,7 +87,12 @@ export const dev = async (cliArgs) => {
|
|
|
69
87
|
replace({
|
|
70
88
|
preventAssignment: true,
|
|
71
89
|
delimiters: ['%%', '%%'],
|
|
72
|
-
'@ossy/app/source-file':
|
|
90
|
+
'@ossy/app/source-file': path.resolve(scriptDir, 'default-app.jsx'),
|
|
91
|
+
}),
|
|
92
|
+
replace({
|
|
93
|
+
preventAssignment: true,
|
|
94
|
+
delimiters: ['%%', '%%'],
|
|
95
|
+
'@ossy/pages/source-file': effectivePagesSource,
|
|
73
96
|
}),
|
|
74
97
|
replace({
|
|
75
98
|
preventAssignment: true,
|
|
@@ -81,6 +104,11 @@ export const dev = async (cliArgs) => {
|
|
|
81
104
|
delimiters: ['%%', '%%'],
|
|
82
105
|
'@ossy/middleware/source-file': middlewareSourcePath,
|
|
83
106
|
}),
|
|
107
|
+
replace({
|
|
108
|
+
preventAssignment: true,
|
|
109
|
+
delimiters: ['%%', '%%'],
|
|
110
|
+
'@ossy/config/source-file': configSourcePath,
|
|
111
|
+
}),
|
|
84
112
|
replace({
|
|
85
113
|
preventAssignment: true,
|
|
86
114
|
'process.env.NODE_ENV': JSON.stringify('development')
|
|
@@ -173,4 +201,16 @@ export const dev = async (cliArgs) => {
|
|
|
173
201
|
restartServer()
|
|
174
202
|
}
|
|
175
203
|
})
|
|
204
|
+
|
|
205
|
+
if (isPageFiles) {
|
|
206
|
+
fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
|
|
207
|
+
if (filename && /\.page\.(jsx?|tsx?)$/.test(filename)) {
|
|
208
|
+
const files = discoverPageFiles(srcDir)
|
|
209
|
+
if (files.length > 0) {
|
|
210
|
+
const generatedPath = path.join(cwd, '.ossy-pages.generated.jsx')
|
|
211
|
+
fs.writeFileSync(generatedPath, generatePagesModule(files, cwd, pagesOpt))
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
176
216
|
};
|
package/cli/proxy-internal.js
CHANGED
|
@@ -37,7 +37,7 @@ export function ProxyInternal() {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (req.originalUrl.startsWith('/@ossy/users/me/app-settings') && req.method === 'GET') {
|
|
40
|
-
|
|
40
|
+
console.log(`[@ossy/app][proxy] GET /@ossy/users/me/app-settings`)
|
|
41
41
|
const userSettings = JSON.parse(req.signedCookies?.['x-ossy-user-settings'] || '{}')
|
|
42
42
|
res.status(200)
|
|
43
43
|
res.json(userSettings)
|
package/cli/server.js
CHANGED
|
@@ -11,6 +11,9 @@ import cookieParser from 'cookie-parser'
|
|
|
11
11
|
import App from '%%@ossy/app/source-file%%'
|
|
12
12
|
import ApiRoutes from '%%@ossy/api/source-file%%'
|
|
13
13
|
import Middleware from '%%@ossy/middleware/source-file%%'
|
|
14
|
+
import configModule from '%%@ossy/config/source-file%%'
|
|
15
|
+
|
|
16
|
+
const buildTimeConfig = configModule?.default ?? configModule ?? {}
|
|
14
17
|
|
|
15
18
|
const app = express();
|
|
16
19
|
|
|
@@ -108,14 +111,18 @@ app.all('*all', (req, res) => {
|
|
|
108
111
|
if (apiRoute) {
|
|
109
112
|
console.log(`[@ossy/app][server] Handling API route: ${pathname}`)
|
|
110
113
|
apiRoute.handle(req, res)
|
|
114
|
+
return
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
const userAppSettings = req.userAppSettings || {}
|
|
114
118
|
|
|
115
119
|
const appConfig = {
|
|
120
|
+
...buildTimeConfig,
|
|
116
121
|
url: req.url,
|
|
117
|
-
theme: userAppSettings.theme || 'light',
|
|
122
|
+
theme: userAppSettings.theme || buildTimeConfig.theme || 'light',
|
|
118
123
|
isAuthenticated: req.isAuthenticated || false,
|
|
124
|
+
workspaceId: userAppSettings.workspaceId || buildTimeConfig.workspaceId,
|
|
125
|
+
apiUrl: buildTimeConfig.apiUrl,
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
renderToString(App, appConfig)
|
package/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
|
-
"bin": "./cli/index.js",
|
|
8
7
|
"type": "module",
|
|
9
8
|
"scripts": {
|
|
10
9
|
"build": "echo Building not required",
|
|
11
|
-
"test": "
|
|
10
|
+
"test": "node --experimental-vm-modules ../node_modules/jest/bin/jest.js --verbose",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [],
|
|
14
14
|
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
15
15
|
"license": "MIT",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@jest/globals": "^30.2.0",
|
|
18
|
+
"jest": "^30.2.0",
|
|
19
|
+
"typescript": "^5.9.3"
|
|
20
|
+
},
|
|
16
21
|
"peerDependencies": {
|
|
17
22
|
"@babel/cli": "^7.26.4",
|
|
18
23
|
"@babel/core": "^7.26.0",
|
|
@@ -22,7 +27,7 @@
|
|
|
22
27
|
"@babel/register": "^7.25.9",
|
|
23
28
|
"@ossy/connected-components": ">=0.5.0 <1.0.0",
|
|
24
29
|
"@ossy/design-system": ">=0.5.0 <1.0.0",
|
|
25
|
-
"@ossy/
|
|
30
|
+
"@ossy/pages": ">=0.5.0 <1.0.0",
|
|
26
31
|
"@ossy/resource-templates": ">=0.5.0 <1.0.0",
|
|
27
32
|
"@ossy/router": ">=0.5.0 <1.0.0",
|
|
28
33
|
"@ossy/router-react": ">=0.5.0 <1.0.0",
|
|
@@ -56,7 +61,9 @@
|
|
|
56
61
|
},
|
|
57
62
|
"files": [
|
|
58
63
|
"/cli",
|
|
59
|
-
"
|
|
64
|
+
"/src",
|
|
65
|
+
"README.md",
|
|
66
|
+
"tsconfig.json"
|
|
60
67
|
],
|
|
61
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "3019a8da590435ba6f10d7d861addeba011baf5d"
|
|
62
69
|
}
|
package/src/index.js
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"checkJs": false,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["cli/**/*.js", "src/**/*.js"],
|
|
18
|
+
"exclude": ["node_modules", "build", "**/__tests__/**"]
|
|
19
|
+
}
|
package/cli/index.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable global-require, no-unused-vars */
|
|
3
|
-
import { build } from './build.js'
|
|
4
|
-
import { dev } from './dev.js'
|
|
5
|
-
|
|
6
|
-
const [_, __, command, ...restArgs] = process.argv
|
|
7
|
-
|
|
8
|
-
if (!command) {
|
|
9
|
-
console.error({ message: '[@ossy/app] No command provided' })
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const commandHandler = {
|
|
13
|
-
'build': build,
|
|
14
|
-
'dev': dev,
|
|
15
|
-
}[command]
|
|
16
|
-
|
|
17
|
-
if (!commandHandler) {
|
|
18
|
-
console.error({ message: `[@ossy/app] Unknown command: ${command}` })
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
commandHandler(restArgs)
|