@wraps.dev/cli 2.14.2 → 2.14.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/cli.js
CHANGED
|
@@ -1756,18 +1756,6 @@ You may need to merge your existing rules into the wraps rule set.`,
|
|
|
1756
1756
|
"TEMPLATE_PUSH_FAILED",
|
|
1757
1757
|
"Check your API key and network connection.",
|
|
1758
1758
|
"https://wraps.dev/docs/templates-as-code"
|
|
1759
|
-
),
|
|
1760
|
-
workflowGenerationFailed: (message) => new WrapsError(
|
|
1761
|
-
`Workflow generation failed${message ? `: ${message}` : ""}`,
|
|
1762
|
-
"WORKFLOW_GENERATION_FAILED",
|
|
1763
|
-
"Try rephrasing your description, or use a built-in template:\n wraps email workflows generate --template welcome\n wraps email workflows generate --template cart-recovery",
|
|
1764
|
-
"https://wraps.dev/docs/guides/orchestration"
|
|
1765
|
-
),
|
|
1766
|
-
aiUsageLimitReached: () => new WrapsError(
|
|
1767
|
-
"AI generation usage limit reached",
|
|
1768
|
-
"AI_USAGE_LIMIT_REACHED",
|
|
1769
|
-
"Upgrade your plan for more AI generations, or use built-in templates:\n wraps email workflows generate --template welcome\n wraps email workflows generate --template cart-recovery",
|
|
1770
|
-
"https://wraps.dev/docs/pricing"
|
|
1771
1759
|
)
|
|
1772
1760
|
};
|
|
1773
1761
|
}
|
|
@@ -8969,8 +8957,8 @@ async function authStatus(_options = {}) {
|
|
|
8969
8957
|
init_esm_shims();
|
|
8970
8958
|
init_events();
|
|
8971
8959
|
init_aws();
|
|
8972
|
-
init_json_output();
|
|
8973
8960
|
init_aws_detection();
|
|
8961
|
+
init_json_output();
|
|
8974
8962
|
import * as clack5 from "@clack/prompts";
|
|
8975
8963
|
import pc6 from "picocolors";
|
|
8976
8964
|
async function runDiagnostics(state) {
|
|
@@ -9902,8 +9890,8 @@ init_events();
|
|
|
9902
9890
|
init_route53();
|
|
9903
9891
|
init_aws();
|
|
9904
9892
|
init_errors();
|
|
9905
|
-
init_json_output();
|
|
9906
9893
|
init_fs();
|
|
9894
|
+
init_json_output();
|
|
9907
9895
|
init_metadata();
|
|
9908
9896
|
init_output();
|
|
9909
9897
|
import * as clack9 from "@clack/prompts";
|
|
@@ -11158,8 +11146,8 @@ function validateConfig2(config2) {
|
|
|
11158
11146
|
// src/commands/cdn/init.ts
|
|
11159
11147
|
init_aws();
|
|
11160
11148
|
init_errors();
|
|
11161
|
-
init_json_output();
|
|
11162
11149
|
init_fs();
|
|
11150
|
+
init_json_output();
|
|
11163
11151
|
init_metadata();
|
|
11164
11152
|
init_output();
|
|
11165
11153
|
init_prompts();
|
|
@@ -12083,10 +12071,10 @@ import * as pulumi7 from "@pulumi/pulumi";
|
|
|
12083
12071
|
import pc13 from "picocolors";
|
|
12084
12072
|
init_client();
|
|
12085
12073
|
init_events();
|
|
12086
|
-
init_errors();
|
|
12087
|
-
init_json_output();
|
|
12088
12074
|
init_aws();
|
|
12075
|
+
init_errors();
|
|
12089
12076
|
init_fs();
|
|
12077
|
+
init_json_output();
|
|
12090
12078
|
init_metadata();
|
|
12091
12079
|
init_output();
|
|
12092
12080
|
async function cdnSync(options) {
|
|
@@ -12262,10 +12250,10 @@ import * as pulumi8 from "@pulumi/pulumi";
|
|
|
12262
12250
|
import pc14 from "picocolors";
|
|
12263
12251
|
init_client();
|
|
12264
12252
|
init_events();
|
|
12265
|
-
init_errors();
|
|
12266
|
-
init_json_output();
|
|
12267
12253
|
init_aws();
|
|
12254
|
+
init_errors();
|
|
12268
12255
|
init_fs();
|
|
12256
|
+
init_json_output();
|
|
12269
12257
|
init_metadata();
|
|
12270
12258
|
init_output();
|
|
12271
12259
|
async function cdnUpgrade(options) {
|
|
@@ -12273,7 +12261,9 @@ async function cdnUpgrade(options) {
|
|
|
12273
12261
|
const progress = new DeploymentProgress();
|
|
12274
12262
|
if (!isJsonMode()) {
|
|
12275
12263
|
clack13.intro(
|
|
12276
|
-
pc14.bold(
|
|
12264
|
+
pc14.bold(
|
|
12265
|
+
options.preview ? "Wraps CDN Upgrade Preview" : "Wraps CDN Upgrade"
|
|
12266
|
+
)
|
|
12277
12267
|
);
|
|
12278
12268
|
}
|
|
12279
12269
|
const identity = await progress.execute(
|
|
@@ -12562,10 +12552,10 @@ ${pc14.green("")} ${pc14.bold("Upgrade complete!")}
|
|
|
12562
12552
|
// src/commands/cdn/verify.ts
|
|
12563
12553
|
init_esm_shims();
|
|
12564
12554
|
init_events();
|
|
12565
|
-
init_errors();
|
|
12566
|
-
init_json_output();
|
|
12567
12555
|
init_aws();
|
|
12556
|
+
init_errors();
|
|
12568
12557
|
init_fs();
|
|
12558
|
+
init_json_output();
|
|
12569
12559
|
init_metadata();
|
|
12570
12560
|
init_output();
|
|
12571
12561
|
import * as clack14 from "@clack/prompts";
|
|
@@ -16941,8 +16931,8 @@ async function deployEmailStack(config2) {
|
|
|
16941
16931
|
init_events();
|
|
16942
16932
|
init_aws();
|
|
16943
16933
|
init_errors();
|
|
16944
|
-
init_json_output();
|
|
16945
16934
|
init_fs();
|
|
16935
|
+
init_json_output();
|
|
16946
16936
|
init_metadata();
|
|
16947
16937
|
init_output();
|
|
16948
16938
|
async function config(options) {
|
|
@@ -17230,8 +17220,8 @@ init_events();
|
|
|
17230
17220
|
init_presets();
|
|
17231
17221
|
init_aws();
|
|
17232
17222
|
init_errors();
|
|
17233
|
-
init_json_output();
|
|
17234
17223
|
init_fs();
|
|
17224
|
+
init_json_output();
|
|
17235
17225
|
init_metadata();
|
|
17236
17226
|
init_output();
|
|
17237
17227
|
init_prompts();
|
|
@@ -17825,11 +17815,10 @@ ${pc18.dim("Example:")}`);
|
|
|
17825
17815
|
init_esm_shims();
|
|
17826
17816
|
init_events();
|
|
17827
17817
|
init_route53();
|
|
17828
|
-
init_errors();
|
|
17829
|
-
init_json_output();
|
|
17830
17818
|
init_aws();
|
|
17831
17819
|
init_errors();
|
|
17832
17820
|
init_fs();
|
|
17821
|
+
init_json_output();
|
|
17833
17822
|
init_metadata();
|
|
17834
17823
|
init_output();
|
|
17835
17824
|
import * as clack18 from "@clack/prompts";
|
|
@@ -19234,8 +19223,8 @@ async function deleteReceiptRuleSet(region) {
|
|
|
19234
19223
|
// src/commands/email/inbound.ts
|
|
19235
19224
|
init_aws();
|
|
19236
19225
|
init_errors();
|
|
19237
|
-
init_json_output();
|
|
19238
19226
|
init_fs();
|
|
19227
|
+
init_json_output();
|
|
19239
19228
|
init_metadata();
|
|
19240
19229
|
init_output();
|
|
19241
19230
|
init_prompts();
|
|
@@ -19636,9 +19625,7 @@ Enable it: ${pc21.cyan("wraps email inbound init")}
|
|
|
19636
19625
|
console.log();
|
|
19637
19626
|
console.log(pc21.bold(" Inbound Email Configuration"));
|
|
19638
19627
|
console.log();
|
|
19639
|
-
console.log(
|
|
19640
|
-
` ${pc21.dim("Receiving domain:")} ${pc21.cyan(receivingDomain)}`
|
|
19641
|
-
);
|
|
19628
|
+
console.log(` ${pc21.dim("Receiving domain:")} ${pc21.cyan(receivingDomain)}`);
|
|
19642
19629
|
console.log(
|
|
19643
19630
|
` ${pc21.dim("S3 bucket:")} ${pc21.cyan(inbound.bucketName || "")}`
|
|
19644
19631
|
);
|
|
@@ -19913,7 +19900,6 @@ init_costs();
|
|
|
19913
19900
|
init_presets();
|
|
19914
19901
|
init_aws();
|
|
19915
19902
|
init_errors();
|
|
19916
|
-
init_json_output();
|
|
19917
19903
|
init_fs();
|
|
19918
19904
|
|
|
19919
19905
|
// src/utils/shared/iam-check.ts
|
|
@@ -20060,6 +20046,7 @@ function formatDeniedActions(actions) {
|
|
|
20060
20046
|
}
|
|
20061
20047
|
|
|
20062
20048
|
// src/commands/email/init.ts
|
|
20049
|
+
init_json_output();
|
|
20063
20050
|
init_metadata();
|
|
20064
20051
|
init_output();
|
|
20065
20052
|
init_prompts();
|
|
@@ -20661,8 +20648,8 @@ init_esm_shims();
|
|
|
20661
20648
|
init_events();
|
|
20662
20649
|
init_aws();
|
|
20663
20650
|
init_errors();
|
|
20664
|
-
init_json_output();
|
|
20665
20651
|
init_fs();
|
|
20652
|
+
init_json_output();
|
|
20666
20653
|
init_metadata();
|
|
20667
20654
|
init_output();
|
|
20668
20655
|
import * as clack24 from "@clack/prompts";
|
|
@@ -20865,8 +20852,8 @@ init_events();
|
|
|
20865
20852
|
init_aws();
|
|
20866
20853
|
init_errors();
|
|
20867
20854
|
init_fs();
|
|
20868
|
-
init_metadata();
|
|
20869
20855
|
init_json_output();
|
|
20856
|
+
init_metadata();
|
|
20870
20857
|
init_output();
|
|
20871
20858
|
import * as clack25 from "@clack/prompts";
|
|
20872
20859
|
import * as pulumi19 from "@pulumi/pulumi";
|
|
@@ -21075,7 +21062,8 @@ async function scaffoldClaudeSkill({
|
|
|
21075
21062
|
const skillDir = join8(projectDir, ".claude", "skills", skillName);
|
|
21076
21063
|
const skillPath = join8(skillDir, "SKILL.md");
|
|
21077
21064
|
await mkdir2(skillDir, { recursive: true });
|
|
21078
|
-
await writeFile4(skillPath, skillContent.trim()
|
|
21065
|
+
await writeFile4(skillPath, `${skillContent.trim()}
|
|
21066
|
+
`, "utf-8");
|
|
21079
21067
|
}
|
|
21080
21068
|
function escapeRegex(str) {
|
|
21081
21069
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -21587,9 +21575,10 @@ wraps/.wraps/
|
|
|
21587
21575
|
});
|
|
21588
21576
|
claudeFiles.push(".claude/skills/wraps-templates/SKILL.md");
|
|
21589
21577
|
progress.succeed("Claude Code context scaffolded");
|
|
21590
|
-
} catch {
|
|
21578
|
+
} catch (e) {
|
|
21579
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
21591
21580
|
progress.info(
|
|
21592
|
-
|
|
21581
|
+
`Could not scaffold .claude/ context \u2014 template files are still ready (${msg})`
|
|
21593
21582
|
);
|
|
21594
21583
|
}
|
|
21595
21584
|
}
|
|
@@ -22809,8 +22798,8 @@ init_costs();
|
|
|
22809
22798
|
init_presets();
|
|
22810
22799
|
init_aws();
|
|
22811
22800
|
init_errors();
|
|
22812
|
-
init_json_output();
|
|
22813
22801
|
init_fs();
|
|
22802
|
+
init_json_output();
|
|
22814
22803
|
init_metadata();
|
|
22815
22804
|
init_output();
|
|
22816
22805
|
init_prompts();
|
|
@@ -24734,852 +24723,133 @@ ${pc30.green("\u2713")} ${pc30.bold("Upgrade complete!")}
|
|
|
24734
24723
|
// src/commands/email/workflows/generate.ts
|
|
24735
24724
|
init_esm_shims();
|
|
24736
24725
|
init_events();
|
|
24737
|
-
|
|
24738
|
-
import {
|
|
24739
|
-
import {
|
|
24726
|
+
init_json_output();
|
|
24727
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
24728
|
+
import { join as join14 } from "path";
|
|
24729
|
+
import { intro as intro27, log as log29 } from "@clack/prompts";
|
|
24740
24730
|
import pc31 from "picocolors";
|
|
24731
|
+
var TEMPLATES = {
|
|
24732
|
+
welcome: `import {
|
|
24733
|
+
defineWorkflow,
|
|
24734
|
+
sendEmail,
|
|
24735
|
+
delay,
|
|
24736
|
+
condition,
|
|
24737
|
+
exit,
|
|
24738
|
+
} from '@wraps.dev/client';
|
|
24741
24739
|
|
|
24742
|
-
|
|
24743
|
-
|
|
24744
|
-
|
|
24745
|
-
|
|
24746
|
-
|
|
24747
|
-
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24754
|
-
steps,
|
|
24755
|
-
transitions,
|
|
24756
|
-
triggerType: definition.trigger.type,
|
|
24757
|
-
triggerConfig: extractTriggerConfig(definition.trigger),
|
|
24758
|
-
settings: definition.settings,
|
|
24759
|
-
defaults: definition.defaults
|
|
24760
|
-
};
|
|
24761
|
-
}
|
|
24762
|
-
function createTriggerStep(trigger) {
|
|
24763
|
-
const triggerConfig = {
|
|
24764
|
-
type: "trigger",
|
|
24765
|
-
triggerType: trigger.type,
|
|
24766
|
-
...extractTriggerConfig(trigger)
|
|
24767
|
-
};
|
|
24768
|
-
return {
|
|
24769
|
-
id: "trigger",
|
|
24770
|
-
type: "trigger",
|
|
24771
|
-
name: getTriggerName(trigger.type),
|
|
24772
|
-
position: { x: 0, y: 0 },
|
|
24773
|
-
// Will be updated by assignPositions
|
|
24774
|
-
config: triggerConfig
|
|
24775
|
-
};
|
|
24776
|
-
}
|
|
24777
|
-
function extractTriggerConfig(trigger) {
|
|
24778
|
-
const config2 = {};
|
|
24779
|
-
if (trigger.eventName) {
|
|
24780
|
-
config2.eventName = trigger.eventName;
|
|
24781
|
-
}
|
|
24782
|
-
if (trigger.segmentId) {
|
|
24783
|
-
config2.segmentId = trigger.segmentId;
|
|
24784
|
-
}
|
|
24785
|
-
if (trigger.schedule) {
|
|
24786
|
-
config2.schedule = trigger.schedule;
|
|
24787
|
-
}
|
|
24788
|
-
if (trigger.timezone) {
|
|
24789
|
-
config2.timezone = trigger.timezone;
|
|
24790
|
-
}
|
|
24791
|
-
if (trigger.topicId) {
|
|
24792
|
-
config2.topicId = trigger.topicId;
|
|
24793
|
-
}
|
|
24794
|
-
return config2;
|
|
24795
|
-
}
|
|
24796
|
-
function getTriggerName(type) {
|
|
24797
|
-
const names = {
|
|
24798
|
-
event: "When event occurs",
|
|
24799
|
-
contact_created: "When contact is created",
|
|
24800
|
-
contact_updated: "When contact is updated",
|
|
24801
|
-
segment_entry: "When contact enters segment",
|
|
24802
|
-
segment_exit: "When contact exits segment",
|
|
24803
|
-
schedule: "On schedule",
|
|
24804
|
-
api: "When triggered via API",
|
|
24805
|
-
topic_subscribed: "When contact subscribes to topic",
|
|
24806
|
-
topic_unsubscribed: "When contact unsubscribes from topic"
|
|
24807
|
-
};
|
|
24808
|
-
return names[type] || "Trigger";
|
|
24809
|
-
}
|
|
24810
|
-
function flattenSteps(stepDefs, steps, transitions, fromStepId, branch) {
|
|
24811
|
-
let prevIds = [fromStepId];
|
|
24812
|
-
let firstStepInBranch = true;
|
|
24813
|
-
for (const def of stepDefs) {
|
|
24814
|
-
const step = toWorkflowStep(def);
|
|
24815
|
-
steps.push(step);
|
|
24816
|
-
for (const prevId of prevIds) {
|
|
24817
|
-
const transition = {
|
|
24818
|
-
id: `t-${prevId}-${step.id}`,
|
|
24819
|
-
fromStepId: prevId,
|
|
24820
|
-
toStepId: step.id
|
|
24821
|
-
};
|
|
24822
|
-
if (firstStepInBranch && branch && prevId === fromStepId) {
|
|
24823
|
-
transition.condition = { branch };
|
|
24824
|
-
}
|
|
24825
|
-
transitions.push(transition);
|
|
24826
|
-
}
|
|
24827
|
-
firstStepInBranch = false;
|
|
24828
|
-
if (def.type === "condition" && def.branches) {
|
|
24829
|
-
const leafIds = [];
|
|
24830
|
-
if (def.branches.yes && def.branches.yes.length > 0) {
|
|
24831
|
-
const yesLeaves = flattenSteps(
|
|
24832
|
-
def.branches.yes,
|
|
24833
|
-
steps,
|
|
24834
|
-
transitions,
|
|
24835
|
-
step.id,
|
|
24836
|
-
"yes"
|
|
24837
|
-
);
|
|
24838
|
-
leafIds.push(...yesLeaves);
|
|
24839
|
-
} else {
|
|
24840
|
-
leafIds.push(step.id);
|
|
24841
|
-
}
|
|
24842
|
-
if (def.branches.no && def.branches.no.length > 0) {
|
|
24843
|
-
const noLeaves = flattenSteps(
|
|
24844
|
-
def.branches.no,
|
|
24845
|
-
steps,
|
|
24846
|
-
transitions,
|
|
24847
|
-
step.id,
|
|
24848
|
-
"no"
|
|
24849
|
-
);
|
|
24850
|
-
leafIds.push(...noLeaves);
|
|
24851
|
-
} else if (!leafIds.includes(step.id)) {
|
|
24852
|
-
leafIds.push(step.id);
|
|
24853
|
-
}
|
|
24854
|
-
prevIds = leafIds;
|
|
24855
|
-
continue;
|
|
24856
|
-
}
|
|
24857
|
-
if (def.type === "exit") {
|
|
24858
|
-
prevIds = [];
|
|
24859
|
-
continue;
|
|
24860
|
-
}
|
|
24861
|
-
prevIds = [step.id];
|
|
24862
|
-
}
|
|
24863
|
-
return prevIds;
|
|
24864
|
-
}
|
|
24865
|
-
function toWorkflowStep(def) {
|
|
24866
|
-
const step = {
|
|
24867
|
-
id: def.id,
|
|
24868
|
-
type: def.type,
|
|
24869
|
-
name: def.name || getDefaultStepName(def.type),
|
|
24870
|
-
position: { x: 0, y: 0 },
|
|
24871
|
-
// Will be updated by assignPositions
|
|
24872
|
-
config: def.config
|
|
24873
|
-
};
|
|
24874
|
-
if (def.cascadeGroupId) {
|
|
24875
|
-
step.cascadeGroupId = def.cascadeGroupId;
|
|
24876
|
-
}
|
|
24877
|
-
return step;
|
|
24878
|
-
}
|
|
24879
|
-
function getDefaultStepName(type) {
|
|
24880
|
-
const names = {
|
|
24881
|
-
send_email: "Send Email",
|
|
24882
|
-
send_sms: "Send SMS",
|
|
24883
|
-
delay: "Wait",
|
|
24884
|
-
exit: "Exit",
|
|
24885
|
-
condition: "Condition",
|
|
24886
|
-
webhook: "Webhook",
|
|
24887
|
-
update_contact: "Update Contact",
|
|
24888
|
-
wait_for_event: "Wait for Event",
|
|
24889
|
-
wait_for_email_engagement: "Wait for Email Engagement",
|
|
24890
|
-
subscribe_topic: "Subscribe to Topic",
|
|
24891
|
-
unsubscribe_topic: "Unsubscribe from Topic"
|
|
24892
|
-
};
|
|
24893
|
-
return names[type] || type;
|
|
24894
|
-
}
|
|
24895
|
-
function assignPositions(steps, transitions) {
|
|
24896
|
-
const LEVEL_HEIGHT = 200;
|
|
24897
|
-
const BRANCH_OFFSET = 300;
|
|
24898
|
-
const childrenMap = /* @__PURE__ */ new Map();
|
|
24899
|
-
for (const t of transitions) {
|
|
24900
|
-
if (!childrenMap.has(t.fromStepId)) {
|
|
24901
|
-
childrenMap.set(t.fromStepId, []);
|
|
24902
|
-
}
|
|
24903
|
-
childrenMap.get(t.fromStepId)?.push({
|
|
24904
|
-
id: t.toStepId,
|
|
24905
|
-
branch: t.condition?.branch
|
|
24906
|
-
});
|
|
24907
|
-
}
|
|
24908
|
-
const visited = /* @__PURE__ */ new Set();
|
|
24909
|
-
const queue = [];
|
|
24910
|
-
const triggerStep = steps.find((s) => s.type === "trigger");
|
|
24911
|
-
if (triggerStep) {
|
|
24912
|
-
queue.push({ stepId: triggerStep.id, level: 0, xOffset: 0 });
|
|
24913
|
-
}
|
|
24914
|
-
while (queue.length > 0) {
|
|
24915
|
-
const { stepId, level, xOffset } = queue.shift();
|
|
24916
|
-
if (visited.has(stepId)) {
|
|
24917
|
-
continue;
|
|
24918
|
-
}
|
|
24919
|
-
visited.add(stepId);
|
|
24920
|
-
const step = steps.find((s) => s.id === stepId);
|
|
24921
|
-
if (step) {
|
|
24922
|
-
step.position = {
|
|
24923
|
-
x: xOffset,
|
|
24924
|
-
y: level * LEVEL_HEIGHT
|
|
24925
|
-
};
|
|
24926
|
-
}
|
|
24927
|
-
const children = childrenMap.get(stepId) || [];
|
|
24928
|
-
for (const child of children) {
|
|
24929
|
-
if (!visited.has(child.id)) {
|
|
24930
|
-
let childXOffset = xOffset;
|
|
24931
|
-
if (child.branch === "yes") {
|
|
24932
|
-
childXOffset = xOffset - BRANCH_OFFSET;
|
|
24933
|
-
} else if (child.branch === "no") {
|
|
24934
|
-
childXOffset = xOffset + BRANCH_OFFSET;
|
|
24935
|
-
}
|
|
24936
|
-
queue.push({
|
|
24937
|
-
stepId: child.id,
|
|
24938
|
-
level: level + 1,
|
|
24939
|
-
xOffset: childXOffset
|
|
24940
|
-
});
|
|
24941
|
-
}
|
|
24942
|
-
}
|
|
24943
|
-
}
|
|
24944
|
-
for (const step of steps) {
|
|
24945
|
-
if (!visited.has(step.id)) {
|
|
24946
|
-
step.position = {
|
|
24947
|
-
x: 600,
|
|
24948
|
-
y: steps.indexOf(step) * LEVEL_HEIGHT
|
|
24949
|
-
};
|
|
24950
|
-
}
|
|
24951
|
-
}
|
|
24952
|
-
}
|
|
24953
|
-
|
|
24954
|
-
// src/utils/email/workflow-ts.ts
|
|
24955
|
-
init_esm_shims();
|
|
24956
|
-
import { createHash as createHash2 } from "crypto";
|
|
24957
|
-
import { existsSync as existsSync13 } from "fs";
|
|
24958
|
-
import { mkdir as mkdir7, readdir as readdir3, readFile as readFile7, writeFile as writeFile9 } from "fs/promises";
|
|
24959
|
-
import { basename, join as join14 } from "path";
|
|
24960
|
-
async function discoverWorkflows(dir, filter) {
|
|
24961
|
-
if (!existsSync13(dir)) {
|
|
24962
|
-
return [];
|
|
24963
|
-
}
|
|
24964
|
-
const entries = await readdir3(dir);
|
|
24965
|
-
const workflows = entries.filter(
|
|
24966
|
-
(f) => (
|
|
24967
|
-
// Include .ts files only (not .tsx for workflows)
|
|
24968
|
-
f.endsWith(".ts") && // Exclude private/helper files starting with _
|
|
24969
|
-
!f.startsWith("_") && // Exclude type definition files
|
|
24970
|
-
!f.endsWith(".d.ts")
|
|
24971
|
-
)
|
|
24972
|
-
);
|
|
24973
|
-
if (filter) {
|
|
24974
|
-
const slug = filter.replace(/\.ts$/, "");
|
|
24975
|
-
return workflows.filter((f) => f.replace(/\.ts$/, "") === slug);
|
|
24976
|
-
}
|
|
24977
|
-
return workflows;
|
|
24978
|
-
}
|
|
24979
|
-
async function parseWorkflowTs(filePath, wrapsDir) {
|
|
24980
|
-
const { build: build2 } = await import("esbuild");
|
|
24981
|
-
const source = await readFile7(filePath, "utf-8");
|
|
24982
|
-
const sourceHash = createHash2("sha256").update(source).digest("hex");
|
|
24983
|
-
const slug = basename(filePath, ".ts");
|
|
24984
|
-
const shimDir = join14(wrapsDir, ".wraps", "_shims");
|
|
24985
|
-
await mkdir7(shimDir, { recursive: true });
|
|
24986
|
-
const clientShimContent = `
|
|
24987
|
-
// Identity functions for workflow definitions
|
|
24988
|
-
export const defineWorkflow = (def) => def;
|
|
24989
|
-
|
|
24990
|
-
// Step helper functions - they just create step definition objects
|
|
24991
|
-
export const sendEmail = (id, config) => ({
|
|
24992
|
-
id,
|
|
24993
|
-
type: 'send_email',
|
|
24994
|
-
name: config.name ?? \`Send email: \${config.template || 'custom'}\`,
|
|
24995
|
-
config: { type: 'send_email', ...config },
|
|
24996
|
-
});
|
|
24740
|
+
/**
|
|
24741
|
+
* Welcome Sequence
|
|
24742
|
+
*
|
|
24743
|
+
* Send a welcome email when a contact is created,
|
|
24744
|
+
* wait 1 day, then check if they activated.
|
|
24745
|
+
* If not, send a follow-up with tips.
|
|
24746
|
+
*/
|
|
24747
|
+
export default defineWorkflow({
|
|
24748
|
+
name: 'Welcome Sequence',
|
|
24749
|
+
trigger: {
|
|
24750
|
+
type: 'contact_created',
|
|
24751
|
+
},
|
|
24997
24752
|
|
|
24998
|
-
|
|
24999
|
-
|
|
25000
|
-
|
|
25001
|
-
|
|
25002
|
-
|
|
24753
|
+
steps: [
|
|
24754
|
+
sendEmail('send-welcome', { template: 'welcome-email' }),
|
|
24755
|
+
delay('wait-1-day', { days: 1 }),
|
|
24756
|
+
condition('check-activated', {
|
|
24757
|
+
field: 'contact.hasActivated',
|
|
24758
|
+
operator: 'equals',
|
|
24759
|
+
value: true,
|
|
24760
|
+
branches: {
|
|
24761
|
+
yes: [exit('already-active')],
|
|
24762
|
+
no: [
|
|
24763
|
+
sendEmail('send-tips', { template: 'getting-started-tips' }),
|
|
24764
|
+
],
|
|
24765
|
+
},
|
|
24766
|
+
}),
|
|
24767
|
+
],
|
|
25003
24768
|
});
|
|
24769
|
+
`,
|
|
24770
|
+
"cart-recovery": `import {
|
|
24771
|
+
defineWorkflow,
|
|
24772
|
+
sendEmail,
|
|
24773
|
+
sendSms,
|
|
24774
|
+
delay,
|
|
24775
|
+
cascade,
|
|
24776
|
+
exit,
|
|
24777
|
+
} from '@wraps.dev/client';
|
|
25004
24778
|
|
|
25005
|
-
|
|
25006
|
-
|
|
25007
|
-
|
|
25008
|
-
|
|
25009
|
-
|
|
25010
|
-
|
|
25011
|
-
|
|
25012
|
-
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
const { branches, name, ...conditionConfig } = config;
|
|
25018
|
-
return {
|
|
25019
|
-
id,
|
|
25020
|
-
type: 'condition',
|
|
25021
|
-
name: name ?? \`Check: \${config.field} \${config.operator}\`,
|
|
25022
|
-
config: { type: 'condition', ...conditionConfig },
|
|
25023
|
-
branches,
|
|
25024
|
-
};
|
|
25025
|
-
};
|
|
25026
|
-
|
|
25027
|
-
export const waitForEvent = (id, config) => {
|
|
25028
|
-
const { name, timeout, ...eventConfig } = config;
|
|
25029
|
-
return {
|
|
25030
|
-
id,
|
|
25031
|
-
type: 'wait_for_event',
|
|
25032
|
-
name: name ?? \`Wait for: \${config.eventName}\`,
|
|
25033
|
-
config: {
|
|
25034
|
-
type: 'wait_for_event',
|
|
25035
|
-
eventName: eventConfig.eventName,
|
|
25036
|
-
timeoutSeconds: durationToSeconds(timeout),
|
|
25037
|
-
},
|
|
25038
|
-
};
|
|
25039
|
-
};
|
|
25040
|
-
|
|
25041
|
-
export const waitForEmailEngagement = (id, config) => {
|
|
25042
|
-
const { name, timeout, emailStepId, engagementType } = config;
|
|
25043
|
-
return {
|
|
25044
|
-
id,
|
|
25045
|
-
type: 'wait_for_email_engagement',
|
|
25046
|
-
name: name ?? \`Wait for email \${engagementType}: \${emailStepId}\`,
|
|
25047
|
-
config: {
|
|
25048
|
-
type: 'wait_for_email_engagement',
|
|
25049
|
-
timeoutSeconds: durationToSeconds(timeout),
|
|
25050
|
-
},
|
|
25051
|
-
};
|
|
25052
|
-
};
|
|
25053
|
-
|
|
25054
|
-
export const exit = (id, config) => {
|
|
25055
|
-
const { name, ...exitConfig } = config ?? {};
|
|
25056
|
-
return {
|
|
25057
|
-
id,
|
|
25058
|
-
type: 'exit',
|
|
25059
|
-
name: name ?? 'Exit',
|
|
25060
|
-
config: { type: 'exit', ...exitConfig },
|
|
25061
|
-
};
|
|
25062
|
-
};
|
|
25063
|
-
|
|
25064
|
-
export const updateContact = (id, config) => {
|
|
25065
|
-
const { name, ...updateConfig } = config;
|
|
25066
|
-
return {
|
|
25067
|
-
id,
|
|
25068
|
-
type: 'update_contact',
|
|
25069
|
-
name: name ?? 'Update contact',
|
|
25070
|
-
config: { type: 'update_contact', ...updateConfig },
|
|
25071
|
-
};
|
|
25072
|
-
};
|
|
24779
|
+
/**
|
|
24780
|
+
* Cart Recovery Cascade
|
|
24781
|
+
*
|
|
24782
|
+
* When a cart is abandoned, wait 30 minutes, then try
|
|
24783
|
+
* email first. If not opened after 2 hours, fall back to SMS.
|
|
24784
|
+
*/
|
|
24785
|
+
export default defineWorkflow({
|
|
24786
|
+
name: 'Cart Recovery Cascade',
|
|
24787
|
+
trigger: {
|
|
24788
|
+
type: 'event',
|
|
24789
|
+
eventName: 'cart.abandoned',
|
|
24790
|
+
},
|
|
25073
24791
|
|
|
25074
|
-
|
|
25075
|
-
|
|
25076
|
-
return {
|
|
25077
|
-
id,
|
|
25078
|
-
type: 'subscribe_topic',
|
|
25079
|
-
name: name ?? \`Subscribe to topic: \${config.topicId}\`,
|
|
25080
|
-
config: { type: 'subscribe_topic', ...topicConfig },
|
|
25081
|
-
};
|
|
25082
|
-
};
|
|
24792
|
+
steps: [
|
|
24793
|
+
delay('initial-wait', { minutes: 30 }),
|
|
25083
24794
|
|
|
25084
|
-
|
|
25085
|
-
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
|
|
25091
|
-
|
|
25092
|
-
|
|
24795
|
+
...cascade('recover-cart', {
|
|
24796
|
+
channels: [
|
|
24797
|
+
{
|
|
24798
|
+
type: 'email',
|
|
24799
|
+
template: 'cart-recovery',
|
|
24800
|
+
waitFor: { hours: 2 },
|
|
24801
|
+
engagement: 'opened',
|
|
24802
|
+
},
|
|
24803
|
+
{
|
|
24804
|
+
type: 'sms',
|
|
24805
|
+
template: 'cart-sms-reminder',
|
|
24806
|
+
},
|
|
24807
|
+
],
|
|
24808
|
+
}),
|
|
25093
24809
|
|
|
25094
|
-
|
|
25095
|
-
|
|
25096
|
-
|
|
25097
|
-
|
|
25098
|
-
|
|
25099
|
-
|
|
25100
|
-
|
|
25101
|
-
|
|
25102
|
-
|
|
24810
|
+
exit('cascade-complete'),
|
|
24811
|
+
],
|
|
24812
|
+
});
|
|
24813
|
+
`,
|
|
24814
|
+
"trial-conversion": `import {
|
|
24815
|
+
defineWorkflow,
|
|
24816
|
+
sendEmail,
|
|
24817
|
+
delay,
|
|
24818
|
+
condition,
|
|
24819
|
+
exit,
|
|
24820
|
+
} from '@wraps.dev/client';
|
|
25103
24821
|
|
|
25104
24822
|
/**
|
|
25105
|
-
*
|
|
25106
|
-
*
|
|
25107
|
-
* For each email channel (except the last), we emit:
|
|
25108
|
-
* send_email \u2192 wait_for_email_engagement \u2192 condition (engaged?)
|
|
25109
|
-
* with the condition's "yes" branch containing an exit node and the "no"
|
|
25110
|
-
* branch falling through to the next channel.
|
|
25111
|
-
*
|
|
25112
|
-
* Non-email channels (SMS) emit only a send step.
|
|
24823
|
+
* Trial Conversion
|
|
25113
24824
|
*
|
|
25114
|
-
*
|
|
25115
|
-
*
|
|
24825
|
+
* Remind users 3 days before their trial ends.
|
|
24826
|
+
* If they haven't upgraded after 1 day, send a final nudge.
|
|
25116
24827
|
*/
|
|
25117
|
-
export
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
|
|
25122
|
-
|
|
25123
|
-
const isLast = i === channels.length - 1;
|
|
25124
|
-
|
|
25125
|
-
if (channel.type === 'email') {
|
|
25126
|
-
// Send email step
|
|
25127
|
-
steps.push({
|
|
25128
|
-
id: id + '-send-' + i,
|
|
25129
|
-
type: 'send_email',
|
|
25130
|
-
name: 'Cascade: send ' + (channel.template || 'email'),
|
|
25131
|
-
config: { type: 'send_email', templateId: channel.template },
|
|
25132
|
-
cascadeGroupId: id,
|
|
25133
|
-
});
|
|
24828
|
+
export default defineWorkflow({
|
|
24829
|
+
name: 'Trial Conversion',
|
|
24830
|
+
trigger: {
|
|
24831
|
+
type: 'event',
|
|
24832
|
+
eventName: 'trial.ending',
|
|
24833
|
+
},
|
|
25134
24834
|
|
|
25135
|
-
|
|
25136
|
-
|
|
25137
|
-
|
|
25138
|
-
|
|
25139
|
-
|
|
25140
|
-
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
id: condId,
|
|
25154
|
-
type: 'condition',
|
|
25155
|
-
name: 'Cascade: email engaged?',
|
|
25156
|
-
config: {
|
|
25157
|
-
type: 'condition',
|
|
25158
|
-
field: 'engagement.status',
|
|
25159
|
-
operator: 'equals',
|
|
25160
|
-
value: 'true',
|
|
25161
|
-
},
|
|
25162
|
-
cascadeGroupId: id,
|
|
25163
|
-
branches: {
|
|
25164
|
-
yes: [{
|
|
25165
|
-
id: exitId,
|
|
25166
|
-
type: 'exit',
|
|
25167
|
-
name: 'Exit',
|
|
25168
|
-
config: { type: 'exit', reason: 'Engaged via email' },
|
|
25169
|
-
cascadeGroupId: id,
|
|
25170
|
-
}],
|
|
25171
|
-
},
|
|
25172
|
-
});
|
|
25173
|
-
}
|
|
25174
|
-
} else if (channel.type === 'sms') {
|
|
25175
|
-
// Send SMS step
|
|
25176
|
-
steps.push({
|
|
25177
|
-
id: id + '-send-' + i,
|
|
25178
|
-
type: 'send_sms',
|
|
25179
|
-
name: 'Cascade: send ' + (channel.template || 'sms'),
|
|
25180
|
-
config: { type: 'send_sms', template: channel.template, body: channel.body },
|
|
25181
|
-
cascadeGroupId: id,
|
|
25182
|
-
});
|
|
25183
|
-
}
|
|
25184
|
-
}
|
|
25185
|
-
|
|
25186
|
-
return steps;
|
|
25187
|
-
};
|
|
25188
|
-
|
|
25189
|
-
// Internal helpers
|
|
25190
|
-
function normalizeDuration(duration) {
|
|
25191
|
-
if (duration.days !== undefined) {
|
|
25192
|
-
return { amount: duration.days, unit: 'days' };
|
|
25193
|
-
}
|
|
25194
|
-
if (duration.hours !== undefined) {
|
|
25195
|
-
return { amount: duration.hours, unit: 'hours' };
|
|
25196
|
-
}
|
|
25197
|
-
if (duration.minutes !== undefined) {
|
|
25198
|
-
return { amount: duration.minutes, unit: 'minutes' };
|
|
25199
|
-
}
|
|
25200
|
-
return { amount: 1, unit: 'hours' };
|
|
25201
|
-
}
|
|
25202
|
-
|
|
25203
|
-
function durationToSeconds(duration) {
|
|
25204
|
-
if (!duration) return undefined;
|
|
25205
|
-
let seconds = 0;
|
|
25206
|
-
if (duration.days) seconds += duration.days * 24 * 60 * 60;
|
|
25207
|
-
if (duration.hours) seconds += duration.hours * 60 * 60;
|
|
25208
|
-
if (duration.minutes) seconds += duration.minutes * 60;
|
|
25209
|
-
return seconds > 0 ? seconds : undefined;
|
|
25210
|
-
}
|
|
25211
|
-
`;
|
|
25212
|
-
await writeFile9(
|
|
25213
|
-
join14(shimDir, "wraps-client-shim.mjs"),
|
|
25214
|
-
clientShimContent,
|
|
25215
|
-
"utf-8"
|
|
25216
|
-
);
|
|
25217
|
-
const result = await build2({
|
|
25218
|
-
entryPoints: [filePath],
|
|
25219
|
-
bundle: true,
|
|
25220
|
-
write: false,
|
|
25221
|
-
format: "esm",
|
|
25222
|
-
platform: "node",
|
|
25223
|
-
target: "node20",
|
|
25224
|
-
alias: {
|
|
25225
|
-
"@wraps.dev/client": join14(shimDir, "wraps-client-shim.mjs")
|
|
25226
|
-
}
|
|
25227
|
-
});
|
|
25228
|
-
const bundledCode = result.outputFiles[0].text;
|
|
25229
|
-
const tmpDir = join14(wrapsDir, ".wraps", "_workflows");
|
|
25230
|
-
await mkdir7(tmpDir, { recursive: true });
|
|
25231
|
-
const tmpPath = join14(tmpDir, `${slug}.mjs`);
|
|
25232
|
-
await writeFile9(tmpPath, bundledCode, "utf-8");
|
|
25233
|
-
const mod = await import(`${tmpPath}?t=${Date.now()}`);
|
|
25234
|
-
const definition = mod.default;
|
|
25235
|
-
if (!definition || typeof definition !== "object") {
|
|
25236
|
-
throw new Error(
|
|
25237
|
-
"Workflow must have a default export (workflow definition from defineWorkflow())"
|
|
25238
|
-
);
|
|
25239
|
-
}
|
|
25240
|
-
if (!definition.name) {
|
|
25241
|
-
throw new Error("Workflow definition must have a 'name' property");
|
|
25242
|
-
}
|
|
25243
|
-
if (!definition.trigger) {
|
|
25244
|
-
throw new Error("Workflow definition must have a 'trigger' property");
|
|
25245
|
-
}
|
|
25246
|
-
if (!Array.isArray(definition.steps)) {
|
|
25247
|
-
throw new Error("Workflow definition must have a 'steps' array");
|
|
25248
|
-
}
|
|
25249
|
-
return {
|
|
25250
|
-
slug,
|
|
25251
|
-
filePath,
|
|
25252
|
-
source,
|
|
25253
|
-
sourceHash,
|
|
25254
|
-
definition,
|
|
25255
|
-
cliProjectPath: `workflows/${slug}.ts`
|
|
25256
|
-
};
|
|
25257
|
-
}
|
|
25258
|
-
|
|
25259
|
-
// src/utils/email/workflow-validator.ts
|
|
25260
|
-
init_esm_shims();
|
|
25261
|
-
function validateTransformedWorkflow(transformed, localTemplateSlugs) {
|
|
25262
|
-
const errors2 = [];
|
|
25263
|
-
errors2.push(...validateStructure(transformed.steps, transformed.transitions));
|
|
25264
|
-
for (const step of transformed.steps) {
|
|
25265
|
-
errors2.push(...validateStep(step));
|
|
25266
|
-
}
|
|
25267
|
-
if (localTemplateSlugs) {
|
|
25268
|
-
errors2.push(
|
|
25269
|
-
...validateTemplateReferences(transformed.steps, localTemplateSlugs)
|
|
25270
|
-
);
|
|
25271
|
-
}
|
|
25272
|
-
const errorsByNodeId = /* @__PURE__ */ new Map();
|
|
25273
|
-
for (const error of errors2) {
|
|
25274
|
-
if (error.nodeId) {
|
|
25275
|
-
const existing = errorsByNodeId.get(error.nodeId) || [];
|
|
25276
|
-
existing.push(error);
|
|
25277
|
-
errorsByNodeId.set(error.nodeId, existing);
|
|
25278
|
-
}
|
|
25279
|
-
}
|
|
25280
|
-
return {
|
|
25281
|
-
isValid: errors2.filter((e) => e.severity === "error").length === 0,
|
|
25282
|
-
errors: errors2,
|
|
25283
|
-
errorsByNodeId
|
|
25284
|
-
};
|
|
25285
|
-
}
|
|
25286
|
-
function validateStructure(steps, transitions) {
|
|
25287
|
-
const errors2 = [];
|
|
25288
|
-
const triggerSteps = steps.filter((s) => s.type === "trigger");
|
|
25289
|
-
if (triggerSteps.length === 0) {
|
|
25290
|
-
errors2.push({
|
|
25291
|
-
message: "Workflow must have a trigger node",
|
|
25292
|
-
severity: "error"
|
|
25293
|
-
});
|
|
25294
|
-
} else if (triggerSteps.length > 1) {
|
|
25295
|
-
errors2.push({
|
|
25296
|
-
message: "Workflow can only have one trigger node",
|
|
25297
|
-
severity: "error"
|
|
25298
|
-
});
|
|
25299
|
-
}
|
|
25300
|
-
const actionSteps = steps.filter(
|
|
25301
|
-
(s) => s.type !== "trigger" && s.type !== "exit"
|
|
25302
|
-
);
|
|
25303
|
-
if (actionSteps.length === 0 && steps.length > 1) {
|
|
25304
|
-
errors2.push({
|
|
25305
|
-
message: "Workflow must have at least one action step",
|
|
25306
|
-
severity: "error"
|
|
25307
|
-
});
|
|
25308
|
-
}
|
|
25309
|
-
if (triggerSteps.length === 1) {
|
|
25310
|
-
const reachableIds = getReachableNodeIds(triggerSteps[0].id, transitions);
|
|
25311
|
-
const orphanSteps = steps.filter(
|
|
25312
|
-
(s) => s.type !== "trigger" && !reachableIds.has(s.id)
|
|
25313
|
-
);
|
|
25314
|
-
for (const orphan of orphanSteps) {
|
|
25315
|
-
errors2.push({
|
|
25316
|
-
nodeId: orphan.id,
|
|
25317
|
-
message: `"${orphan.name}" is not connected to the workflow`,
|
|
25318
|
-
severity: "warning"
|
|
25319
|
-
});
|
|
25320
|
-
}
|
|
25321
|
-
}
|
|
25322
|
-
const stepIds = new Set(steps.map((s) => s.id));
|
|
25323
|
-
for (const transition of transitions) {
|
|
25324
|
-
if (!stepIds.has(transition.fromStepId)) {
|
|
25325
|
-
errors2.push({
|
|
25326
|
-
message: `Transition references non-existent source step: ${transition.fromStepId}`,
|
|
25327
|
-
severity: "error"
|
|
25328
|
-
});
|
|
25329
|
-
}
|
|
25330
|
-
if (!stepIds.has(transition.toStepId)) {
|
|
25331
|
-
errors2.push({
|
|
25332
|
-
message: `Transition references non-existent target step: ${transition.toStepId}`,
|
|
25333
|
-
severity: "error"
|
|
25334
|
-
});
|
|
25335
|
-
}
|
|
25336
|
-
}
|
|
25337
|
-
return errors2;
|
|
25338
|
-
}
|
|
25339
|
-
function getReachableNodeIds(startId, transitions) {
|
|
25340
|
-
const reachable = /* @__PURE__ */ new Set();
|
|
25341
|
-
const queue = [startId];
|
|
25342
|
-
while (queue.length > 0) {
|
|
25343
|
-
const currentId = queue.shift();
|
|
25344
|
-
if (reachable.has(currentId)) {
|
|
25345
|
-
continue;
|
|
25346
|
-
}
|
|
25347
|
-
reachable.add(currentId);
|
|
25348
|
-
const outgoing = transitions.filter((t) => t.fromStepId === currentId);
|
|
25349
|
-
for (const t of outgoing) {
|
|
25350
|
-
if (!reachable.has(t.toStepId)) {
|
|
25351
|
-
queue.push(t.toStepId);
|
|
25352
|
-
}
|
|
25353
|
-
}
|
|
25354
|
-
}
|
|
25355
|
-
return reachable;
|
|
25356
|
-
}
|
|
25357
|
-
function validateStep(step) {
|
|
25358
|
-
const errors2 = [];
|
|
25359
|
-
const config2 = step.config;
|
|
25360
|
-
const configType = config2.type;
|
|
25361
|
-
switch (configType) {
|
|
25362
|
-
case "trigger":
|
|
25363
|
-
errors2.push(...validateTrigger(step.id, config2));
|
|
25364
|
-
break;
|
|
25365
|
-
case "send_email":
|
|
25366
|
-
errors2.push(...validateSendEmail(step.id, config2));
|
|
25367
|
-
break;
|
|
25368
|
-
case "condition":
|
|
25369
|
-
errors2.push(...validateCondition(step.id, config2));
|
|
25370
|
-
break;
|
|
25371
|
-
case "webhook":
|
|
25372
|
-
errors2.push(...validateWebhook(step.id, config2));
|
|
25373
|
-
break;
|
|
25374
|
-
case "subscribe_topic":
|
|
25375
|
-
case "unsubscribe_topic":
|
|
25376
|
-
errors2.push(...validateTopic(step.id, config2));
|
|
25377
|
-
break;
|
|
25378
|
-
case "wait_for_event":
|
|
25379
|
-
errors2.push(...validateWaitForEvent(step.id, config2));
|
|
25380
|
-
break;
|
|
25381
|
-
case "delay":
|
|
25382
|
-
errors2.push(...validateDelay(step.id, config2));
|
|
25383
|
-
break;
|
|
25384
|
-
}
|
|
25385
|
-
return errors2;
|
|
25386
|
-
}
|
|
25387
|
-
function validateTrigger(nodeId, config2) {
|
|
25388
|
-
const errors2 = [];
|
|
25389
|
-
switch (config2.triggerType) {
|
|
25390
|
-
case "event":
|
|
25391
|
-
if (!config2.eventName) {
|
|
25392
|
-
errors2.push({
|
|
25393
|
-
nodeId,
|
|
25394
|
-
field: "eventName",
|
|
25395
|
-
message: "Event name is required",
|
|
25396
|
-
severity: "error"
|
|
25397
|
-
});
|
|
25398
|
-
}
|
|
25399
|
-
break;
|
|
25400
|
-
case "segment_entry":
|
|
25401
|
-
case "segment_exit":
|
|
25402
|
-
if (!config2.segmentId) {
|
|
25403
|
-
errors2.push({
|
|
25404
|
-
nodeId,
|
|
25405
|
-
field: "segmentId",
|
|
25406
|
-
message: "Segment is required",
|
|
25407
|
-
severity: "error"
|
|
25408
|
-
});
|
|
25409
|
-
}
|
|
25410
|
-
break;
|
|
25411
|
-
case "topic_subscribed":
|
|
25412
|
-
case "topic_unsubscribed":
|
|
25413
|
-
if (!config2.topicId) {
|
|
25414
|
-
errors2.push({
|
|
25415
|
-
nodeId,
|
|
25416
|
-
field: "topicId",
|
|
25417
|
-
message: "Topic is required",
|
|
25418
|
-
severity: "error"
|
|
25419
|
-
});
|
|
25420
|
-
}
|
|
25421
|
-
break;
|
|
25422
|
-
case "schedule":
|
|
25423
|
-
if (!config2.schedule) {
|
|
25424
|
-
errors2.push({
|
|
25425
|
-
nodeId,
|
|
25426
|
-
field: "schedule",
|
|
25427
|
-
message: "Schedule (cron expression) is required",
|
|
25428
|
-
severity: "error"
|
|
25429
|
-
});
|
|
25430
|
-
}
|
|
25431
|
-
if (!config2.timezone) {
|
|
25432
|
-
errors2.push({
|
|
25433
|
-
nodeId,
|
|
25434
|
-
field: "timezone",
|
|
25435
|
-
message: "Timezone is required",
|
|
25436
|
-
severity: "error"
|
|
25437
|
-
});
|
|
25438
|
-
}
|
|
25439
|
-
break;
|
|
25440
|
-
}
|
|
25441
|
-
return errors2;
|
|
25442
|
-
}
|
|
25443
|
-
function validateSendEmail(nodeId, config2) {
|
|
25444
|
-
const errors2 = [];
|
|
25445
|
-
const templateRef = config2.templateId || config2.template;
|
|
25446
|
-
if (!templateRef) {
|
|
25447
|
-
errors2.push({
|
|
25448
|
-
nodeId,
|
|
25449
|
-
field: "templateId",
|
|
25450
|
-
message: "Email template is required",
|
|
25451
|
-
severity: "error"
|
|
25452
|
-
});
|
|
25453
|
-
}
|
|
25454
|
-
return errors2;
|
|
25455
|
-
}
|
|
25456
|
-
function validateCondition(nodeId, config2) {
|
|
25457
|
-
const errors2 = [];
|
|
25458
|
-
if (!config2.field) {
|
|
25459
|
-
errors2.push({
|
|
25460
|
-
nodeId,
|
|
25461
|
-
field: "field",
|
|
25462
|
-
message: "Condition field is required",
|
|
25463
|
-
severity: "error"
|
|
25464
|
-
});
|
|
25465
|
-
}
|
|
25466
|
-
if (!config2.operator) {
|
|
25467
|
-
errors2.push({
|
|
25468
|
-
nodeId,
|
|
25469
|
-
field: "operator",
|
|
25470
|
-
message: "Condition operator is required",
|
|
25471
|
-
severity: "error"
|
|
25472
|
-
});
|
|
25473
|
-
}
|
|
25474
|
-
if (config2.operator !== "is_set" && config2.operator !== "is_not_set" && config2.operator !== "is_true" && config2.operator !== "is_false" && (config2.value === void 0 || config2.value === "")) {
|
|
25475
|
-
errors2.push({
|
|
25476
|
-
nodeId,
|
|
25477
|
-
field: "value",
|
|
25478
|
-
message: "Condition value is required",
|
|
25479
|
-
severity: "error"
|
|
25480
|
-
});
|
|
25481
|
-
}
|
|
25482
|
-
return errors2;
|
|
25483
|
-
}
|
|
25484
|
-
function validateWebhook(nodeId, config2) {
|
|
25485
|
-
const errors2 = [];
|
|
25486
|
-
if (config2.url) {
|
|
25487
|
-
try {
|
|
25488
|
-
new URL(config2.url);
|
|
25489
|
-
} catch {
|
|
25490
|
-
errors2.push({
|
|
25491
|
-
nodeId,
|
|
25492
|
-
field: "url",
|
|
25493
|
-
message: "Invalid URL format",
|
|
25494
|
-
severity: "error"
|
|
25495
|
-
});
|
|
25496
|
-
}
|
|
25497
|
-
} else {
|
|
25498
|
-
errors2.push({
|
|
25499
|
-
nodeId,
|
|
25500
|
-
field: "url",
|
|
25501
|
-
message: "Webhook URL is required",
|
|
25502
|
-
severity: "error"
|
|
25503
|
-
});
|
|
25504
|
-
}
|
|
25505
|
-
return errors2;
|
|
25506
|
-
}
|
|
25507
|
-
function validateTopic(nodeId, config2) {
|
|
25508
|
-
const errors2 = [];
|
|
25509
|
-
if (!config2.topicId) {
|
|
25510
|
-
errors2.push({
|
|
25511
|
-
nodeId,
|
|
25512
|
-
field: "topicId",
|
|
25513
|
-
message: "Topic is required",
|
|
25514
|
-
severity: "error"
|
|
25515
|
-
});
|
|
25516
|
-
}
|
|
25517
|
-
return errors2;
|
|
25518
|
-
}
|
|
25519
|
-
function validateWaitForEvent(nodeId, config2) {
|
|
25520
|
-
const errors2 = [];
|
|
25521
|
-
if (!config2.eventName) {
|
|
25522
|
-
errors2.push({
|
|
25523
|
-
nodeId,
|
|
25524
|
-
field: "eventName",
|
|
25525
|
-
message: "Event name is required",
|
|
25526
|
-
severity: "error"
|
|
25527
|
-
});
|
|
25528
|
-
}
|
|
25529
|
-
return errors2;
|
|
25530
|
-
}
|
|
25531
|
-
function validateDelay(nodeId, config2) {
|
|
25532
|
-
const errors2 = [];
|
|
25533
|
-
const amount = config2.amount;
|
|
25534
|
-
if (!amount || amount < 1) {
|
|
25535
|
-
errors2.push({
|
|
25536
|
-
nodeId,
|
|
25537
|
-
field: "amount",
|
|
25538
|
-
message: "Delay duration must be at least 1",
|
|
25539
|
-
severity: "error"
|
|
25540
|
-
});
|
|
25541
|
-
}
|
|
25542
|
-
return errors2;
|
|
25543
|
-
}
|
|
25544
|
-
function validateTemplateReferences(steps, localTemplateSlugs) {
|
|
25545
|
-
const errors2 = [];
|
|
25546
|
-
for (const step of steps) {
|
|
25547
|
-
if (step.config.type === "send_email") {
|
|
25548
|
-
const config2 = step.config;
|
|
25549
|
-
const templateRef = config2.templateId || config2.template;
|
|
25550
|
-
if (templateRef && !localTemplateSlugs.has(templateRef)) {
|
|
25551
|
-
errors2.push({
|
|
25552
|
-
nodeId: step.id,
|
|
25553
|
-
field: "templateId",
|
|
25554
|
-
message: `Template "${templateRef}" not found in templates/ directory`,
|
|
25555
|
-
severity: "error"
|
|
25556
|
-
});
|
|
25557
|
-
}
|
|
25558
|
-
}
|
|
25559
|
-
if (step.config.type === "send_sms") {
|
|
25560
|
-
const config2 = step.config;
|
|
25561
|
-
const templateRef = config2.templateId || config2.template;
|
|
25562
|
-
if (templateRef && !localTemplateSlugs.has(templateRef)) {
|
|
25563
|
-
errors2.push({
|
|
25564
|
-
nodeId: step.id,
|
|
25565
|
-
field: "templateId",
|
|
25566
|
-
message: `SMS template "${templateRef}" not found in templates/ directory`,
|
|
25567
|
-
severity: "warning"
|
|
25568
|
-
// Warning for SMS since we might not have SMS templates yet
|
|
25569
|
-
});
|
|
25570
|
-
}
|
|
25571
|
-
}
|
|
25572
|
-
}
|
|
25573
|
-
return errors2;
|
|
25574
|
-
}
|
|
25575
|
-
|
|
25576
|
-
// src/commands/email/workflows/generate.ts
|
|
25577
|
-
init_config();
|
|
25578
|
-
init_errors();
|
|
25579
|
-
init_json_output();
|
|
25580
|
-
init_output();
|
|
25581
|
-
var TEMPLATES = {
|
|
25582
|
-
welcome: `import {
|
|
24835
|
+
steps: [
|
|
24836
|
+
sendEmail('send-reminder', { template: 'trial-ending-reminder' }),
|
|
24837
|
+
delay('wait-1-day', { days: 1 }),
|
|
24838
|
+
condition('check-upgraded', {
|
|
24839
|
+
field: 'contact.plan',
|
|
24840
|
+
operator: 'not_equals',
|
|
24841
|
+
value: 'free',
|
|
24842
|
+
branches: {
|
|
24843
|
+
yes: [exit('already-upgraded')],
|
|
24844
|
+
no: [
|
|
24845
|
+
sendEmail('send-upgrade-nudge', { template: 'upgrade-offer' }),
|
|
24846
|
+
],
|
|
24847
|
+
},
|
|
24848
|
+
}),
|
|
24849
|
+
],
|
|
24850
|
+
});
|
|
24851
|
+
`,
|
|
24852
|
+
"re-engagement": `import {
|
|
25583
24853
|
defineWorkflow,
|
|
25584
24854
|
sendEmail,
|
|
25585
24855
|
delay,
|
|
@@ -25588,178 +24858,58 @@ var TEMPLATES = {
|
|
|
25588
24858
|
} from '@wraps.dev/client';
|
|
25589
24859
|
|
|
25590
24860
|
/**
|
|
25591
|
-
*
|
|
24861
|
+
* Re-engagement Campaign
|
|
25592
24862
|
*
|
|
25593
|
-
*
|
|
25594
|
-
*
|
|
25595
|
-
* If not, send a follow-up with tips.
|
|
24863
|
+
* Win back inactive users with a personalized email.
|
|
24864
|
+
* Wait 3 days for engagement, then send a final offer.
|
|
25596
24865
|
*/
|
|
25597
24866
|
export default defineWorkflow({
|
|
25598
|
-
name: '
|
|
24867
|
+
name: 'Re-engagement Campaign',
|
|
25599
24868
|
trigger: {
|
|
25600
|
-
type: '
|
|
24869
|
+
type: 'event',
|
|
24870
|
+
eventName: 'contact.inactive',
|
|
25601
24871
|
},
|
|
25602
24872
|
|
|
25603
24873
|
steps: [
|
|
25604
|
-
sendEmail('send-
|
|
25605
|
-
delay('wait-
|
|
25606
|
-
condition('check-
|
|
25607
|
-
field: 'contact.
|
|
25608
|
-
operator: '
|
|
24874
|
+
sendEmail('send-win-back', { template: 'we-miss-you' }),
|
|
24875
|
+
delay('wait-3-days', { days: 3 }),
|
|
24876
|
+
condition('check-engaged', {
|
|
24877
|
+
field: 'contact.lastActiveAt',
|
|
24878
|
+
operator: 'is_set',
|
|
25609
24879
|
value: true,
|
|
25610
24880
|
branches: {
|
|
25611
|
-
yes: [exit('
|
|
24881
|
+
yes: [exit('re-engaged')],
|
|
25612
24882
|
no: [
|
|
25613
|
-
sendEmail('send-
|
|
24883
|
+
sendEmail('send-final-offer', { template: 'final-offer' }),
|
|
25614
24884
|
],
|
|
25615
24885
|
},
|
|
25616
24886
|
}),
|
|
25617
24887
|
],
|
|
25618
24888
|
});
|
|
25619
24889
|
`,
|
|
25620
|
-
|
|
24890
|
+
onboarding: `import {
|
|
25621
24891
|
defineWorkflow,
|
|
25622
24892
|
sendEmail,
|
|
25623
|
-
sendSms,
|
|
25624
24893
|
delay,
|
|
25625
|
-
|
|
24894
|
+
condition,
|
|
25626
24895
|
exit,
|
|
25627
24896
|
} from '@wraps.dev/client';
|
|
25628
24897
|
|
|
25629
24898
|
/**
|
|
25630
|
-
*
|
|
24899
|
+
* Multi-step Onboarding
|
|
25631
24900
|
*
|
|
25632
|
-
*
|
|
25633
|
-
*
|
|
24901
|
+
* Guide new users through setup with a series of emails.
|
|
24902
|
+
* Check progress at each step and skip ahead if they're done.
|
|
25634
24903
|
*/
|
|
25635
24904
|
export default defineWorkflow({
|
|
25636
|
-
name: '
|
|
24905
|
+
name: 'Onboarding Sequence',
|
|
25637
24906
|
trigger: {
|
|
25638
|
-
type: '
|
|
25639
|
-
eventName: 'cart.abandoned',
|
|
24907
|
+
type: 'contact_created',
|
|
25640
24908
|
},
|
|
25641
24909
|
|
|
25642
24910
|
steps: [
|
|
25643
|
-
|
|
25644
|
-
|
|
25645
|
-
...cascade('recover-cart', {
|
|
25646
|
-
channels: [
|
|
25647
|
-
{
|
|
25648
|
-
type: 'email',
|
|
25649
|
-
template: 'cart-recovery',
|
|
25650
|
-
waitFor: { hours: 2 },
|
|
25651
|
-
engagement: 'opened',
|
|
25652
|
-
},
|
|
25653
|
-
{
|
|
25654
|
-
type: 'sms',
|
|
25655
|
-
template: 'cart-sms-reminder',
|
|
25656
|
-
},
|
|
25657
|
-
],
|
|
25658
|
-
}),
|
|
25659
|
-
|
|
25660
|
-
exit('cascade-complete'),
|
|
25661
|
-
],
|
|
25662
|
-
});
|
|
25663
|
-
`,
|
|
25664
|
-
"trial-conversion": `import {
|
|
25665
|
-
defineWorkflow,
|
|
25666
|
-
sendEmail,
|
|
25667
|
-
delay,
|
|
25668
|
-
condition,
|
|
25669
|
-
exit,
|
|
25670
|
-
} from '@wraps.dev/client';
|
|
25671
|
-
|
|
25672
|
-
/**
|
|
25673
|
-
* Trial Conversion
|
|
25674
|
-
*
|
|
25675
|
-
* Remind users 3 days before their trial ends.
|
|
25676
|
-
* If they haven't upgraded after 1 day, send a final nudge.
|
|
25677
|
-
*/
|
|
25678
|
-
export default defineWorkflow({
|
|
25679
|
-
name: 'Trial Conversion',
|
|
25680
|
-
trigger: {
|
|
25681
|
-
type: 'event',
|
|
25682
|
-
eventName: 'trial.ending',
|
|
25683
|
-
},
|
|
25684
|
-
|
|
25685
|
-
steps: [
|
|
25686
|
-
sendEmail('send-reminder', { template: 'trial-ending-reminder' }),
|
|
25687
|
-
delay('wait-1-day', { days: 1 }),
|
|
25688
|
-
condition('check-upgraded', {
|
|
25689
|
-
field: 'contact.plan',
|
|
25690
|
-
operator: 'not_equals',
|
|
25691
|
-
value: 'free',
|
|
25692
|
-
branches: {
|
|
25693
|
-
yes: [exit('already-upgraded')],
|
|
25694
|
-
no: [
|
|
25695
|
-
sendEmail('send-upgrade-nudge', { template: 'upgrade-offer' }),
|
|
25696
|
-
],
|
|
25697
|
-
},
|
|
25698
|
-
}),
|
|
25699
|
-
],
|
|
25700
|
-
});
|
|
25701
|
-
`,
|
|
25702
|
-
"re-engagement": `import {
|
|
25703
|
-
defineWorkflow,
|
|
25704
|
-
sendEmail,
|
|
25705
|
-
delay,
|
|
25706
|
-
condition,
|
|
25707
|
-
exit,
|
|
25708
|
-
} from '@wraps.dev/client';
|
|
25709
|
-
|
|
25710
|
-
/**
|
|
25711
|
-
* Re-engagement Campaign
|
|
25712
|
-
*
|
|
25713
|
-
* Win back inactive users with a personalized email.
|
|
25714
|
-
* Wait 3 days for engagement, then send a final offer.
|
|
25715
|
-
*/
|
|
25716
|
-
export default defineWorkflow({
|
|
25717
|
-
name: 'Re-engagement Campaign',
|
|
25718
|
-
trigger: {
|
|
25719
|
-
type: 'event',
|
|
25720
|
-
eventName: 'contact.inactive',
|
|
25721
|
-
},
|
|
25722
|
-
|
|
25723
|
-
steps: [
|
|
25724
|
-
sendEmail('send-win-back', { template: 'we-miss-you' }),
|
|
25725
|
-
delay('wait-3-days', { days: 3 }),
|
|
25726
|
-
condition('check-engaged', {
|
|
25727
|
-
field: 'contact.lastActiveAt',
|
|
25728
|
-
operator: 'is_set',
|
|
25729
|
-
value: true,
|
|
25730
|
-
branches: {
|
|
25731
|
-
yes: [exit('re-engaged')],
|
|
25732
|
-
no: [
|
|
25733
|
-
sendEmail('send-final-offer', { template: 'final-offer' }),
|
|
25734
|
-
],
|
|
25735
|
-
},
|
|
25736
|
-
}),
|
|
25737
|
-
],
|
|
25738
|
-
});
|
|
25739
|
-
`,
|
|
25740
|
-
onboarding: `import {
|
|
25741
|
-
defineWorkflow,
|
|
25742
|
-
sendEmail,
|
|
25743
|
-
delay,
|
|
25744
|
-
condition,
|
|
25745
|
-
exit,
|
|
25746
|
-
} from '@wraps.dev/client';
|
|
25747
|
-
|
|
25748
|
-
/**
|
|
25749
|
-
* Multi-step Onboarding
|
|
25750
|
-
*
|
|
25751
|
-
* Guide new users through setup with a series of emails.
|
|
25752
|
-
* Check progress at each step and skip ahead if they're done.
|
|
25753
|
-
*/
|
|
25754
|
-
export default defineWorkflow({
|
|
25755
|
-
name: 'Onboarding Sequence',
|
|
25756
|
-
trigger: {
|
|
25757
|
-
type: 'contact_created',
|
|
25758
|
-
},
|
|
25759
|
-
|
|
25760
|
-
steps: [
|
|
25761
|
-
sendEmail('send-welcome', { template: 'onboarding-welcome' }),
|
|
25762
|
-
delay('wait-1-day', { days: 1 }),
|
|
24911
|
+
sendEmail('send-welcome', { template: 'onboarding-welcome' }),
|
|
24912
|
+
delay('wait-1-day', { days: 1 }),
|
|
25763
24913
|
|
|
25764
24914
|
condition('check-profile-complete', {
|
|
25765
24915
|
field: 'contact.profileComplete',
|
|
@@ -25799,8 +24949,6 @@ async function workflowsGenerate(options) {
|
|
|
25799
24949
|
const startTime = Date.now();
|
|
25800
24950
|
if (options.template) {
|
|
25801
24951
|
generateFromTemplate(options);
|
|
25802
|
-
} else if (options.description) {
|
|
25803
|
-
await generateFromDescription(options);
|
|
25804
24952
|
} else {
|
|
25805
24953
|
showUsage();
|
|
25806
24954
|
return;
|
|
@@ -25808,31 +24956,34 @@ async function workflowsGenerate(options) {
|
|
|
25808
24956
|
trackCommand("email:workflows:generate", {
|
|
25809
24957
|
success: true,
|
|
25810
24958
|
duration_ms: Date.now() - startTime,
|
|
25811
|
-
mode:
|
|
24959
|
+
mode: "template"
|
|
25812
24960
|
});
|
|
25813
24961
|
}
|
|
25814
24962
|
function showUsage() {
|
|
25815
24963
|
if (isJsonMode()) {
|
|
25816
24964
|
jsonError("email.workflows.generate", {
|
|
25817
24965
|
code: "MISSING_INPUT",
|
|
25818
|
-
message: "Provide a --template name
|
|
24966
|
+
message: "Provide a --template name to generate a workflow"
|
|
25819
24967
|
});
|
|
25820
24968
|
return;
|
|
25821
24969
|
}
|
|
25822
|
-
log29.error("Provide a
|
|
24970
|
+
log29.error("Provide a --template to generate a workflow.");
|
|
25823
24971
|
console.log();
|
|
25824
|
-
console.log(` ${pc31.bold("
|
|
24972
|
+
console.log(` ${pc31.bold("Usage:")}`);
|
|
25825
24973
|
console.log(
|
|
25826
24974
|
` ${pc31.cyan("wraps email workflows generate --template welcome")}`
|
|
25827
24975
|
);
|
|
25828
24976
|
console.log();
|
|
25829
|
-
console.log(` ${pc31.bold("AI mode:")}`);
|
|
25830
24977
|
console.log(
|
|
25831
|
-
`
|
|
24978
|
+
` ${pc31.bold("Available templates:")} ${TEMPLATE_NAMES.join(", ")}`
|
|
25832
24979
|
);
|
|
25833
24980
|
console.log();
|
|
24981
|
+
console.log(` ${pc31.bold("Want a custom workflow?")}`);
|
|
25834
24982
|
console.log(
|
|
25835
|
-
|
|
24983
|
+
" Describe what you need to your AI coding assistant (Claude Code, Cursor, etc.)"
|
|
24984
|
+
);
|
|
24985
|
+
console.log(
|
|
24986
|
+
` and it will generate a workflow file using the ${pc31.cyan("@wraps.dev/client")} DSL.`
|
|
25836
24987
|
);
|
|
25837
24988
|
console.log();
|
|
25838
24989
|
}
|
|
@@ -25857,9 +25008,9 @@ function generateFromTemplate(options) {
|
|
|
25857
25008
|
}
|
|
25858
25009
|
const slug = options.name || templateName;
|
|
25859
25010
|
const cwd = process.cwd();
|
|
25860
|
-
const workflowsDir =
|
|
25861
|
-
const filePath =
|
|
25862
|
-
if (
|
|
25011
|
+
const workflowsDir = join14(cwd, "wraps", "workflows");
|
|
25012
|
+
const filePath = join14(workflowsDir, `${slug}.ts`);
|
|
25013
|
+
if (existsSync13(filePath) && !options.force) {
|
|
25863
25014
|
if (isJsonMode()) {
|
|
25864
25015
|
jsonError("email.workflows.generate", {
|
|
25865
25016
|
code: "FILE_EXISTS",
|
|
@@ -25889,190 +25040,27 @@ function generateFromTemplate(options) {
|
|
|
25889
25040
|
showNextSteps2(slug);
|
|
25890
25041
|
}
|
|
25891
25042
|
}
|
|
25892
|
-
|
|
25893
|
-
const description = options.description ?? "";
|
|
25894
|
-
const slug = options.name || slugify(description);
|
|
25895
|
-
if (!checkFileExists(slug, options.force)) {
|
|
25896
|
-
return;
|
|
25897
|
-
}
|
|
25898
|
-
if (!isJsonMode()) {
|
|
25899
|
-
intro27(pc31.bold("Generate Workflow"));
|
|
25900
|
-
}
|
|
25901
|
-
const progress = new DeploymentProgress();
|
|
25902
|
-
const token = await resolveTokenAsync({ token: options.token });
|
|
25903
|
-
if (!token) {
|
|
25904
|
-
throw errors.notAuthenticated();
|
|
25905
|
-
}
|
|
25906
|
-
const code = await callGenerateApi(description, slug, token, progress);
|
|
25907
|
-
if (!code) {
|
|
25908
|
-
return;
|
|
25909
|
-
}
|
|
25910
|
-
if (options.dryRun) {
|
|
25911
|
-
showDryRun(slug, code);
|
|
25912
|
-
return;
|
|
25913
|
-
}
|
|
25914
|
-
if (!(options.yes || isJsonMode())) {
|
|
25915
|
-
const shouldWrite = await showPreviewAndConfirm(slug, code);
|
|
25916
|
-
if (!shouldWrite) {
|
|
25917
|
-
return;
|
|
25918
|
-
}
|
|
25919
|
-
}
|
|
25920
|
-
writeWorkflowFile(slug, code);
|
|
25921
|
-
const workflowsDir = join15(process.cwd(), "wraps", "workflows");
|
|
25922
|
-
const filePath = join15(workflowsDir, `${slug}.ts`);
|
|
25923
|
-
await autoValidate(filePath, join15(process.cwd(), "wraps"), slug, progress);
|
|
25924
|
-
if (isJsonMode()) {
|
|
25925
|
-
jsonSuccess("email.workflows.generate", {
|
|
25926
|
-
mode: "llm",
|
|
25927
|
-
slug,
|
|
25928
|
-
path: `wraps/workflows/${slug}.ts`
|
|
25929
|
-
});
|
|
25930
|
-
} else {
|
|
25931
|
-
log29.success(`Created ${pc31.cyan(`wraps/workflows/${slug}.ts`)}`);
|
|
25932
|
-
showNextSteps2(slug, true);
|
|
25933
|
-
}
|
|
25934
|
-
}
|
|
25935
|
-
function slugify(text9) {
|
|
25936
|
-
return text9.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
|
|
25937
|
-
}
|
|
25938
|
-
function checkFileExists(slug, force) {
|
|
25939
|
-
const filePath = join15(process.cwd(), "wraps", "workflows", `${slug}.ts`);
|
|
25940
|
-
if (existsSync14(filePath) && !force) {
|
|
25941
|
-
if (isJsonMode()) {
|
|
25942
|
-
jsonError("email.workflows.generate", {
|
|
25943
|
-
code: "FILE_EXISTS",
|
|
25944
|
-
message: `wraps/workflows/${slug}.ts already exists. Use --force to overwrite.`
|
|
25945
|
-
});
|
|
25946
|
-
} else {
|
|
25947
|
-
log29.error(
|
|
25948
|
-
`${pc31.cyan(`wraps/workflows/${slug}.ts`)} already exists. Use ${pc31.bold("--force")} to overwrite.`
|
|
25949
|
-
);
|
|
25950
|
-
}
|
|
25951
|
-
return false;
|
|
25952
|
-
}
|
|
25953
|
-
return true;
|
|
25954
|
-
}
|
|
25955
|
-
async function callGenerateApi(description, slug, token, progress) {
|
|
25956
|
-
progress.start("Generating workflow from description");
|
|
25957
|
-
const apiBase = getApiBaseUrl();
|
|
25958
|
-
const resp = await fetch(`${apiBase}/v1/workflows/generate`, {
|
|
25959
|
-
method: "POST",
|
|
25960
|
-
headers: {
|
|
25961
|
-
"Content-Type": "application/json",
|
|
25962
|
-
Authorization: `Bearer ${token}`
|
|
25963
|
-
},
|
|
25964
|
-
body: JSON.stringify({ description, slug })
|
|
25965
|
-
});
|
|
25966
|
-
if (!resp.ok) {
|
|
25967
|
-
progress.fail("Generation failed");
|
|
25968
|
-
if (resp.status === 429) {
|
|
25969
|
-
throw errors.aiUsageLimitReached();
|
|
25970
|
-
}
|
|
25971
|
-
const body = await resp.text();
|
|
25972
|
-
let message;
|
|
25973
|
-
try {
|
|
25974
|
-
const parsed = JSON.parse(body);
|
|
25975
|
-
message = parsed.error || parsed.message || body;
|
|
25976
|
-
} catch {
|
|
25977
|
-
message = body;
|
|
25978
|
-
}
|
|
25979
|
-
throw errors.workflowGenerationFailed(message);
|
|
25980
|
-
}
|
|
25981
|
-
const data = await resp.json();
|
|
25982
|
-
progress.succeed("Workflow generated");
|
|
25983
|
-
return data.code;
|
|
25984
|
-
}
|
|
25985
|
-
function showDryRun(slug, code) {
|
|
25986
|
-
if (isJsonMode()) {
|
|
25987
|
-
jsonSuccess("email.workflows.generate", {
|
|
25988
|
-
mode: "llm",
|
|
25989
|
-
dryRun: true,
|
|
25990
|
-
slug,
|
|
25991
|
-
code
|
|
25992
|
-
});
|
|
25993
|
-
} else {
|
|
25994
|
-
console.log();
|
|
25995
|
-
log29.info(pc31.bold("Dry run \u2014 no file written"));
|
|
25996
|
-
console.log();
|
|
25997
|
-
console.log(pc31.dim("\u2500".repeat(60)));
|
|
25998
|
-
console.log(code);
|
|
25999
|
-
console.log(pc31.dim("\u2500".repeat(60)));
|
|
26000
|
-
console.log();
|
|
26001
|
-
}
|
|
26002
|
-
}
|
|
26003
|
-
async function showPreviewAndConfirm(slug, code) {
|
|
26004
|
-
console.log();
|
|
26005
|
-
console.log(pc31.dim("\u2500".repeat(60)));
|
|
26006
|
-
console.log(code);
|
|
26007
|
-
console.log(pc31.dim("\u2500".repeat(60)));
|
|
26008
|
-
console.log();
|
|
26009
|
-
const confirmed = await confirm14({
|
|
26010
|
-
message: `Write to ${pc31.cyan(`wraps/workflows/${slug}.ts`)}?`,
|
|
26011
|
-
initialValue: true
|
|
26012
|
-
});
|
|
26013
|
-
if (isCancel20(confirmed) || !confirmed) {
|
|
26014
|
-
cancel21("Generation cancelled.");
|
|
26015
|
-
return false;
|
|
26016
|
-
}
|
|
26017
|
-
return true;
|
|
26018
|
-
}
|
|
26019
|
-
function writeWorkflowFile(slug, code) {
|
|
26020
|
-
const workflowsDir = join15(process.cwd(), "wraps", "workflows");
|
|
26021
|
-
mkdirSync2(workflowsDir, { recursive: true });
|
|
26022
|
-
writeFileSync(join15(workflowsDir, `${slug}.ts`), code, "utf-8");
|
|
26023
|
-
}
|
|
26024
|
-
function showNextSteps2(slug, isLlm = false) {
|
|
25043
|
+
function showNextSteps2(slug) {
|
|
26025
25044
|
console.log();
|
|
26026
25045
|
console.log(` ${pc31.bold("Next steps:")}`);
|
|
26027
|
-
|
|
26028
|
-
|
|
26029
|
-
|
|
26030
|
-
);
|
|
26031
|
-
} else {
|
|
26032
|
-
console.log(
|
|
26033
|
-
` 1. Edit template references in ${pc31.cyan(`wraps/workflows/${slug}.ts`)}`
|
|
26034
|
-
);
|
|
26035
|
-
}
|
|
25046
|
+
console.log(
|
|
25047
|
+
` 1. Edit template references in ${pc31.cyan(`wraps/workflows/${slug}.ts`)}`
|
|
25048
|
+
);
|
|
26036
25049
|
console.log(
|
|
26037
25050
|
` 2. Validate: ${pc31.cyan(`wraps email workflows validate --workflow ${slug}`)}`
|
|
26038
25051
|
);
|
|
26039
25052
|
console.log(` 3. Push: ${pc31.cyan("wraps email workflows push")}`);
|
|
26040
25053
|
console.log();
|
|
26041
25054
|
}
|
|
26042
|
-
async function autoValidate(filePath, wrapsDir, slug, progress) {
|
|
26043
|
-
try {
|
|
26044
|
-
progress.start(`Validating ${pc31.cyan(slug)}`);
|
|
26045
|
-
const parsed = await parseWorkflowTs(filePath, wrapsDir);
|
|
26046
|
-
const transformed = transformWorkflow(parsed.definition);
|
|
26047
|
-
const result = validateTransformedWorkflow(transformed);
|
|
26048
|
-
const errs = result.errors.filter((e) => e.severity === "error");
|
|
26049
|
-
const warnings = result.errors.filter((e) => e.severity === "warning");
|
|
26050
|
-
if (errs.length === 0 && warnings.length === 0) {
|
|
26051
|
-
progress.succeed(`${pc31.cyan(slug)} is valid`);
|
|
26052
|
-
} else if (errs.length === 0) {
|
|
26053
|
-
progress.succeed(
|
|
26054
|
-
`${pc31.cyan(slug)} is valid with ${warnings.length} warning(s)`
|
|
26055
|
-
);
|
|
26056
|
-
} else {
|
|
26057
|
-
progress.fail(
|
|
26058
|
-
`${pc31.cyan(slug)} has ${errs.length} validation error(s) \u2014 review and fix manually`
|
|
26059
|
-
);
|
|
26060
|
-
}
|
|
26061
|
-
} catch {
|
|
26062
|
-
progress.info(
|
|
26063
|
-
`Could not auto-validate ${pc31.cyan(slug)} \u2014 run ${pc31.cyan("wraps email workflows validate")} manually`
|
|
26064
|
-
);
|
|
26065
|
-
}
|
|
26066
|
-
}
|
|
26067
25055
|
|
|
26068
25056
|
// src/commands/email/workflows/init.ts
|
|
26069
25057
|
init_esm_shims();
|
|
26070
25058
|
init_events();
|
|
26071
25059
|
init_json_output();
|
|
26072
25060
|
init_output();
|
|
26073
|
-
import { existsSync as
|
|
26074
|
-
import { mkdir as
|
|
26075
|
-
import { join as
|
|
25061
|
+
import { existsSync as existsSync14 } from "fs";
|
|
25062
|
+
import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile9 } from "fs/promises";
|
|
25063
|
+
import { join as join15 } from "path";
|
|
26076
25064
|
import * as clack30 from "@clack/prompts";
|
|
26077
25065
|
import pc32 from "picocolors";
|
|
26078
25066
|
|
|
@@ -26425,177 +25413,1008 @@ wraps email workflows generate "description..." # Generate from AI descriptio
|
|
|
26425
25413
|
\`\`\`
|
|
26426
25414
|
`;
|
|
26427
25415
|
|
|
26428
|
-
// src/commands/email/workflows/init.ts
|
|
26429
|
-
var EXAMPLE_WORKFLOW = `import {
|
|
26430
|
-
defineWorkflow,
|
|
26431
|
-
sendEmail,
|
|
26432
|
-
delay,
|
|
26433
|
-
condition,
|
|
26434
|
-
exit,
|
|
26435
|
-
} from '@wraps.dev/client';
|
|
25416
|
+
// src/commands/email/workflows/init.ts
|
|
25417
|
+
var EXAMPLE_WORKFLOW = `import {
|
|
25418
|
+
defineWorkflow,
|
|
25419
|
+
sendEmail,
|
|
25420
|
+
delay,
|
|
25421
|
+
condition,
|
|
25422
|
+
exit,
|
|
25423
|
+
} from '@wraps.dev/client';
|
|
25424
|
+
|
|
25425
|
+
/**
|
|
25426
|
+
* Welcome Sequence
|
|
25427
|
+
*
|
|
25428
|
+
* Send a welcome email when a contact is created,
|
|
25429
|
+
* wait 1 day, then check if they activated.
|
|
25430
|
+
* If not, send a follow-up with tips.
|
|
25431
|
+
*/
|
|
25432
|
+
export default defineWorkflow({
|
|
25433
|
+
name: 'Welcome Sequence',
|
|
25434
|
+
trigger: {
|
|
25435
|
+
type: 'contact_created',
|
|
25436
|
+
},
|
|
25437
|
+
|
|
25438
|
+
steps: [
|
|
25439
|
+
sendEmail('send-welcome', { template: 'welcome-email' }),
|
|
25440
|
+
delay('wait-1-day', { days: 1 }),
|
|
25441
|
+
condition('check-activated', {
|
|
25442
|
+
field: 'contact.hasActivated',
|
|
25443
|
+
operator: 'equals',
|
|
25444
|
+
value: true,
|
|
25445
|
+
branches: {
|
|
25446
|
+
yes: [exit('already-active')],
|
|
25447
|
+
no: [
|
|
25448
|
+
sendEmail('send-tips', { template: 'getting-started-tips' }),
|
|
25449
|
+
],
|
|
25450
|
+
},
|
|
25451
|
+
}),
|
|
25452
|
+
],
|
|
25453
|
+
});
|
|
25454
|
+
`;
|
|
25455
|
+
async function workflowsInit(options) {
|
|
25456
|
+
const startTime = Date.now();
|
|
25457
|
+
const cwd = process.cwd();
|
|
25458
|
+
const workflowsDir = join15(cwd, "wraps", "workflows");
|
|
25459
|
+
if (!isJsonMode()) {
|
|
25460
|
+
clack30.intro(pc32.bold("Workflows as Code"));
|
|
25461
|
+
}
|
|
25462
|
+
const progress = new DeploymentProgress();
|
|
25463
|
+
if (existsSync14(workflowsDir) && !options.force) {
|
|
25464
|
+
const { readdir: readdir4 } = await import("fs/promises");
|
|
25465
|
+
const files = await readdir4(workflowsDir);
|
|
25466
|
+
const tsFiles = files.filter(
|
|
25467
|
+
(f) => f.endsWith(".ts") && !f.startsWith("_")
|
|
25468
|
+
);
|
|
25469
|
+
if (tsFiles.length > 0 && !options.force && !isJsonMode()) {
|
|
25470
|
+
clack30.log.warn(
|
|
25471
|
+
`${pc32.cyan("wraps/workflows/")} already contains ${tsFiles.length} workflow file(s). Use ${pc32.bold("--force")} to overwrite.`
|
|
25472
|
+
);
|
|
25473
|
+
}
|
|
25474
|
+
}
|
|
25475
|
+
progress.start("Creating wraps/workflows/ directory");
|
|
25476
|
+
await mkdir7(workflowsDir, { recursive: true });
|
|
25477
|
+
const configPath = join15(cwd, "wraps", "wraps.config.ts");
|
|
25478
|
+
if (existsSync14(configPath)) {
|
|
25479
|
+
const configContent = await readFile7(configPath, "utf-8");
|
|
25480
|
+
if (!configContent.includes("workflowsDir")) {
|
|
25481
|
+
const updated = configContent.replace(
|
|
25482
|
+
/}\);(\s*)$/,
|
|
25483
|
+
` workflowsDir: './workflows',
|
|
25484
|
+
});$1`
|
|
25485
|
+
);
|
|
25486
|
+
if (updated !== configContent) {
|
|
25487
|
+
await writeFile9(configPath, updated, "utf-8");
|
|
25488
|
+
}
|
|
25489
|
+
}
|
|
25490
|
+
} else {
|
|
25491
|
+
await writeFile9(configPath, generateMinimalConfig(), "utf-8");
|
|
25492
|
+
}
|
|
25493
|
+
const filesCreated = [];
|
|
25494
|
+
if (!options.noExample) {
|
|
25495
|
+
const examplePath = join15(workflowsDir, "welcome.ts");
|
|
25496
|
+
if (!existsSync14(examplePath) || options.force) {
|
|
25497
|
+
await writeFile9(examplePath, EXAMPLE_WORKFLOW, "utf-8");
|
|
25498
|
+
filesCreated.push("wraps/workflows/welcome.ts");
|
|
25499
|
+
}
|
|
25500
|
+
}
|
|
25501
|
+
progress.succeed("Workflows directory ready");
|
|
25502
|
+
if (!options.noClaude) {
|
|
25503
|
+
try {
|
|
25504
|
+
progress.start("Scaffolding Claude Code context");
|
|
25505
|
+
await scaffoldClaudeMdSection({
|
|
25506
|
+
projectDir: cwd,
|
|
25507
|
+
sectionId: "workflows",
|
|
25508
|
+
sectionContent: WORKFLOWS_CLAUDE_MD_SECTION
|
|
25509
|
+
});
|
|
25510
|
+
filesCreated.push(".claude/CLAUDE.md");
|
|
25511
|
+
await scaffoldClaudeSkill({
|
|
25512
|
+
projectDir: cwd,
|
|
25513
|
+
skillName: "wraps-workflows",
|
|
25514
|
+
skillContent: WORKFLOWS_SKILL_CONTENT
|
|
25515
|
+
});
|
|
25516
|
+
filesCreated.push(".claude/skills/wraps-workflows/SKILL.md");
|
|
25517
|
+
progress.succeed("Claude Code context scaffolded");
|
|
25518
|
+
} catch (e) {
|
|
25519
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
25520
|
+
progress.info(
|
|
25521
|
+
`Could not scaffold .claude/ context \u2014 workflow files are still ready (${msg})`
|
|
25522
|
+
);
|
|
25523
|
+
}
|
|
25524
|
+
}
|
|
25525
|
+
trackCommand("email:workflows:init", {
|
|
25526
|
+
success: true,
|
|
25527
|
+
duration_ms: Date.now() - startTime
|
|
25528
|
+
});
|
|
25529
|
+
if (isJsonMode()) {
|
|
25530
|
+
jsonSuccess("email.workflows.init", {
|
|
25531
|
+
dir: "wraps/workflows",
|
|
25532
|
+
files: filesCreated
|
|
25533
|
+
});
|
|
25534
|
+
return;
|
|
25535
|
+
}
|
|
25536
|
+
console.log();
|
|
25537
|
+
clack30.log.success(pc32.green("Workflows as Code initialized!"));
|
|
25538
|
+
console.log();
|
|
25539
|
+
console.log(` ${pc32.dim("Directory:")} ${pc32.cyan("wraps/workflows/")}`);
|
|
25540
|
+
if (!options.noExample) {
|
|
25541
|
+
console.log(
|
|
25542
|
+
` ${pc32.dim("Example:")} ${pc32.cyan("wraps/workflows/welcome.ts")}`
|
|
25543
|
+
);
|
|
25544
|
+
}
|
|
25545
|
+
if (!options.noClaude) {
|
|
25546
|
+
console.log(
|
|
25547
|
+
` ${pc32.dim("AI Context:")} ${pc32.cyan(".claude/skills/wraps-workflows/")}`
|
|
25548
|
+
);
|
|
25549
|
+
}
|
|
25550
|
+
console.log();
|
|
25551
|
+
console.log(`${pc32.bold("Next steps:")}`);
|
|
25552
|
+
console.log(
|
|
25553
|
+
` 1. Edit or create workflows in ${pc32.cyan("wraps/workflows/")}`
|
|
25554
|
+
);
|
|
25555
|
+
console.log(` 2. Validate: ${pc32.cyan("wraps email workflows validate")}`);
|
|
25556
|
+
console.log(` 3. Push: ${pc32.cyan("wraps email workflows push")}`);
|
|
25557
|
+
if (!options.noClaude) {
|
|
25558
|
+
console.log(" 4. Use Claude Code to generate workflows from descriptions");
|
|
25559
|
+
}
|
|
25560
|
+
console.log();
|
|
25561
|
+
}
|
|
25562
|
+
function generateMinimalConfig() {
|
|
25563
|
+
return `import { defineConfig } from '@wraps.dev/client';
|
|
25564
|
+
|
|
25565
|
+
export default defineConfig({
|
|
25566
|
+
org: 'my-org',
|
|
25567
|
+
// from: { email: 'hello@yourapp.com', name: 'My App' },
|
|
25568
|
+
// region: 'us-east-1',
|
|
25569
|
+
templatesDir: './templates',
|
|
25570
|
+
workflowsDir: './workflows',
|
|
25571
|
+
});
|
|
25572
|
+
`;
|
|
25573
|
+
}
|
|
25574
|
+
|
|
25575
|
+
// src/commands/email/workflows/push.ts
|
|
25576
|
+
init_esm_shims();
|
|
25577
|
+
init_events();
|
|
25578
|
+
import { existsSync as existsSync16 } from "fs";
|
|
25579
|
+
import { join as join17 } from "path";
|
|
25580
|
+
import * as clack31 from "@clack/prompts";
|
|
25581
|
+
import pc33 from "picocolors";
|
|
25582
|
+
|
|
25583
|
+
// src/utils/email/workflow-transform.ts
|
|
25584
|
+
init_esm_shims();
|
|
25585
|
+
function transformWorkflow(definition) {
|
|
25586
|
+
const steps = [];
|
|
25587
|
+
const transitions = [];
|
|
25588
|
+
const triggerStep = createTriggerStep(definition.trigger);
|
|
25589
|
+
steps.push(triggerStep);
|
|
25590
|
+
if (definition.steps.length > 0) {
|
|
25591
|
+
flattenSteps(definition.steps, steps, transitions, triggerStep.id, null);
|
|
25592
|
+
}
|
|
25593
|
+
assignPositions(steps, transitions);
|
|
25594
|
+
return {
|
|
25595
|
+
steps,
|
|
25596
|
+
transitions,
|
|
25597
|
+
triggerType: definition.trigger.type,
|
|
25598
|
+
triggerConfig: extractTriggerConfig(definition.trigger),
|
|
25599
|
+
settings: definition.settings,
|
|
25600
|
+
defaults: definition.defaults
|
|
25601
|
+
};
|
|
25602
|
+
}
|
|
25603
|
+
function createTriggerStep(trigger) {
|
|
25604
|
+
const triggerConfig = {
|
|
25605
|
+
type: "trigger",
|
|
25606
|
+
triggerType: trigger.type,
|
|
25607
|
+
...extractTriggerConfig(trigger)
|
|
25608
|
+
};
|
|
25609
|
+
return {
|
|
25610
|
+
id: "trigger",
|
|
25611
|
+
type: "trigger",
|
|
25612
|
+
name: getTriggerName(trigger.type),
|
|
25613
|
+
position: { x: 0, y: 0 },
|
|
25614
|
+
// Will be updated by assignPositions
|
|
25615
|
+
config: triggerConfig
|
|
25616
|
+
};
|
|
25617
|
+
}
|
|
25618
|
+
function extractTriggerConfig(trigger) {
|
|
25619
|
+
const config2 = {};
|
|
25620
|
+
if (trigger.eventName) {
|
|
25621
|
+
config2.eventName = trigger.eventName;
|
|
25622
|
+
}
|
|
25623
|
+
if (trigger.segmentId) {
|
|
25624
|
+
config2.segmentId = trigger.segmentId;
|
|
25625
|
+
}
|
|
25626
|
+
if (trigger.schedule) {
|
|
25627
|
+
config2.schedule = trigger.schedule;
|
|
25628
|
+
}
|
|
25629
|
+
if (trigger.timezone) {
|
|
25630
|
+
config2.timezone = trigger.timezone;
|
|
25631
|
+
}
|
|
25632
|
+
if (trigger.topicId) {
|
|
25633
|
+
config2.topicId = trigger.topicId;
|
|
25634
|
+
}
|
|
25635
|
+
return config2;
|
|
25636
|
+
}
|
|
25637
|
+
function getTriggerName(type) {
|
|
25638
|
+
const names = {
|
|
25639
|
+
event: "When event occurs",
|
|
25640
|
+
contact_created: "When contact is created",
|
|
25641
|
+
contact_updated: "When contact is updated",
|
|
25642
|
+
segment_entry: "When contact enters segment",
|
|
25643
|
+
segment_exit: "When contact exits segment",
|
|
25644
|
+
schedule: "On schedule",
|
|
25645
|
+
api: "When triggered via API",
|
|
25646
|
+
topic_subscribed: "When contact subscribes to topic",
|
|
25647
|
+
topic_unsubscribed: "When contact unsubscribes from topic"
|
|
25648
|
+
};
|
|
25649
|
+
return names[type] || "Trigger";
|
|
25650
|
+
}
|
|
25651
|
+
function flattenSteps(stepDefs, steps, transitions, fromStepId, branch) {
|
|
25652
|
+
let prevIds = [fromStepId];
|
|
25653
|
+
let firstStepInBranch = true;
|
|
25654
|
+
for (const def of stepDefs) {
|
|
25655
|
+
const step = toWorkflowStep(def);
|
|
25656
|
+
steps.push(step);
|
|
25657
|
+
for (const prevId of prevIds) {
|
|
25658
|
+
const transition = {
|
|
25659
|
+
id: `t-${prevId}-${step.id}`,
|
|
25660
|
+
fromStepId: prevId,
|
|
25661
|
+
toStepId: step.id
|
|
25662
|
+
};
|
|
25663
|
+
if (firstStepInBranch && branch && prevId === fromStepId) {
|
|
25664
|
+
transition.condition = { branch };
|
|
25665
|
+
}
|
|
25666
|
+
transitions.push(transition);
|
|
25667
|
+
}
|
|
25668
|
+
firstStepInBranch = false;
|
|
25669
|
+
if (def.type === "condition" && def.branches) {
|
|
25670
|
+
const leafIds = [];
|
|
25671
|
+
if (def.branches.yes && def.branches.yes.length > 0) {
|
|
25672
|
+
const yesLeaves = flattenSteps(
|
|
25673
|
+
def.branches.yes,
|
|
25674
|
+
steps,
|
|
25675
|
+
transitions,
|
|
25676
|
+
step.id,
|
|
25677
|
+
"yes"
|
|
25678
|
+
);
|
|
25679
|
+
leafIds.push(...yesLeaves);
|
|
25680
|
+
} else {
|
|
25681
|
+
leafIds.push(step.id);
|
|
25682
|
+
}
|
|
25683
|
+
if (def.branches.no && def.branches.no.length > 0) {
|
|
25684
|
+
const noLeaves = flattenSteps(
|
|
25685
|
+
def.branches.no,
|
|
25686
|
+
steps,
|
|
25687
|
+
transitions,
|
|
25688
|
+
step.id,
|
|
25689
|
+
"no"
|
|
25690
|
+
);
|
|
25691
|
+
leafIds.push(...noLeaves);
|
|
25692
|
+
} else if (!leafIds.includes(step.id)) {
|
|
25693
|
+
leafIds.push(step.id);
|
|
25694
|
+
}
|
|
25695
|
+
prevIds = leafIds;
|
|
25696
|
+
continue;
|
|
25697
|
+
}
|
|
25698
|
+
if (def.type === "exit") {
|
|
25699
|
+
prevIds = [];
|
|
25700
|
+
continue;
|
|
25701
|
+
}
|
|
25702
|
+
prevIds = [step.id];
|
|
25703
|
+
}
|
|
25704
|
+
return prevIds;
|
|
25705
|
+
}
|
|
25706
|
+
function toWorkflowStep(def) {
|
|
25707
|
+
const step = {
|
|
25708
|
+
id: def.id,
|
|
25709
|
+
type: def.type,
|
|
25710
|
+
name: def.name || getDefaultStepName(def.type),
|
|
25711
|
+
position: { x: 0, y: 0 },
|
|
25712
|
+
// Will be updated by assignPositions
|
|
25713
|
+
config: def.config
|
|
25714
|
+
};
|
|
25715
|
+
if (def.cascadeGroupId) {
|
|
25716
|
+
step.cascadeGroupId = def.cascadeGroupId;
|
|
25717
|
+
}
|
|
25718
|
+
return step;
|
|
25719
|
+
}
|
|
25720
|
+
function getDefaultStepName(type) {
|
|
25721
|
+
const names = {
|
|
25722
|
+
send_email: "Send Email",
|
|
25723
|
+
send_sms: "Send SMS",
|
|
25724
|
+
delay: "Wait",
|
|
25725
|
+
exit: "Exit",
|
|
25726
|
+
condition: "Condition",
|
|
25727
|
+
webhook: "Webhook",
|
|
25728
|
+
update_contact: "Update Contact",
|
|
25729
|
+
wait_for_event: "Wait for Event",
|
|
25730
|
+
wait_for_email_engagement: "Wait for Email Engagement",
|
|
25731
|
+
subscribe_topic: "Subscribe to Topic",
|
|
25732
|
+
unsubscribe_topic: "Unsubscribe from Topic"
|
|
25733
|
+
};
|
|
25734
|
+
return names[type] || type;
|
|
25735
|
+
}
|
|
25736
|
+
function assignPositions(steps, transitions) {
|
|
25737
|
+
const LEVEL_HEIGHT = 200;
|
|
25738
|
+
const BRANCH_OFFSET = 300;
|
|
25739
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
25740
|
+
for (const t of transitions) {
|
|
25741
|
+
if (!childrenMap.has(t.fromStepId)) {
|
|
25742
|
+
childrenMap.set(t.fromStepId, []);
|
|
25743
|
+
}
|
|
25744
|
+
childrenMap.get(t.fromStepId)?.push({
|
|
25745
|
+
id: t.toStepId,
|
|
25746
|
+
branch: t.condition?.branch
|
|
25747
|
+
});
|
|
25748
|
+
}
|
|
25749
|
+
const visited = /* @__PURE__ */ new Set();
|
|
25750
|
+
const queue = [];
|
|
25751
|
+
const triggerStep = steps.find((s) => s.type === "trigger");
|
|
25752
|
+
if (triggerStep) {
|
|
25753
|
+
queue.push({ stepId: triggerStep.id, level: 0, xOffset: 0 });
|
|
25754
|
+
}
|
|
25755
|
+
while (queue.length > 0) {
|
|
25756
|
+
const { stepId, level, xOffset } = queue.shift();
|
|
25757
|
+
if (visited.has(stepId)) {
|
|
25758
|
+
continue;
|
|
25759
|
+
}
|
|
25760
|
+
visited.add(stepId);
|
|
25761
|
+
const step = steps.find((s) => s.id === stepId);
|
|
25762
|
+
if (step) {
|
|
25763
|
+
step.position = {
|
|
25764
|
+
x: xOffset,
|
|
25765
|
+
y: level * LEVEL_HEIGHT
|
|
25766
|
+
};
|
|
25767
|
+
}
|
|
25768
|
+
const children = childrenMap.get(stepId) || [];
|
|
25769
|
+
for (const child of children) {
|
|
25770
|
+
if (!visited.has(child.id)) {
|
|
25771
|
+
let childXOffset = xOffset;
|
|
25772
|
+
if (child.branch === "yes") {
|
|
25773
|
+
childXOffset = xOffset - BRANCH_OFFSET;
|
|
25774
|
+
} else if (child.branch === "no") {
|
|
25775
|
+
childXOffset = xOffset + BRANCH_OFFSET;
|
|
25776
|
+
}
|
|
25777
|
+
queue.push({
|
|
25778
|
+
stepId: child.id,
|
|
25779
|
+
level: level + 1,
|
|
25780
|
+
xOffset: childXOffset
|
|
25781
|
+
});
|
|
25782
|
+
}
|
|
25783
|
+
}
|
|
25784
|
+
}
|
|
25785
|
+
for (const step of steps) {
|
|
25786
|
+
if (!visited.has(step.id)) {
|
|
25787
|
+
step.position = {
|
|
25788
|
+
x: 600,
|
|
25789
|
+
y: steps.indexOf(step) * LEVEL_HEIGHT
|
|
25790
|
+
};
|
|
25791
|
+
}
|
|
25792
|
+
}
|
|
25793
|
+
}
|
|
25794
|
+
|
|
25795
|
+
// src/utils/email/workflow-ts.ts
|
|
25796
|
+
init_esm_shims();
|
|
25797
|
+
import { createHash as createHash2 } from "crypto";
|
|
25798
|
+
import { existsSync as existsSync15 } from "fs";
|
|
25799
|
+
import { mkdir as mkdir8, readdir as readdir3, readFile as readFile8, writeFile as writeFile10 } from "fs/promises";
|
|
25800
|
+
import { basename, join as join16 } from "path";
|
|
25801
|
+
async function discoverWorkflows(dir, filter) {
|
|
25802
|
+
if (!existsSync15(dir)) {
|
|
25803
|
+
return [];
|
|
25804
|
+
}
|
|
25805
|
+
const entries = await readdir3(dir);
|
|
25806
|
+
const workflows = entries.filter(
|
|
25807
|
+
(f) => (
|
|
25808
|
+
// Include .ts files only (not .tsx for workflows)
|
|
25809
|
+
f.endsWith(".ts") && // Exclude private/helper files starting with _
|
|
25810
|
+
!f.startsWith("_") && // Exclude type definition files
|
|
25811
|
+
!f.endsWith(".d.ts")
|
|
25812
|
+
)
|
|
25813
|
+
);
|
|
25814
|
+
if (filter) {
|
|
25815
|
+
const slug = filter.replace(/\.ts$/, "");
|
|
25816
|
+
return workflows.filter((f) => f.replace(/\.ts$/, "") === slug);
|
|
25817
|
+
}
|
|
25818
|
+
return workflows;
|
|
25819
|
+
}
|
|
25820
|
+
async function parseWorkflowTs(filePath, wrapsDir) {
|
|
25821
|
+
const { build: build2 } = await import("esbuild");
|
|
25822
|
+
const source = await readFile8(filePath, "utf-8");
|
|
25823
|
+
const sourceHash = createHash2("sha256").update(source).digest("hex");
|
|
25824
|
+
const slug = basename(filePath, ".ts");
|
|
25825
|
+
const shimDir = join16(wrapsDir, ".wraps", "_shims");
|
|
25826
|
+
await mkdir8(shimDir, { recursive: true });
|
|
25827
|
+
const clientShimContent = `
|
|
25828
|
+
// Identity functions for workflow definitions
|
|
25829
|
+
export const defineWorkflow = (def) => def;
|
|
25830
|
+
|
|
25831
|
+
// Step helper functions - they just create step definition objects
|
|
25832
|
+
export const sendEmail = (id, config) => ({
|
|
25833
|
+
id,
|
|
25834
|
+
type: 'send_email',
|
|
25835
|
+
name: config.name ?? \`Send email: \${config.template || 'custom'}\`,
|
|
25836
|
+
config: { type: 'send_email', ...config },
|
|
25837
|
+
});
|
|
25838
|
+
|
|
25839
|
+
export const sendSms = (id, config) => ({
|
|
25840
|
+
id,
|
|
25841
|
+
type: 'send_sms',
|
|
25842
|
+
name: config.name ?? \`Send SMS: \${config.template || 'custom'}\`,
|
|
25843
|
+
config: { type: 'send_sms', ...config },
|
|
25844
|
+
});
|
|
25845
|
+
|
|
25846
|
+
export const delay = (id, duration) => {
|
|
25847
|
+
const { name, ...durationConfig } = duration;
|
|
25848
|
+
const normalized = normalizeDuration(durationConfig);
|
|
25849
|
+
return {
|
|
25850
|
+
id,
|
|
25851
|
+
type: 'delay',
|
|
25852
|
+
name: name ?? \`Wait \${normalized.amount} \${normalized.unit}\`,
|
|
25853
|
+
config: { type: 'delay', ...normalized },
|
|
25854
|
+
};
|
|
25855
|
+
};
|
|
25856
|
+
|
|
25857
|
+
export const condition = (id, config) => {
|
|
25858
|
+
const { branches, name, ...conditionConfig } = config;
|
|
25859
|
+
return {
|
|
25860
|
+
id,
|
|
25861
|
+
type: 'condition',
|
|
25862
|
+
name: name ?? \`Check: \${config.field} \${config.operator}\`,
|
|
25863
|
+
config: { type: 'condition', ...conditionConfig },
|
|
25864
|
+
branches,
|
|
25865
|
+
};
|
|
25866
|
+
};
|
|
25867
|
+
|
|
25868
|
+
export const waitForEvent = (id, config) => {
|
|
25869
|
+
const { name, timeout, ...eventConfig } = config;
|
|
25870
|
+
return {
|
|
25871
|
+
id,
|
|
25872
|
+
type: 'wait_for_event',
|
|
25873
|
+
name: name ?? \`Wait for: \${config.eventName}\`,
|
|
25874
|
+
config: {
|
|
25875
|
+
type: 'wait_for_event',
|
|
25876
|
+
eventName: eventConfig.eventName,
|
|
25877
|
+
timeoutSeconds: durationToSeconds(timeout),
|
|
25878
|
+
},
|
|
25879
|
+
};
|
|
25880
|
+
};
|
|
25881
|
+
|
|
25882
|
+
export const waitForEmailEngagement = (id, config) => {
|
|
25883
|
+
const { name, timeout, emailStepId, engagementType } = config;
|
|
25884
|
+
return {
|
|
25885
|
+
id,
|
|
25886
|
+
type: 'wait_for_email_engagement',
|
|
25887
|
+
name: name ?? \`Wait for email \${engagementType}: \${emailStepId}\`,
|
|
25888
|
+
config: {
|
|
25889
|
+
type: 'wait_for_email_engagement',
|
|
25890
|
+
timeoutSeconds: durationToSeconds(timeout),
|
|
25891
|
+
},
|
|
25892
|
+
};
|
|
25893
|
+
};
|
|
25894
|
+
|
|
25895
|
+
export const exit = (id, config) => {
|
|
25896
|
+
const { name, ...exitConfig } = config ?? {};
|
|
25897
|
+
return {
|
|
25898
|
+
id,
|
|
25899
|
+
type: 'exit',
|
|
25900
|
+
name: name ?? 'Exit',
|
|
25901
|
+
config: { type: 'exit', ...exitConfig },
|
|
25902
|
+
};
|
|
25903
|
+
};
|
|
25904
|
+
|
|
25905
|
+
export const updateContact = (id, config) => {
|
|
25906
|
+
const { name, ...updateConfig } = config;
|
|
25907
|
+
return {
|
|
25908
|
+
id,
|
|
25909
|
+
type: 'update_contact',
|
|
25910
|
+
name: name ?? 'Update contact',
|
|
25911
|
+
config: { type: 'update_contact', ...updateConfig },
|
|
25912
|
+
};
|
|
25913
|
+
};
|
|
25914
|
+
|
|
25915
|
+
export const subscribeTopic = (id, config) => {
|
|
25916
|
+
const { name, ...topicConfig } = config;
|
|
25917
|
+
return {
|
|
25918
|
+
id,
|
|
25919
|
+
type: 'subscribe_topic',
|
|
25920
|
+
name: name ?? \`Subscribe to topic: \${config.topicId}\`,
|
|
25921
|
+
config: { type: 'subscribe_topic', ...topicConfig },
|
|
25922
|
+
};
|
|
25923
|
+
};
|
|
25924
|
+
|
|
25925
|
+
export const unsubscribeTopic = (id, config) => {
|
|
25926
|
+
const { name, ...topicConfig } = config;
|
|
25927
|
+
return {
|
|
25928
|
+
id,
|
|
25929
|
+
type: 'unsubscribe_topic',
|
|
25930
|
+
name: name ?? \`Unsubscribe from topic: \${config.topicId}\`,
|
|
25931
|
+
config: { type: 'unsubscribe_topic', ...topicConfig },
|
|
25932
|
+
};
|
|
25933
|
+
};
|
|
25934
|
+
|
|
25935
|
+
export const webhook = (id, config) => {
|
|
25936
|
+
const { name, ...webhookConfig } = config;
|
|
25937
|
+
return {
|
|
25938
|
+
id,
|
|
25939
|
+
type: 'webhook',
|
|
25940
|
+
name: name ?? \`Webhook: \${config.url}\`,
|
|
25941
|
+
config: { type: 'webhook', method: 'POST', ...webhookConfig },
|
|
25942
|
+
};
|
|
25943
|
+
};
|
|
25944
|
+
|
|
25945
|
+
/**
|
|
25946
|
+
* cascade(id, config) \u2014 expand a cross-channel cascade into primitive steps.
|
|
25947
|
+
*
|
|
25948
|
+
* For each email channel (except the last), we emit:
|
|
25949
|
+
* send_email \u2192 wait_for_email_engagement \u2192 condition (engaged?)
|
|
25950
|
+
* with the condition's "yes" branch containing an exit node and the "no"
|
|
25951
|
+
* branch falling through to the next channel.
|
|
25952
|
+
*
|
|
25953
|
+
* Non-email channels (SMS) emit only a send step.
|
|
25954
|
+
*
|
|
25955
|
+
* Every generated step carries cascadeGroupId = id so the execution
|
|
25956
|
+
* engine can scope engagement queries to the correct group.
|
|
25957
|
+
*/
|
|
25958
|
+
export const cascade = (id, config) => {
|
|
25959
|
+
const channels = config.channels || [];
|
|
25960
|
+
const steps = [];
|
|
25961
|
+
|
|
25962
|
+
for (let i = 0; i < channels.length; i++) {
|
|
25963
|
+
const channel = channels[i];
|
|
25964
|
+
const isLast = i === channels.length - 1;
|
|
25965
|
+
|
|
25966
|
+
if (channel.type === 'email') {
|
|
25967
|
+
// Send email step
|
|
25968
|
+
steps.push({
|
|
25969
|
+
id: id + '-send-' + i,
|
|
25970
|
+
type: 'send_email',
|
|
25971
|
+
name: 'Cascade: send ' + (channel.template || 'email'),
|
|
25972
|
+
config: { type: 'send_email', templateId: channel.template },
|
|
25973
|
+
cascadeGroupId: id,
|
|
25974
|
+
});
|
|
25975
|
+
|
|
25976
|
+
// If not last channel, add wait + condition
|
|
25977
|
+
if (!isLast && channel.waitFor) {
|
|
25978
|
+
const waitSeconds = durationToSeconds(channel.waitFor) || 259200;
|
|
25979
|
+
const waitId = id + '-wait-' + i;
|
|
25980
|
+
const condId = id + '-cond-' + i;
|
|
25981
|
+
const exitId = id + '-exit-' + i;
|
|
25982
|
+
|
|
25983
|
+
// Wait for engagement step
|
|
25984
|
+
steps.push({
|
|
25985
|
+
id: waitId,
|
|
25986
|
+
type: 'wait_for_email_engagement',
|
|
25987
|
+
name: 'Cascade: wait for ' + (channel.engagement || 'opened'),
|
|
25988
|
+
config: { type: 'wait_for_email_engagement', timeoutSeconds: waitSeconds },
|
|
25989
|
+
cascadeGroupId: id,
|
|
25990
|
+
});
|
|
25991
|
+
|
|
25992
|
+
// Condition step: check engagement.status
|
|
25993
|
+
steps.push({
|
|
25994
|
+
id: condId,
|
|
25995
|
+
type: 'condition',
|
|
25996
|
+
name: 'Cascade: email engaged?',
|
|
25997
|
+
config: {
|
|
25998
|
+
type: 'condition',
|
|
25999
|
+
field: 'engagement.status',
|
|
26000
|
+
operator: 'equals',
|
|
26001
|
+
value: 'true',
|
|
26002
|
+
},
|
|
26003
|
+
cascadeGroupId: id,
|
|
26004
|
+
branches: {
|
|
26005
|
+
yes: [{
|
|
26006
|
+
id: exitId,
|
|
26007
|
+
type: 'exit',
|
|
26008
|
+
name: 'Exit',
|
|
26009
|
+
config: { type: 'exit', reason: 'Engaged via email' },
|
|
26010
|
+
cascadeGroupId: id,
|
|
26011
|
+
}],
|
|
26012
|
+
},
|
|
26013
|
+
});
|
|
26014
|
+
}
|
|
26015
|
+
} else if (channel.type === 'sms') {
|
|
26016
|
+
// Send SMS step
|
|
26017
|
+
steps.push({
|
|
26018
|
+
id: id + '-send-' + i,
|
|
26019
|
+
type: 'send_sms',
|
|
26020
|
+
name: 'Cascade: send ' + (channel.template || 'sms'),
|
|
26021
|
+
config: { type: 'send_sms', template: channel.template, body: channel.body },
|
|
26022
|
+
cascadeGroupId: id,
|
|
26023
|
+
});
|
|
26024
|
+
}
|
|
26025
|
+
}
|
|
26026
|
+
|
|
26027
|
+
return steps;
|
|
26028
|
+
};
|
|
26436
26029
|
|
|
26437
|
-
|
|
26438
|
-
|
|
26439
|
-
|
|
26440
|
-
|
|
26441
|
-
|
|
26442
|
-
|
|
26443
|
-
|
|
26444
|
-
|
|
26445
|
-
|
|
26446
|
-
|
|
26447
|
-
|
|
26448
|
-
}
|
|
26030
|
+
// Internal helpers
|
|
26031
|
+
function normalizeDuration(duration) {
|
|
26032
|
+
if (duration.days !== undefined) {
|
|
26033
|
+
return { amount: duration.days, unit: 'days' };
|
|
26034
|
+
}
|
|
26035
|
+
if (duration.hours !== undefined) {
|
|
26036
|
+
return { amount: duration.hours, unit: 'hours' };
|
|
26037
|
+
}
|
|
26038
|
+
if (duration.minutes !== undefined) {
|
|
26039
|
+
return { amount: duration.minutes, unit: 'minutes' };
|
|
26040
|
+
}
|
|
26041
|
+
return { amount: 1, unit: 'hours' };
|
|
26042
|
+
}
|
|
26449
26043
|
|
|
26450
|
-
|
|
26451
|
-
|
|
26452
|
-
|
|
26453
|
-
|
|
26454
|
-
|
|
26455
|
-
|
|
26456
|
-
|
|
26457
|
-
|
|
26458
|
-
yes: [exit('already-active')],
|
|
26459
|
-
no: [
|
|
26460
|
-
sendEmail('send-tips', { template: 'getting-started-tips' }),
|
|
26461
|
-
],
|
|
26462
|
-
},
|
|
26463
|
-
}),
|
|
26464
|
-
],
|
|
26465
|
-
});
|
|
26044
|
+
function durationToSeconds(duration) {
|
|
26045
|
+
if (!duration) return undefined;
|
|
26046
|
+
let seconds = 0;
|
|
26047
|
+
if (duration.days) seconds += duration.days * 24 * 60 * 60;
|
|
26048
|
+
if (duration.hours) seconds += duration.hours * 60 * 60;
|
|
26049
|
+
if (duration.minutes) seconds += duration.minutes * 60;
|
|
26050
|
+
return seconds > 0 ? seconds : undefined;
|
|
26051
|
+
}
|
|
26466
26052
|
`;
|
|
26467
|
-
|
|
26468
|
-
|
|
26469
|
-
|
|
26470
|
-
|
|
26471
|
-
|
|
26472
|
-
|
|
26053
|
+
await writeFile10(
|
|
26054
|
+
join16(shimDir, "wraps-client-shim.mjs"),
|
|
26055
|
+
clientShimContent,
|
|
26056
|
+
"utf-8"
|
|
26057
|
+
);
|
|
26058
|
+
const result = await build2({
|
|
26059
|
+
entryPoints: [filePath],
|
|
26060
|
+
bundle: true,
|
|
26061
|
+
write: false,
|
|
26062
|
+
format: "esm",
|
|
26063
|
+
platform: "node",
|
|
26064
|
+
target: "node20",
|
|
26065
|
+
alias: {
|
|
26066
|
+
"@wraps.dev/client": join16(shimDir, "wraps-client-shim.mjs")
|
|
26067
|
+
}
|
|
26068
|
+
});
|
|
26069
|
+
const bundledCode = result.outputFiles[0].text;
|
|
26070
|
+
const tmpDir = join16(wrapsDir, ".wraps", "_workflows");
|
|
26071
|
+
await mkdir8(tmpDir, { recursive: true });
|
|
26072
|
+
const tmpPath = join16(tmpDir, `${slug}.mjs`);
|
|
26073
|
+
await writeFile10(tmpPath, bundledCode, "utf-8");
|
|
26074
|
+
const mod = await import(`${tmpPath}?t=${Date.now()}`);
|
|
26075
|
+
const definition = mod.default;
|
|
26076
|
+
if (!definition || typeof definition !== "object") {
|
|
26077
|
+
throw new Error(
|
|
26078
|
+
"Workflow must have a default export (workflow definition from defineWorkflow())"
|
|
26079
|
+
);
|
|
26473
26080
|
}
|
|
26474
|
-
|
|
26475
|
-
|
|
26476
|
-
|
|
26477
|
-
|
|
26478
|
-
|
|
26479
|
-
|
|
26081
|
+
if (!definition.name) {
|
|
26082
|
+
throw new Error("Workflow definition must have a 'name' property");
|
|
26083
|
+
}
|
|
26084
|
+
if (!definition.trigger) {
|
|
26085
|
+
throw new Error("Workflow definition must have a 'trigger' property");
|
|
26086
|
+
}
|
|
26087
|
+
if (!Array.isArray(definition.steps)) {
|
|
26088
|
+
throw new Error("Workflow definition must have a 'steps' array");
|
|
26089
|
+
}
|
|
26090
|
+
return {
|
|
26091
|
+
slug,
|
|
26092
|
+
filePath,
|
|
26093
|
+
source,
|
|
26094
|
+
sourceHash,
|
|
26095
|
+
definition,
|
|
26096
|
+
cliProjectPath: `workflows/${slug}.ts`
|
|
26097
|
+
};
|
|
26098
|
+
}
|
|
26099
|
+
|
|
26100
|
+
// src/utils/email/workflow-validator.ts
|
|
26101
|
+
init_esm_shims();
|
|
26102
|
+
function validateTransformedWorkflow(transformed, localTemplateSlugs) {
|
|
26103
|
+
const errors2 = [];
|
|
26104
|
+
errors2.push(...validateStructure(transformed.steps, transformed.transitions));
|
|
26105
|
+
for (const step of transformed.steps) {
|
|
26106
|
+
errors2.push(...validateStep(step));
|
|
26107
|
+
}
|
|
26108
|
+
if (localTemplateSlugs) {
|
|
26109
|
+
errors2.push(
|
|
26110
|
+
...validateTemplateReferences(transformed.steps, localTemplateSlugs)
|
|
26480
26111
|
);
|
|
26481
|
-
if (tsFiles.length > 0 && !options.force) {
|
|
26482
|
-
if (!isJsonMode()) {
|
|
26483
|
-
clack30.log.warn(
|
|
26484
|
-
`${pc32.cyan("wraps/workflows/")} already contains ${tsFiles.length} workflow file(s). Use ${pc32.bold("--force")} to overwrite.`
|
|
26485
|
-
);
|
|
26486
|
-
}
|
|
26487
|
-
}
|
|
26488
26112
|
}
|
|
26489
|
-
|
|
26490
|
-
|
|
26491
|
-
|
|
26492
|
-
|
|
26493
|
-
|
|
26494
|
-
|
|
26495
|
-
const configContent = await readFile8(configPath, "utf-8");
|
|
26496
|
-
if (!configContent.includes("workflowsDir")) {
|
|
26497
|
-
const updated = configContent.replace(
|
|
26498
|
-
/}\);(\s*)$/,
|
|
26499
|
-
` workflowsDir: './workflows',
|
|
26500
|
-
});$1`
|
|
26501
|
-
);
|
|
26502
|
-
if (updated !== configContent) {
|
|
26503
|
-
await writeFile10(configPath, updated, "utf-8");
|
|
26504
|
-
}
|
|
26113
|
+
const errorsByNodeId = /* @__PURE__ */ new Map();
|
|
26114
|
+
for (const error of errors2) {
|
|
26115
|
+
if (error.nodeId) {
|
|
26116
|
+
const existing = errorsByNodeId.get(error.nodeId) || [];
|
|
26117
|
+
existing.push(error);
|
|
26118
|
+
errorsByNodeId.set(error.nodeId, existing);
|
|
26505
26119
|
}
|
|
26506
26120
|
}
|
|
26507
|
-
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
26121
|
+
return {
|
|
26122
|
+
isValid: errors2.filter((e) => e.severity === "error").length === 0,
|
|
26123
|
+
errors: errors2,
|
|
26124
|
+
errorsByNodeId
|
|
26125
|
+
};
|
|
26126
|
+
}
|
|
26127
|
+
function validateStructure(steps, transitions) {
|
|
26128
|
+
const errors2 = [];
|
|
26129
|
+
const triggerSteps = steps.filter((s) => s.type === "trigger");
|
|
26130
|
+
if (triggerSteps.length === 0) {
|
|
26131
|
+
errors2.push({
|
|
26132
|
+
message: "Workflow must have a trigger node",
|
|
26133
|
+
severity: "error"
|
|
26134
|
+
});
|
|
26135
|
+
} else if (triggerSteps.length > 1) {
|
|
26136
|
+
errors2.push({
|
|
26137
|
+
message: "Workflow can only have one trigger node",
|
|
26138
|
+
severity: "error"
|
|
26139
|
+
});
|
|
26140
|
+
}
|
|
26141
|
+
const actionSteps = steps.filter(
|
|
26142
|
+
(s) => s.type !== "trigger" && s.type !== "exit"
|
|
26143
|
+
);
|
|
26144
|
+
if (actionSteps.length === 0 && steps.length > 1) {
|
|
26145
|
+
errors2.push({
|
|
26146
|
+
message: "Workflow must have at least one action step",
|
|
26147
|
+
severity: "error"
|
|
26148
|
+
});
|
|
26149
|
+
}
|
|
26150
|
+
if (triggerSteps.length === 1) {
|
|
26151
|
+
const reachableIds = getReachableNodeIds(triggerSteps[0].id, transitions);
|
|
26152
|
+
const orphanSteps = steps.filter(
|
|
26153
|
+
(s) => s.type !== "trigger" && !reachableIds.has(s.id)
|
|
26154
|
+
);
|
|
26155
|
+
for (const orphan of orphanSteps) {
|
|
26156
|
+
errors2.push({
|
|
26157
|
+
nodeId: orphan.id,
|
|
26158
|
+
message: `"${orphan.name}" is not connected to the workflow`,
|
|
26159
|
+
severity: "warning"
|
|
26160
|
+
});
|
|
26513
26161
|
}
|
|
26514
26162
|
}
|
|
26515
|
-
|
|
26516
|
-
|
|
26517
|
-
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
|
|
26521
|
-
sectionId: "workflows",
|
|
26522
|
-
sectionContent: WORKFLOWS_CLAUDE_MD_SECTION
|
|
26163
|
+
const stepIds = new Set(steps.map((s) => s.id));
|
|
26164
|
+
for (const transition of transitions) {
|
|
26165
|
+
if (!stepIds.has(transition.fromStepId)) {
|
|
26166
|
+
errors2.push({
|
|
26167
|
+
message: `Transition references non-existent source step: ${transition.fromStepId}`,
|
|
26168
|
+
severity: "error"
|
|
26523
26169
|
});
|
|
26524
|
-
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
|
|
26528
|
-
|
|
26170
|
+
}
|
|
26171
|
+
if (!stepIds.has(transition.toStepId)) {
|
|
26172
|
+
errors2.push({
|
|
26173
|
+
message: `Transition references non-existent target step: ${transition.toStepId}`,
|
|
26174
|
+
severity: "error"
|
|
26529
26175
|
});
|
|
26530
|
-
filesCreated.push(".claude/skills/wraps-workflows/SKILL.md");
|
|
26531
|
-
progress.succeed("Claude Code context scaffolded");
|
|
26532
|
-
} catch {
|
|
26533
|
-
progress.info(
|
|
26534
|
-
"Could not scaffold .claude/ context \u2014 workflow files are still ready"
|
|
26535
|
-
);
|
|
26536
26176
|
}
|
|
26537
26177
|
}
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
26542
|
-
|
|
26543
|
-
|
|
26544
|
-
|
|
26545
|
-
|
|
26178
|
+
return errors2;
|
|
26179
|
+
}
|
|
26180
|
+
function getReachableNodeIds(startId, transitions) {
|
|
26181
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
26182
|
+
const queue = [startId];
|
|
26183
|
+
while (queue.length > 0) {
|
|
26184
|
+
const currentId = queue.shift();
|
|
26185
|
+
if (reachable.has(currentId)) {
|
|
26186
|
+
continue;
|
|
26187
|
+
}
|
|
26188
|
+
reachable.add(currentId);
|
|
26189
|
+
const outgoing = transitions.filter((t) => t.fromStepId === currentId);
|
|
26190
|
+
for (const t of outgoing) {
|
|
26191
|
+
if (!reachable.has(t.toStepId)) {
|
|
26192
|
+
queue.push(t.toStepId);
|
|
26193
|
+
}
|
|
26194
|
+
}
|
|
26195
|
+
}
|
|
26196
|
+
return reachable;
|
|
26197
|
+
}
|
|
26198
|
+
function validateStep(step) {
|
|
26199
|
+
const errors2 = [];
|
|
26200
|
+
const config2 = step.config;
|
|
26201
|
+
const configType = config2.type;
|
|
26202
|
+
switch (configType) {
|
|
26203
|
+
case "trigger":
|
|
26204
|
+
errors2.push(...validateTrigger(step.id, config2));
|
|
26205
|
+
break;
|
|
26206
|
+
case "send_email":
|
|
26207
|
+
errors2.push(...validateSendEmail(step.id, config2));
|
|
26208
|
+
break;
|
|
26209
|
+
case "condition":
|
|
26210
|
+
errors2.push(...validateCondition(step.id, config2));
|
|
26211
|
+
break;
|
|
26212
|
+
case "webhook":
|
|
26213
|
+
errors2.push(...validateWebhook(step.id, config2));
|
|
26214
|
+
break;
|
|
26215
|
+
case "subscribe_topic":
|
|
26216
|
+
case "unsubscribe_topic":
|
|
26217
|
+
errors2.push(...validateTopic(step.id, config2));
|
|
26218
|
+
break;
|
|
26219
|
+
case "wait_for_event":
|
|
26220
|
+
errors2.push(...validateWaitForEvent(step.id, config2));
|
|
26221
|
+
break;
|
|
26222
|
+
case "delay":
|
|
26223
|
+
errors2.push(...validateDelay(step.id, config2));
|
|
26224
|
+
break;
|
|
26225
|
+
}
|
|
26226
|
+
return errors2;
|
|
26227
|
+
}
|
|
26228
|
+
function validateTrigger(nodeId, config2) {
|
|
26229
|
+
const errors2 = [];
|
|
26230
|
+
switch (config2.triggerType) {
|
|
26231
|
+
case "event":
|
|
26232
|
+
if (!config2.eventName) {
|
|
26233
|
+
errors2.push({
|
|
26234
|
+
nodeId,
|
|
26235
|
+
field: "eventName",
|
|
26236
|
+
message: "Event name is required",
|
|
26237
|
+
severity: "error"
|
|
26238
|
+
});
|
|
26239
|
+
}
|
|
26240
|
+
break;
|
|
26241
|
+
case "segment_entry":
|
|
26242
|
+
case "segment_exit":
|
|
26243
|
+
if (!config2.segmentId) {
|
|
26244
|
+
errors2.push({
|
|
26245
|
+
nodeId,
|
|
26246
|
+
field: "segmentId",
|
|
26247
|
+
message: "Segment is required",
|
|
26248
|
+
severity: "error"
|
|
26249
|
+
});
|
|
26250
|
+
}
|
|
26251
|
+
break;
|
|
26252
|
+
case "topic_subscribed":
|
|
26253
|
+
case "topic_unsubscribed":
|
|
26254
|
+
if (!config2.topicId) {
|
|
26255
|
+
errors2.push({
|
|
26256
|
+
nodeId,
|
|
26257
|
+
field: "topicId",
|
|
26258
|
+
message: "Topic is required",
|
|
26259
|
+
severity: "error"
|
|
26260
|
+
});
|
|
26261
|
+
}
|
|
26262
|
+
break;
|
|
26263
|
+
case "schedule":
|
|
26264
|
+
if (!config2.schedule) {
|
|
26265
|
+
errors2.push({
|
|
26266
|
+
nodeId,
|
|
26267
|
+
field: "schedule",
|
|
26268
|
+
message: "Schedule (cron expression) is required",
|
|
26269
|
+
severity: "error"
|
|
26270
|
+
});
|
|
26271
|
+
}
|
|
26272
|
+
if (!config2.timezone) {
|
|
26273
|
+
errors2.push({
|
|
26274
|
+
nodeId,
|
|
26275
|
+
field: "timezone",
|
|
26276
|
+
message: "Timezone is required",
|
|
26277
|
+
severity: "error"
|
|
26278
|
+
});
|
|
26279
|
+
}
|
|
26280
|
+
break;
|
|
26281
|
+
}
|
|
26282
|
+
return errors2;
|
|
26283
|
+
}
|
|
26284
|
+
function validateSendEmail(nodeId, config2) {
|
|
26285
|
+
const errors2 = [];
|
|
26286
|
+
const templateRef = config2.templateId || config2.template;
|
|
26287
|
+
if (!templateRef) {
|
|
26288
|
+
errors2.push({
|
|
26289
|
+
nodeId,
|
|
26290
|
+
field: "templateId",
|
|
26291
|
+
message: "Email template is required",
|
|
26292
|
+
severity: "error"
|
|
26546
26293
|
});
|
|
26547
|
-
return;
|
|
26548
26294
|
}
|
|
26549
|
-
|
|
26550
|
-
|
|
26551
|
-
|
|
26552
|
-
|
|
26553
|
-
if (!
|
|
26554
|
-
|
|
26555
|
-
|
|
26556
|
-
|
|
26295
|
+
return errors2;
|
|
26296
|
+
}
|
|
26297
|
+
function validateCondition(nodeId, config2) {
|
|
26298
|
+
const errors2 = [];
|
|
26299
|
+
if (!config2.field) {
|
|
26300
|
+
errors2.push({
|
|
26301
|
+
nodeId,
|
|
26302
|
+
field: "field",
|
|
26303
|
+
message: "Condition field is required",
|
|
26304
|
+
severity: "error"
|
|
26305
|
+
});
|
|
26557
26306
|
}
|
|
26558
|
-
if (!
|
|
26559
|
-
|
|
26560
|
-
|
|
26561
|
-
|
|
26307
|
+
if (!config2.operator) {
|
|
26308
|
+
errors2.push({
|
|
26309
|
+
nodeId,
|
|
26310
|
+
field: "operator",
|
|
26311
|
+
message: "Condition operator is required",
|
|
26312
|
+
severity: "error"
|
|
26313
|
+
});
|
|
26562
26314
|
}
|
|
26563
|
-
|
|
26564
|
-
|
|
26565
|
-
|
|
26566
|
-
|
|
26567
|
-
|
|
26568
|
-
|
|
26569
|
-
|
|
26570
|
-
);
|
|
26571
|
-
console.log(` 3. Push: ${pc32.cyan("wraps email workflows push")}`);
|
|
26572
|
-
if (!options.noClaude) {
|
|
26573
|
-
console.log(
|
|
26574
|
-
` 4. Use Claude Code to generate workflows from descriptions`
|
|
26575
|
-
);
|
|
26315
|
+
if (config2.operator !== "is_set" && config2.operator !== "is_not_set" && config2.operator !== "is_true" && config2.operator !== "is_false" && (config2.value === void 0 || config2.value === "")) {
|
|
26316
|
+
errors2.push({
|
|
26317
|
+
nodeId,
|
|
26318
|
+
field: "value",
|
|
26319
|
+
message: "Condition value is required",
|
|
26320
|
+
severity: "error"
|
|
26321
|
+
});
|
|
26576
26322
|
}
|
|
26577
|
-
|
|
26323
|
+
return errors2;
|
|
26578
26324
|
}
|
|
26579
|
-
function
|
|
26580
|
-
|
|
26581
|
-
|
|
26582
|
-
|
|
26583
|
-
|
|
26584
|
-
|
|
26585
|
-
|
|
26586
|
-
|
|
26587
|
-
|
|
26588
|
-
|
|
26589
|
-
|
|
26325
|
+
function validateWebhook(nodeId, config2) {
|
|
26326
|
+
const errors2 = [];
|
|
26327
|
+
if (config2.url) {
|
|
26328
|
+
try {
|
|
26329
|
+
new URL(config2.url);
|
|
26330
|
+
} catch {
|
|
26331
|
+
errors2.push({
|
|
26332
|
+
nodeId,
|
|
26333
|
+
field: "url",
|
|
26334
|
+
message: "Invalid URL format",
|
|
26335
|
+
severity: "error"
|
|
26336
|
+
});
|
|
26337
|
+
}
|
|
26338
|
+
} else {
|
|
26339
|
+
errors2.push({
|
|
26340
|
+
nodeId,
|
|
26341
|
+
field: "url",
|
|
26342
|
+
message: "Webhook URL is required",
|
|
26343
|
+
severity: "error"
|
|
26344
|
+
});
|
|
26345
|
+
}
|
|
26346
|
+
return errors2;
|
|
26347
|
+
}
|
|
26348
|
+
function validateTopic(nodeId, config2) {
|
|
26349
|
+
const errors2 = [];
|
|
26350
|
+
if (!config2.topicId) {
|
|
26351
|
+
errors2.push({
|
|
26352
|
+
nodeId,
|
|
26353
|
+
field: "topicId",
|
|
26354
|
+
message: "Topic is required",
|
|
26355
|
+
severity: "error"
|
|
26356
|
+
});
|
|
26357
|
+
}
|
|
26358
|
+
return errors2;
|
|
26359
|
+
}
|
|
26360
|
+
function validateWaitForEvent(nodeId, config2) {
|
|
26361
|
+
const errors2 = [];
|
|
26362
|
+
if (!config2.eventName) {
|
|
26363
|
+
errors2.push({
|
|
26364
|
+
nodeId,
|
|
26365
|
+
field: "eventName",
|
|
26366
|
+
message: "Event name is required",
|
|
26367
|
+
severity: "error"
|
|
26368
|
+
});
|
|
26369
|
+
}
|
|
26370
|
+
return errors2;
|
|
26371
|
+
}
|
|
26372
|
+
function validateDelay(nodeId, config2) {
|
|
26373
|
+
const errors2 = [];
|
|
26374
|
+
const amount = config2.amount;
|
|
26375
|
+
if (!amount || amount < 1) {
|
|
26376
|
+
errors2.push({
|
|
26377
|
+
nodeId,
|
|
26378
|
+
field: "amount",
|
|
26379
|
+
message: "Delay duration must be at least 1",
|
|
26380
|
+
severity: "error"
|
|
26381
|
+
});
|
|
26382
|
+
}
|
|
26383
|
+
return errors2;
|
|
26384
|
+
}
|
|
26385
|
+
function validateTemplateReferences(steps, localTemplateSlugs) {
|
|
26386
|
+
const errors2 = [];
|
|
26387
|
+
for (const step of steps) {
|
|
26388
|
+
if (step.config.type === "send_email") {
|
|
26389
|
+
const config2 = step.config;
|
|
26390
|
+
const templateRef = config2.templateId || config2.template;
|
|
26391
|
+
if (templateRef && !localTemplateSlugs.has(templateRef)) {
|
|
26392
|
+
errors2.push({
|
|
26393
|
+
nodeId: step.id,
|
|
26394
|
+
field: "templateId",
|
|
26395
|
+
message: `Template "${templateRef}" not found in templates/ directory`,
|
|
26396
|
+
severity: "error"
|
|
26397
|
+
});
|
|
26398
|
+
}
|
|
26399
|
+
}
|
|
26400
|
+
if (step.config.type === "send_sms") {
|
|
26401
|
+
const config2 = step.config;
|
|
26402
|
+
const templateRef = config2.templateId || config2.template;
|
|
26403
|
+
if (templateRef && !localTemplateSlugs.has(templateRef)) {
|
|
26404
|
+
errors2.push({
|
|
26405
|
+
nodeId: step.id,
|
|
26406
|
+
field: "templateId",
|
|
26407
|
+
message: `SMS template "${templateRef}" not found in templates/ directory`,
|
|
26408
|
+
severity: "warning"
|
|
26409
|
+
// Warning for SMS since we might not have SMS templates yet
|
|
26410
|
+
});
|
|
26411
|
+
}
|
|
26412
|
+
}
|
|
26413
|
+
}
|
|
26414
|
+
return errors2;
|
|
26590
26415
|
}
|
|
26591
26416
|
|
|
26592
26417
|
// src/commands/email/workflows/push.ts
|
|
26593
|
-
init_esm_shims();
|
|
26594
|
-
init_events();
|
|
26595
|
-
import { existsSync as existsSync16 } from "fs";
|
|
26596
|
-
import { join as join17 } from "path";
|
|
26597
|
-
import * as clack31 from "@clack/prompts";
|
|
26598
|
-
import pc33 from "picocolors";
|
|
26599
26418
|
init_config();
|
|
26600
26419
|
init_errors();
|
|
26601
26420
|
init_json_output();
|
|
@@ -27628,15 +27447,15 @@ import {
|
|
|
27628
27447
|
IAMClient as IAMClient2,
|
|
27629
27448
|
PutRolePolicyCommand
|
|
27630
27449
|
} from "@aws-sdk/client-iam";
|
|
27631
|
-
import { confirm as
|
|
27450
|
+
import { confirm as confirm14, intro as intro33, isCancel as isCancel20, log as log33, outro as outro19, select as select14 } from "@clack/prompts";
|
|
27632
27451
|
import * as pulumi21 from "@pulumi/pulumi";
|
|
27633
27452
|
import pc37 from "picocolors";
|
|
27634
27453
|
init_events();
|
|
27635
27454
|
init_aws();
|
|
27636
27455
|
init_config();
|
|
27637
27456
|
init_fs();
|
|
27638
|
-
init_metadata();
|
|
27639
27457
|
init_json_output();
|
|
27458
|
+
init_metadata();
|
|
27640
27459
|
init_output();
|
|
27641
27460
|
init_prompts();
|
|
27642
27461
|
function buildConsolePolicyDocument(emailConfig, smsConfig) {
|
|
@@ -27979,7 +27798,7 @@ async function resolveOrganization() {
|
|
|
27979
27798
|
hint: org.slug
|
|
27980
27799
|
}))
|
|
27981
27800
|
});
|
|
27982
|
-
if (
|
|
27801
|
+
if (isCancel20(selected)) {
|
|
27983
27802
|
outro19("Operation cancelled");
|
|
27984
27803
|
process.exit(0);
|
|
27985
27804
|
}
|
|
@@ -28041,11 +27860,11 @@ async function authenticatedConnect(token, options) {
|
|
|
28041
27860
|
"Event tracking must be enabled to connect to the Wraps Platform."
|
|
28042
27861
|
);
|
|
28043
27862
|
}
|
|
28044
|
-
const enableTracking = options.yes || await
|
|
27863
|
+
const enableTracking = options.yes || await confirm14({
|
|
28045
27864
|
message: "Enable event tracking now?",
|
|
28046
27865
|
initialValue: true
|
|
28047
27866
|
});
|
|
28048
|
-
if (
|
|
27867
|
+
if (isCancel20(enableTracking) || !enableTracking) {
|
|
28049
27868
|
outro19("Platform connection cancelled.");
|
|
28050
27869
|
process.exit(0);
|
|
28051
27870
|
}
|
|
@@ -28235,11 +28054,11 @@ Run ${pc37.cyan("wraps email init")} or ${pc37.cyan("wraps sms init")} first.
|
|
|
28235
28054
|
log33.info(
|
|
28236
28055
|
"Enabling event tracking will allow SES events to be streamed to the dashboard."
|
|
28237
28056
|
);
|
|
28238
|
-
const enableEventTracking = await
|
|
28057
|
+
const enableEventTracking = await confirm14({
|
|
28239
28058
|
message: "Enable event tracking now?",
|
|
28240
28059
|
initialValue: true
|
|
28241
28060
|
});
|
|
28242
|
-
if (
|
|
28061
|
+
if (isCancel20(enableEventTracking) || !enableEventTracking) {
|
|
28243
28062
|
outro19("Platform connection cancelled.");
|
|
28244
28063
|
process.exit(0);
|
|
28245
28064
|
}
|
|
@@ -28287,18 +28106,18 @@ Run ${pc37.cyan("wraps email init")} or ${pc37.cyan("wraps sms init")} first.
|
|
|
28287
28106
|
}
|
|
28288
28107
|
]
|
|
28289
28108
|
});
|
|
28290
|
-
if (
|
|
28109
|
+
if (isCancel20(action)) {
|
|
28291
28110
|
outro19("Operation cancelled");
|
|
28292
28111
|
process.exit(0);
|
|
28293
28112
|
}
|
|
28294
28113
|
if (action === "keep") {
|
|
28295
28114
|
webhookSecret = existingSecret;
|
|
28296
28115
|
} else if (action === "disconnect") {
|
|
28297
|
-
const confirmDisconnect = await
|
|
28116
|
+
const confirmDisconnect = await confirm14({
|
|
28298
28117
|
message: "Are you sure? Events will no longer be sent to the Wraps Platform.",
|
|
28299
28118
|
initialValue: false
|
|
28300
28119
|
});
|
|
28301
|
-
if (
|
|
28120
|
+
if (isCancel20(confirmDisconnect) || !confirmDisconnect) {
|
|
28302
28121
|
outro19("Disconnect cancelled");
|
|
28303
28122
|
process.exit(0);
|
|
28304
28123
|
}
|
|
@@ -28509,7 +28328,7 @@ import {
|
|
|
28509
28328
|
GetRoleCommand as GetRoleCommand2,
|
|
28510
28329
|
IAMClient as IAMClient3
|
|
28511
28330
|
} from "@aws-sdk/client-iam";
|
|
28512
|
-
import { confirm as
|
|
28331
|
+
import { confirm as confirm15, intro as intro35, isCancel as isCancel21, log as log34, outro as outro20 } from "@clack/prompts";
|
|
28513
28332
|
import pc39 from "picocolors";
|
|
28514
28333
|
async function updateRole(options) {
|
|
28515
28334
|
const startTime = Date.now();
|
|
@@ -28570,11 +28389,11 @@ Run ${pc39.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
28570
28389
|
if (!options.force) {
|
|
28571
28390
|
progress.stop();
|
|
28572
28391
|
const actionLabel = roleExists2 ? "Update" : "Create";
|
|
28573
|
-
const shouldContinue = await
|
|
28392
|
+
const shouldContinue = await confirm15({
|
|
28574
28393
|
message: `${actionLabel} IAM role ${pc39.cyan(roleName)} with latest permissions?`,
|
|
28575
28394
|
initialValue: true
|
|
28576
28395
|
});
|
|
28577
|
-
if (
|
|
28396
|
+
if (isCancel21(shouldContinue) || !shouldContinue) {
|
|
28578
28397
|
outro20(`${actionLabel} cancelled`);
|
|
28579
28398
|
process.exit(0);
|
|
28580
28399
|
}
|
|
@@ -32699,8 +32518,8 @@ async function deleteSMSProtectConfigurationWithSDK(region) {
|
|
|
32699
32518
|
init_events();
|
|
32700
32519
|
init_aws();
|
|
32701
32520
|
init_errors();
|
|
32702
|
-
init_json_output();
|
|
32703
32521
|
init_fs();
|
|
32522
|
+
init_json_output();
|
|
32704
32523
|
init_metadata();
|
|
32705
32524
|
init_output();
|
|
32706
32525
|
async function smsDestroy(options) {
|
|
@@ -32931,8 +32750,8 @@ import pc44 from "picocolors";
|
|
|
32931
32750
|
init_events();
|
|
32932
32751
|
init_aws();
|
|
32933
32752
|
init_errors();
|
|
32934
|
-
init_json_output();
|
|
32935
32753
|
init_fs();
|
|
32754
|
+
init_json_output();
|
|
32936
32755
|
init_metadata();
|
|
32937
32756
|
init_output();
|
|
32938
32757
|
init_prompts();
|
|
@@ -34113,8 +33932,8 @@ import pc47 from "picocolors";
|
|
|
34113
33932
|
init_events();
|
|
34114
33933
|
init_aws();
|
|
34115
33934
|
init_errors();
|
|
34116
|
-
init_json_output();
|
|
34117
33935
|
init_fs();
|
|
33936
|
+
init_json_output();
|
|
34118
33937
|
init_metadata();
|
|
34119
33938
|
init_output();
|
|
34120
33939
|
async function smsSync(options) {
|
|
@@ -34548,8 +34367,8 @@ import pc49 from "picocolors";
|
|
|
34548
34367
|
init_events();
|
|
34549
34368
|
init_aws();
|
|
34550
34369
|
init_errors();
|
|
34551
|
-
init_json_output();
|
|
34552
34370
|
init_fs();
|
|
34371
|
+
init_json_output();
|
|
34553
34372
|
init_metadata();
|
|
34554
34373
|
init_output();
|
|
34555
34374
|
init_prompts();
|
|
@@ -36327,7 +36146,7 @@ function showHelp() {
|
|
|
36327
36146
|
` ${pc54.cyan("email workflows push")} Push workflows to dashboard`
|
|
36328
36147
|
);
|
|
36329
36148
|
console.log(
|
|
36330
|
-
` ${pc54.cyan("email workflows generate")} Generate workflow from template
|
|
36149
|
+
` ${pc54.cyan("email workflows generate")} Generate workflow from template
|
|
36331
36150
|
`
|
|
36332
36151
|
);
|
|
36333
36152
|
console.log("SMS Commands:");
|
|
@@ -37105,14 +36924,10 @@ Available commands: ${pc54.cyan("init")}, ${pc54.cyan("push")}, ${pc54.cyan("pre
|
|
|
37105
36924
|
break;
|
|
37106
36925
|
case "generate":
|
|
37107
36926
|
await workflowsGenerate({
|
|
37108
|
-
description: args.sub[3],
|
|
37109
36927
|
template: flags.template,
|
|
37110
36928
|
name: flags.name,
|
|
37111
|
-
dryRun: flags.dryRun,
|
|
37112
|
-
yes: flags.yes,
|
|
37113
36929
|
force: flags.force,
|
|
37114
|
-
json: flags.json
|
|
37115
|
-
token: flags.token
|
|
36930
|
+
json: flags.json
|
|
37116
36931
|
});
|
|
37117
36932
|
break;
|
|
37118
36933
|
default:
|