@ossy/app 1.28.0 → 1.29.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/cli/build.task.js CHANGED
@@ -15,10 +15,11 @@ import getPlatformFiles, {
15
15
  TASK_FILE_PATTERN,
16
16
  RESOURCE_FILE_PATTERN,
17
17
  COMPONENT_FILE_PATTERN,
18
+ AGGREGATE_FILE_PATTERN,
18
19
  } from './get-platform-files.task.js'
19
20
  import { manifestPlugin } from './manifest-plugin.js'
20
21
 
21
- export { PAGE_FILE_PATTERN, API_FILE_PATTERN, TASK_FILE_PATTERN, RESOURCE_FILE_PATTERN, COMPONENT_FILE_PATTERN }
22
+ export { PAGE_FILE_PATTERN, API_FILE_PATTERN, TASK_FILE_PATTERN, RESOURCE_FILE_PATTERN, COMPONENT_FILE_PATTERN, AGGREGATE_FILE_PATTERN }
22
23
 
23
24
  // Generated entry stubs live under `build/.ossy/entries/`. Putting them
24
25
  // inside `build/` means they're cleaned automatically with every build,
@@ -143,11 +144,24 @@ function generateComponentStub ({ stubAbs, sourceAbs }) {
143
144
  ].join('\n')
144
145
  }
145
146
 
147
+ // Aggregates are server-side only. The stub re-exports everything so the
148
+ // manifest plugin can read `Aggregate` (the class) and `id` (the AggregateType
149
+ // string) from the bundled output.
150
+ function generateAggregateStub ({ stubAbs, sourceAbs }) {
151
+ const importPath = relImport(stubAbs, sourceAbs)
152
+ return [
153
+ '// Generated by @ossy/app — do not edit',
154
+ `export * from '${importPath}'`,
155
+ '',
156
+ ].join('\n')
157
+ }
158
+
146
159
  function stubFor (kind, args) {
147
160
  if (kind === 'page') return generatePageStub(args)
148
161
  if (kind === 'api') return generateApiStub(args)
149
162
  if (kind === 'resource') return generateResourceStub(args)
150
163
  if (kind === 'component') return generateComponentStub(args)
164
+ if (kind === 'aggregate') return generateAggregateStub(args)
151
165
  return generateTaskStub(args)
152
166
  }
153
167
 
@@ -189,6 +203,7 @@ export async function build (cliArgs = []) {
189
203
  ...platformFiles.tasks,
190
204
  ...platformFiles.resources,
191
205
  ...platformFiles.components,
206
+ ...platformFiles.aggregates,
192
207
  ]
193
208
 
194
209
  const entriesByStub = new Map()
@@ -6,9 +6,10 @@ export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
6
6
  export const TASK_FILE_PATTERN = /\.task\.(mjs|cjs|js)$/
7
7
  export const RESOURCE_FILE_PATTERN = /\.resource\.(mjs|cjs|js)$/
8
8
  export const COMPONENT_FILE_PATTERN = /\.component\.(jsx?|tsx?)$/
9
+ export const AGGREGATE_FILE_PATTERN = /\.aggregate\.(mjs|cjs|js)$/
9
10
 
10
11
  /**
11
- * @typedef {'page' | 'api' | 'task' | 'resource' | 'component'} EntryKind
12
+ * @typedef {'page' | 'api' | 'task' | 'resource' | 'component' | 'aggregate'} EntryKind
12
13
  *
13
14
  * @typedef {object} PlatformEntry
14
15
  * @property {EntryKind} kind Which bucket this entry lives in.
@@ -22,6 +23,7 @@ export const COMPONENT_FILE_PATTERN = /\.component\.(jsx?|tsx?)$/
22
23
  * @property {PlatformEntry[]} tasks Discovered `*.task.{js,mjs,cjs}` entries.
23
24
  * @property {PlatformEntry[]} resources Discovered `*.resource.{js,mjs,cjs}` entries.
24
25
  * @property {PlatformEntry[]} components Discovered `*.component.{jsx,tsx,...}` entries.
26
+ * @property {PlatformEntry[]} aggregates Discovered `*.aggregate.{js,mjs,cjs}` entries (installed packages only).
25
27
  */
26
28
  export function discoverFilesByPattern (srcDir, filePattern) {
27
29
  const dir = path.resolve(srcDir)
@@ -45,6 +47,7 @@ function classifyFile (absPath) {
45
47
  if (TASK_FILE_PATTERN.test(base)) return 'task'
46
48
  if (RESOURCE_FILE_PATTERN.test(base)) return 'resource'
47
49
  if (COMPONENT_FILE_PATTERN.test(base)) return 'component'
50
+ if (AGGREGATE_FILE_PATTERN.test(base)) return 'aggregate'
48
51
  return null
49
52
  }
50
53
 
@@ -114,6 +117,7 @@ export function discoverInstalledPackageEntries (projectRoot) {
114
117
  ...discoverFilesByPattern(packageSrcDir, TASK_FILE_PATTERN),
115
118
  ...discoverFilesByPattern(packageSrcDir, RESOURCE_FILE_PATTERN),
116
119
  ...discoverFilesByPattern(packageSrcDir, COMPONENT_FILE_PATTERN),
120
+ ...discoverFilesByPattern(packageSrcDir, AGGREGATE_FILE_PATTERN),
117
121
  ]
118
122
  for (const sourcePath of files) {
119
123
  const stat = fs.statSync(sourcePath)
@@ -157,9 +161,12 @@ export function discoverInstalledPackageEntries (projectRoot) {
157
161
  * @returns {Promise<PlatformFiles>}
158
162
  */
159
163
  export default async function getPlatformFiles (srcDir) {
160
- const out = { pages: [], apis: [], tasks: [], resources: [], components: [] }
161
- const bucketByKind = { page: 'pages', api: 'apis', task: 'tasks', resource: 'resources', component: 'components' }
164
+ const out = { pages: [], apis: [], tasks: [], resources: [], components: [], aggregates: [] }
165
+ const bucketByKind = { page: 'pages', api: 'apis', task: 'tasks', resource: 'resources', component: 'components', aggregate: 'aggregates' }
162
166
 
167
+ // Aggregates are only discovered from installed packages — never from the
168
+ // local `src/` — to avoid pulling in per-project duplicates of the same
169
+ // aggregate class that is canonically owned by the feature package.
163
170
  const localFiles = [
164
171
  ...discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN),
165
172
  ...discoverFilesByPattern(srcDir, API_FILE_PATTERN),
@@ -171,7 +178,7 @@ export default async function getPlatformFiles (srcDir) {
171
178
  const stat = fs.statSync(sourcePath)
172
179
  if (!stat.isFile() || stat.size === 0) continue
173
180
  const kind = classifyFile(sourcePath)
174
- if (!kind) continue
181
+ if (!kind || kind === 'aggregate') continue
175
182
  out[bucketByKind[kind]].push({ kind, sourcePath })
176
183
  }
177
184
 
@@ -42,10 +42,15 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
42
42
  * @property {string} id Unique component id (from `metadata.id`).
43
43
  * @property {string} entry URL the platform serves the component bundle from.
44
44
  *
45
+ * @typedef {object} AggregateManifestEntry
46
+ * @property {string} id AggregateType string (e.g. `'User'`).
47
+ * @property {string} entry URL the platform serves the aggregate bundle from.
48
+ *
45
49
  * @typedef {object} Manifest
46
50
  * @property {ManifestEntry[]} entries Flat, ordered list of every routable thing.
47
51
  * @property {ComponentManifestEntry[]} components Discovered `*.component.*` entries, keyed by id.
48
52
  * @property {object[]} resourceTemplates Each `*.resource.js` file's default-exported template object.
53
+ * @property {AggregateManifestEntry[]} aggregates Discovered `*.aggregate.js` entries from installed packages.
49
54
  * @property {object} config Inlined `src/config.js` default export.
50
55
  */
51
56
 
@@ -93,8 +98,11 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
93
98
  const resourceTemplates = []
94
99
  /** @type {import('./get-platform-files.task.js').ComponentManifestEntry[]} */
95
100
  const components = []
101
+ /** @type {AggregateManifestEntry[]} */
102
+ const aggregates = []
96
103
  const seenIds = { page: new Set(), api: new Set(), task: new Set(), component: new Set() }
97
104
  const seenResourceIds = new Set()
105
+ const seenAggregateIds = new Set()
98
106
 
99
107
  for (const fileName of Object.keys(bundle)) {
100
108
  const chunk = bundle[fileName]
@@ -120,6 +128,30 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
120
128
  )
121
129
  }
122
130
 
131
+ // Aggregates are server-side classes — the bundle exports `Aggregate`
132
+ // (the class) and `id` (the AggregateType string). They don't produce
133
+ // routable entries; they accumulate into a separate `aggregates` array.
134
+ if (entryInfo.kind === 'aggregate') {
135
+ const AggregateClass = mod && mod.Aggregate
136
+ const aggregateId = mod && mod.id
137
+ if (typeof AggregateClass !== 'function') {
138
+ this.error(
139
+ `[@ossy/app][build] aggregate entry ${entryInfo.sourcePath} must export a class as "Aggregate"`,
140
+ )
141
+ }
142
+ if (typeof aggregateId !== 'string' || aggregateId.trim() === '') {
143
+ this.error(
144
+ `[@ossy/app][build] aggregate entry ${entryInfo.sourcePath} must export a non-empty string "id"`,
145
+ )
146
+ }
147
+ if (seenAggregateIds.has(aggregateId)) {
148
+ this.error(`[@ossy/app][build] Duplicate aggregate id "${aggregateId}"`)
149
+ }
150
+ seenAggregateIds.add(aggregateId)
151
+ aggregates.push({ id: aggregateId, entry: url })
152
+ continue
153
+ }
154
+
123
155
  // Resources are pure data — the default export *is* the template.
124
156
  // They don't use `metadata`, don't need a derived id, and don't
125
157
  // produce a routable manifest entry; they accumulate into a separate
@@ -181,7 +213,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
181
213
  }
182
214
 
183
215
  /** @type {Manifest} */
184
- const manifest = { entries, components, resourceTemplates, config: configValue || {} }
216
+ const manifest = { entries, components, resourceTemplates, aggregates, config: configValue || {} }
185
217
  fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
186
218
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
187
219
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.28.0",
3
+ "version": "1.29.0",
4
4
  "description": "",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
@@ -37,15 +37,15 @@
37
37
  "@babel/eslint-parser": "^7.15.8",
38
38
  "@babel/preset-react": "^7.26.3",
39
39
  "@babel/register": "^7.25.9",
40
- "@ossy/connected-components": "^1.28.0",
41
- "@ossy/design-system": "^1.28.0",
40
+ "@ossy/connected-components": "^1.29.0",
41
+ "@ossy/design-system": "^1.29.0",
42
42
  "@ossy/pages": "^1.23.0",
43
- "@ossy/platform": "^1.27.0",
44
- "@ossy/router": "^1.28.0",
45
- "@ossy/router-react": "^1.28.0",
46
- "@ossy/sdk": "^1.28.0",
47
- "@ossy/sdk-react": "^1.28.0",
48
- "@ossy/themes": "^1.28.0",
43
+ "@ossy/platform": "^1.28.0",
44
+ "@ossy/router": "^1.29.0",
45
+ "@ossy/router-react": "^1.29.0",
46
+ "@ossy/sdk": "^1.29.0",
47
+ "@ossy/sdk-react": "^1.29.0",
48
+ "@ossy/themes": "^1.29.0",
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": "5f1feadf3b630aa96ec61f1d48c11e0e1aa4b095"
82
+ "gitHead": "c71b23b5721bfd12dec0dfb4b50d945b65314898"
83
83
  }