@lunora/rspack 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,137 @@
1
+ import { R as ResolvedLunoraRspackOptions, L as LunoraRspackOptions } from "./packem_shared/types.d-DcuVeaxB.mjs";
2
+ import '@lunora/codegen';
3
+ /**
4
+ * The slice of the Rspack compiler this plugin touches, projected structurally.
5
+ *
6
+ * `@rspack/core` is an OPTIONAL peer here, so its `Compiler` type must not appear
7
+ * in the emitted `.d.ts` — a consumer without it installed would fail to resolve
8
+ * the reference. Projecting only what we tap also makes the plugin work unchanged
9
+ * under webpack 5, whose plugin API these members come from.
10
+ *
11
+ * A projection is only safe while it stays a real subset of the thing it
12
+ * projects, and a renamed hook or a reshaped dependency set upstream would
13
+ * silently stop it being one — this package would keep compiling and fail at the
14
+ * first `rspack build`. `__tests__/compiler-projection.test.ts` asserts a real
15
+ * `Compiler`/`Compilation` still satisfies every member below, so that drift is a
16
+ * type error here rather than a runtime one in a user's project.
17
+ */
18
+ /** A `lite-tapable` async hook, narrowed to the `tapPromise` registration we use. */
19
+ interface AsyncTapHook<T> {
20
+ tapPromise: (name: string, callback: (value: T) => Promise<void>) => void;
21
+ }
22
+ /** The mutable dependency set a compilation exposes for watch invalidation. */
23
+ interface DependencySet {
24
+ add: (dependency: string) => void;
25
+ }
26
+ /** The slice of `Compilation` this plugin reads or mutates. */
27
+ interface CompilationLike {
28
+ /** Directories whose contents invalidate this compilation when they change. */
29
+ contextDependencies: DependencySet;
30
+ /**
31
+ * Build errors. Pushing here fails the build — `stats.hasErrors()` goes true,
32
+ * assets are not emitted, and the CLI exits non-zero — WITHOUT aborting a
33
+ * watch session, which is why every blocking finding this plugin reports goes
34
+ * here rather than out of a rejected hook.
35
+ */
36
+ errors: Error[];
37
+ /** Individual files that invalidate this compilation when they change. */
38
+ fileDependencies: DependencySet;
39
+ }
40
+ /** The slice of `Compiler` this plugin taps. */
41
+ interface CompilerLike {
42
+ hooks: {
43
+ /** After each compilation seals — where dependencies and findings are registered. */
44
+ afterCompile: AsyncTapHook<CompilationLike>;
45
+ /** Before each compilation — where codegen runs, so the build sees fresh output. */
46
+ beforeCompile: AsyncTapHook<unknown>;
47
+ };
48
+ /**
49
+ * `true` under `compiler.watch()`, `false` for a one-shot `compiler.run()`.
50
+ *
51
+ * Only ever read from INSIDE a hook callback. `apply()` runs during
52
+ * `createCompiler()`, before `watch()` assigns this — read at tap time it is
53
+ * unconditionally `false`, which silently inverts every decision that depends
54
+ * on it.
55
+ */
56
+ watchMode?: boolean;
57
+ }
58
+ /**
59
+ * The Rspack (and webpack 5) plugin: runs `@lunora/codegen` before every
60
+ * compilation, provisions the Cloudflare bindings the code implies into
61
+ * `wrangler.jsonc`, and validates that config.
62
+ *
63
+ * Everything hangs off two hooks. `beforeCompile` carries the startup and
64
+ * regeneration pass, so a compilation never reads a stale `_generated/*`.
65
+ * `afterCompile` registers the watch dependencies and reports whatever the pass
66
+ * found into `compilation.errors`.
67
+ *
68
+ * **Findings are reported, never thrown.** A rejected hook takes a watch session
69
+ * down with it; `compilation.errors` fails a one-shot build just as hard (assets
70
+ * are not emitted and the CLI exits non-zero) while leaving the watcher up to
71
+ * regenerate on the next save. That is what lets one code path serve both modes,
72
+ * and `compiler.watchMode` then decides only whether a *schema advisory* is worth
73
+ * failing over — a production build refuses one, exactly as `vite build` and
74
+ * `lunora deploy` do, and a watch rebuild logs it and carries on.
75
+ */
76
+ declare class LunoraRspackPlugin {
77
+ #private;
78
+ constructor(options: ResolvedLunoraRspackOptions);
79
+ /**
80
+ * Run the once-per-session dev preparation (`.dev.vars` scaffolding, the
81
+ * agent-rules hint) and mark the session started.
82
+ *
83
+ * Public because ORDER matters and only the caller knows it: wrangler reads
84
+ * `.dev.vars` once, while resolving bindings at startup. A host that spawns
85
+ * the Worker — `@lunora/rspack/rsbuild` does, from `onBeforeStartDevServer`,
86
+ * which Rsbuild runs before the first compilation — must therefore await this
87
+ * first, or the very first `rsbuild dev` after a fresh clone boots a Worker
88
+ * with every secret `undefined`. That surfaces as auth failures rather than a
89
+ * clear error, and the SECOND run works, because `.dev.vars` is on disk by
90
+ * then. Neither creating the file nor rewriting `wrangler.jsonc` afterwards
91
+ * rescues the running process.
92
+ *
93
+ * Idempotent: the pass below sees `#started` and skips its own call.
94
+ */
95
+ prepareDevSession(): Promise<void>;
96
+ /**
97
+ * Run a watch-mode codegen pass now, ahead of the first compilation.
98
+ *
99
+ * Public for the same ordering reason as {@link prepareDevSession}: the
100
+ * Worker `@lunora/rspack/rsbuild` spawns is bundled by `wrangler dev` at
101
+ * startup, and it imports `_generated/app` — which, on a fresh clone, only
102
+ * this pass writes. Spawned first, wrangler dies on an unresolvable import
103
+ * before the first compilation ever generates it. The first compilation then
104
+ * finds the fingerprint unchanged and skips its own pass.
105
+ */
106
+ generateForDev(): Promise<void>;
107
+ /** Rspack's plugin entry point. */
108
+ apply(compiler: CompilerLike): void;
109
+ }
110
+ /**
111
+ * Lunora Rspack plugin. Add it to `plugins` in `rspack.config.*` (or
112
+ * `tools.rspack.plugins` under Rsbuild) and it will:
113
+ *
114
+ * 1. Run `@lunora/codegen` before every compilation, and again on any edit
115
+ * under `lunora/` in watch mode.
116
+ * 2. Write the Cloudflare bindings your code implies into `wrangler.jsonc`,
117
+ * plus your cron triggers and the compatibility date.
118
+ * 3. Validate that config against the schema's requirements (`validateWrangler`).
119
+ * 4. Run the project's `postcodegen` hook.
120
+ *
121
+ * A production build fails on an ERROR-level advisory or platform diagnostic; a
122
+ * watch rebuild logs them and carries on.
123
+ *
124
+ * **It does not run your Worker.** Rspack has no `@cloudflare/vite-plugin`
125
+ * equivalent — no workerd/miniflare integration — so this plugin builds the
126
+ * client half and `wrangler dev` (or `lunora dev`) runs the Worker, which is the
127
+ * same split as `@lunora/vite`'s `cloudflare: false` path. The Vite plugin's
128
+ * dev-server features therefore have no counterpart here: no error overlay, no
129
+ * embedded Studio, no worker log streaming, no remote-binding injection. Use
130
+ * `lunora dev` for those, or `@lunora/vite` if you want them in-process.
131
+ *
132
+ * The plugin API tapped here is webpack 5's, so the same instance works in a
133
+ * webpack build unchanged.
134
+ */
135
+ declare const lunoraRspack: (options?: LunoraRspackOptions) => LunoraRspackPlugin;
136
+ declare const VERSION: string;
137
+ export { type CompilerLike, type LunoraRspackOptions, LunoraRspackPlugin, VERSION, lunoraRspack };
@@ -0,0 +1,137 @@
1
+ import { R as ResolvedLunoraRspackOptions, L as LunoraRspackOptions } from "./packem_shared/types.d-DcuVeaxB.js";
2
+ import '@lunora/codegen';
3
+ /**
4
+ * The slice of the Rspack compiler this plugin touches, projected structurally.
5
+ *
6
+ * `@rspack/core` is an OPTIONAL peer here, so its `Compiler` type must not appear
7
+ * in the emitted `.d.ts` — a consumer without it installed would fail to resolve
8
+ * the reference. Projecting only what we tap also makes the plugin work unchanged
9
+ * under webpack 5, whose plugin API these members come from.
10
+ *
11
+ * A projection is only safe while it stays a real subset of the thing it
12
+ * projects, and a renamed hook or a reshaped dependency set upstream would
13
+ * silently stop it being one — this package would keep compiling and fail at the
14
+ * first `rspack build`. `__tests__/compiler-projection.test.ts` asserts a real
15
+ * `Compiler`/`Compilation` still satisfies every member below, so that drift is a
16
+ * type error here rather than a runtime one in a user's project.
17
+ */
18
+ /** A `lite-tapable` async hook, narrowed to the `tapPromise` registration we use. */
19
+ interface AsyncTapHook<T> {
20
+ tapPromise: (name: string, callback: (value: T) => Promise<void>) => void;
21
+ }
22
+ /** The mutable dependency set a compilation exposes for watch invalidation. */
23
+ interface DependencySet {
24
+ add: (dependency: string) => void;
25
+ }
26
+ /** The slice of `Compilation` this plugin reads or mutates. */
27
+ interface CompilationLike {
28
+ /** Directories whose contents invalidate this compilation when they change. */
29
+ contextDependencies: DependencySet;
30
+ /**
31
+ * Build errors. Pushing here fails the build — `stats.hasErrors()` goes true,
32
+ * assets are not emitted, and the CLI exits non-zero — WITHOUT aborting a
33
+ * watch session, which is why every blocking finding this plugin reports goes
34
+ * here rather than out of a rejected hook.
35
+ */
36
+ errors: Error[];
37
+ /** Individual files that invalidate this compilation when they change. */
38
+ fileDependencies: DependencySet;
39
+ }
40
+ /** The slice of `Compiler` this plugin taps. */
41
+ interface CompilerLike {
42
+ hooks: {
43
+ /** After each compilation seals — where dependencies and findings are registered. */
44
+ afterCompile: AsyncTapHook<CompilationLike>;
45
+ /** Before each compilation — where codegen runs, so the build sees fresh output. */
46
+ beforeCompile: AsyncTapHook<unknown>;
47
+ };
48
+ /**
49
+ * `true` under `compiler.watch()`, `false` for a one-shot `compiler.run()`.
50
+ *
51
+ * Only ever read from INSIDE a hook callback. `apply()` runs during
52
+ * `createCompiler()`, before `watch()` assigns this — read at tap time it is
53
+ * unconditionally `false`, which silently inverts every decision that depends
54
+ * on it.
55
+ */
56
+ watchMode?: boolean;
57
+ }
58
+ /**
59
+ * The Rspack (and webpack 5) plugin: runs `@lunora/codegen` before every
60
+ * compilation, provisions the Cloudflare bindings the code implies into
61
+ * `wrangler.jsonc`, and validates that config.
62
+ *
63
+ * Everything hangs off two hooks. `beforeCompile` carries the startup and
64
+ * regeneration pass, so a compilation never reads a stale `_generated/*`.
65
+ * `afterCompile` registers the watch dependencies and reports whatever the pass
66
+ * found into `compilation.errors`.
67
+ *
68
+ * **Findings are reported, never thrown.** A rejected hook takes a watch session
69
+ * down with it; `compilation.errors` fails a one-shot build just as hard (assets
70
+ * are not emitted and the CLI exits non-zero) while leaving the watcher up to
71
+ * regenerate on the next save. That is what lets one code path serve both modes,
72
+ * and `compiler.watchMode` then decides only whether a *schema advisory* is worth
73
+ * failing over — a production build refuses one, exactly as `vite build` and
74
+ * `lunora deploy` do, and a watch rebuild logs it and carries on.
75
+ */
76
+ declare class LunoraRspackPlugin {
77
+ #private;
78
+ constructor(options: ResolvedLunoraRspackOptions);
79
+ /**
80
+ * Run the once-per-session dev preparation (`.dev.vars` scaffolding, the
81
+ * agent-rules hint) and mark the session started.
82
+ *
83
+ * Public because ORDER matters and only the caller knows it: wrangler reads
84
+ * `.dev.vars` once, while resolving bindings at startup. A host that spawns
85
+ * the Worker — `@lunora/rspack/rsbuild` does, from `onBeforeStartDevServer`,
86
+ * which Rsbuild runs before the first compilation — must therefore await this
87
+ * first, or the very first `rsbuild dev` after a fresh clone boots a Worker
88
+ * with every secret `undefined`. That surfaces as auth failures rather than a
89
+ * clear error, and the SECOND run works, because `.dev.vars` is on disk by
90
+ * then. Neither creating the file nor rewriting `wrangler.jsonc` afterwards
91
+ * rescues the running process.
92
+ *
93
+ * Idempotent: the pass below sees `#started` and skips its own call.
94
+ */
95
+ prepareDevSession(): Promise<void>;
96
+ /**
97
+ * Run a watch-mode codegen pass now, ahead of the first compilation.
98
+ *
99
+ * Public for the same ordering reason as {@link prepareDevSession}: the
100
+ * Worker `@lunora/rspack/rsbuild` spawns is bundled by `wrangler dev` at
101
+ * startup, and it imports `_generated/app` — which, on a fresh clone, only
102
+ * this pass writes. Spawned first, wrangler dies on an unresolvable import
103
+ * before the first compilation ever generates it. The first compilation then
104
+ * finds the fingerprint unchanged and skips its own pass.
105
+ */
106
+ generateForDev(): Promise<void>;
107
+ /** Rspack's plugin entry point. */
108
+ apply(compiler: CompilerLike): void;
109
+ }
110
+ /**
111
+ * Lunora Rspack plugin. Add it to `plugins` in `rspack.config.*` (or
112
+ * `tools.rspack.plugins` under Rsbuild) and it will:
113
+ *
114
+ * 1. Run `@lunora/codegen` before every compilation, and again on any edit
115
+ * under `lunora/` in watch mode.
116
+ * 2. Write the Cloudflare bindings your code implies into `wrangler.jsonc`,
117
+ * plus your cron triggers and the compatibility date.
118
+ * 3. Validate that config against the schema's requirements (`validateWrangler`).
119
+ * 4. Run the project's `postcodegen` hook.
120
+ *
121
+ * A production build fails on an ERROR-level advisory or platform diagnostic; a
122
+ * watch rebuild logs them and carries on.
123
+ *
124
+ * **It does not run your Worker.** Rspack has no `@cloudflare/vite-plugin`
125
+ * equivalent — no workerd/miniflare integration — so this plugin builds the
126
+ * client half and `wrangler dev` (or `lunora dev`) runs the Worker, which is the
127
+ * same split as `@lunora/vite`'s `cloudflare: false` path. The Vite plugin's
128
+ * dev-server features therefore have no counterpart here: no error overlay, no
129
+ * embedded Studio, no worker log streaming, no remote-binding injection. Use
130
+ * `lunora dev` for those, or `@lunora/vite` if you want them in-process.
131
+ *
132
+ * The plugin API tapped here is webpack 5's, so the same instance works in a
133
+ * webpack build unchanged.
134
+ */
135
+ declare const lunoraRspack: (options?: LunoraRspackOptions) => LunoraRspackPlugin;
136
+ declare const VERSION: string;
137
+ export { type CompilerLike, type LunoraRspackOptions, LunoraRspackPlugin, VERSION, lunoraRspack };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{createRequire as r}from"node:module";import{r as e}from"./packem_shared/options-rWtSt-oe.mjs";import{LunoraRspackPlugin as t}from"./packem_shared/LunoraRspackPlugin-DPrjGG-k.mjs";const s=o=>new t(e(o)),i=r(import.meta.url)("../package.json").version;export{t as LunoraRspackPlugin,i as VERSION,s as lunoraRspack};
@@ -0,0 +1,3 @@
1
+ import{createHash as g}from"node:crypto";import{existsSync as u,readFileSync as p}from"node:fs";import{join as m,resolve as v}from"node:path";import{createCodegenProject as w,refreshCodegenProject as R,runCodegen as j,findTsconfig as a,fingerprintSchemaSources as S}from"@lunora/codegen";import{LUNORA_TAG as y,reportCodegenFindings as E,blockingFindingsMessage as D,isCodegenDisabled as P,CODEGEN_ENV as c,lunoraLine as n,runPostCodegenHook as b,ensureDevVariables as k,createConfirm as C,fillDevSecrets as F,ensureDevWorkerEnv as A,detectAgentRules as L,claimAgentRulesHint as T,AGENT_RULES_HINT as N}from"@lunora/config";import{collectWranglerSecretVariables as _,reconcileWranglerExtras as W,findWranglerFile as h,reconcileBindingsSafely as x,assertWranglerSatisfiesSchema as M}from"@lunora/config/cloudflare";const G=(t,e,r)=>{const i=m(t.projectRoot,t.schemaDir,"schema.ts");if(!u(i)){e.warn(`${y} no schema found at ${i} — run \`lunora init\` or create it, then rebuild.`);return}const s=j({apiSpec:t.apiSpec,lunoraDirectory:t.schemaDir,project:r,projectRoot:t.projectRoot,target:t.target,wranglerVariables:_(t.projectRoot)});return W(t.projectRoot,s.cronTriggers,e),E(s,e),D(s)},H=t=>{let e;return{drop:()=>{e=void 0},get:()=>(e===void 0?e=w(t):R(e,t),e)}},d="LunoraRspackPlugin",I=2,l=t=>{try{return p(t,"utf8")}catch{return""}},o={error:t=>{console.error(t)},info:t=>{console.info(t)},warn:t=>{console.warn(t)}},f=async t=>{const e=r=>{o.info(n(r))};await k({confirm:C("[lunora] "),cwd:t.projectRoot,info:e}),F({cwd:t.projectRoot,info:e}),A(t.projectRoot,e),!L(t.projectRoot).installed&&T()&&o.warn(`
2
+ ${n(N)}
3
+ `)};class q{#e;#r;#c;#o;#i=!1;#n;#h;#s=[];#t;#a;constructor(e){this.#e=e,this.#r=v(e.projectRoot,e.schemaDir),this.#c=P(process.env[c]),this.#o=H(this.#r)}async prepareDevSession(){this.#i||(this.#i=!0,await f(this.#e))}async generateForDev(){await this.#d(!0)}apply(e){e.hooks.beforeCompile.tapPromise(d,async()=>{await this.#d(e.watchMode===!0)}),e.hooks.afterCompile.tapPromise(d,r=>{r.contextDependencies.add(this.#r);for(const i of[a(this.#r),h(this.#e.projectRoot)])i!==void 0&&r.fileDependencies.add(i);for(const i of this.#s)r.errors.push(i);return Promise.resolve()})}async#d(e){this.#a??=this.#f(e).finally(()=>{this.#a=void 0}),await this.#a}async#f(e){if(e&&this.#c){this.#i||(this.#i=!0,o.info(n(`codegen disabled via ${c} — skipping generation and the wrangler checks.`)));return}const r=this.#m();r!==this.#h&&(this.#o.drop(),this.#h=r,this.#n=void 0);const i=this.#u(r);if(!this.#g(i)){this.#s=[];try{!this.#i&&e&&await f(this.#e),this.#i=!0,await this.#p(e),this.#s.length===0?(this.#n=i,this.#t=void 0):this.#l(i)}catch(s){this.#o.drop(),this.#n=void 0,this.#s.push(s instanceof Error?s:new Error(String(s))),this.#l(i),o.error(s instanceof Error?s.message:String(s))}}}#g(e){return e===this.#n?!0:this.#t!==void 0&&this.#t.fingerprint===e&&this.#t.attempts>=I}#l(e){this.#t=this.#t?.fingerprint===e?{attempts:this.#t.attempts+1,fingerprint:e}:{attempts:1,fingerprint:e}}#u(e){const r=h(this.#e.projectRoot);return[S(this.#r),e,r===void 0?"":l(r)].map(s=>g("sha256").update(s).digest("hex")).join("")}async#p(e){await x(this.#e,o),this.#e.validateWrangler&&M(this.#e,o.warn,"Update your wrangler.jsonc and rebuild.");const r=G(this.#e,o,this.#o.get()),i=await b({cwd:this.#e.projectRoot,logger:o});i.error!==void 0&&this.#s.push(new Error(i.error)),!e&&r!==void 0&&this.#s.push(new Error(r))}#m(){const e=a(this.#r);return e===void 0?"":l(e)}}export{q as LunoraRspackPlugin,d as PLUGIN_NAME};
@@ -0,0 +1 @@
1
+ import{resolveTargetOrThrow as o,isRunnableTarget as a,runnableTargetIds as n}from"@lunora/config";const i=(t,r)=>{const e=o(t,r);if(!a(e))throw new Error(`target "${e}" has no command-line toolchain, so the Lunora Rspack plugin cannot build for it — it can only generate for it (\`lunora codegen --target ${e}\`). Buildable targets: ${n().join(", ")}`);return e},c=t=>{const r=t??{},e=r.projectRoot??process.cwd();return{apiSpec:r.apiSpec??"openapi",projectRoot:e,schemaDir:r.schemaDir??"lunora",target:i(e,r.target),validateWrangler:r.validateWrangler??!0}};export{c as r};
@@ -0,0 +1,44 @@
1
+ import { CodegenOptions } from '@lunora/codegen';
2
+ /**
3
+ * Options for the `lunoraRspack()` plugin factory.
4
+ *
5
+ * Deliberately a subset of `@lunora/vite`'s. Everything absent here is
6
+ * Vite-dev-server machinery with no Rspack counterpart (`overlay`,
7
+ * `cloudflare`, `shard`) — see the package README for which of those the
8
+ * wrangler side of the stack covers instead. `studio` lives on the Rsbuild
9
+ * options, since it needs a dev server to mount on.
10
+ */
11
+ interface LunoraRspackOptions {
12
+ /**
13
+ * Which machine-readable API spec(s) codegen emits into `_generated/`.
14
+ * `"openapi"` (default) writes `openapi.json` (OpenAPI 3.1; RPC + REST),
15
+ * `"openrpc"` writes `openrpc.json` (OpenRPC 1.x; RPC-only), `"both"` writes
16
+ * both, and `"none"` writes neither. Forwarded to `runCodegen({ apiSpec })`;
17
+ * the value set is derived from `CodegenOptions` so it can't drift.
18
+ */
19
+ apiSpec?: CodegenOptions["apiSpec"];
20
+ /** Project root containing the `lunora/` directory. Defaults to `process.cwd()`. */
21
+ projectRoot?: string;
22
+ /** Directory name (relative to `projectRoot`) containing `schema.ts` and function files. Defaults to `"lunora"`. */
23
+ schemaDir?: string;
24
+ /**
25
+ * Deploy target the emitted `ctx.*` surface is tailored to. Defaults to
26
+ * `"target"` in `lunora.config.*`, then `"cloudflare"`.
27
+ *
28
+ * Set it here only to override the project config for one build — keeping
29
+ * this and `lunora deploy` on the same target is what the shared resolution
30
+ * in `@lunora/config` exists to guarantee.
31
+ */
32
+ target?: string;
33
+ /** Validate that `wrangler.jsonc` declares the bindings the schema implies. Defaults to `true`. */
34
+ validateWrangler?: boolean;
35
+ }
36
+ /** Resolved options after merging defaults. */
37
+ interface ResolvedLunoraRspackOptions {
38
+ apiSpec: NonNullable<CodegenOptions["apiSpec"]>;
39
+ projectRoot: string;
40
+ schemaDir: string;
41
+ target: string;
42
+ validateWrangler: boolean;
43
+ }
44
+ export { LunoraRspackOptions as L, ResolvedLunoraRspackOptions as R };
@@ -0,0 +1,44 @@
1
+ import { CodegenOptions } from '@lunora/codegen';
2
+ /**
3
+ * Options for the `lunoraRspack()` plugin factory.
4
+ *
5
+ * Deliberately a subset of `@lunora/vite`'s. Everything absent here is
6
+ * Vite-dev-server machinery with no Rspack counterpart (`overlay`,
7
+ * `cloudflare`, `shard`) — see the package README for which of those the
8
+ * wrangler side of the stack covers instead. `studio` lives on the Rsbuild
9
+ * options, since it needs a dev server to mount on.
10
+ */
11
+ interface LunoraRspackOptions {
12
+ /**
13
+ * Which machine-readable API spec(s) codegen emits into `_generated/`.
14
+ * `"openapi"` (default) writes `openapi.json` (OpenAPI 3.1; RPC + REST),
15
+ * `"openrpc"` writes `openrpc.json` (OpenRPC 1.x; RPC-only), `"both"` writes
16
+ * both, and `"none"` writes neither. Forwarded to `runCodegen({ apiSpec })`;
17
+ * the value set is derived from `CodegenOptions` so it can't drift.
18
+ */
19
+ apiSpec?: CodegenOptions["apiSpec"];
20
+ /** Project root containing the `lunora/` directory. Defaults to `process.cwd()`. */
21
+ projectRoot?: string;
22
+ /** Directory name (relative to `projectRoot`) containing `schema.ts` and function files. Defaults to `"lunora"`. */
23
+ schemaDir?: string;
24
+ /**
25
+ * Deploy target the emitted `ctx.*` surface is tailored to. Defaults to
26
+ * `"target"` in `lunora.config.*`, then `"cloudflare"`.
27
+ *
28
+ * Set it here only to override the project config for one build — keeping
29
+ * this and `lunora deploy` on the same target is what the shared resolution
30
+ * in `@lunora/config` exists to guarantee.
31
+ */
32
+ target?: string;
33
+ /** Validate that `wrangler.jsonc` declares the bindings the schema implies. Defaults to `true`. */
34
+ validateWrangler?: boolean;
35
+ }
36
+ /** Resolved options after merging defaults. */
37
+ interface ResolvedLunoraRspackOptions {
38
+ apiSpec: NonNullable<CodegenOptions["apiSpec"]>;
39
+ projectRoot: string;
40
+ schemaDir: string;
41
+ target: string;
42
+ validateWrangler: boolean;
43
+ }
44
+ export { LunoraRspackOptions as L, ResolvedLunoraRspackOptions as R };
@@ -0,0 +1,122 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { L as LunoraRspackOptions } from "./packem_shared/types.d-DcuVeaxB.mjs";
3
+ import '@lunora/codegen';
4
+ /** Path prefix the Lunora Worker serves — RPC, the WebSocket, and the studio. */
5
+ declare const LUNORA_PATH = "/_lunora";
6
+ /** Name Rsbuild lists this plugin under. */
7
+ declare const RSBUILD_PLUGIN_NAME = "lunora:rsbuild";
8
+ interface LunoraRsbuildOptions extends LunoraRspackOptions {
9
+ /**
10
+ * Serve Lunora Studio at `/__lunora` on the dev server — the same URL, and
11
+ * the same app, as `@lunora/vite`. Needs `@lunora/studio` installed.
12
+ * `false` opts out.
13
+ *
14
+ * Defaults to `true`.
15
+ */
16
+ studio?: boolean;
17
+ /**
18
+ * Run `wrangler dev` alongside the dev server and proxy {@link LUNORA_PATH}
19
+ * to it. `false` opts out — for a project that starts the Worker itself, or
20
+ * an Rsbuild build with no dev server to attach to.
21
+ *
22
+ * Defaults to `true`.
23
+ */
24
+ worker?: boolean;
25
+ /**
26
+ * Port the Worker serves on. Defaults to the wrangler config's `dev.port`,
27
+ * then `8787`.
28
+ */
29
+ workerPort?: number;
30
+ /** Extra arguments appended to `wrangler dev`. */
31
+ wranglerArgs?: ReadonlyArray<string>;
32
+ }
33
+ /**
34
+ * The slice of Rsbuild's plugin API this uses, projected structurally for the
35
+ * same reason `./compiler.ts` projects the Rspack compiler: `@rsbuild/core` is an
36
+ * OPTIONAL peer, so its types must not reach this package's published `.d.ts`.
37
+ */
38
+ interface RsbuildApiLike {
39
+ getRsbuildConfig: () => Readonly<RsbuildConfigLike>;
40
+ modifyRsbuildConfig: (callback: (config: RsbuildConfigLike) => RsbuildConfigLike) => void;
41
+ modifyRspackConfig: (callback: (config: RspackConfigLike) => RspackConfigLike) => void;
42
+ onAfterStartDevServer: (callback: (params: {
43
+ port: number;
44
+ }) => Promise<void> | void) => void;
45
+ onBeforeStartDevServer: (callback: (params: {
46
+ server: DevServerLike;
47
+ }) => Promise<void> | void) => void;
48
+ onCloseDevServer: (callback: () => Promise<void> | void) => void;
49
+ }
50
+ /** The slice of Rsbuild's dev server this uses: its Connect middleware stack. */
51
+ interface DevServerLike {
52
+ middlewares: {
53
+ use: (handler: (request: IncomingMessage, response: ServerResponse, next: () => void) => void) => unknown;
54
+ };
55
+ }
56
+ /** A single entry of Rsbuild's array-form `server.proxy`. */
57
+ type ProxyArrayEntry = Record<string, unknown> & {
58
+ pathFilter?: unknown;
59
+ };
60
+ /**
61
+ * Rsbuild's `server.proxy`, which is a record OR an array — the distinction that
62
+ * matters, because spreading an array into an object literal turns its entries
63
+ * into numeric keys and Rsbuild then reads each key as a `pathFilter`, silently
64
+ * unrouting every rule that relied on the default match-all.
65
+ */
66
+ type ProxyConfigLike = ProxyArrayEntry[] | Record<string, unknown>;
67
+ /** The slice of the Rspack config this plugin writes. */
68
+ interface RspackConfigLike {
69
+ plugins?: unknown;
70
+ }
71
+ /** The slice of Rsbuild's config this plugin writes. */
72
+ interface RsbuildConfigLike {
73
+ server?: {
74
+ base?: string;
75
+ host?: string;
76
+ proxy?: ProxyConfigLike;
77
+ };
78
+ }
79
+ /** The Rsbuild plugin shape. */
80
+ interface RsbuildPluginLike {
81
+ name: string;
82
+ setup: (api: RsbuildApiLike) => void;
83
+ }
84
+ /**
85
+ * Add the Lunora route to whichever form of `server.proxy` the project uses,
86
+ * without converting one form into the other.
87
+ *
88
+ * In both forms the project's own entry for this path WINS: the plugin supplies
89
+ * a default, not a policy. `127.0.0.1` rather than `localhost` because Node
90
+ * resolves `localhost` to `::1` first on some hosts while wrangler binds IPv4.
91
+ */
92
+ declare const withLunoraProxy: (existing: ProxyConfigLike | undefined, port: number) => ProxyConfigLike;
93
+ /**
94
+ * Lunora Rsbuild plugin — the one-command dev story.
95
+ *
96
+ * `rsbuild dev` starts the client dev server AND the Lunora Worker, and routes
97
+ * `/_lunora/*` to it. Nothing to wire: no hand-written proxy, no second terminal,
98
+ * no `wrangler.dev.jsonc`.
99
+ *
100
+ * It is a different shape from `lunoraRspack` because Rsbuild plugins are
101
+ * `{ name, setup }` and Rspack plugins are `{ apply }`. This one registers the
102
+ * Rspack plugin itself through `modifyRspackConfig`, so codegen, binding
103
+ * provisioning and wrangler validation come along with it — one entry in
104
+ * `plugins` wires the whole thing.
105
+ *
106
+ * **Why a child process rather than in-process workerd.** `@cloudflare/vite-plugin`
107
+ * runs the Worker inside the dev server using Vite's Environment API: a module
108
+ * runner in workerd pulls each module over RPC from Vite. Rspack has no equivalent
109
+ * runner protocol, and the alternative — bundling the Worker ourselves and handing
110
+ * it to Miniflare — means reimplementing wrangler's `nodejs_compat`, Durable Object
111
+ * migrations, binding wiring and local persistence. `wrangler dev` already does all
112
+ * of that correctly. The process boundary is invisible from the developer's seat;
113
+ * what differs is that the Worker restarts rather than hot-swapping modules.
114
+ *
115
+ * The proxy is same-origin on purpose: the browser talks to the dev server's own
116
+ * origin, so `LunoraClient` needs no CORS and the auth cookie is first-party.
117
+ * `ws: true` is what carries the live-query socket — without it queries answer and
118
+ * subscriptions never arrive, which is the single most expensive way this can be
119
+ * misconfigured by hand.
120
+ */
121
+ declare const lunoraRsbuild: (options?: LunoraRsbuildOptions) => RsbuildPluginLike;
122
+ export { type DevServerLike, LUNORA_PATH, type LunoraRsbuildOptions, type ProxyConfigLike, RSBUILD_PLUGIN_NAME, type RsbuildApiLike, type RsbuildConfigLike, type RsbuildPluginLike, type RspackConfigLike, lunoraRsbuild, withLunoraProxy };
@@ -0,0 +1,122 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { L as LunoraRspackOptions } from "./packem_shared/types.d-DcuVeaxB.js";
3
+ import '@lunora/codegen';
4
+ /** Path prefix the Lunora Worker serves — RPC, the WebSocket, and the studio. */
5
+ declare const LUNORA_PATH = "/_lunora";
6
+ /** Name Rsbuild lists this plugin under. */
7
+ declare const RSBUILD_PLUGIN_NAME = "lunora:rsbuild";
8
+ interface LunoraRsbuildOptions extends LunoraRspackOptions {
9
+ /**
10
+ * Serve Lunora Studio at `/__lunora` on the dev server — the same URL, and
11
+ * the same app, as `@lunora/vite`. Needs `@lunora/studio` installed.
12
+ * `false` opts out.
13
+ *
14
+ * Defaults to `true`.
15
+ */
16
+ studio?: boolean;
17
+ /**
18
+ * Run `wrangler dev` alongside the dev server and proxy {@link LUNORA_PATH}
19
+ * to it. `false` opts out — for a project that starts the Worker itself, or
20
+ * an Rsbuild build with no dev server to attach to.
21
+ *
22
+ * Defaults to `true`.
23
+ */
24
+ worker?: boolean;
25
+ /**
26
+ * Port the Worker serves on. Defaults to the wrangler config's `dev.port`,
27
+ * then `8787`.
28
+ */
29
+ workerPort?: number;
30
+ /** Extra arguments appended to `wrangler dev`. */
31
+ wranglerArgs?: ReadonlyArray<string>;
32
+ }
33
+ /**
34
+ * The slice of Rsbuild's plugin API this uses, projected structurally for the
35
+ * same reason `./compiler.ts` projects the Rspack compiler: `@rsbuild/core` is an
36
+ * OPTIONAL peer, so its types must not reach this package's published `.d.ts`.
37
+ */
38
+ interface RsbuildApiLike {
39
+ getRsbuildConfig: () => Readonly<RsbuildConfigLike>;
40
+ modifyRsbuildConfig: (callback: (config: RsbuildConfigLike) => RsbuildConfigLike) => void;
41
+ modifyRspackConfig: (callback: (config: RspackConfigLike) => RspackConfigLike) => void;
42
+ onAfterStartDevServer: (callback: (params: {
43
+ port: number;
44
+ }) => Promise<void> | void) => void;
45
+ onBeforeStartDevServer: (callback: (params: {
46
+ server: DevServerLike;
47
+ }) => Promise<void> | void) => void;
48
+ onCloseDevServer: (callback: () => Promise<void> | void) => void;
49
+ }
50
+ /** The slice of Rsbuild's dev server this uses: its Connect middleware stack. */
51
+ interface DevServerLike {
52
+ middlewares: {
53
+ use: (handler: (request: IncomingMessage, response: ServerResponse, next: () => void) => void) => unknown;
54
+ };
55
+ }
56
+ /** A single entry of Rsbuild's array-form `server.proxy`. */
57
+ type ProxyArrayEntry = Record<string, unknown> & {
58
+ pathFilter?: unknown;
59
+ };
60
+ /**
61
+ * Rsbuild's `server.proxy`, which is a record OR an array — the distinction that
62
+ * matters, because spreading an array into an object literal turns its entries
63
+ * into numeric keys and Rsbuild then reads each key as a `pathFilter`, silently
64
+ * unrouting every rule that relied on the default match-all.
65
+ */
66
+ type ProxyConfigLike = ProxyArrayEntry[] | Record<string, unknown>;
67
+ /** The slice of the Rspack config this plugin writes. */
68
+ interface RspackConfigLike {
69
+ plugins?: unknown;
70
+ }
71
+ /** The slice of Rsbuild's config this plugin writes. */
72
+ interface RsbuildConfigLike {
73
+ server?: {
74
+ base?: string;
75
+ host?: string;
76
+ proxy?: ProxyConfigLike;
77
+ };
78
+ }
79
+ /** The Rsbuild plugin shape. */
80
+ interface RsbuildPluginLike {
81
+ name: string;
82
+ setup: (api: RsbuildApiLike) => void;
83
+ }
84
+ /**
85
+ * Add the Lunora route to whichever form of `server.proxy` the project uses,
86
+ * without converting one form into the other.
87
+ *
88
+ * In both forms the project's own entry for this path WINS: the plugin supplies
89
+ * a default, not a policy. `127.0.0.1` rather than `localhost` because Node
90
+ * resolves `localhost` to `::1` first on some hosts while wrangler binds IPv4.
91
+ */
92
+ declare const withLunoraProxy: (existing: ProxyConfigLike | undefined, port: number) => ProxyConfigLike;
93
+ /**
94
+ * Lunora Rsbuild plugin — the one-command dev story.
95
+ *
96
+ * `rsbuild dev` starts the client dev server AND the Lunora Worker, and routes
97
+ * `/_lunora/*` to it. Nothing to wire: no hand-written proxy, no second terminal,
98
+ * no `wrangler.dev.jsonc`.
99
+ *
100
+ * It is a different shape from `lunoraRspack` because Rsbuild plugins are
101
+ * `{ name, setup }` and Rspack plugins are `{ apply }`. This one registers the
102
+ * Rspack plugin itself through `modifyRspackConfig`, so codegen, binding
103
+ * provisioning and wrangler validation come along with it — one entry in
104
+ * `plugins` wires the whole thing.
105
+ *
106
+ * **Why a child process rather than in-process workerd.** `@cloudflare/vite-plugin`
107
+ * runs the Worker inside the dev server using Vite's Environment API: a module
108
+ * runner in workerd pulls each module over RPC from Vite. Rspack has no equivalent
109
+ * runner protocol, and the alternative — bundling the Worker ourselves and handing
110
+ * it to Miniflare — means reimplementing wrangler's `nodejs_compat`, Durable Object
111
+ * migrations, binding wiring and local persistence. `wrangler dev` already does all
112
+ * of that correctly. The process boundary is invisible from the developer's seat;
113
+ * what differs is that the Worker restarts rather than hot-swapping modules.
114
+ *
115
+ * The proxy is same-origin on purpose: the browser talks to the dev server's own
116
+ * origin, so `LunoraClient` needs no CORS and the auth cookie is first-party.
117
+ * `ws: true` is what carries the live-query socket — without it queries answer and
118
+ * subscriptions never arrive, which is the single most expensive way this can be
119
+ * misconfigured by hand.
120
+ */
121
+ declare const lunoraRsbuild: (options?: LunoraRsbuildOptions) => RsbuildPluginLike;
122
+ export { type DevServerLike, LUNORA_PATH, type LunoraRsbuildOptions, type ProxyConfigLike, RSBUILD_PLUGIN_NAME, type RsbuildApiLike, type RsbuildConfigLike, type RsbuildPluginLike, type RspackConfigLike, lunoraRsbuild, withLunoraProxy };
@@ -0,0 +1,2 @@
1
+ import{formatLunoraEvent as y,lunoraLine as l}from"@lunora/config";import{createStudioMiddleware as k,isNonLoopbackHost as A,studioMountPath as R}from"@lunora/config/studio-host";import{r as E}from"./packem_shared/options-rWtSt-oe.mjs";import{LunoraRspackPlugin as L}from"./packem_shared/LunoraRspackPlugin-DPrjGG-k.mjs";import{spawn as x}from"node:child_process";import{existsSync as D,mkdirSync as P}from"node:fs";import{connect as T}from"node:net";import{resolve as v,dirname as b}from"node:path";import{findWranglerFile as O,readWranglerJsonc as _}from"@lunora/config/cloudflare";const C=8787,d=3e4,W=150,$=5e3,u=(e,r,t)=>{let o;for(const[n,s]of e.entries())s.startsWith(`${r}=`)?o=s.slice(r.length+1):(s===r||s===t)&&n+1<e.length&&(o=e[n+1]);return o},h=(e,r=[])=>{const t=u(r,"--config","-c"),o=t===void 0?O(e):v(e,t);if(o===void 0||!D(o))return;const{parsed:n}=_(o);return n===void 0?void 0:{config:n,env:u(r,"--env","-e"),path:o}},I=(e,r,t)=>{if(r!==void 0)return r;const o=h(e,t)?.config.dev?.port;return typeof o=="number"?o:C},N=(e,r)=>{const t=h(e,r);if(t===void 0)return;const n=((t.env===void 0?void 0:t.config.env?.[t.env]?.assets)??t.config.assets)?.directory;typeof n=="string"&&P(v(b(t.path),n),{recursive:!0})},w=async e=>new Promise(r=>{const t=T({host:"127.0.0.1",port:e}),o=n=>{t.destroy(),r(n)};t.once("connect",()=>{o(!0)}),t.once("error",()=>{o(!1)})}),f=e=>{const r=y(e);if(r===void 0){e.trim()!==""&&console.log(e);return}const t=l(r.text);r.level==="error"?console.error(t):r.level==="warn"?console.warn(t):console.info(t)},p=e=>{let r="";e?.setEncoding("utf8"),e?.on("data",t=>{r+=t;const o=r.split(`
2
+ `);r=o.pop()??"";for(const n of o)f(n)}),e?.once("end",()=>{r!==""&&(f(r),r="")})},M=async e=>{if(await w(e.port))throw new Error(`could not start the worker: port ${String(e.port)} is already in use. Stop whatever is serving there (another \`rsbuild dev\` or \`lunora dev\`?), or set \`workerPort\`.`);N(e.projectRoot,e.wranglerArgs);const r=["dev","--port",String(e.port),"--var","WORKER_ENV:development",...e.wranglerArgs??[]],t=process.platform==="win32",o=x(t?"wrangler.cmd":"wrangler",r,{cwd:e.projectRoot,shell:t,stdio:["ignore","pipe","pipe"]});p(o.stdout),p(o.stderr);let n;o.once("error",a=>{n=a.code==="ENOENT"?new Error("could not start the worker: `wrangler` was not found on PATH. Add it to the project (`npm install -D wrangler`), or pass `worker: false` to run the worker yourself."):a});const s=()=>{o.kill("SIGKILL")};process.once("exit",s);const i=async()=>{process.removeListener("exit",s),!(o.exitCode!==null||o.signalCode!==null)&&await new Promise(a=>{const S=setTimeout(()=>{o.kill("SIGKILL")},$);o.once("exit",()=>{clearTimeout(S),a()}),o.kill("SIGTERM")})};let c;o.once("exit",a=>{c??=new Error(`wrangler dev exited with code ${String(a)} before it was ready`)});const m=Date.now()+d;for(;Date.now()<m;){if(n!==void 0)throw n;if(c!==void 0)throw c;if(await w(e.port)){if(o.exitCode!==null||o.signalCode!==null)throw await i(),new Error(`the worker on port ${String(e.port)} is not the one this plugin started — it exited while something else took the port.`);return{port:e.port,stop:i}}await new Promise(a=>{setTimeout(a,W)})}throw await i(),new Error(`wrangler dev did not start listening on port ${String(e.port)} within ${String(d/1e3)}s`)},g="/_lunora",j="lunora:rsbuild",F=(e,r)=>{const t=`http://127.0.0.1:${String(r)}`;return Array.isArray(e)?[...e,{changeOrigin:!0,pathFilter:g,target:t,ws:!0}]:{[g]:{changeOrigin:!0,target:t,ws:!0},...e}},U=(e,r)=>{e.onBeforeStartDevServer(({server:t})=>{const{base:o,host:n}=e.getRsbuildConfig().server??{};t.middlewares.use(k({apiSpec:r.apiSpec,base:o,isNonLoopbackBind:A(n),projectRoot:r.projectRoot,schemaDirectory:r.schemaDir}))}),e.onAfterStartDevServer(({port:t})=>{console.info(l(`studio on http://localhost:${String(t)}${R(e.getRsbuildConfig().server?.base)}`))})},Q=e=>{const r=E(e),t=I(r.projectRoot,e?.workerPort,e?.wranglerArgs),o=new L(r);return{name:j,setup:n=>{if(n.modifyRspackConfig(i=>({...i,plugins:[...Array.isArray(i.plugins)?i.plugins:[],o]})),n.modifyRsbuildConfig(i=>({...i,server:{...i.server,proxy:F(i.server?.proxy,t)}})),e?.studio!==!1&&U(n,r),e?.worker===!1)return;let s;n.onBeforeStartDevServer(async()=>{await o.prepareDevSession(),await o.generateForDev(),console.info(l(`starting the worker on http://127.0.0.1:${String(t)} …`)),s=await M({port:t,projectRoot:r.projectRoot,wranglerArgs:e?.wranglerArgs})}),n.onCloseDevServer(async()=>{await s?.stop(),s=void 0})}}};export{g as LUNORA_PATH,j as RSBUILD_PLUGIN_NAME,Q as lunoraRsbuild,F as withLunoraProxy};