@ossy/app 1.39.0 → 1.39.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/build.task.js CHANGED
@@ -137,13 +137,20 @@ function generateResourceStub ({ stubAbs, sourceAbs }) {
137
137
  }
138
138
 
139
139
  // Components are re-exported as-is — the manifest plugin reads `metadata.id`
140
- // from the bundled output and records the entry URL. Components don't get
140
+ // and the optional `slot` export from the bundled output. Components don't get
141
141
  // route stubs or hydration bootstrapping; they're loaded on-demand at runtime.
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
148
  function generateComponentStub ({ stubAbs, sourceAbs }) {
143
149
  const importPath = relImport(stubAbs, sourceAbs)
144
150
  return [
145
151
  '// Generated by @ossy/app — do not edit',
146
- `export { default, metadata } from '${importPath}'`,
152
+ `export * from '${importPath}'`,
153
+ `export { default } from '${importPath}'`,
147
154
  '',
148
155
  ].join('\n')
149
156
  }
@@ -35,7 +35,7 @@ export const LAYOUT_FILE_PATTERN = /\.layout\.(jsx?|tsx?)$/
35
35
  * @property {PlatformEntry[]} emails Discovered `*.email.{jsx,tsx,...}` entries.
36
36
  * @property {PlatformEntry[]} actions Discovered `*.action.{js,mjs,cjs}` entries.
37
37
  * @property {PlatformEntry[]} e2es Discovered `*.e2e.{js,mjs,cjs}` entries (installed packages only).
38
- * @property {PlatformEntry[]} layouts Discovered `*.layout.{jsx,tsx,...}` entries.
38
+ * @property {PlatformEntry[]} layouts Discovered `*.layout.{jsx,tsx,...}` entries (app root `src/` only — never from installed packages).
39
39
  */
40
40
  export function discoverFilesByPattern (srcDir, filePattern) {
41
41
  const dir = path.resolve(srcDir)
@@ -104,24 +104,57 @@ export function filePathToRoute (absPath, srcDir) {
104
104
  }
105
105
 
106
106
  /**
107
- * Walks `node_modules/` in `projectRoot` looking for packages that declare an
108
- * `"ossy": { "src": "./src" }` field in their `package.json`. Returns entries
109
- * from those packages' source trees, tagged with `packageName` and
110
- * `packageSrcDir` so the build pipeline can namespace them correctly.
107
+ * Collects all `node_modules` directories that Node.js would search when
108
+ * resolving imports from `projectRoot`, walking up the filesystem the same
109
+ * way the Node module-resolution algorithm does. This is essential for npm
110
+ * workspaces where packages are hoisted to a root-level `node_modules` that
111
+ * may be several directories above the consuming project.
112
+ *
113
+ * @param {string} projectRoot
114
+ * @returns {string[]} Absolute paths to existing `node_modules` directories,
115
+ * ordered from most-specific (closest to `projectRoot`) to least-specific.
116
+ */
117
+ function collectNodeModulesDirs (projectRoot) {
118
+ const dirs = []
119
+ let current = projectRoot
120
+ while (true) {
121
+ const nm = path.join(current, 'node_modules')
122
+ if (fs.existsSync(nm) && fs.statSync(nm).isDirectory()) dirs.push(nm)
123
+ const parent = path.dirname(current)
124
+ if (parent === current) break
125
+ current = parent
126
+ }
127
+ return dirs
128
+ }
129
+
130
+ /**
131
+ * Walks `node_modules/` directories reachable from `projectRoot` looking for
132
+ * packages that declare an `"ossy": { "src": "./src" }` field in their
133
+ * `package.json`. Returns entries from those packages' source trees, tagged
134
+ * with `packageName` and `packageSrcDir` so the build pipeline can namespace
135
+ * them correctly.
111
136
  *
112
137
  * Handles both flat packages (`node_modules/foo`) and scoped packages
113
- * (`node_modules/@scope/foo`).
138
+ * (`node_modules/@scope/foo`). Walks up the directory tree following Node.js
139
+ * module-resolution order so that monorepo setups with hoisted packages
140
+ * (where dependencies live in a root-level `node_modules` rather than a
141
+ * package-local one) are discovered correctly.
114
142
  *
115
143
  * @param {string} projectRoot Absolute path to the consuming project root (parent of `src/`).
116
144
  * @returns {PlatformEntry[]}
117
145
  */
118
146
  export function discoverInstalledPackageEntries (projectRoot) {
119
- const nmDir = path.join(projectRoot, 'node_modules')
120
- if (!fs.existsSync(nmDir) || !fs.statSync(nmDir).isDirectory()) return []
147
+ const nmDirs = collectNodeModulesDirs(projectRoot)
148
+ if (nmDirs.length === 0) return []
121
149
 
122
150
  const entries = []
151
+ const seen = new Set()
123
152
 
124
153
  const tryPackageDir = (pkgDir) => {
154
+ const real = fs.realpathSync(pkgDir)
155
+ if (seen.has(real)) return
156
+ seen.add(real)
157
+
125
158
  const pkgJsonPath = path.join(pkgDir, 'package.json')
126
159
  if (!fs.existsSync(pkgJsonPath)) return
127
160
  let pkg
@@ -141,7 +174,8 @@ export function discoverInstalledPackageEntries (projectRoot) {
141
174
  ...discoverFilesByPattern(packageSrcDir, EMAIL_FILE_PATTERN),
142
175
  ...discoverFilesByPattern(packageSrcDir, ACTION_FILE_PATTERN),
143
176
  ...discoverFilesByPattern(packageSrcDir, E2E_FILE_PATTERN),
144
- ...discoverFilesByPattern(packageSrcDir, LAYOUT_FILE_PATTERN),
177
+ // Layouts are intentionally excluded: *.layout.jsx is an app-level
178
+ // primitive only. Feature packages must never define layouts.
145
179
  ]
146
180
  for (const sourcePath of files) {
147
181
  const stat = fs.statSync(sourcePath)
@@ -152,17 +186,19 @@ export function discoverInstalledPackageEntries (projectRoot) {
152
186
  }
153
187
  }
154
188
 
155
- for (const entry of fs.readdirSync(nmDir, { withFileTypes: true })) {
156
- if (!entry.isDirectory() && !entry.isSymbolicLink()) continue
157
- if (entry.name.startsWith('@')) {
158
- const scopeDir = path.join(nmDir, entry.name)
159
- for (const scoped of fs.readdirSync(scopeDir, { withFileTypes: true })) {
160
- if (scoped.isDirectory() || scoped.isSymbolicLink()) {
161
- tryPackageDir(path.join(scopeDir, scoped.name))
189
+ for (const nmDir of nmDirs) {
190
+ for (const entry of fs.readdirSync(nmDir, { withFileTypes: true })) {
191
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue
192
+ if (entry.name.startsWith('@')) {
193
+ const scopeDir = path.join(nmDir, entry.name)
194
+ for (const scoped of fs.readdirSync(scopeDir, { withFileTypes: true })) {
195
+ if (scoped.isDirectory() || scoped.isSymbolicLink()) {
196
+ tryPackageDir(path.join(scopeDir, scoped.name))
197
+ }
162
198
  }
199
+ } else {
200
+ tryPackageDir(path.join(nmDir, entry.name))
163
201
  }
164
- } else {
165
- tryPackageDir(path.join(nmDir, entry.name))
166
202
  }
167
203
  }
168
204
 
@@ -192,6 +228,9 @@ export default async function getPlatformFiles (srcDir) {
192
228
  // from the local `src/` — to avoid pulling in per-project duplicates of the
193
229
  // same aggregate class or test suite that is canonically owned by the feature
194
230
  // package.
231
+ // Layouts are the opposite restriction: they are only discovered from the
232
+ // app's own `src/`, never from installed packages. Feature packages are pure
233
+ // content (pages, actions, tasks, etc.) and must never define layouts.
195
234
  const localFiles = [
196
235
  ...discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN),
197
236
  ...discoverFilesByPattern(srcDir, API_FILE_PATTERN),
@@ -39,8 +39,14 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
39
39
  * (e.g. `/static/home.page-7f2a.js`).
40
40
  *
41
41
  * @typedef {object} ComponentManifestEntry
42
- * @property {string} id Unique component id (from `metadata.id`).
43
- * @property {string} entry URL the platform serves the component bundle from.
42
+ * @property {string} id Unique component id (from `metadata.id`).
43
+ * @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).
44
50
  *
45
51
  * @typedef {object} AggregateManifestEntry
46
52
  * @property {string} id AggregateType string (e.g. `'User'`).
@@ -78,6 +84,7 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
78
84
  * @typedef {object} Manifest
79
85
  * @property {ManifestEntry[]} entries Flat, ordered list of every routable thing.
80
86
  * @property {ComponentManifestEntry[]} components Discovered `*.component.*` entries, keyed by id.
87
+ * @property {SlotManifestEntry[]} slots Components that declare `export const slot` — auto-injected into shell slots.
81
88
  * @property {object[]} resourceTemplates Each `*.resource.js` file's default-exported template object.
82
89
  * @property {AggregateManifestEntry[]} aggregates Discovered `*.aggregate.js` entries from installed packages.
83
90
  * @property {IntegrationManifestEntry[]} integrations Discovered `*.integration.js` entries.
@@ -150,6 +157,8 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
150
157
  const seenStartupIds = new Set()
151
158
  const seenEmailIds = new Set()
152
159
  const seenActionIds = new Set()
160
+ /** @type {SlotManifestEntry[]} */
161
+ const slots = []
153
162
  /** @type {E2eManifestEntry[]} */
154
163
  const e2es = []
155
164
  const seenE2eIds = new Set()
@@ -399,7 +408,12 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
399
408
  seenIds[entryInfo.kind].add(id)
400
409
 
401
410
  if (entryInfo.kind === 'component') {
402
- components.push({ id, entry: url })
411
+ const slotName = typeof mod.slot === 'string' && mod.slot.trim() ? mod.slot.trim() : undefined
412
+ components.push({ id, entry: url, ...(slotName ? { slot: slotName } : {}) })
413
+ if (slotName) {
414
+ const packageName = entryInfo.packageName
415
+ slots.push({ slot: slotName, entry: url, ...(packageName ? { package: packageName } : {}) })
416
+ }
403
417
  continue
404
418
  }
405
419
 
@@ -425,7 +439,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
425
439
  }
426
440
 
427
441
  /** @type {Manifest} */
428
- const manifest = { entries, components, resourceTemplates, aggregates, integrations, startups, emails, actions, e2es, layouts, config: configValue || {} }
442
+ const manifest = { entries, components, slots, resourceTemplates, aggregates, integrations, startups, emails, actions, e2es, layouts, config: configValue || {} }
429
443
  fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
430
444
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
431
445
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.39.0",
3
+ "version": "1.39.2",
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.39.0",
41
+ "@ossy/design-system": "^1.39.2",
42
42
  "@ossy/pages": "^1.23.0",
43
- "@ossy/platform": "^1.38.0",
44
- "@ossy/router": "^1.39.0",
45
- "@ossy/router-react": "^1.39.0",
46
- "@ossy/sdk": "^1.39.0",
47
- "@ossy/sdk-react": "^1.39.0",
48
- "@ossy/themes": "^1.39.0",
43
+ "@ossy/platform": "^1.38.2",
44
+ "@ossy/router": "^1.39.2",
45
+ "@ossy/router-react": "^1.39.2",
46
+ "@ossy/sdk": "^1.39.2",
47
+ "@ossy/sdk-react": "^1.39.2",
48
+ "@ossy/themes": "^1.39.2",
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": "150285065e45f9be2ee19794e493abc50cd85e44"
82
+ "gitHead": "51e33c2eaff6973c34f00024a082c6912252ec72"
83
83
  }