@ossy/app 1.11.0 → 1.11.2

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/dev.js CHANGED
@@ -3,13 +3,15 @@ import url from 'url';
3
3
  import fs from 'fs';
4
4
  import {
5
5
  printBuildOverview,
6
+ getBuildOverviewSnapshot,
7
+ filePathToRoute,
6
8
  discoverFilesByPattern,
7
9
  PAGE_FILE_PATTERN,
8
- generatePagesModule,
10
+ writePagesManifest,
9
11
  resolveApiSource,
10
12
  resolveTaskSource,
11
13
  resetOssyBuildDir,
12
- bundleOssyNodeEntry,
14
+ compileOssyNodeArtifacts,
13
15
  copyOssyAppRuntime,
14
16
  writeAppRuntimeShims,
15
17
  createOssyAppBundlePlugins,
@@ -19,26 +21,19 @@ import {
19
21
  clientHydrateIdForPage,
20
22
  ossyGeneratedDir,
21
23
  OSSY_GEN_PAGES_BASENAME,
22
- OSSY_GEN_API_BASENAME,
23
- OSSY_GEN_TASKS_BASENAME,
24
- OSSY_PAGES_PRERENDER_BUNDLE,
25
- OSSY_API_SERVER_BUNDLE,
26
- OSSY_TASKS_SERVER_BUNDLE,
24
+ OSSY_PAGES_RUNTIME_BASENAME,
27
25
  writeResourceTemplatesBarrelIfPresent,
28
26
  resourceTemplatesDir,
29
27
  OSSY_RESOURCE_TEMPLATES_OUT,
30
28
  } from './build.js';
31
29
  import prerenderReactTask from './prerender-react.task.js'
32
- import { createParallelPagesReporter } from './build-terminal.js'
30
+ import { createBuildDashboard } from './build-terminal.js'
33
31
  import { watch } from 'rollup';
34
32
  import arg from 'arg'
35
33
  import { spawn } from 'node:child_process'
36
34
  // import inject from '@rollup/plugin-inject'
37
35
 
38
36
  export const dev = async (cliArgs) => {
39
- console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mdev\x1b[0m')
40
- console.log(' \x1b[2m────────────────────────────────────────\x1b[0m')
41
-
42
37
  const options = arg({
43
38
  '--config': String,
44
39
  '-c': '--config',
@@ -49,40 +44,50 @@ export const dev = async (cliArgs) => {
49
44
  const buildPath = path.resolve('build')
50
45
  const srcDir = path.resolve('src')
51
46
  const configPath = path.resolve(options['--config'] || 'src/config.js');
52
- const pageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
47
+ let currentPageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
53
48
 
54
49
  resetOssyBuildDir(buildPath)
55
50
 
56
- writeResourceTemplatesBarrelIfPresent({ cwd: process.cwd(), log: true })
51
+ const resourceTemplatesResult = writeResourceTemplatesBarrelIfPresent({
52
+ cwd: process.cwd(),
53
+ log: false,
54
+ })
55
+ let resourceTemplatesDevLogged = false
57
56
 
58
- const pagesGeneratedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
59
- fs.writeFileSync(
60
- pagesGeneratedPath,
61
- generatePagesModule(pageFiles, srcDir, pagesGeneratedPath)
62
- )
63
57
  const ossyDir = ossyGeneratedDir(buildPath)
64
- writePageHydrateStubs(pageFiles, srcDir, ossyDir)
65
- const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
66
-
67
- const {
68
- apiSourcePath: resolvedApi,
69
- apiOverviewFiles,
70
- } = resolveApiSource({
58
+ const pagesGeneratedPath = path.join(ossyDir, OSSY_GEN_PAGES_BASENAME)
59
+ writePagesManifest({
60
+ pageFiles: currentPageFiles,
71
61
  srcDir,
72
- buildPath,
62
+ pagesGeneratedPath,
73
63
  })
74
- let apiSourcePath = resolvedApi
75
- resolveTaskSource({ srcDir, buildPath })
64
+ writePageHydrateStubs(currentPageFiles, srcDir, ossyDir)
65
+ const clientHydrateInput = buildClientHydrateInput(currentPageFiles, srcDir, ossyDir)
66
+
67
+ let apiOverviewFiles = []
68
+ let taskOverviewFiles = []
69
+ const refreshApiTaskManifests = () => {
70
+ apiOverviewFiles = resolveApiSource({ srcDir, buildPath }).apiOverviewFiles
71
+ taskOverviewFiles = resolveTaskSource({ srcDir, buildPath }).taskOverviewFiles
72
+ }
73
+ refreshApiTaskManifests()
76
74
  let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/middleware.js');
77
75
  const publicDir = path.resolve('public')
78
76
 
79
- printBuildOverview({
80
- pagesSourcePath: pagesGeneratedPath,
81
- apiSourcePath,
82
- apiOverviewFiles,
83
- configPath,
84
- pageFiles,
85
- });
77
+ if (currentPageFiles.length === 0) {
78
+ console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mdev\x1b[0m')
79
+ printBuildOverview({
80
+ pagesSourcePath: pagesGeneratedPath,
81
+ apiOverviewFiles,
82
+ configPath,
83
+ pageFiles: currentPageFiles,
84
+ })
85
+ if (resourceTemplatesResult.wrote && resourceTemplatesResult.path) {
86
+ console.log(
87
+ `[@ossy/app][resource-templates] merged ${resourceTemplatesResult.count} template(s) → ${path.relative(process.cwd(), resourceTemplatesResult.path)}`
88
+ )
89
+ }
90
+ }
86
91
 
87
92
  if (!fs.existsSync(middlewareSourcePath)) {
88
93
  middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
@@ -92,29 +97,21 @@ export const dev = async (cliArgs) => {
92
97
  ? configPath
93
98
  : path.resolve(scriptDir, 'default-config.js')
94
99
 
95
- const pagesBundlePath = path.join(ossyDir, OSSY_PAGES_PRERENDER_BUNDLE)
96
- const apiBundlePath = path.join(ossyDir, OSSY_API_SERVER_BUNDLE)
97
- const tasksBundlePath = path.join(ossyDir, OSSY_TASKS_SERVER_BUNDLE)
98
- const tasksGeneratedPath = path.join(ossyDir, OSSY_GEN_TASKS_BASENAME)
100
+ const pagesEntryPath = path.join(ossyDir, OSSY_PAGES_RUNTIME_BASENAME)
99
101
 
100
102
  const runNodeBundles = async () => {
101
- await bundleOssyNodeEntry({
102
- inputPath: pagesGeneratedPath,
103
- outputFile: pagesBundlePath,
104
- nodeEnv: 'development',
105
- })
106
- await bundleOssyNodeEntry({
107
- inputPath: apiSourcePath,
108
- outputFile: apiBundlePath,
109
- nodeEnv: 'development',
110
- })
111
- await bundleOssyNodeEntry({
112
- inputPath: tasksGeneratedPath,
113
- outputFile: tasksBundlePath,
103
+ refreshApiTaskManifests()
104
+ await compileOssyNodeArtifacts({
105
+ pageFiles: currentPageFiles,
106
+ srcDir,
107
+ ossyDir,
108
+ apiFiles: apiOverviewFiles,
109
+ taskFiles: taskOverviewFiles,
114
110
  nodeEnv: 'development',
115
111
  })
116
112
  writeAppRuntimeShims({
117
113
  middlewareSourcePath,
114
+ configSourcePath,
118
115
  ossyDir,
119
116
  })
120
117
  copyOssyAppRuntime({ scriptDir, buildPath })
@@ -183,38 +180,7 @@ export const dev = async (cliArgs) => {
183
180
  }
184
181
 
185
182
  const nodeWatchOpts = { watch: { clearScreen: false } }
186
- const watchConfigs = [
187
- {
188
- input: pagesGeneratedPath,
189
- output: {
190
- file: pagesBundlePath,
191
- format: 'esm',
192
- inlineDynamicImports: true,
193
- },
194
- plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
195
- ...nodeWatchOpts,
196
- },
197
- {
198
- input: apiSourcePath,
199
- output: {
200
- file: apiBundlePath,
201
- format: 'esm',
202
- inlineDynamicImports: true,
203
- },
204
- plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
205
- ...nodeWatchOpts,
206
- },
207
- {
208
- input: tasksGeneratedPath,
209
- output: {
210
- file: tasksBundlePath,
211
- format: 'esm',
212
- inlineDynamicImports: true,
213
- },
214
- plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
215
- ...nodeWatchOpts,
216
- },
217
- ]
183
+ const watchConfigs = []
218
184
  if (Object.keys(clientHydrateInput).length > 0) {
219
185
  watchConfigs.push({
220
186
  input: clientHydrateInput,
@@ -222,6 +188,30 @@ export const dev = async (cliArgs) => {
222
188
  plugins: clientPlugins,
223
189
  watch: { clearScreen: false },
224
190
  })
191
+ } else {
192
+ watchConfigs.push({
193
+ input: '\0ossy-dev-noop',
194
+ output: {
195
+ file: path.join(ossyDir, '.dev-noop-out.mjs'),
196
+ format: 'esm',
197
+ inlineDynamicImports: true,
198
+ },
199
+ plugins: [
200
+ {
201
+ name: 'ossy-dev-noop',
202
+ resolveId (id) {
203
+ if (id === '\0ossy-dev-noop') return id
204
+ return null
205
+ },
206
+ load (id) {
207
+ if (id === '\0ossy-dev-noop') return 'export default 0\n'
208
+ return null
209
+ },
210
+ },
211
+ ...createOssyAppBundlePlugins({ nodeEnv: 'development' }),
212
+ ],
213
+ ...nodeWatchOpts,
214
+ })
225
215
  }
226
216
  const watcher = watch(watchConfigs)
227
217
 
@@ -240,21 +230,54 @@ export const dev = async (cliArgs) => {
240
230
  if (event.code === 'END') {
241
231
  writeAppRuntimeShims({
242
232
  middlewareSourcePath,
233
+ configSourcePath,
243
234
  ossyDir,
244
235
  })
245
236
  copyOssyAppRuntime({ scriptDir, buildPath })
246
- if (pageFiles.length > 0) {
237
+ if (currentPageFiles.length > 0) {
247
238
  const pageIds = [
248
- ...new Set(pageFiles.map((f) => clientHydrateIdForPage(f, srcDir))),
239
+ ...new Set(currentPageFiles.map((f) => clientHydrateIdForPage(f, srcDir))),
249
240
  ].sort()
241
+ const overviewSnap = getBuildOverviewSnapshot({
242
+ pagesSourcePath: pagesGeneratedPath,
243
+ apiOverviewFiles,
244
+ configPath,
245
+ pageFiles: currentPageFiles,
246
+ })
247
+ const idToPath = Object.fromEntries(
248
+ currentPageFiles.map((f) => [
249
+ clientHydrateIdForPage(f, srcDir),
250
+ filePathToRoute(f, srcDir).path,
251
+ ])
252
+ )
250
253
  const reporter = pageIds.length
251
- ? createParallelPagesReporter({ pageIds, mode: 'prerender-only' })
254
+ ? createBuildDashboard({
255
+ mode: 'prerender-only',
256
+ pageIds,
257
+ idToPath,
258
+ overview: {
259
+ title: '@ossy/app dev',
260
+ configRel: overviewSnap.configRel,
261
+ apiRoutes: overviewSnap.apiRoutes,
262
+ },
263
+ })
252
264
  : null
253
- reporter?.printSectionTitle('prerender (parallel)')
265
+ reporter?.start()
266
+ if (
267
+ !resourceTemplatesDevLogged &&
268
+ resourceTemplatesResult.wrote &&
269
+ resourceTemplatesResult.path &&
270
+ reporter
271
+ ) {
272
+ reporter.pushLog(
273
+ `[resource-templates] merged ${resourceTemplatesResult.count} → ${path.relative(process.cwd(), resourceTemplatesResult.path)}`
274
+ )
275
+ resourceTemplatesDevLogged = true
276
+ }
254
277
  try {
255
278
  await prerenderReactTask.handler({
256
279
  op: 'prerenderPagesParallel',
257
- pagesBundlePath,
280
+ pagesEntryPath,
258
281
  configSourcePath,
259
282
  publicDir: path.join(buildPath, 'public'),
260
283
  reporter,
@@ -271,37 +294,34 @@ export const dev = async (cliArgs) => {
271
294
 
272
295
  const regenApiGenerated = () => {
273
296
  if (options['--api-source']) return
274
- resolveApiSource({
275
- srcDir,
276
- buildPath,
277
- })
278
- const gen = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_API_BASENAME)
279
- if (fs.existsSync(gen) && typeof watcher?.invalidate === 'function') {
280
- watcher.invalidate(gen)
281
- }
297
+ void runNodeBundles().then(() => scheduleRestart())
282
298
  }
283
299
 
284
300
  const regenTasksGenerated = () => {
285
- resolveTaskSource({ srcDir, buildPath })
286
- if (fs.existsSync(tasksGeneratedPath) && typeof watcher?.invalidate === 'function') {
287
- watcher.invalidate(tasksGeneratedPath)
288
- }
301
+ void runNodeBundles().then(() => scheduleRestart())
289
302
  }
290
303
 
291
304
  fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
292
305
  if (!filename) return
293
306
  if (/\.page\.(jsx?|tsx?)$/.test(filename)) {
294
307
  const refreshedPageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
308
+ currentPageFiles = refreshedPageFiles
295
309
  const regenPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
296
- fs.writeFileSync(regenPath, generatePagesModule(refreshedPageFiles, srcDir, regenPath))
310
+ writePagesManifest({
311
+ pageFiles: refreshedPageFiles,
312
+ srcDir,
313
+ pagesGeneratedPath: regenPath,
314
+ })
297
315
  writePageHydrateStubs(refreshedPageFiles, srcDir, ossyDir)
298
- if (typeof watcher?.invalidate === 'function') {
299
- watcher.invalidate(regenPath)
300
- for (const f of refreshedPageFiles) {
301
- const hid = clientHydrateIdForPage(f, srcDir)
302
- watcher.invalidate(path.join(ossyDir, `hydrate-${hid}.jsx`))
316
+ void runNodeBundles().then(() => {
317
+ if (typeof watcher?.invalidate === 'function') {
318
+ for (const f of refreshedPageFiles) {
319
+ const hid = clientHydrateIdForPage(f, srcDir)
320
+ watcher.invalidate(path.join(ossyDir, `hydrate-${hid}.jsx`))
321
+ }
303
322
  }
304
- }
323
+ scheduleRestart()
324
+ })
305
325
  }
306
326
  if (/\.api\.(mjs|cjs|js)$/.test(filename)) {
307
327
  regenApiGenerated()
@@ -311,7 +331,7 @@ export const dev = async (cliArgs) => {
311
331
  }
312
332
  const norm = filename.replace(/\\/g, '/')
313
333
  if (/\.resource\.js$/.test(norm) && norm.includes('resource-templates/')) {
314
- writeResourceTemplatesBarrelIfPresent({ cwd: process.cwd(), log: true })
334
+ writeResourceTemplatesBarrelIfPresent({ cwd: process.cwd(), log: false })
315
335
  const rtOut = path.join(resourceTemplatesDir(process.cwd()), OSSY_RESOURCE_TEMPLATES_OUT)
316
336
  if (fs.existsSync(rtOut) && typeof watcher?.invalidate === 'function') {
317
337
  watcher.invalidate(rtOut)
@@ -0,0 +1,39 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath, pathToFileURL } from 'node:url'
4
+ import React from 'react'
5
+
6
+ const __ossyDir = path.dirname(fileURLToPath(import.meta.url))
7
+
8
+ function readJson (name) {
9
+ return JSON.parse(fs.readFileSync(path.join(__ossyDir, name), 'utf8'))
10
+ }
11
+
12
+ function toPage (mod, derived) {
13
+ const meta = mod?.metadata || {}
14
+ const def = mod?.default
15
+ if (typeof def === 'function') {
16
+ return { ...derived, ...meta, element: React.createElement(def) }
17
+ }
18
+ return { ...derived, ...meta, ...(def || {}) }
19
+ }
20
+
21
+ const { pages: metaPages } = readJson('pages.generated.json')
22
+ const { pages: bundlePages } = readJson('pages.bundle.json')
23
+
24
+ if (metaPages.length !== bundlePages.length) {
25
+ throw new Error(
26
+ '[@ossy/app][pages.runtime] pages.generated.json and pages.bundle.json must list the same number of pages'
27
+ )
28
+ }
29
+
30
+ const out = []
31
+ for (let i = 0; i < metaPages.length; i++) {
32
+ const derived = { id: metaPages[i].id, path: metaPages[i].path }
33
+ const rel = bundlePages[i].module
34
+ const abs = path.resolve(__ossyDir, rel)
35
+ const mod = await import(pathToFileURL(abs).href)
36
+ out.push(toPage(mod, derived))
37
+ }
38
+
39
+ export default out
@@ -2,7 +2,7 @@ import path from 'path'
2
2
  import url from 'url'
3
3
  import fs from 'fs'
4
4
  import { rollup } from 'rollup'
5
- import { BuildPage } from './render-page.task.js'
5
+ import { BuildPage, buildPrerenderAppConfig } from './render-page.task.js'
6
6
  import { pageIdFromHydrateEntryName } from './build-terminal.js'
7
7
 
8
8
  /**
@@ -24,35 +24,6 @@ function pathIsPrerenderable (routePath) {
24
24
  return true
25
25
  }
26
26
 
27
- /** Mirrors server-era app shell config with static defaults (no cookies / session). */
28
- export function buildPrerenderAppConfig ({
29
- buildTimeConfig,
30
- pageList,
31
- activeRouteId,
32
- urlPath,
33
- }) {
34
- const pages = pageList.map((page) => {
35
- const entry = {
36
- id: page?.id,
37
- path: page?.path,
38
- }
39
- if (activeRouteId != null && page?.id === activeRouteId) {
40
- entry.element = page?.element
41
- }
42
- return entry
43
- })
44
- return {
45
- ...buildTimeConfig,
46
- url: urlPath,
47
- theme: buildTimeConfig.theme || 'light',
48
- isAuthenticated: false,
49
- workspaceId: buildTimeConfig.workspaceId,
50
- apiUrl: buildTimeConfig.apiUrl,
51
- pages,
52
- sidebarPrimaryCollapsed: false,
53
- }
54
- }
55
-
56
27
  function copyPublicToBuild ({ copyPublicFrom, buildPath }) {
57
28
  if (!copyPublicFrom || !fs.existsSync(copyPublicFrom)) return
58
29
  const dest = path.join(buildPath, 'public')
@@ -170,13 +141,13 @@ async function prerenderOnePage ({
170
141
  }
171
142
 
172
143
  async function prerenderPagesParallel ({
173
- pagesBundlePath,
144
+ pagesEntryPath,
174
145
  configSourcePath,
175
146
  publicDir,
176
147
  reporter,
177
148
  }) {
178
149
  const cfgHref = url.pathToFileURL(path.resolve(configSourcePath)).href
179
- const pagesHref = url.pathToFileURL(path.resolve(pagesBundlePath)).href
150
+ const pagesHref = url.pathToFileURL(path.resolve(pagesEntryPath)).href
180
151
 
181
152
  const configModule = await import(cfgHref)
182
153
  const pagesModule = await import(pagesHref)
@@ -237,7 +208,7 @@ async function runProduction ({
237
208
  copyPublicFrom,
238
209
  buildPath,
239
210
  nodeEnv,
240
- pagesBundlePath,
211
+ pagesEntryPath,
241
212
  configSourcePath,
242
213
  createClientRollupPlugins,
243
214
  minifyBrowserStaticChunks,
@@ -259,7 +230,7 @@ async function runProduction ({
259
230
  })
260
231
 
261
232
  const { failures: prerenderFailures } = await prerenderPagesParallel({
262
- pagesBundlePath,
233
+ pagesEntryPath,
263
234
  configSourcePath,
264
235
  publicDir: path.join(buildPath, 'public'),
265
236
  reporter,
@@ -284,7 +255,7 @@ export default {
284
255
  }
285
256
  if (op === 'prerenderPagesParallel') {
286
257
  return prerenderPagesParallel({
287
- pagesBundlePath: input.pagesBundlePath,
258
+ pagesEntryPath: input.pagesEntryPath,
288
259
  configSourcePath: input.configSourcePath,
289
260
  publicDir: input.publicDir,
290
261
  reporter: input.reporter,
@@ -1,6 +1,38 @@
1
1
  import React, { cloneElement } from 'react'
2
2
  import { prerenderToNodeStream } from 'react-dom/static'
3
3
 
4
+ /**
5
+ * App shell config for SSR / prerender (mirrors client: theme, pages metadata, active route element).
6
+ */
7
+ export function buildPrerenderAppConfig ({
8
+ buildTimeConfig,
9
+ pageList,
10
+ activeRouteId,
11
+ urlPath,
12
+ isAuthenticated = false,
13
+ }) {
14
+ const pages = pageList.map((page) => {
15
+ const entry = {
16
+ id: page?.id,
17
+ path: page?.path,
18
+ }
19
+ if (activeRouteId != null && page?.id === activeRouteId) {
20
+ entry.element = page?.element
21
+ }
22
+ return entry
23
+ })
24
+ return {
25
+ ...buildTimeConfig,
26
+ url: urlPath,
27
+ theme: buildTimeConfig.theme || 'light',
28
+ isAuthenticated,
29
+ workspaceId: buildTimeConfig.workspaceId,
30
+ apiUrl: buildTimeConfig.apiUrl,
31
+ pages,
32
+ sidebarPrimaryCollapsed: false,
33
+ }
34
+ }
35
+
4
36
  /** Strips non-JSON content (e.g. React elements on `pages`) for the bootstrap script. */
5
37
  export function appConfigForBootstrap (appConfig) {
6
38
  if (!appConfig || typeof appConfig !== 'object') return appConfig
package/cli/server.js CHANGED
@@ -6,12 +6,17 @@ import { Router as OssyRouter } from '@ossy/router'
6
6
  import { ProxyInternal } from './proxy-internal.js'
7
7
  import cookieParser from 'cookie-parser'
8
8
 
9
- import ApiRoutes from './.ossy/api.bundle.js'
9
+ import ApiRoutes from './.ossy/api.runtime.mjs'
10
+ import pageRoutes from './.ossy/pages.runtime.mjs'
11
+ import buildTimeConfig from './.ossy/server-config.runtime.mjs'
12
+ import { BuildPage, buildPrerenderAppConfig } from './.ossy/render-page.task.js'
10
13
  import Middleware from './.ossy/middleware.runtime.js'
11
14
 
12
15
  /** API bundle default may be an empty array. */
13
16
  const apiRouteList = ApiRoutes ?? []
14
17
 
18
+ const sitePageList = Array.isArray(pageRoutes) ? pageRoutes : []
19
+
15
20
  const app = express();
16
21
 
17
22
  const currentDir = path.dirname(url.fileURLToPath(import.meta.url))
@@ -101,22 +106,57 @@ const middleware = [
101
106
 
102
107
  app.use(middleware)
103
108
 
104
- const Router = OssyRouter.of({
109
+ const apiRouter = OssyRouter.of({
105
110
  pages: apiRouteList,
106
111
  })
107
112
 
108
- app.all('*all', (req, res) => {
109
- const pathname = req.originalUrl
113
+ const pageRouter = OssyRouter.of({
114
+ pages: sitePageList,
115
+ defaultLanguage: buildTimeConfig.defaultLanguage,
116
+ supportedLanguages: buildTimeConfig.supportedLanguages || [],
117
+ })
110
118
 
111
- const route = Router.getPageByUrl(pathname)
119
+ app.all('*all', async (req, res) => {
120
+ const requestUrl = req.originalUrl || '/'
112
121
 
113
- if (route && typeof route.handle === 'function') {
114
- console.log(`[@ossy/app][server] Handling API route: ${pathname}`)
115
- route.handle(req, res)
116
- return
117
- }
122
+ try {
123
+ const apiRoute = apiRouter.getPageByUrl(requestUrl)
124
+ if (apiRoute && typeof apiRoute.handle === 'function') {
125
+ console.log(`[@ossy/app][server] Handling API route: ${requestUrl}`)
126
+ apiRoute.handle(req, res)
127
+ return
128
+ }
129
+
130
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
131
+ res.status(404).send('Not found')
132
+ return
133
+ }
134
+
135
+ const pageRoute = pageRouter.getPageByUrl(requestUrl)
136
+ if (pageRoute?.element) {
137
+ const appConfig = buildPrerenderAppConfig({
138
+ buildTimeConfig,
139
+ pageList: sitePageList,
140
+ activeRouteId: pageRoute.id,
141
+ urlPath: requestUrl,
142
+ isAuthenticated: !!req.isAuthenticated,
143
+ })
144
+ const html = await BuildPage.handle({
145
+ route: pageRoute,
146
+ appConfig,
147
+ isDevReloadEnabled,
148
+ })
149
+ res.status(200).type('html').send(html)
150
+ return
151
+ }
118
152
 
119
- res.status(404).send('Not found')
153
+ res.status(404).send('Not found')
154
+ } catch (err) {
155
+ console.error('[@ossy/app][server] Request handling failed:', err)
156
+ if (!res.headersSent) {
157
+ res.status(500).type('text').send('Internal Server Error')
158
+ }
159
+ }
120
160
  })
121
161
 
122
162
  app.listen(port, () => {
@@ -0,0 +1,22 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath, pathToFileURL } from 'node:url'
4
+
5
+ const __ossyDir = path.dirname(fileURLToPath(import.meta.url))
6
+
7
+ function normalizeTaskExport (mod) {
8
+ const d = mod?.default
9
+ if (d == null) return []
10
+ return Array.isArray(d) ? d : [d]
11
+ }
12
+
13
+ const { modules } = JSON.parse(fs.readFileSync(path.join(__ossyDir, 'tasks.bundle.json'), 'utf8'))
14
+
15
+ const out = []
16
+ for (const rel of modules) {
17
+ const abs = path.resolve(__ossyDir, rel)
18
+ const mod = await import(pathToFileURL(abs).href)
19
+ out.push(...normalizeTaskExport(mod))
20
+ }
21
+
22
+ export default out
@@ -1,5 +1,5 @@
1
1
  import 'dotenv/config'
2
- import taskHandlers from './.ossy/tasks.bundle.js'
2
+ import taskHandlers from './.ossy/tasks.runtime.mjs'
3
3
  import { runWorkerScheduler } from './worker-runtime.js'
4
4
 
5
5
  runWorkerScheduler(taskHandlers)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
@@ -27,14 +27,14 @@
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.11.0",
31
- "@ossy/design-system": "^1.11.0",
32
- "@ossy/pages": "^1.11.0",
33
- "@ossy/router": "^1.11.0",
34
- "@ossy/router-react": "^1.11.0",
35
- "@ossy/sdk": "^1.11.0",
36
- "@ossy/sdk-react": "^1.11.0",
37
- "@ossy/themes": "^1.11.0",
30
+ "@ossy/connected-components": "^1.11.2",
31
+ "@ossy/design-system": "^1.11.2",
32
+ "@ossy/pages": "^1.11.2",
33
+ "@ossy/router": "^1.11.2",
34
+ "@ossy/router-react": "^1.11.2",
35
+ "@ossy/sdk": "^1.11.2",
36
+ "@ossy/sdk-react": "^1.11.2",
37
+ "@ossy/themes": "^1.11.2",
38
38
  "@rollup/plugin-alias": "^6.0.0",
39
39
  "@rollup/plugin-babel": "6.1.0",
40
40
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -67,5 +67,5 @@
67
67
  "README.md",
68
68
  "tsconfig.json"
69
69
  ],
70
- "gitHead": "a151faf7a9af599b885ea98137575978fa1dffae"
70
+ "gitHead": "3998c8b461e8b7317462c3d1d65b25b9454ba18a"
71
71
  }