@synap-core/workspace-templates 0.9.2 → 0.9.4
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/dist/index.d.ts +202 -31
- package/dist/index.js +782 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/business-developer.yaml +24 -6
- package/src/content-os.yaml +19 -0
- package/src/crm.yaml +94 -2
- package/src/define.primary-surface.test.ts +183 -0
- package/src/define.ts +142 -19
- package/src/index.ts +26 -3
- package/src/marketing.yaml +19 -0
- package/src/templates.ts +186 -8
- package/src/types.ts +111 -32
- package/src/validate-flow.drift.test.ts +83 -0
- package/src/validate-flow.ts +555 -0
- package/src/validate-onboarding.test.ts +88 -0
- package/src/validate.test.ts +121 -0
- package/src/validate.ts +247 -12
package/dist/index.js
CHANGED
|
@@ -6841,14 +6841,13 @@ var DATA = {
|
|
|
6841
6841
|
"valueType": "string",
|
|
6842
6842
|
"inputType": "select",
|
|
6843
6843
|
"enumValues": [
|
|
6844
|
-
"
|
|
6845
|
-
"referral",
|
|
6844
|
+
"cold-outreach",
|
|
6846
6845
|
"linkedin",
|
|
6847
|
-
"
|
|
6846
|
+
"twitter",
|
|
6847
|
+
"landing-page",
|
|
6848
|
+
"inbound-call",
|
|
6849
|
+
"referral",
|
|
6848
6850
|
"event",
|
|
6849
|
-
"inbound",
|
|
6850
|
-
"partner",
|
|
6851
|
-
"import",
|
|
6852
6851
|
"other"
|
|
6853
6852
|
]
|
|
6854
6853
|
},
|
|
@@ -7218,6 +7217,37 @@ var DATA = {
|
|
|
7218
7217
|
"label": "Notes",
|
|
7219
7218
|
"valueType": "string",
|
|
7220
7219
|
"inputType": "textarea"
|
|
7220
|
+
},
|
|
7221
|
+
{
|
|
7222
|
+
"slug": "campaign-type",
|
|
7223
|
+
"label": "Type",
|
|
7224
|
+
"valueType": "string",
|
|
7225
|
+
"inputType": "select",
|
|
7226
|
+
"enumValues": [
|
|
7227
|
+
"scoping",
|
|
7228
|
+
"outreach",
|
|
7229
|
+
"live",
|
|
7230
|
+
"partnership",
|
|
7231
|
+
"event",
|
|
7232
|
+
"other"
|
|
7233
|
+
],
|
|
7234
|
+
"constraints": {
|
|
7235
|
+
"defaultValue": "outreach"
|
|
7236
|
+
}
|
|
7237
|
+
},
|
|
7238
|
+
{
|
|
7239
|
+
"slug": "campaign-color",
|
|
7240
|
+
"label": "Color",
|
|
7241
|
+
"valueType": "string",
|
|
7242
|
+
"inputType": "select",
|
|
7243
|
+
"enumValues": [
|
|
7244
|
+
"indigo",
|
|
7245
|
+
"violet",
|
|
7246
|
+
"amber",
|
|
7247
|
+
"cyan",
|
|
7248
|
+
"pink",
|
|
7249
|
+
"teal"
|
|
7250
|
+
]
|
|
7221
7251
|
}
|
|
7222
7252
|
]
|
|
7223
7253
|
}
|
|
@@ -8922,6 +8952,37 @@ var DATA = {
|
|
|
8922
8952
|
"label": "Notes",
|
|
8923
8953
|
"valueType": "string",
|
|
8924
8954
|
"inputType": "textarea"
|
|
8955
|
+
},
|
|
8956
|
+
{
|
|
8957
|
+
"slug": "campaign-type",
|
|
8958
|
+
"label": "Type",
|
|
8959
|
+
"valueType": "string",
|
|
8960
|
+
"inputType": "select",
|
|
8961
|
+
"enumValues": [
|
|
8962
|
+
"scoping",
|
|
8963
|
+
"outreach",
|
|
8964
|
+
"live",
|
|
8965
|
+
"partnership",
|
|
8966
|
+
"event",
|
|
8967
|
+
"other"
|
|
8968
|
+
],
|
|
8969
|
+
"constraints": {
|
|
8970
|
+
"defaultValue": "outreach"
|
|
8971
|
+
}
|
|
8972
|
+
},
|
|
8973
|
+
{
|
|
8974
|
+
"slug": "campaign-color",
|
|
8975
|
+
"label": "Color",
|
|
8976
|
+
"valueType": "string",
|
|
8977
|
+
"inputType": "select",
|
|
8978
|
+
"enumValues": [
|
|
8979
|
+
"indigo",
|
|
8980
|
+
"violet",
|
|
8981
|
+
"amber",
|
|
8982
|
+
"cyan",
|
|
8983
|
+
"pink",
|
|
8984
|
+
"teal"
|
|
8985
|
+
]
|
|
8925
8986
|
}
|
|
8926
8987
|
]
|
|
8927
8988
|
},
|
|
@@ -10024,6 +10085,12 @@ var DATA = {
|
|
|
10024
10085
|
],
|
|
10025
10086
|
"isPublic": true
|
|
10026
10087
|
},
|
|
10088
|
+
"app": {
|
|
10089
|
+
"kind": "url",
|
|
10090
|
+
"appId": "crm",
|
|
10091
|
+
"url": "https://crm.synap.live",
|
|
10092
|
+
"title": "CRM"
|
|
10093
|
+
},
|
|
10027
10094
|
"dependencies": [
|
|
10028
10095
|
{
|
|
10029
10096
|
"slug": "foundation",
|
|
@@ -10094,7 +10161,17 @@ var DATA = {
|
|
|
10094
10161
|
"slug": "source",
|
|
10095
10162
|
"label": "Source",
|
|
10096
10163
|
"valueType": "string",
|
|
10097
|
-
"inputType": "
|
|
10164
|
+
"inputType": "select",
|
|
10165
|
+
"enumValues": [
|
|
10166
|
+
"cold-outreach",
|
|
10167
|
+
"linkedin",
|
|
10168
|
+
"twitter",
|
|
10169
|
+
"landing-page",
|
|
10170
|
+
"inbound-call",
|
|
10171
|
+
"referral",
|
|
10172
|
+
"event",
|
|
10173
|
+
"other"
|
|
10174
|
+
]
|
|
10098
10175
|
},
|
|
10099
10176
|
{
|
|
10100
10177
|
"slug": "icpScore",
|
|
@@ -10183,7 +10260,17 @@ var DATA = {
|
|
|
10183
10260
|
"slug": "source",
|
|
10184
10261
|
"label": "Source",
|
|
10185
10262
|
"valueType": "string",
|
|
10186
|
-
"inputType": "
|
|
10263
|
+
"inputType": "select",
|
|
10264
|
+
"enumValues": [
|
|
10265
|
+
"cold-outreach",
|
|
10266
|
+
"linkedin",
|
|
10267
|
+
"twitter",
|
|
10268
|
+
"landing-page",
|
|
10269
|
+
"inbound-call",
|
|
10270
|
+
"referral",
|
|
10271
|
+
"event",
|
|
10272
|
+
"other"
|
|
10273
|
+
]
|
|
10187
10274
|
},
|
|
10188
10275
|
{
|
|
10189
10276
|
"slug": "icpScore",
|
|
@@ -10571,6 +10658,66 @@ var DATA = {
|
|
|
10571
10658
|
"type": "linked_to_deal"
|
|
10572
10659
|
}
|
|
10573
10660
|
],
|
|
10661
|
+
"onboarding": {
|
|
10662
|
+
"goal": "Turn the user's real commercial motion into a usable pipeline \u2014 the right companies and people, their active opportunities, and an explicit next step for every deal \u2014 so CRM becomes the place sales work moves forward.\n",
|
|
10663
|
+
"framing": "THE REVENUE OPERATOR: Act as a pragmatic sales lead setting up a CRM people will actually maintain. Start from the user's existing motion, vocabulary, and active conversations. Resolve before creating so imported or Pod-wide people are reused rather than duplicated. Push for a concrete next step and date; never invent revenue, probability, or contact details.\n",
|
|
10664
|
+
"expertise": {
|
|
10665
|
+
"starters": [
|
|
10666
|
+
"A useful first pipeline is deliberately small. Start with the few stages that change how the team acts \u2014 contacted, qualifying, proposal, negotiating, won, and lost \u2014 and keep the template's default stages unless the user's real process needs a different view.\n",
|
|
10667
|
+
"Populate the workspace with live work, not a perfect historical archive: propose the three to five opportunities the user is actively trying to move, then add the companies and people needed to understand those deals.\n",
|
|
10668
|
+
'Every active deal needs one observable next step with a date. If the next step is "follow up" or "keep warm", help turn it into a specific action such as "send the revised proposal Thursday" before capturing it.\n'
|
|
10669
|
+
],
|
|
10670
|
+
"blindSpots": [
|
|
10671
|
+
"A deal without a real person is usually an account-shaped hope. If an opportunity has no stakeholder, surface the gap instead of fabricating a placeholder contact.\n",
|
|
10672
|
+
"Pipelines become fiction when stages describe confidence rather than an event the buyer completed. Ask what evidence moves a deal into its current stage, especially around proposal and negotiation.\n",
|
|
10673
|
+
"More fields do not create better sales hygiene. Prioritize stage, value when known, expected close date, and next step; leave unverifiable fields empty and offer enrichment separately.\n"
|
|
10674
|
+
],
|
|
10675
|
+
"bar": "A sales lead can open the pipeline and immediately see the few live opportunities, the real company and stakeholder behind each one, its evidence-based stage, and the dated next action \u2014 with no duplicate people and no invented commercial data.\n"
|
|
10676
|
+
},
|
|
10677
|
+
"collect": [
|
|
10678
|
+
{
|
|
10679
|
+
"profileSlug": "company",
|
|
10680
|
+
"what": "The real prospect organisations behind current conversations.",
|
|
10681
|
+
"cardinality": "few",
|
|
10682
|
+
"keyFields": [
|
|
10683
|
+
"website",
|
|
10684
|
+
"industry",
|
|
10685
|
+
"companySize",
|
|
10686
|
+
"source",
|
|
10687
|
+
"nextStep"
|
|
10688
|
+
]
|
|
10689
|
+
},
|
|
10690
|
+
{
|
|
10691
|
+
"profileSlug": "person",
|
|
10692
|
+
"what": "The known stakeholders for those organisations and opportunities.",
|
|
10693
|
+
"cardinality": "few",
|
|
10694
|
+
"keyFields": [
|
|
10695
|
+
"email",
|
|
10696
|
+
"jobTitle",
|
|
10697
|
+
"company",
|
|
10698
|
+
"source",
|
|
10699
|
+
"nextFollowUpDate"
|
|
10700
|
+
]
|
|
10701
|
+
},
|
|
10702
|
+
{
|
|
10703
|
+
"profileSlug": "deal",
|
|
10704
|
+
"what": "The live opportunities worth actively moving through the pipeline.",
|
|
10705
|
+
"cardinality": "few",
|
|
10706
|
+
"keyFields": [
|
|
10707
|
+
"dealStage",
|
|
10708
|
+
"value",
|
|
10709
|
+
"closeDate",
|
|
10710
|
+
"nextStep"
|
|
10711
|
+
]
|
|
10712
|
+
}
|
|
10713
|
+
],
|
|
10714
|
+
"openingQuestions": [
|
|
10715
|
+
"Which three to five opportunities are you actively trying to move right now?",
|
|
10716
|
+
"For the most important one, which company and real stakeholder are involved?",
|
|
10717
|
+
"What has the buyer actually done so far, and what is the next dated action?"
|
|
10718
|
+
],
|
|
10719
|
+
"doneWhen": "At least one real opportunity is linked to its company or stakeholder and has an evidence-based stage plus a concrete next step; the user can see how to add or import the rest without duplicating existing people.\n"
|
|
10720
|
+
},
|
|
10574
10721
|
"playbooks": [
|
|
10575
10722
|
{
|
|
10576
10723
|
"name": "Enrich Lead/Company",
|
|
@@ -22039,6 +22186,37 @@ var DATA = {
|
|
|
22039
22186
|
"label": "Notes",
|
|
22040
22187
|
"valueType": "string",
|
|
22041
22188
|
"inputType": "textarea"
|
|
22189
|
+
},
|
|
22190
|
+
{
|
|
22191
|
+
"slug": "campaign-type",
|
|
22192
|
+
"label": "Type",
|
|
22193
|
+
"valueType": "string",
|
|
22194
|
+
"inputType": "select",
|
|
22195
|
+
"enumValues": [
|
|
22196
|
+
"scoping",
|
|
22197
|
+
"outreach",
|
|
22198
|
+
"live",
|
|
22199
|
+
"partnership",
|
|
22200
|
+
"event",
|
|
22201
|
+
"other"
|
|
22202
|
+
],
|
|
22203
|
+
"constraints": {
|
|
22204
|
+
"defaultValue": "outreach"
|
|
22205
|
+
}
|
|
22206
|
+
},
|
|
22207
|
+
{
|
|
22208
|
+
"slug": "campaign-color",
|
|
22209
|
+
"label": "Color",
|
|
22210
|
+
"valueType": "string",
|
|
22211
|
+
"inputType": "select",
|
|
22212
|
+
"enumValues": [
|
|
22213
|
+
"indigo",
|
|
22214
|
+
"violet",
|
|
22215
|
+
"amber",
|
|
22216
|
+
"cyan",
|
|
22217
|
+
"pink",
|
|
22218
|
+
"teal"
|
|
22219
|
+
]
|
|
22042
22220
|
}
|
|
22043
22221
|
]
|
|
22044
22222
|
},
|
|
@@ -28861,6 +29039,339 @@ function groupByDomain(items, domainOf) {
|
|
|
28861
29039
|
}));
|
|
28862
29040
|
}
|
|
28863
29041
|
|
|
29042
|
+
// src/validate-flow.ts
|
|
29043
|
+
var FLOW_NODE_TYPES = [
|
|
29044
|
+
"trigger",
|
|
29045
|
+
"command",
|
|
29046
|
+
"condition",
|
|
29047
|
+
"delay",
|
|
29048
|
+
"output",
|
|
29049
|
+
"loop",
|
|
29050
|
+
"transform",
|
|
29051
|
+
"fetch",
|
|
29052
|
+
"query",
|
|
29053
|
+
"entity_read",
|
|
29054
|
+
"related_entities",
|
|
29055
|
+
"guard",
|
|
29056
|
+
"compute",
|
|
29057
|
+
"select",
|
|
29058
|
+
"claim",
|
|
29059
|
+
"messages_query",
|
|
29060
|
+
"switch",
|
|
29061
|
+
"skill",
|
|
29062
|
+
"capability",
|
|
29063
|
+
"sub_automation",
|
|
29064
|
+
"playbook_run"
|
|
29065
|
+
];
|
|
29066
|
+
var FLOW_OUTPUT_TYPES = [
|
|
29067
|
+
"notification",
|
|
29068
|
+
"entity_create",
|
|
29069
|
+
"entity_update",
|
|
29070
|
+
"facet_attach",
|
|
29071
|
+
"facet_update",
|
|
29072
|
+
"facet_detach",
|
|
29073
|
+
"relation_create",
|
|
29074
|
+
"webhook",
|
|
29075
|
+
"channel_message",
|
|
29076
|
+
"session_update",
|
|
29077
|
+
"set_state"
|
|
29078
|
+
];
|
|
29079
|
+
var CHANNEL_MESSAGE_CHANNEL_TYPES = [
|
|
29080
|
+
"personal_thread",
|
|
29081
|
+
"proactive",
|
|
29082
|
+
"subjectEntity"
|
|
29083
|
+
];
|
|
29084
|
+
var NODE_TYPE_SET = new Set(FLOW_NODE_TYPES);
|
|
29085
|
+
var OUTPUT_TYPE_SET = new Set(FLOW_OUTPUT_TYPES);
|
|
29086
|
+
var CHANNEL_TYPE_SET = new Set(CHANNEL_MESSAGE_CHANNEL_TYPES);
|
|
29087
|
+
function isRecord(v) {
|
|
29088
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
29089
|
+
}
|
|
29090
|
+
function nonEmptyString(v) {
|
|
29091
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
29092
|
+
}
|
|
29093
|
+
function validateFlowDefinition(flow, resolvers) {
|
|
29094
|
+
const errors = [];
|
|
29095
|
+
if (!isRecord(flow)) {
|
|
29096
|
+
return {
|
|
29097
|
+
valid: false,
|
|
29098
|
+
errors: [
|
|
29099
|
+
{
|
|
29100
|
+
code: "flow_not_object",
|
|
29101
|
+
message: "flowDefinition must be an object with `nodes` and `edges` arrays."
|
|
29102
|
+
}
|
|
29103
|
+
]
|
|
29104
|
+
};
|
|
29105
|
+
}
|
|
29106
|
+
const f = flow;
|
|
29107
|
+
if (!Array.isArray(f.nodes)) {
|
|
29108
|
+
errors.push({
|
|
29109
|
+
code: "nodes_not_array",
|
|
29110
|
+
message: "flowDefinition.nodes must be an array."
|
|
29111
|
+
});
|
|
29112
|
+
}
|
|
29113
|
+
if (!Array.isArray(f.edges)) {
|
|
29114
|
+
errors.push({
|
|
29115
|
+
code: "edges_not_array",
|
|
29116
|
+
message: "flowDefinition.edges must be an array."
|
|
29117
|
+
});
|
|
29118
|
+
}
|
|
29119
|
+
if (errors.length > 0) return { valid: false, errors };
|
|
29120
|
+
const nodes = f.nodes.map(
|
|
29121
|
+
(n) => isRecord(n) ? n : {}
|
|
29122
|
+
);
|
|
29123
|
+
const edges = f.edges.map(
|
|
29124
|
+
(e) => isRecord(e) ? e : {}
|
|
29125
|
+
);
|
|
29126
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
29127
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
29128
|
+
nodes.forEach((node, idx) => {
|
|
29129
|
+
if (!nonEmptyString(node.id)) {
|
|
29130
|
+
errors.push({
|
|
29131
|
+
code: "node_missing_id",
|
|
29132
|
+
message: `Node at index ${idx} is missing a non-empty string \`id\`.`
|
|
29133
|
+
});
|
|
29134
|
+
return;
|
|
29135
|
+
}
|
|
29136
|
+
if (seenIds.has(node.id)) {
|
|
29137
|
+
errors.push({
|
|
29138
|
+
nodeId: node.id,
|
|
29139
|
+
code: "duplicate_node_id",
|
|
29140
|
+
message: `Duplicate node id "${node.id}" \u2014 every node id must be unique.`
|
|
29141
|
+
});
|
|
29142
|
+
return;
|
|
29143
|
+
}
|
|
29144
|
+
seenIds.add(node.id);
|
|
29145
|
+
nodeIds.add(node.id);
|
|
29146
|
+
});
|
|
29147
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
29148
|
+
for (const node of nodes) {
|
|
29149
|
+
if (nonEmptyString(node.id)) nodesById.set(node.id, node);
|
|
29150
|
+
const nodeId = nonEmptyString(node.id) ? node.id : void 0;
|
|
29151
|
+
if (!nonEmptyString(node.type) || !NODE_TYPE_SET.has(node.type)) {
|
|
29152
|
+
errors.push({
|
|
29153
|
+
nodeId,
|
|
29154
|
+
code: "unknown_node_type",
|
|
29155
|
+
message: `Node ${nodeId ? `"${nodeId}"` : `at index ?`} has ${nonEmptyString(node.type) ? `unknown type "${node.type}"` : "no type"}. Must be one of: ${FLOW_NODE_TYPES.join(", ")}.`
|
|
29156
|
+
});
|
|
29157
|
+
continue;
|
|
29158
|
+
}
|
|
29159
|
+
const type = node.type;
|
|
29160
|
+
const data = isRecord(node.data) ? node.data : {};
|
|
29161
|
+
switch (type) {
|
|
29162
|
+
case "capability": {
|
|
29163
|
+
if (!nonEmptyString(data.verbId)) {
|
|
29164
|
+
errors.push({
|
|
29165
|
+
nodeId,
|
|
29166
|
+
code: "capability_missing_verbId",
|
|
29167
|
+
message: `Capability node "${nodeId}" has no \`verbId\`. Pick a verb on the selected tool.`
|
|
29168
|
+
});
|
|
29169
|
+
} else if (resolvers?.verbExists && !resolvers.verbExists(data.verbId)) {
|
|
29170
|
+
errors.push({
|
|
29171
|
+
nodeId,
|
|
29172
|
+
code: "capability_unknown_verbId",
|
|
29173
|
+
message: `Capability node "${nodeId}" references verb "${data.verbId}", which is not in the capability catalog.`
|
|
29174
|
+
});
|
|
29175
|
+
}
|
|
29176
|
+
break;
|
|
29177
|
+
}
|
|
29178
|
+
case "skill": {
|
|
29179
|
+
const skillId = nonEmptyString(data.skillId) ? data.skillId : void 0;
|
|
29180
|
+
const skillName = nonEmptyString(data.skillName) ? data.skillName : void 0;
|
|
29181
|
+
if (!skillId && !skillName) {
|
|
29182
|
+
errors.push({
|
|
29183
|
+
nodeId,
|
|
29184
|
+
code: "skill_missing_ref",
|
|
29185
|
+
message: `Skill node "${nodeId}" has neither \`skillId\` nor \`skillName\`. One is required.`
|
|
29186
|
+
});
|
|
29187
|
+
} else if (resolvers?.skillExists && !resolvers.skillExists({ skillId, skillName })) {
|
|
29188
|
+
errors.push({
|
|
29189
|
+
nodeId,
|
|
29190
|
+
code: "skill_unknown_ref",
|
|
29191
|
+
message: `Skill node "${nodeId}" references a skill (${skillId ? `id "${skillId}"` : `name "${skillName}"`}) that does not exist.`
|
|
29192
|
+
});
|
|
29193
|
+
}
|
|
29194
|
+
break;
|
|
29195
|
+
}
|
|
29196
|
+
case "playbook_run": {
|
|
29197
|
+
const playbookId = nonEmptyString(data.playbookId) ? data.playbookId : void 0;
|
|
29198
|
+
const playbookName = nonEmptyString(data.playbookName) ? data.playbookName : void 0;
|
|
29199
|
+
if (!playbookId && !playbookName) {
|
|
29200
|
+
errors.push({
|
|
29201
|
+
nodeId,
|
|
29202
|
+
code: "playbook_run_missing_ref",
|
|
29203
|
+
message: `Playbook-run node "${nodeId}" has neither \`playbookId\` nor \`playbookName\`. One is required.`
|
|
29204
|
+
});
|
|
29205
|
+
} else if (resolvers?.playbookExists && !resolvers.playbookExists({ playbookId, playbookName })) {
|
|
29206
|
+
errors.push({
|
|
29207
|
+
nodeId,
|
|
29208
|
+
code: "playbook_run_unknown_ref",
|
|
29209
|
+
message: `Playbook-run node "${nodeId}" references a playbook (${playbookId ? `id "${playbookId}"` : `name "${playbookName}"`}) that does not exist.`
|
|
29210
|
+
});
|
|
29211
|
+
}
|
|
29212
|
+
break;
|
|
29213
|
+
}
|
|
29214
|
+
case "output": {
|
|
29215
|
+
const outputType = data.outputType;
|
|
29216
|
+
if (!nonEmptyString(outputType) || !OUTPUT_TYPE_SET.has(outputType)) {
|
|
29217
|
+
errors.push({
|
|
29218
|
+
nodeId,
|
|
29219
|
+
code: "output_bad_outputType",
|
|
29220
|
+
message: `Output node "${nodeId}" has ${nonEmptyString(outputType) ? `unknown outputType "${outputType}"` : "no outputType"}. Must be one of: ${FLOW_OUTPUT_TYPES.join(", ")}.`
|
|
29221
|
+
});
|
|
29222
|
+
break;
|
|
29223
|
+
}
|
|
29224
|
+
if (outputType === "channel_message") {
|
|
29225
|
+
const config = isRecord(data.config) ? data.config : {};
|
|
29226
|
+
const channelType = config.channelType;
|
|
29227
|
+
if (channelType !== void 0 && channelType !== null) {
|
|
29228
|
+
if (!nonEmptyString(channelType) || !CHANNEL_TYPE_SET.has(channelType)) {
|
|
29229
|
+
errors.push({
|
|
29230
|
+
nodeId,
|
|
29231
|
+
code: "channel_message_unknown_channelType",
|
|
29232
|
+
message: `Channel-message output "${nodeId}" has unknown channelType ${nonEmptyString(channelType) ? `"${channelType}"` : JSON.stringify(channelType)}. Use one of: ${CHANNEL_MESSAGE_CHANNEL_TYPES.join(
|
|
29233
|
+
", "
|
|
29234
|
+
)}, or set \`config.channelEntityRef\`/\`config.channelId\`, or omit the target to post to the automation's own run channel.`
|
|
29235
|
+
});
|
|
29236
|
+
}
|
|
29237
|
+
}
|
|
29238
|
+
}
|
|
29239
|
+
break;
|
|
29240
|
+
}
|
|
29241
|
+
case "loop": {
|
|
29242
|
+
if (!nonEmptyString(data.iteratorExpression)) {
|
|
29243
|
+
errors.push({
|
|
29244
|
+
nodeId,
|
|
29245
|
+
code: "loop_missing_iterator",
|
|
29246
|
+
message: `Loop node "${nodeId}" has no \`iteratorExpression\` (the items to iterate, e.g. "steps.query.output.entities").`
|
|
29247
|
+
});
|
|
29248
|
+
}
|
|
29249
|
+
break;
|
|
29250
|
+
}
|
|
29251
|
+
case "condition": {
|
|
29252
|
+
if (!nonEmptyString(data.expression)) {
|
|
29253
|
+
errors.push({
|
|
29254
|
+
nodeId,
|
|
29255
|
+
code: "condition_missing_expression",
|
|
29256
|
+
message: `Condition node "${nodeId}" has no \`expression\` (the predicate to evaluate).`
|
|
29257
|
+
});
|
|
29258
|
+
}
|
|
29259
|
+
break;
|
|
29260
|
+
}
|
|
29261
|
+
case "switch": {
|
|
29262
|
+
if (!nonEmptyString(data.expression)) {
|
|
29263
|
+
errors.push({
|
|
29264
|
+
nodeId,
|
|
29265
|
+
code: "switch_missing_expression",
|
|
29266
|
+
message: `Switch node "${nodeId}" has no \`expression\` (the value to match against cases).`
|
|
29267
|
+
});
|
|
29268
|
+
}
|
|
29269
|
+
if (!Array.isArray(data.cases) || data.cases.length === 0) {
|
|
29270
|
+
errors.push({
|
|
29271
|
+
nodeId,
|
|
29272
|
+
code: "switch_missing_cases",
|
|
29273
|
+
message: `Switch node "${nodeId}" has no \`cases\`. Add at least one case with a \`value\`.`
|
|
29274
|
+
});
|
|
29275
|
+
}
|
|
29276
|
+
break;
|
|
29277
|
+
}
|
|
29278
|
+
}
|
|
29279
|
+
}
|
|
29280
|
+
edges.forEach((edge, idx) => {
|
|
29281
|
+
const edgeId = nonEmptyString(edge.id) ? edge.id : void 0;
|
|
29282
|
+
const label = edgeId ? `"${edgeId}"` : `at index ${idx}`;
|
|
29283
|
+
if (!nonEmptyString(edge.source) || !nodeIds.has(edge.source)) {
|
|
29284
|
+
errors.push({
|
|
29285
|
+
edgeId,
|
|
29286
|
+
code: "edge_bad_source",
|
|
29287
|
+
message: `Edge ${label} has ${nonEmptyString(edge.source) ? `source "${edge.source}" which is not a node in this flow` : "no source node"}.`
|
|
29288
|
+
});
|
|
29289
|
+
}
|
|
29290
|
+
if (!nonEmptyString(edge.target) || !nodeIds.has(edge.target)) {
|
|
29291
|
+
errors.push({
|
|
29292
|
+
edgeId,
|
|
29293
|
+
code: "edge_bad_target",
|
|
29294
|
+
message: `Edge ${label} has ${nonEmptyString(edge.target) ? `target "${edge.target}" which is not a node in this flow` : "no target node"}.`
|
|
29295
|
+
});
|
|
29296
|
+
}
|
|
29297
|
+
});
|
|
29298
|
+
for (const edge of edges) {
|
|
29299
|
+
if (!nonEmptyString(edge.source) || !nodesById.has(edge.source)) continue;
|
|
29300
|
+
if (edge.sourceHandle === void 0 || edge.sourceHandle === null) continue;
|
|
29301
|
+
if (!nonEmptyString(edge.sourceHandle)) continue;
|
|
29302
|
+
const src = nodesById.get(edge.source);
|
|
29303
|
+
const edgeId = nonEmptyString(edge.id) ? edge.id : void 0;
|
|
29304
|
+
if (src.type === "condition") {
|
|
29305
|
+
if (edge.sourceHandle !== "yes" && edge.sourceHandle !== "no") {
|
|
29306
|
+
errors.push({
|
|
29307
|
+
edgeId,
|
|
29308
|
+
nodeId: edge.source,
|
|
29309
|
+
code: "condition_bad_handle",
|
|
29310
|
+
message: `Edge from condition "${edge.source}" has sourceHandle "${edge.sourceHandle}" \u2014 must be "yes" or "no".`
|
|
29311
|
+
});
|
|
29312
|
+
}
|
|
29313
|
+
} else if (src.type === "switch") {
|
|
29314
|
+
const data = isRecord(src.data) ? src.data : {};
|
|
29315
|
+
const caseValues = Array.isArray(data.cases) ? data.cases.map((c) => isRecord(c) ? c.value : void 0).filter((v) => typeof v === "string") : [];
|
|
29316
|
+
if (!caseValues.includes(edge.sourceHandle)) {
|
|
29317
|
+
errors.push({
|
|
29318
|
+
edgeId,
|
|
29319
|
+
nodeId: edge.source,
|
|
29320
|
+
code: "switch_bad_handle",
|
|
29321
|
+
message: `Edge from switch "${edge.source}" has sourceHandle "${edge.sourceHandle}", which matches no declared case value (${caseValues.length ? caseValues.join(", ") : "none declared"}).`
|
|
29322
|
+
});
|
|
29323
|
+
}
|
|
29324
|
+
}
|
|
29325
|
+
}
|
|
29326
|
+
const cyclic = detectCycle(nodeIds, edges);
|
|
29327
|
+
if (cyclic.length > 0) {
|
|
29328
|
+
errors.push({
|
|
29329
|
+
code: "flow_has_cycle",
|
|
29330
|
+
message: `Flow has a cycle \u2014 these nodes cannot be ordered and would never run: ${cyclic.join(
|
|
29331
|
+
", "
|
|
29332
|
+
)}. Remove the back-edge(s) so the graph is acyclic.`
|
|
29333
|
+
});
|
|
29334
|
+
}
|
|
29335
|
+
return { valid: errors.length === 0, errors };
|
|
29336
|
+
}
|
|
29337
|
+
function detectCycle(nodeIds, edges) {
|
|
29338
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
29339
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
29340
|
+
for (const id of nodeIds) {
|
|
29341
|
+
inDegree.set(id, 0);
|
|
29342
|
+
adjacency.set(id, []);
|
|
29343
|
+
}
|
|
29344
|
+
for (const edge of edges) {
|
|
29345
|
+
const source = nonEmptyString(edge.source) ? edge.source : void 0;
|
|
29346
|
+
const target = nonEmptyString(edge.target) ? edge.target : void 0;
|
|
29347
|
+
if (!source || !target) continue;
|
|
29348
|
+
if (!nodeIds.has(source) || !nodeIds.has(target)) continue;
|
|
29349
|
+
adjacency.get(source).push(target);
|
|
29350
|
+
inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
|
|
29351
|
+
}
|
|
29352
|
+
const queue = [];
|
|
29353
|
+
for (const [id, degree] of inDegree) if (degree === 0) queue.push(id);
|
|
29354
|
+
let visited = 0;
|
|
29355
|
+
while (queue.length > 0) {
|
|
29356
|
+
const id = queue.shift();
|
|
29357
|
+
visited++;
|
|
29358
|
+
for (const target of adjacency.get(id) ?? []) {
|
|
29359
|
+
const next = (inDegree.get(target) ?? 1) - 1;
|
|
29360
|
+
inDegree.set(target, next);
|
|
29361
|
+
if (next === 0) queue.push(target);
|
|
29362
|
+
}
|
|
29363
|
+
}
|
|
29364
|
+
if (visited === nodeIds.size) return [];
|
|
29365
|
+
const cyclic = [];
|
|
29366
|
+
for (const [id, degree] of inDegree) if (degree > 0) cyclic.push(id);
|
|
29367
|
+
return cyclic;
|
|
29368
|
+
}
|
|
29369
|
+
function flowValidationErrorMessage(flow, resolvers) {
|
|
29370
|
+
const result = validateFlowDefinition(flow, resolvers);
|
|
29371
|
+
if (result.valid) return null;
|
|
29372
|
+
return "Invalid automation flow: " + result.errors.map((e) => e.nodeId ? `[${e.nodeId}] ${e.message}` : e.message).join(" ");
|
|
29373
|
+
}
|
|
29374
|
+
|
|
28864
29375
|
// src/validate.ts
|
|
28865
29376
|
var REQUIRED_META_FIELDS = [
|
|
28866
29377
|
"slug",
|
|
@@ -28947,7 +29458,8 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
28947
29458
|
if (!tpl.meta) add("meta-required", "meta", "Missing required field: meta");
|
|
28948
29459
|
else {
|
|
28949
29460
|
for (const f of REQUIRED_META_FIELDS) {
|
|
28950
|
-
if (!tpl.meta[f])
|
|
29461
|
+
if (!tpl.meta[f])
|
|
29462
|
+
add("meta-field-required", `meta.${f}`, `meta.${f} is required`);
|
|
28951
29463
|
}
|
|
28952
29464
|
}
|
|
28953
29465
|
if (!tpl.workspace)
|
|
@@ -28955,12 +29467,80 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
28955
29467
|
else {
|
|
28956
29468
|
for (const f of REQUIRED_WORKSPACE_FIELDS) {
|
|
28957
29469
|
if (!tpl.workspace[f])
|
|
28958
|
-
add(
|
|
29470
|
+
add(
|
|
29471
|
+
"workspace-field-required",
|
|
29472
|
+
`workspace.${f}`,
|
|
29473
|
+
`workspace.${f} is required`
|
|
29474
|
+
);
|
|
28959
29475
|
}
|
|
28960
29476
|
}
|
|
28961
29477
|
if (!tpl.profiles || tpl.profiles.length === 0) {
|
|
28962
29478
|
add("profiles-required", "profiles", "At least one profile is required");
|
|
28963
29479
|
}
|
|
29480
|
+
if (tpl.app && tpl.layout && Object.prototype.hasOwnProperty.call(tpl.layout, "primarySurface")) {
|
|
29481
|
+
add(
|
|
29482
|
+
"primary-surface-authoring-conflict",
|
|
29483
|
+
"layout.primarySurface",
|
|
29484
|
+
"Declare either top-level app or layout.primarySurface, not both"
|
|
29485
|
+
);
|
|
29486
|
+
}
|
|
29487
|
+
if (tpl.app?.kind === "native" && !tpl.app.appId) {
|
|
29488
|
+
add(
|
|
29489
|
+
"app-native-id-required",
|
|
29490
|
+
"app.appId",
|
|
29491
|
+
"A native workspace app requires appId"
|
|
29492
|
+
);
|
|
29493
|
+
}
|
|
29494
|
+
if (tpl.app?.kind === "url" && (!tpl.app.appId || !tpl.app.url)) {
|
|
29495
|
+
add(
|
|
29496
|
+
"app-url-identity-required",
|
|
29497
|
+
"app",
|
|
29498
|
+
"A hosted workspace app requires both appId and url"
|
|
29499
|
+
);
|
|
29500
|
+
}
|
|
29501
|
+
if (tpl.app?.kind === "stored" && !tpl.app.cellKey) {
|
|
29502
|
+
add(
|
|
29503
|
+
"app-cell-key-required",
|
|
29504
|
+
"app.cellKey",
|
|
29505
|
+
"A stored workspace app requires cellKey"
|
|
29506
|
+
);
|
|
29507
|
+
}
|
|
29508
|
+
const authoredPrimary = tpl.layout?.primarySurface;
|
|
29509
|
+
if (authoredPrimary?.kind === "view" && !("viewId" in authoredPrimary)) {
|
|
29510
|
+
const viewName = authoredPrimary.viewName;
|
|
29511
|
+
const viewSlug = authoredPrimary.viewSlug;
|
|
29512
|
+
if (!viewName && !viewSlug) {
|
|
29513
|
+
add(
|
|
29514
|
+
"primary-view-reference-required",
|
|
29515
|
+
"layout.primarySurface",
|
|
29516
|
+
"A view primary surface requires viewName or viewSlug"
|
|
29517
|
+
);
|
|
29518
|
+
} else {
|
|
29519
|
+
const declaredViews = [
|
|
29520
|
+
...(tpl.views ?? []).map((view) => ({
|
|
29521
|
+
name: view.name,
|
|
29522
|
+
slug: view.slug
|
|
29523
|
+
})),
|
|
29524
|
+
...tpl.bento?.viewName ? [{ name: tpl.bento.viewName, slug: void 0 }] : []
|
|
29525
|
+
];
|
|
29526
|
+
const matches = declaredViews.filter(
|
|
29527
|
+
(view) => (!viewName || view.name === viewName) && (!viewSlug || view.slug === viewSlug)
|
|
29528
|
+
);
|
|
29529
|
+
if (matches.length === 0) {
|
|
29530
|
+
add(
|
|
29531
|
+
"primary-view-reference-unknown",
|
|
29532
|
+
"layout.primarySurface",
|
|
29533
|
+
`Primary surface references an unknown view (${viewName ?? viewSlug})`
|
|
29534
|
+
);
|
|
29535
|
+
} else if (matches.length > 1) {
|
|
29536
|
+
add(
|
|
29537
|
+
"primary-view-reference-ambiguous",
|
|
29538
|
+
"layout.primarySurface",
|
|
29539
|
+
`Primary surface view reference matches ${matches.length} authored views; add a unique viewSlug`
|
|
29540
|
+
);
|
|
29541
|
+
}
|
|
29542
|
+
}
|
|
29543
|
+
}
|
|
28964
29544
|
const baseProfileSlugs = collectBaseProfileSlugs(tpl, resolveDepFrom(opts));
|
|
28965
29545
|
const slugs = new Set((tpl.profiles || []).map((p) => p.slug));
|
|
28966
29546
|
const known = (s) => slugs.has(s) || SYSTEM_PROFILES.has(s) || baseProfileSlugs.has(s);
|
|
@@ -29020,7 +29600,85 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29020
29600
|
);
|
|
29021
29601
|
}
|
|
29022
29602
|
}
|
|
29023
|
-
|
|
29603
|
+
const onboarding = tpl.onboarding;
|
|
29604
|
+
if (onboarding) {
|
|
29605
|
+
if (typeof onboarding.goal !== "string" || onboarding.goal.trim() === "") {
|
|
29606
|
+
add(
|
|
29607
|
+
"onboarding-goal-empty",
|
|
29608
|
+
"onboarding.goal",
|
|
29609
|
+
"onboarding.goal must be a non-empty string"
|
|
29610
|
+
);
|
|
29611
|
+
}
|
|
29612
|
+
if (typeof onboarding.framing !== "string" || onboarding.framing.trim() === "") {
|
|
29613
|
+
add(
|
|
29614
|
+
"onboarding-framing-empty",
|
|
29615
|
+
"onboarding.framing",
|
|
29616
|
+
"onboarding.framing must be a non-empty string"
|
|
29617
|
+
);
|
|
29618
|
+
}
|
|
29619
|
+
if (!Array.isArray(onboarding.collect) || onboarding.collect.length === 0) {
|
|
29620
|
+
add(
|
|
29621
|
+
"onboarding-collect-empty",
|
|
29622
|
+
"onboarding.collect",
|
|
29623
|
+
"onboarding.collect must contain at least one structured target"
|
|
29624
|
+
);
|
|
29625
|
+
}
|
|
29626
|
+
const expertise = onboarding.expertise;
|
|
29627
|
+
for (const [field, values] of [
|
|
29628
|
+
["starters", expertise?.starters],
|
|
29629
|
+
["blindSpots", expertise?.blindSpots]
|
|
29630
|
+
]) {
|
|
29631
|
+
if (values !== void 0 && (!Array.isArray(values) || values.some(
|
|
29632
|
+
(value) => typeof value !== "string" || value.trim() === ""
|
|
29633
|
+
))) {
|
|
29634
|
+
add(
|
|
29635
|
+
"onboarding-expertise-invalid",
|
|
29636
|
+
`onboarding.expertise.${field}`,
|
|
29637
|
+
`onboarding.expertise.${field} must contain only non-empty strings`
|
|
29638
|
+
);
|
|
29639
|
+
}
|
|
29640
|
+
}
|
|
29641
|
+
if (expertise?.bar !== void 0 && (typeof expertise.bar !== "string" || expertise.bar.trim() === "")) {
|
|
29642
|
+
add(
|
|
29643
|
+
"onboarding-expertise-invalid",
|
|
29644
|
+
"onboarding.expertise.bar",
|
|
29645
|
+
"onboarding.expertise.bar must be a non-empty string"
|
|
29646
|
+
);
|
|
29647
|
+
}
|
|
29648
|
+
if (onboarding.openingQuestions !== void 0 && (!Array.isArray(onboarding.openingQuestions) || onboarding.openingQuestions.some(
|
|
29649
|
+
(question) => typeof question !== "string" || question.trim() === ""
|
|
29650
|
+
))) {
|
|
29651
|
+
add(
|
|
29652
|
+
"onboarding-opening-questions-invalid",
|
|
29653
|
+
"onboarding.openingQuestions",
|
|
29654
|
+
"onboarding.openingQuestions must contain only non-empty strings"
|
|
29655
|
+
);
|
|
29656
|
+
}
|
|
29657
|
+
if (onboarding.doneWhen !== void 0 && (typeof onboarding.doneWhen !== "string" || onboarding.doneWhen.trim() === "")) {
|
|
29658
|
+
add(
|
|
29659
|
+
"onboarding-done-when-invalid",
|
|
29660
|
+
"onboarding.doneWhen",
|
|
29661
|
+
"onboarding.doneWhen must be a non-empty string"
|
|
29662
|
+
);
|
|
29663
|
+
}
|
|
29664
|
+
}
|
|
29665
|
+
for (const c of onboarding?.collect || []) {
|
|
29666
|
+
if (typeof c.what !== "string" || c.what.trim() === "") {
|
|
29667
|
+
add(
|
|
29668
|
+
"onboarding-collect-what-empty",
|
|
29669
|
+
`onboarding.collect.${c.profileSlug}.what`,
|
|
29670
|
+
`onboarding.collect target "${c.profileSlug}" must describe what to capture`
|
|
29671
|
+
);
|
|
29672
|
+
}
|
|
29673
|
+
if (c.keyFields !== void 0 && (!Array.isArray(c.keyFields) || c.keyFields.some(
|
|
29674
|
+
(field) => typeof field !== "string" || field.trim() === ""
|
|
29675
|
+
))) {
|
|
29676
|
+
add(
|
|
29677
|
+
"onboarding-collect-key-fields-invalid",
|
|
29678
|
+
`onboarding.collect.${c.profileSlug}.keyFields`,
|
|
29679
|
+
`onboarding.collect target "${c.profileSlug}" keyFields must contain only non-empty strings`
|
|
29680
|
+
);
|
|
29681
|
+
}
|
|
29024
29682
|
if (!known(c.profileSlug))
|
|
29025
29683
|
add(
|
|
29026
29684
|
"onboarding-ref-unknown",
|
|
@@ -29038,11 +29696,27 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29038
29696
|
}
|
|
29039
29697
|
for (const a of tpl.automations || []) {
|
|
29040
29698
|
const refs = [];
|
|
29041
|
-
collectProfileSlugRefs(
|
|
29699
|
+
collectProfileSlugRefs(
|
|
29700
|
+
a.triggerConfig,
|
|
29701
|
+
`automation "${a.name}".triggerConfig`,
|
|
29702
|
+
refs
|
|
29703
|
+
);
|
|
29042
29704
|
collectProfileSlugRefs(a.flow, `automation "${a.name}".flow`, refs);
|
|
29043
29705
|
for (const r of refs)
|
|
29044
29706
|
if (!known(r.slug))
|
|
29045
|
-
add(
|
|
29707
|
+
add(
|
|
29708
|
+
"automation-ref-unknown",
|
|
29709
|
+
r.where,
|
|
29710
|
+
`${r.where} references unknown profile "${r.slug}"`
|
|
29711
|
+
);
|
|
29712
|
+
const flow = a.flow ?? a.flowDefinition;
|
|
29713
|
+
if (flow != null) {
|
|
29714
|
+
const { errors: flowErrors } = validateFlowDefinition(flow);
|
|
29715
|
+
for (const fe of flowErrors) {
|
|
29716
|
+
const loc = fe.nodeId ? `automation "${a.name}".flow.node "${fe.nodeId}"` : fe.edgeId ? `automation "${a.name}".flow.edge "${fe.edgeId}"` : `automation "${a.name}".flow`;
|
|
29717
|
+
add(`automation-flow-${fe.code}`, loc, `${loc}: ${fe.message}`);
|
|
29718
|
+
}
|
|
29719
|
+
}
|
|
29046
29720
|
}
|
|
29047
29721
|
for (const pb of tpl.playbooks || []) {
|
|
29048
29722
|
const ps = pb.subjectProfile?.profileSlug;
|
|
@@ -29088,7 +29762,9 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29088
29762
|
`profileEntityBentoTemplates has an entry for unknown profile "${key}"`
|
|
29089
29763
|
);
|
|
29090
29764
|
}
|
|
29091
|
-
const composeDeps = (tpl.dependencies || []).filter(
|
|
29765
|
+
const composeDeps = (tpl.dependencies || []).filter(
|
|
29766
|
+
(d) => d.relation === "compose"
|
|
29767
|
+
);
|
|
29092
29768
|
for (const dep of composeDeps) {
|
|
29093
29769
|
if (tpl.workspace?.visibility !== void 0) {
|
|
29094
29770
|
add(
|
|
@@ -29108,7 +29784,9 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29108
29784
|
const blocks = tpl.bento?.blocks ?? [];
|
|
29109
29785
|
const authoredWidgets = blocks.filter((b) => b.kind === "widget");
|
|
29110
29786
|
const authoredViews = blocks.filter((b) => b.kind === "view");
|
|
29111
|
-
const unknownKind = blocks.filter(
|
|
29787
|
+
const unknownKind = blocks.filter(
|
|
29788
|
+
(b) => b.kind !== "widget" && b.kind !== "view"
|
|
29789
|
+
);
|
|
29112
29790
|
if (unknownKind.length) {
|
|
29113
29791
|
add(
|
|
29114
29792
|
"bento-unknown-kind",
|
|
@@ -29140,7 +29818,11 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29140
29818
|
const expectW = authoredWidgets.length - badWidgets.length;
|
|
29141
29819
|
const expectV = authoredViews.length - badViews.length;
|
|
29142
29820
|
const doors = [
|
|
29143
|
-
[
|
|
29821
|
+
[
|
|
29822
|
+
"tRPC createFromDefinition",
|
|
29823
|
+
definition.bentoLayout ?? [],
|
|
29824
|
+
definition.bentoViewBlocks ?? []
|
|
29825
|
+
],
|
|
29144
29826
|
["CP packages/apply", pkg.bentoLayout ?? [], pkg.bentoViewBlocks ?? []]
|
|
29145
29827
|
];
|
|
29146
29828
|
for (const [door, layout, viewBlocks] of doors) {
|
|
@@ -29151,7 +29833,9 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29151
29833
|
`[${door}]: authored ${expectW} widget/${expectV} view \u2192 got ${layout.length} bentoLayout/${viewBlocks.length} bentoViewBlocks`
|
|
29152
29834
|
);
|
|
29153
29835
|
}
|
|
29154
|
-
const layoutBad = layout.filter(
|
|
29836
|
+
const layoutBad = layout.filter(
|
|
29837
|
+
(b) => typeof b.widgetType !== "string"
|
|
29838
|
+
);
|
|
29155
29839
|
const viewsBad = viewBlocks.filter(
|
|
29156
29840
|
(b) => typeof b.viewName !== "string" || b.kind !== "view"
|
|
29157
29841
|
);
|
|
@@ -29221,6 +29905,79 @@ function toAutomationWire(automations) {
|
|
|
29221
29905
|
status: a.status
|
|
29222
29906
|
}));
|
|
29223
29907
|
}
|
|
29908
|
+
var hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
29909
|
+
function appRefToPrimarySurface(app) {
|
|
29910
|
+
if (!app) return void 0;
|
|
29911
|
+
if (app.kind === "native") {
|
|
29912
|
+
if (!app.appId) {
|
|
29913
|
+
throw new Error("Workspace template native app is missing appId");
|
|
29914
|
+
}
|
|
29915
|
+
return {
|
|
29916
|
+
kind: "app",
|
|
29917
|
+
appId: app.appId,
|
|
29918
|
+
rendererType: "native",
|
|
29919
|
+
...app.title ? { title: app.title } : {},
|
|
29920
|
+
...app.props ? { props: app.props } : {}
|
|
29921
|
+
};
|
|
29922
|
+
}
|
|
29923
|
+
if (app.kind === "url") {
|
|
29924
|
+
if (!app.appId || !app.url) {
|
|
29925
|
+
throw new Error(
|
|
29926
|
+
"Workspace template URL app requires both appId and url"
|
|
29927
|
+
);
|
|
29928
|
+
}
|
|
29929
|
+
return {
|
|
29930
|
+
kind: "app",
|
|
29931
|
+
appId: app.appId,
|
|
29932
|
+
rendererType: "external",
|
|
29933
|
+
url: app.url,
|
|
29934
|
+
...app.title ? { title: app.title } : {},
|
|
29935
|
+
...app.props ? { props: app.props } : {}
|
|
29936
|
+
};
|
|
29937
|
+
}
|
|
29938
|
+
if (app.kind === "stored") {
|
|
29939
|
+
if (!app.cellKey) {
|
|
29940
|
+
throw new Error("Workspace template stored app is missing cellKey");
|
|
29941
|
+
}
|
|
29942
|
+
if (app.cellKey === "entity-detail" && !app.props?.entityId) {
|
|
29943
|
+
return void 0;
|
|
29944
|
+
}
|
|
29945
|
+
return {
|
|
29946
|
+
kind: "cell",
|
|
29947
|
+
cellKey: app.cellKey,
|
|
29948
|
+
...app.title ? { title: app.title } : {},
|
|
29949
|
+
...app.props ? { props: app.props } : {}
|
|
29950
|
+
};
|
|
29951
|
+
}
|
|
29952
|
+
return void 0;
|
|
29953
|
+
}
|
|
29954
|
+
function normalizeTemplateLayout(layout, app) {
|
|
29955
|
+
if (app && layout && hasOwn(layout, "primarySurface")) {
|
|
29956
|
+
throw new Error(
|
|
29957
|
+
"Workspace template declares both top-level app and layout.primarySurface; choose one authoring source"
|
|
29958
|
+
);
|
|
29959
|
+
}
|
|
29960
|
+
const primarySurface = (() => {
|
|
29961
|
+
if (layout && hasOwn(layout, "primarySurface")) {
|
|
29962
|
+
return layout.primarySurface;
|
|
29963
|
+
}
|
|
29964
|
+
const fromApp = appRefToPrimarySurface(app);
|
|
29965
|
+
if (fromApp) return fromApp;
|
|
29966
|
+
if (layout && hasOwn(layout, "defaultApp")) {
|
|
29967
|
+
return layout.defaultApp ? {
|
|
29968
|
+
kind: "app",
|
|
29969
|
+
appId: layout.defaultApp,
|
|
29970
|
+
rendererType: "native"
|
|
29971
|
+
} : null;
|
|
29972
|
+
}
|
|
29973
|
+
return void 0;
|
|
29974
|
+
})();
|
|
29975
|
+
if (!layout && primarySurface === void 0) return void 0;
|
|
29976
|
+
return {
|
|
29977
|
+
...layout,
|
|
29978
|
+
...primarySurface !== void 0 || layout && hasOwn(layout, "primarySurface") ? { primarySurface } : {}
|
|
29979
|
+
};
|
|
29980
|
+
}
|
|
29224
29981
|
function resolveBlockKind(block) {
|
|
29225
29982
|
if (block.kind === "widget" || block.kind === "view") return block.kind;
|
|
29226
29983
|
if (block.widgetType) return "widget";
|
|
@@ -29281,7 +30038,7 @@ function toWorkspaceDefinition(slugOrYaml, nameOverride) {
|
|
|
29281
30038
|
seedEntities: tpl.seedEntities ?? [],
|
|
29282
30039
|
suggestedRelations: tpl.suggestedRelations,
|
|
29283
30040
|
displayTemplates: tpl.displayTemplates,
|
|
29284
|
-
layoutConfig: tpl.layout,
|
|
30041
|
+
layoutConfig: normalizeTemplateLayout(tpl.layout, tpl.app),
|
|
29285
30042
|
bentoViewName: tpl.bento?.viewName,
|
|
29286
30043
|
bentoLayout: bento.bentoLayout,
|
|
29287
30044
|
bentoViewBlocks: bento.bentoViewBlocks,
|
|
@@ -29355,7 +30112,7 @@ function toPackageDefinition(slugOrYaml) {
|
|
|
29355
30112
|
bentoViewBlocks: bento.bentoViewBlocks,
|
|
29356
30113
|
bentoViewName: tpl.bento?.viewName,
|
|
29357
30114
|
profileEntityBentoTemplates: tpl.profileEntityBentoTemplates,
|
|
29358
|
-
layoutConfig: tpl.layout,
|
|
30115
|
+
layoutConfig: normalizeTemplateLayout(tpl.layout, tpl.app),
|
|
29359
30116
|
capabilities: tpl.integrations,
|
|
29360
30117
|
playbooks: tpl.playbooks,
|
|
29361
30118
|
automations: toAutomationWire(tpl.automations),
|
|
@@ -29365,7 +30122,10 @@ function toPackageDefinition(slugOrYaml) {
|
|
|
29365
30122
|
};
|
|
29366
30123
|
}
|
|
29367
30124
|
export {
|
|
30125
|
+
CHANNEL_MESSAGE_CHANNEL_TYPES,
|
|
29368
30126
|
DECLARED_DIVERGENCES,
|
|
30127
|
+
FLOW_NODE_TYPES,
|
|
30128
|
+
FLOW_OUTPUT_TYPES,
|
|
29369
30129
|
OTHER_DOMAIN_ID,
|
|
29370
30130
|
OTHER_DOMAIN_LABEL,
|
|
29371
30131
|
PACKAGE_DEPENDENCY_KINDS,
|
|
@@ -29381,6 +30141,7 @@ export {
|
|
|
29381
30141
|
conceptTerm,
|
|
29382
30142
|
domainLabel,
|
|
29383
30143
|
findConceptViolations,
|
|
30144
|
+
flowValidationErrorMessage,
|
|
29384
30145
|
formatConceptViolations,
|
|
29385
30146
|
getWorkspaceTemplate,
|
|
29386
30147
|
groupByDomain,
|
|
@@ -29390,10 +30151,12 @@ export {
|
|
|
29390
30151
|
listPublicTemplates,
|
|
29391
30152
|
listWorkspaceTemplates,
|
|
29392
30153
|
mergeCatalog,
|
|
30154
|
+
normalizeTemplateLayout,
|
|
29393
30155
|
rowToRemoteEntry,
|
|
29394
30156
|
splitBentoBlocks,
|
|
29395
30157
|
toPackageDefinition,
|
|
29396
30158
|
toWorkspaceDefinition,
|
|
30159
|
+
validateFlowDefinition,
|
|
29397
30160
|
validateTemplate
|
|
29398
30161
|
};
|
|
29399
30162
|
//# sourceMappingURL=index.js.map
|