@electrojs/codegen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anton Ryuben
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # @electrojs/codegen
2
+
3
+ Code generator for Electro's module runtime — scans decorator metadata from source and emits preload scripts, runtime registry, and environment type declarations.
4
+
5
+ `@electrojs/codegen` provides the `scan` → `generate` pipeline that powers `electro generate` and runs automatically during `electro dev` and `electro build`. It uses the OXC parser to extract `@Module`, `@Injectable`, `@Window`, `@View`, `@command`, `@query`, `@signal`, and `@job` decorators from TypeScript source without executing it.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pnpm add -D @electrojs/codegen
13
+ ```
14
+
15
+ > Peer dependencies: `@electrojs/common`, `@electrojs/config`, `@electrojs/runtime`.
16
+
17
+ ---
18
+
19
+ ## Pipeline
20
+
21
+ ### 1. Scan
22
+
23
+ ```ts
24
+ import { scan } from "@electrojs/codegen";
25
+
26
+ const result = await scan("./src");
27
+ ```
28
+
29
+ `scan()` walks the source directory, parses each `.ts` file with OXC, and extracts:
30
+
31
+ - **Modules** — classes with `@Module()`, including their imports, providers, exports, windows, and views
32
+ - **Providers** — classes with `@Injectable()`, including their decorated methods
33
+ - **Windows** — classes with `@Window()`, with id and export status
34
+ - **Views** — classes with `@View()`, with id, source, access keys, and signal keys
35
+ - **Methods** — `@command()` and `@query()` decorated methods, with derived IDs
36
+ - **Signals** — detected from `@signal()` decorators, `SignalBus.publish()`, and `SignalBus.subscribe()` calls
37
+ - **Jobs** — `@job()` decorated methods, with optional cron expressions
38
+
39
+ Files matching `*.d.ts`, `*.test.ts`, `*.spec.ts`, `*.gen.ts`, and `node_modules/` are excluded.
40
+
41
+ ### 2. Generate
42
+
43
+ ```ts
44
+ import { generate } from "@electrojs/codegen";
45
+
46
+ const output = generate({
47
+ scanResult: result,
48
+ views: [{ id: "main" }, { id: "auth" }],
49
+ outputDir: ".electro/generated",
50
+ srcDir: "./runtime",
51
+ packageTargets: [
52
+ { packageRoot: "./views/main", viewId: "main" },
53
+ { packageRoot: "./views/auth", viewId: "auth" },
54
+ ],
55
+ });
56
+ ```
57
+
58
+ `generate()` validates the scan result, then produces:
59
+
60
+ | Output | Path | Description |
61
+ | ------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
62
+ | Preload scripts | `generated/preload/{viewId}.gen.ts` | Per-view bridge client setup via `createBridgeClient()` |
63
+ | Runtime registry | `generated/runtime/registry.gen.ts` | Exports scanned modules, windows, views; identifies root module and creates `electroAppDefinition` |
64
+ | Runtime environment types | `runtime/electro-env.d.ts` | Ambient registry interfaces for runtime authoring: methods, signals, jobs, injectables, windows, views, and class augmentations |
65
+ | View environment types | `views/*/electro-env.d.ts` | Package-local ambient types for `@electrojs/renderer` bridge contracts and forwarded signal payloads |
66
+
67
+ ---
68
+
69
+ ## Validation
70
+
71
+ Before generating, the pipeline runs exhaustive checks:
72
+
73
+ - Duplicate module, window, or view IDs
74
+ - Duplicate method IDs within a module (including its providers)
75
+ - Unknown access keys — views must reference valid `module:method` pairs
76
+ - Unknown signal keys — views must reference known signal IDs
77
+ - Missing runtime views — every generator view definition must match a scanned `@View()` class
78
+
79
+ All diagnostics are collected and thrown as a single `ValidationError` with the full array.
80
+
81
+ ---
82
+
83
+ ## ID derivation
84
+
85
+ | Entity | Default ID | Override |
86
+ | ------ | ------------------------------------------------------- | --------------------------------------------------------- |
87
+ | Module | Class name minus `Module` suffix, lowercased first char | `@Module({ id: "custom" })` |
88
+ | Method | Method name | `@command({ id: "custom" })` / `@query({ id: "custom" })` |
89
+ | View | Extracted from `source: "view:<id>"` | `@View({ id: "custom" })` |
90
+ | Job | Method name | `@job({ id: "custom" })` |
91
+
92
+ Public bridge keys follow the `module:method` pattern (e.g., `workspace:openProject`). Signal keys follow the `signalId` pattern (e.g., `projectOpened`).
93
+
94
+ ---
95
+
96
+ ## Exports
97
+
98
+ ```ts
99
+ // Pipeline
100
+ import { scan, generate } from "@electrojs/codegen";
101
+
102
+ // Errors
103
+ import { CodegenError, ValidationError } from "@electrojs/codegen";
104
+ ```
105
+
106
+ Type-only imports:
107
+
108
+ ```ts
109
+ import type {
110
+ ScanResult,
111
+ ScannedModule,
112
+ ScannedProvider,
113
+ ScannedMethod,
114
+ ScannedSignal,
115
+ ScannedJob,
116
+ ScannedWindow,
117
+ ScannedView,
118
+ GeneratorInput,
119
+ GeneratorOutput,
120
+ GeneratedFile,
121
+ GeneratorViewDefinition,
122
+ CodegenDiagnostic,
123
+ } from "@electrojs/codegen";
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Package layering
129
+
130
+ ```txt
131
+ @electrojs/common ← decorator definitions, metadata keys
132
+ @electrojs/config ← config schema (view definitions)
133
+
134
+ @electrojs/codegen ← this package: scanner + generator
135
+
136
+ @electrojs/cli ← invokes scan/generate during dev, build, generate commands
137
+ ```
@@ -0,0 +1,214 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Public type contracts for `@electrojs/codegen`.
4
+ *
5
+ * All scan result types, generator input/output types, and diagnostic types
6
+ * are defined here and re-exported from the package entry point.
7
+ *
8
+ * @module types
9
+ */
10
+ /** Complete result of scanning a project's source files for Electro decorators. */
11
+ interface ScanResult {
12
+ readonly modules: readonly ScannedModule[];
13
+ readonly windows: readonly ScannedWindow[];
14
+ readonly views: readonly ScannedView[];
15
+ }
16
+ /** A class decorated with `@Module()`. */
17
+ interface ScannedModule {
18
+ /** Derived or explicit module ID (from `@Module({ id })` or class name). */
19
+ readonly id: string;
20
+ readonly className: string;
21
+ readonly filePath: string;
22
+ readonly exported: boolean;
23
+ /** Module class names referenced in `@Module({ imports })`. */
24
+ readonly imports: readonly string[];
25
+ /** `@Injectable()` providers registered in this module via `@Module({ providers })`. */
26
+ readonly providers: readonly ScannedProvider[];
27
+ /** `@command` / `@query` methods declared directly on the module class. */
28
+ readonly methods: readonly ScannedMethod[];
29
+ /** `@signal` handler methods declared directly on the module class. */
30
+ readonly signals: readonly ScannedSignal[];
31
+ /** `@job` methods declared directly on the module class. */
32
+ readonly jobs: readonly ScannedJob[];
33
+ }
34
+ /** A class decorated with `@Injectable()` that belongs to a module. */
35
+ interface ScannedProvider {
36
+ readonly className: string;
37
+ readonly filePath: string;
38
+ readonly exported: boolean;
39
+ /** `@command` / `@query` methods declared on this provider. */
40
+ readonly methods: readonly ScannedMethod[];
41
+ /** `@signal` handler methods declared on this provider. */
42
+ readonly signals: readonly ScannedSignal[];
43
+ /** `@job` methods declared on this provider. */
44
+ readonly jobs: readonly ScannedJob[];
45
+ }
46
+ /** A method decorated with `@command()` or `@query()`. */
47
+ interface ScannedMethod {
48
+ /** Method ID: from `@command({ id })` / `@query({ id })`, or the method name. */
49
+ readonly id: string;
50
+ readonly methodName: string;
51
+ readonly kind: "command" | "query";
52
+ /** Class name that owns this method. */
53
+ readonly ownerClassName: string;
54
+ }
55
+ type ScannedSignalSource = "decorator" | "publish" | "subscribe";
56
+ type ScannedSignalPayload = {
57
+ readonly kind: "method-parameter";
58
+ readonly parameterIndex: number;
59
+ } | {
60
+ readonly kind: "method-parameter-pick";
61
+ readonly parameterIndex: number;
62
+ readonly keys: readonly string[];
63
+ } | {
64
+ readonly kind: "void";
65
+ } | {
66
+ readonly kind: "unknown";
67
+ };
68
+ /** A signal observed from `@signal()`, `publish()`, or `subscribe()`. */
69
+ interface ScannedSignal {
70
+ /** Signal ID observed in the runtime class. */
71
+ readonly id: string;
72
+ /** Method that declared, published, or subscribed to this signal. */
73
+ readonly methodName: string;
74
+ /** Class name that owns this signal declaration/publication/subscription. */
75
+ readonly ownerClassName: string;
76
+ /** How this signal was discovered. */
77
+ readonly source: ScannedSignalSource;
78
+ /** Best-effort payload source for bridge/env type generation. */
79
+ readonly payload: ScannedSignalPayload;
80
+ }
81
+ /** A method decorated with `@job()`. */
82
+ interface ScannedJob {
83
+ /** Job ID: from `@job({ id })`, or the method name. */
84
+ readonly id: string;
85
+ readonly methodName: string;
86
+ /** Class name that owns this job. */
87
+ readonly ownerClassName: string;
88
+ /** Cron expression from `@job({ cron })`, or `null`. */
89
+ readonly cron: string | null;
90
+ }
91
+ /** A class decorated with `@Window()`. */
92
+ interface ScannedWindow {
93
+ readonly id: string;
94
+ readonly className: string;
95
+ readonly filePath: string;
96
+ readonly exported: boolean;
97
+ }
98
+ /** A class decorated with `@View()`. */
99
+ interface ScannedView {
100
+ readonly id: string;
101
+ readonly className: string;
102
+ readonly filePath: string;
103
+ readonly exported: boolean;
104
+ /** Source URL from `@View({ source })`. */
105
+ readonly source: string;
106
+ /** Bridge access keys from `@View({ access })`. */
107
+ readonly access: readonly string[];
108
+ /** Signal keys from `@View({ signals })`. */
109
+ readonly signals: readonly string[];
110
+ }
111
+ /** Target for per-package `electro-env.d.ts` generation. */
112
+ interface PackageTypeTarget {
113
+ /** Absolute path to the renderer package root (dirname of `view.config.ts`). */
114
+ readonly packageRoot: string;
115
+ /** View ID that this renderer package belongs to. */
116
+ readonly viewId: string;
117
+ }
118
+ /** Input for the `generate()` function. */
119
+ interface GeneratorInput {
120
+ readonly scanResult: ScanResult;
121
+ /** CLI-discovered view configs (for preload extension paths). */
122
+ readonly views?: readonly GeneratorViewDefinition[];
123
+ /** Directory where `generated/` folder will be placed. */
124
+ readonly outputDir: string;
125
+ /** Project source root for relative import resolution. */
126
+ readonly srcDir: string;
127
+ /** Targets for per-package `electro-env.d.ts` generation. */
128
+ readonly packageTargets?: readonly PackageTypeTarget[];
129
+ }
130
+ /** A view definition provided by the CLI (not from AST scanning). */
131
+ interface GeneratorViewDefinition {
132
+ readonly id: string;
133
+ /** Path to the user's preload extension script, or `null`/`undefined`. */
134
+ readonly preload?: string | null;
135
+ /** Original file path for relative import resolution. */
136
+ readonly __source: string;
137
+ }
138
+ /** A generated package-local `electro-env.d.ts` file. */
139
+ interface PackageTypeOutput {
140
+ /** Absolute path to the package root. */
141
+ readonly packageRoot: string;
142
+ /** Relative output path inside the package root. */
143
+ readonly path: string;
144
+ /** Content of the generated declaration file. */
145
+ readonly content: string;
146
+ /** Backward-compatible alias for the generated declaration content. */
147
+ readonly indexDts?: string;
148
+ /** Deprecated leftover from the old `@electrojs/types` package layout. */
149
+ readonly packageJson?: string;
150
+ }
151
+ /** Output from the `generate()` function. */
152
+ interface GeneratorOutput {
153
+ /** Generated files: preload scripts and runtime registry. */
154
+ readonly files: readonly GeneratedFile[];
155
+ /** The runtime package `electro-env.d.ts` ambient type declaration file. */
156
+ readonly envTypes: GeneratedFile;
157
+ /** Per-view renderer package `electro-env.d.ts` outputs. */
158
+ readonly packageTypes: readonly PackageTypeOutput[];
159
+ }
160
+ /** A generated file with its output path and content. */
161
+ interface GeneratedFile {
162
+ readonly path: string;
163
+ readonly content: string;
164
+ }
165
+ /** A diagnostic message produced during validation. */
166
+ interface CodegenDiagnostic {
167
+ readonly code: string;
168
+ readonly message: string;
169
+ readonly filePath?: string;
170
+ }
171
+ //#endregion
172
+ //#region src/scanner/index.d.ts
173
+ /**
174
+ * Scan a project directory for Electro decorators and return a normalized result.
175
+ *
176
+ * This is the main entry point for the codegen scanner.
177
+ *
178
+ * @param basePath - Root directory to scan for `.ts` files.
179
+ * @returns A {@link ScanResult} with all discovered modules, windows, and views.
180
+ */
181
+ declare function scan(basePath: string): Promise<ScanResult>;
182
+ //#endregion
183
+ //#region src/generator/index.d.ts
184
+ /**
185
+ * Generate all codegen artifacts from a scan result.
186
+ *
187
+ * 1. Validates the input (throws {@link ValidationError} on failure).
188
+ * 2. For each `@View()`, generates a preload script.
189
+ * 3. Generates the runtime registry.
190
+ * 4. Generates the ambient `electro-env.d.ts` type files.
191
+ *
192
+ * @param input - The generator input containing scan result, view definitions, and paths.
193
+ * @returns Generated files and the env types file.
194
+ * @throws {ValidationError} If the scan result has consistency issues.
195
+ */
196
+ declare function generate(input: GeneratorInput): GeneratorOutput;
197
+ //#endregion
198
+ //#region src/errors.d.ts
199
+ /** Base error class for codegen operations. */
200
+ declare class CodegenError extends Error {
201
+ readonly code: string;
202
+ constructor(message: string, code: string);
203
+ }
204
+ /**
205
+ * Thrown when the generator detects validation issues in the scan result.
206
+ *
207
+ * Contains an array of {@link CodegenDiagnostic} entries describing each issue.
208
+ */
209
+ declare class ValidationError extends CodegenError {
210
+ readonly diagnostics: readonly CodegenDiagnostic[];
211
+ constructor(diagnostics: readonly CodegenDiagnostic[]);
212
+ }
213
+ //#endregion
214
+ export { type CodegenDiagnostic, CodegenError, type GeneratedFile, type GeneratorInput, type GeneratorOutput, type GeneratorViewDefinition, type PackageTypeOutput, type PackageTypeTarget, type ScanResult, type ScannedJob, type ScannedMethod, type ScannedModule, type ScannedProvider, type ScannedSignal, type ScannedView, type ScannedWindow, ValidationError, generate, scan };
package/dist/index.mjs ADDED
@@ -0,0 +1,124 @@
1
+ import{readFileSync as e}from"node:fs";import{parseSync as t}from"oxc-parser";import{basename as n,dirname as r,isAbsolute as i,join as a,relative as o,resolve as s}from"node:path";function c(n){let r=t(n,e(n,`utf8`),{sourceType:`module`});for(let e of r.errors)console.warn(`[codegen] Parse error in ${n}: ${e.message}`);return{program:r.program,filePath:n}}function l(e){return e&&e.type===`Literal`&&typeof e.value==`string`?e.value:null}function u(e){if(!e||e.type!==`ArrayExpression`)return[];let t=[];for(let n of e.elements){let e=l(n);e!==null&&t.push(e)}return t}function d(e){if(!e||e.type!==`ArrayExpression`)return[];let t=[];for(let n of e.elements)n?.type===`Identifier`&&typeof n.name==`string`&&t.push(n.name);return t}function f(e){return e?e.type===`Identifier`&&typeof e.name==`string`?e.name:e.type===`Literal`&&typeof e.value==`string`?e.value:null:null}function p(e,t){let n=e.properties;if(!n)return null;for(let e of n)if(e.type===`Property`&&f(e.key)===t)return e.value;return null}function m(e){return e&&e.type===`Identifier`&&typeof e.name==`string`?e.name:null}function h(e){return(e.type===`ClassDeclaration`||e.type===`ClassExpression`)&&e.id?m(e.id):null}function g(e,t){let n=Array.isArray(t)?t:[t],r=e.decorators??[];for(let e of r){if(e.type!==`Decorator`)continue;let t=e.expression;if(t.type===`Identifier`&&n.includes(t.name))return t;if(t.type===`CallExpression`){let e=t.callee;if(e.type===`Identifier`&&n.includes(e.name))return t}}return null}function _(e,t){let n=g(e,t);if(!n||n.type!==`CallExpression`)return null;let r=n.arguments[0];return!r||r.type!==`ObjectExpression`?null:r}function v(e){let t=new Set,n=e.body;if(!n)return t;for(let e of n){if(e.type===`ExportNamedDeclaration`){let n=e.declaration;if(n?.type===`ClassDeclaration`||n?.type===`FunctionDeclaration`||n?.type===`TSInterfaceDeclaration`||n?.type===`TSTypeAliasDeclaration`){let e=m(n.id);e&&t.add(e);continue}if(n?.type===`VariableDeclaration`){for(let e of n.declarations){let n=m(e.id);n&&t.add(n)}continue}for(let n of e.specifiers??[]){if(n.type!==`ExportSpecifier`)continue;let e=m(n.local);e&&t.add(e)}continue}if(e.type===`ExportDefaultDeclaration`){let n=e.declaration,r=m(n)||m(n?.id??null);r&&t.add(r)}}return t}function y(e,t){let n=t?.trim();if(n&&n.length>0)return n;let r=e.endsWith(`Module`)?e.slice(0,-6):e,i=r.length>0?r:e;return i.charAt(0).toLowerCase()+i.slice(1)}function b(e,t){let n=t?.trim();return n&&n.length>0?n:e}function x(e,t){let n=e.trim();if(n.startsWith(`view:`)){let e=n.slice(5).trim();return e.length>0?e:null}let r=t?.trim();return r&&r.length>0?r:null}const S=new Set([`onInit`,`onReady`,`onShutdown`,`onDispose`]);function C(e){if(!e||e.type!==`CallExpression`)return!1;let t=e.callee;if(t?.type!==`Identifier`||t.name!==`inject`)return!1;let n=e.arguments??[];return n[0]?.type===`Identifier`&&n[0].name===`SignalBus`}function ee(e){let t=new Set,n=e.body?.body??[];for(let e of n){if(e.type!==`PropertyDefinition`||e.computed||!C(e.value))continue;let n=f(e.key);n&&t.add(n)}return t}function w(e){let t=e.value?.params??[],n=[];return t.forEach((e,t)=>{let r=m(e);r&&n.push({index:t,name:r})}),n}function T(e,t){let n=e.find(e=>e.name===t);return n?n.index:null}function te(e,t){if(!e)return{kind:`void`};if(e.type===`Identifier`){if(e.name===`undefined`)return{kind:`void`};let n=T(t,e.name);return n===null?{kind:`unknown`}:{kind:`method-parameter`,parameterIndex:n}}if(e.type!==`ObjectExpression`)return{kind:`unknown`};let n=null,r=[];for(let t of e.properties??[]){if(t.type!==`Property`||t.kind!==`init`||t.computed)return{kind:`unknown`};let e=f(t.key),i=t.value;if(!e||!i||i.type!==`MemberExpression`||i.computed)return{kind:`unknown`};let a=m(i.object),o=f(i.property);if(!a||!o||e!==o)return{kind:`unknown`};if(n===null)n=a;else if(n!==a)return{kind:`unknown`};r.push(e)}if(!n)return{kind:`unknown`};let i=T(t,n);return i===null?{kind:`unknown`}:{kind:`method-parameter-pick`,parameterIndex:i,keys:r}}function E(e,t){if(!e||e.type!==`MemberExpression`||e.computed)return!1;let n=f(e.property);return e.object?.type===`ThisExpression`&&!!n&&t.has(n)}function D(e,t){return E(e,t)||C(e)}function O(e,t){if(!(!e||typeof e!=`object`)){t(e);for(let n of Object.values(e)){if(Array.isArray(n)){for(let e of n)O(e,t);continue}O(n,t)}}}function k(e,t,n,r){let i=[],a=w(e);return O(e.value?.body,e=>{if(e.type!==`CallExpression`)return;let o=e.callee;if(!o||o.type!==`MemberExpression`||o.computed||!D(o.object,r))return;let s=f(o.property);if(s!==`publish`&&s!==`subscribe`)return;let c=e.arguments??[],u=l(c[0]);u&&i.push({id:u,methodName:n,ownerClassName:t,source:s,payload:s===`publish`?te(c[1],a):{kind:`unknown`}})}),i}function A(e,t){let n=[],r=[],i=[],a=ee(e),o=e.body?.body??[];for(let e of o){if(e.type!==`MethodDefinition`||e.kind!==`method`||e.computed||e.static||e.accessibility===`private`)continue;let o=f(e.key);if(!o||(r.push(...k(e,t,o,a)),S.has(o)))continue;let s=g(e,`command`),c=g(e,`query`),u=g(e,`signal`),d=g(e,`job`);if(s||c){let r=_(e,s?`command`:`query`),i=l(r?p(r,`id`):null);n.push({id:b(o,i),methodName:o,kind:s?`command`:`query`,ownerClassName:t})}if(u){let n=_(e,`signal`),i=l(n?p(n,`id`):null);r.push({id:b(o,i),methodName:o,ownerClassName:t,source:`decorator`,payload:{kind:`method-parameter`,parameterIndex:0}})}if(d){let n=_(e,`job`),r=l(n?p(n,`id`):null),a=l(n?p(n,`cron`):null);i.push({id:b(o,r),methodName:o,ownerClassName:t,cron:a})}}return{methods:n,signals:r,jobs:i}}function ne(e,t){let n=v(e),r=[],i=[],a=[],o=[],s=e.body;if(!s)return{modules:r,providers:i,windows:a,views:o};for(let e of s){let s=null;if(e.type===`ClassDeclaration`?s=e:(e.type===`ExportNamedDeclaration`&&e.declaration?.type===`ClassDeclaration`||e.type===`ExportDefaultDeclaration`&&e.declaration?.type===`ClassDeclaration`)&&(s=e.declaration),!s)continue;let c=h(s);if(!c)continue;let f=n.has(c);if(g(s,`Module`)){let e=_(s,`Module`),n=l(e?p(e,`id`):null),i=[...d(e?p(e,`imports`):null),...d(e?p(e,`dependsOn`):null)],a=[...d(e?p(e,`providers`):null),...d(e?p(e,`services`):null)],{methods:o,signals:u,jobs:m}=A(s,c);r.push({kind:`module`,id:y(c,n),className:c,filePath:t,exported:f,importClassNames:[...new Set(i)],providerClassNames:[...new Set(a)],methods:o,signals:u,jobs:m});continue}if(g(s,`Injectable`)){let{methods:e,signals:n,jobs:r}=A(s,c);i.push({kind:`injectable`,className:c,filePath:t,exported:f,methods:e,signals:n,jobs:r});continue}if(g(s,`Window`)){let{methods:e,signals:n,jobs:r}=A(s,c),o=_(s,`Window`),u=l(o?p(o,`id`):null);if(!u){console.warn(`[codegen] Skipping @Window() with non-literal id in ${t}`);continue}i.push({kind:`injectable`,className:c,filePath:t,exported:f,methods:e,signals:n,jobs:r}),a.push({id:u,className:c,filePath:t,exported:f});continue}if(g(s,`View`)){let{methods:e,signals:n,jobs:r}=A(s,c),a=_(s,`View`),d=l(a?p(a,`source`):null);if(!d){console.warn(`[codegen] Skipping @View() with non-literal source in ${t}`);continue}let m=x(d,l(a?p(a,`id`):null));if(!m){console.warn(`[codegen] Skipping @View() with non-literal id in ${t}`);continue}i.push({kind:`injectable`,className:c,filePath:t,exported:f,methods:e,signals:n,jobs:r}),o.push({id:m,className:c,filePath:t,exported:f,source:d,access:u(a?p(a,`access`):null),signals:u(a?p(a,`signals`):null)})}}return{modules:r,providers:i,windows:a,views:o}}const re=[/\.d\.ts$/,/\.test\.ts$/,/\.spec\.ts$/,/\.gen\.ts$/];function ie(e){return n(e).startsWith(`._`)||e.includes(`/__MACOSX/`)?!1:e.endsWith(`.ts`)&&!re.some(t=>t.test(e))}async function ae(e){let{glob:t}=await import(`tinyglobby`);return(await t([`**/*.ts`],{cwd:e,absolute:!0,ignore:[`node_modules/**`,`**/__MACOSX/**`,`**/.DS_Store`,`**/._*`]})).filter(ie).sort()}async function oe(e){let t=await ae(e),n=[],r=[],i=[],a=[];for(let e of t){let{program:t}=c(e),o=ne(t,e);n.push(...o.modules),r.push(...o.providers),i.push(...o.windows),a.push(...o.views)}let o=new Map(r.map(e=>[e.className,e])),s=new Map(n.map(e=>[e.className,e]));return{modules:n.map(e=>{let t=[];for(let n of e.providerClassNames){let r=o.get(n);if(!r){console.warn(`[codegen] Module "${e.id}" references unknown provider class "${n}" in ${e.filePath}`);continue}t.push({className:r.className,filePath:r.filePath,exported:r.exported,methods:[...r.methods],signals:[...r.signals],jobs:[...r.jobs]})}let n=[];for(let t of e.importClassNames){let r=s.get(t);if(!r){console.warn(`[codegen] Module "${e.id}" references unknown module class "${t}" in ${e.filePath}`);continue}n.push(r.id)}return{id:e.id,className:e.className,filePath:e.filePath,exported:e.exported,imports:[...new Set(n)],providers:t,methods:[...e.methods],signals:[...e.signals],jobs:[...e.jobs]}}),windows:i,views:a}}function j(e,t){let n=o(r(e),t).replaceAll(`\\`,`/`).replace(/\.(?:[cm]?tsx?|[cm]?jsx?)$/,``);return n.startsWith(`.`)?n:`./${n}`}function M(e,t){let n=o(r(e),t).replaceAll(`\\`,`/`);return n.startsWith(`.`)?n:`./${n}`}function N(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:JSON.stringify(e)}const P=`// Auto-generated by Electro codegen. Do not edit.
2
+ `;function F(e){switch(e.source){case`decorator`:return 400;case`publish`:switch(e.payload.kind){case`method-parameter-pick`:return 350;case`method-parameter`:return 340;case`void`:return 330;case`unknown`:return 320}case`subscribe`:return 100}}function I(e,t){return F(t)>F(e)}function L(e){let t=new Map,n=(e,n)=>{let r=t.get(e.id);(!r||I(r.signal,e))&&t.set(e.id,{signal:e,filePath:n})};for(let t of e){for(let e of t.signals)n(e,t.filePath);for(let e of t.providers)for(let t of e.signals)n(t,e.filePath)}return t}const R=`${P}// @ts-nocheck
3
+ // Electro runtime authoring contract types — provides IDE completions for modules, signals, jobs, windows, and runtime-declared views.
4
+
5
+ export {};
6
+
7
+ type _Instance<T> = T extends abstract new (...args: never[]) => infer R ? R : never;
8
+ type _ModuleAuthoringApi<TModuleId extends import("@electrojs/runtime").ModuleRegistryId> =
9
+ import("@electrojs/runtime").ModuleAuthoringApi<TModuleId>;
10
+ type _WindowAuthoringApi = import("@electrojs/runtime").WindowAuthoringApi;
11
+ type _ViewAuthoringApi = import("@electrojs/runtime").ViewAuthoringApi;
12
+ type _InvokeMethod<T, K extends PropertyKey> =
13
+ K extends keyof _Instance<T>
14
+ ? _Instance<T>[K] extends (...args: infer A) => infer V
15
+ ? (...args: A) => Promise<Awaited<V>>
16
+ : never
17
+ : never;
18
+ type _SignalPayloadFromMethodParam<T, K extends PropertyKey, I extends number> =
19
+ K extends keyof _Instance<T>
20
+ ? _Instance<T>[K] extends (...args: infer A) => infer _Ignored
21
+ ? I extends keyof A
22
+ ? A[I]
23
+ : void
24
+ : never
25
+ : never;
26
+ type _SignalPayloadFromMethod<T, K extends PropertyKey> =
27
+ _SignalPayloadFromMethodParam<T, K, 0>;
28
+ `;function z(e,t,n){return`_InvokeMethod<typeof import("${j(e,n)}").${t.ownerClassName}, ${JSON.stringify(t.methodName)}>`}function B(e,t,n){let r=`typeof import("${j(e,n)}").${t.ownerClassName}`;switch(t.payload.kind){case`method-parameter`:return t.source===`decorator`&&t.payload.parameterIndex===0?`_SignalPayloadFromMethod<${r}, ${JSON.stringify(t.methodName)}>`:`_SignalPayloadFromMethodParam<${r}, ${JSON.stringify(t.methodName)}, ${t.payload.parameterIndex}>`;case`method-parameter-pick`:{let e=t.payload.keys.map(e=>JSON.stringify(e)).join(` | `)||`never`;return`Pick<_SignalPayloadFromMethodParam<${r}, ${JSON.stringify(t.methodName)}, ${t.payload.parameterIndex}>, ${e}>`}case`void`:return`void`;case`unknown`:return`unknown`}}function V(e,t){let n=[];for(let r of e){for(let e of r.methods){let i=`${r.id}:${e.id}`;n.push(` ${JSON.stringify(i)}: ${z(t,e,r.filePath)};`)}for(let e of r.providers)for(let i of e.methods){let a=`${r.id}:${i.id}`;n.push(` ${JSON.stringify(a)}: ${z(t,i,e.filePath)};`)}}return`\n interface ModuleMethodMap {\n${n.join(`
29
+ `)}\n }\n`}function H(e,t){return`\n interface ModuleApiRegistry {\n${e.filter(e=>e.exported).map(e=>{let n=[...e.methods.map(t=>({method:t,filePath:e.filePath})),...e.providers.flatMap(e=>e.methods.map(t=>({method:t,filePath:e.filePath})))].map(({method:e,filePath:n})=>` ${N(e.id)}: ${z(t,e,n)};`),r=n.length>0?`{\n${n.join(`
30
+ `)}\n }`:`{}`;return` ${JSON.stringify(e.id)}: ${r};`}).join(`
31
+ `)}\n }\n`}function U(e,t){let n=new Map;for(let r of L(e).values())n.set(r.signal.id,` ${JSON.stringify(r.signal.id)}: ${B(t,r.signal,r.filePath)};`);return`\n interface ModuleSignalPayloadMap {\n${[...n.values()].join(`
32
+ `)}\n }\n`}function W(e){return`\n interface ModuleJobRegistry {\n${e.map(e=>{let t=[...new Set([...e.jobs.map(e=>e.id),...e.providers.flatMap(e=>e.jobs.map(e=>e.id))])],n=t.length>0?t.map(e=>JSON.stringify(e)).join(` | `):`never`;return` ${JSON.stringify(e.id)}: ${n};`}).join(`
33
+ `)}\n }\n`}function G(e,t){let n=new Map;for(let r of e)for(let e of r.providers)if(e.exported&&!n.has(e.className)){let r=j(t,e.filePath);n.set(e.className,` ${JSON.stringify(e.className)}: typeof import("${r}").${e.className};`)}return`\n interface InjectableClassRegistry {\n${[...n.values()].join(`
34
+ `)}\n }\n`}function K(e,t){return`\n interface WindowClassRegistry {\n${e.filter(e=>e.exported).map(e=>{let n=j(t,e.filePath);return` ${JSON.stringify(e.id)}: typeof import("${n}").${e.className};`}).join(`
35
+ `)}\n }\n`}function q(e,t){return`\n interface ViewClassRegistry {\n${e.filter(e=>e.exported).map(e=>{let n=j(t,e.filePath);return` ${JSON.stringify(e.id)}: typeof import("${n}").${e.className};`}).join(`
36
+ `)}\n }\n`}function se(e){return`\n interface ViewAccessRegistry {\n${[...new Set([...e.flatMap(e=>e.methods.map(t=>`${e.id}:${t.id}`)),...e.flatMap(e=>e.providers.flatMap(t=>t.methods.map(t=>`${e.id}:${t.id}`)))])].map(e=>` ${JSON.stringify(e)}: true;`).join(`
37
+ `)}\n }\n`}function ce(e){return`\n interface ViewSignalRegistry {\n${[...L(e).keys()].map(e=>` ${JSON.stringify(e)}: true;`).join(`
38
+ `)}\n }\n`}function le(e){return`\n interface BundledViewIdRegistry {\n${e.filter(e=>e.source.startsWith(`view:`)).map(e=>e.id).map(e=>` ${JSON.stringify(e)}: true;`).join(`
39
+ `)}\n }\n`}function ue(e,t){let n=new Set,r=new Set(e.views.map(e=>e.className)),i=new Set(e.windows.map(e=>e.className)),a=(e,r,i,a,o)=>{if(!a)return;let s=j(t,e),c;switch(i){case`module`:c=`_ModuleAuthoringApi<${JSON.stringify(o)}>`;break;case`window`:c=`_WindowAuthoringApi`;break;case`view`:c=`_ViewAuthoringApi`;break}n.add(`import "${s}";\ndeclare module "${s}" {\n interface ${r} extends ${c} {}\n}`)};for(let t of e.modules){a(t.filePath,t.className,`module`,t.exported,t.id);for(let e of t.providers)r.has(e.className)||i.has(e.className)||a(e.filePath,e.className,`module`,e.exported,t.id)}for(let t of e.windows)a(t.filePath,t.className,`window`,t.exported);for(let t of e.views)a(t.filePath,t.className,`view`,t.exported);return n.size>0?`\n${[...n].join(`
40
+
41
+ `)}\n`:`
42
+ `}function de(e,t){let n=a(t,`electro-env.d.ts`);return{path:`electro-env.d.ts`,content:`${R}
43
+ declare module "@electrojs/runtime" {
44
+ ${V(e.modules,n)}${H(e.modules,n)}${U(e.modules,n)}${W(e.modules)}${G(e.modules,n)}${K(e.windows,n)}${q(e.views,n)}
45
+ }
46
+
47
+ declare module "@electrojs/common" {
48
+ ${se(e.modules)}${ce(e.modules)}${le(e.views)}
49
+ }${ue(e,n)}`}}const fe=`${P}// @ts-nocheck
50
+ // Electro renderer bridge contract types — provides IDE completions for bridge access and forwarded signals.
51
+
52
+ export {};
53
+
54
+ type _Instance<T> = T extends abstract new (...args: never[]) => infer R ? R : never;
55
+ type _SignalPayloadFromMethodParam<T, K extends PropertyKey, I extends number> =
56
+ K extends keyof _Instance<T>
57
+ ? _Instance<T>[K] extends (...args: infer A) => infer _Ignored
58
+ ? I extends keyof A
59
+ ? A[I]
60
+ : void
61
+ : never
62
+ : never;
63
+ type _SignalPayloadFromMethod<T, K extends PropertyKey> =
64
+ _SignalPayloadFromMethodParam<T, K, 0>;
65
+ type _BridgeInputFromMethod<T, K extends PropertyKey> =
66
+ K extends keyof _Instance<T>
67
+ ? _Instance<T>[K] extends (...args: infer A) => infer _Ignored
68
+ ? A extends []
69
+ ? undefined
70
+ : A extends [infer TOnly]
71
+ ? TOnly
72
+ : A
73
+ : never
74
+ : never;
75
+ type _BridgeOutputFromMethod<T, K extends PropertyKey> =
76
+ K extends keyof _Instance<T>
77
+ ? _Instance<T>[K] extends (...args: infer _Args) => infer V
78
+ ? Awaited<V>
79
+ : never
80
+ : never;
81
+ `;function pe(e,t){let n=new Set(e.access),r=[];for(let e of t){for(let t of e.methods)n.has(`${e.id}:${t.id}`)&&r.push({moduleId:e.id,method:t,filePath:e.filePath});for(let t of e.providers)for(let i of t.methods)n.has(`${e.id}:${i.id}`)&&r.push({moduleId:e.id,method:i,filePath:t.filePath})}return r}function me(e,t){let n=new Set(e.signals),r=[];for(let e of L(t).values())n.has(e.signal.id)&&r.push({signal:e.signal,filePath:e.filePath});return r}function he(e,t){return`_BridgeInputFromMethod<typeof import("${j(e,t.filePath)}").${t.method.ownerClassName}, ${JSON.stringify(t.method.methodName)}>`}function ge(e,t){return`_BridgeOutputFromMethod<typeof import("${j(e,t.filePath)}").${t.method.ownerClassName}, ${JSON.stringify(t.method.methodName)}>`}function _e(e,t){return`import("@electrojs/renderer").BridgeContractEntry<${he(e,t)}, ${ge(e,t)}>`}function ve(e,t){let n=`typeof import("${j(e,t.filePath)}").${t.signal.ownerClassName}`;switch(t.signal.payload.kind){case`method-parameter`:return t.signal.source===`decorator`&&t.signal.payload.parameterIndex===0?`_SignalPayloadFromMethod<${n}, ${JSON.stringify(t.signal.methodName)}>`:`_SignalPayloadFromMethodParam<${n}, ${JSON.stringify(t.signal.methodName)}, ${t.signal.payload.parameterIndex}>`;case`method-parameter-pick`:{let e=t.signal.payload.keys.map(e=>JSON.stringify(e)).join(` | `)||`never`;return`Pick<_SignalPayloadFromMethodParam<${n}, ${JSON.stringify(t.signal.methodName)}, ${t.signal.payload.parameterIndex}>, ${e}>`}case`void`:return`void`;case`unknown`:return`unknown`}}function ye(e,t,n){let r=pe(e,t),i=me(e,t),a=e=>r.filter(t=>t.method.kind===e).map(e=>` ${JSON.stringify(`${e.moduleId}:${e.method.id}`)}: ${_e(n,e)};`).join(`
82
+ `);return`declare module "@electrojs/renderer" {
83
+ interface BridgeQueries {
84
+ ${[a(`query`),a(`command`)].filter(Boolean).join(`
85
+ `)}
86
+ }
87
+
88
+ interface BridgeCommands {
89
+ ${a(`command`)}
90
+ }
91
+
92
+ interface BridgeSignals {
93
+ ${i.map(e=>` ${JSON.stringify(e.signal.id)}: ${ve(n,e)};`).join(`
94
+ `)}
95
+ }
96
+ }`}function be(e,t,n){return`${fe}
97
+ ${t?ye(t,e.modules,n):``}
98
+ `}function xe(e,t){return t.map(t=>{let n=a(t.packageRoot,`electro-env.d.ts`),r=be(e,e.views.find(e=>e.id===t.viewId),n);return{packageRoot:t.packageRoot,path:`electro-env.d.ts`,content:r,indexDts:r}})}function Se(e,t,n){let a=n.trim();return a.length===0||!(a.startsWith(`.`)||i(a))?a:M(t,i(a)?a:s(r(e.__source),a))}function Ce(e,t,n){let r=`generated/preload/${e.id}.gen.ts`,i=`${n}/${r}`,a=`${P}
99
+ import { contextBridge, ipcRenderer } from "electron";
100
+ import { createBridgeClient } from "@electrojs/runtime/client";
101
+
102
+ contextBridge.exposeInMainWorld("__ELECTRO_RENDERER__", createBridgeClient({
103
+ viewId: ${JSON.stringify(e.id)},
104
+ ipcRenderer,
105
+ }));
106
+ `;if(t?.preload&&t.preload.length>0){let e=Se(t,i,t.preload);e.length>0&&(a+=`\n// User preload extension\nimport ${JSON.stringify(e)};\n`)}return{path:r,content:a}}function J(e,t){return e.id.localeCompare(t.id)||e.filePath.localeCompare(t.filePath)}function Y(e){return e.length===0?`[]`:`[\n${e.map(e=>` ${e},`).join(`
107
+ `)}\n]`}function X(e,t){let{modules:n,windows:r,views:i}=e;if(n.length===0&&r.length===0&&i.length===0)return null;let o=`generated/runtime/registry.gen.ts`,s=a(t,o),c=[],l=[...n].sort(J).flatMap((e,t)=>{if(!e.exported)return console.warn(`[codegen] Skipping non-exported module "${e.className}" from runtime registry`),[];let n=`__electro_module_${t}`,r=j(s,e.filePath);return c.push(`import { ${e.className} as ${n} } from "${r}";`),[{...e,localName:n}]}),u=[...r].sort(J).flatMap((e,t)=>{if(!e.exported)return console.warn(`[codegen] Skipping non-exported window "${e.className}" from runtime registry`),[];let n=`__electro_window_${t}`,r=j(s,e.filePath);return c.push(`import { ${e.className} as ${n} } from "${r}";`),[{...e,localName:n}]}),d=[...i].sort(J).flatMap((e,t)=>{if(!e.exported)return console.warn(`[codegen] Skipping non-exported view "${e.className}" from runtime registry`),[];let n=`__electro_view_${t}`,r=j(s,e.filePath);return c.push(`import { ${e.className} as ${n} } from "${r}";`),[{...e,localName:n}]}),f=new Set(l.flatMap(e=>e.imports)),p=l.filter(e=>!f.has(e.id)),m=new Set(l.map(e=>e.id));for(let e of l)for(let t of e.imports)m.has(t)||console.warn(`[codegen] Module "${e.id}" depends on non-exported module "${t}" in runtime registry`);l.length>0&&p.length===0?console.warn(`[codegen] Runtime registry found no root module candidate; skipping electroAppDefinition`):p.length>1&&console.warn(`[codegen] Runtime registry found multiple root module candidates (${p.map(e=>e.id).join(`, `)}); skipping electroAppDefinition`);let h=p.length===1,g=h?[`AppKernelDefinition`,`ModuleClass`,`ViewClass`,`WindowClass`]:[`ModuleClass`,`ViewClass`,`WindowClass`],_=Y(l.map(e=>e.localName)),v=Y(u.map(e=>e.localName)),y=Y(d.map(e=>e.localName)),b=Y(p.map(e=>e.localName)),x=p[0],S=h&&x?`\nexport const electroAppDefinition = {\n root: ${x.localName},\n modules: electroModules,\n windows: electroWindows,\n views: electroViews,\n} satisfies AppKernelDefinition;\n`:``;return{path:o,content:`${P}
108
+ import type { ${g.join(`, `)} } from "@electrojs/runtime";
109
+ ${c.length>0?`${c.join(`
110
+ `)}\n`:``}
111
+ export const electroModules = ${_} as const satisfies readonly ModuleClass[];
112
+ export const electroWindows = ${v} as const satisfies readonly WindowClass[];
113
+ export const electroViews = ${y} as const satisfies readonly ViewClass[];
114
+ export const electroRootModules = ${b} as const satisfies readonly ModuleClass[];
115
+
116
+ export const electroRuntimeRegistry = {
117
+ modules: electroModules,
118
+ windows: electroWindows,
119
+ views: electroViews,
120
+ rootModules: electroRootModules,
121
+ } as const;${S}
122
+ export default electroRuntimeRegistry;
123
+ `}}var Z=class extends Error{constructor(e,t){super(e),this.name=`CodegenError`,this.code=t}},Q=class extends Z{constructor(e){super(e.map(e=>e.message).join(`
124
+ `),`VALIDATION_ERROR`),this.name=`ValidationError`,this.diagnostics=e}};function we(e){let t=De(e);if(t.length>0)throw new Q(t)}function $(e){let t=new Set,n=new Set;for(let r of e)t.has(r)?n.add(r):t.add(r);return[...n]}function Te(e){let t=new Set;for(let n of e.modules){for(let e of n.methods)t.add(`${n.id}:${e.id}`);for(let e of n.providers)for(let r of e.methods)t.add(`${n.id}:${r.id}`)}return t}function Ee(e){return new Set(L(e.modules).keys())}function De(e){let{scanResult:t}=e,n=[];for(let e of $(t.modules.map(e=>e.id)))n.push({code:`duplicate-module-id`,message:`Duplicate module id "${e}" detected in scan result`});for(let e of t.modules){let t=[...e.methods.map(e=>e.id),...e.providers.flatMap(e=>e.methods.map(e=>e.id))];for(let r of $(t))n.push({code:`duplicate-module-method`,message:`Module "${e.id}" exposes duplicate method id "${r}"`})}for(let e of $(t.windows.map(e=>e.id)))n.push({code:`duplicate-window-id`,message:`Duplicate window id "${e}" detected in scan result`});for(let e of $(t.views.map(e=>e.id)))n.push({code:`duplicate-view-id`,message:`Duplicate view id "${e}" detected in scan result`});let r=Te(t);for(let e of t.views)for(let t of e.access)r.has(t)||n.push({code:`unknown-access-key`,message:`View "${e.id}" references unknown access key "${t}"`,filePath:e.filePath});let i=Ee(t);for(let e of t.views)for(let t of e.signals)i.has(t)||n.push({code:`unknown-signal-key`,message:`View "${e.id}" references unknown signal key "${t}"`,filePath:e.filePath});if(e.views&&e.views.length>0){let r=new Set(t.views.map(e=>e.id));for(let t of e.views)r.has(t.id)||n.push({code:`missing-runtime-view`,message:`Generator view definition "${t.id}" has no matching @View() class`})}return n}function Oe(e){we(e);let{scanResult:t,outputDir:n,srcDir:r}=e,i=[],a=new Map;if(e.views)for(let t of e.views)a.set(t.id,t);for(let e of t.views){let t=a.get(e.id);i.push(Ce(e,t,n))}let o=X(t,n);return o&&i.push(o),{files:i,envTypes:de(t,r),packageTypes:e.packageTargets?xe(t,e.packageTargets):[]}}export{Z as CodegenError,Q as ValidationError,Oe as generate,oe as scan};
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@electrojs/codegen",
3
+ "version": "1.0.0",
4
+ "description": "Code generator for electro module runtime — scans modules/views and emits bridge and registry artifacts",
5
+ "homepage": "https://github.com/MyraxByte/electrojs#readme",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/MyraxByte/electrojs.git",
10
+ "directory": "packages/codegen"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "type": "module",
16
+ "main": "./dist/index.mjs",
17
+ "types": "./dist/index.d.mts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.mts",
21
+ "import": "./dist/index.mjs"
22
+ }
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "dependencies": {
28
+ "oxc-parser": "^0.121.0",
29
+ "tinyglobby": "^0.2.15"
30
+ },
31
+ "devDependencies": {
32
+ "tsdown": "^0.21.4",
33
+ "vitest": "^4.1.1",
34
+ "@electrojs/config": "1.0.0",
35
+ "@electrojs/runtime": "1.0.0",
36
+ "@electrojs/common": "1.0.0"
37
+ },
38
+ "peerDependencies": {
39
+ "@electrojs/common": ">=1",
40
+ "@electrojs/config": ">=1",
41
+ "@electrojs/runtime": ">=1",
42
+ "@types/node": "^25.5.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsdown",
46
+ "test": "vitest"
47
+ }
48
+ }