@ossy/app 1.34.0 → 1.35.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 +31 -2
- package/cli/get-platform-files.task.js +13 -3
- package/cli/manifest-plugin.js +66 -1
- package/package.json +9 -9
package/cli/build.task.js
CHANGED
|
@@ -18,10 +18,12 @@ import getPlatformFiles, {
|
|
|
18
18
|
AGGREGATE_FILE_PATTERN,
|
|
19
19
|
INTEGRATION_FILE_PATTERN,
|
|
20
20
|
STARTUP_FILE_PATTERN,
|
|
21
|
+
EMAIL_FILE_PATTERN,
|
|
22
|
+
ACTION_FILE_PATTERN,
|
|
21
23
|
} from './get-platform-files.task.js'
|
|
22
24
|
import { manifestPlugin } from './manifest-plugin.js'
|
|
23
25
|
|
|
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 }
|
|
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 }
|
|
25
27
|
|
|
26
28
|
// Generated entry stubs live under `build/.ossy/entries/`. Putting them
|
|
27
29
|
// inside `build/` means they're cleaned automatically with every build,
|
|
@@ -71,7 +73,7 @@ function entryNameFromSource (sourcePath, srcDir, packageName) {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
function stubFileNameFor (kind, sourcePath, srcDir, packageName) {
|
|
74
|
-
const ext = (kind === 'page' || kind === 'component') ? '.entry.jsx' : '.entry.js'
|
|
76
|
+
const ext = (kind === 'page' || kind === 'component' || kind === 'email') ? '.entry.jsx' : '.entry.js'
|
|
75
77
|
return entryNameFromSource(sourcePath, srcDir, packageName) + ext
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -182,6 +184,29 @@ function generateStartupStub ({ stubAbs, sourceAbs }) {
|
|
|
182
184
|
].join('\n')
|
|
183
185
|
}
|
|
184
186
|
|
|
187
|
+
// Emails are React components. The stub re-exports `id`, `subject`, and the
|
|
188
|
+
// default component export so the manifest plugin can validate them.
|
|
189
|
+
function generateEmailStub ({ stubAbs, sourceAbs }) {
|
|
190
|
+
const importPath = relImport(stubAbs, sourceAbs)
|
|
191
|
+
return [
|
|
192
|
+
'// Generated by @ossy/app — do not edit',
|
|
193
|
+
`export { id, subject, default } from '${importPath}'`,
|
|
194
|
+
'',
|
|
195
|
+
].join('\n')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Actions are named command handlers. The stub re-exports `id`, `access`, and
|
|
199
|
+
// `run` so the manifest plugin can validate them and the platform server can
|
|
200
|
+
// invoke them at runtime.
|
|
201
|
+
function generateActionStub ({ stubAbs, sourceAbs }) {
|
|
202
|
+
const importPath = relImport(stubAbs, sourceAbs)
|
|
203
|
+
return [
|
|
204
|
+
'// Generated by @ossy/app — do not edit',
|
|
205
|
+
`export { id, access, run } from '${importPath}'`,
|
|
206
|
+
'',
|
|
207
|
+
].join('\n')
|
|
208
|
+
}
|
|
209
|
+
|
|
185
210
|
function stubFor (kind, args) {
|
|
186
211
|
if (kind === 'page') return generatePageStub(args)
|
|
187
212
|
if (kind === 'api') return generateApiStub(args)
|
|
@@ -190,6 +215,8 @@ function stubFor (kind, args) {
|
|
|
190
215
|
if (kind === 'aggregate') return generateAggregateStub(args)
|
|
191
216
|
if (kind === 'integration') return generateIntegrationStub(args)
|
|
192
217
|
if (kind === 'startup') return generateStartupStub(args)
|
|
218
|
+
if (kind === 'email') return generateEmailStub(args)
|
|
219
|
+
if (kind === 'action') return generateActionStub(args)
|
|
193
220
|
return generateTaskStub(args)
|
|
194
221
|
}
|
|
195
222
|
|
|
@@ -234,6 +261,8 @@ export async function build (cliArgs = []) {
|
|
|
234
261
|
...platformFiles.aggregates,
|
|
235
262
|
...platformFiles.integrations,
|
|
236
263
|
...platformFiles.startups,
|
|
264
|
+
...platformFiles.emails,
|
|
265
|
+
...platformFiles.actions,
|
|
237
266
|
]
|
|
238
267
|
|
|
239
268
|
const entriesByStub = new Map()
|
|
@@ -9,9 +9,11 @@ export const COMPONENT_FILE_PATTERN = /\.component\.(jsx?|tsx?)$/
|
|
|
9
9
|
export const AGGREGATE_FILE_PATTERN = /\.aggregate\.(mjs|cjs|js)$/
|
|
10
10
|
export const INTEGRATION_FILE_PATTERN = /\.integration\.(mjs|cjs|js)$/
|
|
11
11
|
export const STARTUP_FILE_PATTERN = /\.startup\.(mjs|cjs|js)$/
|
|
12
|
+
export const EMAIL_FILE_PATTERN = /\.email\.(jsx?|tsx?)$/
|
|
13
|
+
export const ACTION_FILE_PATTERN = /\.action\.(mjs|cjs|js)$/
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
* @typedef {'page' | 'api' | 'task' | 'resource' | 'component' | 'aggregate' | 'integration' | 'startup'} EntryKind
|
|
16
|
+
* @typedef {'page' | 'api' | 'task' | 'resource' | 'component' | 'aggregate' | 'integration' | 'startup' | 'email' | 'action'} EntryKind
|
|
15
17
|
*
|
|
16
18
|
* @typedef {object} PlatformEntry
|
|
17
19
|
* @property {EntryKind} kind Which bucket this entry lives in.
|
|
@@ -28,6 +30,8 @@ export const STARTUP_FILE_PATTERN = /\.startup\.(mjs|cjs|js)$/
|
|
|
28
30
|
* @property {PlatformEntry[]} aggregates Discovered `*.aggregate.{js,mjs,cjs}` entries (installed packages only).
|
|
29
31
|
* @property {PlatformEntry[]} integrations Discovered `*.integration.{js,mjs,cjs}` entries.
|
|
30
32
|
* @property {PlatformEntry[]} startups Discovered `*.startup.{js,mjs,cjs}` entries.
|
|
33
|
+
* @property {PlatformEntry[]} emails Discovered `*.email.{jsx,tsx,...}` entries.
|
|
34
|
+
* @property {PlatformEntry[]} actions Discovered `*.action.{js,mjs,cjs}` entries.
|
|
31
35
|
*/
|
|
32
36
|
export function discoverFilesByPattern (srcDir, filePattern) {
|
|
33
37
|
const dir = path.resolve(srcDir)
|
|
@@ -54,6 +58,8 @@ function classifyFile (absPath) {
|
|
|
54
58
|
if (AGGREGATE_FILE_PATTERN.test(base)) return 'aggregate'
|
|
55
59
|
if (INTEGRATION_FILE_PATTERN.test(base)) return 'integration'
|
|
56
60
|
if (STARTUP_FILE_PATTERN.test(base)) return 'startup'
|
|
61
|
+
if (EMAIL_FILE_PATTERN.test(base)) return 'email'
|
|
62
|
+
if (ACTION_FILE_PATTERN.test(base)) return 'action'
|
|
57
63
|
return null
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -126,6 +132,8 @@ export function discoverInstalledPackageEntries (projectRoot) {
|
|
|
126
132
|
...discoverFilesByPattern(packageSrcDir, AGGREGATE_FILE_PATTERN),
|
|
127
133
|
...discoverFilesByPattern(packageSrcDir, INTEGRATION_FILE_PATTERN),
|
|
128
134
|
...discoverFilesByPattern(packageSrcDir, STARTUP_FILE_PATTERN),
|
|
135
|
+
...discoverFilesByPattern(packageSrcDir, EMAIL_FILE_PATTERN),
|
|
136
|
+
...discoverFilesByPattern(packageSrcDir, ACTION_FILE_PATTERN),
|
|
129
137
|
]
|
|
130
138
|
for (const sourcePath of files) {
|
|
131
139
|
const stat = fs.statSync(sourcePath)
|
|
@@ -169,8 +177,8 @@ export function discoverInstalledPackageEntries (projectRoot) {
|
|
|
169
177
|
* @returns {Promise<PlatformFiles>}
|
|
170
178
|
*/
|
|
171
179
|
export default async function getPlatformFiles (srcDir) {
|
|
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' }
|
|
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' }
|
|
174
182
|
|
|
175
183
|
// Aggregates are only discovered from installed packages — never from the
|
|
176
184
|
// local `src/` — to avoid pulling in per-project duplicates of the same
|
|
@@ -183,6 +191,8 @@ export default async function getPlatformFiles (srcDir) {
|
|
|
183
191
|
...discoverFilesByPattern(srcDir, COMPONENT_FILE_PATTERN),
|
|
184
192
|
...discoverFilesByPattern(srcDir, INTEGRATION_FILE_PATTERN),
|
|
185
193
|
...discoverFilesByPattern(srcDir, STARTUP_FILE_PATTERN),
|
|
194
|
+
...discoverFilesByPattern(srcDir, EMAIL_FILE_PATTERN),
|
|
195
|
+
...discoverFilesByPattern(srcDir, ACTION_FILE_PATTERN),
|
|
186
196
|
]
|
|
187
197
|
for (const sourcePath of localFiles) {
|
|
188
198
|
const stat = fs.statSync(sourcePath)
|
package/cli/manifest-plugin.js
CHANGED
|
@@ -55,6 +55,15 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
55
55
|
* @property {string} id Unique startup slug (e.g. `'create-bot-user'`).
|
|
56
56
|
* @property {string} entry URL the platform serves the startup bundle from.
|
|
57
57
|
*
|
|
58
|
+
* @typedef {object} EmailManifestEntry
|
|
59
|
+
* @property {string} id Unique email id (e.g. `'authentication/verify-sign-in'`).
|
|
60
|
+
* @property {string} entry URL the platform serves the email bundle from.
|
|
61
|
+
*
|
|
62
|
+
* @typedef {object} ActionManifestEntry
|
|
63
|
+
* @property {string} id Unique action slug (e.g. `'authentication/request-sign-in'`).
|
|
64
|
+
* @property {string} entry URL the platform serves the action bundle from.
|
|
65
|
+
* @property {string} access Access level: `'public'` | `'authenticated'` | `'workspace'`.
|
|
66
|
+
*
|
|
58
67
|
* @typedef {object} Manifest
|
|
59
68
|
* @property {ManifestEntry[]} entries Flat, ordered list of every routable thing.
|
|
60
69
|
* @property {ComponentManifestEntry[]} components Discovered `*.component.*` entries, keyed by id.
|
|
@@ -62,6 +71,8 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
62
71
|
* @property {AggregateManifestEntry[]} aggregates Discovered `*.aggregate.js` entries from installed packages.
|
|
63
72
|
* @property {IntegrationManifestEntry[]} integrations Discovered `*.integration.js` entries.
|
|
64
73
|
* @property {StartupManifestEntry[]} startups Discovered `*.startup.js` entries.
|
|
74
|
+
* @property {EmailManifestEntry[]} emails Discovered `*.email.{jsx,tsx}` entries.
|
|
75
|
+
* @property {ActionManifestEntry[]} actions Discovered `*.action.js` entries.
|
|
65
76
|
* @property {object} config Inlined `src/config.js` default export.
|
|
66
77
|
*/
|
|
67
78
|
|
|
@@ -115,11 +126,17 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
115
126
|
const integrations = []
|
|
116
127
|
/** @type {StartupManifestEntry[]} */
|
|
117
128
|
const startups = []
|
|
129
|
+
/** @type {EmailManifestEntry[]} */
|
|
130
|
+
const emails = []
|
|
131
|
+
/** @type {ActionManifestEntry[]} */
|
|
132
|
+
const actions = []
|
|
118
133
|
const seenIds = { page: new Set(), api: new Set(), task: new Set(), component: new Set() }
|
|
119
134
|
const seenResourceIds = new Set()
|
|
120
135
|
const seenAggregateIds = new Set()
|
|
121
136
|
const seenIntegrationIds = new Set()
|
|
122
137
|
const seenStartupIds = new Set()
|
|
138
|
+
const seenEmailIds = new Set()
|
|
139
|
+
const seenActionIds = new Set()
|
|
123
140
|
|
|
124
141
|
for (const fileName of Object.keys(bundle)) {
|
|
125
142
|
const chunk = bundle[fileName]
|
|
@@ -197,6 +214,54 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
197
214
|
continue
|
|
198
215
|
}
|
|
199
216
|
|
|
217
|
+
// Emails are React components that render to HTML/text via EmailRenderer.
|
|
218
|
+
// The bundle exports `id` (a namespaced slug) and a default export
|
|
219
|
+
// (the component). They don't produce routable entries.
|
|
220
|
+
if (entryInfo.kind === 'email') {
|
|
221
|
+
const emailId = mod && mod.id
|
|
222
|
+
const component = mod && mod.default
|
|
223
|
+
if (typeof emailId !== 'string' || emailId.trim() === '') {
|
|
224
|
+
this.error(
|
|
225
|
+
`[@ossy/app][build] email entry ${entryInfo.sourcePath} must export a non-empty string "id"`,
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
if (typeof component !== 'function') {
|
|
229
|
+
this.error(
|
|
230
|
+
`[@ossy/app][build] email entry ${entryInfo.sourcePath} must default-export a component function`,
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
if (seenEmailIds.has(emailId)) {
|
|
234
|
+
this.error(`[@ossy/app][build] Duplicate email id "${emailId}"`)
|
|
235
|
+
}
|
|
236
|
+
seenEmailIds.add(emailId)
|
|
237
|
+
emails.push({ id: emailId, entry: url })
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Actions are named, discoverable command handlers. The bundle exports
|
|
242
|
+
// `id` (a unique slug), `run` (the handler function), and optionally
|
|
243
|
+
// `access` ('public' | 'authenticated' | 'workspace', default 'authenticated').
|
|
244
|
+
if (entryInfo.kind === 'action') {
|
|
245
|
+
const actionId = mod && mod.id
|
|
246
|
+
if (typeof actionId !== 'string' || actionId.trim() === '') {
|
|
247
|
+
this.error(
|
|
248
|
+
`[@ossy/app][build] action entry ${entryInfo.sourcePath} must export a non-empty string "id"`,
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
if (typeof mod.run !== 'function') {
|
|
252
|
+
this.error(
|
|
253
|
+
`[@ossy/app][build] action entry ${entryInfo.sourcePath} must export a "run" function`,
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
if (seenActionIds.has(actionId)) {
|
|
257
|
+
this.error(`[@ossy/app][build] Duplicate action id "${actionId}"`)
|
|
258
|
+
}
|
|
259
|
+
const access = mod.access ?? 'authenticated'
|
|
260
|
+
seenActionIds.add(actionId)
|
|
261
|
+
actions.push({ id: actionId, entry: url, access })
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
|
|
200
265
|
// Aggregates are server-side classes — the bundle exports `Aggregate`
|
|
201
266
|
// (the class) and `id` (the AggregateType string). They don't produce
|
|
202
267
|
// routable entries; they accumulate into a separate `aggregates` array.
|
|
@@ -282,7 +347,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
282
347
|
}
|
|
283
348
|
|
|
284
349
|
/** @type {Manifest} */
|
|
285
|
-
const manifest = { entries, components, resourceTemplates, aggregates, integrations, startups, config: configValue || {} }
|
|
350
|
+
const manifest = { entries, components, resourceTemplates, aggregates, integrations, startups, emails, actions, config: configValue || {} }
|
|
286
351
|
fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
|
|
287
352
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
|
|
288
353
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
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.35.0",
|
|
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.34.0",
|
|
44
|
+
"@ossy/router": "^1.35.0",
|
|
45
|
+
"@ossy/router-react": "^1.35.0",
|
|
46
|
+
"@ossy/sdk": "^1.35.0",
|
|
47
|
+
"@ossy/sdk-react": "^1.35.0",
|
|
48
|
+
"@ossy/themes": "^1.35.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": "
|
|
82
|
+
"gitHead": "c6697078268867d88553ca0bac08faad5cea1546"
|
|
83
83
|
}
|