devflare 1.0.0-next.51 → 1.0.0-next.53

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/LLM.md CHANGED
@@ -4800,6 +4800,8 @@ The `devflare/test` entrypoint intentionally has multiple lanes: runtime-shaped
4800
4800
  | `createMockArtifacts` | Mock Artifacts repo APIs. |
4801
4801
  | `createMockVectorize` | Mock a Vectorize index (in-memory cosine query). |
4802
4802
  | `createMockAnalyticsEngine` | Mock an Analytics Engine dataset (write-only recording stub). |
4803
+ | `createMockSendEmail` | Mock a SendEmail binding (records dispatched mail, enforces allow-lists). |
4804
+ | `createLocalSendEmailBinding` | Underlying pure SendEmail simulator (non-recording). |
4803
4805
  | `createMockSecretsStoreSecret` | Mock a Secrets Store secret. |
4804
4806
  | `createMockEnv` | Create a pure env with selected mock bindings. |
4805
4807
  | `hasServiceBindings` | Advanced/internal service-binding resolution predicate. |
@@ -11351,6 +11353,7 @@ test('sends an outbound transactional email', async () => {
11351
11353
 
11352
11354
  - Use the outbound binding directly when the worker is sending mail.
11353
11355
  - Use the inbound `email` helper surface (`cf.email.send(...)` from `devflare/test`) when the worker is handling inbound email in `src/email.ts`.
11356
+ - For pure offline tests, `createMockSendEmail()` (or `createMockEnv({ sendEmail })` / `createOfflineEnv()`) records every dispatched message into `.sentEmails` while still enforcing the configured sender/destination allow-lists; `createLocalSendEmailBinding()` is the underlying non-recording simulator.
11354
11357
  - Keep address restrictions visible in tests when those restrictions are part of the safety story.
11355
11358
 
11356
11359
  #### When to move beyond the default harness
@@ -712,13 +712,15 @@ const PRODUCTION_HELP_PAGES = [
712
712
  usage: [
713
713
  "devflare productions [--config <path>] [--env <name>] [--account <id>] [--worker <name>]",
714
714
  "devflare productions versions [--config <path>] [--env <name>] [--account <id>] [--worker <name>]",
715
+ "devflare productions deployments [--config <path>] [--env <name>] [--account <id>] [--worker <name>]",
715
716
  "devflare productions rollback [--config <path>] [--account <id>] [--worker <name>] [--version-id <id>] [--message <text>] [--apply]",
716
717
  "devflare productions delete [--config <path>] [--account <id>] [--worker <name>] [--apply]"
717
718
  ],
718
- description: ["The default view inspects live Cloudflare production deployment state for locally configured Workers, or for one explicitly selected Worker.", "Other subcommands list recent production versions or mutate a single Worker by rolling back or deleting its live production script."],
719
+ description: ["The default view inspects live Cloudflare production deployment state for locally configured Workers, or for one explicitly selected Worker.", "Other subcommands list recent production versions, show the full deployment history, or mutate a single Worker by rolling back or deleting its live production script."],
719
720
  subcommands: [
720
721
  entry("list", "List live production Workers and their active deployments (default)"),
721
722
  entry("versions", "Show recent stored production versions and which one is currently active"),
723
+ entry("deployments", "Show the full chronological production deployment history with traffic splits"),
722
724
  entry("rollback", "Roll a Worker back to the previous or specified production version"),
723
725
  entry("delete", "Delete a live production Worker script")
724
726
  ],
@@ -757,6 +759,12 @@ const PRODUCTION_HELP_PAGES = [
757
759
  entry("--account <id>", "Use a specific Cloudflare account"),
758
760
  entry("--worker <name>", "Target a specific Worker instead of the locally configured set")
759
761
  ], [entry("devflare productions versions", "Show recent stored production versions for the resolved Workers"), entry("devflare productions versions --worker my-worker", "Show recent stored production versions for `my-worker`")]),
762
+ createProductionsSubcommandPage("deployments", "Show the full chronological production deployment history", ["devflare productions deployments [--config <path>] [--env <name>] [--account <id>] [--worker <name>]"], ["Lists every production deployment for the selected Worker set in reverse-chronological order, including each deployment id, strategy, per-version traffic split, source, who triggered it, and the deployment message."], [
763
+ entry("--config <path>", "Use a specific devflare config file"),
764
+ entry("--env <name>", "Resolve `config.env[name]` while discovering related production Workers (defaults to `production`)"),
765
+ entry("--account <id>", "Use a specific Cloudflare account"),
766
+ entry("--worker <name>", "Target a specific Worker instead of the locally configured set")
767
+ ], [entry("devflare productions deployments", "Show the full production deployment history for the resolved Workers"), entry("devflare productions deployments --worker my-worker", "Show the full production deployment history for `my-worker`")], ["This view is read-only and is backed by live Cloudflare deployment history."]),
760
768
  createProductionsSubcommandPage("rollback", "Roll a Worker back to the previous or specified production version", ["devflare productions rollback [--config <path>] [--account <id>] [--worker <name>] [--version-id <id>] [--message <text>] [--apply]"], ["Uses Wrangler rollback to create a fresh production deployment that points at the previous or explicitly selected version."], [
761
769
  entry("--config <path>", "Use a specific devflare config file"),
762
770
  entry("--account <id>", "Use a specific Cloudflare account"),
@@ -1132,7 +1140,7 @@ async function runPreviews(parsed, logger, options) {
1132
1140
  return runPreviewsCommand(parsed, logger, options);
1133
1141
  }
1134
1142
  async function runProductions(parsed, logger, options) {
1135
- const { runProductionsCommand } = await import("./productions-DK7xwbEs.js");
1143
+ const { runProductionsCommand } = await import("./productions-DXQ045cq.js");
1136
1144
  return runProductionsCommand(parsed, logger, options);
1137
1145
  }
1138
1146
  async function runWorker(parsed, logger, options) {
@@ -10,6 +10,7 @@ const CLI_API_OPTIONS = { timeout: 1e4 };
10
10
  const PRODUCTION_SUBCOMMANDS = [
11
11
  "list",
12
12
  "versions",
13
+ "deployments",
13
14
  "rollback",
14
15
  "delete"
15
16
  ];
@@ -23,6 +24,10 @@ function shortenVersionId(versionId, length = 12) {
23
24
  function selectDeploymentVersionId(deployment) {
24
25
  return deployment.versions.find((version) => version.percentage === 100)?.versionId ?? deployment.versions[0]?.versionId;
25
26
  }
27
+ function formatDeploymentSplit(deployment) {
28
+ if (deployment.versions.length === 0) return "N/A";
29
+ return deployment.versions.map((version) => `${version.percentage}% → ${shortenVersionId(version.versionId, 8)}`).join(", ");
30
+ }
26
31
  function getWorkerVersionTimestamp(version) {
27
32
  return version.metadata.modifiedOn ?? version.metadata.createdOn;
28
33
  }
@@ -282,7 +287,7 @@ function showProductionOverview(logger, context, rows, theme) {
282
287
  titleAccent: "green"
283
288
  });
284
289
  logLine(logger);
285
- logLine(logger, dim("Use `devflare productions versions` for recent production versions, or `rollback` / `delete` to mutate one Worker.", theme));
290
+ logLine(logger, dim("Use `devflare productions versions` for recent production versions, `deployments` for the full deployment history, or `rollback` / `delete` to mutate one Worker.", theme));
286
291
  logLine(logger);
287
292
  }
288
293
  function showWorkerVersions(logger, overviews, theme) {
@@ -309,6 +314,82 @@ function showWorkerVersions(logger, overviews, theme) {
309
314
  }
310
315
  logLine(logger);
311
316
  }
317
+ function buildDeploymentColumns(theme) {
318
+ return [
319
+ {
320
+ label: "Deployed",
321
+ width: 19,
322
+ value: (row) => whiteDim(formatRecordDate(row.createdOn), theme)
323
+ },
324
+ {
325
+ label: "Deployment",
326
+ width: 12,
327
+ value: (row) => shortenVersionId(row.deploymentId, 11)
328
+ },
329
+ {
330
+ label: "Strategy",
331
+ width: 12,
332
+ value: (row) => row.strategy || dim("N/A", theme)
333
+ },
334
+ {
335
+ label: "Traffic split",
336
+ width: 28,
337
+ value: (row) => row.split
338
+ },
339
+ {
340
+ label: "Source",
341
+ width: 12,
342
+ value: (row) => row.source || dim("N/A", theme)
343
+ },
344
+ {
345
+ label: "Triggered by",
346
+ width: 18,
347
+ value: (row) => row.triggeredBy ? whiteDim(row.triggeredBy, theme) : dim("N/A", theme)
348
+ },
349
+ {
350
+ label: "Message",
351
+ value: (row) => row.message ? row.message : dim("N/A", theme)
352
+ }
353
+ ];
354
+ }
355
+ async function loadWorkerDeploymentHistory(accountId, workerName, apiOptions) {
356
+ return {
357
+ workerName,
358
+ rows: [...await account.workerDeployments(accountId, workerName, apiOptions)].sort((left, right) => right.createdOn.getTime() - left.createdOn.getTime()).map((deployment) => ({
359
+ createdOn: deployment.createdOn,
360
+ deploymentId: deployment.id,
361
+ strategy: deployment.strategy,
362
+ split: formatDeploymentSplit(deployment),
363
+ source: deployment.source,
364
+ message: deployment.message,
365
+ triggeredBy: deployment.triggeredBy ?? deployment.authorEmail
366
+ }))
367
+ };
368
+ }
369
+ function showWorkerDeployments(logger, overviews, theme) {
370
+ logLine(logger);
371
+ if (overviews.length === 0) {
372
+ logLine(logger, dim("No production deployments were found for the current selection.", theme));
373
+ logLine(logger);
374
+ return;
375
+ }
376
+ for (const [index, overview] of overviews.entries()) {
377
+ if (index > 0) logLine(logger);
378
+ logLine(logger, `${bold("worker", theme)} ${green(overview.workerName, theme)}`);
379
+ if (overview.rows.length === 0) {
380
+ logLine(logger, dim("No production deployments were found for this Worker.", theme));
381
+ continue;
382
+ }
383
+ logTable(logger, {
384
+ title: "Deployments",
385
+ rows: overview.rows,
386
+ columns: buildDeploymentColumns(theme),
387
+ theme,
388
+ titleAccent: "cyan"
389
+ });
390
+ }
391
+ logLine(logger);
392
+ }
312
393
  async function runRollback(context, parsed, options, logger, theme) {
313
394
  if (!context.workerName) {
314
395
  logger.error("A worker name is required for production rollback.");
@@ -357,6 +438,9 @@ async function runDelete(context, parsed, logger) {
357
438
  logger.warn("Devflare deleted the Worker script only. Review any shared account resources separately before cleaning them up.");
358
439
  return { exitCode: 0 };
359
440
  }
441
+ function resolveSelectedWorkerNames(context, selectedFamilies) {
442
+ return Array.from(new Set(context.workerName ? [context.workerName] : selectedFamilies.map((family) => family.baseName))).sort((left, right) => left.localeCompare(right));
443
+ }
360
444
  async function runProductionsCommand(parsed, logger, options) {
361
445
  if (!await account.isAuthenticated()) {
362
446
  logger.error("Not authenticated with Cloudflare");
@@ -381,7 +465,7 @@ async function runProductionsCommand(parsed, logger, options) {
381
465
  }] : [];
382
466
  switch (subcommand) {
383
467
  case "versions": {
384
- const workerNames = Array.from(new Set(context.workerName ? [context.workerName] : selectedFamilies.map((family) => family.baseName))).sort((left, right) => left.localeCompare(right));
468
+ const workerNames = resolveSelectedWorkerNames(context, selectedFamilies);
385
469
  if (workerNames.length === 0) {
386
470
  logger.error("No production Workers could be resolved. Use --worker or run inside a configured package.");
387
471
  return { exitCode: 1 };
@@ -389,6 +473,15 @@ async function runProductionsCommand(parsed, logger, options) {
389
473
  showWorkerVersions(logger, await Promise.all(workerNames.map((workerName) => loadWorkerVersionOverview(context.accountId, workerName, CLI_API_OPTIONS))), theme);
390
474
  return { exitCode: 0 };
391
475
  }
476
+ case "deployments": {
477
+ const workerNames = resolveSelectedWorkerNames(context, selectedFamilies);
478
+ if (workerNames.length === 0) {
479
+ logger.error("No production Workers could be resolved. Use --worker or run inside a configured package.");
480
+ return { exitCode: 1 };
481
+ }
482
+ showWorkerDeployments(logger, await Promise.all(workerNames.map((workerName) => loadWorkerDeploymentHistory(context.accountId, workerName, CLI_API_OPTIONS))), theme);
483
+ return { exitCode: 0 };
484
+ }
392
485
  case "rollback": return runRollback(context, parsed, options, logger, theme);
393
486
  case "delete": return runDelete(context, parsed, logger);
394
487
  default:
@@ -1 +1 @@
1
- {"version":3,"file":"productions.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/productions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAgB9C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAipBjE,wBAAsB,qBAAqB,CAC1C,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,UAAU,GACjB,OAAO,CAAC,SAAS,CAAC,CAyFpB"}
1
+ {"version":3,"file":"productions.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/productions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAgB9C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAwxBjE,wBAAsB,qBAAqB,CAC1C,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,UAAU,GACjB,OAAO,CAAC,SAAS,CAAC,CAoGpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"productions.d.ts","sourceRoot":"","sources":["../../../../src/cli/help-pages/pages/productions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,eAAO,MAAM,qBAAqB,EAAE,QAAQ,EA0L3C,CAAA"}
1
+ {"version":3,"file":"productions.d.ts","sourceRoot":"","sources":["../../../../src/cli/help-pages/pages/productions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,eAAO,MAAM,qBAAqB,EAAE,QAAQ,EA6N3C,CAAA"}
package/dist/cli/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as runCli, t as parseArgs } from "../_chunks/cli-CU7wSG1-.js";
1
+ import { n as runCli, t as parseArgs } from "../_chunks/cli-CqIDn-Hy.js";
2
2
  export { parseArgs, runCli };
package/dist/index.js CHANGED
@@ -4,6 +4,6 @@ import { a as ConfigResourceResolutionError, d as configSchema, n as ConfigValid
4
4
  import { n as compileConfig, s as stringifyConfig } from "./_chunks/compiler-BtLp9T9N.js";
5
5
  import { t as workerName } from "./_chunks/workerName-CFJsLZA-.js";
6
6
  import { n as getDurableObjectOptions, t as durableObject } from "./_chunks/decorators-QmV57ixr.js";
7
- import { n as runCli, t as parseArgs } from "./_chunks/cli-CU7wSG1-.js";
7
+ import { n as runCli, t as parseArgs } from "./_chunks/cli-CqIDn-Hy.js";
8
8
  import { i as vars, r as env } from "./_chunks/env-S0_nMVz1.js";
9
9
  export { ConfigNotFoundError, ConfigResourceResolutionError, ConfigValidationError, compileConfig, configSchema, defineConfig as default, defineConfig, durableObject, env, getDurableObjectOptions, loadConfig, loadResolvedConfig, parseArgs, preview, ref, runCli, stringifyConfig, vars, workerName };
@@ -17,5 +17,5 @@ export { shouldSkip } from './should-skip.js';
17
17
  export { containers, createContainerManager, detectContainerEngine, getContainerSkipReason, stopActiveContainers, type ContainerCommandResult, type ContainerCommandRunner, type ContainerEngineCheck, type ContainerEngineName, type ContainerEnginePreference, type ContainerEngineStatus, type ContainerManager, type ContainerManagerOptions, type DevflareContainerInstance, type LocalContainerState, type StartContainerOptions } from './containers.js';
18
18
  export { createOfflineBindings, createOfflineEnv, describeOfflineSupport, getOfflineSupportMatrix, type OfflineBindingFixtures, type OfflineBindingsResult, type OfflineMissingFixture, type OfflineRemoteBoundary, type OfflineSupportEntry, type OfflineSupportTier } from './offline-bindings.js';
19
19
  export { createMockAISearchInstance, createMockAISearchNamespace, type MockAISearchInstance, type MockAISearchInstanceOptions, type MockAISearchItemFixture, type MockAISearchNamespace, type MockAISearchNamespaceOptions } from './ai-search.js';
20
- export { createMockTestContext, createMockKV, createMockD1, createMockR2, createMockQueue, createMockRateLimit, createMockVersionMetadata, createMockHyperdrive, createMockWorkerLoader, createMockMTLSCertificate, createMockDispatchNamespace, createMockWorkflow, createMockPipeline, createMockImagesBinding, createMockMediaBinding, createMockStreamBinding, createMockFlagshipBinding, createMockArtifacts, createMockVectorize, createMockAnalyticsEngine, createMockSecretsStoreSecret, createMockEnv, withTestContext, type TestContext, type TestContextOptions, type MockEnvOptions, type MockRateLimitOptions, type MockWorkerLoaderOptions, type MockFetchInput, type MockFetcherHandler, type MockDispatchNamespaceOptions, type MockWorkflowOptions, type MockWorkflowInstanceOptions, type MockPipeline, type MockImagesBindingOptions, type MockMediaBindingOptions, type MockStreamBindingOptions, type MockFlagshipBindingOptions, type MockArtifactsOptions, type MockVectorizeOptions, type MockVectorizeIndex, type MockAnalyticsEngineDataset } from './utilities.js';
20
+ export { createMockTestContext, createMockKV, createMockD1, createMockR2, createMockQueue, createMockRateLimit, createMockVersionMetadata, createMockHyperdrive, createMockWorkerLoader, createMockMTLSCertificate, createMockDispatchNamespace, createMockWorkflow, createMockPipeline, createMockImagesBinding, createMockMediaBinding, createMockStreamBinding, createMockFlagshipBinding, createMockArtifacts, createMockVectorize, createMockAnalyticsEngine, createMockSendEmail, createLocalSendEmailBinding, createMockSecretsStoreSecret, createMockEnv, withTestContext, type TestContext, type TestContextOptions, type MockEnvOptions, type MockRateLimitOptions, type MockWorkerLoaderOptions, type MockFetchInput, type MockFetcherHandler, type MockDispatchNamespaceOptions, type MockWorkflowOptions, type MockWorkflowInstanceOptions, type MockPipeline, type MockImagesBindingOptions, type MockMediaBindingOptions, type MockStreamBindingOptions, type MockFlagshipBindingOptions, type MockArtifactsOptions, type MockVectorizeOptions, type MockVectorizeIndex, type MockAnalyticsEngineDataset, type MockSendEmailBinding, type LocalSendEmailBindingConfig } from './utilities.js';
21
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"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"}
@@ -1437,6 +1437,42 @@ function createMockAnalyticsEngine() {
1437
1437
  };
1438
1438
  }
1439
1439
  //#endregion
1440
+ //#region src/test/utilities/send-email.ts
1441
+ /**
1442
+ * Creates a recording SendEmail binding for pure unit tests.
1443
+ *
1444
+ * Delegates `from`/destination allow-list enforcement to
1445
+ * {@link createLocalSendEmailBinding} (so disallowed senders/recipients throw
1446
+ * exactly as they would locally), and records each accepted message.
1447
+ *
1448
+ * @example
1449
+ * ```ts
1450
+ * const email = createMockSendEmail()
1451
+ * await email.send(new EmailMessage('from@a.com', 'to@b.com', '...'))
1452
+ * expect(email.sentEmails).toHaveLength(1)
1453
+ * ```
1454
+ */
1455
+ function createMockSendEmail(config = {}) {
1456
+ const sentEmails = [];
1457
+ const binding = createLocalSendEmailBinding(config, { onSend(message) {
1458
+ sentEmails.push(message);
1459
+ } });
1460
+ return {
1461
+ send(message) {
1462
+ return binding.send(message);
1463
+ },
1464
+ get sentEmails() {
1465
+ return sentEmails;
1466
+ },
1467
+ get messages() {
1468
+ return sentEmails;
1469
+ },
1470
+ clear() {
1471
+ sentEmails.length = 0;
1472
+ }
1473
+ };
1474
+ }
1475
+ //#endregion
1440
1476
  //#region src/test/utilities/env.ts
1441
1477
  function isVectorizeBinding$1(value) {
1442
1478
  return typeof value.query === "function";
@@ -1451,6 +1487,16 @@ function addVectorizeMockBindings(env, vectorize) {
1451
1487
  function addAnalyticsEngineMockBindings(env, analyticsEngine) {
1452
1488
  for (const name of analyticsEngine ?? []) env[name] = createMockAnalyticsEngine();
1453
1489
  }
1490
+ function isSendEmailBinding$1(value) {
1491
+ return typeof value.send === "function";
1492
+ }
1493
+ function addSendEmailMockBindings(env, sendEmail) {
1494
+ if (Array.isArray(sendEmail)) {
1495
+ for (const name of sendEmail) env[name] = createMockSendEmail();
1496
+ return;
1497
+ }
1498
+ if (sendEmail) for (const [name, value] of Object.entries(sendEmail)) env[name] = isSendEmailBinding$1(value) ? value : createMockSendEmail(value);
1499
+ }
1454
1500
  /**
1455
1501
  * Creates a complete mock environment with specified bindings
1456
1502
  *
@@ -1490,6 +1536,7 @@ function createMockEnv(options = {}) {
1490
1536
  else if (options.artifacts) for (const [name, artifactsOptions] of Object.entries(options.artifacts)) env[name] = isArtifactsBinding$1(artifactsOptions) ? artifactsOptions : createMockArtifacts(artifactsOptions);
1491
1537
  addVectorizeMockBindings(env, options.vectorize);
1492
1538
  addAnalyticsEngineMockBindings(env, options.analyticsEngine);
1539
+ addSendEmailMockBindings(env, options.sendEmail);
1493
1540
  if (options.secretsStore) for (const [name, value] of Object.entries(options.secretsStore)) env[name] = createMockSecretsStoreSecret(value);
1494
1541
  if (options.vars) Object.assign(env, options.vars);
1495
1542
  if (options.secrets) Object.assign(env, options.secrets);
@@ -4666,6 +4713,12 @@ const SUPPORT_MATRIX = {
4666
4713
  reason: "Analytics Engine writes are recordable in-memory (createMockAnalyticsEngine), but there is no in-worker read API — querying is the hosted SQL API/dashboard.",
4667
4714
  recommendation: "Use createMockAnalyticsEngine() to assert recorded writeDataPoint() calls; query analytics through the hosted SQL API/dashboard."
4668
4715
  },
4716
+ sendEmail: {
4717
+ service: "sendEmail",
4718
+ tier: "offline-native",
4719
+ reason: "SendEmail is send-only (Email Routing has no in-worker read API), so Devflare provides a complete pure mock: send() enforces the configured sender/destination allow-lists locally and records dispatched mail. Real delivery is Cloudflare Email Routing.",
4720
+ recommendation: "Use createOfflineEnv() or createMockSendEmail() to assert dispatched mail in pure tests; use Cloudflare Email Routing for real delivery."
4721
+ },
4669
4722
  builds: {
4670
4723
  service: "builds",
4671
4724
  tier: "remote-boundary",
@@ -4736,6 +4789,9 @@ function isAISearchInstance(value) {
4736
4789
  function isVectorizeBinding(value) {
4737
4790
  return typeof value?.query === "function";
4738
4791
  }
4792
+ function isSendEmailBinding(value) {
4793
+ return typeof value?.send === "function";
4794
+ }
4739
4795
  function isAISearchNamespace(value) {
4740
4796
  return typeof value?.get === "function";
4741
4797
  }
@@ -4774,6 +4830,12 @@ function addVectorizeBindings(env, bindings, fixtures) {
4774
4830
  function addAnalyticsEngineBindings(env, bindings, fixtures) {
4775
4831
  for (const name of Object.keys(bindings?.analyticsEngine ?? {})) env[name] = fixtures.analyticsEngine?.[name] ?? createMockAnalyticsEngine();
4776
4832
  }
4833
+ function addSendEmailBindings(env, bindings, fixtures) {
4834
+ for (const [name, binding] of Object.entries(bindings?.sendEmail ?? {})) {
4835
+ const fixture = fixtures.sendEmail?.[name];
4836
+ env[name] = isSendEmailBinding(fixture) ? fixture : createMockSendEmail(fixture ?? binding);
4837
+ }
4838
+ }
4777
4839
  function addVersionMetadataBinding(env, bindings) {
4778
4840
  if (bindings?.versionMetadata) env[bindings.versionMetadata.binding] = createMockVersionMetadata();
4779
4841
  }
@@ -4921,7 +4983,8 @@ function addRemoteBoundaries(remoteBoundaries, bindings) {
4921
4983
  * entry overrides the auto-mock), alongside rate limits, version metadata,
4922
4984
  * hyperdrive, worker loaders, mTLS, dispatch namespaces, workflows, pipelines,
4923
4985
  * images, media, stream, flagship, artifacts, secrets store, AI Search,
4924
- * Vectorize, and Analytics Engine. It does **not** create the wiring bindings
4986
+ * Vectorize, Analytics Engine, and SendEmail. It does **not** create the wiring
4987
+ * bindings
4925
4988
  * (`durableObjects`, `services`) — those require a real Miniflare runtime, which
4926
4989
  * only `createTestContext()` provides. When such a binding is present in config
4927
4990
  * it is reported in the returned `missingFixtures` (its `env` entry is left
@@ -4940,6 +5003,7 @@ function createOfflineBindings(config, fixtures = {}, options = {}) {
4940
5003
  addQueueBindings(env, bindings, fixtures);
4941
5004
  addVectorizeBindings(env, bindings, fixtures);
4942
5005
  addAnalyticsEngineBindings(env, bindings, fixtures);
5006
+ addSendEmailBindings(env, bindings, fixtures);
4943
5007
  addRateLimitBindings(env, bindings);
4944
5008
  addVersionMetadataBinding(env, bindings);
4945
5009
  addHyperdriveBindings(env, bindings, fixtures, missingFixtures);
@@ -4979,4 +5043,4 @@ function createOfflineEnv(config, fixtures = {}, options = {}) {
4979
5043
  return createOfflineBindings(config, fixtures, options).env;
4980
5044
  }
4981
5045
  //#endregion
4982
- export { alarm, cf, clearBundleCache, containers, createContainerManager, createMockAISearchInstance, createMockAISearchNamespace, createMockAnalyticsEngine, createMockArtifacts, createMockD1, createMockDispatchNamespace, createMockEnv, createMockFlagshipBinding, createMockHyperdrive, createMockImagesBinding, createMockKV, createMockMTLSCertificate, createMockMediaBinding, createMockPipeline, createMockQueue, createMockR2, createMockRateLimit, createMockSecretsStoreSecret, createMockStreamBinding, createMockTestContext, createMockVectorize, createMockVersionMetadata, createMockWorkerLoader, createMockWorkflow, createOfflineBindings, createOfflineEnv, createTestContext, describeOfflineSupport, detectContainerEngine, email, env, getContainerSkipReason, getOfflineSupportMatrix, hasCrossWorkerDOs, hasServiceBindings, queue, resolveDOBindings, resolveServiceBindings, scheduled, shouldSkip, stopActiveContainers, tail, withTestContext, worker };
5046
+ export { alarm, cf, clearBundleCache, containers, createContainerManager, createLocalSendEmailBinding, createMockAISearchInstance, createMockAISearchNamespace, createMockAnalyticsEngine, createMockArtifacts, createMockD1, createMockDispatchNamespace, createMockEnv, createMockFlagshipBinding, createMockHyperdrive, createMockImagesBinding, createMockKV, createMockMTLSCertificate, createMockMediaBinding, createMockPipeline, createMockQueue, createMockR2, createMockRateLimit, createMockSecretsStoreSecret, createMockSendEmail, createMockStreamBinding, createMockTestContext, createMockVectorize, createMockVersionMetadata, createMockWorkerLoader, createMockWorkflow, createOfflineBindings, createOfflineEnv, createTestContext, describeOfflineSupport, detectContainerEngine, email, env, getContainerSkipReason, getOfflineSupportMatrix, hasCrossWorkerDOs, hasServiceBindings, queue, resolveDOBindings, resolveServiceBindings, scheduled, shouldSkip, stopActiveContainers, tail, withTestContext, worker };
@@ -1,5 +1,6 @@
1
1
  import type { Pipeline } from 'cloudflare:pipelines';
2
2
  import { type DevflareConfig } from '../config/index.js';
3
+ import type { LocalSendEmailBindingConfig } from '../utils/send-email.js';
3
4
  import { type MockAISearchInstanceOptions, type MockAISearchNamespaceOptions } from './ai-search.js';
4
5
  import { type MockArtifactsOptions, type MockDispatchNamespaceOptions, type MockFetcherHandler, type MockFlagshipBindingOptions, type MockImagesBindingOptions, type MockMediaBindingOptions, type MockStreamBindingOptions, type MockVectorizeOptions, type MockWorkerLoaderOptions, type MockWorkflowOptions } from './utilities.js';
5
6
  export type OfflineSupportTier = 'offline-native' | 'offline-fixture' | 'remote-boundary';
@@ -51,6 +52,13 @@ export interface OfflineBindingFixtures {
51
52
  * without an entry here is auto-created with `createMockAnalyticsEngine()`.
52
53
  */
53
54
  analyticsEngine?: Record<string, AnalyticsEngineDataset>;
55
+ /**
56
+ * SendEmail bindings. Any sendEmail binding in config without an entry here
57
+ * is auto-created with `createMockSendEmail()` using the binding's configured
58
+ * sender/destination allow-lists, so dispatched mail is recorded for
59
+ * assertions. Provide a `SendEmail` to inject a fully custom fake.
60
+ */
61
+ sendEmail?: Record<string, SendEmail | LocalSendEmailBindingConfig>;
54
62
  secretsStore?: Record<string, string>;
55
63
  workerLoaders?: Record<string, MockWorkerLoaderOptions>;
56
64
  mtlsCertificates?: Record<string, MockFetcherHandler>;
@@ -94,7 +102,8 @@ export declare function describeOfflineSupport(service: string): OfflineSupportE
94
102
  * entry overrides the auto-mock), alongside rate limits, version metadata,
95
103
  * hyperdrive, worker loaders, mTLS, dispatch namespaces, workflows, pipelines,
96
104
  * images, media, stream, flagship, artifacts, secrets store, AI Search,
97
- * Vectorize, and Analytics Engine. It does **not** create the wiring bindings
105
+ * Vectorize, Analytics Engine, and SendEmail. It does **not** create the wiring
106
+ * bindings
98
107
  * (`durableObjects`, `services`) — those require a real Miniflare runtime, which
99
108
  * only `createTestContext()` provides. When such a binding is present in config
100
109
  * it is reported in the returned `missingFixtures` (its `env` entry is left
@@ -1 +1 @@
1
- {"version":3,"file":"offline-bindings.d.ts","sourceRoot":"","sources":["../../src/test/offline-bindings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"offline-bindings.d.ts","sourceRoot":"","sources":["../../src/test/offline-bindings.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import type { Pipeline } from 'cloudflare:pipelines';
2
+ import type { LocalSendEmailBindingConfig } from '../../utils/send-email.js';
2
3
  import { type MockArtifactsOptions } from './artifacts.js';
3
4
  import { type MockDispatchNamespaceOptions, type MockFetcherHandler, type MockRateLimitOptions, type MockWorkerLoaderOptions } from './platform.js';
4
5
  import { type MockVectorizeOptions } from './vectorize.js';
@@ -21,6 +22,7 @@ export interface MockEnvOptions {
21
22
  artifacts?: string[] | Record<string, MockArtifactsOptions | Artifacts>;
22
23
  vectorize?: string[] | Record<string, MockVectorizeOptions | VectorizeIndex>;
23
24
  analyticsEngine?: string[];
25
+ sendEmail?: string[] | Record<string, SendEmail | LocalSendEmailBindingConfig>;
24
26
  secretsStore?: Record<string, string>;
25
27
  durableObjects?: string[];
26
28
  vars?: Record<string, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/test/utilities/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAEpD,OAAO,EAAE,KAAK,oBAAoB,EAA2C,MAAM,aAAa,CAAA;AAIhG,OAAO,EACN,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAQ5B,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,KAAK,oBAAoB,EAAuB,MAAM,aAAa,CAAA;AAC5E,OAAO,EAAE,KAAK,mBAAmB,EAA0C,MAAM,aAAa,CAAA;AAE9F,MAAM,WAAW,cAAc;IAC9B,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;IACjD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;IAChD,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;IAClE,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IAChE,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAA;IAC5E,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,QAAQ,CAAC,CAAA;IACrE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/C,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,CAAC,CAAA;IACvE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,CAAC,CAAA;IAC5E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAoCD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkKnF"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/test/utilities/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAA;AAEzE,OAAO,EAAE,KAAK,oBAAoB,EAA2C,MAAM,aAAa,CAAA;AAIhG,OAAO,EACN,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAQ5B,MAAM,YAAY,CAAA;AAInB,OAAO,EAAE,KAAK,oBAAoB,EAAuB,MAAM,aAAa,CAAA;AAC5E,OAAO,EAAE,KAAK,mBAAmB,EAA0C,MAAM,aAAa,CAAA;AAE9F,MAAM,WAAW,cAAc;IAC9B,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;IACjD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;IAChD,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;IAClE,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IAChE,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAA;IAC5E,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,QAAQ,CAAC,CAAA;IACrE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/C,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,CAAC,CAAA;IACvE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,CAAC,CAAA;IAC5E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,2BAA2B,CAAC,CAAA;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAyDD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqKnF"}
@@ -0,0 +1,27 @@
1
+ import { type LocalSendEmailBindingConfig, createLocalSendEmailBinding } from '../../utils/send-email.js';
2
+ export { createLocalSendEmailBinding, type LocalSendEmailBindingConfig };
3
+ type SentEmail = Parameters<SendEmail['send']>[0];
4
+ export type MockSendEmailBinding = SendEmail & {
5
+ /** Every message accepted by `send()`, in call order. */
6
+ readonly sentEmails: SentEmail[];
7
+ /** Alias for {@link sentEmails} (parity with other record-only mocks). */
8
+ readonly messages: SentEmail[];
9
+ /** Clear the recorded messages. */
10
+ clear(): void;
11
+ };
12
+ /**
13
+ * Creates a recording SendEmail binding for pure unit tests.
14
+ *
15
+ * Delegates `from`/destination allow-list enforcement to
16
+ * {@link createLocalSendEmailBinding} (so disallowed senders/recipients throw
17
+ * exactly as they would locally), and records each accepted message.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const email = createMockSendEmail()
22
+ * await email.send(new EmailMessage('from@a.com', 'to@b.com', '...'))
23
+ * expect(email.sentEmails).toHaveLength(1)
24
+ * ```
25
+ */
26
+ export declare function createMockSendEmail(config?: LocalSendEmailBindingConfig): MockSendEmailBinding;
27
+ //# sourceMappingURL=send-email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send-email.d.ts","sourceRoot":"","sources":["../../../src/test/utilities/send-email.ts"],"names":[],"mappings":"AAcA,OAAO,EACN,KAAK,2BAA2B,EAChC,2BAA2B,EAC3B,MAAM,wBAAwB,CAAA;AAI/B,OAAO,EAAE,2BAA2B,EAAE,KAAK,2BAA2B,EAAE,CAAA;AAExE,KAAK,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAEjD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG;IAC9C,yDAAyD;IACzD,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAA;IAC9B,mCAAmC;IACnC,KAAK,IAAI,IAAI,CAAA;CACb,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAClC,MAAM,GAAE,2BAAgC,GACtC,oBAAoB,CAuBtB"}
@@ -10,5 +10,6 @@ export * from './utilities/stream-flagship.js';
10
10
  export * from './utilities/artifacts.js';
11
11
  export * from './utilities/vectorize.js';
12
12
  export * from './utilities/analytics-engine.js';
13
+ export * from './utilities/send-email.js';
13
14
  export * from './utilities/env.js';
14
15
  //# sourceMappingURL=utilities.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../../src/test/utilities.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../../src/test/utilities.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,wBAAwB,CAAA;AACtC,cAAc,iBAAiB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devflare",
3
- "version": "1.0.0-next.51",
3
+ "version": "1.0.0-next.53",
4
4
  "description": "Devflare is a developer-first toolkit for Cloudflare Workers that sits on top of Miniflare and Wrangler-compatible config",
5
5
  "repository": {
6
6
  "type": "git",