@cowliss/cli 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,7 +45,6 @@ import { defineJourney } from "@cowliss/cli/journeys";
45
45
  export default defineJourney({
46
46
  trigger: { event: "checkout_started" },
47
47
  purpose: "emailMarketing",
48
- senderIdentity: "cowliss-default",
49
48
  run: async (event, api) => {
50
49
  const purchased = await api.waitForEvent("purchase_completed", {
51
50
  timeout: "24h",
@@ -73,11 +72,11 @@ cow build
73
72
  # upload the project; the server compiles it
74
73
  cow push
75
74
 
76
- # what the server holds, and what is on where
75
+ # what the server holds, and what is running
77
76
  cow status
78
77
 
79
- # turn it on, per environment
80
- cow enable abandoned-checkout --env development
78
+ # turn it on
79
+ cow enable abandoned-checkout
81
80
  ```
82
81
 
83
82
  A push creates a new version of every journey and template whose code
@@ -1,4 +1,4 @@
1
- import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-C8j8Sqvl.js";
1
+ import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-BrcKEXz0.js";
2
2
  import { z } from "zod";
3
3
  import { createElement } from "react";
4
4
  import { renderToStaticMarkup } from "react-dom/server.browser";
@@ -124,7 +124,8 @@ function createApi(input, journey) {
124
124
  args: {
125
125
  template: args.template,
126
126
  props: args.props,
127
- senderIdentity: args.senderIdentity ?? journey.senderIdentity
127
+ from: args.from ?? journey.from,
128
+ replyTo: args.replyTo
128
129
  }
129
130
  }),
130
131
  webhook: (args) => call({
@@ -5572,7 +5573,7 @@ function readManifest(module) {
5572
5573
  kind: "journey",
5573
5574
  trigger: journey.trigger,
5574
5575
  purpose: journey.purpose,
5575
- senderIdentity: journey.senderIdentity,
5576
+ from: journey.from,
5576
5577
  tags: journey.tags
5577
5578
  });
5578
5579
  }
@@ -1,4 +1,4 @@
1
- import { a as ManifestOutput, i as JourneyStepOutput, o as TemplateRenderOutput } from "./index-BDj02EsQ.js";
1
+ import { a as ManifestOutput, i as JourneyStepOutput, o as TemplateRenderOutput } from "./index-CXcCEcAg.js";
2
2
  //#region src/guest/driver.d.ts
3
3
  /**
4
4
  * The in-process driver: JSON in, JSON out (spec: Guest protocol). One
@@ -1,3 +1,3 @@
1
- import { t as runGuest } from "./driver-14FnzM-h.js";
1
+ import { t as runGuest } from "./driver-DBt5l1Z5.js";
2
2
 
3
3
  export { runGuest };
@@ -34,7 +34,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
34
34
  args: z.ZodObject<{
35
35
  template: z.ZodString;
36
36
  props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
37
- senderIdentity: z.ZodString;
37
+ from: z.ZodOptional<z.ZodString>;
38
+ replyTo: z.ZodOptional<z.ZodEmail>;
38
39
  }, z.core.$strict>;
39
40
  }, z.core.$strip>, z.ZodObject<{
40
41
  name: z.ZodLiteral<"send.webhook">;
@@ -124,7 +125,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
124
125
  args: z.ZodObject<{
125
126
  template: z.ZodString;
126
127
  props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
127
- senderIdentity: z.ZodString;
128
+ from: z.ZodOptional<z.ZodString>;
129
+ replyTo: z.ZodOptional<z.ZodEmail>;
128
130
  }, z.core.$strict>;
129
131
  }, z.core.$strip>, z.ZodObject<{
130
132
  name: z.ZodLiteral<"send.webhook">;
@@ -183,7 +185,8 @@ declare const journeyStepOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
183
185
  args: z.ZodObject<{
184
186
  template: z.ZodString;
185
187
  props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
186
- senderIdentity: z.ZodString;
188
+ from: z.ZodOptional<z.ZodString>;
189
+ replyTo: z.ZodOptional<z.ZodEmail>;
187
190
  }, z.core.$strict>;
188
191
  }, z.core.$strip>, z.ZodObject<{
189
192
  name: z.ZodLiteral<"send.webhook">;
@@ -257,7 +260,7 @@ declare const manifestOutputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
257
260
  segment: z.ZodString;
258
261
  }, z.core.$strict>]>;
259
262
  purpose: z.ZodString;
260
- senderIdentity: z.ZodOptional<z.ZodString>;
263
+ from: z.ZodOptional<z.ZodString>;
261
264
  kind: z.ZodLiteral<"journey">;
262
265
  }, z.core.$strip>, z.ZodObject<{
263
266
  tags: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[]>>;
@@ -151,15 +151,44 @@ const triggerSchema = z.union([z.strictObject({
151
151
  appId: patternSchema.optional()
152
152
  }), z.strictObject({ segment: z.string().min(1) })]);
153
153
  /**
154
- * A destination's name: the token a journey addresses it by, in a
155
- * `send.webhook` call or a journey's `senderIdentity`.
154
+ * The address half of a from-header: a local part, an `@`, and a dotted
155
+ * domain. Deliberately narrower than RFC 5322 (no quoted local parts, no
156
+ * address literals): every character it refuses would have to be quoted or
157
+ * escaped to survive a header, and none of them belong in an address a
158
+ * journey sends from.
159
+ */
160
+ const FROM_ADDRESS = /^[^\s@<>",;]+@([^\s@<>",;.]+(?:\.[^\s@<>",;.]+)+)$/;
161
+ /**
162
+ * Parse a journey's or a send's `from`: `addr@domain`, or
163
+ * `Name <addr@domain>` with the name optionally quoted. Null when it is
164
+ * neither, which is what `cow build` refuses on and what the send-time
165
+ * domain gate turns into a skip.
156
166
  *
157
- * Defined here rather than beside the destinations contract, and imported
158
- * from here by it, because a journey manifest names one and the guest layer
159
- * is bundled into every tenant module: importing it the other way round
160
- * would pull the Drizzle destinations table into all of them.
167
+ * One parser, and the send facade reuses it. A control character anywhere is
168
+ * a refusal rather than something to strip: it survives quoting and would
169
+ * inject a second header.
170
+ */
171
+ function parseFromAddress(from) {
172
+ const text = from.trim();
173
+ if (/\p{Cc}/u.test(text)) return null;
174
+ const angled = /^(.*?)\s*<([^<>]*)>$/.exec(text);
175
+ const address = (angled?.[2] ?? text).trim();
176
+ const domain = FROM_ADDRESS.exec(address)?.[1];
177
+ if (!domain) return null;
178
+ const name = (angled?.[1] ?? "").trim().replace(/^"(.*)"$/s, "$1").replace(/\\(.)/g, "$1").trim();
179
+ return {
180
+ ...name ? { name } : {},
181
+ address,
182
+ domain: domain.toLowerCase()
183
+ };
184
+ }
185
+ /**
186
+ * The address a journey or one send leaves as: a string, not the name of a
187
+ * configured row (ADR 0014). The domain is checked against the
188
+ * organization's verified ones at push time and again at send time; the
189
+ * shape is all that is checked here, because it is all a build can know.
161
190
  */
162
- const destinationNameSchema = z.string().trim().min(1, "name is required").max(100, "name must be at most 100 characters");
191
+ const fromSchema = z.string().trim().max(200, "from must be at most 200 characters").refine((value) => parseFromAddress(value) !== null, { message: "from must be an address (\"billing@acme.com\") or a name and address (\"Billing <billing@acme.com>\")" });
163
192
  /** A content address: `sha256:` plus the lowercase hex digest. */
164
193
  const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/, "digest must be sha256:<64 hex>");
165
194
  /**
@@ -195,13 +224,6 @@ const SPINE_ENTRY_NAMES = [
195
224
  const spineEntrySchema = z.object({
196
225
  name: z.enum(SPINE_ENTRY_NAMES),
197
226
  detail: z.string().max(200).optional(),
198
- /**
199
- * The sender identity a `send.email` call named for itself, overriding
200
- * the journey's own. Present only when the author wrote one on the call,
201
- * which is what lets the deploy warning and the journey detail page name
202
- * the override without re-reading the code.
203
- */
204
- senderIdentity: z.string().max(100).optional(),
205
227
  /** A `waitForEvent` timeout, as the author wrote it. */
206
228
  timeout: z.string().max(50).optional(),
207
229
  get steps() {
@@ -213,10 +235,10 @@ const spineEntrySchema = z.object({
213
235
  }).meta({ id: "JourneySpineEntry" });
214
236
  /**
215
237
  * One journey in a stored manifest. A plain (non-strict) object on purpose:
216
- * a manifest pushed before the author's rollout gate went away still carries
217
- * `environments`, and every stored manifest has to keep parsing for good
218
- * (ADR 0011). Zod strips the key, and the journey runs wherever the
219
- * per-environment enabled flag says it runs, which is the one gate there is.
238
+ * a manifest pushed before the author's rollout gate went away still
239
+ * carries keys that no longer exist, and every stored manifest has to keep
240
+ * parsing for good (ADR 0011). Zod strips them, and the journey runs when
241
+ * its `enabled` flag says so, which is the one gate there is.
220
242
  */
221
243
  const manifestJourneySchema = z.object({
222
244
  key: journeyKeySchema,
@@ -230,16 +252,12 @@ const manifestJourneySchema = z.object({
230
252
  */
231
253
  purpose: consentPurposeKeySchema,
232
254
  /**
233
- * The sender identity every `send.email` in this journey goes out as,
234
- * unless the call names its own. A name, never a `dst_` id: an org has one
235
- * row per environment, so an id would send in production and fail in
236
- * development, which is the one thing a journey must not do.
237
- *
238
- * Optional here and required at `defineJourney`, exactly like `purposes`: a
239
- * version pushed before the field existed carries none and its stored
240
- * manifest still parses. The author's build is where the error is useful.
255
+ * The address every `send.email` in this journey goes out as, unless the
256
+ * call names its own. Optional everywhere: an omitted one is the
257
+ * organization's shared fallback address, which is what makes day-one
258
+ * sending zero-config (ADR 0014).
241
259
  */
242
- senderIdentity: destinationNameSchema.optional(),
260
+ from: fromSchema.optional(),
243
261
  spine: z.array(spineEntrySchema),
244
262
  bundle: digestSchema
245
263
  });
@@ -319,9 +337,8 @@ const versionManifestSchema = z.union([manifestJourneySchema, manifestTemplateSc
319
337
  const projectNameSchema = z.string().trim().min(1).max(200);
320
338
  /**
321
339
  * `cow.json` (spec: Project layout): the org, and which of its projects this
322
- * directory is. The environment is always a flag, and auth never lives in
323
- * the project. Strict, so a typo'd key is a build error rather than a
324
- * silently ignored setting.
340
+ * directory is. Auth never lives in the project. Strict, so a typo'd key
341
+ * is a build error rather than a silently ignored setting.
325
342
  */
326
343
  const cowConfigSchema = z.strictObject({
327
344
  $schema: z.url().optional(),
@@ -376,8 +393,8 @@ const properties = z.record(z.string(), z.unknown());
376
393
  * them. A name outside the union is rejected, never dispatched.
377
394
  *
378
395
  * Every `args` is strict, and that is the tenancy boundary made mechanical:
379
- * a module that returns an `orgId`, an `environment`, or any other field
380
- * beside the ones a capability takes fails the parse instead of having it
396
+ * a module that returns an `orgId` or any other field beside the ones a
397
+ * capability takes fails the parse instead of having it
381
398
  * quietly dropped. Tenant context comes from the workflow input, never from
382
399
  * guest output, and this is where saying so becomes checkable.
383
400
  */
@@ -399,12 +416,14 @@ const commandSchema = z.discriminatedUnion("name", [
399
416
  template: journeyKeySchema,
400
417
  props: properties,
401
418
  /**
402
- * Which sender identity this mail leaves as, by name. Required, and
403
- * the guest SDK fills in the journey's own when the call does not name
404
- * one, so the host has one resolution path and never has to read the
405
- * manifest to find a sender.
419
+ * The address this mail leaves as. The guest SDK fills in the
420
+ * journey's own whenever the call does not name one, so the host has
421
+ * one resolution path and never reads the manifest to find it;
422
+ * absent on both is the organization's shared fallback address.
406
423
  */
407
- senderIdentity: destinationNameSchema
424
+ from: fromSchema.optional(),
425
+ /** Where a reply to this one mail goes, instead of the from-address. */
426
+ replyTo: z.email().optional()
408
427
  })
409
428
  }),
410
429
  z.object({
@@ -485,7 +504,7 @@ const executionLimitsSchema = z.object({
485
504
  logLineBytes: z.number().int().positive()
486
505
  });
487
506
  const journeyStepInputSchema = z.object({
488
- protocol: z.literal(2),
507
+ protocol: z.literal(3),
489
508
  kind: z.literal("journey"),
490
509
  key: journeyKeySchema,
491
510
  event: guestEventSchema,
@@ -525,7 +544,7 @@ const journeyStepOutputSchema = z.discriminatedUnion("status", [
525
544
  })
526
545
  ]);
527
546
  const templateRenderInputSchema = z.object({
528
- protocol: z.literal(2),
547
+ protocol: z.literal(3),
529
548
  kind: z.literal("template"),
530
549
  key: journeyKeySchema,
531
550
  props: properties
@@ -544,7 +563,7 @@ const manifestInputSchema = z.object({ kind: z.literal("manifest") });
544
563
  const manifestOutputSchema = z.discriminatedUnion("kind", [manifestJourneySchema.pick({
545
564
  trigger: true,
546
565
  purpose: true,
547
- senderIdentity: true,
566
+ from: true,
548
567
  tags: true
549
568
  }).extend({ kind: z.literal("journey") }), manifestTemplateSchema.pick({
550
569
  sendClass: true,
@@ -600,7 +619,7 @@ const sandboxFailureCodeSchema = z.enum(SANDBOX_FAILURE_CODES);
600
619
  * to be written by hand or by an agent, not generated.
601
620
  *
602
621
  * The schema lives here rather than in packages/journeys so the CLI can reject
603
- * a bad file before it boots a Temporal environment, and so the docs site can
622
+ * a bad file before it boots a Temporal dev server, and so the docs site can
604
623
  * render the format from one source.
605
624
  */
606
625
  const journeyScenarioSchema = z.object({
@@ -642,7 +661,7 @@ const journeyScenarioSchema = z.object({
642
661
  //#endregion
643
662
  //#region src/guest/journeys.ts
644
663
  /**
645
- * A capability that failed host-side (an unknown destination, invalid
664
+ * A capability that failed host-side (an unknown webhook, invalid
646
665
  * props, a profile that is not there). The failure is journaled, so a
647
666
  * journey that catches it takes the same branch on every replay.
648
667
  */
@@ -656,29 +675,29 @@ var CapabilityError = class extends Error {
656
675
  };
657
676
  /**
658
677
  * The config half of a journey, validated with the same schema the release
659
- * manifest is validated with, so a bad trigger or a malformed purpose key
660
- * fails at build time rather than at the first execution. Whether the org
661
- * declares that purpose is a question only the deploy can answer.
678
+ * manifest is validated with, so a bad trigger, a malformed purpose key, or
679
+ * a `from` that is not an address fails at build time rather than at the
680
+ * first execution. Whether the org declares that purpose, and whether it may
681
+ * send from that domain, are questions only the push can answer.
662
682
  */
663
683
  const journeyConfigSchema = manifestJourneySchema.pick({
664
684
  trigger: true,
665
685
  purpose: true,
666
- senderIdentity: true,
686
+ from: true,
667
687
  tags: true
668
- }).extend({ senderIdentity: destinationNameSchema });
688
+ });
669
689
  /**
670
690
  * Author a journey. Throws at definition time on an invalid config.
671
691
  *
672
- * There is no environment gate here: a journey runs wherever it is enabled,
673
- * and nowhere else (ADR 0011). A source that still passes `environments` is
674
- * a typecheck error in `cow build`, which is where the author can fix it.
692
+ * There is no environment gate here: an organization has one data space, and
693
+ * a journey runs wherever it is enabled and nowhere else (ADR 0011, ADR 0013).
675
694
  */
676
695
  function defineJourney(input) {
677
696
  return {
678
697
  ...journeyConfigSchema.parse({
679
698
  trigger: input.trigger,
680
699
  purpose: input.purpose,
681
- senderIdentity: input.senderIdentity,
700
+ from: input.from,
682
701
  tags: input.tags
683
702
  }),
684
703
  run: input.run
@@ -1,11 +1,11 @@
1
- import { n as Duration, r as GuestEvent, t as Trigger } from "./index-BDj02EsQ.js";
1
+ import { n as Duration, r as GuestEvent, t as Trigger } from "./index-CXcCEcAg.js";
2
2
  //#region src/guest/journeys.d.ts
3
3
  /** The event a journey runs for, or the one a `waitForEvent` resolved with. */
4
4
  type Event = GuestEvent;
5
5
  /**
6
6
  * A profile as the capability layer hands it back: the recipient of this
7
- * execution (`api.profile.get()`) or another profile in the same org and
8
- * environment (`api.profiles.get(id)`). A fresh read on every call.
7
+ * execution (`api.profile.get()`) or another profile in the same org
8
+ * (`api.profiles.get(id)`). A fresh read on every call.
9
9
  */
10
10
  type Profile = {
11
11
  /** The Cowliss-generated profile id (`usr_`). */
@@ -23,7 +23,7 @@ type EmailSendResult = {
23
23
  status: string;
24
24
  };
25
25
  /**
26
- * A capability that failed host-side (an unknown destination, invalid
26
+ * A capability that failed host-side (an unknown webhook, invalid
27
27
  * props, a profile that is not there). The failure is journaled, so a
28
28
  * journey that catches it takes the same branch on every replay.
29
29
  */
@@ -68,11 +68,16 @@ type Api = {
68
68
  template: Key;
69
69
  props: TemplateProps<Key>;
70
70
  /**
71
- * Send this one mail from a different sender identity than the
72
- * journey's own. A name the organization has configured, resolved in
73
- * the environment the execution is running in.
71
+ * Send this one mail from a different address than the journey's own:
72
+ * `"billing@acme.com"` or `"Billing <billing@acme.com>"`. The domain
73
+ * must be one your organization has verified, or the send is skipped.
74
74
  */
75
- senderIdentity?: string;
75
+ from?: string;
76
+ /**
77
+ * Where a reply to this one mail goes, instead of the from-address.
78
+ * Any address: nothing is sent from it.
79
+ */
80
+ replyTo?: string;
76
81
  }): Promise<EmailSendResult>;
77
82
  webhook(args: {
78
83
  destination: string;
@@ -122,16 +127,16 @@ type JourneyConfig = {
122
127
  */
123
128
  purpose: string;
124
129
  /**
125
- * The sender identity every `api.send.email` in this journey goes out as:
126
- * the name of one your organization has configured, or `cowliss-default`
127
- * for the address Cowliss gives you with nothing to set up. A single send
130
+ * The address every `api.send.email` in this journey goes out as:
131
+ * `"billing@acme.com"` or `"Billing <billing@acme.com>"`. A single send
128
132
  * can override it.
129
133
  *
130
- * Required, and named here rather than in a settings page, so the address
131
- * a journey sends from is readable in the journey's own source. It is
132
- * resolved per environment, which is why it is a name and not an id.
134
+ * Optional: leave it out and Cowliss sends from the address it gives your
135
+ * organization, with nothing to set up. Named here rather than in a
136
+ * settings page, so the address a journey sends from is readable in the
137
+ * journey's own source.
133
138
  */
134
- senderIdentity: string;
139
+ from?: string;
135
140
  /** The author's labels; the dashboard's only grouping. Defaults to none. */
136
141
  tags: string[];
137
142
  };
@@ -141,9 +146,8 @@ type Journey = JourneyConfig & {
141
146
  /**
142
147
  * Author a journey. Throws at definition time on an invalid config.
143
148
  *
144
- * There is no environment gate here: a journey runs wherever it is enabled,
145
- * and nowhere else (ADR 0011). A source that still passes `environments` is
146
- * a typecheck error in `cow build`, which is where the author can fix it.
149
+ * There is no environment gate here: an organization has one data space, and
150
+ * a journey runs wherever it is enabled and nowhere else (ADR 0011, ADR 0013).
147
151
  */
148
152
  declare function defineJourney(input: Omit<JourneyConfig, "tags"> & {
149
153
  tags?: string[];
@@ -1,3 +1,3 @@
1
- import { n as defineJourney, t as CapabilityError } from "./journeys-C8j8Sqvl.js";
1
+ import { n as defineJourney, t as CapabilityError } from "./journeys-BrcKEXz0.js";
2
2
 
3
3
  export { CapabilityError, defineJourney };
@@ -1,4 +1,4 @@
1
- import { t as runGuest } from "./driver-14FnzM-h.js";
1
+ import { t as runGuest } from "./driver-DBt5l1Z5.js";
2
2
 
3
3
  //#region src/guest/wasi.ts
4
4
  const STDIN = 0;