@ossy/app 1.39.6 → 1.40.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/README.md +13 -10
- package/cli/build.task.js +28 -12
- package/cli/discover-translation-files.js +139 -0
- package/cli/get-platform-files.task.js +1 -0
- package/cli/manifest-plugin.js +81 -26
- package/cli/merge-translations.task.js +170 -0
- package/package.json +15 -10
- package/runtime/page-runtime.js +53 -8
- package/runtime/resolve-shell-slots.js +112 -0
- package/src/manifest/build-manifest-summary.js +115 -0
- package/src/manifest/discover-package-definitions.js +100 -0
- package/src/manifest/serialize-package-definition.js +30 -0
- package/src/shell/App.jsx +22 -14
- package/src/shell/AppSettings.jsx +2 -0
- package/src/shell/DevPagesPanel.jsx +128 -0
- package/src/shell/ThemeEditor.jsx +36 -65
- package/src/shell/devPagesUtils.js +53 -0
package/README.md
CHANGED
|
@@ -113,22 +113,25 @@ Build output: `build/.ossy/tasks.generated.json` — a registry of `[{ type, mod
|
|
|
113
113
|
|
|
114
114
|
## Components (`*.component.jsx`)
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
Register injectable UI via `*.component.jsx` in `src/` or feature packages. Components are bundled separately and resolved into slots at runtime.
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
// src/workspace-users.component.jsx
|
|
120
|
-
export const metadata = {
|
|
121
|
-
id: 'workspace/users', // rendered when pageId === 'workspace/users'
|
|
122
|
-
}
|
|
118
|
+
**Shell chrome** — the app assigns placement in `*.layout.jsx`:
|
|
123
119
|
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
```jsx
|
|
121
|
+
export const slots = {
|
|
122
|
+
'shell:header': 'app-header',
|
|
123
|
+
'shell:sidebar': 'app-sidebar',
|
|
126
124
|
}
|
|
127
125
|
```
|
|
128
126
|
|
|
129
|
-
|
|
127
|
+
**Resource / input UI** — feature packages use `metadata.id` as the slot key:
|
|
128
|
+
|
|
129
|
+
```jsx
|
|
130
|
+
export const metadata = { id: 'resource:booking/service/form' }
|
|
131
|
+
export default function ServiceForm() { … }
|
|
132
|
+
```
|
|
130
133
|
|
|
131
|
-
See [`docs/component-primitive.md`](./docs/component-primitive.md)
|
|
134
|
+
See [`docs/component-primitive.md`](./docs/component-primitive.md) and [design-system/docs/SLOTS.md](../design-system/docs/SLOTS.md).
|
|
132
135
|
|
|
133
136
|
## Port configuration
|
|
134
137
|
|
package/cli/build.task.js
CHANGED
|
@@ -22,10 +22,12 @@ import getPlatformFiles, {
|
|
|
22
22
|
ACTION_FILE_PATTERN,
|
|
23
23
|
E2E_FILE_PATTERN,
|
|
24
24
|
LAYOUT_FILE_PATTERN,
|
|
25
|
+
TRANSLATIONS_FILE_PATTERN,
|
|
25
26
|
} from './get-platform-files.task.js'
|
|
26
27
|
import { manifestPlugin } from './manifest-plugin.js'
|
|
28
|
+
import { mergeAndEmitTranslations } from './merge-translations.task.js'
|
|
27
29
|
|
|
28
|
-
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, LAYOUT_FILE_PATTERN }
|
|
30
|
+
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, LAYOUT_FILE_PATTERN, TRANSLATIONS_FILE_PATTERN }
|
|
29
31
|
|
|
30
32
|
// Generated entry stubs live under `build/.ossy/entries/`. Putting them
|
|
31
33
|
// inside `build/` means they're cleaned automatically with every build,
|
|
@@ -137,14 +139,8 @@ function generateResourceStub ({ stubAbs, sourceAbs }) {
|
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
// Components are re-exported as-is — the manifest plugin reads `metadata.id`
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// The `slot` export (when present) identifies the named shell slot this
|
|
143
|
-
// component fills automatically — see PlatformShell for the slot registry.
|
|
144
|
-
// We use `export *` for named exports rather than an explicit list because
|
|
145
|
-
// `slot` is optional: some components don't export it, and a static named
|
|
146
|
-
// re-export of a binding that doesn't exist causes a Rollup MISSING_EXPORT
|
|
147
|
-
// error at build time.
|
|
142
|
+
// from the bundled output. Shell placement is app-controlled via `export const slots`
|
|
143
|
+
// in `*.layout.jsx`, not `export const slot` on components.
|
|
148
144
|
function generateComponentStub ({ stubAbs, sourceAbs }) {
|
|
149
145
|
const importPath = relImport(stubAbs, sourceAbs)
|
|
150
146
|
return [
|
|
@@ -191,13 +187,13 @@ function generateStartupStub ({ stubAbs, sourceAbs }) {
|
|
|
191
187
|
].join('\n')
|
|
192
188
|
}
|
|
193
189
|
|
|
194
|
-
// Emails are React components. The stub re-exports `id
|
|
195
|
-
//
|
|
190
|
+
// Emails are React components. The stub re-exports `id` and the default component.
|
|
191
|
+
// Optional `subject` may be resolved from translations at send time instead.
|
|
196
192
|
function generateEmailStub ({ stubAbs, sourceAbs }) {
|
|
197
193
|
const importPath = relImport(stubAbs, sourceAbs)
|
|
198
194
|
return [
|
|
199
195
|
'// Generated by @ossy/app — do not edit',
|
|
200
|
-
`export { id,
|
|
196
|
+
`export { id, default } from '${importPath}'`,
|
|
201
197
|
'',
|
|
202
198
|
].join('\n')
|
|
203
199
|
}
|
|
@@ -261,6 +257,15 @@ async function loadConfig (configPath) {
|
|
|
261
257
|
return mod.default || {}
|
|
262
258
|
}
|
|
263
259
|
|
|
260
|
+
function readAppPackageName (cwd) {
|
|
261
|
+
try {
|
|
262
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'))
|
|
263
|
+
return typeof pkgJson.name === 'string' && pkgJson.name.trim() ? pkgJson.name : '@ossy/app'
|
|
264
|
+
} catch {
|
|
265
|
+
return '@ossy/app'
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
264
269
|
export async function build (cliArgs = []) {
|
|
265
270
|
arg(
|
|
266
271
|
{
|
|
@@ -317,6 +322,7 @@ export async function build (cliArgs = []) {
|
|
|
317
322
|
|
|
318
323
|
const configPath = path.resolve(srcDir, 'config.js')
|
|
319
324
|
const configValue = await loadConfig(configPath)
|
|
325
|
+
const appPackageName = readAppPackageName(cwd)
|
|
320
326
|
|
|
321
327
|
const middlewareSrc = path.resolve(srcDir, 'middleware.js')
|
|
322
328
|
if (fs.existsSync(middlewareSrc)) {
|
|
@@ -374,7 +380,9 @@ export async function build (cliArgs = []) {
|
|
|
374
380
|
srcDir,
|
|
375
381
|
staticOutDir,
|
|
376
382
|
configValue,
|
|
383
|
+
appPackageName,
|
|
377
384
|
manifestPath: path.join(buildPath, 'manifest.json'),
|
|
385
|
+
projectRoot: cwd,
|
|
378
386
|
}),
|
|
379
387
|
],
|
|
380
388
|
})
|
|
@@ -393,4 +401,12 @@ export async function build (cliArgs = []) {
|
|
|
393
401
|
fs.cpSync(publicSrc, publicOutDir, { recursive: true, force: true })
|
|
394
402
|
}
|
|
395
403
|
|
|
404
|
+
mergeAndEmitTranslations({
|
|
405
|
+
srcDir,
|
|
406
|
+
projectRoot: cwd,
|
|
407
|
+
publicOutDir,
|
|
408
|
+
manifestPath: path.join(buildPath, 'manifest.json'),
|
|
409
|
+
configValue,
|
|
410
|
+
warn: (message) => console.warn(message),
|
|
411
|
+
})
|
|
396
412
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
discoverFilesByPattern,
|
|
6
|
+
TRANSLATIONS_FILE_PATTERN,
|
|
7
|
+
} from './get-platform-files.task.js'
|
|
8
|
+
import {
|
|
9
|
+
isValidLocaleCode,
|
|
10
|
+
parseLocaleFromTranslationFilename,
|
|
11
|
+
} from '@ossy/locale'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} projectRoot
|
|
15
|
+
* @returns {string[]}
|
|
16
|
+
*/
|
|
17
|
+
function collectNodeModulesDirs (projectRoot) {
|
|
18
|
+
const dirs = []
|
|
19
|
+
let current = projectRoot
|
|
20
|
+
while (true) {
|
|
21
|
+
const nm = path.join(current, 'node_modules')
|
|
22
|
+
if (fs.existsSync(nm) && fs.statSync(nm).isDirectory()) dirs.push(nm)
|
|
23
|
+
const parent = path.dirname(current)
|
|
24
|
+
if (parent === current) break
|
|
25
|
+
current = parent
|
|
26
|
+
}
|
|
27
|
+
return dirs
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {import('@ossy/locale').TranslationSourceFile} TranslationSourceFile
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Discover `*.translations.json` from installed packages and app `src/`.
|
|
36
|
+
*
|
|
37
|
+
* @param {{ srcDir: string, projectRoot: string }} options
|
|
38
|
+
* @returns {TranslationSourceFile[]}
|
|
39
|
+
*/
|
|
40
|
+
export function discoverTranslationFiles ({ srcDir, projectRoot }) {
|
|
41
|
+
/** @type {TranslationSourceFile[]} */
|
|
42
|
+
const files = []
|
|
43
|
+
const seenPaths = new Set()
|
|
44
|
+
|
|
45
|
+
const nmDirs = collectNodeModulesDirs(projectRoot)
|
|
46
|
+
/** @type {Array<{ packageName: string, sourcePath: string }>} */
|
|
47
|
+
const packageFiles = []
|
|
48
|
+
|
|
49
|
+
const tryPackageDir = (pkgDir) => {
|
|
50
|
+
const real = fs.realpathSync(pkgDir)
|
|
51
|
+
const pkgJsonPath = path.join(real, 'package.json')
|
|
52
|
+
if (!fs.existsSync(pkgJsonPath)) return
|
|
53
|
+
|
|
54
|
+
let pkg
|
|
55
|
+
try {
|
|
56
|
+
pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))
|
|
57
|
+
} catch {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
if (!pkg.ossy?.src) return
|
|
61
|
+
|
|
62
|
+
const packageSrcDir = path.resolve(real, pkg.ossy.src)
|
|
63
|
+
const matches = discoverFilesByPattern(packageSrcDir, TRANSLATIONS_FILE_PATTERN)
|
|
64
|
+
for (const sourcePath of matches) {
|
|
65
|
+
const stat = fs.statSync(sourcePath)
|
|
66
|
+
if (!stat.isFile() || stat.size === 0) continue
|
|
67
|
+
packageFiles.push({ packageName: pkg.name, sourcePath })
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const nmDir of nmDirs) {
|
|
72
|
+
for (const entry of fs.readdirSync(nmDir, { withFileTypes: true })) {
|
|
73
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue
|
|
74
|
+
if (entry.name.startsWith('@')) {
|
|
75
|
+
const scopeDir = path.join(nmDir, entry.name)
|
|
76
|
+
if (!fs.existsSync(scopeDir)) continue
|
|
77
|
+
for (const scoped of fs.readdirSync(scopeDir, { withFileTypes: true })) {
|
|
78
|
+
if (scoped.isDirectory() || scoped.isSymbolicLink()) {
|
|
79
|
+
tryPackageDir(path.join(scopeDir, scoped.name))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
tryPackageDir(path.join(nmDir, entry.name))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
packageFiles.sort((a, b) => {
|
|
89
|
+
const byPackage = a.packageName.localeCompare(b.packageName)
|
|
90
|
+
if (byPackage !== 0) return byPackage
|
|
91
|
+
return a.sourcePath.localeCompare(b.sourcePath)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
for (const { packageName, sourcePath } of packageFiles) {
|
|
95
|
+
if (seenPaths.has(sourcePath)) continue
|
|
96
|
+
seenPaths.add(sourcePath)
|
|
97
|
+
const locale = parseLocaleFromTranslationFilename(path.basename(sourcePath))
|
|
98
|
+
if (!locale) continue
|
|
99
|
+
files.push({
|
|
100
|
+
locale,
|
|
101
|
+
sourcePath,
|
|
102
|
+
tier: 'package',
|
|
103
|
+
packageName,
|
|
104
|
+
sortKey: `${packageName}:${sourcePath}`,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const appFiles = discoverFilesByPattern(srcDir, TRANSLATIONS_FILE_PATTERN).sort()
|
|
109
|
+
for (const sourcePath of appFiles) {
|
|
110
|
+
if (seenPaths.has(sourcePath)) continue
|
|
111
|
+
seenPaths.add(sourcePath)
|
|
112
|
+
const stat = fs.statSync(sourcePath)
|
|
113
|
+
if (!stat.isFile() || stat.size === 0) continue
|
|
114
|
+
const locale = parseLocaleFromTranslationFilename(path.basename(sourcePath))
|
|
115
|
+
if (!locale) continue
|
|
116
|
+
files.push({
|
|
117
|
+
locale,
|
|
118
|
+
sourcePath,
|
|
119
|
+
tier: 'app',
|
|
120
|
+
sortKey: sourcePath,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return files
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {TranslationSourceFile[]} files
|
|
129
|
+
*/
|
|
130
|
+
export function validateTranslationLocales (files) {
|
|
131
|
+
for (const file of files) {
|
|
132
|
+
if (!isValidLocaleCode(file.locale)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`[@ossy/app][build] Invalid locale "${file.locale}" in ${file.sourcePath}. ` +
|
|
135
|
+
'Expected ISO 639-1 code (e.g. en, sv).',
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -13,6 +13,7 @@ export const EMAIL_FILE_PATTERN = /\.email\.(jsx?|tsx?)$/
|
|
|
13
13
|
export const ACTION_FILE_PATTERN = /\.action\.(mjs|cjs|js)$/
|
|
14
14
|
export const E2E_FILE_PATTERN = /\.e2e\.(mjs|cjs|js)$/
|
|
15
15
|
export const LAYOUT_FILE_PATTERN = /\.layout\.(jsx?|tsx?)$/
|
|
16
|
+
export const TRANSLATIONS_FILE_PATTERN = /\.translations\.json$/
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* @typedef {'page' | 'api' | 'task' | 'resource' | 'component' | 'aggregate' | 'integration' | 'startup' | 'email' | 'action' | 'e2e' | 'layout'} EntryKind
|
package/cli/manifest-plugin.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
import { pathToFileURL } from 'node:url'
|
|
4
4
|
|
|
5
5
|
import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.js'
|
|
6
|
+
import { discoverPackageDefinitions } from '../src/manifest/discover-package-definitions.js'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @typedef {import('./get-platform-files.task.js').PlatformEntry} PlatformEntry
|
|
@@ -24,8 +25,13 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
24
25
|
* The serializable config object (typically `src/config.js`'s default
|
|
25
26
|
* export). Inlined under `manifest.config` so the platform server doesn't
|
|
26
27
|
* need a runtime `import('./config.js')`.
|
|
28
|
+
* @property {string} appPackageName
|
|
29
|
+
* npm package name of the app being built (e.g. `@ossy/app-test`). Used as
|
|
30
|
+
* the `package` field on manifest entries that live in the app `src/` tree.
|
|
27
31
|
* @property {string} manifestPath
|
|
28
32
|
* Absolute path to write the manifest to (typically `build/manifest.json`).
|
|
33
|
+
* @property {string} projectRoot
|
|
34
|
+
* Absolute path to the consuming app root — used to discover package definitions.
|
|
29
35
|
*
|
|
30
36
|
* @typedef {'page' | 'api' | 'task'} ManifestEntryType
|
|
31
37
|
*
|
|
@@ -41,12 +47,6 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
41
47
|
* @typedef {object} ComponentManifestEntry
|
|
42
48
|
* @property {string} id Unique component id (from `metadata.id`).
|
|
43
49
|
* @property {string} entry URL the platform serves the component bundle from.
|
|
44
|
-
* @property {string} [slot] Named shell slot this component fills (from `export const slot`).
|
|
45
|
-
*
|
|
46
|
-
* @typedef {object} SlotManifestEntry
|
|
47
|
-
* @property {string} slot Named shell slot (e.g. `'header'`, `'sidebar'`, `'resource:booking/service/form'`).
|
|
48
|
-
* @property {string} entry URL the platform serves the component bundle from.
|
|
49
|
-
* @property {string} [package] npm package name that contributed this slot (from installed packages).
|
|
50
50
|
*
|
|
51
51
|
* @typedef {object} AggregateManifestEntry
|
|
52
52
|
* @property {string} id AggregateType string (e.g. `'User'`).
|
|
@@ -73,6 +73,8 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
73
73
|
* @typedef {object} LayoutManifestEntry
|
|
74
74
|
* @property {string} id Unique layout id (e.g. `'app-shell'`).
|
|
75
75
|
* @property {string} entry URL the platform serves the layout bundle from.
|
|
76
|
+
* @property {Record<string, string>} [slots]
|
|
77
|
+
* App-controlled slot map (`slotName` → component `metadata.id`), from `export const slots` in `*.layout.jsx`.
|
|
76
78
|
*
|
|
77
79
|
* @typedef {object} E2eManifestEntry
|
|
78
80
|
* @property {string} id Unique test id (e.g. `'authentication/sign-in'`).
|
|
@@ -83,7 +85,6 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
83
85
|
* @typedef {object} Manifest
|
|
84
86
|
* @property {ManifestEntry[]} entries Flat, ordered list of every routable thing.
|
|
85
87
|
* @property {ComponentManifestEntry[]} components Discovered `*.component.*` entries, keyed by id.
|
|
86
|
-
* @property {SlotManifestEntry[]} slots Components that declare `export const slot` — auto-injected into shell slots.
|
|
87
88
|
* @property {object[]} resourceTemplates Each `*.resource.js` file's default-exported template object.
|
|
88
89
|
* @property {AggregateManifestEntry[]} aggregates Discovered `*.aggregate.js` entries from installed packages.
|
|
89
90
|
* @property {IntegrationManifestEntry[]} integrations Discovered `*.integration.js` entries.
|
|
@@ -91,8 +92,9 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
91
92
|
* @property {EmailManifestEntry[]} emails Discovered `*.email.{jsx,tsx}` entries.
|
|
92
93
|
* @property {ActionManifestEntry[]} actions Discovered `*.action.js` entries.
|
|
93
94
|
* @property {E2eManifestEntry[]} e2es Discovered `*.e2e.js` entries from installed packages.
|
|
94
|
-
* @property {LayoutManifestEntry[]} layouts Discovered `*.layout.{jsx,tsx}` entries.
|
|
95
|
+
* @property {LayoutManifestEntry[]} layouts Discovered `*.layout.{jsx,tsx}` entries (includes app `slots` map).
|
|
95
96
|
* @property {object} config Inlined `src/config.js` default export.
|
|
97
|
+
* @property {Record<string, object>} [definitions] Package Definition metadata keyed by slug.
|
|
96
98
|
*/
|
|
97
99
|
|
|
98
100
|
/**
|
|
@@ -129,7 +131,11 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
129
131
|
* @param {ManifestPluginOptions} options
|
|
130
132
|
* @returns {import('rollup').Plugin}
|
|
131
133
|
*/
|
|
132
|
-
|
|
134
|
+
function entryPackage (entryInfo, appPackageName) {
|
|
135
|
+
return entryInfo.packageName || appPackageName
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configValue, appPackageName = '@ossy/app', manifestPath, projectRoot }) {
|
|
133
139
|
return {
|
|
134
140
|
name: 'ossy-manifest',
|
|
135
141
|
async writeBundle (_, bundle) {
|
|
@@ -156,8 +162,6 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
156
162
|
const seenStartupIds = new Set()
|
|
157
163
|
const seenEmailIds = new Set()
|
|
158
164
|
const seenActionIds = new Set()
|
|
159
|
-
/** @type {SlotManifestEntry[]} */
|
|
160
|
-
const slots = []
|
|
161
165
|
/** @type {E2eManifestEntry[]} */
|
|
162
166
|
const e2es = []
|
|
163
167
|
const seenE2eIds = new Set()
|
|
@@ -213,7 +217,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
213
217
|
this.error(`[@ossy/app][build] Duplicate integration id "${integrationId}"`)
|
|
214
218
|
}
|
|
215
219
|
seenIntegrationIds.add(integrationId)
|
|
216
|
-
integrations.push({ id: integrationId, entry: url, credentials })
|
|
220
|
+
integrations.push({ id: integrationId, entry: url, credentials, package: entryPackage(entryInfo, appPackageName) })
|
|
217
221
|
continue
|
|
218
222
|
}
|
|
219
223
|
|
|
@@ -236,7 +240,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
236
240
|
this.error(`[@ossy/app][build] Duplicate startup id "${startupId}"`)
|
|
237
241
|
}
|
|
238
242
|
seenStartupIds.add(startupId)
|
|
239
|
-
startups.push({ id: startupId, entry: url })
|
|
243
|
+
startups.push({ id: startupId, entry: url, package: entryPackage(entryInfo, appPackageName) })
|
|
240
244
|
continue
|
|
241
245
|
}
|
|
242
246
|
|
|
@@ -260,7 +264,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
260
264
|
this.error(`[@ossy/app][build] Duplicate email id "${emailId}"`)
|
|
261
265
|
}
|
|
262
266
|
seenEmailIds.add(emailId)
|
|
263
|
-
emails.push({ id: emailId, entry: url })
|
|
267
|
+
emails.push({ id: emailId, entry: url, package: entryPackage(entryInfo, appPackageName) })
|
|
264
268
|
continue
|
|
265
269
|
}
|
|
266
270
|
|
|
@@ -284,7 +288,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
284
288
|
}
|
|
285
289
|
const access = mod.access ?? 'authenticated'
|
|
286
290
|
seenActionIds.add(actionId)
|
|
287
|
-
actions.push({ id: actionId, entry: url, access })
|
|
291
|
+
actions.push({ id: actionId, entry: url, access, package: entryPackage(entryInfo, appPackageName) })
|
|
288
292
|
continue
|
|
289
293
|
}
|
|
290
294
|
|
|
@@ -308,7 +312,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
308
312
|
this.error(`[@ossy/app][build] Duplicate aggregate id "${aggregateId}"`)
|
|
309
313
|
}
|
|
310
314
|
seenAggregateIds.add(aggregateId)
|
|
311
|
-
aggregates.push({ id: aggregateId, entry: url })
|
|
315
|
+
aggregates.push({ id: aggregateId, entry: url, package: entryPackage(entryInfo, appPackageName) })
|
|
312
316
|
continue
|
|
313
317
|
}
|
|
314
318
|
|
|
@@ -341,6 +345,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
341
345
|
feature: typeof feature === 'string' ? feature : '',
|
|
342
346
|
requires: Array.isArray(requires) ? requires : [],
|
|
343
347
|
entry: url,
|
|
348
|
+
package: entryPackage(entryInfo, appPackageName),
|
|
344
349
|
})
|
|
345
350
|
continue
|
|
346
351
|
}
|
|
@@ -366,7 +371,38 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
366
371
|
this.error(`[@ossy/app][build] Duplicate layout id "${layoutId}"`)
|
|
367
372
|
}
|
|
368
373
|
seenLayoutIds.add(layoutId)
|
|
369
|
-
|
|
374
|
+
/** @type {Record<string, string> | undefined} */
|
|
375
|
+
let layoutSlots
|
|
376
|
+
const rawSlots = mod.slots
|
|
377
|
+
if (rawSlots != null) {
|
|
378
|
+
if (typeof rawSlots !== 'object' || Array.isArray(rawSlots)) {
|
|
379
|
+
this.error(
|
|
380
|
+
`[@ossy/app][build] layout entry ${entryInfo.sourcePath} must export \`slots\` as a plain object (slot name → component id)`,
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
layoutSlots = {}
|
|
384
|
+
for (const [slotKey, componentId] of Object.entries(rawSlots)) {
|
|
385
|
+
if (typeof slotKey !== 'string' || typeof componentId !== 'string') {
|
|
386
|
+
this.error(
|
|
387
|
+
`[@ossy/app][build] layout slots in ${entryInfo.sourcePath} must map string slot names to string component ids`,
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
const sk = slotKey.trim()
|
|
391
|
+
const cid = componentId.trim()
|
|
392
|
+
if (!sk || !cid) {
|
|
393
|
+
this.error(
|
|
394
|
+
`[@ossy/app][build] layout slots in ${entryInfo.sourcePath} must use non-empty slot names and component ids`,
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
layoutSlots[sk] = cid
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
layouts.push({
|
|
401
|
+
id: layoutId,
|
|
402
|
+
entry: url,
|
|
403
|
+
package: entryPackage(entryInfo, appPackageName),
|
|
404
|
+
...(layoutSlots && Object.keys(layoutSlots).length > 0 ? { slots: layoutSlots } : {}),
|
|
405
|
+
})
|
|
370
406
|
continue
|
|
371
407
|
}
|
|
372
408
|
|
|
@@ -390,7 +426,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
390
426
|
this.error(`[@ossy/app][build] Duplicate resource template id "${template.id}"`)
|
|
391
427
|
}
|
|
392
428
|
seenResourceIds.add(template.id)
|
|
393
|
-
resourceTemplates.push(template)
|
|
429
|
+
resourceTemplates.push({ ...template, package: entryPackage(entryInfo, appPackageName) })
|
|
394
430
|
continue
|
|
395
431
|
}
|
|
396
432
|
|
|
@@ -405,12 +441,13 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
405
441
|
seenIds[entryInfo.kind].add(id)
|
|
406
442
|
|
|
407
443
|
if (entryInfo.kind === 'component') {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
444
|
+
if (mod.slot != null) {
|
|
445
|
+
this.error(
|
|
446
|
+
`[@ossy/app][build] component ${entryInfo.sourcePath} must not export \`slot\`. ` +
|
|
447
|
+
'Assign components to shell slots in `*.layout.jsx` via `export const slots`.',
|
|
448
|
+
)
|
|
413
449
|
}
|
|
450
|
+
components.push({ id, entry: url, package: entryPackage(entryInfo, appPackageName) })
|
|
414
451
|
continue
|
|
415
452
|
}
|
|
416
453
|
|
|
@@ -422,6 +459,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
422
459
|
path: pagePath,
|
|
423
460
|
title: rawMeta.title,
|
|
424
461
|
entry: url,
|
|
462
|
+
package: entryPackage(entryInfo, appPackageName),
|
|
425
463
|
})
|
|
426
464
|
} else if (entryInfo.kind === 'api') {
|
|
427
465
|
// APIs have no sensible default route (unlike pages where we can
|
|
@@ -435,9 +473,9 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
435
473
|
`Add a \`path\` to the metadata or default-export object (e.g. \`path: '/api/${id}'\`).`,
|
|
436
474
|
)
|
|
437
475
|
}
|
|
438
|
-
entries.push({ type: 'api', id, path: rawMeta.path, entry: url })
|
|
476
|
+
entries.push({ type: 'api', id, path: rawMeta.path, entry: url, package: entryPackage(entryInfo, appPackageName) })
|
|
439
477
|
} else {
|
|
440
|
-
entries.push({ type: 'task', id, entry: url })
|
|
478
|
+
entries.push({ type: 'task', id, entry: url, package: entryPackage(entryInfo, appPackageName) })
|
|
441
479
|
}
|
|
442
480
|
}
|
|
443
481
|
|
|
@@ -449,7 +487,24 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
449
487
|
}
|
|
450
488
|
|
|
451
489
|
/** @type {Manifest} */
|
|
452
|
-
const
|
|
490
|
+
const definitions = projectRoot
|
|
491
|
+
? await discoverPackageDefinitions(projectRoot)
|
|
492
|
+
: {}
|
|
493
|
+
|
|
494
|
+
const manifest = {
|
|
495
|
+
entries,
|
|
496
|
+
components,
|
|
497
|
+
resourceTemplates,
|
|
498
|
+
aggregates,
|
|
499
|
+
integrations,
|
|
500
|
+
startups,
|
|
501
|
+
emails,
|
|
502
|
+
actions,
|
|
503
|
+
e2es,
|
|
504
|
+
layouts,
|
|
505
|
+
definitions,
|
|
506
|
+
config: configValue || {},
|
|
507
|
+
}
|
|
453
508
|
fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
|
|
454
509
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
|
|
455
510
|
},
|