@ossy/platform 3.13.0 → 3.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 -6
- package/src/runtime.js +15 -3
- package/src/server.js +13 -0
- package/src/test/flow-runner.js +31 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/platform",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "Ossy application server runtime",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,14 +48,15 @@
|
|
|
48
48
|
"@ossy/config": "^3.0.9",
|
|
49
49
|
"@ossy/event-store": "^3.12.0",
|
|
50
50
|
"@ossy/locale": "^3.4.0",
|
|
51
|
-
"@ossy/manifest": "^3.
|
|
51
|
+
"@ossy/manifest": "^3.15.0",
|
|
52
52
|
"@ossy/observability": "^3.0.9",
|
|
53
53
|
"@ossy/policies": "^3.0.9",
|
|
54
|
-
"@ossy/
|
|
54
|
+
"@ossy/router": "^3.0.9",
|
|
55
|
+
"@ossy/schema": "^3.15.0",
|
|
55
56
|
"@ossy/sdk": "^3.12.0",
|
|
56
57
|
"@ossy/tokens": "^3.11.1",
|
|
57
|
-
"@ossy/users": "^3.
|
|
58
|
-
"@ossy/workspaces": "^3.
|
|
58
|
+
"@ossy/users": "^3.15.0",
|
|
59
|
+
"@ossy/workspaces": "^3.15.0",
|
|
59
60
|
"cookie-parser": "^1.4.7",
|
|
60
61
|
"dotenv": ">=16.0.0 <18.0.0",
|
|
61
62
|
"express": ">=5.0.0 <6.0.0",
|
|
@@ -76,5 +77,5 @@
|
|
|
76
77
|
"Dockerfile",
|
|
77
78
|
"docker-healthcheck.js"
|
|
78
79
|
],
|
|
79
|
-
"gitHead": "
|
|
80
|
+
"gitHead": "23517232ddc4f2c64ca0062faa3cad84ceaa34a7"
|
|
80
81
|
}
|
package/src/runtime.js
CHANGED
|
@@ -180,14 +180,23 @@ export async function startRuntime ({ port } = {}) {
|
|
|
180
180
|
return
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
const config = manifest.config
|
|
184
|
+
const { language, messages, fallbackMessages } = resolveRequestLocale(pageRouter, requestUrl, buildDir, config)
|
|
185
|
+
if (typeof pageEntry.redirect === 'string' && pageEntry.redirect.trim()) {
|
|
186
|
+
const targetId = pageEntry.redirect.trim().replace(/^@/, '')
|
|
187
|
+
const targetPath = pageRouter.getPathname({ id: targetId, language })
|
|
188
|
+
if (targetPath) {
|
|
189
|
+
const search = new URL(requestUrl, 'http://localhost').search || ''
|
|
190
|
+
res.redirect(302, `${targetPath}${search}`)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
183
195
|
const mod = await loadEntry(pageEntry.entry)
|
|
184
196
|
if (typeof mod.render !== 'function') {
|
|
185
197
|
res.status(503).type('text').send('SSR runtime unavailable')
|
|
186
198
|
return
|
|
187
199
|
}
|
|
188
|
-
|
|
189
|
-
const config = manifest.config
|
|
190
|
-
const { language, messages, fallbackMessages } = resolveRequestLocale(pageRouter, requestUrl, buildDir, config)
|
|
191
200
|
const {
|
|
192
201
|
Layout,
|
|
193
202
|
layoutEntry,
|
|
@@ -212,6 +221,9 @@ export async function startRuntime ({ port } = {}) {
|
|
|
212
221
|
id: page.id,
|
|
213
222
|
path: page.path,
|
|
214
223
|
...(page.package ? { package: page.package } : {}),
|
|
224
|
+
...(typeof page.redirect === 'string' && page.redirect.trim()
|
|
225
|
+
? { redirect: page.redirect.trim() }
|
|
226
|
+
: {}),
|
|
215
227
|
})),
|
|
216
228
|
pageId: pageRoute.id,
|
|
217
229
|
componentEntries: manifest.components,
|
package/src/server.js
CHANGED
|
@@ -505,6 +505,16 @@ export async function startServer (options = {}) {
|
|
|
505
505
|
res.status(404).send('Not found')
|
|
506
506
|
return
|
|
507
507
|
}
|
|
508
|
+
if (typeof pageEntry.redirect === 'string' && pageEntry.redirect.trim()) {
|
|
509
|
+
const { language } = resolveRequestLocale(pageRouter, requestUrl, buildDir, config)
|
|
510
|
+
const targetId = pageEntry.redirect.trim().replace(/^@/, '')
|
|
511
|
+
const targetPath = pageRouter.getPathname({ id: targetId, language })
|
|
512
|
+
if (targetPath) {
|
|
513
|
+
const search = new URL(requestUrl, 'http://localhost').search || ''
|
|
514
|
+
res.redirect(302, `${targetPath}${search}`)
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
}
|
|
508
518
|
const loadEntryTimer = createTimedOperation(`SSR loadEntry ${pageRoute.id}`, { log })
|
|
509
519
|
const mod = await loadEntry(pageEntry.entry)
|
|
510
520
|
loadEntryTimer.finish()
|
|
@@ -575,6 +585,9 @@ export async function startServer (options = {}) {
|
|
|
575
585
|
id: page.id,
|
|
576
586
|
path: page.path,
|
|
577
587
|
...(page.package ? { package: page.package } : {}),
|
|
588
|
+
...(typeof page.redirect === 'string' && page.redirect.trim()
|
|
589
|
+
? { redirect: page.redirect.trim() }
|
|
590
|
+
: {}),
|
|
578
591
|
})),
|
|
579
592
|
pageId: pageRoute.id,
|
|
580
593
|
componentEntries: manifest.components,
|
package/src/test/flow-runner.js
CHANGED
|
@@ -328,23 +328,49 @@ export async function runFlow (flow, options = {}) {
|
|
|
328
328
|
if (value === undefined || value === null) {
|
|
329
329
|
throw new Error(`mockFormContent produced no value for field "${field.name}"`)
|
|
330
330
|
}
|
|
331
|
-
const
|
|
332
|
-
|
|
331
|
+
const asText = String(value)
|
|
332
|
+
const nameAttr = escapeCssAttrValue(field.name)
|
|
333
|
+
const locator = formRoot.locator(`[name="${nameAttr}"]`).first()
|
|
334
|
+
// Overlay Select uses a hidden input; wait for attach, not visibility.
|
|
335
|
+
await locator.waitFor({ state: 'attached', timeout: formTimeout })
|
|
333
336
|
const tag = await locator.evaluate(el => el.tagName.toLowerCase()).catch(() => 'input')
|
|
334
337
|
if (tag === 'select') {
|
|
335
|
-
await locator.
|
|
338
|
+
await locator.waitFor({ state: 'visible', timeout: formTimeout })
|
|
339
|
+
await locator.selectOption(asText)
|
|
336
340
|
return
|
|
337
341
|
}
|
|
338
342
|
if (tag === 'input') {
|
|
339
343
|
const inputType = await locator.getAttribute('type')
|
|
340
344
|
if (inputType === 'checkbox') {
|
|
345
|
+
await locator.waitFor({ state: 'visible', timeout: formTimeout })
|
|
341
346
|
if (value) await locator.check()
|
|
342
347
|
else await locator.uncheck()
|
|
343
348
|
return
|
|
344
349
|
}
|
|
350
|
+
// Design-system Select: hidden input + Dropdown / ContextMenu options.
|
|
351
|
+
const inOverlaySelect = await locator.evaluate(
|
|
352
|
+
(el) => !!el.closest?.('[data-select]'),
|
|
353
|
+
).catch(() => false)
|
|
354
|
+
if (inputType === 'hidden' && (inOverlaySelect || field.type === 'select' || field.type === 'month')) {
|
|
355
|
+
const current = await locator.inputValue().catch(() => '')
|
|
356
|
+
if (current === asText) return
|
|
357
|
+
const trigger = formRoot
|
|
358
|
+
.locator(`[data-select]:has([name="${nameAttr}"]) [data-select-trigger]`)
|
|
359
|
+
.first()
|
|
360
|
+
await trigger.waitFor({ state: 'visible', timeout: formTimeout })
|
|
361
|
+
await trigger.click()
|
|
362
|
+
// Menu is portaled via Overlay — scope to the page, not the form.
|
|
363
|
+
const option = page
|
|
364
|
+
.locator(`[data-select-menu] [role="option"][data-value="${escapeCssAttrValue(asText)}"]`)
|
|
365
|
+
.first()
|
|
366
|
+
await option.waitFor({ state: 'visible', timeout: formTimeout })
|
|
367
|
+
await option.click()
|
|
368
|
+
await expectFn(locator).toHaveValue(asText, { timeout: 2000 })
|
|
369
|
+
return
|
|
370
|
+
}
|
|
345
371
|
}
|
|
372
|
+
await locator.waitFor({ state: 'visible', timeout: formTimeout })
|
|
346
373
|
// Retry fills: early hydration / sibling controlled updates can wipe earlier fields.
|
|
347
|
-
const asText = String(value)
|
|
348
374
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
349
375
|
await locator.fill(asText)
|
|
350
376
|
// Blur so React controlled state commits before the next field fill.
|
|
@@ -381,6 +407,7 @@ export async function runFlow (flow, options = {}) {
|
|
|
381
407
|
if (tag === 'input') {
|
|
382
408
|
const inputType = await locator.getAttribute('type')
|
|
383
409
|
if (inputType === 'checkbox' || inputType === 'file') continue
|
|
410
|
+
// Overlay Select keeps value on a hidden input — still verify/repair it.
|
|
384
411
|
}
|
|
385
412
|
const asText = String(value)
|
|
386
413
|
const current = await locator.inputValue().catch(() => '')
|