@ossy/app 1.32.2 → 1.32.3

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
@@ -16,10 +16,12 @@ import getPlatformFiles, {
16
16
  RESOURCE_FILE_PATTERN,
17
17
  COMPONENT_FILE_PATTERN,
18
18
  AGGREGATE_FILE_PATTERN,
19
+ INTEGRATION_FILE_PATTERN,
20
+ STARTUP_FILE_PATTERN,
19
21
  } from './get-platform-files.task.js'
20
22
  import { manifestPlugin } from './manifest-plugin.js'
21
23
 
22
- export { PAGE_FILE_PATTERN, API_FILE_PATTERN, TASK_FILE_PATTERN, RESOURCE_FILE_PATTERN, COMPONENT_FILE_PATTERN, AGGREGATE_FILE_PATTERN }
24
+ 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 }
23
25
 
24
26
  // Generated entry stubs live under `build/.ossy/entries/`. Putting them
25
27
  // inside `build/` means they're cleaned automatically with every build,
@@ -156,12 +158,38 @@ function generateAggregateStub ({ stubAbs, sourceAbs }) {
156
158
  ].join('\n')
157
159
  }
158
160
 
161
+ // Integrations are server-side only. The stub re-exports `id`, `credentials`,
162
+ // and `connect` so the manifest plugin can validate them and the platform
163
+ // server can call `connect({ env })` at startup.
164
+ function generateIntegrationStub ({ stubAbs, sourceAbs }) {
165
+ const importPath = relImport(stubAbs, sourceAbs)
166
+ return [
167
+ '// Generated by @ossy/app — do not edit',
168
+ `export * from '${importPath}'`,
169
+ '',
170
+ ].join('\n')
171
+ }
172
+
173
+ // Startups are server-side one-shot hooks. The stub re-exports `id` and `run`
174
+ // so the manifest plugin can validate them and the platform server can call
175
+ // `run({ env })` at boot time.
176
+ function generateStartupStub ({ stubAbs, sourceAbs }) {
177
+ const importPath = relImport(stubAbs, sourceAbs)
178
+ return [
179
+ '// Generated by @ossy/app — do not edit',
180
+ `export * from '${importPath}'`,
181
+ '',
182
+ ].join('\n')
183
+ }
184
+
159
185
  function stubFor (kind, args) {
160
186
  if (kind === 'page') return generatePageStub(args)
161
187
  if (kind === 'api') return generateApiStub(args)
162
188
  if (kind === 'resource') return generateResourceStub(args)
163
189
  if (kind === 'component') return generateComponentStub(args)
164
190
  if (kind === 'aggregate') return generateAggregateStub(args)
191
+ if (kind === 'integration') return generateIntegrationStub(args)
192
+ if (kind === 'startup') return generateStartupStub(args)
165
193
  return generateTaskStub(args)
166
194
  }
167
195
 
@@ -204,6 +232,8 @@ export async function build (cliArgs = []) {
204
232
  ...platformFiles.resources,
205
233
  ...platformFiles.components,
206
234
  ...platformFiles.aggregates,
235
+ ...platformFiles.integrations,
236
+ ...platformFiles.startups,
207
237
  ]
208
238
 
209
239
  const entriesByStub = new Map()
@@ -7,9 +7,11 @@ 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
9
  export const AGGREGATE_FILE_PATTERN = /\.aggregate\.(mjs|cjs|js)$/
10
+ export const INTEGRATION_FILE_PATTERN = /\.integration\.(mjs|cjs|js)$/
11
+ export const STARTUP_FILE_PATTERN = /\.startup\.(mjs|cjs|js)$/
10
12
 
11
13
  /**
12
- * @typedef {'page' | 'api' | 'task' | 'resource' | 'component' | 'aggregate'} EntryKind
14
+ * @typedef {'page' | 'api' | 'task' | 'resource' | 'component' | 'aggregate' | 'integration' | 'startup'} EntryKind
13
15
  *
14
16
  * @typedef {object} PlatformEntry
15
17
  * @property {EntryKind} kind Which bucket this entry lives in.
@@ -18,12 +20,14 @@ export const AGGREGATE_FILE_PATTERN = /\.aggregate\.(mjs|cjs|js)$/
18
20
  * @property {string} [packageSrcDir] Absolute path to the package's `src/` dir, set only for installed-package entries.
19
21
  *
20
22
  * @typedef {object} PlatformFiles
21
- * @property {PlatformEntry[]} pages Discovered `*.page.{jsx,tsx,...}` entries.
22
- * @property {PlatformEntry[]} apis Discovered `*.api.{js,mjs,cjs}` entries.
23
- * @property {PlatformEntry[]} tasks Discovered `*.task.{js,mjs,cjs}` entries.
24
- * @property {PlatformEntry[]} resources Discovered `*.resource.{js,mjs,cjs}` entries.
25
- * @property {PlatformEntry[]} components Discovered `*.component.{jsx,tsx,...}` entries.
26
- * @property {PlatformEntry[]} aggregates Discovered `*.aggregate.{js,mjs,cjs}` entries (installed packages only).
23
+ * @property {PlatformEntry[]} pages Discovered `*.page.{jsx,tsx,...}` entries.
24
+ * @property {PlatformEntry[]} apis Discovered `*.api.{js,mjs,cjs}` entries.
25
+ * @property {PlatformEntry[]} tasks Discovered `*.task.{js,mjs,cjs}` entries.
26
+ * @property {PlatformEntry[]} resources Discovered `*.resource.{js,mjs,cjs}` entries.
27
+ * @property {PlatformEntry[]} components Discovered `*.component.{jsx,tsx,...}` entries.
28
+ * @property {PlatformEntry[]} aggregates Discovered `*.aggregate.{js,mjs,cjs}` entries (installed packages only).
29
+ * @property {PlatformEntry[]} integrations Discovered `*.integration.{js,mjs,cjs}` entries.
30
+ * @property {PlatformEntry[]} startups Discovered `*.startup.{js,mjs,cjs}` entries.
27
31
  */
28
32
  export function discoverFilesByPattern (srcDir, filePattern) {
29
33
  const dir = path.resolve(srcDir)
@@ -48,6 +52,8 @@ function classifyFile (absPath) {
48
52
  if (RESOURCE_FILE_PATTERN.test(base)) return 'resource'
49
53
  if (COMPONENT_FILE_PATTERN.test(base)) return 'component'
50
54
  if (AGGREGATE_FILE_PATTERN.test(base)) return 'aggregate'
55
+ if (INTEGRATION_FILE_PATTERN.test(base)) return 'integration'
56
+ if (STARTUP_FILE_PATTERN.test(base)) return 'startup'
51
57
  return null
52
58
  }
53
59
 
@@ -118,6 +124,8 @@ export function discoverInstalledPackageEntries (projectRoot) {
118
124
  ...discoverFilesByPattern(packageSrcDir, RESOURCE_FILE_PATTERN),
119
125
  ...discoverFilesByPattern(packageSrcDir, COMPONENT_FILE_PATTERN),
120
126
  ...discoverFilesByPattern(packageSrcDir, AGGREGATE_FILE_PATTERN),
127
+ ...discoverFilesByPattern(packageSrcDir, INTEGRATION_FILE_PATTERN),
128
+ ...discoverFilesByPattern(packageSrcDir, STARTUP_FILE_PATTERN),
121
129
  ]
122
130
  for (const sourcePath of files) {
123
131
  const stat = fs.statSync(sourcePath)
@@ -161,8 +169,8 @@ export function discoverInstalledPackageEntries (projectRoot) {
161
169
  * @returns {Promise<PlatformFiles>}
162
170
  */
163
171
  export default async function getPlatformFiles (srcDir) {
164
- const out = { pages: [], apis: [], tasks: [], resources: [], components: [], aggregates: [] }
165
- const bucketByKind = { page: 'pages', api: 'apis', task: 'tasks', resource: 'resources', component: 'components', aggregate: 'aggregates' }
172
+ const out = { pages: [], apis: [], tasks: [], resources: [], components: [], aggregates: [], integrations: [], startups: [] }
173
+ const bucketByKind = { page: 'pages', api: 'apis', task: 'tasks', resource: 'resources', component: 'components', aggregate: 'aggregates', integration: 'integrations', startup: 'startups' }
166
174
 
167
175
  // Aggregates are only discovered from installed packages — never from the
168
176
  // local `src/` — to avoid pulling in per-project duplicates of the same
@@ -173,6 +181,8 @@ export default async function getPlatformFiles (srcDir) {
173
181
  ...discoverFilesByPattern(srcDir, TASK_FILE_PATTERN),
174
182
  ...discoverFilesByPattern(srcDir, RESOURCE_FILE_PATTERN),
175
183
  ...discoverFilesByPattern(srcDir, COMPONENT_FILE_PATTERN),
184
+ ...discoverFilesByPattern(srcDir, INTEGRATION_FILE_PATTERN),
185
+ ...discoverFilesByPattern(srcDir, STARTUP_FILE_PATTERN),
176
186
  ]
177
187
  for (const sourcePath of localFiles) {
178
188
  const stat = fs.statSync(sourcePath)
@@ -46,12 +46,23 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
46
46
  * @property {string} id AggregateType string (e.g. `'User'`).
47
47
  * @property {string} entry URL the platform serves the aggregate bundle from.
48
48
  *
49
+ * @typedef {object} IntegrationManifestEntry
50
+ * @property {string} id Unique integration slug (e.g. `'email'`).
51
+ * @property {string} entry URL the platform serves the integration bundle from.
52
+ * @property {string[]} credentials Env var names required by the integration.
53
+ *
54
+ * @typedef {object} StartupManifestEntry
55
+ * @property {string} id Unique startup slug (e.g. `'create-bot-user'`).
56
+ * @property {string} entry URL the platform serves the startup bundle from.
57
+ *
49
58
  * @typedef {object} Manifest
50
- * @property {ManifestEntry[]} entries Flat, ordered list of every routable thing.
59
+ * @property {ManifestEntry[]} entries Flat, ordered list of every routable thing.
51
60
  * @property {ComponentManifestEntry[]} components Discovered `*.component.*` entries, keyed by id.
52
- * @property {object[]} resourceTemplates Each `*.resource.js` file's default-exported template object.
61
+ * @property {object[]} resourceTemplates Each `*.resource.js` file's default-exported template object.
53
62
  * @property {AggregateManifestEntry[]} aggregates Discovered `*.aggregate.js` entries from installed packages.
54
- * @property {object} config Inlined `src/config.js` default export.
63
+ * @property {IntegrationManifestEntry[]} integrations Discovered `*.integration.js` entries.
64
+ * @property {StartupManifestEntry[]} startups Discovered `*.startup.js` entries.
65
+ * @property {object} config Inlined `src/config.js` default export.
55
66
  */
56
67
 
57
68
  /**
@@ -100,9 +111,15 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
100
111
  const components = []
101
112
  /** @type {AggregateManifestEntry[]} */
102
113
  const aggregates = []
114
+ /** @type {IntegrationManifestEntry[]} */
115
+ const integrations = []
116
+ /** @type {StartupManifestEntry[]} */
117
+ const startups = []
103
118
  const seenIds = { page: new Set(), api: new Set(), task: new Set(), component: new Set() }
104
119
  const seenResourceIds = new Set()
105
120
  const seenAggregateIds = new Set()
121
+ const seenIntegrationIds = new Set()
122
+ const seenStartupIds = new Set()
106
123
 
107
124
  for (const fileName of Object.keys(bundle)) {
108
125
  const chunk = bundle[fileName]
@@ -128,6 +145,58 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
128
145
  )
129
146
  }
130
147
 
148
+ // Integrations provide third-party clients. The bundle exports `id`,
149
+ // `credentials` (env var names), and `connect({ env })`. They don't
150
+ // produce routable entries; they accumulate into a separate array.
151
+ if (entryInfo.kind === 'integration') {
152
+ const integrationId = mod && mod.id
153
+ const credentials = mod && mod.credentials
154
+ if (typeof integrationId !== 'string' || integrationId.trim() === '') {
155
+ this.error(
156
+ `[@ossy/app][build] integration entry ${entryInfo.sourcePath} must export a non-empty string "id"`,
157
+ )
158
+ }
159
+ if (!Array.isArray(credentials)) {
160
+ this.error(
161
+ `[@ossy/app][build] integration entry ${entryInfo.sourcePath} must export a "credentials" array`,
162
+ )
163
+ }
164
+ if (typeof mod.connect !== 'function') {
165
+ this.error(
166
+ `[@ossy/app][build] integration entry ${entryInfo.sourcePath} must export a "connect" function`,
167
+ )
168
+ }
169
+ if (seenIntegrationIds.has(integrationId)) {
170
+ this.error(`[@ossy/app][build] Duplicate integration id "${integrationId}"`)
171
+ }
172
+ seenIntegrationIds.add(integrationId)
173
+ integrations.push({ id: integrationId, entry: url, credentials })
174
+ continue
175
+ }
176
+
177
+ // Startups are server-side one-shot hooks. The bundle exports `id`
178
+ // (a unique slug) and `run({ env })`. They don't produce routable
179
+ // entries; they accumulate into a separate `startups` array.
180
+ if (entryInfo.kind === 'startup') {
181
+ const startupId = mod && mod.id
182
+ if (typeof startupId !== 'string' || startupId.trim() === '') {
183
+ this.error(
184
+ `[@ossy/app][build] startup entry ${entryInfo.sourcePath} must export a non-empty string "id"`,
185
+ )
186
+ }
187
+ if (typeof mod.run !== 'function') {
188
+ this.error(
189
+ `[@ossy/app][build] startup entry ${entryInfo.sourcePath} must export a "run" function`,
190
+ )
191
+ }
192
+ if (seenStartupIds.has(startupId)) {
193
+ this.error(`[@ossy/app][build] Duplicate startup id "${startupId}"`)
194
+ }
195
+ seenStartupIds.add(startupId)
196
+ startups.push({ id: startupId, entry: url })
197
+ continue
198
+ }
199
+
131
200
  // Aggregates are server-side classes — the bundle exports `Aggregate`
132
201
  // (the class) and `id` (the AggregateType string). They don't produce
133
202
  // routable entries; they accumulate into a separate `aggregates` array.
@@ -213,7 +282,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
213
282
  }
214
283
 
215
284
  /** @type {Manifest} */
216
- const manifest = { entries, components, resourceTemplates, aggregates, config: configValue || {} }
285
+ const manifest = { entries, components, resourceTemplates, aggregates, integrations, startups, config: configValue || {} }
217
286
  fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
218
287
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
219
288
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.32.2",
3
+ "version": "1.32.3",
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.32.2",
41
+ "@ossy/design-system": "^1.32.3",
42
42
  "@ossy/pages": "^1.23.0",
43
- "@ossy/platform": "^1.31.2",
44
- "@ossy/router": "^1.32.2",
45
- "@ossy/router-react": "^1.32.2",
46
- "@ossy/sdk": "^1.32.2",
47
- "@ossy/sdk-react": "^1.32.2",
48
- "@ossy/themes": "^1.32.2",
43
+ "@ossy/platform": "^1.31.3",
44
+ "@ossy/router": "^1.32.3",
45
+ "@ossy/router-react": "^1.32.3",
46
+ "@ossy/sdk": "^1.32.3",
47
+ "@ossy/sdk-react": "^1.32.3",
48
+ "@ossy/themes": "^1.32.3",
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": "5c17e379298f7027f1dbdcf064a4aff69d1497d5"
82
+ "gitHead": "b8cbdc545a659e96af8948aa08a89316a2c2d193"
83
83
  }