@ossy/platform 1.14.2 → 1.15.1
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 +6 -8
- package/src/server.js +84 -114
- package/src/worker-entry.js +10 -3
- package/src/worker-runtime.js +19 -7
- package/src/getPlatformFiles.task.js +0 -50
- 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.1",
|
|
4
4
|
"description": "Ossy application server runtime",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,17 +22,15 @@
|
|
|
22
22
|
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@ossy/
|
|
26
|
-
"@ossy/
|
|
27
|
-
"@ossy/sdk": "^1.15.2",
|
|
25
|
+
"@ossy/router": "^1.16.1",
|
|
26
|
+
"@ossy/sdk": "^1.16.1",
|
|
28
27
|
"cookie-parser": "^1.4.7",
|
|
28
|
+
"dotenv": ">=16.0.0 <17.0.0",
|
|
29
29
|
"express": ">=5.0.0 <6.0.0",
|
|
30
|
-
"morgan": ">=1.10.1 <2.0.0"
|
|
31
|
-
"react": ">=19.0.0 <20.0.0",
|
|
32
|
-
"react-dom": ">=19.0.0 <20.0.0"
|
|
30
|
+
"morgan": ">=1.10.1 <2.0.0"
|
|
33
31
|
},
|
|
34
32
|
"files": [
|
|
35
33
|
"src"
|
|
36
34
|
],
|
|
37
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "031e2329e267a3e0cebc2ec0d484fa11b53082d9"
|
|
38
36
|
}
|
package/src/server.js
CHANGED
|
@@ -6,12 +6,9 @@ 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
|
-
import getPlatformFiles, {
|
|
10
|
-
PAGE_FILE_PATTERN,
|
|
11
|
-
API_FILE_PATTERN,
|
|
12
|
-
} from './getPlatformFiles.task.js'
|
|
13
9
|
|
|
14
10
|
const DEFAULT_PORT = 3000
|
|
11
|
+
const MANIFEST_FILE = 'manifest.json'
|
|
15
12
|
|
|
16
13
|
function parsePortFromArgv (argv) {
|
|
17
14
|
const flagIdx = argv.findIndex((a) => a === '--port' || a === '-p')
|
|
@@ -27,92 +24,42 @@ function resolvePort (explicit) {
|
|
|
27
24
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PORT
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const id = metadata.id
|
|
50
|
-
const routePath = metadata.path
|
|
51
|
-
if (!id || !routePath) continue
|
|
52
|
-
|
|
53
|
-
const entry = { id, path: routePath, title: metadata.title }
|
|
54
|
-
if (isPage) {
|
|
55
|
-
pages.push(entry)
|
|
56
|
-
pageModules.set(id, mod)
|
|
57
|
-
} else {
|
|
58
|
-
apis.push(entry)
|
|
59
|
-
apiModules.set(id, mod)
|
|
60
|
-
}
|
|
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 || {},
|
|
61
46
|
}
|
|
62
|
-
|
|
63
|
-
return { pages, apis, pageModules, apiModules }
|
|
64
47
|
}
|
|
65
48
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const mwPath = path.resolve(buildDir, 'middleware.js')
|
|
75
|
-
if (!fs.existsSync(mwPath)) return []
|
|
76
|
-
const mod = await importModule(mwPath)
|
|
77
|
-
const value = mod.default
|
|
78
|
-
if (Array.isArray(value)) return value
|
|
79
|
-
if (typeof value === 'function') return [value]
|
|
80
|
-
return []
|
|
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
|
|
81
57
|
}
|
|
82
58
|
|
|
83
59
|
function cloneSerializable (value) {
|
|
84
60
|
return value == null ? value : JSON.parse(JSON.stringify(value))
|
|
85
61
|
}
|
|
86
62
|
|
|
87
|
-
async function renderPage ({ Component, metadata, appConfig }) {
|
|
88
|
-
const [{ createElement }, { renderToString }, { App }] = await Promise.all([
|
|
89
|
-
import('react'),
|
|
90
|
-
import('react-dom/server'),
|
|
91
|
-
import('@ossy/connected-components'),
|
|
92
|
-
])
|
|
93
|
-
|
|
94
|
-
const appProps = {
|
|
95
|
-
...appConfig,
|
|
96
|
-
theme: cloneSerializable(appConfig.theme),
|
|
97
|
-
themes: cloneSerializable(appConfig.themes),
|
|
98
|
-
resourceTemplates: cloneSerializable(appConfig.resourceTemplates),
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const element = createElement(
|
|
102
|
-
'html',
|
|
103
|
-
{ lang: appProps.htmlLang || appProps.defaultLanguage || 'en' },
|
|
104
|
-
createElement(
|
|
105
|
-
'head',
|
|
106
|
-
null,
|
|
107
|
-
createElement('meta', { charSet: 'utf-8' }),
|
|
108
|
-
createElement('title', null, metadata?.title || appProps.documentTitle || ''),
|
|
109
|
-
),
|
|
110
|
-
createElement(App, appProps, createElement(Component, appProps)),
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
return '<!doctype html>' + renderToString(element)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
63
|
export async function startServer (options = {}) {
|
|
117
64
|
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd()
|
|
118
65
|
const buildDir = path.resolve(cwd, options.buildDir || 'build')
|
|
@@ -123,16 +70,30 @@ export async function startServer (options = {}) {
|
|
|
123
70
|
throw new Error(`[@ossy/platform][server] Build directory not found: ${buildDir}. Run \`app build\` first.`)
|
|
124
71
|
}
|
|
125
72
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
loadMiddleware(buildDir),
|
|
129
|
-
loadRoutes(buildDir),
|
|
130
|
-
])
|
|
73
|
+
const manifest = loadManifest(buildDir)
|
|
74
|
+
const config = manifest.config
|
|
131
75
|
|
|
132
|
-
const
|
|
76
|
+
const supportedLanguages = Array.isArray(config.supportedLanguages) ? config.supportedLanguages : []
|
|
77
|
+
const defaultLanguage = config.defaultLanguage
|
|
133
78
|
|
|
134
|
-
const
|
|
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)
|
|
135
95
|
|
|
96
|
+
const app = express()
|
|
136
97
|
app.use(morgan('tiny'))
|
|
137
98
|
app.use(express.json({ strict: false }))
|
|
138
99
|
app.use(cookieParser(process.env.OSSY_COOKIE_SECRET || 'default_secret'))
|
|
@@ -150,23 +111,18 @@ export async function startServer (options = {}) {
|
|
|
150
111
|
if (fs.existsSync(publicDir)) app.use(express.static(publicDir))
|
|
151
112
|
app.use(ProxyInternal())
|
|
152
113
|
|
|
153
|
-
const apiRouter = OssyRouter.of({ pages: apis })
|
|
154
|
-
const pageRouter = OssyRouter.of({
|
|
155
|
-
pages,
|
|
156
|
-
supportedLanguages: Array.isArray(config.supportedLanguages) ? config.supportedLanguages : [],
|
|
157
|
-
defaultLanguage: config.defaultLanguage,
|
|
158
|
-
})
|
|
159
|
-
|
|
160
114
|
app.all('*all', async (req, res) => {
|
|
161
115
|
const requestUrl = req.originalUrl || '/'
|
|
162
116
|
try {
|
|
163
117
|
const apiRoute = apiRouter.getPageByUrl(requestUrl)
|
|
164
118
|
if (apiRoute) {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
+
}
|
|
170
126
|
}
|
|
171
127
|
}
|
|
172
128
|
|
|
@@ -177,28 +133,28 @@ export async function startServer (options = {}) {
|
|
|
177
133
|
|
|
178
134
|
const pageRoute = pageRouter.getPageByUrl(requestUrl)
|
|
179
135
|
if (pageRoute) {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
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') {
|
|
183
143
|
res.status(503).type('text').send('SSR runtime unavailable')
|
|
184
144
|
return
|
|
185
145
|
}
|
|
186
|
-
const
|
|
146
|
+
const props = {
|
|
187
147
|
...config,
|
|
148
|
+
theme: cloneSerializable(config?.theme),
|
|
149
|
+
themes: cloneSerializable(config?.themes),
|
|
150
|
+
resourceTemplates: cloneSerializable(config?.resourceTemplates),
|
|
188
151
|
url: requestUrl,
|
|
189
|
-
theme: config?.theme || 'light',
|
|
190
152
|
isAuthenticated: !!req.isAuthenticated,
|
|
191
|
-
|
|
192
|
-
apiUrl: config?.apiUrl,
|
|
193
|
-
pages: pages.map((page) => ({ id: page?.id, path: page?.path })),
|
|
153
|
+
pages: manifest.pages.map((page) => ({ id: page.id, path: page.path })),
|
|
194
154
|
pageId: pageRoute.id,
|
|
195
155
|
sidebarPrimaryCollapsed: false,
|
|
196
156
|
}
|
|
197
|
-
const html = await
|
|
198
|
-
Component,
|
|
199
|
-
metadata: mod.metadata,
|
|
200
|
-
appConfig,
|
|
201
|
-
})
|
|
157
|
+
const html = await mod.render(props)
|
|
202
158
|
res.status(200).type('html').send(html)
|
|
203
159
|
return
|
|
204
160
|
}
|
|
@@ -239,8 +195,22 @@ export async function startServer (options = {}) {
|
|
|
239
195
|
process.on('SIGTERM', () => handleShutdown('SIGTERM'))
|
|
240
196
|
|
|
241
197
|
const lifetime = new Promise(() => {})
|
|
242
|
-
|
|
243
198
|
return { app, server, port, close: closeServer, lifetime }
|
|
244
199
|
}
|
|
245
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
|
+
|
|
246
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')
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
export const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
|
|
5
|
-
export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
|
|
6
|
-
export const TASK_FILE_PATTERN = /\.task\.(mjs|cjs|js)$/
|
|
7
|
-
export const RESOURCE_TEMPLATE_FILE_PATTERN = /\.resource\.(mjs|cjs|js)$/
|
|
8
|
-
|
|
9
|
-
function discoverFilesByPattern (srcDir, filePattern) {
|
|
10
|
-
const dir = path.resolve(srcDir)
|
|
11
|
-
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
12
|
-
return []
|
|
13
|
-
}
|
|
14
|
-
const files = []
|
|
15
|
-
const walk = (d) => {
|
|
16
|
-
const entries = fs.readdirSync(d, { withFileTypes: true })
|
|
17
|
-
for (const e of entries) {
|
|
18
|
-
const full = path.join(d, e.name)
|
|
19
|
-
if (e.isDirectory()) walk(full)
|
|
20
|
-
else if (filePattern.test(e.name)) files.push(full)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
walk(dir)
|
|
24
|
-
return files
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export default async function getPlatformFiles(_folder) {
|
|
29
|
-
const cwd = process.cwd()
|
|
30
|
-
const folder = path.resolve(cwd, _folder)
|
|
31
|
-
|
|
32
|
-
const configPath = fs.existsSync(path.resolve(folder, 'config.js')) ? path.resolve(folder, 'config.js') : null
|
|
33
|
-
const middlewarePath = fs.existsSync(path.resolve(folder, 'middleware.js')) ? path.resolve(folder, 'middleware.js') : null
|
|
34
|
-
|
|
35
|
-
const pageFiles = discoverFilesByPattern(folder, PAGE_FILE_PATTERN)
|
|
36
|
-
const apiFiles = discoverFilesByPattern(folder, API_FILE_PATTERN)
|
|
37
|
-
const taskFiles = discoverFilesByPattern(folder, TASK_FILE_PATTERN)
|
|
38
|
-
const resourceTemplatesFiles = discoverFilesByPattern(folder, TASK_FILE_PATTERN)
|
|
39
|
-
|
|
40
|
-
const entries = [
|
|
41
|
-
...(middlewarePath ? [middlewarePath] : []),
|
|
42
|
-
...(configPath ? [configPath] : []),
|
|
43
|
-
...pageFiles,
|
|
44
|
-
...apiFiles,
|
|
45
|
-
...taskFiles,
|
|
46
|
-
...resourceTemplatesFiles,
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
return entries
|
|
50
|
-
}
|
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
|
-
});
|