@ossy/platform 1.14.1 → 1.15.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/package.json +7 -8
- package/src/server.js +186 -113
- package/src/worker-entry.js +10 -3
- package/src/worker-runtime.js +19 -7
- package/src/http.js +0 -85
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/platform",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "Ossy application server runtime",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,22 +16,21 @@
|
|
|
16
16
|
"./worker-runtime": "./src/worker-runtime.js"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"start": "PORT=3003 node ./src/server.js"
|
|
19
|
+
"start": "PORT=3003 node -e \"import('./src/server.js').then(m => m.startServer())\""
|
|
20
20
|
},
|
|
21
21
|
"keywords": [],
|
|
22
22
|
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@ossy/router": "^1.
|
|
26
|
-
"@ossy/sdk": "^1.
|
|
25
|
+
"@ossy/router": "^1.16.0",
|
|
26
|
+
"@ossy/sdk": "^1.16.0",
|
|
27
27
|
"cookie-parser": "^1.4.7",
|
|
28
|
+
"dotenv": ">=16.0.0 <17.0.0",
|
|
28
29
|
"express": ">=5.0.0 <6.0.0",
|
|
29
|
-
"morgan": ">=1.10.1 <2.0.0"
|
|
30
|
-
"react": ">=19.0.0 <20.0.0",
|
|
31
|
-
"react-dom": ">=19.0.0 <20.0.0"
|
|
30
|
+
"morgan": ">=1.10.1 <2.0.0"
|
|
32
31
|
},
|
|
33
32
|
"files": [
|
|
34
33
|
"src"
|
|
35
34
|
],
|
|
36
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "db2ab43d6b3266c59eb53c8046811e0acb3f87f1"
|
|
37
36
|
}
|
package/src/server.js
CHANGED
|
@@ -1,59 +1,103 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import {
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
4
|
import express from 'express'
|
|
5
5
|
import morgan from 'morgan'
|
|
6
6
|
import { Router as OssyRouter } from '@ossy/router'
|
|
7
7
|
import cookieParser from 'cookie-parser'
|
|
8
8
|
import { ProxyInternal } from './proxy-internal.js'
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
const DEFAULT_PORT = 3000
|
|
11
|
+
const MANIFEST_FILE = 'manifest.json'
|
|
12
|
+
|
|
13
|
+
function parsePortFromArgv (argv) {
|
|
14
|
+
const flagIdx = argv.findIndex((a) => a === '--port' || a === '-p')
|
|
15
|
+
if (flagIdx !== -1 && argv[flagIdx + 1]) return argv[flagIdx + 1]
|
|
16
|
+
const eq = argv.find((a) => a.startsWith('--port='))
|
|
17
|
+
if (eq) return eq.split('=')[1]
|
|
18
|
+
return undefined
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
function resolvePort (explicit) {
|
|
22
|
+
const raw = explicit ?? parsePortFromArgv(process.argv) ?? process.env.PORT
|
|
23
|
+
const parsed = Number.parseInt(String(raw ?? ''), 10)
|
|
24
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PORT
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function loadManifest (buildDir) {
|
|
28
|
+
const manifestPath = path.join(buildDir, MANIFEST_FILE)
|
|
29
|
+
if (!fs.existsSync(manifestPath)) {
|
|
30
|
+
throw new Error(`[@ossy/platform][server] Build manifest not found: ${manifestPath}. Run \`app build\` first.`)
|
|
31
|
+
}
|
|
32
|
+
const raw = fs.readFileSync(manifestPath, 'utf8')
|
|
33
|
+
const manifest = JSON.parse(raw)
|
|
34
|
+
// The manifest is a flat `entries` array discriminated by `type`. Bucket
|
|
35
|
+
// them once at boot so the per-request hot path stays a simple lookup.
|
|
36
|
+
const entries = Array.isArray(manifest.entries) ? manifest.entries : []
|
|
37
|
+
const pages = entries.filter((e) => e.type === 'page')
|
|
38
|
+
const apis = entries.filter((e) => e.type === 'api')
|
|
39
|
+
const tasks = entries.filter((e) => e.type === 'task')
|
|
40
|
+
return {
|
|
41
|
+
entries,
|
|
42
|
+
pages,
|
|
43
|
+
apis,
|
|
44
|
+
tasks,
|
|
45
|
+
config: manifest.config || {},
|
|
28
46
|
}
|
|
29
47
|
}
|
|
30
48
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// SSR is optional at startup; page requests return 503 when unavailable.
|
|
49
|
+
// `entry` URLs in the manifest are public-relative (`/static/<file>`). Translate
|
|
50
|
+
// them to local file URLs for `import()` while passing absolute URLs (e.g. CDN
|
|
51
|
+
// hosted bundles) through unchanged.
|
|
52
|
+
export function resolveEntryUrl (entryUrl, buildDir) {
|
|
53
|
+
if (!entryUrl) throw new Error('[@ossy/platform][server] Entry URL missing in manifest entry')
|
|
54
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(entryUrl)) return entryUrl
|
|
55
|
+
const absolute = path.join(buildDir, 'public', entryUrl.startsWith('/') ? entryUrl.slice(1) : entryUrl)
|
|
56
|
+
return pathToFileURL(absolute).href
|
|
40
57
|
}
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const port =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
function cloneSerializable (value) {
|
|
60
|
+
return value == null ? value : JSON.parse(JSON.stringify(value))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function startServer (options = {}) {
|
|
64
|
+
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd()
|
|
65
|
+
const buildDir = path.resolve(cwd, options.buildDir || 'build')
|
|
66
|
+
const publicDir = path.resolve(buildDir, 'public')
|
|
67
|
+
const port = resolvePort(options.port)
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(buildDir)) {
|
|
70
|
+
throw new Error(`[@ossy/platform][server] Build directory not found: ${buildDir}. Run \`app build\` first.`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const manifest = loadManifest(buildDir)
|
|
74
|
+
const config = manifest.config
|
|
75
|
+
|
|
76
|
+
const supportedLanguages = Array.isArray(config.supportedLanguages) ? config.supportedLanguages : []
|
|
77
|
+
const defaultLanguage = config.defaultLanguage
|
|
78
|
+
|
|
79
|
+
const pageRouter = OssyRouter.of({
|
|
80
|
+
pages: manifest.pages,
|
|
81
|
+
supportedLanguages,
|
|
82
|
+
defaultLanguage,
|
|
83
|
+
})
|
|
84
|
+
const apiRouter = OssyRouter.of({ pages: manifest.apis })
|
|
85
|
+
|
|
86
|
+
const moduleCache = new Map()
|
|
87
|
+
async function loadEntry (entryUrl) {
|
|
88
|
+
if (moduleCache.has(entryUrl)) return moduleCache.get(entryUrl)
|
|
89
|
+
const promise = import(resolveEntryUrl(entryUrl, buildDir))
|
|
90
|
+
moduleCache.set(entryUrl, promise)
|
|
91
|
+
return promise
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const userMiddleware = await loadMiddleware(buildDir)
|
|
95
|
+
|
|
96
|
+
const app = express()
|
|
97
|
+
app.use(morgan('tiny'))
|
|
98
|
+
app.use(express.json({ strict: false }))
|
|
99
|
+
app.use(cookieParser(process.env.OSSY_COOKIE_SECRET || 'default_secret'))
|
|
100
|
+
app.use((req, _res, next) => {
|
|
57
101
|
const userSettings = JSON.parse(req.signedCookies?.['x-ossy-user-settings'] || '{}')
|
|
58
102
|
req.userAppSettings = userSettings
|
|
59
103
|
if (userSettings.workspaceId && !req.get('workspaceId')) {
|
|
@@ -62,82 +106,111 @@ const middleware = [
|
|
|
62
106
|
const cookieHeader = req.headers.cookie
|
|
63
107
|
req.isAuthenticated = cookieHeader ? cookieHeader.includes('auth=') : false
|
|
64
108
|
next()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
express.static(
|
|
68
|
-
ProxyInternal()
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
app.use(middleware)
|
|
72
|
-
|
|
73
|
-
let supportedLanguages = buildTimeConfig?.supportedLanguages
|
|
74
|
-
let defaultLanguage = buildTimeConfig?.defaultLanguage
|
|
75
|
-
if ((!supportedLanguages || supportedLanguages.length <= 1) && sitePageList.length > 0) {
|
|
76
|
-
const firstPage = sitePageList[0]
|
|
77
|
-
if (firstPage && typeof firstPage.path === 'object' && firstPage.path != null) {
|
|
78
|
-
supportedLanguages = Object.keys(firstPage.path)
|
|
79
|
-
defaultLanguage = defaultLanguage || supportedLanguages[0]
|
|
80
|
-
}
|
|
81
|
-
}
|
|
109
|
+
})
|
|
110
|
+
for (const mw of userMiddleware) app.use(mw)
|
|
111
|
+
if (fs.existsSync(publicDir)) app.use(express.static(publicDir))
|
|
112
|
+
app.use(ProxyInternal())
|
|
82
113
|
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
114
|
+
app.all('*all', async (req, res) => {
|
|
115
|
+
const requestUrl = req.originalUrl || '/'
|
|
116
|
+
try {
|
|
117
|
+
const apiRoute = apiRouter.getPageByUrl(requestUrl)
|
|
118
|
+
if (apiRoute) {
|
|
119
|
+
const apiEntry = manifest.apis.find((a) => a.id === apiRoute.id)
|
|
120
|
+
if (apiEntry) {
|
|
121
|
+
const mod = await loadEntry(apiEntry.entry)
|
|
122
|
+
if (typeof mod.handle === 'function') {
|
|
123
|
+
await mod.handle(req, res)
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
130
|
+
res.status(404).send('Not found')
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const pageRoute = pageRouter.getPageByUrl(requestUrl)
|
|
135
|
+
if (pageRoute) {
|
|
136
|
+
const pageEntry = manifest.pages.find((p) => p.id === pageRoute.id)
|
|
137
|
+
if (!pageEntry) {
|
|
138
|
+
res.status(404).send('Not found')
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
const mod = await loadEntry(pageEntry.entry)
|
|
142
|
+
if (typeof mod.render !== 'function') {
|
|
143
|
+
res.status(503).type('text').send('SSR runtime unavailable')
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
const props = {
|
|
147
|
+
...config,
|
|
148
|
+
theme: cloneSerializable(config?.theme),
|
|
149
|
+
themes: cloneSerializable(config?.themes),
|
|
150
|
+
resourceTemplates: cloneSerializable(config?.resourceTemplates),
|
|
151
|
+
url: requestUrl,
|
|
152
|
+
isAuthenticated: !!req.isAuthenticated,
|
|
153
|
+
pages: manifest.pages.map((page) => ({ id: page.id, path: page.path })),
|
|
154
|
+
pageId: pageRoute.id,
|
|
155
|
+
sidebarPrimaryCollapsed: false,
|
|
156
|
+
}
|
|
157
|
+
const html = await mod.render(props)
|
|
158
|
+
res.status(200).type('html').send(html)
|
|
107
159
|
return
|
|
108
160
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
apiUrl: buildTimeConfig?.apiUrl,
|
|
116
|
-
pages: sitePageList.map((page) => ({ id: page?.id, path: page?.path })),
|
|
117
|
-
pageId: pageRoute.id,
|
|
118
|
-
sidebarPrimaryCollapsed: false,
|
|
161
|
+
|
|
162
|
+
res.status(404).send('Not found')
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error('[@ossy/platform][server] Request handling failed:', err)
|
|
165
|
+
if (!res.headersSent) {
|
|
166
|
+
res.status(500).type('text').send('Internal Server Error')
|
|
119
167
|
}
|
|
120
|
-
const html = await ssrRender(pageRoute.id, {
|
|
121
|
-
...appConfig,
|
|
122
|
-
theme: appConfig?.theme == null ? appConfig?.theme : JSON.parse(JSON.stringify(appConfig.theme)),
|
|
123
|
-
themes: appConfig?.themes == null ? appConfig?.themes : JSON.parse(JSON.stringify(appConfig.themes)),
|
|
124
|
-
resourceTemplates:
|
|
125
|
-
appConfig?.resourceTemplates == null
|
|
126
|
-
? appConfig?.resourceTemplates
|
|
127
|
-
: JSON.parse(JSON.stringify(appConfig.resourceTemplates)),
|
|
128
|
-
}, {})
|
|
129
|
-
res.status(200).type('html').send(html)
|
|
130
|
-
return
|
|
131
168
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const server = await new Promise((resolve, reject) => {
|
|
172
|
+
const httpServer = app.listen(port)
|
|
173
|
+
httpServer.once('listening', () => resolve(httpServer))
|
|
174
|
+
httpServer.once('error', reject)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
console.log(`[@ossy/platform][server] Running on http://localhost:${port}`)
|
|
178
|
+
console.log('[@ossy/platform][server] Press Ctrl+C to stop.')
|
|
179
|
+
|
|
180
|
+
const closeServer = () => new Promise((resolve) => server.close(() => resolve()))
|
|
181
|
+
|
|
182
|
+
let shuttingDown = false
|
|
183
|
+
const handleShutdown = async (signal) => {
|
|
184
|
+
if (shuttingDown) return
|
|
185
|
+
shuttingDown = true
|
|
186
|
+
console.log(`\n[@ossy/platform][server] Received ${signal}, shutting down...`)
|
|
187
|
+
try {
|
|
188
|
+
await closeServer()
|
|
189
|
+
} finally {
|
|
190
|
+
process.exit(0)
|
|
137
191
|
}
|
|
138
192
|
}
|
|
139
|
-
})
|
|
140
193
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
194
|
+
process.on('SIGINT', () => handleShutdown('SIGINT'))
|
|
195
|
+
process.on('SIGTERM', () => handleShutdown('SIGTERM'))
|
|
196
|
+
|
|
197
|
+
const lifetime = new Promise(() => {})
|
|
198
|
+
return { app, server, port, close: closeServer, lifetime }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function loadMiddleware (buildDir) {
|
|
202
|
+
const candidates = [
|
|
203
|
+
path.resolve(buildDir, 'public', 'static', 'middleware.js'),
|
|
204
|
+
path.resolve(buildDir, 'middleware.js'),
|
|
205
|
+
]
|
|
206
|
+
for (const candidate of candidates) {
|
|
207
|
+
if (!fs.existsSync(candidate)) continue
|
|
208
|
+
const mod = await import(pathToFileURL(candidate).href)
|
|
209
|
+
const value = mod.default
|
|
210
|
+
if (Array.isArray(value)) return value
|
|
211
|
+
if (typeof value === 'function') return [value]
|
|
212
|
+
}
|
|
213
|
+
return []
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export default startServer
|
package/src/worker-entry.js
CHANGED
|
@@ -4,7 +4,14 @@ import path from 'node:path'
|
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import { runWorkerScheduler } from './worker-runtime.js'
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const buildDir = process.env.OSSY_BUILD_DIR
|
|
9
|
+
? path.resolve(process.env.OSSY_BUILD_DIR)
|
|
10
|
+
: path.resolve(__dirname, '..', '..', '..', 'build')
|
|
11
|
+
const manifestPath = path.join(buildDir, 'manifest.json')
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
|
14
|
+
const entries = Array.isArray(manifest.entries) ? manifest.entries : []
|
|
15
|
+
const tasks = entries.filter((e) => e.type === 'task')
|
|
16
|
+
|
|
17
|
+
runWorkerScheduler(tasks, buildDir)
|
package/src/worker-runtime.js
CHANGED
|
@@ -2,11 +2,18 @@ import path from 'node:path'
|
|
|
2
2
|
import { pathToFileURL } from 'node:url'
|
|
3
3
|
import { SDK } from '@ossy/sdk'
|
|
4
4
|
|
|
5
|
+
function resolveTaskEntryUrl (entryUrl, buildDir) {
|
|
6
|
+
if (!entryUrl) throw new Error('[@ossy/platform][worker] Task entry URL missing')
|
|
7
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(entryUrl)) return entryUrl
|
|
8
|
+
const trimmed = entryUrl.startsWith('/') ? entryUrl.slice(1) : entryUrl
|
|
9
|
+
return pathToFileURL(path.join(buildDir, 'public', trimmed)).href
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
/**
|
|
6
|
-
* @param {Array<{
|
|
7
|
-
* @param {string}
|
|
13
|
+
* @param {Array<{ id: string, entry: string }>} tasks
|
|
14
|
+
* @param {string} buildDir - absolute path to the build directory
|
|
8
15
|
*/
|
|
9
|
-
export function runWorkerScheduler (tasks,
|
|
16
|
+
export function runWorkerScheduler (tasks, buildDir) {
|
|
10
17
|
const sdk = SDK.of({
|
|
11
18
|
workspaceId: process.env.OSSY_WORKSPACE_ID,
|
|
12
19
|
apiUrl: process.env.OSSY_API_URL,
|
|
@@ -92,20 +99,25 @@ export function runWorkerScheduler (tasks, ossyDir) {
|
|
|
92
99
|
console.log(`Processing ${jobList.length} jobs`)
|
|
93
100
|
for (const job of jobList) {
|
|
94
101
|
console.log(`Processing job ${job.id}`)
|
|
95
|
-
const task = tasks.find((t) => t.
|
|
102
|
+
const task = tasks.find((t) => t.id === job.type)
|
|
96
103
|
if (!task) {
|
|
97
104
|
console.log('No handler found for job', job.id)
|
|
98
105
|
continue
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
console.log(`Handler found for ${task.
|
|
108
|
+
console.log(`Handler found for ${task.id}`)
|
|
102
109
|
try {
|
|
103
|
-
const mod = await import(
|
|
110
|
+
const mod = await import(resolveTaskEntryUrl(task.entry, buildDir))
|
|
104
111
|
const jobSdk = SDK.of({
|
|
105
112
|
workspaceId: job.belongsTo,
|
|
106
113
|
authorization: process.env.OSSY_API_TOKEN,
|
|
107
114
|
})
|
|
108
|
-
|
|
115
|
+
const runner = typeof mod.run === 'function' ? mod.run : mod.default
|
|
116
|
+
if (typeof runner !== 'function') {
|
|
117
|
+
console.log(`Task ${task.id} has no callable handler`)
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
await runner({ sdk: jobSdk, job }).catch(() => {})
|
|
109
121
|
} catch (error) {
|
|
110
122
|
console.error(error)
|
|
111
123
|
console.log('Failed to processing job')
|
package/src/http.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import url from 'url'
|
|
3
|
-
import express from 'express'
|
|
4
|
-
import morgan from 'morgan'
|
|
5
|
-
import { Router as OssyRouter } from '@ossy/router'
|
|
6
|
-
import cookieParser from 'cookie-parser'
|
|
7
|
-
|
|
8
|
-
const app = express();
|
|
9
|
-
|
|
10
|
-
const currentDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
11
|
-
|
|
12
|
-
function parsePortFromArgv(argv) {
|
|
13
|
-
// Supports: --port 4000, --port=4000, -p 4000
|
|
14
|
-
const idx = argv.findIndex(a => a === '--port' || a === '-p')
|
|
15
|
-
if (idx !== -1 && argv[idx + 1]) return argv[idx + 1]
|
|
16
|
-
|
|
17
|
-
const eq = argv.find(a => a.startsWith('--port='))
|
|
18
|
-
if (eq) return eq.split('=')[1]
|
|
19
|
-
|
|
20
|
-
return undefined
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function normalizePort(value, fallback) {
|
|
24
|
-
if (value === undefined || value === null || value === '') return fallback
|
|
25
|
-
const n = Number.parseInt(String(value), 10)
|
|
26
|
-
if (!Number.isFinite(n) || n <= 0) return fallback
|
|
27
|
-
return n
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const DEFAULT_PORT = 3000
|
|
31
|
-
const port = normalizePort(parsePortFromArgv(process.argv) ?? process.env.PORT, DEFAULT_PORT)
|
|
32
|
-
|
|
33
|
-
const middleware = [
|
|
34
|
-
morgan('tiny'),
|
|
35
|
-
express.json({ strict: false }),
|
|
36
|
-
cookieParser(process.env.OSSY_COOKIE_SECRET || 'default_secret'),
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
app.use(middleware)
|
|
40
|
-
|
|
41
|
-
// const PageRegistry = OssyRouter.of({ pages: [] })
|
|
42
|
-
// const ApiRegistry = OssyRouter.of({ api: [] })
|
|
43
|
-
// const TaskRegistry = OssyRouter.of({ tasks: [] })
|
|
44
|
-
// const StaticRegistry = OssyRouter.of({ static: [] })
|
|
45
|
-
|
|
46
|
-
const Registry = OssyRouter.of({ pages: [
|
|
47
|
-
{
|
|
48
|
-
id: 'home',
|
|
49
|
-
type: '@ossy/platform/page',
|
|
50
|
-
path: '/',
|
|
51
|
-
content: '<h1>Hello World from page route</h1>'
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: 'home',
|
|
55
|
-
type: '@ossy/platform/api',
|
|
56
|
-
path: '/api',
|
|
57
|
-
handle: (req, res) => {
|
|
58
|
-
res.send({ message: 'Hello World from api route' })
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
]})
|
|
62
|
-
|
|
63
|
-
app.all('*all', (req, res) => {
|
|
64
|
-
const pathname = req.originalUrl
|
|
65
|
-
|
|
66
|
-
// Will be replaced with fetching the resource from ossy storage
|
|
67
|
-
const resource = Registry.getPageByUrl(pathname)
|
|
68
|
-
|
|
69
|
-
if (resource.type === '@ossy/platform/page') {
|
|
70
|
-
res.send(resource.content)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
else if (resource.type === '@ossy/platform/api') {
|
|
74
|
-
resource.handle(req, res)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
else {
|
|
78
|
-
res.status(404).send('Not found')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
app.listen(port, () => {
|
|
84
|
-
console.log(`[@ossy/app][server] Running on http://localhost:${port}`);
|
|
85
|
-
});
|