@lunora/vite 0.0.0 → 1.0.0-alpha.10

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.
@@ -0,0 +1,374 @@
1
+ import { CodegenOptions } from '@lunora/codegen';
2
+ import errorOverlayPlugin from '@visulima/vite-overlay';
3
+ import { Plugin } from 'vite';
4
+ import { FrameworkDetection, DetectedFramework, materializeRemoteWranglerConfig, readProjectRemotePreference } from '@lunora/config';
5
+ export { type DetectedFramework, type FrameworkClass, type FrameworkDetection, detectFramework } from '@lunora/config';
6
+ import { AddressInfo } from 'node:net';
7
+ /** Options forwarded to `@cloudflare/vite-plugin`'s cloudflare plugin. */
8
+ type CloudflarePluginOptions = Record<string, unknown>;
9
+ /**
10
+ * Options forwarded to `@visulima/vite-overlay`'s error-overlay plugin. Derived
11
+ * from the plugin's own factory signature so it tracks the real shape
12
+ * (`forwardConsole`, `forwardedConsoleMethods`, `reactPluginName`,
13
+ * `solutionFinders`, `showBallonButton`, `vuePluginName`, …).
14
+ */
15
+ type OverlayPluginOptions = NonNullable<Parameters<typeof errorOverlayPlugin>[0]>;
16
+ interface LunoraPluginOptions {
17
+ /**
18
+ * Which machine-readable API spec(s) codegen emits into `_generated/`.
19
+ * `"openapi"` (default) writes `openapi.json` (OpenAPI 3.1; RPC + REST),
20
+ * `"openrpc"` writes `openrpc.json` (OpenRPC 1.x; RPC-only), `"both"` writes
21
+ * both, and `"none"` writes neither. Forwarded to `runCodegen({ apiSpec })`;
22
+ * the value set is derived from `CodegenOptions` so it can't drift.
23
+ */
24
+ apiSpec?: CodegenOptions["apiSpec"];
25
+ /** Pass through to `@cloudflare/vite-plugin`. Pass `false` to opt out. Defaults to `true`. */
26
+ cloudflare?: boolean | CloudflarePluginOptions;
27
+ /** Directory name (relative to `projectRoot`) where generated files are written. Defaults to `"lunora/_generated"`. */
28
+ generatedDir?: string;
29
+ /**
30
+ * Inject `@visulima/vite-overlay` for runtime errors (dev only). Pass
31
+ * `false` to opt out, or an options object to forward to the overlay.
32
+ * Defaults to `true`.
33
+ */
34
+ overlay?: boolean | OverlayPluginOptions;
35
+ /** Project root containing the `lunora/` directory. Defaults to `process.cwd()`. */
36
+ projectRoot?: string;
37
+ /** Directory name (relative to `projectRoot`) containing `schema.ts` and function files. Defaults to `"lunora"`. */
38
+ schemaDir?: string;
39
+ /** Serve the Lunora studio at `/__lunora` during dev. Pass `false` to opt out. Defaults to `true`. */
40
+ studio?: boolean;
41
+ /** Validate that `wrangler.jsonc` declares the bindings the schema implies. Defaults to `true`. */
42
+ validateWrangler?: boolean;
43
+ }
44
+ /** Resolved options after merging defaults. */
45
+ interface ResolvedLunoraPluginOptions {
46
+ apiSpec: NonNullable<CodegenOptions["apiSpec"]>;
47
+ cloudflare: false | CloudflarePluginOptions;
48
+ generatedDir: string;
49
+ overlay: false | OverlayPluginOptions;
50
+ projectRoot: string;
51
+ schemaDir: string;
52
+ studio: boolean;
53
+ validateWrangler: boolean;
54
+ }
55
+ /**
56
+ * The plugins `lunora()` returns. A mutable `Plugin[]` (not `ReadonlyArray`) so
57
+ * it slots directly into Vite's `plugins` — which recursively flattens nested
58
+ * plugin arrays — without a spread: `plugins: [lunora()]`.
59
+ */
60
+ type LunoraPlugins = Plugin[];
61
+ /**
62
+ * Vite plugin that runs `@lunora/codegen` on startup and on file changes
63
+ * inside the lunora schema directory.
64
+ */
65
+ declare const codegenPlugin: (options: ResolvedLunoraPluginOptions) => Plugin;
66
+ interface ReconcileResult {
67
+ /** `true` when `wrangler.jsonc` was rewritten. */
68
+ changed: boolean;
69
+ /** Human-readable reason when reconciliation was skipped (for logging). */
70
+ reason?: string;
71
+ /** Resolved wrangler path, or `undefined` when none was found. */
72
+ wranglerPath?: string;
73
+ }
74
+ /**
75
+ * Reconcile the codegen-derived cron schedules into the project's
76
+ * `wrangler.jsonc` `triggers.crons` array, preserving comments and formatting
77
+ * via `jsonc-parser`'s structural edits.
78
+ *
79
+ * When `triggers.crons` already matches `cronTriggers`, nothing is written (so
80
+ * we don't churn the file or trip the dev server's file watcher). When the
81
+ * project declares no crons, a stale non-empty array is cleared so removed
82
+ * crons stop firing.
83
+ *
84
+ * This intentionally writes the SAME `triggers.crons` shape the
85
+ * `@lunora/config` validator accepts, so the wrangler validator never fights
86
+ * the generated value.
87
+ */
88
+ declare const reconcileWranglerCrons: (projectRoot: string, cronTriggers: ReadonlyArray<string>) => ReconcileResult;
89
+ /**
90
+ * Dev-only Vite plugin that offers to scaffold `.dev.vars` before the worker
91
+ * boots. `@cloudflare/vite-plugin` loads `.dev.vars` into the worker's `env`,
92
+ * but the file is gitignored — so a fresh clone has none and the worker throws
93
+ * on the first required secret (e.g. `AUTH_SECRET is required`). When a
94
+ * `.dev.vars.example` exists, we prompt to generate `.dev.vars` from it with
95
+ * secrets auto-filled. Identical behaviour to `lunora dev` — both call
96
+ * `ensureDevVariables` from `@lunora/config`.
97
+ *
98
+ * Runs in `configResolved` (awaited by Vite) so it completes before the
99
+ * Cloudflare plugin reads the file. Non-interactive runs decline silently.
100
+ */
101
+ declare const devVariablesPlugin: (options: ResolvedLunoraPluginOptions) => Plugin;
102
+ /**
103
+ * Worker env var the dev tooling sets so the Lunora runtime recognises a
104
+ * development deployment (`@lunora/do`'s `isDevEnvironment`) and therefore
105
+ * streams every RPC dispatch summary to the terminal by default — the
106
+ * `lunora dev` CLI sets the same var via `wrangler dev --var`.
107
+ *
108
+ * It is injected ONLY during `vite` serve, never a production `vite build`, so
109
+ * it can never leak into a deployed worker. A `WORKER_ENV` the user already
110
+ * declares (in `wrangler.jsonc` `[vars]` or `.dev.vars`) takes precedence, so
111
+ * this only fills the gap when none is set.
112
+ */
113
+ declare const DEV_WORKER_ENV_VAR = "WORKER_ENV";
114
+ declare const DEV_WORKER_ENV_VALUE = "development";
115
+ /** The structural slice of `@cloudflare/vite-plugin`'s worker config we read/write. */
116
+ /**
117
+ * Wrap the cloudflare-plugin options so the dev worker's `vars` gain a
118
+ * `WORKER_ENV` of `development` when — and only when — `isServe()` reports a
119
+ * `vite` serve. Any `config` customizer the caller already supplied is
120
+ * preserved and applied first; an existing `WORKER_ENV` wins, so a user
121
+ * override is never clobbered.
122
+ */
123
+ declare const withDevWorkerEnv: (options: CloudflarePluginOptions, isServe: () => boolean) => CloudflarePluginOptions;
124
+ /**
125
+ * A Vite plugin that captures the resolved command (`serve` vs `build`) so
126
+ * {@link withDevWorkerEnv} injects the dev var only during `vite`, plus an
127
+ * `isServe` probe sharing the same closure. `enforce: "pre"` so the command is
128
+ * captured before the cloudflare plugin resolves its worker config.
129
+ */
130
+ declare const createCommandProbe: () => {
131
+ isServe: () => boolean;
132
+ plugin: Plugin;
133
+ };
134
+ /**
135
+ * Mutable, plugin-shared context. The `lunora()` factory creates one instance
136
+ * and threads it through every Lunora sub-plugin, so detection runs once and
137
+ * downstream plugins (codegen, composition, the dev hint) read the same result
138
+ * without re-scanning `package.json`. PLAN4 §2.4.
139
+ */
140
+ interface LunoraPluginContext {
141
+ /** The detected framework + class, populated during `config` / `configResolved`. `undefined` until detection runs. */
142
+ framework?: FrameworkDetection;
143
+ }
144
+ /** Create an empty shared context object for one `lunora()` invocation. */
145
+ /**
146
+ * The virtual module id the Lunora plugin resolves to a generated, class-A
147
+ * worker entry. A class-A template points its wrangler `main` at this id (or
148
+ * re-exports it from a one-line `src/server.ts`) and never hand-writes
149
+ * `createWorker({ httpRouter })` — the plugin composes the framework's SSR
150
+ * handler under `composeWorker`'s `httpRouter` seam for it.
151
+ *
152
+ * Exposed publicly so `@lunora/cli`'s build/deploy path and the templates can
153
+ * reference the same constant rather than re-typing the literal.
154
+ */
155
+ declare const LUNORA_WORKER_VIRTUAL_ID: string;
156
+ /**
157
+ * Per-class-A-framework wiring the generated worker entry needs: how to obtain
158
+ * the framework's SSR handler as a `composeWorker`-compatible `httpRouter`.
159
+ *
160
+ * `imports` is the full import statement(s) the generated entry needs (each
161
+ * framework controls its own import shape — a namespace import, or a named one);
162
+ * `handler` is a JS expression (evaluated in the generated module's scope, where
163
+ * `imports`' symbols are in scope) that yields an `HttpRouterLike`
164
+ * (`{ fetch(request, env?, ctx?) }`). Both are data — not codegen branches — so
165
+ * the set of supported class-A frameworks is one readable table and adding a
166
+ * framework is a pure data edit.
167
+ *
168
+ * Honesty note: these handler expressions encode each framework's *documented*
169
+ * Cloudflare SSR-handler shape — the same expressions the hand-wired template
170
+ * entries use today (React Router's `createRequestHandler` over its virtual
171
+ * server build; SolidStart's `cloudflare-module` handler; TanStack Start's
172
+ * server entry). The plugin just emits them so the developer doesn't.
173
+ */
174
+ interface ClassAWiring {
175
+ /** JS expression yielding the `httpRouter` ({ fetch }), referencing symbols brought in by `imports`. */
176
+ handler: string;
177
+ /** The import statement(s) the generated entry needs to bring `handler`'s symbols into scope. */
178
+ imports: string;
179
+ }
180
+ declare const CLASS_A_WIRING: Readonly<Partial<Record<DetectedFramework, ClassAWiring>>>;
181
+ /**
182
+ * Whether the detected framework is one the plugin can auto-compose. Only
183
+ * class-A frameworks with a known SSR-handler wiring qualify; everything else
184
+ * (class B/C, `none`) falls back to the existing flow.
185
+ */
186
+ declare const isAutoComposable: (context: LunoraPluginContext) => boolean;
187
+ /**
188
+ * Build the source of the virtual class-A worker entry. Pure (no fs / no Vite),
189
+ * so the emitted composition is unit-testable in isolation.
190
+ *
191
+ * The emitted module imports the framework SSR handler + the project's
192
+ * generated artifacts (functions registry, OpenAPI doc, `createShardDO`) and
193
+ * composes them through `composeWorker` — reserved `/_lunora/*` paths route to
194
+ * Lunora, everything else falls through to the framework SSR handler. The
195
+ * `generatedImportBase` MUST be an absolute filesystem path to the `_generated`
196
+ * directory. Virtual modules have no real filesystem path, so relative specifiers
197
+ * like `./lunora/_generated/functions` cannot be resolved by Vite/rolldown from a
198
+ * virtual module id. Absolute paths are resolved correctly in all environments
199
+ * (Vite 8 + rolldown 1.x confirmed).
200
+ */
201
+ declare const buildWorkerEntrySource: (framework: DetectedFramework, generatedImportBase: string, hasContainers?: boolean, useUmbrella?: boolean) => string;
202
+ /**
203
+ * Vite plugin that auto-composes a detected class-A meta-framework's SSR
204
+ * handler with Lunora into one Cloudflare Worker (PLAN4 §2.4 / §3 class-A row).
205
+ *
206
+ * Mechanism: it resolves the {@link LUNORA_WORKER_VIRTUAL_ID} virtual module to
207
+ * a generated worker entry that wires the framework SSR handler under
208
+ * `composeWorker`'s `httpRouter` seam — so the developer never writes
209
+ * `createWorker({ httpRouter })`. The composed worker is an ordinary module
210
+ * entry, so it HMRs under `@cloudflare/vite-plugin` exactly like a hand-written
211
+ * one (PLAN4 M5 risk #5): the virtual entry only imports the framework handler
212
+ * and the generated artifacts, both of which the framework's plugin + codegen
213
+ * already make HMR-aware.
214
+ *
215
+ * Safety: it is a strict no-op unless `context.framework.class === "A"` with a
216
+ * known wiring. For class-C (SPA) projects and undetected frameworks it
217
+ * resolves/loads nothing. `cloudflare: false` does NOT disable the virtual
218
+ * entry — it only means "don't add the Cloudflare Vite plugin a second time"
219
+ * (the user supplied it themselves); the composed worker must still be
220
+ * resolvable so the user-supplied CF plugin can find the wrangler `main`.
221
+ */
222
+ declare const frameworkComposePlugin: (options: ResolvedLunoraPluginOptions, context: LunoraPluginContext) => Plugin;
223
+ /**
224
+ * Vite plugin (serve-only) that formats Lunora worker logs in the terminal.
225
+ * Patches `process.stdout`/`process.stderr` once the dev server is configured
226
+ * and restores them when it closes.
227
+ */
228
+ declare const logStreamPlugin: () => Plugin;
229
+ /** The decision a {@link planViteRemoteBindings} call returns. */
230
+ interface ViteRemotePlan {
231
+ /** Idempotent disposer for the temp config; always present + safe to call. */
232
+ cleanup: () => void;
233
+ /**
234
+ * Absolute path to the materialized temp wrangler config to hand the
235
+ * cloudflare plugin's `configPath`, or `undefined` when remote mode is off
236
+ * or nothing was materialized (no eligible binding, no wrangler file, …).
237
+ */
238
+ configPath?: string;
239
+ /** Whether remote mode was requested for this dev session. */
240
+ enabled: boolean;
241
+ /** Why remote mode didn't take effect despite being requested, for logging. */
242
+ reason?: string;
243
+ }
244
+ /** Inputs to the Vite remote-binding decision — injectable so tests don't touch the env/fs. */
245
+ interface PlanViteRemoteOptions {
246
+ /** Injection seam — defaults to the real materializer. */
247
+ materialize?: typeof materializeRemoteWranglerConfig;
248
+ /** Project root containing `wrangler.jsonc` + the optional `lunora.json`. */
249
+ projectRoot: string;
250
+ /** Injection seam — defaults to the real `lunora.json` reader. */
251
+ readPreference?: typeof readProjectRemotePreference;
252
+ /** The raw `LUNORA_REMOTE` env value; defaults to `process.env.LUNORA_REMOTE`. */
253
+ remoteEnv?: string;
254
+ }
255
+ /**
256
+ * Decide whether the Vite dev worker uses remote bindings and, if so,
257
+ * materialize the temp config. Pure decision + a single fs write via the
258
+ * injected materializer; returns a `cleanup` for the dev server's close hook.
259
+ *
260
+ * There is no `--remote` flag on the Vite path (Vite has no Lunora CLI flags),
261
+ * so the precedence reduces to `LUNORA_REMOTE` env > `lunora.json` `remote`.
262
+ */
263
+ declare const planViteRemoteBindings: (options: PlanViteRemoteOptions) => ViteRemotePlan;
264
+ /**
265
+ * Wrap the cloudflare-plugin options so the dev worker loads the materialized
266
+ * remote temp config (via `configPath`) when — and only when — remote mode is
267
+ * on AND it's a `vite` serve. A `configPath` the caller already set wins (their
268
+ * explicit choice), and during a production build nothing is injected, so the
269
+ * deployed worker is never affected.
270
+ *
271
+ * Returns the wrapped options plus the plan, so `index.ts` can register the
272
+ * cleanup on a close hook. Materialization happens lazily inside the `configPath`
273
+ * resolution path: it's only meaningful during serve, but computing it eagerly
274
+ * is harmless (the materializer is a no-op when disabled) and keeps the wiring
275
+ * simple — the plan is computed once here.
276
+ */
277
+ declare const withRemoteBindings: (options: CloudflarePluginOptions, isServe: () => boolean, plan: ViteRemotePlan) => CloudflarePluginOptions;
278
+ /**
279
+ * A tiny Vite plugin that runs the remote temp-config disposer when the dev
280
+ * server tears down (`buildEnd` fires on close in serve; `closeBundle` covers
281
+ * the build/close path). Idempotent cleanup means firing on both is safe.
282
+ */
283
+ declare const remoteBindingsCleanupPlugin: (cleanup: () => void) => Plugin;
284
+ /** Dev-server path the studio SPA is served from. */
285
+ declare const STUDIO_PATH = "/__lunora";
286
+ /** Static asset routes the studio document references. */
287
+
288
+ /**
289
+ * Build the user-facing studio URL from the dev server's resolved address.
290
+ * Pure so it can be unit-tested without a live server. Prefers Vite's own
291
+ * `resolvedUrls.local` (honours `host` / `base` / https); falls back to the raw
292
+ * socket address, bracketing IPv6 and normalising the wildcard host.
293
+ */
294
+ declare const buildStudioUrl: (input: {
295
+ address?: AddressInfo | string;
296
+ base?: string;
297
+ resolvedLocal?: string;
298
+ }) => string;
299
+ /**
300
+ * Vite plugin that serves the composed Lunora studio at
301
+ * {@link STUDIO_PATH} during dev and prints its URL once the server is
302
+ * listening. Dev-only (`apply: "serve"`); it adds nothing to production builds.
303
+ *
304
+ * Because `lunora dev` spawns Vite, this makes the studio available on
305
+ * `lunora dev` and on a plain `vite` with no per-project files. The studio
306
+ * is served as a prebuilt static bundle, independent of the host app.
307
+ */
308
+ declare const studioPlugin: () => Plugin;
309
+ /**
310
+ * When a module under `lunora/` throws while the Worker entry is first
311
+ * evaluated, `@cloudflare/vite-plugin` surfaces the failure from deep inside
312
+ * its `runner-worker` running in `workerd`. The real error crosses a workerd
313
+ * RPC boundary on the way out, which drops the user-code stack frames — so all
314
+ * the dev server sees is a bare, file-less message like:
315
+ *
316
+ * ```
317
+ * TypeError: Cannot read properties of undefined (reading 'string')
318
+ * at runInRunnerObject (workers/runner-worker/index.js:107:3)
319
+ * at getWorkerEntryExportTypes (workers/runner-worker/index.js:246:24)
320
+ * ```
321
+ *
322
+ * The classic cause is a **circular import**: a `lunora/` query/mutation/action
323
+ * module runs `mutation({ args: { x: v.string() } })` at the top level while the
324
+ * module it imported `v`/`query`/`mutation` from is still mid-initialization, so
325
+ * those bindings read as `undefined`. The message names a validator method
326
+ * (`'string'`, `'id'`, …) but never the file.
327
+ *
328
+ * We can't recover the dropped frames at this layer, but we can recognise the
329
+ * shape of the failure and append an actionable hint pointing at the likely
330
+ * cause — turning a dead-end stack into something a user can act on.
331
+ */
332
+ declare const WORKER_STARTUP_HINT: string;
333
+ /**
334
+ * True when `error` looks like a Worker-entry evaluation failure routed through
335
+ * `@cloudflare/vite-plugin`'s runner worker (the stack references the runner
336
+ * worker / export-types probe). Kept narrow so we only annotate this specific
337
+ * class of dev-startup error.
338
+ */
339
+ declare const isWorkerEntryEvalError: (error: unknown) => boolean;
340
+ /**
341
+ * Append {@link WORKER_STARTUP_HINT} to a recognised Worker-entry eval error
342
+ * (idempotently). Any other value is returned untouched.
343
+ */
344
+ declare const augmentWorkerStartupError: (error: unknown) => unknown;
345
+ /**
346
+ * Wrap the startup hooks of `@cloudflare/vite-plugin`'s plugins so a Worker-entry
347
+ * evaluation failure carries the Lunora hint. Returns a new array; the input
348
+ * plugins are shallow-cloned (never mutated in place) so re-using the cloudflare
349
+ * plugin instances elsewhere stays safe.
350
+ */
351
+ declare const withWorkerStartupHint: (plugins: ReadonlyArray<Plugin>) => Plugin[];
352
+ /**
353
+ * Vite plugin that validates the project's `wrangler.jsonc` against the
354
+ * bindings implied by `lunora/schema.ts`. Throws (Vite renders nicely) on
355
+ * missing requirements during `configResolved`. Delegates the parsing /
356
+ * validation logic to `@lunora/config` so the rules stay in lockstep with
357
+ * the CLI (`lunora deploy`).
358
+ */
359
+ declare const wranglerValidatorPlugin: (options: ResolvedLunoraPluginOptions) => Plugin;
360
+ /**
361
+ * Lunora Vite plugin. Returns a flat array of Vite plugins that:
362
+ *
363
+ * 1. Run `@lunora/codegen` on startup + on schema file changes.
364
+ * 2. Validate the project's `wrangler.jsonc` against the schema's implied bindings.
365
+ * 3. Inject `@visulima/vite-overlay` for runtime error overlays (unless `overlay: false`).
366
+ * 4. Include `@cloudflare/vite-plugin` so users get one-import setup (unless `cloudflare: false`).
367
+ *
368
+ * `@cloudflare/vite-plugin` and `@visulima/vite-overlay` are direct dependencies,
369
+ * so they're imported statically — opt out per-feature via the options rather
370
+ * than relying on whether they're installed.
371
+ */
372
+ declare const lunora: (options?: LunoraPluginOptions) => LunoraPlugins;
373
+ declare const VERSION = "0.0.0";
374
+ export { CLASS_A_WIRING, type ClassAWiring, type CloudflarePluginOptions, DEV_WORKER_ENV_VALUE, DEV_WORKER_ENV_VAR, LUNORA_WORKER_VIRTUAL_ID, type LunoraPluginOptions, type LunoraPlugins, type OverlayPluginOptions, type PlanViteRemoteOptions, type ReconcileResult, type ResolvedLunoraPluginOptions, STUDIO_PATH, VERSION, type ViteRemotePlan, WORKER_STARTUP_HINT, augmentWorkerStartupError, buildStudioUrl, buildWorkerEntrySource, codegenPlugin, createCommandProbe, devVariablesPlugin, frameworkComposePlugin, isAutoComposable, isWorkerEntryEvalError, logStreamPlugin, lunora, planViteRemoteBindings, reconcileWranglerCrons, remoteBindingsCleanupPlugin, studioPlugin, withDevWorkerEnv, withRemoteBindings, withWorkerStartupHint, wranglerValidatorPlugin };
package/dist/index.mjs ADDED
@@ -0,0 +1,155 @@
1
+ import { cloudflare } from '@cloudflare/vite-plugin';
2
+ import errorOverlayPlugin from '@visulima/vite-overlay';
3
+ import { detectAgentRules, claimAgentRulesHint, AGENT_RULES_HINT, detectFramework } from '@lunora/config';
4
+ export { detectFramework } from '@lunora/config';
5
+ import { l as lunoraLine } from './packem_shared/log-BjO9EWah.mjs';
6
+ import codegenPlugin from './packem_shared/codegenPlugin-MuvbqAP8.mjs';
7
+ import devVariablesPlugin from './packem_shared/devVariablesPlugin-CVjkQay7.mjs';
8
+ import { createCommandProbe, withDevWorkerEnv } from './packem_shared/DEV_WORKER_ENV_VALUE-Coo6bgVz.mjs';
9
+ export { DEV_WORKER_ENV_VALUE, DEV_WORKER_ENV_VAR } from './packem_shared/DEV_WORKER_ENV_VALUE-Coo6bgVz.mjs';
10
+ import { frameworkComposePlugin } from './packem_shared/CLASS_A_WIRING-CZVcjgKo.mjs';
11
+ export { CLASS_A_WIRING, LUNORA_WORKER_VIRTUAL_ID, buildWorkerEntrySource, isAutoComposable } from './packem_shared/CLASS_A_WIRING-CZVcjgKo.mjs';
12
+ import logStreamPlugin from './packem_shared/logStreamPlugin-CqvZ17kd.mjs';
13
+ import { planViteRemoteBindings, remoteBindingsCleanupPlugin, withRemoteBindings } from './packem_shared/planViteRemoteBindings-QN5ncUS1.mjs';
14
+ import { studioPlugin } from './packem_shared/STUDIO_PATH-5ppCdBHa.mjs';
15
+ export { STUDIO_PATH, buildStudioUrl } from './packem_shared/STUDIO_PATH-5ppCdBHa.mjs';
16
+ import { withWorkerStartupHint } from './packem_shared/WORKER_STARTUP_HINT-DhsXUW8k.mjs';
17
+ export { WORKER_STARTUP_HINT, augmentWorkerStartupError, isWorkerEntryEvalError } from './packem_shared/WORKER_STARTUP_HINT-DhsXUW8k.mjs';
18
+ import { wranglerValidatorPlugin } from './packem_shared/wranglerValidatorPlugin-CEoJEghS.mjs';
19
+ export { reconcileWranglerCrons } from './packem_shared/reconcileWranglerCrons-PxGwfCp_.mjs';
20
+
21
+ const agentRulesHintPlugin = (options) => {
22
+ return {
23
+ apply: "serve",
24
+ configureServer(server) {
25
+ return () => {
26
+ if (detectAgentRules(options.projectRoot).installed || !claimAgentRulesHint()) {
27
+ return;
28
+ }
29
+ server.config.logger.warn(`
30
+ ${lunoraLine(AGENT_RULES_HINT)}
31
+ `);
32
+ };
33
+ },
34
+ name: "lunora:agent-rules-hint"
35
+ };
36
+ };
37
+
38
+ const createPluginContext = () => {
39
+ return {};
40
+ };
41
+ const FRAMEWORK_LABELS = {
42
+ astro: "Astro",
43
+ none: "standalone (SPA / SSR-less)",
44
+ nuxt: "Nuxt",
45
+ "react-router": "React Router",
46
+ "solid-start": "SolidStart",
47
+ sveltekit: "SvelteKit",
48
+ "tanstack-start": "TanStack Start",
49
+ "tanstack-start-solid": "TanStack Start (Solid)"
50
+ };
51
+ const formatFrameworkDetection = (detection) => {
52
+ const label = FRAMEWORK_LABELS[detection.framework];
53
+ if (detection.framework === "none") {
54
+ return lunoraLine("no meta-framework detected — running standalone.");
55
+ }
56
+ if (detection.class === "B") {
57
+ return lunoraLine(`detected ${label} — composition is handled by the framework adapter.`);
58
+ }
59
+ return lunoraLine(`detected ${label} — composing the Lunora worker into one Cloudflare Worker.`);
60
+ };
61
+ const frameworkDetectPlugin = (options, context) => {
62
+ let logged = false;
63
+ const detect = () => {
64
+ context.framework ??= detectFramework(options.projectRoot);
65
+ };
66
+ return {
67
+ config() {
68
+ detect();
69
+ },
70
+ configResolved() {
71
+ detect();
72
+ },
73
+ configureServer() {
74
+ detect();
75
+ if (!logged && context.framework !== void 0) {
76
+ logged = true;
77
+ console.info(formatFrameworkDetection(context.framework));
78
+ }
79
+ },
80
+ name: "lunora:framework-detect"
81
+ };
82
+ };
83
+
84
+ const resolveOptions = (options) => {
85
+ const input = options ?? {};
86
+ const schemaDirectory = input.schemaDir ?? "lunora";
87
+ let cloudflareOption;
88
+ if (input.cloudflare === false) {
89
+ cloudflareOption = false;
90
+ } else if (input.cloudflare === true || input.cloudflare === void 0) {
91
+ cloudflareOption = {};
92
+ } else {
93
+ cloudflareOption = input.cloudflare;
94
+ }
95
+ const overlayDefaults = { forwardedConsoleMethods: ["error", "warn"] };
96
+ let overlayOption;
97
+ if (input.overlay === false) {
98
+ overlayOption = false;
99
+ } else if (input.overlay === true || input.overlay === void 0) {
100
+ overlayOption = { ...overlayDefaults };
101
+ } else {
102
+ overlayOption = { ...overlayDefaults, ...input.overlay };
103
+ }
104
+ return {
105
+ apiSpec: input.apiSpec ?? "openapi",
106
+ cloudflare: cloudflareOption,
107
+ studio: input.studio ?? true,
108
+ generatedDir: input.generatedDir ?? `${schemaDirectory}/_generated`,
109
+ overlay: overlayOption,
110
+ projectRoot: input.projectRoot ?? process.cwd(),
111
+ schemaDir: schemaDirectory,
112
+ validateWrangler: input.validateWrangler ?? true
113
+ };
114
+ };
115
+ const lunora = (options) => {
116
+ const resolved = resolveOptions(options);
117
+ const context = createPluginContext();
118
+ const { isServe, plugin: commandProbe } = createCommandProbe();
119
+ const plugins = [
120
+ commandProbe,
121
+ frameworkDetectPlugin(resolved, context),
122
+ // Reads the detected framework off `context` and, for a class-A
123
+ // framework (and only when the CF integration is on), resolves the
124
+ // `virtual:lunora/worker` entry to a `composeWorker`-based worker that
125
+ // routes `/_lunora/*` to Lunora and falls through to the framework SSR
126
+ // handler — so the template never hand-wires `createWorker({ httpRouter })`.
127
+ // A strict no-op for class-C and the `cloudflare: false` BYO path.
128
+ frameworkComposePlugin(resolved, context),
129
+ devVariablesPlugin(resolved),
130
+ codegenPlugin(resolved),
131
+ logStreamPlugin(),
132
+ agentRulesHintPlugin(resolved)
133
+ ];
134
+ if (resolved.studio) {
135
+ plugins.push(studioPlugin());
136
+ }
137
+ if (resolved.validateWrangler) {
138
+ plugins.push(wranglerValidatorPlugin(resolved));
139
+ }
140
+ if (resolved.overlay !== false) {
141
+ plugins.push(errorOverlayPlugin(resolved.overlay));
142
+ }
143
+ if (resolved.cloudflare !== false) {
144
+ const remotePlan = planViteRemoteBindings({ projectRoot: resolved.projectRoot });
145
+ if (remotePlan.enabled && remotePlan.configPath !== void 0) {
146
+ plugins.push(remoteBindingsCleanupPlugin(remotePlan.cleanup));
147
+ }
148
+ const cloudflareOptions = withRemoteBindings(withDevWorkerEnv(resolved.cloudflare, isServe), isServe, remotePlan);
149
+ plugins.push(...withWorkerStartupHint(cloudflare(cloudflareOptions)));
150
+ }
151
+ return plugins;
152
+ };
153
+ const VERSION = "0.0.0";
154
+
155
+ export { VERSION, codegenPlugin, createCommandProbe, devVariablesPlugin, frameworkComposePlugin, logStreamPlugin, lunora, planViteRemoteBindings, remoteBindingsCleanupPlugin, studioPlugin, withDevWorkerEnv, withRemoteBindings, withWorkerStartupHint, wranglerValidatorPlugin };
@@ -0,0 +1,106 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve, join } from 'node:path';
3
+
4
+ const LUNORA_WORKER_VIRTUAL_ID = "virtual:lunora/worker";
5
+ const RESOLVED_VIRTUAL_PREFIX = "\0";
6
+ const RESOLVED_LUNORA_WORKER_ID = `${RESOLVED_VIRTUAL_PREFIX}${LUNORA_WORKER_VIRTUAL_ID}`;
7
+ const TRAILING_SLASH = /\/$/;
8
+ const CLASS_A_WIRING = {
9
+ "react-router": {
10
+ // `@react-router/dev` provides `virtual:react-router/server-build`; the
11
+ // runtime helper turns it into a `(request) => Promise<Response>`, which
12
+ // is exactly the `httpRouter.fetch` contract. Needs a named import.
13
+ handler: '{ fetch: (request) => createRequestHandler(() => import("virtual:react-router/server-build"), import.meta.env.MODE)(request) }',
14
+ imports: 'import { createRequestHandler } from "react-router";'
15
+ },
16
+ "solid-start": {
17
+ // SolidStart's `cloudflare-module` preset default-exports a fetch
18
+ // handler — structurally an `HttpRouterLike` already.
19
+ handler: "ssrModule.default",
20
+ imports: 'import * as ssrModule from "@solidjs/start/server-handler";'
21
+ },
22
+ "tanstack-start": {
23
+ // TanStack Start's server entry default-exports a `{ fetch }` handler.
24
+ handler: "ssrModule.default",
25
+ imports: 'import * as ssrModule from "@tanstack/react-start/server-entry";'
26
+ },
27
+ "tanstack-start-solid": {
28
+ // TanStack Start (Solid)'s server entry default-exports a `{ fetch }`
29
+ // handler — same shape as the React variant, different package.
30
+ handler: "ssrModule.default",
31
+ imports: 'import * as ssrModule from "@tanstack/solid-start/server-entry";'
32
+ }
33
+ };
34
+ const isAutoComposable = (context) => {
35
+ const detected = context.framework;
36
+ return detected?.class === "A" && CLASS_A_WIRING[detected.framework] !== void 0;
37
+ };
38
+ const isWorkerVirtualActive = (context) => isAutoComposable(context);
39
+ const projectUsesUmbrella = (projectRoot) => {
40
+ try {
41
+ const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8"));
42
+ return ["dependencies", "devDependencies", "peerDependencies"].some((field) => pkg[field]?.["lunorash"] !== void 0);
43
+ } catch {
44
+ return false;
45
+ }
46
+ };
47
+ const buildWorkerEntrySource = (framework, generatedImportBase, hasContainers = false, useUmbrella = false) => {
48
+ const wiring = CLASS_A_WIRING[framework];
49
+ if (wiring === void 0) {
50
+ throw new Error(`[lunora] no class-A worker wiring for framework "${framework}"`);
51
+ }
52
+ const runtimeModule = useUmbrella ? "lunorash/runtime" : "@lunora/runtime";
53
+ const containersReexport = hasContainers ? `
54
+ export * from "${generatedImportBase}/containers";
55
+ ` : "";
56
+ return `// Generated by @lunora/vite — class-A worker composition (PLAN4 M2).
57
+ // Do not edit: emitted from the detected framework (${framework}). Point your
58
+ // wrangler \`main\` here (or re-export it) instead of hand-wiring createWorker.
59
+ import { composeWorker } from "${runtimeModule}";
60
+ ${wiring.imports}
61
+ import { LUNORA_FUNCTIONS } from "${generatedImportBase}/functions";
62
+ import { openApiSpec } from "${generatedImportBase}/openapi";
63
+ import { createShardDO } from "${generatedImportBase}/shard";
64
+
65
+ export const ShardDO = createShardDO();
66
+ ${containersReexport}
67
+
68
+ let worker;
69
+
70
+ export default {
71
+ async fetch(request, env, context) {
72
+ worker ??= composeWorker({
73
+ functions: LUNORA_FUNCTIONS,
74
+ httpRouter: ${wiring.handler},
75
+ openApiSpec,
76
+ routes: {},
77
+ shardDO: env.SHARD,
78
+ });
79
+
80
+ return worker.fetch(request, env, context);
81
+ },
82
+ };
83
+ `;
84
+ };
85
+ const frameworkComposePlugin = (options, context) => {
86
+ const generatedImportBase = resolve(options.projectRoot, options.generatedDir.replace(TRAILING_SLASH, ""));
87
+ const useUmbrella = projectUsesUmbrella(options.projectRoot);
88
+ return {
89
+ load(id) {
90
+ if (id === RESOLVED_LUNORA_WORKER_ID && isWorkerVirtualActive(context) && context.framework !== void 0) {
91
+ const hasContainers = existsSync(join(generatedImportBase, "containers.ts"));
92
+ return buildWorkerEntrySource(context.framework.framework, generatedImportBase, hasContainers, useUmbrella);
93
+ }
94
+ return void 0;
95
+ },
96
+ name: "lunora:framework-compose",
97
+ resolveId(id) {
98
+ if (id === LUNORA_WORKER_VIRTUAL_ID && isWorkerVirtualActive(context)) {
99
+ return RESOLVED_LUNORA_WORKER_ID;
100
+ }
101
+ return void 0;
102
+ }
103
+ };
104
+ };
105
+
106
+ export { CLASS_A_WIRING, LUNORA_WORKER_VIRTUAL_ID, RESOLVED_LUNORA_WORKER_ID, buildWorkerEntrySource, frameworkComposePlugin, isAutoComposable };
@@ -0,0 +1,36 @@
1
+ const DEV_WORKER_ENV_VAR = "WORKER_ENV";
2
+ const DEV_WORKER_ENV_VALUE = "development";
3
+ const withDevWorkerEnv = (options, isServe) => {
4
+ const userConfig = options.config;
5
+ return {
6
+ ...options,
7
+ config: (workerConfig) => {
8
+ if (typeof userConfig === "function") {
9
+ const partial = userConfig(workerConfig);
10
+ if (partial) {
11
+ Object.assign(workerConfig, partial);
12
+ }
13
+ } else if (userConfig) {
14
+ Object.assign(workerConfig, userConfig);
15
+ }
16
+ if (isServe()) {
17
+ workerConfig.vars = { [DEV_WORKER_ENV_VAR]: DEV_WORKER_ENV_VALUE, ...workerConfig.vars };
18
+ }
19
+ }
20
+ };
21
+ };
22
+ const createCommandProbe = () => {
23
+ let command;
24
+ return {
25
+ isServe: () => command === "serve",
26
+ plugin: {
27
+ config(_userConfig, env) {
28
+ command = env.command;
29
+ },
30
+ enforce: "pre",
31
+ name: "lunora:command-probe"
32
+ }
33
+ };
34
+ };
35
+
36
+ export { DEV_WORKER_ENV_VALUE, DEV_WORKER_ENV_VAR, createCommandProbe, withDevWorkerEnv };