devflare 1.0.0-next.10 → 1.0.0-next.12

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.
Files changed (45) hide show
  1. package/LLM.md +683 -13
  2. package/README.md +33 -5
  3. package/dist/{build-k36xrzvy.js → build-rfh8cgh3.js} +40 -11
  4. package/dist/bundler/index.d.ts +1 -0
  5. package/dist/bundler/index.d.ts.map +1 -1
  6. package/dist/bundler/worker-bundler.d.ts +14 -0
  7. package/dist/bundler/worker-bundler.d.ts.map +1 -0
  8. package/dist/cli/commands/build.d.ts.map +1 -1
  9. package/dist/cli/commands/deploy.d.ts.map +1 -1
  10. package/dist/cli/commands/dev.d.ts.map +1 -1
  11. package/dist/config/compiler.d.ts.map +1 -1
  12. package/dist/config/index.d.ts +1 -0
  13. package/dist/config/index.d.ts.map +1 -1
  14. package/dist/config/resolve.d.ts +3 -0
  15. package/dist/config/resolve.d.ts.map +1 -0
  16. package/dist/config/schema.d.ts +37 -31
  17. package/dist/config/schema.d.ts.map +1 -1
  18. package/dist/{deploy-dbvfq8vq.js → deploy-k0fcgt3d.js} +40 -11
  19. package/dist/{dev-rk8p6pse.js → dev-d4wabqyf.js} +73 -470
  20. package/dist/dev-server/server.d.ts.map +1 -1
  21. package/dist/{doctor-06y8nxd4.js → doctor-z4ffybce.js} +2 -2
  22. package/dist/{index-jht2j546.js → index-0kzg8wed.js} +26 -6
  23. package/dist/index-1xqeptt2.js +623 -0
  24. package/dist/{index-pwgyy2q9.js → index-dr6sbp8d.js} +1 -1
  25. package/dist/{index-6v3wjg1r.js → index-rfhx0yd5.js} +11 -7
  26. package/dist/{index-05fyzwne.js → index-twpgq9k9.js} +5 -5
  27. package/dist/{index-1phx14av.js → index-wyf3s77s.js} +1 -1
  28. package/dist/{index-vs49yxn4.js → index-xxwbb2nt.js} +1 -1
  29. package/dist/index-zbvmtcn2.js +795 -0
  30. package/dist/src/browser.js +1 -1
  31. package/dist/src/cli/index.js +1 -1
  32. package/dist/src/index.js +12 -13
  33. package/dist/src/sveltekit/index.js +4 -5
  34. package/dist/src/test/index.js +6 -7
  35. package/dist/src/vite/index.js +19 -399
  36. package/dist/test/simple-context.d.ts.map +1 -1
  37. package/dist/{types-x9q7t491.js → types-sffr9681.js} +7 -8
  38. package/dist/vite/config-file.d.ts +25 -0
  39. package/dist/vite/config-file.d.ts.map +1 -0
  40. package/dist/vite/index.d.ts +1 -0
  41. package/dist/vite/index.d.ts.map +1 -1
  42. package/dist/worker-entry/composed-worker.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/index-k7r18na8.js +0 -0
  45. package/dist/index-ws68xvq2.js +0 -311
package/LLM.md CHANGED
@@ -64,7 +64,8 @@ The classic mixups are:
64
64
 
65
65
  - `files.routes` vs top-level `routes`
66
66
  - `vars` vs `secrets`
67
- - Bun `.env` loading vs runtime secret loading
67
+ - Bun or host `.env*` loading vs runtime secret loading
68
+ - config-time `.env*` files vs local runtime `.dev.vars*` files
68
69
  - Devflare `config.env` overrides vs Wrangler environment blocks
69
70
  - main-entry `env` vs runtime `env`
70
71
 
@@ -110,6 +111,11 @@ Core public capabilities today:
110
111
 
111
112
  Devflare is not a replacement runtime. It is a higher-level developer system that sits on top of the Cloudflare ecosystem.
112
113
 
114
+ The shortest truthful mental model is:
115
+
116
+ - **Vite** is the optional outer app/framework host. Devflare runs it when the current package has a local `vite.config.*` or a non-empty `config.vite`, and Devflare merges that config into the actual Vite config it executes.
117
+ - **Rolldown** is the inner builder Devflare uses when Devflare itself needs to transform Worker source into runnable Worker modules. Today that covers worker-only main-worker bundles and Durable Object bundles.
118
+
113
119
  ### Authoring model
114
120
 
115
121
  A **surface** is a distinct handler or entry file that Devflare treats as its own concern. The common surfaces are:
@@ -524,10 +530,236 @@ Secondary keys:
524
530
  | `assets` | static asset config |
525
531
  | `limits` | worker limits |
526
532
  | `wsRoutes` | local WebSocket proxy rules for dev |
527
- | `vite` | Devflare Vite metadata |
528
- | `rolldown` | worker-only bundler options |
533
+ | `vite` | inline Vite config for Devflare-run Vite flows |
534
+ | `rolldown` | Devflare-owned Worker bundler options |
529
535
  | `wrangler.passthrough` | raw Wrangler overrides after compile |
530
536
 
537
+ ### Complete config property reference
538
+
539
+ Treat this as the exhaustive property checklist for `defineConfig({...})`. The later sections explain behavior in narrative form; this section answers “what keys exist right now, what shape do they accept, and what do they control?”
540
+
541
+ #### Root properties
542
+
543
+ | Property | Shape | Required | Current behavior |
544
+ |---|---|---|---|
545
+ | `name` | `string` | yes | Worker name compiled to Wrangler `name`. It is also the base name used for generated auxiliary DO workers (`${name}-do`) when Devflare creates one. |
546
+ | `accountId` | `string` | no | Compiled to Wrangler `account_id`. Most relevant for deploy flows and remote-oriented bindings such as AI and Vectorize. Not currently supported inside `config.env`. |
547
+ | `compatibilityDate` | `string` (`YYYY-MM-DD`) | no | Compiled to Wrangler `compatibility_date`. Defaults to the current date when omitted. |
548
+ | `compatibilityFlags` | `string[]` | no | Additional Workers compatibility flags. Devflare also forces `nodejs_compat` and `nodejs_als`. |
549
+ | `files` | object | no | Explicit surface file paths and discovery globs. Use this when a surface matters to generated output. |
550
+ | `bindings` | object | no | Cloudflare binding declarations. These compile to Wrangler binding sections and also drive generated typing. |
551
+ | `triggers` | object | no | Scheduled trigger configuration such as cron expressions. |
552
+ | `vars` | `Record<string, string>` | no | Non-secret runtime bindings compiled into Wrangler `vars`. |
553
+ | `secrets` | `Record<string, { required?: boolean }>` | no | Secret declarations only. Values do not live here. |
554
+ | `routes` | `Array<{ pattern; zone_name?; zone_id?; custom_domain? }>` | no | Cloudflare deployment routes. This is separate from the built-in file router. |
555
+ | `wsRoutes` | `Array<{ pattern; doNamespace; idParam?; forwardPath? }>` | no | Dev-only WebSocket proxy rules that forward matching upgrade requests to Durable Objects. Not compiled into Wrangler config. |
556
+ | `assets` | `{ directory: string; binding?: string }` | no | Static asset directory config compiled into Wrangler `assets`. |
557
+ | `limits` | `{ cpu_ms?: number }` | no | Worker execution limits compiled into Wrangler `limits`. |
558
+ | `observability` | `{ enabled?: boolean; head_sampling_rate?: number }` | no | Workers observability and sampling config compiled into Wrangler `observability`. |
559
+ | `migrations` | `Migration[]` | no | Durable Object migration history compiled into Wrangler `migrations`. |
560
+ | `rolldown` | object | no | Rolldown builder configuration for Devflare's own code-transformation path across worker-only main-worker bundles and Durable Object bundles. This is not a replacement for the main Vite app build. |
561
+ | `vite` | object | no | Inline Vite config merged into the actual Vite config Devflare runs. A local `vite.config.*` remains optional and is merged first when present. |
562
+ | `env` | `Record<string, EnvOverride>` | no | Named environment overlays merged into the base config before compile/build/deploy. |
563
+ | `wrangler` | `{ passthrough?: Record<string, unknown> }` | no | Escape hatch for unsupported Wrangler keys, merged after native Devflare compile. |
564
+ | `build` | object | legacy | Deprecated alias normalized into `rolldown`. Keep accepting it for compatibility; teach `rolldown` in new docs. |
565
+ | `plugins` | `unknown[]` | legacy | Deprecated alias normalized into `vite.plugins`. Raw Vite plugin wiring still belongs in `vite.config.*`. |
566
+
567
+ #### `files`
568
+
569
+ `files` is where Devflare discovers or pins the files that make up your worker surfaces.
570
+
571
+ | Key | Shape | Default or convention | Meaning |
572
+ |---|---|---|---|
573
+ | `fetch` | `string \| false` | `src/fetch.ts` when present | Main HTTP entry. Keep this explicit when build or deploy output depends on it. |
574
+ | `queue` | `string \| false` | `src/queue.ts` when present | Queue consumer surface. |
575
+ | `scheduled` | `string \| false` | `src/scheduled.ts` when present | Scheduled/cron surface. |
576
+ | `email` | `string \| false` | `src/email.ts` when present | Inbound email surface. |
577
+ | `durableObjects` | `string \| false` | `**/do.*.{ts,js}` | Discovery glob for Durable Object classes. Respects `.gitignore`. |
578
+ | `entrypoints` | `string \| false` | `**/ep.*.{ts,js}` | Discovery glob for `WorkerEntrypoint` classes. Respects `.gitignore`. |
579
+ | `workflows` | `string \| false` | `**/wf.*.{ts,js}` | Discovery glob for workflow classes. Respects `.gitignore`. |
580
+ | `routes` | `{ dir: string; prefix?: string } \| false` | `src/routes` when that directory exists | Built-in route-tree config. `dir` changes the route root; `prefix` mounts it under a static pathname prefix such as `/api`; `false` disables route discovery. |
581
+ | `transport` | `string \| null` | `src/transport.{ts,js,mts,mjs}` when present | Custom serialization transport file. The file must export a named `transport` object. Set `null` to disable autodiscovery explicitly. |
582
+
583
+ Current `files` rules worth keeping explicit:
584
+
585
+ - `compileConfig()` only writes Wrangler `main` when `files.fetch` is explicit
586
+ - higher-level `build`, `deploy`, and `devflare/vite` flows may still generate a composed `.devflare/worker-entrypoints/main.ts` when multiple surfaces must be stitched together
587
+ - `wrangler.passthrough.main` opts out of that composed-entry generation path
588
+ - `createTestContext()` and local dev can still auto-discover conventional files even when you omit them from config
589
+
590
+ #### `bindings`
591
+
592
+ `bindings` groups Cloudflare service bindings by kind.
593
+
594
+ | Key | Shape | Meaning |
595
+ |---|---|---|
596
+ | `kv` | `Record<string, string>` | KV namespace binding name → namespace id |
597
+ | `d1` | `Record<string, string>` | D1 binding name → database id |
598
+ | `r2` | `Record<string, string>` | R2 binding name → bucket name |
599
+ | `durableObjects` | `Record<string, string \| { className: string; scriptName?: string }>` | Durable Object namespace binding. String form is shorthand for `{ className }`. Object form also covers cross-worker DOs and `ref()`-driven bindings. |
600
+ | `queues` | `{ producers?: Record<string, string>; consumers?: QueueConsumer[] }` | Queue producer bindings plus consumer settings |
601
+ | `services` | `Record<string, { service: string; environment?: string; entrypoint?: string }>` | Worker service bindings. `ref().worker` and `ref().worker('Entrypoint')` normalize here. |
602
+ | `ai` | `{ binding: string }` | Workers AI binding |
603
+ | `vectorize` | `Record<string, { indexName: string }>` | Vectorize index bindings |
604
+ | `hyperdrive` | `Record<string, { id: string }>` | Hyperdrive bindings |
605
+ | `browser` | `{ binding: string }` | Browser Rendering binding |
606
+ | `analyticsEngine` | `Record<string, { dataset: string }>` | Analytics Engine dataset bindings |
607
+ | `sendEmail` | `Record<string, { destinationAddress?: string; allowedDestinationAddresses?: string[]; allowedSenderAddresses?: string[] }>` | Outbound email bindings |
608
+
609
+ Queue consumer objects currently support:
610
+
611
+ | Field | Shape | Meaning |
612
+ |---|---|---|
613
+ | `queue` | `string` | Queue name to consume |
614
+ | `maxBatchSize` | `number` | Max messages per batch |
615
+ | `maxBatchTimeout` | `number` | Max seconds to wait for a batch |
616
+ | `maxRetries` | `number` | Max retry attempts |
617
+ | `deadLetterQueue` | `string` | Queue for permanently failed messages |
618
+ | `maxConcurrency` | `number` | Max concurrent batch invocations |
619
+ | `retryDelay` | `number` | Delay between retries in seconds |
620
+
621
+ Two `bindings` details that matter in practice:
622
+
623
+ - `bindings.sendEmail` must use either `destinationAddress` or `allowedDestinationAddresses`, not both
624
+ - `bindings.durableObjects.*.scriptName` is how you point a binding at another worker when the class does not live in the main worker bundle
625
+
626
+ #### `triggers`, `routes`, and `wsRoutes`
627
+
628
+ | Property | Shape | Current behavior |
629
+ |---|---|---|
630
+ | `triggers.crons` | `string[]` | Cloudflare cron expressions compiled into Wrangler `triggers.crons` |
631
+ | `routes[].pattern` | `string` | Deployment route pattern such as `example.com/*` |
632
+ | `routes[].zone_name` | `string` | Optional zone association |
633
+ | `routes[].zone_id` | `string` | Optional zone association alternative to `zone_name` |
634
+ | `routes[].custom_domain` | `boolean` | Mark route as a custom domain |
635
+ | `wsRoutes[].pattern` | `string` | Local URL pattern to intercept for WebSocket upgrades |
636
+ | `wsRoutes[].doNamespace` | `string` | Target Durable Object namespace binding name |
637
+ | `wsRoutes[].idParam` | `string` | Query parameter used to pick the DO instance. Defaults to `'id'`. |
638
+ | `wsRoutes[].forwardPath` | `string` | Path forwarded inside the DO. Defaults to `'/websocket'`. |
639
+
640
+ Remember the split:
641
+
642
+ - `files.routes` is app routing
643
+ - top-level `routes` is Cloudflare deployment routing
644
+ - `wsRoutes` is local dev-time WebSocket proxy routing for Durable Objects
645
+
646
+ #### `vars` and `secrets`
647
+
648
+ | Property | Shape | Current behavior |
649
+ |---|---|---|
650
+ | `vars` | `Record<string, string>` | Non-secret runtime bindings compiled into Wrangler `vars` |
651
+ | `secrets` | `Record<string, { required?: boolean }>` | Secret declarations only. `required` defaults to `true`. Values must come from Cloudflare secrets, tests, or upstream local tooling. |
652
+
653
+ #### `assets`, `limits`, and `observability`
654
+
655
+ | Property | Shape | Current behavior |
656
+ |---|---|---|
657
+ | `assets.directory` | `string` | Required asset directory path |
658
+ | `assets.binding` | `string` | Optional asset binding name for programmatic access |
659
+ | `limits.cpu_ms` | `number` | Optional CPU limit for unbound workers |
660
+ | `observability.enabled` | `boolean` | Enable Worker Logs |
661
+ | `observability.head_sampling_rate` | `number` | Log sampling rate from `0` to `1` |
662
+
663
+ #### `migrations`
664
+
665
+ Each migration object has this shape:
666
+
667
+ | Field | Shape | Meaning |
668
+ |---|---|---|
669
+ | `tag` | `string` | Required migration version label |
670
+ | `new_classes` | `string[]` | Newly introduced DO classes |
671
+ | `renamed_classes` | `Array<{ from: string; to: string }>` | DO class renames with state preservation |
672
+ | `deleted_classes` | `string[]` | Deleted DO classes |
673
+ | `new_sqlite_classes` | `string[]` | DO classes migrating to SQLite storage |
674
+
675
+ #### `rolldown`
676
+
677
+ `rolldown` config applies to Devflare's own Worker bundling outputs.
678
+
679
+ | Key | Shape | Current behavior |
680
+ |---|---|---|
681
+ | `target` | `string` | Bundle target for emitted Devflare-owned Worker bundles |
682
+ | `minify` | `boolean` | Minify Devflare-owned Worker bundles |
683
+ | `sourcemap` | `boolean` | Emit source maps for Devflare-owned Worker bundles |
684
+ | `options` | `DevflareRolldownOptions` | Additional Rolldown input/output options and plugins, minus Devflare-owned fields |
685
+
686
+ Current `rolldown.options` ownership rules:
687
+
688
+ - Devflare owns `cwd`, `input`, `platform`, and `watch`
689
+ - Devflare also owns output `codeSplitting`, `dir`, `file`, `format`, and `inlineDynamicImports`
690
+ - output stays single-file ESM so Devflare's worker-owned bundles remain worker-friendly
691
+ - `rolldown.options.plugins` is the intended extension point for custom transforms and Rollup-compatible plugins
692
+
693
+ #### `vite`
694
+
695
+ `vite` is Devflare's inline Vite config namespace. When Devflare runs Vite, this object is merged into the actual Vite config that Vite receives.
696
+
697
+ | Key | Shape | Current behavior |
698
+ |---|---|---|
699
+ | `plugins` | `unknown[]` | Accepted by the schema and normalized from the legacy top-level `plugins` alias, then merged into the actual Vite plugin chain when Devflare runs Vite |
700
+ | any other key | `unknown` | Passed through as actual Vite config when Devflare runs Vite |
701
+
702
+ That distinction is intentional:
703
+
704
+ - use `config.vite` when you want Devflare config to be your Vite config source of truth
705
+ - keep `vite.config.ts` when you prefer a standalone Vite config file or need advanced programmatic control
706
+ - if both exist, Devflare merges `vite.config.*` first, then `config.vite`, and injects `devflarePlugin()` into the resulting config
707
+ - use `devflare/vite` helpers when Devflare needs to participate in the Vite pipeline programmatically
708
+
709
+ #### `env`
710
+
711
+ `env` is `Record<string, EnvOverride>`, where each environment can currently override these keys:
712
+
713
+ - `name`
714
+ - `compatibilityDate`
715
+ - `compatibilityFlags`
716
+ - `files`
717
+ - `bindings`
718
+ - `triggers`
719
+ - `vars`
720
+ - `secrets`
721
+ - `routes`
722
+ - `assets`
723
+ - `limits`
724
+ - `observability`
725
+ - `migrations`
726
+ - `rolldown`
727
+ - `vite`
728
+ - `wrangler`
729
+ - deprecated `build`
730
+ - deprecated `plugins`
731
+
732
+ Current exclusions still matter:
733
+
734
+ - `accountId` is not supported inside `env`
735
+ - `wsRoutes` is not supported inside `env`
736
+ - nested `env` blocks are not part of the override shape
737
+
738
+ Merge behavior is also part of the contract:
739
+
740
+ - scalars override base values
741
+ - nested objects merge
742
+ - arrays append instead of replacing
743
+ - `null` and `undefined` do not delete inherited values
744
+
745
+ #### `wrangler`
746
+
747
+ `wrangler` currently exposes one native child key:
748
+
749
+ | Key | Shape | Current behavior |
750
+ |---|---|---|
751
+ | `passthrough` | `Record<string, unknown>` | Shallow-merged on top of the compiled Wrangler config. Use this for unsupported Wrangler keys or to take full ownership of `main`. |
752
+
753
+ #### Deprecated aliases
754
+
755
+ | Legacy key | Current canonical key | Notes |
756
+ |---|---|---|
757
+ | `build.target` | `rolldown.target` | Deprecated but still normalized |
758
+ | `build.minify` | `rolldown.minify` | Deprecated but still normalized |
759
+ | `build.sourcemap` | `rolldown.sourcemap` | Deprecated but still normalized |
760
+ | `build.rolldownOptions` | `rolldown.options` | Deprecated but still normalized |
761
+ | `plugins` | `vite.plugins` | Deprecated top-level alias; raw Vite plugin wiring still belongs in `vite.config.*` |
762
+
531
763
  ### Native config coverage vs `wrangler.passthrough`
532
764
 
533
765
  Devflare natively models the common Worker config it actively composes around. It does **not** try to mirror every Wrangler field one-by-one as a first-class Devflare schema key.
@@ -579,7 +811,7 @@ Two practical rules:
579
811
  | `entrypoints` | <code>string &#124; false</code> | `**/ep.*.{ts,js}` | WorkerEntrypoint discovery glob |
580
812
  | `workflows` | <code>string &#124; false</code> | `**/wf.*.{ts,js}` | workflow discovery glob |
581
813
  | `routes` | <code>{ dir, prefix? } &#124; false</code> | `src/routes` when that directory exists | built-in file router configuration |
582
- | `transport` | `string` | none | custom transport definition file |
814
+ | `transport` | <code>string &#124; null</code> | `src/transport.{ts,js,mts,mjs}` when one of those files exists | custom transport definition file |
583
815
 
584
816
  Discovery does not behave identically in every subsystem:
585
817
 
@@ -615,25 +847,48 @@ export default defineConfig({
615
847
  })
616
848
  ```
617
849
 
618
- `files.transport` is opt-in. Devflare only loads it when `files.transport` is explicitly set, and the file must export a named `transport` object.
850
+ `files.transport` is convention-first. `createTestContext()` auto-loads `src/transport.{ts,js,mts,mjs}` when present, a string value points at a different transport file, and `files.transport: null` disables transport loading explicitly. The file must export a named `transport` object.
619
851
 
620
852
  There is no public `files.tail` config key today.
621
853
 
622
- ### `.env`, `vars`, `secrets`, and `config.env`
854
+ ### `.env`, `.dev.vars`, `vars`, `secrets`, and `config.env`
623
855
 
624
856
  Keep these layers separate:
625
857
 
626
858
  | Layer | Holds values? | Compiled into generated config? | Use it for |
627
859
  |---|---|---|---|
628
860
  | `.env` / `process.env` | yes | indirectly, only when your config reads from it | local process-time inputs |
861
+ | `.env.dev` / `.env.<name>` | tool/runtime-dependent | indirectly at most, only if the surrounding tool has already populated `process.env` | local process-time variants, not a Devflare-native contract |
862
+ | `.dev.vars` / `.dev.vars.<name>` | yes | no | local runtime secret/value files in upstream Cloudflare tooling when applicable |
629
863
  | `vars` | yes | yes | non-secret string bindings |
630
864
  | `secrets` | no, declaration only | no | required/optional runtime secret bindings |
631
865
  | `config.env` | yes, as config overlays | yes after merge | environment-specific config overrides |
632
866
 
633
- #### `.env` and process env
867
+ #### `.env`, `.env.dev`, and process env
634
868
 
635
869
  `loadConfig()` does not do dotenv loading by itself. The Devflare CLI runs under Bun, and Bun may auto-load `.env` files into `process.env`.
636
870
 
871
+ Important boundary:
872
+
873
+ - Devflare sets `dotenv: false` in its config loader
874
+ - Devflare does **not** define special first-class semantics for `.env.dev` or `.env.<name>`
875
+ - if those files affect `process.env`, that comes from the surrounding host tool/runtime rather than a Devflare-native loader
876
+ - config-time/build-time code can still read `process.env` inside `defineConfig()` or other Node-side tooling
877
+
878
+ Treat `.env*` files as **config/build-time inputs**, not as Devflare's runtime secret system.
879
+
880
+ #### `.dev.vars` and local runtime secrets
881
+
882
+ Devflare does **not** currently implement its own first-class `.dev.vars` / `.dev.vars.<name>` loader for worker-only dev mode or `createTestContext()`.
883
+
884
+ That means:
885
+
886
+ - do not document `.dev.vars*` as a guaranteed Devflare-native feature across all modes
887
+ - `secrets` declares the names of expected runtime secrets, but does not provide values
888
+ - worker-only dev and `createTestContext()` should not be described as automatically materializing secret values from `.dev.vars*`
889
+
890
+ In Vite-backed flows, some local runtime variable behavior may come from upstream Cloudflare/Vite tooling rather than from Devflare itself. Document that as inherited upstream behavior, not as a unified Devflare contract.
891
+
637
892
  #### `vars`
638
893
 
639
894
  Use `vars` for non-secret runtime values that can safely appear in generated config, such as public URLs, modes, IDs, and feature flags.
@@ -664,6 +919,135 @@ secrets: {
664
919
 
665
920
  means “`API_KEY` is a required runtime secret,” not “optional secret with no requirements.”
666
921
 
922
+ In practice, secret **values** come from outside Devflare config:
923
+
924
+ - Cloudflare-stored runtime secrets in deployed environments
925
+ - explicit test injection or lower-level mocks in tests
926
+ - upstream local-dev tooling when you intentionally rely on it
927
+
928
+ Do not describe `secrets` as a place that stores values.
929
+
930
+ #### Example files such as `.env.example` and `.dev.vars.example`
931
+
932
+ Example files are a **team convention**, not a Devflare feature.
933
+
934
+ Current truthful guidance:
935
+
936
+ - use `.env.example` to document required config-time/build-time variables that your config or Node-side tooling reads from `process.env`
937
+ - use `.dev.vars.example` to document expected local runtime secret names **if your project chooses to rely on upstream `.dev.vars` workflows**
938
+ - keep example files committed with placeholder or fake values only
939
+ - do not claim that Devflare auto-generates, auto-loads, or validates these example files today
940
+
941
+ #### Canonical env and secrets layout
942
+
943
+ If you want the lowest-confusion setup, use this split:
944
+
945
+ - `.env.example` documents config-time/build-time inputs
946
+ - `.dev.vars.example` documents local runtime secret names **only if your project intentionally relies on upstream `.dev.vars` workflows**
947
+ - `devflare.config.ts` reads config-time values from `process.env`, puts safe runtime values in `vars`, and declares required runtime secret names in `secrets`
948
+ - deployed secret values live in Cloudflare, not in your repo
949
+
950
+ Recommended project shape:
951
+
952
+ ```text
953
+ my-worker/
954
+ ├─ .env.example
955
+ ├─ .dev.vars.example # optional; only if you intentionally use upstream .dev.vars flows
956
+ ├─ .gitignore
957
+ ├─ devflare.config.ts
958
+ └─ src/
959
+ └─ fetch.ts
960
+ ```
961
+
962
+ Example `.env.example`:
963
+
964
+ ```dotenv
965
+ WORKER_NAME=my-worker
966
+ API_ORIGIN=http://localhost:3000
967
+ ```
968
+
969
+ Example `.dev.vars.example`:
970
+
971
+ ```dotenv
972
+ API_KEY=replace-me
973
+ SESSION_SECRET=replace-me
974
+ ```
975
+
976
+ Typical git ignore pattern for user projects:
977
+
978
+ ```gitignore
979
+ .env
980
+ .env.*
981
+ !.env.example
982
+ .dev.vars
983
+ .dev.vars.*
984
+ !.dev.vars.example
985
+ ```
986
+
987
+ Example `devflare.config.ts`:
988
+
989
+ ```ts
990
+ import { defineConfig } from 'devflare'
991
+
992
+ export default defineConfig({
993
+ name: process.env.WORKER_NAME ?? 'my-worker',
994
+ compatibilityDate: '2026-03-17',
995
+ files: {
996
+ fetch: 'src/fetch.ts'
997
+ },
998
+ vars: {
999
+ API_ORIGIN: process.env.API_ORIGIN ?? 'http://localhost:3000'
1000
+ },
1001
+ secrets: {
1002
+ API_KEY: {},
1003
+ SESSION_SECRET: {}
1004
+ },
1005
+ env: {
1006
+ production: {
1007
+ vars: {
1008
+ API_ORIGIN: 'https://api.example.com'
1009
+ }
1010
+ }
1011
+ }
1012
+ })
1013
+ ```
1014
+
1015
+ Example `src/fetch.ts`:
1016
+
1017
+ ```ts
1018
+ import type { FetchEvent } from 'devflare/runtime'
1019
+
1020
+ export async function GET({ env }: FetchEvent<DevflareEnv>): Promise<Response> {
1021
+ return Response.json({
1022
+ origin: env.API_ORIGIN,
1023
+ hasApiKey: Boolean(env.API_KEY),
1024
+ hasSessionSecret: Boolean(env.SESSION_SECRET)
1025
+ })
1026
+ }
1027
+ ```
1028
+
1029
+ Deployed runtime secrets should be created with Cloudflare/Wrangler tooling, not committed to config files or example files.
1030
+
1031
+ Typical deployed secret flow:
1032
+
1033
+ ```bash
1034
+ bunx --bun wrangler secret put API_KEY
1035
+ bunx --bun wrangler secret put SESSION_SECRET
1036
+ ```
1037
+
1038
+ If you use named Cloudflare environments, set the secret in that environment explicitly:
1039
+
1040
+ ```bash
1041
+ bunx --bun wrangler secret put API_KEY --env production
1042
+ bunx --bun wrangler secret put SESSION_SECRET --env production
1043
+ ```
1044
+
1045
+ Practical rule of thumb:
1046
+
1047
+ - if a value is needed while evaluating config, put it in the `.env*` / `process.env` bucket
1048
+ - if a value should exist as a runtime binding but must not be committed, declare it in `secrets`
1049
+ - if a project wants local runtime secret files, treat `.dev.vars*` as an upstream convention and document it explicitly per project
1050
+
667
1051
  #### `devflare types`
668
1052
 
669
1053
  `devflare types` generates `env.d.ts` from the resolved config plus discovered surfaces. The stable public result is typed `DevflareEnv` coverage for bindings such as `vars`, `secrets`, services, Durable Objects, and discovered entrypoints.
@@ -937,20 +1321,299 @@ Advanced members such as `.name`, `.config`, `.configPath`, and `.resolve()` are
937
1321
 
938
1322
  ## Development workflows
939
1323
 
1324
+ ### Vite vs Rolldown: the truthful mental model
1325
+
1326
+ They are both important, but they are not two names for the same job.
1327
+
1328
+ | Tool | Role inside Devflare | When it matters most | What it is not |
1329
+ |---|---|---|---|
1330
+ | `Vite` | the optional outer dev/build host for packages that are already Vite apps or frameworks; Devflare plugs generated Worker config, config watching, auxiliary DO workers, and bridge behavior into that pipeline | packages with a local `vite.config.*`, packages that only define inline `config.vite`, SvelteKit, frontend HMR | not Devflare's own Worker bundler |
1331
+ | `Rolldown` | the inner code-transforming builder Devflare uses when Devflare itself bundles Worker code | worker-only main worker bundles, Durable Object bundles, watch/rebuild, worker-side plugin transforms such as `.svelte` imported by a worker or DO module | not the main app's Vite build |
1332
+
1333
+ Three practical consequences fall straight out of the implementation:
1334
+
1335
+ - remove both `vite.config.*` and inline `config.vite`, and Devflare drops back to worker-only mode instead of starting Vite
1336
+ - import `.svelte` from a worker-only surface or Durable Object, and the compilation belongs to `rolldown.options.plugins`, not to the main Vite plugin chain
1337
+ - generated `.devflare/worker-entrypoints/main.ts` is separate glue code produced by Devflare when it needs to compose fetch, queue, scheduled, email, or route-tree surfaces into one Worker entry
1338
+
940
1339
  ### Operational decision rules
941
1340
 
942
1341
  Use these rules in order:
943
1342
 
944
- 1. a local `vite.config.*` in the current package decides whether `dev`, `build`, and `deploy` run in Vite-backed mode or worker-only mode
1343
+ 1. a local `vite.config.*` or a non-empty `config.vite` in the current package decides whether `dev`, `build`, and `deploy` run in Vite-backed mode or worker-only mode
945
1344
  2. `devflare/vite` is an explicit helper layer, not a separate CLI mode
946
- 3. worker-only mode is a supported first-class path; no local `vite.config.*` means Devflare does not start Vite
947
- 4. raw Vite config belongs in `vite.config.*`; `config.vite` is Devflare metadata, not full Vite config
1345
+ 3. worker-only mode is a supported first-class path; no local `vite.config.*` and no inline `config.vite` means Devflare does not start Vite
1346
+ 4. `config.vite` is real Vite config when Devflare runs Vite; a local `vite.config.*` remains optional and is merged first when present
948
1347
  5. treat `.devflare/*`, `env.d.ts`, and generated Wrangler config as outputs, not authoring inputs
949
1348
  6. remote mode is mainly for remote-oriented services such as AI and Vectorize, not a blanket “make everything remote” switch
950
1349
 
1350
+ ### Vite-backed workflows
1351
+
1352
+ A package enters Vite-backed mode when it has a local `vite.config.*` or a non-empty `config.vite`. In that mode, Vite is the outer application pipeline: it owns the package's dev server and app build, while Devflare injects Worker-aware config, generated Wrangler output, auxiliary DO worker config, and bridge behavior into that Vite stack.
1353
+
1354
+ Current Vite-backed flow:
1355
+
1356
+ 1. Devflare loads and validates `devflare.config.*`
1357
+ 2. if a local `vite.config.*` exists, Devflare loads it and merges `config.vite` on top; otherwise Devflare synthesizes `.devflare/vite.config.mjs` from `config.vite`
1358
+ 3. `devflarePlugin()` compiles that config into a generated `.devflare/wrangler.jsonc`
1359
+ 4. Devflare may generate `.devflare/worker-entrypoints/main.ts` when multiple surfaces must be composed into one Worker entry
1360
+ 5. if Durable Object files are discovered, Devflare builds an auxiliary DO worker config for Vite / Cloudflare interop
1361
+ 6. in serve mode, Devflare watches the resolved Devflare config file and triggers a full reload when it changes
1362
+ 7. if `wsRoutes` are configured, Devflare can proxy matching WebSocket upgrade paths to the Miniflare bridge
1363
+ 8. on build, Devflare runs `bunx vite build --config .devflare/vite.config.mjs` when it needs a generated config wrapper
1364
+
1365
+ Two ownership rules matter here:
1366
+
1367
+ - setting `wrangler.passthrough.main` tells Devflare to preserve your explicit Worker `main` instead of generating a composed one
1368
+ - no local `vite.config.*` and no inline `config.vite` means none of this Vite-specific behavior runs; the package stays in worker-only mode
1369
+
1370
+ #### `devflare/vite` helpers
1371
+
1372
+ | Helper | Use it for | Timing |
1373
+ |---|---|---|
1374
+ | `devflarePlugin(options)` | generated `.devflare/wrangler.jsonc`, config watching, DO discovery, DO transforms, and WebSocket proxy wiring | include it in `vite.config.*` plugins |
1375
+ | `getCloudflareConfig(options)` | compiled programmatic config for `cloudflare({ config })` | call during Vite config creation |
1376
+ | `getDevflareConfigs(options)` | compiled config plus `auxiliaryWorkers` array for DO workers | call during Vite config creation |
1377
+ | `resolveViteUserConfig(configEnv, options)` | merge local `vite.config.*`, inline `config.vite`, and `devflarePlugin()` into the actual Vite config object | advanced / generated-config use |
1378
+ | `writeGeneratedViteConfig(options)` | write `.devflare/vite.config.mjs` for CLI-driven Vite runs | advanced / generated-config use |
1379
+ | `getPluginContext()` | read resolved plugin state such as `wranglerConfig`, `cloudflareConfig`, discovered DOs, and `projectRoot` | advanced use only, after Vite has resolved config |
1380
+
1381
+ `devflarePlugin(options)` currently supports these options:
1382
+
1383
+ | Option | Default | What it changes |
1384
+ |---|---|---|
1385
+ | `configPath` | auto-resolve local supported config | point Vite at a specific `devflare.config.*` file |
1386
+ | `environment` | no explicit override | resolve `config.env[name]` before compilation |
1387
+ | `doTransforms` | `true` | enable or disable Devflare's DO code transforms |
1388
+ | `watchConfig` | `true` | watch the resolved config file and full-reload on change |
1389
+ | `bridgePort` | `process.env.DEVFLARE_BRIDGE_PORT`, then `8787` when proxying | choose the Miniflare bridge port for WebSocket proxying |
1390
+ | `wsProxyPatterns` | `[]` | add extra WebSocket proxy patterns beyond configured `wsRoutes` |
1391
+
1392
+ Timing rule of thumb:
1393
+
1394
+ - if you need config while building the Vite config object, use `getCloudflareConfig()` or `getDevflareConfigs()`
1395
+ - if you want Devflare to treat `config.vite` as the actual Vite config source of truth, rely on the CLI-generated config path or `resolveViteUserConfig()`
1396
+ - if another Vite plugin needs to inspect the already-resolved Devflare state, `getPluginContext()` is the advanced hook
1397
+
1398
+ #### Minimal Vite wiring
1399
+
1400
+ ```ts
1401
+ import { defineConfig } from 'vite'
1402
+ import { devflarePlugin } from 'devflare/vite'
1403
+
1404
+ export default defineConfig({
1405
+ plugins: [devflarePlugin()]
1406
+ })
1407
+ ```
1408
+
1409
+ #### Explicit `@cloudflare/vite-plugin` wiring
1410
+
1411
+ ```ts
1412
+ import { defineConfig } from 'vite'
1413
+ import { cloudflare } from '@cloudflare/vite-plugin'
1414
+ import { devflarePlugin, getDevflareConfigs } from 'devflare/vite'
1415
+
1416
+ export default defineConfig(async () => {
1417
+ const { cloudflareConfig, auxiliaryWorkers } = await getDevflareConfigs()
1418
+
1419
+ return {
1420
+ plugins: [
1421
+ devflarePlugin(),
1422
+ cloudflare({
1423
+ config: cloudflareConfig,
1424
+ auxiliaryWorkers: auxiliaryWorkers.length > 0 ? auxiliaryWorkers : undefined
1425
+ })
1426
+ ]
1427
+ }
1428
+ })
1429
+ ```
1430
+
1431
+ That is the current high-signal pattern when you want Vite to stay the package's app/build host while Devflare owns Worker config compilation and Durable Object discovery.
1432
+
1433
+ #### SvelteKit-backed Worker example
1434
+
1435
+ ```ts
1436
+ // devflare.config.ts
1437
+ import { defineConfig } from 'devflare'
1438
+
1439
+ export default defineConfig({
1440
+ name: 'notes-app',
1441
+ files: {
1442
+ fetch: '.svelte-kit/cloudflare/_worker.js',
1443
+ durableObjects: 'src/do/**/*.ts',
1444
+ transport: 'src/transport.ts'
1445
+ },
1446
+ bindings: {
1447
+ durableObjects: {
1448
+ CHAT_ROOM: 'ChatRoom'
1449
+ }
1450
+ }
1451
+ })
1452
+ ```
1453
+
1454
+ ```ts
1455
+ // vite.config.ts
1456
+ import { defineConfig } from 'vite'
1457
+ import { sveltekit } from '@sveltejs/kit/vite'
1458
+ import { devflarePlugin } from 'devflare/vite'
1459
+
1460
+ export default defineConfig({
1461
+ plugins: [
1462
+ devflarePlugin(),
1463
+ sveltekit()
1464
+ ]
1465
+ })
1466
+ ```
1467
+
1468
+ ```ts
1469
+ // src/hooks.server.ts
1470
+ export { handle } from 'devflare/sveltekit'
1471
+ ```
1472
+
1473
+ Use `createHandle({...})` from `devflare/sveltekit` when you need custom binding hints or want to compose Devflare with other SvelteKit handles via `sequence(...)`.
1474
+
1475
+ ### Rolldown bundling and plugin workflows
1476
+
1477
+ `rolldown` is not just a namespace of knobs. Rolldown is the builder Devflare uses for the code Devflare actively bundles itself. Today that means two Devflare-owned output paths: the worker-only composed main worker bundle and the Durable Object bundle path. Devflare composes worker surfaces when needed, applies its own transforms, lets user plugins transform imports, and emits runnable single-file ESM Worker modules that Miniflare and Wrangler can execute.
1478
+
1479
+ That is why `rolldown` is important but different from Vite:
1480
+
1481
+ - Vite may host the outer app or framework pipeline
1482
+ - Rolldown is the inner code-transform step that turns Devflare-owned Worker source into actual runnable worker code
1483
+ - if a worker-only surface or Durable Object imports `.svelte`, that compilation belongs to the Rolldown plugin pipeline, not to the main Vite app plugin chain
1484
+
1485
+ It is still not Vite config, not a replacement for your app's `vite.config.*`, and not the place to configure the main Vite app build.
1486
+
1487
+ Current worker bundler behavior:
1488
+
1489
+ - in worker-only `dev`, `build`, and `deploy`, Devflare composes the main worker entry to `.devflare/worker-entrypoints/main.ts` and then bundles it to `.devflare/worker-entrypoints/main.js`
1490
+ - Devflare discovers DO files from `files.durableObjects`
1491
+ - discovered DO entries are bundled to worker-compatible ESM
1492
+ - code splitting is disabled so Devflare can emit a worker-friendly single-file bundle
1493
+ - user `rolldown.options.plugins` are merged into the bundle pipeline
1494
+ - internal externals cover `cloudflare:*`, `node:*`, and other worker/runtime modules that should stay external
1495
+ - Devflare also injects a `debug` alias shim so worker bundles do not accidentally drag in a Node-only debug dependency
1496
+ - this same DO bundling path still matters in unified Vite dev; Vite can host the app while Rolldown rebuilds DO worker code underneath it
1497
+
1498
+ Rolldown's plugin API is almost fully compatible with Rollup's, which is why Rollup-style plugins can often be passed through in `rolldown.options.plugins`. That said, compatibility is high, not magical: keep integration tests around nontrivial plugin stacks.
1499
+
1500
+ #### Minimal custom transform example
1501
+
1502
+ ```ts
1503
+ import { defineConfig } from 'devflare'
1504
+ import type { Plugin as RolldownPlugin } from 'rolldown'
1505
+
1506
+ const inlineSvelteFixturePlugin: RolldownPlugin = {
1507
+ name: 'inline-svelte-fixture',
1508
+ transform(_code, id) {
1509
+ if (!id.endsWith('.svelte')) {
1510
+ return null
1511
+ }
1512
+
1513
+ return {
1514
+ code: 'export default { render() { return { html: "<h1>Hello from Svelte</h1>" } } }',
1515
+ map: null
1516
+ }
1517
+ }
1518
+ }
1519
+
1520
+ export default defineConfig({
1521
+ name: 'worker-app',
1522
+ files: {
1523
+ fetch: 'src/fetch.ts'
1524
+ },
1525
+ rolldown: {
1526
+ options: {
1527
+ plugins: [inlineSvelteFixturePlugin]
1528
+ }
1529
+ }
1530
+ })
1531
+ ```
1532
+
1533
+ That mirrors the kind of `.svelte` transform path the repo now exercises for the composed main worker bundle.
1534
+
1535
+ #### Svelte plugin example for Rolldown
1536
+
1537
+ This example is intentionally about a `.svelte` import inside a worker-only fetch surface. In that situation, Rolldown — not the main Vite app build — is the plugin pipeline doing the compilation.
1538
+
1539
+ For this pattern you typically install `svelte`, `rollup-plugin-svelte`, and `@rollup/plugin-node-resolve` in the package that owns the worker code.
1540
+
1541
+ ```ts
1542
+ import { defineConfig } from 'devflare'
1543
+ import resolve from '@rollup/plugin-node-resolve'
1544
+ import type { Plugin as RolldownPlugin } from 'rolldown'
1545
+ import svelte from 'rollup-plugin-svelte'
1546
+
1547
+ export default defineConfig({
1548
+ name: 'chat-worker',
1549
+ files: {
1550
+ fetch: 'src/fetch.ts'
1551
+ },
1552
+ rolldown: {
1553
+ target: 'es2022',
1554
+ sourcemap: true,
1555
+ options: {
1556
+ plugins: [
1557
+ svelte({
1558
+ emitCss: false,
1559
+ compilerOptions: {
1560
+ generate: 'ssr'
1561
+ }
1562
+ }) as unknown as RolldownPlugin,
1563
+ resolve({
1564
+ browser: true,
1565
+ exportConditions: ['svelte'],
1566
+ extensions: ['.svelte']
1567
+ }) as unknown as RolldownPlugin
1568
+ ]
1569
+ }
1570
+ }
1571
+ })
1572
+ ```
1573
+
1574
+ ```svelte
1575
+ <!-- src/Greeting.svelte -->
1576
+ <script lang='ts'>
1577
+ export let name: string
1578
+ </script>
1579
+
1580
+ <h1>Hello {name} from Svelte</h1>
1581
+ ```
1582
+
1583
+ ```ts
1584
+ // src/fetch.ts
1585
+ import Greeting from './Greeting.svelte'
1586
+
1587
+ export async function fetch(): Promise<Response> {
1588
+ return new Response(Greeting.render({ name: 'Devflare' }).html, {
1589
+ headers: {
1590
+ 'content-type': 'text/html; charset=utf-8'
1591
+ }
1592
+ })
1593
+ }
1594
+ ```
1595
+
1596
+ What happens in this flow:
1597
+
1598
+ 1. Devflare discovers or composes the worker-only main entry from `files.fetch` and related surfaces
1599
+ 2. the worker module imports `Greeting.svelte`
1600
+ 3. Rolldown runs the configured plugin pipeline, including `rollup-plugin-svelte`
1601
+ 4. the Svelte component becomes JavaScript before the Worker bundle is written
1602
+ 5. Devflare writes a runnable single-file Worker bundle for that worker entry
1603
+
1604
+ Why this example is shaped that way:
1605
+
1606
+ - `emitCss: false` keeps the Worker bundle single-file instead of emitting a separate CSS asset pipeline
1607
+ - `generate: 'ssr'` fits the Worker-side rendering story better than a browser DOM target
1608
+ - `@rollup/plugin-node-resolve` helps `.svelte` files and `exports.svelte` packages resolve cleanly
1609
+ - some Rollup plugins need a type cast to satisfy Rolldown's TypeScript types even when the runtime hooks work fine
1610
+ - this example is specifically about worker-side component compilation inside a worker-only surface; the same plugin path also applies to DO bundles, while Svelte code in the main app or SvelteKit shell is still Vite's job
1611
+
1612
+ If a plugin relies on Rollup-only hooks that Rolldown does not support yet, keep that plugin in your main Vite build instead of the DO bundler.
1613
+
951
1614
  ### Daily development loop
952
1615
 
953
- Use the same CLI loop for both worker-only and Vite-backed packages. The presence of a local `vite.config.*` changes the mode automatically.
1616
+ Use the same CLI loop for both worker-only and Vite-backed packages. The presence of a local `vite.config.*` or inline `config.vite` changes the mode automatically.
954
1617
 
955
1618
  ```bash
956
1619
  bunx --bun devflare dev
@@ -967,7 +1630,7 @@ Use `--env <name>` on build and deploy when you want Devflare to resolve a named
967
1630
  | Command | What it does | Important note |
968
1631
  |---|---|---|
969
1632
  | `devflare init` | scaffold a project | current templates generate `src/fetch.ts` and explicit `files.fetch` |
970
- | `devflare dev` | start local development | worker-only by default, Vite-backed only when the current package has a local `vite.config.*` |
1633
+ | `devflare dev` | start local development | worker-only by default, Vite-backed when the current package has a local `vite.config.*` or inline `config.vite` |
971
1634
  | `devflare build` | build from resolved config | skips Vite for worker-only packages |
972
1635
  | `devflare deploy` | build and deploy | supports `--env` and `--dry-run` |
973
1636
  | `devflare types` | generate `env.d.ts` | supports `--output` |
@@ -1013,7 +1676,8 @@ Current behavior:
1013
1676
  - it resolves service bindings and cross-worker Durable Object references
1014
1677
  - it can infer conventional fetch, queue, scheduled, and email handler files when present
1015
1678
  - it also auto-detects `src/tail.ts` when present, even though there is no public `files.tail` config key
1016
- - `files.transport` is opt-in rather than auto-loaded by filename convention
1679
+ - it auto-detects `src/transport.{ts,js,mts,mjs}` when present unless `files.transport` is `null`
1680
+ - it does not have a first-class `.dev.vars*` loader for populating declared secret values
1017
1681
 
1018
1682
  Practical example:
1019
1683
 
@@ -1099,12 +1763,18 @@ Keep these caveats explicit:
1099
1763
  - `_`-prefixed files and directories inside the route tree are ignored
1100
1764
  - `vars` values are strings in the current native schema
1101
1765
  - `secrets` declares runtime secret bindings; it does not store secret values
1766
+ - `.env*` and `.dev.vars*` are not a unified Devflare-native loading/validation system; any effect they have comes from Bun or upstream Cloudflare tooling, depending on the mode
1102
1767
  - `wrangler.passthrough` is the escape hatch for unsupported Wrangler keys and is merged after native compilation
1103
1768
  - named service entrypoints need deployment-time validation if they are critical to your app
1104
1769
  - local R2 binding support is real, but there is still no stable public/browser local bucket URL contract to document as public API
1105
1770
  - `sendEmail` is a supported outbound binding, while inbound email is a separate worker surface
1106
1771
  - email and tail helpers have real, useful test paths, but they should not be described as identical to full Cloudflare ingress or tail replay
1107
1772
  - Vite and Rolldown are different systems and should not be blurred together
1773
+ - `config.vite` is real Vite config only when Devflare is actually running Vite; it does not replace the worker-only Rolldown bundle path
1774
+ - `rolldown` config affects Devflare-owned worker bundling outputs (worker-only main bundles and DO bundles), not the main Vite app build
1775
+ - `wrangler.passthrough.main` suppresses Devflare's composed main-entry generation in higher-level build and Vite-backed flows
1776
+ - `getCloudflareConfig()` and `getDevflareConfigs()` are the safe config-time Vite helpers; `getPluginContext()` is advanced post-resolution state
1777
+ - Rollup-compatible plugins often work in `rolldown.options.plugins`, but compatibility is high-not-total and plugin-specific validation still matters
1108
1778
  - higher-level flows may generate composed main worker entries more aggressively than older docs implied
1109
1779
 
1110
1780
  ---