@synap-core/workspace-templates 0.11.0 → 0.23.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 +483 -0
- package/automations/bookmark-enrichment.automation.json +321 -0
- package/automations/github-pr-events.automation.json +505 -0
- package/automations/mail-feed.automation.json +86 -0
- package/automations/ship-a-change-checks-green.automation.json +106 -0
- package/automations/ship-a-change-checks-red.automation.json +106 -0
- package/automations/ship-a-change-deployed.automation.json +56 -0
- package/automations/ship-a-change-pr-merged.automation.json +110 -0
- package/automations/ship-a-change-pr-opened.automation.json +104 -0
- package/automations/url-bookmark-capture.automation.json +132 -0
- package/capabilities/agency-skills.capability.json +157 -0
- package/capabilities/apify.capability.json +265 -0
- package/capabilities/apollo.capability.json +120 -0
- package/capabilities/arch-backend.capability.json +97 -0
- package/capabilities/arch-client-intelligence.capability.json +401 -0
- package/capabilities/backblaze-b2.capability.json +205 -0
- package/capabilities/cal-com.capability.json +266 -0
- package/capabilities/canva.capability.json +274 -0
- package/capabilities/claude-code-routines.capability.json +172 -0
- package/capabilities/claude-managed-agents.capability.json +215 -0
- package/capabilities/client-comms-report.capability.json +246 -0
- package/capabilities/content-idea-to-production.capability.json +29 -0
- package/capabilities/datagouv.capability.json +315 -0
- package/capabilities/discord-bot.capability.json +157 -0
- package/capabilities/dropbox.capability.json +281 -0
- package/capabilities/exa.capability.json +109 -0
- package/capabilities/fal-ai.capability.json +83 -0
- package/capabilities/fal-media-lab.capability.json +158 -0
- package/capabilities/fireflies.capability.json +191 -0
- package/capabilities/freellmapi.capability.json +114 -0
- package/capabilities/generic-apikey.capability.json +92 -0
- package/capabilities/github-merge.capability.json +99 -0
- package/capabilities/github-read.capability.json +325 -0
- package/capabilities/governance-tighten-recommender.capability.json +81 -0
- package/capabilities/jev.capability.json +226 -0
- package/capabilities/lead-conversion-enrollment.capability.json +246 -0
- package/capabilities/mail-feed.capability.json +277 -0
- package/capabilities/mailgun.capability.json +52 -0
- package/capabilities/nango-google.capability.json +639 -0
- package/capabilities/notion.capability.json +417 -0
- package/capabilities/omniroute.capability.json +137 -0
- package/capabilities/posthog-analytics.capability.json +258 -0
- package/capabilities/postiz.capability.json +467 -0
- package/capabilities/proactive-digest.capability.json +190 -0
- package/capabilities/proton.capability.json +288 -0
- package/capabilities/replicate-rvm.capability.json +97 -0
- package/capabilities/research-methods.capability.json +57 -0
- package/capabilities/shopify.capability.json +395 -0
- package/capabilities/slack.capability.json +369 -0
- package/capabilities/stripe.capability.json +420 -0
- package/capabilities/telegram-bridge.capability.json +56 -0
- package/capabilities/treg.capability.json +102 -0
- package/capabilities/unipile-linkedin.capability.json +471 -0
- package/capabilities/vercel.capability.json +424 -0
- package/dist/define-CPS0n8BV.d.ts +1331 -0
- package/dist/index.d.ts +189 -900
- package/dist/index.js +24654 -21639
- package/dist/index.js.map +1 -1
- package/dist/wire.d.ts +222 -0
- package/dist/wire.js +77 -0
- package/dist/wire.js.map +1 -0
- package/package.json +41 -27
- package/packs/bookmark-views-pack.json +33 -0
- package/packs/crm-pack.json +76 -0
- package/packs/customer-profile.json +28 -0
- package/packs/deep-research.playbook.json +30 -0
- package/packs/event-views-pack.json +33 -0
- package/packs/habit-profile.json +27 -0
- package/packs/hr-pack.json +54 -0
- package/packs/invoice-profile.json +28 -0
- package/packs/knowledge-base-pack.json +56 -0
- package/packs/meeting-profile.json +29 -0
- package/packs/product-profile.json +30 -0
- package/packs/project-management-pack.json +49 -0
- package/packs/recipe-profile.json +29 -0
- package/packs/task-views-pack.json +39 -0
- package/retired.yaml +136 -0
- package/skills/app-factory-build-slice/SKILL.md +70 -0
- package/skills/app-factory-build-slice.catalog.json +17 -0
- package/skills/app-factory-design-canvas/SKILL.md +96 -0
- package/skills/app-factory-design-canvas.catalog.json +17 -0
- package/skills/app-factory-design-review/SKILL.md +99 -0
- package/skills/app-factory-design-review.catalog.json +18 -0
- package/skills/app-factory-research/SKILL.md +123 -0
- package/skills/app-factory-research.catalog.json +17 -0
- package/skills/bookmark-enrichment/SKILL.md +40 -0
- package/skills/carousel-format/carousel-format.skill.json +18 -0
- package/skills/collage-broll/collage-broll.skill.json +20 -0
- package/skills/content-taste/content-taste.skill.json +18 -0
- package/skills/creative-director/creative-director.skill.json +18 -0
- package/skills/design-critique/SKILL.md +135 -0
- package/skills/design-critique.catalog.json +12 -0
- package/skills/designing-with-real-components/SKILL.md +140 -0
- package/skills/designing-with-real-components.catalog.json +12 -0
- package/skills/edit-loop/edit-loop.skill.json +25 -0
- package/skills/facecam-edit/facecam-edit.skill.json +24 -0
- package/skills/mapping-the-server/SKILL.md +54 -0
- package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
- package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
- package/skills/open-design-edit/open-design-edit.skill.json +24 -0
- package/skills/reading-client-progress/SKILL.md +65 -0
- package/skills/routing-to-channels/SKILL.md +54 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
- package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
- package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
- package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
- package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
- package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
- package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
- package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
- package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
- package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
- package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
- package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
- package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
- package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
- package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
- package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
- package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
- package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
- package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
- package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
- package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
- package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
- package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
- package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
- package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
- package/skills/video-direction/video-direction.skill.json +20 -0
- package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
- package/skills/video-toolbox/video-toolbox.skill.json +20 -0
- package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
- package/src/agency-os.yaml +122 -0
- package/src/agent-os-skill-slugs.test.ts +65 -0
- package/src/apify-social-reader.test.ts +317 -0
- package/src/base.template.test.ts +25 -19
- package/src/base.yaml +95 -39
- package/src/blockchain-ecosystem.yaml +1 -0
- package/src/brand-library.yaml +525 -14
- package/src/builder-dev-fold.template.test.ts +113 -0
- package/src/builder.yaml +798 -92
- package/src/business-model-catalog-description.test.ts +47 -0
- package/src/business-model-grp.template.test.ts +214 -0
- package/src/business-model.yaml +598 -0
- package/src/catalog-safety.test.ts +198 -0
- package/src/catalog.template-items.test.ts +75 -0
- package/src/catalog.ts +85 -2
- package/src/civic-france.yaml +102 -0
- package/src/concept-guard.test.ts +28 -8
- package/src/concept-guard.ts +2 -2
- package/src/content-creation.template.test.ts +52 -0
- package/src/content-creation.yaml +192 -0
- package/src/content-os.edit-loop.template.test.ts +210 -0
- package/src/content-os.publish-due-posts.template.test.ts +261 -0
- package/src/content-os.template.test.ts +322 -0
- package/src/content-os.video-takes.template.test.ts +338 -0
- package/src/content-os.work-a-take.template.test.ts +125 -0
- package/src/content-os.yaml +1830 -39
- package/src/content-studio-retired.template.test.ts +82 -0
- package/src/corpus-flow-validity.test.ts +4 -5
- package/src/crm-absorbs-outreach.template.test.ts +117 -0
- package/src/crm-build-plan.template.test.ts +128 -0
- package/src/crm-lists.template.test.ts +96 -0
- package/src/crm-messages-cadence.template.test.ts +164 -0
- package/src/crm-one-send-door.template.test.ts +65 -0
- package/src/crm.yaml +1456 -181
- package/src/decisions-wave.template.test.ts +186 -0
- package/src/define.ts +130 -17
- package/src/design-to-implementation.template.test.ts +94 -0
- package/src/dist-freshness.test.ts +97 -0
- package/src/domain-template-type.test.ts +75 -0
- package/src/ecosystem.yaml +27 -66
- package/src/enterprise-os.yaml +10 -134
- package/src/facecam-edit.audio-studio.test.ts +188 -0
- package/src/fal-generate-image.test.ts +200 -0
- package/src/fal-media-lab-pod-files.test.ts +180 -0
- package/src/finance.yaml +254 -1
- package/src/foundation.yaml +130 -28
- package/src/frame-host-modules.d.mts +6 -0
- package/src/frame-host-modules.mjs +58 -0
- package/src/generate.mjs +9 -3
- package/src/github-merge.test.ts +210 -0
- package/src/grants.yaml +57 -17
- package/src/hr.yaml +39 -6
- package/src/index.ts +36 -1
- package/src/intent.test.ts +161 -0
- package/src/internal-runbook.yaml +19 -1
- package/src/jev.test.ts +356 -0
- package/src/legal.yaml +33 -1
- package/src/life-os.yaml +65 -240
- package/src/lifecycle-coverage.test.ts +124 -0
- package/src/lifecycle-declarations.test.ts +252 -0
- package/src/lifecycle-presets.test.ts +468 -0
- package/src/lifecycle-presets.ts +494 -0
- package/src/lifecycle-stuck.test.ts +46 -0
- package/src/lifecycle-waits.parity.test.ts +319 -0
- package/src/marketing.yaml +108 -4
- package/src/meetings-knowledge.yaml +18 -0
- package/src/networking.yaml +23 -2
- package/src/operations.yaml +304 -4
- package/src/pipeline-views.template.test.ts +88 -0
- package/src/playbook-goal-placeholders.test.ts +57 -0
- package/src/postiz-contract.test.ts +134 -0
- package/src/presentation-declarations.test.ts +174 -0
- package/src/process-contract.validate.test.ts +352 -0
- package/src/project-management.yaml +39 -740
- package/src/publish-body.d.mts +29 -0
- package/src/publish-body.mjs +74 -0
- package/src/publish-body.test.ts +74 -0
- package/src/publish-official-lib.test.ts +176 -0
- package/src/replicate-rvm.test.ts +504 -0
- package/src/research-base.yaml +119 -20
- package/src/research-decision.yaml +60 -3
- package/src/reserved-profile-slugs.test.ts +83 -0
- package/src/retired-goal-kinds.test.ts +58 -0
- package/src/role-reference-and-step-domain.test.ts +192 -0
- package/src/seed-obligation.test.ts +176 -0
- package/src/send-message-maps.test.ts +227 -0
- package/src/shared-role-ssot.test.ts +42 -1
- package/src/ship-a-change.template.test.ts +177 -0
- package/src/skills.test.ts +170 -0
- package/src/social.yaml +85 -27
- package/src/space-packages.freshness.test.ts +97 -0
- package/src/space-packages.generated.ts +40 -0
- package/src/space-packages.test.ts +125 -0
- package/src/space-packages.ts +73 -0
- package/src/start.test.ts +188 -0
- package/src/task-intents.test.ts +293 -0
- package/src/templates.ts +21639 -21522
- package/src/types.ts +443 -60
- package/src/unipile-publish-post.test.ts +162 -0
- package/src/validate-flow.drift.test.ts +2 -3
- package/src/validate-flow.ts +32 -2
- package/src/validate-onboarding.test.ts +59 -0
- package/src/validate.test.ts +156 -3
- package/src/validate.ts +1384 -7
- package/src/wire-core.ts +231 -0
- package/src/wire.test.ts +159 -0
- package/src/wire.ts +107 -0
- package/src/__fixtures__/report-automation-flow.json +0 -393
- package/src/agent-fleet.yaml +0 -1645
- package/src/business-developer.template.test.ts +0 -351
- package/src/business-developer.yaml +0 -860
- package/src/content-studio.yaml +0 -361
- package/src/dev-dashboard.yaml +0 -990
- package/src/outreach-comms.yaml +0 -486
- package/src/the-arch.yaml +0 -201
package/src/social.yaml
CHANGED
|
@@ -13,6 +13,11 @@ meta:
|
|
|
13
13
|
color: "#1D9BF0"
|
|
14
14
|
tags: [social, distribution, publishing, channels]
|
|
15
15
|
isPublic: true
|
|
16
|
+
# What this space PRODUCES (homes): the purpose word, then its kinds/roles.
|
|
17
|
+
intent:
|
|
18
|
+
label: Distribution
|
|
19
|
+
produces:
|
|
20
|
+
- profile: post
|
|
16
21
|
|
|
17
22
|
# Social REUSES Content OS's pod-wide `platform` (publishing-account) vocabulary
|
|
18
23
|
# instead of forking its own `channel` kind. `platform` is `scope: shared` in
|
|
@@ -23,7 +28,7 @@ dependencies:
|
|
|
23
28
|
- slug: content-os
|
|
24
29
|
kind: workspace
|
|
25
30
|
relation: require
|
|
26
|
-
reason: "Reuses Content OS's shared platform (publishing-account) vocabulary."
|
|
31
|
+
reason: "Reuses Content OS's shared platform (publishing-account) and post vocabulary."
|
|
27
32
|
|
|
28
33
|
workspace:
|
|
29
34
|
name: Social
|
|
@@ -125,44 +130,97 @@ profiles:
|
|
|
125
130
|
- slug: platform-tags
|
|
126
131
|
label: Tags
|
|
127
132
|
valueType: array
|
|
133
|
+
# ── Postiz channel (the `postiz` capability) ─────────────────────────
|
|
134
|
+
# Which Postiz channel this account is, both read from
|
|
135
|
+
# postiz_list_integrations: its id, and its provider (the `identifier`
|
|
136
|
+
# a post's `settings.__type` names: x, linkedin, instagram…). A post
|
|
137
|
+
# queued on this account passes both to postiz_schedule_post.
|
|
138
|
+
- slug: postiz-integration-id
|
|
139
|
+
label: Postiz channel ID
|
|
140
|
+
valueType: string
|
|
141
|
+
inputType: text
|
|
142
|
+
- slug: postiz-provider
|
|
143
|
+
label: Postiz provider
|
|
144
|
+
valueType: string
|
|
145
|
+
inputType: text
|
|
128
146
|
|
|
129
|
-
|
|
147
|
+
# ── Post (shared post kind — SSOT is content-os.yaml) ──────────────────────
|
|
148
|
+
# RE-DECLARED, like `platform` above: this declaration writes the grant that
|
|
149
|
+
# puts the ONE pod-wide `post` kind in this workspace. The old `social-post`
|
|
150
|
+
# twin is folded into it by the w11.post.* manifest ops (plan D2); its
|
|
151
|
+
# `failed` status became `publish-outcome` (plan D12). Only the properties
|
|
152
|
+
# Social uses are re-declared, each byte-identical to content-os's.
|
|
153
|
+
- slug: post
|
|
130
154
|
displayName: Post
|
|
131
|
-
icon:
|
|
132
|
-
color: "#
|
|
133
|
-
description: "
|
|
155
|
+
icon: file-edit
|
|
156
|
+
color: "#6366F1"
|
|
157
|
+
description: "Your atomic content unit. One idea, multiple platform formats — tracked from spark to publication."
|
|
158
|
+
scope: shared
|
|
159
|
+
# The ONE lifecycle (work model L1): every option's category.
|
|
160
|
+
lifecycle:
|
|
161
|
+
property: post-status
|
|
162
|
+
options:
|
|
163
|
+
- { key: Idea, category: open }
|
|
164
|
+
- { key: Briefed, category: active }
|
|
165
|
+
- { key: Scripted, category: active }
|
|
166
|
+
- { key: "In Draft", category: active }
|
|
167
|
+
- { key: Review, category: active }
|
|
168
|
+
- { key: Scheduled, category: active }
|
|
169
|
+
- { key: Published, category: done }
|
|
170
|
+
- { key: Archived, category: lost, humanOnly: true }
|
|
171
|
+
# How it renders (entity rendering, decision 8): @synap-core/types/presentation.
|
|
172
|
+
presentation:
|
|
173
|
+
family: work
|
|
134
174
|
properties:
|
|
135
175
|
- slug: post-status
|
|
136
176
|
label: Status
|
|
137
177
|
valueType: string
|
|
138
178
|
inputType: select
|
|
139
|
-
enumValues:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
179
|
+
enumValues:
|
|
180
|
+
[
|
|
181
|
+
"Idea",
|
|
182
|
+
"Briefed",
|
|
183
|
+
"Scripted",
|
|
184
|
+
"In Draft",
|
|
185
|
+
"Review",
|
|
186
|
+
"Scheduled",
|
|
187
|
+
"Published",
|
|
188
|
+
"Archived",
|
|
189
|
+
]
|
|
190
|
+
- slug: post-account
|
|
191
|
+
label: Account
|
|
143
192
|
valueType: entity_id
|
|
144
193
|
targetProfileSlug: platform
|
|
145
194
|
inputType: entity_select
|
|
146
|
-
- slug:
|
|
147
|
-
label:
|
|
195
|
+
- slug: publish-date
|
|
196
|
+
label: Publish Date
|
|
197
|
+
valueType: date
|
|
198
|
+
- slug: post-url
|
|
199
|
+
label: Published URL
|
|
200
|
+
valueType: string
|
|
201
|
+
inputType: text
|
|
202
|
+
placeholder: "https://..."
|
|
203
|
+
constraints: { format: url }
|
|
204
|
+
- slug: publish-outcome
|
|
205
|
+
label: Publish outcome
|
|
206
|
+
valueType: string
|
|
207
|
+
inputType: select
|
|
208
|
+
enumValues: [succeeded, failed, queued, sending, waiting]
|
|
209
|
+
- slug: publish-error
|
|
210
|
+
label: Publish error
|
|
148
211
|
valueType: string
|
|
149
212
|
inputType: textarea
|
|
150
|
-
- slug: scheduled-at
|
|
151
|
-
label: Scheduled For
|
|
152
|
-
valueType: date
|
|
153
|
-
inputType: date
|
|
154
213
|
- slug: published-at
|
|
155
214
|
label: Published At
|
|
156
215
|
valueType: date
|
|
157
216
|
inputType: date
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
label: Notes
|
|
217
|
+
# ── Postiz (the `postiz` capability) ─────────────────────────────────
|
|
218
|
+
# The post's id in the person's Postiz, recorded when it is queued there
|
|
219
|
+
# (postiz_schedule_post returns it). Postiz's webhook finds the post by it.
|
|
220
|
+
- slug: postiz-post-id
|
|
221
|
+
label: Postiz post ID
|
|
164
222
|
valueType: string
|
|
165
|
-
inputType:
|
|
223
|
+
inputType: text
|
|
166
224
|
|
|
167
225
|
- slug: post-schedule
|
|
168
226
|
displayName: Schedule
|
|
@@ -238,9 +296,9 @@ views:
|
|
|
238
296
|
- name: Content Calendar
|
|
239
297
|
slug: content-calendar
|
|
240
298
|
type: kanban
|
|
241
|
-
scopeProfileSlug:
|
|
299
|
+
scopeProfileSlug: post
|
|
242
300
|
groupBy: post-status
|
|
243
|
-
description: "Posts on a board by status —
|
|
301
|
+
description: "Posts on a board by status — idea to published."
|
|
244
302
|
- name: Platforms
|
|
245
303
|
slug: platforms
|
|
246
304
|
type: table
|
|
@@ -268,10 +326,10 @@ onboarding:
|
|
|
268
326
|
what: The platforms you actually publish on — one per real account, with its type and handle.
|
|
269
327
|
cardinality: few
|
|
270
328
|
keyFields: [platform-type, handle]
|
|
271
|
-
- profileSlug:
|
|
272
|
-
what: The posts in motion — drafts, scheduled, and recently published — each tied to its
|
|
329
|
+
- profileSlug: post
|
|
330
|
+
what: The posts in motion — drafts, scheduled, and recently published — each tied to its account.
|
|
273
331
|
cardinality: several
|
|
274
|
-
keyFields: [post-status, post-
|
|
332
|
+
keyFields: [post-status, post-account]
|
|
275
333
|
openingQuestions:
|
|
276
334
|
- "Which channels are you actually active on right now, and which one earns the most attention?"
|
|
277
335
|
- "What's your posting cadence per channel — daily, weekly, ad-hoc?"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FRESHNESS GATE for the generated-and-committed `src/space-packages.generated.ts`.
|
|
3
|
+
*
|
|
4
|
+
* A space package bundles ONLY its own modules
|
|
5
|
+
* (`packages/web/src/space-packages/`). The shared components come from the
|
|
6
|
+
* host's `@synap/kit` frame module (`packages/web/src/space-packages/kit.ts`,
|
|
7
|
+
* built into `packages/web/src/cell-runtime/frame-host-modules.generated.ts`,
|
|
8
|
+
* whose own freshness test sits beside it), React from the host's React
|
|
9
|
+
* modules. The build is committed, so it can go stale: a package source
|
|
10
|
+
* changes, nobody rebuilds, and the space keeps shipping the old code. This
|
|
11
|
+
* test re-reads what the build recorded: every repo file the bundle read must
|
|
12
|
+
* still hash the same.
|
|
13
|
+
*
|
|
14
|
+
* It also pins the KIT CONTRACT: every name a package imports from
|
|
15
|
+
* `@synap/kit` is one the host's kit exports, so a kit change that drops a
|
|
16
|
+
* name fails here, not as a frame that cannot link.
|
|
17
|
+
*
|
|
18
|
+
* And it pins the declaration to the build: every template's
|
|
19
|
+
* `spacePackage.cells[]` is built (same key, same source), and nothing is
|
|
20
|
+
* built for a cell no template declares.
|
|
21
|
+
*/
|
|
22
|
+
import { createHash } from 'node:crypto';
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
24
|
+
import { dirname, join, resolve } from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { describe, expect, it } from 'vitest';
|
|
27
|
+
import { readYamlTemplates } from './generate.mjs';
|
|
28
|
+
import { BUILT_SPACE_PACKAGES } from './space-packages.generated';
|
|
29
|
+
import { hostRequiresIn, kitImportsIn } from './frame-host-modules.mjs';
|
|
30
|
+
|
|
31
|
+
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
32
|
+
const REBUILD = 'pnpm --filter @synap-core/workspace-templates build:space-packages';
|
|
33
|
+
const SPACE_PACKAGES_DIR = 'packages/web/src/space-packages/';
|
|
34
|
+
const HOST_MODULES_FILE = join(REPO_ROOT, 'packages/web/src/cell-runtime/frame-host-modules.generated.ts');
|
|
35
|
+
|
|
36
|
+
function sha256(path: string): string {
|
|
37
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The names the host's kit exports, read from the host's generated file (no cross-package import). */
|
|
41
|
+
function hostKitExports(): string[] {
|
|
42
|
+
const m = /export const FRAME_KIT_EXPORTS: readonly string\[\] = (\[[^\]]*\]);/.exec(readFileSync(HOST_MODULES_FILE, 'utf8'));
|
|
43
|
+
if (!m) throw new Error(`no FRAME_KIT_EXPORTS in ${HOST_MODULES_FILE}`);
|
|
44
|
+
return JSON.parse(m[1]!) as string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const declared = readYamlTemplates().flatMap(({ slug, data }) =>
|
|
48
|
+
((data?.spacePackage?.cells ?? []) as Array<{ key: string; source: string }>).map((c) => ({ slug, ...c }))
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
describe('space-packages.generated.ts freshness', () => {
|
|
52
|
+
it('builds exactly the cells the templates declare', () => {
|
|
53
|
+
const built = Object.entries(BUILT_SPACE_PACKAGES).flatMap(([slug, pkg]) =>
|
|
54
|
+
pkg.cells.map((c) => ({ slug, key: c.key, source: c.source }))
|
|
55
|
+
);
|
|
56
|
+
const id = (c: { slug: string; key: string; source: string }) => `${c.slug}:${c.key} ← ${c.source}`;
|
|
57
|
+
expect(built.map(id).sort(), `stale space packages — run: ${REBUILD}`).toEqual(declared.map(id).sort());
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
for (const [slug, pkg] of Object.entries(BUILT_SPACE_PACKAGES)) {
|
|
61
|
+
it(`${slug}: every repo file the bundle read is unchanged`, () => {
|
|
62
|
+
const changed = Object.entries(pkg.inputs)
|
|
63
|
+
.filter(([path, hash]) => !existsSync(join(REPO_ROOT, path)) || sha256(join(REPO_ROOT, path)) !== hash)
|
|
64
|
+
.map(([path]) => path);
|
|
65
|
+
expect(changed, `space package "${slug}" was built from an older source — run: ${REBUILD}`).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it(`${slug}: it bundles only its own modules; the shared layers come from the host's kit`, () => {
|
|
69
|
+
const inputs = Object.keys(pkg.inputs);
|
|
70
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
71
|
+
expect(inputs.filter((p) => !p.startsWith(SPACE_PACKAGES_DIR))).toEqual([]);
|
|
72
|
+
expect(inputs).not.toContain(`${SPACE_PACKAGES_DIR}kit.ts`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it(`${slug}: every name it imports from @synap/kit is one the host's kit exports`, () => {
|
|
76
|
+
const exported = hostKitExports();
|
|
77
|
+
for (const cell of pkg.cells) {
|
|
78
|
+
// The record is the code's own imports, not a hand-kept list.
|
|
79
|
+
expect(cell.kitImports, cell.key).toEqual(kitImportsIn(cell.code));
|
|
80
|
+
expect(cell.kitImports.length, cell.key).toBeGreaterThan(0);
|
|
81
|
+
const missing = cell.kitImports.filter((n) => !exported.includes(n));
|
|
82
|
+
expect(missing, `${slug}:${cell.key} imports names the host's kit no longer exports — run: ${REBUILD}`).toEqual([]);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it(`${slug}: the bundle runs as a network-contained frame module`, () => {
|
|
87
|
+
for (const cell of pkg.cells) {
|
|
88
|
+
// A frame has no network origin for code: esm.sh is in no frame's CSP.
|
|
89
|
+
expect(cell.code.includes('esm.sh'), cell.key).toBe(false);
|
|
90
|
+
expect(cell.code.length, cell.key).toBeGreaterThan(0);
|
|
91
|
+
// A CJS `require()` of a host module throws in the frame (esbuild's
|
|
92
|
+
// `__require` shim): the module would never link.
|
|
93
|
+
expect(hostRequiresIn(cell.code), cell.key).toEqual([]);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// GENERATED by scripts/build-space-packages.mjs from each template's
|
|
2
|
+
// `spacePackage` — do not edit. Rebuild:
|
|
3
|
+
// pnpm --filter @synap-core/workspace-templates build:space-packages
|
|
4
|
+
// `space-packages.freshness.test.ts` fails when this file no longer matches its sources.
|
|
5
|
+
import type { BuiltSpacePackage } from './space-packages';
|
|
6
|
+
|
|
7
|
+
export const BUILT_SPACE_PACKAGES: Record<string, BuiltSpacePackage> = {
|
|
8
|
+
"builder-workspace": {
|
|
9
|
+
"cells": [
|
|
10
|
+
{
|
|
11
|
+
"key": "home",
|
|
12
|
+
"name": "Work",
|
|
13
|
+
"source": "packages/web/src/space-packages/builder/home.tsx",
|
|
14
|
+
"code": "import{useMemo as $}from\"react\";import{installFrameBridge as q}from\"@synap/kit\";import{useCallback as D,useEffect as O,useMemo as T,useState as _}from\"react\";import{LensPageView as x,SpatialAppHeader as M,SpatialButton as W,SpatialPageFrame as j,resolveActionLabel as V,resolveDestinationLabel as Y,resolveObjectNoun as z}from\"@synap/kit\";import{useCallback as E,useEffect as f,useState as g}from\"react\";import{SYNAP_CHANGED_EVENT as b,useLensPageClock as R,useLensPageModelOf as B}from\"@synap/kit\";var C=\"lens.page\",F=750;function H(e){if(!e||typeof e!=\"object\")return new Error(\"The lens page read returned nothing\");let t=e;return typeof t.error==\"string\"?new Error(t.error):!t.page||typeof t.page!=\"object\"?new Error(\"The lens page read returned no page\"):t.page}function S(e,t){let n=R(t),{since:o}=n,[i,c]=g({status:\"loading\",page:null,error:null}),[m,u]=g(0);f(()=>{if(!t)return;let s=!0;return c(r=>({...r,status:r.page?r.status:\"loading\"})),e.query(C,o?{since:o}:{}).then(r=>{if(!s)return;let p=H(r);c(p instanceof Error?{status:\"failed\",page:null,error:p.message}:{status:\"ready\",page:p,error:null})}).catch(r=>{s&&c({status:\"failed\",page:null,error:String(r)})}),()=>{s=!1}},[e,t,o,m]),f(()=>{if(!t)return;let s,r=()=>{s===void 0&&(s=setTimeout(()=>{s=void 0,u(v=>v+1)},F))},p=()=>{document.visibilityState===\"visible\"&&r()};document.addEventListener(\"visibilitychange\",p),window.addEventListener(\"focus\",r);let h=e.subscribe(\"entity:changed\",r),P=e.subscribe(b,r);return()=>{clearTimeout(s),document.removeEventListener(\"visibilitychange\",p),window.removeEventListener(\"focus\",r),h(),P()}},[e,t]);let a=B(i.page,n),l=E(()=>u(s=>s+1),[]);return{model:a,status:a?i.status:i.status===\"failed\"?\"failed\":\"loading\",error:i.error,refetch:l,today:n.today}}import{encodeActivityScope as I,encodeLensScope as y,lensActivityScope as A}from\"@synap/kit\";function N(e,t){let n=y(e);return{blocking:{kind:\"app\",appId:\"inbox\",props:{scope:n}},happening:{kind:\"app\",appId:\"sessions\",props:{view:\"sessions\",scope:n,narrow:\"running\"}},produced:{kind:\"app\",appId:\"data\",props:{view:\"landed\",scope:I(A(e))}},happened:{kind:\"app\",appId:\"sessions\",props:{view:\"activity\",day:t,scope:n}}}}function L(e){return{kind:\"app\",appId:\"sessions\",props:{view:\"plan\",scope:y(e)}}}function k(e,t,n){let o=N(t,n);return{blocking:()=>e(o.blocking),happening:()=>e(o.happening),produced:()=>e(o.produced),happened:()=>e(o.happened)}}import{jsx as d}from\"react/jsx-runtime\";var G=\"Open a decision to answer it on its own page.\";function U(e){let[t,n]=_(null);return O(()=>{e.onInit(o=>n({scope:o.workspaceId?{kind:\"workspace\",workspaceId:o.workspaceId}:{kind:\"pod\"},name:o.workspaceName?.trim()||null}))},[e]),t}function w({bridge:e}){let t=U(e),n=t?.scope??null,o=S(e,n),i=D((a,l)=>e.openSurface({kind:\"object\",objectKind:a.kind,objectId:a.id,...a.view?{view:a.view}:{},...l?{title:l}:{}}),[e]),c=T(()=>n?k(a=>e.openSurface(a),n,o.today):{},[e,n,o.today]),m=Y(\"work\"),u=o.status===\"ready\"&&(o.model?.blocking.items.length??0)>0;return d(j,{className:\"space-work-home\",template:\"hub\",layout:\"column\",width:\"list\",header:d(M,{size:\"lg\",title:t?.name??z(\"workspace\"),...u?{subtitle:G}:{},...n?{actions:d(W,{size:\"sm\",variant:\"secondary\",onClick:()=>e.openSurface(L(n)),children:`${V(\"open\")} in ${m}`})}:{}}),children:d(x,{scope:n??{kind:\"pod\"},model:n?o.model:null,status:n?o.status:\"loading\",onRetry:o.refetch,today:o.today,doors:c,onOpen:i,columns:!0})})}import{jsx as Q}from\"react/jsx-runtime\";function K(){let e=$(()=>q(),[]);return Q(w,{bridge:e})}export{K as default};\n",
|
|
15
|
+
"kitImports": [
|
|
16
|
+
"LensPageView",
|
|
17
|
+
"SYNAP_CHANGED_EVENT",
|
|
18
|
+
"SpatialAppHeader",
|
|
19
|
+
"SpatialButton",
|
|
20
|
+
"SpatialPageFrame",
|
|
21
|
+
"encodeActivityScope",
|
|
22
|
+
"encodeLensScope",
|
|
23
|
+
"installFrameBridge",
|
|
24
|
+
"lensActivityScope",
|
|
25
|
+
"resolveActionLabel",
|
|
26
|
+
"resolveDestinationLabel",
|
|
27
|
+
"resolveObjectNoun",
|
|
28
|
+
"useLensPageClock",
|
|
29
|
+
"useLensPageModelOf"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"inputs": {
|
|
34
|
+
"packages/web/src/space-packages/builder/home.tsx": "7afed696a23b1a474eace193d85688233e2f652e5e4ca56926edc8e1a16e62ef",
|
|
35
|
+
"packages/web/src/space-packages/frame-doors.ts": "662fbd70165f21a758b8677ac7a1169030f76996921e322159ccd67e19ee7520",
|
|
36
|
+
"packages/web/src/space-packages/frame-lens.ts": "627d26ad593a327f4d3cb50ceb3f04529200f99bb97719d02edc346ff60c2073",
|
|
37
|
+
"packages/web/src/space-packages/SpaceWorkHome.tsx": "e44497878da09b9ac0e9356440196e5973329a5ddef462991d9deb7087444718"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { toPackageDefinition, toWorkspaceDefinition } from './define.js';
|
|
3
|
+
import { spacePackageCells, type BuiltSpacePackage } from './space-packages.js';
|
|
4
|
+
import type { WorkspaceYaml } from './types.js';
|
|
5
|
+
import { validateTemplate } from './validate.js';
|
|
6
|
+
|
|
7
|
+
const SOURCE = 'packages/web/src/space-packages/fixture/home.tsx';
|
|
8
|
+
|
|
9
|
+
function template(overrides: Partial<Pick<WorkspaceYaml, 'app' | 'layout' | 'spacePackage'>>): WorkspaceYaml {
|
|
10
|
+
return {
|
|
11
|
+
meta: {
|
|
12
|
+
slug: 'space-package-test',
|
|
13
|
+
name: 'Space package test',
|
|
14
|
+
description: 'Space package fixture',
|
|
15
|
+
icon: 'panels-top-left',
|
|
16
|
+
color: 'blue',
|
|
17
|
+
},
|
|
18
|
+
workspace: { name: 'Space package test', description: 'Space package fixture' },
|
|
19
|
+
profiles: [{ slug: 'thing', displayName: 'Thing', properties: [] }],
|
|
20
|
+
...overrides,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const withPackage = (app?: WorkspaceYaml['app']) =>
|
|
25
|
+
template({
|
|
26
|
+
...(app ? { app } : {}),
|
|
27
|
+
spacePackage: { cells: [{ key: 'home', name: 'Home', source: SOURCE }] },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const built: Record<string, BuiltSpacePackage> = {
|
|
31
|
+
'space-package-test': {
|
|
32
|
+
cells: [{ key: 'home', name: 'Home', source: SOURCE, code: 'export default function Home() { return null; }', kitImports: [] }],
|
|
33
|
+
inputs: {},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const rules = (tpl: WorkspaceYaml) => validateTemplate(tpl).errors.map((e) => e.rule);
|
|
38
|
+
|
|
39
|
+
describe('space package cells', () => {
|
|
40
|
+
it('carries each built module as a network-contained frame cell', () => {
|
|
41
|
+
expect(spacePackageCells(withPackage(), built)).toEqual([
|
|
42
|
+
{ key: 'home', name: 'Home', code: built['space-package-test'].cells[0].code, rendererType: 'frame' },
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('has no cells for a template without a space package', () => {
|
|
47
|
+
expect(spacePackageCells(template({}), built)).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('refuses a declared cell that was never built: no space opens "Unknown cell"', () => {
|
|
51
|
+
expect(() => spacePackageCells(withPackage(), {})).toThrow(/not built/);
|
|
52
|
+
const otherSource = { 'space-package-test': { ...built['space-package-test'], cells: [{ ...built['space-package-test'].cells[0], source: 'packages/web/src/space-packages/fixture/old.tsx' }] } };
|
|
53
|
+
expect(() => spacePackageCells(withPackage(), otherSource)).toThrow(/not built/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('travels on the package definition only: the workspace door has no cells', () => {
|
|
57
|
+
expect('cells' in toWorkspaceDefinition(withPackage())).toBe(false);
|
|
58
|
+
expect('cells' in toPackageDefinition(template({}))).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('space package validation', () => {
|
|
63
|
+
it('accepts an entry that opens a cell the template installs', () => {
|
|
64
|
+
expect(rules(withPackage({ kind: 'stored', cellKey: 'cell:space-package-test:home' }))).not.toContain(
|
|
65
|
+
'space-package-entry-unknown'
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('refuses an entry naming a package cell the template does not build', () => {
|
|
70
|
+
expect(rules(withPackage({ kind: 'stored', cellKey: 'cell:space-package-test:other' }))).toContain(
|
|
71
|
+
'space-package-entry-unknown'
|
|
72
|
+
);
|
|
73
|
+
expect(rules(template({ app: { kind: 'stored', cellKey: 'cell:space-package-test:home' } }))).toContain(
|
|
74
|
+
'space-package-entry-unknown'
|
|
75
|
+
);
|
|
76
|
+
// Another package's namespace is never this template's to open.
|
|
77
|
+
expect(rules(withPackage({ kind: 'stored', cellKey: 'cell:crm:home' }))).toContain('space-package-entry-unknown');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('checks the authored layout.primarySurface the same way', () => {
|
|
81
|
+
const tpl = template({
|
|
82
|
+
layout: { primarySurface: { kind: 'cell', cellKey: 'cell:space-package-test:home' } },
|
|
83
|
+
});
|
|
84
|
+
expect(rules(tpl)).toContain('space-package-entry-unknown');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('leaves built-in cell keys alone', () => {
|
|
88
|
+
expect(rules(template({ app: { kind: 'stored', cellKey: 'content-home' } }))).not.toContain(
|
|
89
|
+
'space-package-entry-unknown'
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('builds only from the shared components under packages/web/src/space-packages/', () => {
|
|
94
|
+
const bad = (source: string) =>
|
|
95
|
+
rules(template({ spacePackage: { cells: [{ key: 'home', name: 'Home', source }] } }));
|
|
96
|
+
expect(bad('apps/devplane/page.tsx')).toContain('space-package-cell-source');
|
|
97
|
+
expect(bad('packages/web/src/space-packages/../../../../etc/x.ts')).toContain('space-package-cell-source');
|
|
98
|
+
expect(bad('packages/web/src/space-packages/x/home.css')).toContain('space-package-cell-source');
|
|
99
|
+
expect(bad(SOURCE)).not.toContain('space-package-cell-source');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('needs unique kebab-case keys', () => {
|
|
103
|
+
const tpl = template({
|
|
104
|
+
spacePackage: {
|
|
105
|
+
cells: [
|
|
106
|
+
{ key: 'home', name: 'Home', source: SOURCE },
|
|
107
|
+
{ key: 'home', name: 'Home again', source: SOURCE },
|
|
108
|
+
{ key: 'Home Page', name: 'Bad', source: SOURCE },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
expect(rules(tpl)).toEqual(expect.arrayContaining(['space-package-cell-key-duplicate', 'space-package-cell-key']));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('the Builder space opens its own built code', () => {
|
|
117
|
+
it('installs the cell its app opens', () => {
|
|
118
|
+
const def = toPackageDefinition('builder-workspace');
|
|
119
|
+
const surface = def.layoutConfig?.primarySurface;
|
|
120
|
+
expect(surface).toMatchObject({ kind: 'cell', cellKey: 'cell:builder-workspace:home' });
|
|
121
|
+
expect(def.cells?.map((c) => c.key)).toEqual(['home']);
|
|
122
|
+
expect(def.cells?.[0]).toMatchObject({ rendererType: 'frame' });
|
|
123
|
+
expect(def.cells?.[0].code.length).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Space packages: the built code a template installs in its space.
|
|
3
|
+
*
|
|
4
|
+
* `scripts/build-space-packages.mjs` bundles each template's
|
|
5
|
+
* `spacePackage.cells[]` into `space-packages.generated.ts` (committed, like
|
|
6
|
+
* `templates.ts`, so every consumer of `toPackageDefinition` gets the same
|
|
7
|
+
* cells without a bundler). This module reads that output for the converter.
|
|
8
|
+
*/
|
|
9
|
+
import type { WorkspaceYaml } from './types';
|
|
10
|
+
import { BUILT_SPACE_PACKAGES } from './space-packages.generated.js';
|
|
11
|
+
|
|
12
|
+
/** One built module: the declared cell plus its bundled ES module source. */
|
|
13
|
+
export interface BuiltSpacePackageCell {
|
|
14
|
+
key: string;
|
|
15
|
+
name: string;
|
|
16
|
+
source: string;
|
|
17
|
+
/** The ES module the frame runs (default export = a React component). */
|
|
18
|
+
code: string;
|
|
19
|
+
/**
|
|
20
|
+
* Every name the module imports from the host's `@synap/kit` frame module:
|
|
21
|
+
* the kit contract it was built against (the build refused any name the kit
|
|
22
|
+
* did not export).
|
|
23
|
+
*/
|
|
24
|
+
kitImports: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A template's built space package. It bundles only its own modules
|
|
29
|
+
* (`packages/web/src/space-packages/`): the shared layers come from the host's
|
|
30
|
+
* kit, whose own build record is `FRAME_HOST_MODULES_BUILD`
|
|
31
|
+
* (`@synap-core/web` cell-runtime).
|
|
32
|
+
*/
|
|
33
|
+
export interface BuiltSpacePackage {
|
|
34
|
+
cells: BuiltSpacePackageCell[];
|
|
35
|
+
/** sha256 of every synap-app file the bundles read (repo-relative path → hex). */
|
|
36
|
+
inputs: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A space package cell on the package wire (`PackageDefinition.cells[]`, the
|
|
41
|
+
* pod's `PackageCellDef`): a sandboxed `frame` cell. No `externalHosts`: a
|
|
42
|
+
* space package is network-contained; its only data path is the host bridge.
|
|
43
|
+
*/
|
|
44
|
+
export interface SpacePackageCellWire {
|
|
45
|
+
key: string;
|
|
46
|
+
name: string;
|
|
47
|
+
code: string;
|
|
48
|
+
rendererType: 'frame';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The cells a template's space package installs, or `undefined` when it has
|
|
53
|
+
* none. Throws when a declared cell was never built (or was built from another
|
|
54
|
+
* source): a template must never ship an entry that opens "Unknown cell".
|
|
55
|
+
*/
|
|
56
|
+
export function spacePackageCells(
|
|
57
|
+
tpl: WorkspaceYaml,
|
|
58
|
+
built: Record<string, BuiltSpacePackage> = BUILT_SPACE_PACKAGES
|
|
59
|
+
): SpacePackageCellWire[] | undefined {
|
|
60
|
+
const declared = tpl.spacePackage?.cells;
|
|
61
|
+
if (!declared?.length) return undefined;
|
|
62
|
+
const pkg = built[tpl.meta.slug];
|
|
63
|
+
return declared.map((cell) => {
|
|
64
|
+
const b = pkg?.cells.find((c) => c.key === cell.key && c.source === cell.source);
|
|
65
|
+
if (!b) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Space package cell "${cell.key}" of template "${tpl.meta.slug}" is not built from ${cell.source}: ` +
|
|
68
|
+
'run pnpm --filter @synap-core/workspace-templates build:space-packages'
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return { key: cell.key, name: cell.name, code: b.code, rendererType: 'frame' };
|
|
72
|
+
});
|
|
73
|
+
}
|