@ossy/app 1.13.5 → 1.14.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/cli/build.js CHANGED
@@ -2,6 +2,7 @@ import path from 'path';
2
2
  import url from 'url';
3
3
  import fs from 'fs';
4
4
  import { pathToFileURL } from 'node:url'
5
+ import { createRequire } from 'node:module'
5
6
  import { rollup } from 'rollup';
6
7
  import babel from '@rollup/plugin-babel';
7
8
  import { nodeResolve as resolveDependencies } from '@rollup/plugin-node-resolve'
@@ -265,8 +266,13 @@ export function writeAppRuntimeShims ({ middlewareSourcePath, configSourcePath,
265
266
  * Middleware loads via `./.ossy/middleware.runtime.js`.
266
267
  */
267
268
  export function copyOssyAppRuntime ({ scriptDir, buildPath }) {
269
+ const require = createRequire(import.meta.url)
270
+ const platformServerPath = require.resolve('@ossy/platform/server')
271
+ const platformWorkerPath = require.resolve('@ossy/platform/worker')
272
+ const platformDir = path.dirname(platformServerPath)
273
+ const platformWorkerDir = path.dirname(platformWorkerPath)
268
274
  for (const name of ['server.js', 'proxy-internal.js']) {
269
- fs.copyFileSync(path.join(scriptDir, name), path.join(buildPath, name))
275
+ fs.copyFileSync(path.join(platformDir, name), path.join(buildPath, name))
270
276
  }
271
277
  const taskRuntimeFiles = fs
272
278
  .readdirSync(scriptDir, { withFileTypes: true })
@@ -275,8 +281,8 @@ export function copyOssyAppRuntime ({ scriptDir, buildPath }) {
275
281
  for (const name of taskRuntimeFiles) {
276
282
  fs.copyFileSync(path.join(scriptDir, name), path.join(buildPath, name))
277
283
  }
278
- fs.copyFileSync(path.join(scriptDir, 'worker-entry.js'), path.join(buildPath, 'worker.js'))
279
- fs.copyFileSync(path.join(scriptDir, 'worker-runtime.js'), path.join(buildPath, 'worker-runtime.js'))
284
+ fs.copyFileSync(path.join(platformWorkerDir, 'worker-entry.js'), path.join(buildPath, 'worker.js'))
285
+ fs.copyFileSync(path.join(platformWorkerDir, 'worker-runtime.js'), path.join(buildPath, 'worker-runtime.js'))
280
286
  const ossyOut = ossyGeneratedDir(buildPath)
281
287
  fs.copyFileSync(
282
288
  path.join(scriptDir, OSSY_RENDER_PAGE_RUNTIME_BASENAME),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.13.5",
3
+ "version": "1.14.0",
4
4
  "description": "",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
@@ -27,14 +27,15 @@
27
27
  "@babel/eslint-parser": "^7.15.8",
28
28
  "@babel/preset-react": "^7.26.3",
29
29
  "@babel/register": "^7.25.9",
30
- "@ossy/connected-components": "^1.13.5",
31
- "@ossy/design-system": "^1.13.5",
32
- "@ossy/pages": "^1.13.5",
33
- "@ossy/router": "^1.13.5",
34
- "@ossy/router-react": "^1.13.5",
35
- "@ossy/sdk": "^1.13.5",
36
- "@ossy/sdk-react": "^1.13.5",
37
- "@ossy/themes": "^1.13.5",
30
+ "@ossy/connected-components": "^1.14.0",
31
+ "@ossy/design-system": "^1.14.0",
32
+ "@ossy/pages": "^1.14.0",
33
+ "@ossy/platform": "^1.13.0",
34
+ "@ossy/router": "^1.14.0",
35
+ "@ossy/router-react": "^1.14.0",
36
+ "@ossy/sdk": "^1.14.0",
37
+ "@ossy/sdk-react": "^1.14.0",
38
+ "@ossy/themes": "^1.14.0",
38
39
  "@rollup/plugin-alias": "^6.0.0",
39
40
  "@rollup/plugin-babel": "6.1.0",
40
41
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -67,5 +68,5 @@
67
68
  "README.md",
68
69
  "tsconfig.json"
69
70
  ],
70
- "gitHead": "6c3b6ef26328e2227ee96f830138250fc1db1126"
71
+ "gitHead": "c496b1f449dba609d65efac16aa100a02a5f0595"
71
72
  }
@@ -1,121 +0,0 @@
1
- /** Node/Express can join duplicate workspace headers as `id, id` — API expects a single id. */
2
- function normalizeWorkspaceIdHeader (value) {
3
- if (!value || value === 'undefined') return undefined
4
- const first = String(value).split(',')[0]?.trim()
5
- return first || undefined
6
- }
7
-
8
- export function ProxyInternal() {
9
- return (req, res, next) => {
10
-
11
- if (!req.originalUrl.startsWith('/@ossy')) {
12
- return next()
13
- }
14
-
15
- if (req.originalUrl.startsWith('/@ossy/users/me/app-settings') && req.method === 'PATCH') {
16
-
17
- if (!req.body) {
18
- res.status(400)
19
- res.json("Invalid request body")
20
- return
21
- }
22
-
23
- const requestedSettings = req.body
24
-
25
-
26
- const expiresMaxAge = 2147483647 // Max age for cookies in milliseconds
27
-
28
- const userSettings = JSON.parse(req.signedCookies?.['x-ossy-user-settings'] || '{}')
29
-
30
- const updatedSettings = {
31
- ...userSettings,
32
- ...requestedSettings
33
- }
34
-
35
- res.cookie('x-ossy-user-settings', JSON.stringify(updatedSettings), {
36
- httpOnly: true,
37
- signed: true,
38
- expires: new Date(Date.now() + expiresMaxAge)
39
- })
40
-
41
- res.status(201)
42
- res.json("")
43
- return
44
- }
45
-
46
- if (req.originalUrl.startsWith('/@ossy/users/me/app-settings') && req.method === 'GET') {
47
- console.log(`[@ossy/app][proxy] GET /@ossy/users/me/app-settings`)
48
- const userSettings = JSON.parse(req.signedCookies?.['x-ossy-user-settings'] || '{}')
49
- res.status(200)
50
- res.json(userSettings)
51
- return
52
- }
53
-
54
- console.log(`[@ossy/app][proxy] ${req.method} ${req.originalUrl}`)
55
-
56
- const domain = process.env.OSSY_API_URL || 'https://api.ossy.se'
57
- const url = `${domain}${req.originalUrl?.replace('/@ossy', '/api/v0')}`
58
- const forwardedHeaders = JSON.parse(JSON.stringify(req.headers))
59
- const workspaceId = normalizeWorkspaceIdHeader(req.get('workspaceId'))
60
-
61
- if (workspaceId) {
62
- forwardedHeaders['workspaceid'] = workspaceId
63
- }
64
-
65
- console.log(`[@ossy/app][proxy] workspaceId ${workspaceId}`)
66
-
67
- const request = {
68
- method: req.method,
69
- headers: forwardedHeaders
70
- }
71
-
72
- if (!['GET', 'HEAD'].includes(req.method)) {
73
- request.body = JSON.stringify(req.body)
74
- }
75
-
76
- fetch(url, request)
77
- .then((response) => {
78
- // Forward headers from the proxy response
79
- response.headers.forEach((value, name) => {
80
- res.setHeader(name, value);
81
- });
82
-
83
- if (response.headers.get('content-type')?.includes('application/json')) {
84
- return response.text().then((text) => {
85
- const trimmed = text?.trim() ?? ''
86
- let data
87
- try {
88
- data = trimmed === '' ? null : JSON.parse(trimmed)
89
- } catch (e) {
90
- console.log(`[@ossy/app][proxy][error]`, e)
91
- res.removeHeader('content-length')
92
- const st = response.status
93
- if (st === 401 || st === 403 || st === 404) {
94
- res.status(st)
95
- res.json(null)
96
- return
97
- }
98
- res.status(502)
99
- res.json({ message: 'Upstream returned invalid JSON' })
100
- return
101
- }
102
- res.removeHeader('content-length')
103
- res.status(response.status)
104
- res.json(data)
105
- })
106
- }
107
-
108
- return response.arrayBuffer().then((buffer) => {
109
- res.status(response.status)
110
- res.send(Buffer.from(buffer))
111
- })
112
- })
113
- .catch((error) => {
114
- console.log(`[@ossy/app][proxy][error]`, error)
115
- const status = error.status
116
- res.status(status || 500)
117
- res.json({ message: error.message || 'Internal Server Error' })
118
- })
119
-
120
- }
121
- }
package/cli/server.js DELETED
@@ -1,157 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'path';
3
- import url from 'url'
4
- import { pathToFileURL } from 'node:url'
5
- import express from 'express'
6
- import morgan from 'morgan'
7
- import { Router as OssyRouter } from '@ossy/router'
8
- import { ProxyInternal } from './proxy-internal.js'
9
- import cookieParser from 'cookie-parser'
10
-
11
- import buildTimeConfig from './.ossy/server-config.runtime.mjs'
12
- import { BuildPage, buildPrerenderAppConfig } from './.ossy/render-page.task.js'
13
- import Middleware from './.ossy/middleware.runtime.js'
14
-
15
- const __ossyDir = path.dirname(url.fileURLToPath(import.meta.url)) + '/.ossy'
16
-
17
- function readOssyJson (name) {
18
- return JSON.parse(fs.readFileSync(path.join(__ossyDir, name), 'utf8'))
19
- }
20
-
21
- const apiRouteList = readOssyJson('api.generated.json') ?? []
22
- const sitePageList = readOssyJson('pages.generated.json') ?? []
23
-
24
- /** When `src/config.js` is minimal, infer language list from the first multi-path page. */
25
- function pageRouterLanguageOptions (config, pages) {
26
- let supported = config?.supportedLanguages
27
- let defaultLanguage = config?.defaultLanguage
28
- if ((!supported || supported.length <= 1) && pages.length > 0) {
29
- const p0 = pages[0]
30
- if (p0 && typeof p0.path === 'object' && p0.path != null) {
31
- supported = Object.keys(p0.path)
32
- defaultLanguage = defaultLanguage || supported[0]
33
- }
34
- }
35
- return {
36
- supportedLanguages: Array.isArray(supported) ? supported : [],
37
- defaultLanguage,
38
- }
39
- }
40
-
41
- const app = express();
42
-
43
- const currentDir = path.dirname(url.fileURLToPath(import.meta.url))
44
- const ROOT_PATH = path.resolve(currentDir, 'public')
45
-
46
- function parsePortFromArgv(argv) {
47
- // Supports: --port 4000, --port=4000, -p 4000
48
- const idx = argv.findIndex(a => a === '--port' || a === '-p')
49
- if (idx !== -1 && argv[idx + 1]) return argv[idx + 1]
50
-
51
- const eq = argv.find(a => a.startsWith('--port='))
52
- if (eq) return eq.split('=')[1]
53
-
54
- return undefined
55
- }
56
-
57
- function normalizePort(value, fallback) {
58
- if (value === undefined || value === null || value === '') return fallback
59
- const n = Number.parseInt(String(value), 10)
60
- if (!Number.isFinite(n) || n <= 0) return fallback
61
- return n
62
- }
63
-
64
- const DEFAULT_PORT = 3000
65
- const port = normalizePort(parsePortFromArgv(process.argv) ?? process.env.PORT, DEFAULT_PORT)
66
-
67
- if (Middleware !== undefined) {
68
- console.log(`[@ossy/app][server] ${Middleware?.length || 0} custom middleware loaded`)
69
- }
70
-
71
-
72
- const middleware = [
73
- morgan('tiny'),
74
- express.json({ strict: false }),
75
- cookieParser(process.env.OSSY_COOKIE_SECRET || 'default_secret'),
76
- (req, res, next) => {
77
- const userSettings = JSON.parse(req.signedCookies?.['x-ossy-user-settings'] || '{}')
78
- req.userAppSettings = userSettings
79
-
80
- // Incoming headers live on lowercase keys (`workspaceid`). Do not use
81
- // `req.headers.workspaceId` — it misses the client header and duplicates
82
- // the id (Node then surfaces `id, id` from req.get('workspaceId')).
83
- if (userSettings.workspaceId && !req.get('workspaceId')) {
84
- req.headers['workspaceid'] = userSettings.workspaceId
85
- }
86
-
87
- // Check for auth cookie
88
- const cookieHeader = req.headers.cookie
89
- req.isAuthenticated = cookieHeader ? cookieHeader.includes('auth=') : false
90
-
91
- next()
92
- },
93
- ...(Middleware || []),
94
- express.static(ROOT_PATH),
95
- ProxyInternal(),
96
- ]
97
-
98
- app.use(middleware)
99
-
100
- const apiRouter = OssyRouter.of({
101
- pages: apiRouteList,
102
- })
103
-
104
- const { supportedLanguages, defaultLanguage } = pageRouterLanguageOptions(
105
- buildTimeConfig,
106
- sitePageList
107
- )
108
-
109
- const pageRouter = OssyRouter.of({
110
- pages: sitePageList,
111
- defaultLanguage,
112
- supportedLanguages,
113
- })
114
-
115
- app.all('*all', async (req, res) => {
116
- const requestUrl = req.originalUrl || '/'
117
-
118
- try {
119
- const apiRoute = apiRouter.getPageByUrl(requestUrl)
120
- if (apiRoute?.module) {
121
- console.log(`[@ossy/app][server] Handling API route: ${requestUrl}`)
122
- const mod = await import(pathToFileURL(path.resolve(__ossyDir, apiRoute.module)).href)
123
- await mod.default(req, res)
124
- return
125
- }
126
-
127
- if (req.method !== 'GET' && req.method !== 'HEAD') {
128
- res.status(404).send('Not found')
129
- return
130
- }
131
-
132
- const pageRoute = pageRouter.getPageByUrl(requestUrl)
133
- if (pageRoute) {
134
- const appConfig = buildPrerenderAppConfig({
135
- buildTimeConfig,
136
- pageList: sitePageList,
137
- activeRouteId: pageRoute.id,
138
- urlPath: requestUrl,
139
- isAuthenticated: !!req.isAuthenticated,
140
- })
141
- const html = await BuildPage.handle({ route: pageRoute, appConfig })
142
- res.status(200).type('html').send(html)
143
- return
144
- }
145
-
146
- res.status(404).send('Not found')
147
- } catch (err) {
148
- console.error('[@ossy/app][server] Request handling failed:', err)
149
- if (!res.headersSent) {
150
- res.status(500).type('text').send('Internal Server Error')
151
- }
152
- }
153
- })
154
-
155
- app.listen(port, () => {
156
- console.log(`[@ossy/app][server] Running on http://localhost:${port}`);
157
- });
@@ -1,10 +0,0 @@
1
- import 'dotenv/config'
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
- import { fileURLToPath } from 'node:url'
5
- import { runWorkerScheduler } from './worker-runtime.js'
6
-
7
- const __ossyDir = path.dirname(fileURLToPath(import.meta.url)) + '/.ossy'
8
- const tasks = JSON.parse(fs.readFileSync(path.join(__ossyDir, 'tasks.generated.json'), 'utf8')) ?? []
9
-
10
- runWorkerScheduler(tasks, __ossyDir)
@@ -1,121 +0,0 @@
1
- import path from 'node:path'
2
- import { pathToFileURL } from 'node:url'
3
- import { SDK } from '@ossy/sdk'
4
-
5
- /**
6
- * @param {Array<{ type: string, module: string }>} tasks
7
- * @param {string} ossyDir - absolute path to the .ossy directory
8
- */
9
- export function runWorkerScheduler(tasks, ossyDir) {
10
- const sdk = SDK.of({
11
- workspaceId: process.env.OSSY_WORKSPACE_ID,
12
- apiUrl: process.env.OSSY_API_URL,
13
- authorization: process.env.OSSY_API_TOKEN,
14
- })
15
-
16
- const jobsClient = /** @type {{ getUnprocessed: () => Promise<unknown[]> }} */ (sdk.jobs)
17
-
18
- let status = 'running'
19
-
20
- console.log('Starting scheduler')
21
-
22
- main()
23
-
24
- setInterval(() => {
25
- if (status === 'running') return
26
- status = 'running'
27
- try {
28
- main()
29
- } catch (error) {
30
- console.log('Error running main')
31
- console.error(error)
32
- status = 'idle'
33
- }
34
- }, 3000)
35
-
36
- function main() {
37
- console.log('Looking for jobs')
38
- jobsClient
39
- .getUnprocessed()
40
- .then(async (jobs) => {
41
- if (!jobs || !jobs.length) {
42
- console.log('No jobs found, going idle')
43
- status = 'idle'
44
- return
45
- }
46
-
47
- const jobsGroupedByResourceId = groupJobsByResourceId(jobs)
48
-
49
- console.log(`Found ${jobs.length} jobs between ${jobsGroupedByResourceId.length} resources`)
50
-
51
- const processedGroups = jobsGroupedByResourceId.map(([resourceId, groupJobs]) => {
52
- console.log(`Processing group for resourceId ${resourceId}`)
53
- return processJobsSequentially(groupJobs)
54
- .then(() => console.log(`Completed group for resourceId ${resourceId}`))
55
- .catch((err) => {
56
- console.log(`Failed to process group for resourceId ${resourceId}`)
57
- console.error(err)
58
- })
59
- })
60
-
61
- try {
62
- await Promise.allSettled(processedGroups)
63
-
64
- console.log(`Finished processing of groups...`)
65
- console.log('Going idle')
66
- status = 'idle'
67
- console.log('----------------------------------')
68
- console.groupEnd()
69
- } catch (error) {
70
- console.log('Error processing groups')
71
- console.error(error)
72
- status = 'idle'
73
- }
74
- })
75
- .catch((error) => {
76
- console.log('Error getting jobs')
77
- console.error(error)
78
- status = 'idle'
79
- })
80
- }
81
-
82
- function groupJobsByResourceId(jobList) {
83
- return Object.entries(
84
- jobList.reduce((acc, job) => {
85
- const content = /** @type {{ resourceId?: string }} */ (job.content || {})
86
- const rid = content.resourceId
87
- return {
88
- ...acc,
89
- [rid]: [...(acc[rid] || []), job],
90
- }
91
- }, /** @type {Record<string, unknown[]>} */ ({}))
92
- )
93
- }
94
-
95
- async function processJobsSequentially(jobList) {
96
- console.log(`Processing ${jobList.length} jobs`)
97
- for (const job of jobList) {
98
- console.log(`Processing job ${job.id}`)
99
- const task = tasks.find((t) => t.type === job.type)
100
-
101
- if (!task) {
102
- console.log('No handler found for job', job.id)
103
- continue
104
- }
105
-
106
- console.log(`Handler found for ${task.type}`)
107
-
108
- try {
109
- const mod = await import(pathToFileURL(path.resolve(ossyDir, task.module)).href)
110
- const jobSdk = SDK.of({
111
- workspaceId: job.belongsTo,
112
- authorization: process.env.OSSY_API_TOKEN,
113
- })
114
- await mod.default({ sdk: jobSdk, job }).catch(() => {})
115
- } catch (error) {
116
- console.error(error)
117
- console.log('Failed to processing job')
118
- }
119
- }
120
- }
121
- }