@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 +21 -0
- package/README.md +137 -0
- package/dist/index.d.mts +214 -0
- package/dist/index.mjs +124 -0
- package/package.json +48 -0
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
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
+
}
|