@ossy/app 1.36.1 → 1.37.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/cli/build.task.js +30 -2
- package/cli/get-platform-files.task.js +12 -7
- package/cli/manifest-plugin.js +44 -1
- package/package.json +9 -9
package/cli/build.task.js
CHANGED
|
@@ -20,10 +20,11 @@ import getPlatformFiles, {
|
|
|
20
20
|
STARTUP_FILE_PATTERN,
|
|
21
21
|
EMAIL_FILE_PATTERN,
|
|
22
22
|
ACTION_FILE_PATTERN,
|
|
23
|
+
E2E_FILE_PATTERN,
|
|
23
24
|
} from './get-platform-files.task.js'
|
|
24
25
|
import { manifestPlugin } from './manifest-plugin.js'
|
|
25
26
|
|
|
26
|
-
export { PAGE_FILE_PATTERN, API_FILE_PATTERN, TASK_FILE_PATTERN, RESOURCE_FILE_PATTERN, COMPONENT_FILE_PATTERN, AGGREGATE_FILE_PATTERN, INTEGRATION_FILE_PATTERN, STARTUP_FILE_PATTERN, EMAIL_FILE_PATTERN, ACTION_FILE_PATTERN }
|
|
27
|
+
export { PAGE_FILE_PATTERN, API_FILE_PATTERN, TASK_FILE_PATTERN, RESOURCE_FILE_PATTERN, COMPONENT_FILE_PATTERN, AGGREGATE_FILE_PATTERN, INTEGRATION_FILE_PATTERN, STARTUP_FILE_PATTERN, EMAIL_FILE_PATTERN, ACTION_FILE_PATTERN, E2E_FILE_PATTERN }
|
|
27
28
|
|
|
28
29
|
// Generated entry stubs live under `build/.ossy/entries/`. Putting them
|
|
29
30
|
// inside `build/` means they're cleaned automatically with every build,
|
|
@@ -205,6 +206,18 @@ function generateActionStub ({ stubAbs, sourceAbs }) {
|
|
|
205
206
|
].join('\n')
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
// E2e tests are Playwright test modules. The stub re-exports `id`, `feature`,
|
|
210
|
+
// `requires`, and the default test function so the manifest plugin can validate
|
|
211
|
+
// them and the e2e runner can import and execute them.
|
|
212
|
+
function generateE2eStub ({ stubAbs, sourceAbs }) {
|
|
213
|
+
const importPath = relImport(stubAbs, sourceAbs)
|
|
214
|
+
return [
|
|
215
|
+
'// Generated by @ossy/app — do not edit',
|
|
216
|
+
`export { id, feature, requires, default } from '${importPath}'`,
|
|
217
|
+
'',
|
|
218
|
+
].join('\n')
|
|
219
|
+
}
|
|
220
|
+
|
|
208
221
|
function stubFor (kind, args) {
|
|
209
222
|
if (kind === 'page') return generatePageStub(args)
|
|
210
223
|
if (kind === 'api') return generateApiStub(args)
|
|
@@ -215,6 +228,7 @@ function stubFor (kind, args) {
|
|
|
215
228
|
if (kind === 'startup') return generateStartupStub(args)
|
|
216
229
|
if (kind === 'email') return generateEmailStub(args)
|
|
217
230
|
if (kind === 'action') return generateActionStub(args)
|
|
231
|
+
if (kind === 'e2e') return generateE2eStub(args)
|
|
218
232
|
return generateTaskStub(args)
|
|
219
233
|
}
|
|
220
234
|
|
|
@@ -261,6 +275,7 @@ export async function build (cliArgs = []) {
|
|
|
261
275
|
...platformFiles.startups,
|
|
262
276
|
...platformFiles.emails,
|
|
263
277
|
...platformFiles.actions,
|
|
278
|
+
...platformFiles.e2es,
|
|
264
279
|
]
|
|
265
280
|
|
|
266
281
|
const entriesByStub = new Map()
|
|
@@ -295,7 +310,20 @@ export async function build (cliArgs = []) {
|
|
|
295
310
|
// bundled task files at build time — bundling sharp would trigger its binary
|
|
296
311
|
// loader during the build, which fails on linux-arm64 builders that don't
|
|
297
312
|
// have the darwin binaries installed.
|
|
298
|
-
|
|
313
|
+
// Singleton packages must resolve to the already-loaded module in the
|
|
314
|
+
// Node.js module cache at runtime, not to a bundled copy with its own
|
|
315
|
+
// empty Map/state. Keeping them external means dynamic `import()`s of
|
|
316
|
+
// *.api.js / *.action.js bundles share the same ActionService instance
|
|
317
|
+
// that the server registered actions into at startup.
|
|
318
|
+
external: (id) => {
|
|
319
|
+
if (id.startsWith('node:') || NODE_BUILTINS.has(id)) return true
|
|
320
|
+
if (id === 'sharp' || id.startsWith('@img/sharp-')) return true
|
|
321
|
+
// Singleton packages — must not be bundled into entry chunks
|
|
322
|
+
if (id === '@ossy/platform' || id.startsWith('@ossy/platform/')) return true
|
|
323
|
+
if (id === '@ossy/event-store' || id.startsWith('@ossy/event-store/')) return true
|
|
324
|
+
if (id === '@ossy/observability' || id.startsWith('@ossy/observability/')) return true
|
|
325
|
+
return false
|
|
326
|
+
},
|
|
299
327
|
plugins: [
|
|
300
328
|
replace({
|
|
301
329
|
preventAssignment: true,
|
|
@@ -11,9 +11,10 @@ export const INTEGRATION_FILE_PATTERN = /\.integration\.(mjs|cjs|js)$/
|
|
|
11
11
|
export const STARTUP_FILE_PATTERN = /\.startup\.(mjs|cjs|js)$/
|
|
12
12
|
export const EMAIL_FILE_PATTERN = /\.email\.(jsx?|tsx?)$/
|
|
13
13
|
export const ACTION_FILE_PATTERN = /\.action\.(mjs|cjs|js)$/
|
|
14
|
+
export const E2E_FILE_PATTERN = /\.e2e\.(mjs|cjs|js)$/
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* @typedef {'page' | 'api' | 'task' | 'resource' | 'component' | 'aggregate' | 'integration' | 'startup' | 'email' | 'action'} EntryKind
|
|
17
|
+
* @typedef {'page' | 'api' | 'task' | 'resource' | 'component' | 'aggregate' | 'integration' | 'startup' | 'email' | 'action' | 'e2e'} EntryKind
|
|
17
18
|
*
|
|
18
19
|
* @typedef {object} PlatformEntry
|
|
19
20
|
* @property {EntryKind} kind Which bucket this entry lives in.
|
|
@@ -32,6 +33,7 @@ export const ACTION_FILE_PATTERN = /\.action\.(mjs|cjs|js)$/
|
|
|
32
33
|
* @property {PlatformEntry[]} startups Discovered `*.startup.{js,mjs,cjs}` entries.
|
|
33
34
|
* @property {PlatformEntry[]} emails Discovered `*.email.{jsx,tsx,...}` entries.
|
|
34
35
|
* @property {PlatformEntry[]} actions Discovered `*.action.{js,mjs,cjs}` entries.
|
|
36
|
+
* @property {PlatformEntry[]} e2es Discovered `*.e2e.{js,mjs,cjs}` entries (installed packages only).
|
|
35
37
|
*/
|
|
36
38
|
export function discoverFilesByPattern (srcDir, filePattern) {
|
|
37
39
|
const dir = path.resolve(srcDir)
|
|
@@ -60,6 +62,7 @@ function classifyFile (absPath) {
|
|
|
60
62
|
if (STARTUP_FILE_PATTERN.test(base)) return 'startup'
|
|
61
63
|
if (EMAIL_FILE_PATTERN.test(base)) return 'email'
|
|
62
64
|
if (ACTION_FILE_PATTERN.test(base)) return 'action'
|
|
65
|
+
if (E2E_FILE_PATTERN.test(base)) return 'e2e'
|
|
63
66
|
return null
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -134,6 +137,7 @@ export function discoverInstalledPackageEntries (projectRoot) {
|
|
|
134
137
|
...discoverFilesByPattern(packageSrcDir, STARTUP_FILE_PATTERN),
|
|
135
138
|
...discoverFilesByPattern(packageSrcDir, EMAIL_FILE_PATTERN),
|
|
136
139
|
...discoverFilesByPattern(packageSrcDir, ACTION_FILE_PATTERN),
|
|
140
|
+
...discoverFilesByPattern(packageSrcDir, E2E_FILE_PATTERN),
|
|
137
141
|
]
|
|
138
142
|
for (const sourcePath of files) {
|
|
139
143
|
const stat = fs.statSync(sourcePath)
|
|
@@ -177,12 +181,13 @@ export function discoverInstalledPackageEntries (projectRoot) {
|
|
|
177
181
|
* @returns {Promise<PlatformFiles>}
|
|
178
182
|
*/
|
|
179
183
|
export default async function getPlatformFiles (srcDir) {
|
|
180
|
-
const out = { pages: [], apis: [], tasks: [], resources: [], components: [], aggregates: [], integrations: [], startups: [], emails: [], actions: [] }
|
|
181
|
-
const bucketByKind = { page: 'pages', api: 'apis', task: 'tasks', resource: 'resources', component: 'components', aggregate: 'aggregates', integration: 'integrations', startup: 'startups', email: 'emails', action: 'actions' }
|
|
184
|
+
const out = { pages: [], apis: [], tasks: [], resources: [], components: [], aggregates: [], integrations: [], startups: [], emails: [], actions: [], e2es: [] }
|
|
185
|
+
const bucketByKind = { page: 'pages', api: 'apis', task: 'tasks', resource: 'resources', component: 'components', aggregate: 'aggregates', integration: 'integrations', startup: 'startups', email: 'emails', action: 'actions', e2e: 'e2es' }
|
|
182
186
|
|
|
183
|
-
// Aggregates are only discovered from installed packages — never
|
|
184
|
-
// local `src/` — to avoid pulling in per-project duplicates of the
|
|
185
|
-
// aggregate class that is canonically owned by the feature
|
|
187
|
+
// Aggregates and e2e tests are only discovered from installed packages — never
|
|
188
|
+
// from the local `src/` — to avoid pulling in per-project duplicates of the
|
|
189
|
+
// same aggregate class or test suite that is canonically owned by the feature
|
|
190
|
+
// package.
|
|
186
191
|
const localFiles = [
|
|
187
192
|
...discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN),
|
|
188
193
|
...discoverFilesByPattern(srcDir, API_FILE_PATTERN),
|
|
@@ -198,7 +203,7 @@ export default async function getPlatformFiles (srcDir) {
|
|
|
198
203
|
const stat = fs.statSync(sourcePath)
|
|
199
204
|
if (!stat.isFile() || stat.size === 0) continue
|
|
200
205
|
const kind = classifyFile(sourcePath)
|
|
201
|
-
if (!kind || kind === 'aggregate') continue
|
|
206
|
+
if (!kind || kind === 'aggregate' || kind === 'e2e') continue
|
|
202
207
|
out[bucketByKind[kind]].push({ kind, sourcePath })
|
|
203
208
|
}
|
|
204
209
|
|
package/cli/manifest-plugin.js
CHANGED
|
@@ -64,6 +64,12 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
64
64
|
* @property {string} entry URL the platform serves the action bundle from.
|
|
65
65
|
* @property {string} access Access level: `'public'` | `'authenticated'` | `'workspace'`.
|
|
66
66
|
*
|
|
67
|
+
* @typedef {object} E2eManifestEntry
|
|
68
|
+
* @property {string} id Unique test id (e.g. `'authentication/sign-in'`).
|
|
69
|
+
* @property {string} feature Grouping label (e.g. `'authentication'`).
|
|
70
|
+
* @property {string[]} requires Runtime dependencies needed (e.g. `['server', 'database']`).
|
|
71
|
+
* @property {string} entry URL the platform serves the e2e bundle from.
|
|
72
|
+
*
|
|
67
73
|
* @typedef {object} Manifest
|
|
68
74
|
* @property {ManifestEntry[]} entries Flat, ordered list of every routable thing.
|
|
69
75
|
* @property {ComponentManifestEntry[]} components Discovered `*.component.*` entries, keyed by id.
|
|
@@ -73,6 +79,7 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
73
79
|
* @property {StartupManifestEntry[]} startups Discovered `*.startup.js` entries.
|
|
74
80
|
* @property {EmailManifestEntry[]} emails Discovered `*.email.{jsx,tsx}` entries.
|
|
75
81
|
* @property {ActionManifestEntry[]} actions Discovered `*.action.js` entries.
|
|
82
|
+
* @property {E2eManifestEntry[]} e2es Discovered `*.e2e.js` entries from installed packages.
|
|
76
83
|
* @property {object} config Inlined `src/config.js` default export.
|
|
77
84
|
*/
|
|
78
85
|
|
|
@@ -137,6 +144,9 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
137
144
|
const seenStartupIds = new Set()
|
|
138
145
|
const seenEmailIds = new Set()
|
|
139
146
|
const seenActionIds = new Set()
|
|
147
|
+
/** @type {E2eManifestEntry[]} */
|
|
148
|
+
const e2es = []
|
|
149
|
+
const seenE2eIds = new Set()
|
|
140
150
|
|
|
141
151
|
for (const fileName of Object.keys(bundle)) {
|
|
142
152
|
const chunk = bundle[fileName]
|
|
@@ -286,6 +296,39 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
286
296
|
continue
|
|
287
297
|
}
|
|
288
298
|
|
|
299
|
+
// E2e tests contain Playwright test logic. The bundle exports `id`
|
|
300
|
+
// (a unique namespaced slug), `feature` (grouping label), `requires`
|
|
301
|
+
// (dependency list), and a default export (the async test function).
|
|
302
|
+
// They don't produce routable entries; they accumulate into a separate
|
|
303
|
+
// `e2es` array so test runners can discover and execute them.
|
|
304
|
+
if (entryInfo.kind === 'e2e') {
|
|
305
|
+
const e2eId = mod && mod.id
|
|
306
|
+
const feature = mod && mod.feature
|
|
307
|
+
const requires = mod && mod.requires
|
|
308
|
+
const testFn = mod && mod.default
|
|
309
|
+
if (typeof e2eId !== 'string' || e2eId.trim() === '') {
|
|
310
|
+
this.error(
|
|
311
|
+
`[@ossy/app][build] e2e entry ${entryInfo.sourcePath} must export a non-empty string "id"`,
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
if (typeof testFn !== 'function') {
|
|
315
|
+
this.error(
|
|
316
|
+
`[@ossy/app][build] e2e entry ${entryInfo.sourcePath} must default-export a function`,
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
if (seenE2eIds.has(e2eId)) {
|
|
320
|
+
this.error(`[@ossy/app][build] Duplicate e2e id "${e2eId}"`)
|
|
321
|
+
}
|
|
322
|
+
seenE2eIds.add(e2eId)
|
|
323
|
+
e2es.push({
|
|
324
|
+
id: e2eId,
|
|
325
|
+
feature: typeof feature === 'string' ? feature : '',
|
|
326
|
+
requires: Array.isArray(requires) ? requires : [],
|
|
327
|
+
entry: url,
|
|
328
|
+
})
|
|
329
|
+
continue
|
|
330
|
+
}
|
|
331
|
+
|
|
289
332
|
// Resources are pure data — the default export *is* the template.
|
|
290
333
|
// They don't use `metadata`, don't need a derived id, and don't
|
|
291
334
|
// produce a routable manifest entry; they accumulate into a separate
|
|
@@ -347,7 +390,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
347
390
|
}
|
|
348
391
|
|
|
349
392
|
/** @type {Manifest} */
|
|
350
|
-
const manifest = { entries, components, resourceTemplates, aggregates, integrations, startups, emails, actions, config: configValue || {} }
|
|
393
|
+
const manifest = { entries, components, resourceTemplates, aggregates, integrations, startups, emails, actions, e2es, config: configValue || {} }
|
|
351
394
|
fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
|
|
352
395
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
|
|
353
396
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"@babel/eslint-parser": "^7.15.8",
|
|
39
39
|
"@babel/preset-react": "^7.26.3",
|
|
40
40
|
"@babel/register": "^7.25.9",
|
|
41
|
-
"@ossy/design-system": "^1.
|
|
41
|
+
"@ossy/design-system": "^1.37.1",
|
|
42
42
|
"@ossy/pages": "^1.23.0",
|
|
43
|
-
"@ossy/platform": "^1.
|
|
44
|
-
"@ossy/router": "^1.
|
|
45
|
-
"@ossy/router-react": "^1.
|
|
46
|
-
"@ossy/sdk": "^1.
|
|
47
|
-
"@ossy/sdk-react": "^1.
|
|
48
|
-
"@ossy/themes": "^1.
|
|
43
|
+
"@ossy/platform": "^1.36.1",
|
|
44
|
+
"@ossy/router": "^1.37.1",
|
|
45
|
+
"@ossy/router-react": "^1.37.1",
|
|
46
|
+
"@ossy/sdk": "^1.37.1",
|
|
47
|
+
"@ossy/sdk-react": "^1.37.1",
|
|
48
|
+
"@ossy/themes": "^1.37.1",
|
|
49
49
|
"@rollup/plugin-alias": "^6.0.0",
|
|
50
50
|
"@rollup/plugin-babel": "^7.0.0",
|
|
51
51
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"README.md",
|
|
80
80
|
"tsconfig.json"
|
|
81
81
|
],
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "d5b71d16ec56c819401c7a74f95d12b2347ca3c4"
|
|
83
83
|
}
|