@ic-reactor/codegen 0.8.0 → 0.10.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/README.md +12 -1
- package/dist/index.cjs +39 -18
- package/dist/index.d.cts +24 -6
- package/dist/index.d.ts +24 -6
- package/dist/index.js +39 -18
- package/package.json +3 -3
- package/src/__snapshots__/reactor.test.ts.snap +21 -0
- package/src/generators/reactor.ts +31 -21
- package/src/index.ts +1 -0
- package/src/pipeline.test.ts +130 -0
- package/src/pipeline.ts +32 -4
- package/src/reactor.test.ts +18 -0
- package/src/types.ts +12 -0
package/README.md
CHANGED
|
@@ -24,7 +24,9 @@ await runCanisterPipeline({
|
|
|
24
24
|
globalConfig: {
|
|
25
25
|
outDir: "src/declarations",
|
|
26
26
|
clientManagerPath: "../../clients",
|
|
27
|
+
target: "react",
|
|
27
28
|
},
|
|
29
|
+
generateReactor: true,
|
|
28
30
|
})
|
|
29
31
|
```
|
|
30
32
|
|
|
@@ -38,8 +40,16 @@ Set `canisterConfig.mode` to choose the generated reactor class:
|
|
|
38
40
|
- `CandidDisplayReactor`
|
|
39
41
|
- `MetadataDisplayReactor`
|
|
40
42
|
|
|
43
|
+
Set `target` to control whether generated files include React hooks:
|
|
44
|
+
|
|
45
|
+
- `react` (default): generates the reactor plus bound `createActorHooks` exports
|
|
46
|
+
- `core`: generates only the typed reactor exports with no `@ic-reactor/react` dependency
|
|
47
|
+
|
|
41
48
|
Codegen now writes two files per canister: a managed `index.generated.ts` implementation that is regenerated on every run, and an `index.ts` entry wrapper. The wrapper is created once, then preserved unless it still matches the default generated wrapper or an older generated scaffold that can be migrated automatically.
|
|
42
49
|
|
|
50
|
+
Set `generateReactor: false` if you only want the bindgen/declaration output and
|
|
51
|
+
need to skip `index.generated.ts` and `index.ts`.
|
|
52
|
+
|
|
43
53
|
## Generators
|
|
44
54
|
|
|
45
55
|
You can also use individual generators if you need more granular control:
|
|
@@ -47,7 +57,8 @@ You can also use individual generators if you need more granular control:
|
|
|
47
57
|
- **`generateDeclarations`**: Generates `.js` (factory), `.d.ts` (types), and `.did` copy.
|
|
48
58
|
- **`generateReactorFile`**: Generates the managed `index.generated.ts` implementation using either `DisplayReactor` or `Reactor`.
|
|
49
59
|
- **`generateReactorEntryFile`**: Generates the stable `index.ts` wrapper that re-exports from `index.generated.ts`.
|
|
50
|
-
- **`generateClientFile`**: Generates a `ClientManager` boilerplate file
|
|
60
|
+
- **`generateClientFile`**: Generates a `ClientManager` boilerplate file that
|
|
61
|
+
imports `ClientManager` from `@ic-reactor/react`.
|
|
51
62
|
|
|
52
63
|
## Utilities
|
|
53
64
|
|
package/dist/index.cjs
CHANGED
|
@@ -124,11 +124,11 @@ function getServiceTypeName(canisterName) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// src/generators/reactor.ts
|
|
127
|
-
function getReactorClassImportSource(reactorClass) {
|
|
127
|
+
function getReactorClassImportSource(reactorClass, runtimeTarget) {
|
|
128
128
|
switch (reactorClass) {
|
|
129
129
|
case "Reactor":
|
|
130
130
|
case "DisplayReactor":
|
|
131
|
-
return "@ic-reactor/react";
|
|
131
|
+
return runtimeTarget === "core" ? "@ic-reactor/core" : "@ic-reactor/react";
|
|
132
132
|
case "CandidReactor":
|
|
133
133
|
case "CandidDisplayReactor":
|
|
134
134
|
case "MetadataDisplayReactor":
|
|
@@ -141,6 +141,7 @@ function generateReactorFile(options) {
|
|
|
141
141
|
didFile,
|
|
142
142
|
clientManagerPath = "../../clients",
|
|
143
143
|
canisterId,
|
|
144
|
+
runtimeTarget = "react",
|
|
144
145
|
reactorClass = "DisplayReactor"
|
|
145
146
|
} = options;
|
|
146
147
|
const pascalName = toPascalCase(canisterName);
|
|
@@ -148,11 +149,24 @@ function generateReactorFile(options) {
|
|
|
148
149
|
const serviceName = getServiceTypeName(canisterName);
|
|
149
150
|
const baseName = import_node_path2.default.basename(didFile, ".did");
|
|
150
151
|
const declarationsPath = `./declarations/${baseName}`;
|
|
151
|
-
const reactorImportSource = getReactorClassImportSource(
|
|
152
|
+
const reactorImportSource = getReactorClassImportSource(
|
|
153
|
+
reactorClass,
|
|
154
|
+
runtimeTarget
|
|
155
|
+
);
|
|
152
156
|
const canisterIdLine = canisterId ? ` canisterId: ${JSON.stringify(canisterId)},
|
|
153
157
|
` : "";
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
const hookExports = runtimeTarget === "react" ? `
|
|
159
|
+
|
|
160
|
+
export const {
|
|
161
|
+
useActorQuery: use${pascalName}Query,
|
|
162
|
+
useActorSuspenseQuery: use${pascalName}SuspenseQuery,
|
|
163
|
+
useActorInfiniteQuery: use${pascalName}InfiniteQuery,
|
|
164
|
+
useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
|
|
165
|
+
useActorMutation: use${pascalName}Mutation,
|
|
166
|
+
useActorMethod: use${pascalName}Method,
|
|
167
|
+
} = createActorHooks(${reactorName})
|
|
168
|
+
` : "";
|
|
169
|
+
return `${runtimeTarget === "react" ? 'import { createActorHooks } from "@ic-reactor/react"\n' : ""}import { ${reactorClass} } from "${reactorImportSource}"
|
|
156
170
|
import { clientManager } from "${clientManagerPath}"
|
|
157
171
|
import { idlFactory, type _SERVICE } from "${declarationsPath}"
|
|
158
172
|
|
|
@@ -168,24 +182,14 @@ export const ${reactorName} = new ${reactorClass}<${serviceName}>({
|
|
|
168
182
|
clientManager,
|
|
169
183
|
idlFactory,
|
|
170
184
|
${canisterIdLine} name: "${canisterName}",
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
export const {
|
|
174
|
-
useActorQuery: use${pascalName}Query,
|
|
175
|
-
useActorSuspenseQuery: use${pascalName}SuspenseQuery,
|
|
176
|
-
useActorInfiniteQuery: use${pascalName}InfiniteQuery,
|
|
177
|
-
useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
|
|
178
|
-
useActorMutation: use${pascalName}Mutation,
|
|
179
|
-
useActorMethod: use${pascalName}Method,
|
|
180
|
-
} = createActorHooks(${reactorName})
|
|
181
|
-
`;
|
|
185
|
+
})${hookExports || "\n"}`;
|
|
182
186
|
}
|
|
183
187
|
function generateReactorEntryFile() {
|
|
184
188
|
return `/**
|
|
185
189
|
* Canister entrypoint.
|
|
186
190
|
*
|
|
187
191
|
* Created once by @ic-reactor/codegen and safe to customize.
|
|
188
|
-
* Keep the re-export below if you want generated
|
|
192
|
+
* Keep the re-export below if you want generated exports and types to stay in sync.
|
|
189
193
|
*/
|
|
190
194
|
export * from "./index.generated"
|
|
191
195
|
`;
|
|
@@ -195,6 +199,9 @@ export * from "./index.generated"
|
|
|
195
199
|
function resolveReactorClass(canisterConfig) {
|
|
196
200
|
return canisterConfig.mode ?? "DisplayReactor";
|
|
197
201
|
}
|
|
202
|
+
function resolveRuntimeTarget(canisterConfig, globalConfig) {
|
|
203
|
+
return canisterConfig.target ?? globalConfig.target ?? "react";
|
|
204
|
+
}
|
|
198
205
|
function normalizeFileContent(content) {
|
|
199
206
|
return content.replace(/\r\n/g, "\n").trim();
|
|
200
207
|
}
|
|
@@ -205,7 +212,12 @@ function isManagedEntryWrapper(content, expectedEntryContent) {
|
|
|
205
212
|
return normalizeFileContent(content) === normalizeFileContent(expectedEntryContent);
|
|
206
213
|
}
|
|
207
214
|
async function runCanisterPipeline(options) {
|
|
208
|
-
const {
|
|
215
|
+
const {
|
|
216
|
+
canisterConfig,
|
|
217
|
+
projectRoot,
|
|
218
|
+
globalConfig,
|
|
219
|
+
generateReactor = true
|
|
220
|
+
} = options;
|
|
209
221
|
const { name, didFile, clientManagerPath } = canisterConfig;
|
|
210
222
|
const files = [];
|
|
211
223
|
const resolvedDidFile = import_node_path3.default.isAbsolute(didFile) ? didFile : import_node_path3.default.resolve(projectRoot, didFile);
|
|
@@ -242,15 +254,24 @@ async function runCanisterPipeline(options) {
|
|
|
242
254
|
error: `Declarations step failed: ${err instanceof Error ? err.message : String(err)}`
|
|
243
255
|
};
|
|
244
256
|
}
|
|
257
|
+
if (!generateReactor) {
|
|
258
|
+
return {
|
|
259
|
+
canisterName: name,
|
|
260
|
+
success: true,
|
|
261
|
+
files
|
|
262
|
+
};
|
|
263
|
+
}
|
|
245
264
|
const reactorPath = import_node_path3.default.join(canisterOutDir, "index.generated.ts");
|
|
246
265
|
const entryPath = import_node_path3.default.join(canisterOutDir, "index.ts");
|
|
247
266
|
const reactorClass = resolveReactorClass(canisterConfig);
|
|
267
|
+
const runtimeTarget = resolveRuntimeTarget(canisterConfig, globalConfig);
|
|
248
268
|
try {
|
|
249
269
|
const reactorContent = generateReactorFile({
|
|
250
270
|
canisterName: name,
|
|
251
271
|
didFile: resolvedDidFile,
|
|
252
272
|
clientManagerPath: resolvedClientManagerPath,
|
|
253
273
|
canisterId: canisterConfig.canisterId,
|
|
274
|
+
runtimeTarget,
|
|
254
275
|
reactorClass
|
|
255
276
|
});
|
|
256
277
|
const entryContent = generateReactorEntryFile();
|
package/dist/index.d.cts
CHANGED
|
@@ -24,10 +24,16 @@ interface CanisterConfig {
|
|
|
24
24
|
* Defaults to DisplayReactor for backward compatibility.
|
|
25
25
|
*/
|
|
26
26
|
mode?: ReactorClassName;
|
|
27
|
+
/**
|
|
28
|
+
* Generated runtime target.
|
|
29
|
+
* `react` emits bound React hooks, `core` emits only the typed reactor exports.
|
|
30
|
+
*/
|
|
31
|
+
target?: CodegenTarget;
|
|
27
32
|
/** Optional fixed canister ID */
|
|
28
33
|
canisterId?: string;
|
|
29
34
|
}
|
|
30
35
|
type ReactorClassName = "Reactor" | "DisplayReactor" | "CandidReactor" | "CandidDisplayReactor" | "MetadataDisplayReactor";
|
|
36
|
+
type CodegenTarget = "react" | "core";
|
|
31
37
|
/**
|
|
32
38
|
* Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
|
|
33
39
|
*/
|
|
@@ -44,6 +50,11 @@ interface CodegenConfig {
|
|
|
44
50
|
* Individual canisters can override via `CanisterConfig.clientManagerPath`.
|
|
45
51
|
*/
|
|
46
52
|
clientManagerPath?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Default generated runtime target.
|
|
55
|
+
* Individual canisters can override via `CanisterConfig.target`.
|
|
56
|
+
*/
|
|
57
|
+
target?: CodegenTarget;
|
|
47
58
|
/** Canister configurations, keyed by canister name */
|
|
48
59
|
canisters: Record<string, CanisterConfig>;
|
|
49
60
|
}
|
|
@@ -68,8 +79,8 @@ interface GeneratorResult {
|
|
|
68
79
|
* Pipeline steps (in order):
|
|
69
80
|
* 1. Resolve paths (didFile, outDir)
|
|
70
81
|
* 2. Generate declarations (JS + .d.ts + .did copy)
|
|
71
|
-
* 3.
|
|
72
|
-
* 4.
|
|
82
|
+
* 3. Optionally generate reactor implementation (`index.generated.ts`)
|
|
83
|
+
* 4. Optionally create or migrate the user entry (`index.ts`)
|
|
73
84
|
*/
|
|
74
85
|
|
|
75
86
|
interface PipelineOptions {
|
|
@@ -83,7 +94,12 @@ interface PipelineOptions {
|
|
|
83
94
|
/**
|
|
84
95
|
* Global codegen config (for fallback outDir and clientManagerPath).
|
|
85
96
|
*/
|
|
86
|
-
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">;
|
|
97
|
+
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath" | "target">;
|
|
98
|
+
/**
|
|
99
|
+
* Whether the managed reactor files should be generated.
|
|
100
|
+
* Defaults to true.
|
|
101
|
+
*/
|
|
102
|
+
generateReactor?: boolean;
|
|
87
103
|
}
|
|
88
104
|
interface PipelineResult {
|
|
89
105
|
canisterName: string;
|
|
@@ -193,8 +209,8 @@ declare function declarationsExist(outDir: string, canisterName: string): boolea
|
|
|
193
209
|
/**
|
|
194
210
|
* Reactor File Generator
|
|
195
211
|
*
|
|
196
|
-
* Generates the managed `index.generated.ts` implementation for a canister
|
|
197
|
-
*
|
|
212
|
+
* Generates the managed `index.generated.ts` implementation for a canister.
|
|
213
|
+
* React targets also emit the full set of typed hooks via `createActorHooks`.
|
|
198
214
|
*
|
|
199
215
|
* Generated output example (for canister "backend"):
|
|
200
216
|
*
|
|
@@ -227,6 +243,8 @@ interface ReactorGeneratorOptions {
|
|
|
227
243
|
clientManagerPath?: string;
|
|
228
244
|
/** Optional fixed canister ID for the generated reactor */
|
|
229
245
|
canisterId?: string;
|
|
246
|
+
/** Generated runtime target */
|
|
247
|
+
runtimeTarget?: CodegenTarget;
|
|
230
248
|
/**
|
|
231
249
|
* Which reactor class should back the generated hooks.
|
|
232
250
|
* Default: "DisplayReactor" (backward compatible)
|
|
@@ -265,4 +283,4 @@ interface ClientGeneratorOptions {
|
|
|
265
283
|
*/
|
|
266
284
|
declare function generateClientFile(options?: ClientGeneratorOptions): string;
|
|
267
285
|
|
|
268
|
-
export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorClassName, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorEntryFile, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
|
|
286
|
+
export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type CodegenTarget, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorClassName, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorEntryFile, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
|
package/dist/index.d.ts
CHANGED
|
@@ -24,10 +24,16 @@ interface CanisterConfig {
|
|
|
24
24
|
* Defaults to DisplayReactor for backward compatibility.
|
|
25
25
|
*/
|
|
26
26
|
mode?: ReactorClassName;
|
|
27
|
+
/**
|
|
28
|
+
* Generated runtime target.
|
|
29
|
+
* `react` emits bound React hooks, `core` emits only the typed reactor exports.
|
|
30
|
+
*/
|
|
31
|
+
target?: CodegenTarget;
|
|
27
32
|
/** Optional fixed canister ID */
|
|
28
33
|
canisterId?: string;
|
|
29
34
|
}
|
|
30
35
|
type ReactorClassName = "Reactor" | "DisplayReactor" | "CandidReactor" | "CandidDisplayReactor" | "MetadataDisplayReactor";
|
|
36
|
+
type CodegenTarget = "react" | "core";
|
|
31
37
|
/**
|
|
32
38
|
* Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
|
|
33
39
|
*/
|
|
@@ -44,6 +50,11 @@ interface CodegenConfig {
|
|
|
44
50
|
* Individual canisters can override via `CanisterConfig.clientManagerPath`.
|
|
45
51
|
*/
|
|
46
52
|
clientManagerPath?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Default generated runtime target.
|
|
55
|
+
* Individual canisters can override via `CanisterConfig.target`.
|
|
56
|
+
*/
|
|
57
|
+
target?: CodegenTarget;
|
|
47
58
|
/** Canister configurations, keyed by canister name */
|
|
48
59
|
canisters: Record<string, CanisterConfig>;
|
|
49
60
|
}
|
|
@@ -68,8 +79,8 @@ interface GeneratorResult {
|
|
|
68
79
|
* Pipeline steps (in order):
|
|
69
80
|
* 1. Resolve paths (didFile, outDir)
|
|
70
81
|
* 2. Generate declarations (JS + .d.ts + .did copy)
|
|
71
|
-
* 3.
|
|
72
|
-
* 4.
|
|
82
|
+
* 3. Optionally generate reactor implementation (`index.generated.ts`)
|
|
83
|
+
* 4. Optionally create or migrate the user entry (`index.ts`)
|
|
73
84
|
*/
|
|
74
85
|
|
|
75
86
|
interface PipelineOptions {
|
|
@@ -83,7 +94,12 @@ interface PipelineOptions {
|
|
|
83
94
|
/**
|
|
84
95
|
* Global codegen config (for fallback outDir and clientManagerPath).
|
|
85
96
|
*/
|
|
86
|
-
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">;
|
|
97
|
+
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath" | "target">;
|
|
98
|
+
/**
|
|
99
|
+
* Whether the managed reactor files should be generated.
|
|
100
|
+
* Defaults to true.
|
|
101
|
+
*/
|
|
102
|
+
generateReactor?: boolean;
|
|
87
103
|
}
|
|
88
104
|
interface PipelineResult {
|
|
89
105
|
canisterName: string;
|
|
@@ -193,8 +209,8 @@ declare function declarationsExist(outDir: string, canisterName: string): boolea
|
|
|
193
209
|
/**
|
|
194
210
|
* Reactor File Generator
|
|
195
211
|
*
|
|
196
|
-
* Generates the managed `index.generated.ts` implementation for a canister
|
|
197
|
-
*
|
|
212
|
+
* Generates the managed `index.generated.ts` implementation for a canister.
|
|
213
|
+
* React targets also emit the full set of typed hooks via `createActorHooks`.
|
|
198
214
|
*
|
|
199
215
|
* Generated output example (for canister "backend"):
|
|
200
216
|
*
|
|
@@ -227,6 +243,8 @@ interface ReactorGeneratorOptions {
|
|
|
227
243
|
clientManagerPath?: string;
|
|
228
244
|
/** Optional fixed canister ID for the generated reactor */
|
|
229
245
|
canisterId?: string;
|
|
246
|
+
/** Generated runtime target */
|
|
247
|
+
runtimeTarget?: CodegenTarget;
|
|
230
248
|
/**
|
|
231
249
|
* Which reactor class should back the generated hooks.
|
|
232
250
|
* Default: "DisplayReactor" (backward compatible)
|
|
@@ -265,4 +283,4 @@ interface ClientGeneratorOptions {
|
|
|
265
283
|
*/
|
|
266
284
|
declare function generateClientFile(options?: ClientGeneratorOptions): string;
|
|
267
285
|
|
|
268
|
-
export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorClassName, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorEntryFile, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
|
|
286
|
+
export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type CodegenTarget, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorClassName, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorEntryFile, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
|
package/dist/index.js
CHANGED
|
@@ -78,11 +78,11 @@ function getServiceTypeName(canisterName) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// src/generators/reactor.ts
|
|
81
|
-
function getReactorClassImportSource(reactorClass) {
|
|
81
|
+
function getReactorClassImportSource(reactorClass, runtimeTarget) {
|
|
82
82
|
switch (reactorClass) {
|
|
83
83
|
case "Reactor":
|
|
84
84
|
case "DisplayReactor":
|
|
85
|
-
return "@ic-reactor/react";
|
|
85
|
+
return runtimeTarget === "core" ? "@ic-reactor/core" : "@ic-reactor/react";
|
|
86
86
|
case "CandidReactor":
|
|
87
87
|
case "CandidDisplayReactor":
|
|
88
88
|
case "MetadataDisplayReactor":
|
|
@@ -95,6 +95,7 @@ function generateReactorFile(options) {
|
|
|
95
95
|
didFile,
|
|
96
96
|
clientManagerPath = "../../clients",
|
|
97
97
|
canisterId,
|
|
98
|
+
runtimeTarget = "react",
|
|
98
99
|
reactorClass = "DisplayReactor"
|
|
99
100
|
} = options;
|
|
100
101
|
const pascalName = toPascalCase(canisterName);
|
|
@@ -102,11 +103,24 @@ function generateReactorFile(options) {
|
|
|
102
103
|
const serviceName = getServiceTypeName(canisterName);
|
|
103
104
|
const baseName = path2.basename(didFile, ".did");
|
|
104
105
|
const declarationsPath = `./declarations/${baseName}`;
|
|
105
|
-
const reactorImportSource = getReactorClassImportSource(
|
|
106
|
+
const reactorImportSource = getReactorClassImportSource(
|
|
107
|
+
reactorClass,
|
|
108
|
+
runtimeTarget
|
|
109
|
+
);
|
|
106
110
|
const canisterIdLine = canisterId ? ` canisterId: ${JSON.stringify(canisterId)},
|
|
107
111
|
` : "";
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
const hookExports = runtimeTarget === "react" ? `
|
|
113
|
+
|
|
114
|
+
export const {
|
|
115
|
+
useActorQuery: use${pascalName}Query,
|
|
116
|
+
useActorSuspenseQuery: use${pascalName}SuspenseQuery,
|
|
117
|
+
useActorInfiniteQuery: use${pascalName}InfiniteQuery,
|
|
118
|
+
useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
|
|
119
|
+
useActorMutation: use${pascalName}Mutation,
|
|
120
|
+
useActorMethod: use${pascalName}Method,
|
|
121
|
+
} = createActorHooks(${reactorName})
|
|
122
|
+
` : "";
|
|
123
|
+
return `${runtimeTarget === "react" ? 'import { createActorHooks } from "@ic-reactor/react"\n' : ""}import { ${reactorClass} } from "${reactorImportSource}"
|
|
110
124
|
import { clientManager } from "${clientManagerPath}"
|
|
111
125
|
import { idlFactory, type _SERVICE } from "${declarationsPath}"
|
|
112
126
|
|
|
@@ -122,24 +136,14 @@ export const ${reactorName} = new ${reactorClass}<${serviceName}>({
|
|
|
122
136
|
clientManager,
|
|
123
137
|
idlFactory,
|
|
124
138
|
${canisterIdLine} name: "${canisterName}",
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
export const {
|
|
128
|
-
useActorQuery: use${pascalName}Query,
|
|
129
|
-
useActorSuspenseQuery: use${pascalName}SuspenseQuery,
|
|
130
|
-
useActorInfiniteQuery: use${pascalName}InfiniteQuery,
|
|
131
|
-
useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
|
|
132
|
-
useActorMutation: use${pascalName}Mutation,
|
|
133
|
-
useActorMethod: use${pascalName}Method,
|
|
134
|
-
} = createActorHooks(${reactorName})
|
|
135
|
-
`;
|
|
139
|
+
})${hookExports || "\n"}`;
|
|
136
140
|
}
|
|
137
141
|
function generateReactorEntryFile() {
|
|
138
142
|
return `/**
|
|
139
143
|
* Canister entrypoint.
|
|
140
144
|
*
|
|
141
145
|
* Created once by @ic-reactor/codegen and safe to customize.
|
|
142
|
-
* Keep the re-export below if you want generated
|
|
146
|
+
* Keep the re-export below if you want generated exports and types to stay in sync.
|
|
143
147
|
*/
|
|
144
148
|
export * from "./index.generated"
|
|
145
149
|
`;
|
|
@@ -149,6 +153,9 @@ export * from "./index.generated"
|
|
|
149
153
|
function resolveReactorClass(canisterConfig) {
|
|
150
154
|
return canisterConfig.mode ?? "DisplayReactor";
|
|
151
155
|
}
|
|
156
|
+
function resolveRuntimeTarget(canisterConfig, globalConfig) {
|
|
157
|
+
return canisterConfig.target ?? globalConfig.target ?? "react";
|
|
158
|
+
}
|
|
152
159
|
function normalizeFileContent(content) {
|
|
153
160
|
return content.replace(/\r\n/g, "\n").trim();
|
|
154
161
|
}
|
|
@@ -159,7 +166,12 @@ function isManagedEntryWrapper(content, expectedEntryContent) {
|
|
|
159
166
|
return normalizeFileContent(content) === normalizeFileContent(expectedEntryContent);
|
|
160
167
|
}
|
|
161
168
|
async function runCanisterPipeline(options) {
|
|
162
|
-
const {
|
|
169
|
+
const {
|
|
170
|
+
canisterConfig,
|
|
171
|
+
projectRoot,
|
|
172
|
+
globalConfig,
|
|
173
|
+
generateReactor = true
|
|
174
|
+
} = options;
|
|
163
175
|
const { name, didFile, clientManagerPath } = canisterConfig;
|
|
164
176
|
const files = [];
|
|
165
177
|
const resolvedDidFile = path3.isAbsolute(didFile) ? didFile : path3.resolve(projectRoot, didFile);
|
|
@@ -196,15 +208,24 @@ async function runCanisterPipeline(options) {
|
|
|
196
208
|
error: `Declarations step failed: ${err instanceof Error ? err.message : String(err)}`
|
|
197
209
|
};
|
|
198
210
|
}
|
|
211
|
+
if (!generateReactor) {
|
|
212
|
+
return {
|
|
213
|
+
canisterName: name,
|
|
214
|
+
success: true,
|
|
215
|
+
files
|
|
216
|
+
};
|
|
217
|
+
}
|
|
199
218
|
const reactorPath = path3.join(canisterOutDir, "index.generated.ts");
|
|
200
219
|
const entryPath = path3.join(canisterOutDir, "index.ts");
|
|
201
220
|
const reactorClass = resolveReactorClass(canisterConfig);
|
|
221
|
+
const runtimeTarget = resolveRuntimeTarget(canisterConfig, globalConfig);
|
|
202
222
|
try {
|
|
203
223
|
const reactorContent = generateReactorFile({
|
|
204
224
|
canisterName: name,
|
|
205
225
|
didFile: resolvedDidFile,
|
|
206
226
|
clientManagerPath: resolvedClientManagerPath,
|
|
207
227
|
canisterId: canisterConfig.canisterId,
|
|
228
|
+
runtimeTarget,
|
|
208
229
|
reactorClass
|
|
209
230
|
});
|
|
210
231
|
const entryContent = generateReactorEntryFile();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ic-reactor/codegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Shared code generation utilities for IC Reactor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"@ic-reactor/parser": "0.4.6"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^25.
|
|
40
|
+
"@types/node": "^25.5.2",
|
|
41
41
|
"tsup": "^8.5.1",
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
|
-
"vitest": "^4.
|
|
43
|
+
"vitest": "^4.1.2"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.json",
|
|
@@ -92,3 +92,24 @@ export const {
|
|
|
92
92
|
} = createActorHooks(ledgerReactor)
|
|
93
93
|
"
|
|
94
94
|
`;
|
|
95
|
+
|
|
96
|
+
exports[`Reactor generator > supports core target generation without React hooks > core-display-reactor-index 1`] = `
|
|
97
|
+
"import { DisplayReactor } from "@ic-reactor/core"
|
|
98
|
+
import { clientManager } from "../../clients"
|
|
99
|
+
import { idlFactory, type _SERVICE } from "./declarations/backend"
|
|
100
|
+
|
|
101
|
+
export type BackendService = _SERVICE
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Backend Reactor
|
|
105
|
+
*
|
|
106
|
+
* Auto-generated by @ic-reactor/codegen — do not edit.
|
|
107
|
+
* This file is overwritten whenever generation runs.
|
|
108
|
+
*/
|
|
109
|
+
export const backendReactor = new DisplayReactor<BackendService>({
|
|
110
|
+
clientManager,
|
|
111
|
+
idlFactory,
|
|
112
|
+
name: "backend",
|
|
113
|
+
})
|
|
114
|
+
"
|
|
115
|
+
`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reactor File Generator
|
|
3
3
|
*
|
|
4
|
-
* Generates the managed `index.generated.ts` implementation for a canister
|
|
5
|
-
*
|
|
4
|
+
* Generates the managed `index.generated.ts` implementation for a canister.
|
|
5
|
+
* React targets also emit the full set of typed hooks via `createActorHooks`.
|
|
6
6
|
*
|
|
7
7
|
* Generated output example (for canister "backend"):
|
|
8
8
|
*
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import path from "node:path"
|
|
24
24
|
import { toPascalCase, getReactorName, getServiceTypeName } from "../naming.js"
|
|
25
|
-
import type { ReactorClassName } from "../types.js"
|
|
25
|
+
import type { CodegenTarget, ReactorClassName } from "../types.js"
|
|
26
26
|
|
|
27
27
|
export interface ReactorGeneratorOptions {
|
|
28
28
|
/** Canister name (e.g. "backend") */
|
|
@@ -39,6 +39,8 @@ export interface ReactorGeneratorOptions {
|
|
|
39
39
|
clientManagerPath?: string
|
|
40
40
|
/** Optional fixed canister ID for the generated reactor */
|
|
41
41
|
canisterId?: string
|
|
42
|
+
/** Generated runtime target */
|
|
43
|
+
runtimeTarget?: CodegenTarget
|
|
42
44
|
/**
|
|
43
45
|
* Which reactor class should back the generated hooks.
|
|
44
46
|
* Default: "DisplayReactor" (backward compatible)
|
|
@@ -47,12 +49,13 @@ export interface ReactorGeneratorOptions {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function getReactorClassImportSource(
|
|
50
|
-
reactorClass: ReactorClassName
|
|
51
|
-
|
|
52
|
+
reactorClass: ReactorClassName,
|
|
53
|
+
runtimeTarget: CodegenTarget
|
|
54
|
+
): "@ic-reactor/react" | "@ic-reactor/core" | "@ic-reactor/candid" {
|
|
52
55
|
switch (reactorClass) {
|
|
53
56
|
case "Reactor":
|
|
54
57
|
case "DisplayReactor":
|
|
55
|
-
return "@ic-reactor/react"
|
|
58
|
+
return runtimeTarget === "core" ? "@ic-reactor/core" : "@ic-reactor/react"
|
|
56
59
|
case "CandidReactor":
|
|
57
60
|
case "CandidDisplayReactor":
|
|
58
61
|
case "MetadataDisplayReactor":
|
|
@@ -69,6 +72,7 @@ export function generateReactorFile(options: ReactorGeneratorOptions): string {
|
|
|
69
72
|
didFile,
|
|
70
73
|
clientManagerPath = "../../clients",
|
|
71
74
|
canisterId,
|
|
75
|
+
runtimeTarget = "react",
|
|
72
76
|
reactorClass = "DisplayReactor",
|
|
73
77
|
} = options
|
|
74
78
|
|
|
@@ -79,13 +83,29 @@ export function generateReactorFile(options: ReactorGeneratorOptions): string {
|
|
|
79
83
|
// Derive the declarations import path from the .did filename
|
|
80
84
|
const baseName = path.basename(didFile, ".did")
|
|
81
85
|
const declarationsPath = `./declarations/${baseName}`
|
|
82
|
-
const reactorImportSource = getReactorClassImportSource(
|
|
86
|
+
const reactorImportSource = getReactorClassImportSource(
|
|
87
|
+
reactorClass,
|
|
88
|
+
runtimeTarget
|
|
89
|
+
)
|
|
83
90
|
const canisterIdLine = canisterId
|
|
84
91
|
? ` canisterId: ${JSON.stringify(canisterId)},\n`
|
|
85
92
|
: ""
|
|
93
|
+
const hookExports =
|
|
94
|
+
runtimeTarget === "react"
|
|
95
|
+
? `
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
export const {
|
|
98
|
+
useActorQuery: use${pascalName}Query,
|
|
99
|
+
useActorSuspenseQuery: use${pascalName}SuspenseQuery,
|
|
100
|
+
useActorInfiniteQuery: use${pascalName}InfiniteQuery,
|
|
101
|
+
useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
|
|
102
|
+
useActorMutation: use${pascalName}Mutation,
|
|
103
|
+
useActorMethod: use${pascalName}Method,
|
|
104
|
+
} = createActorHooks(${reactorName})
|
|
105
|
+
`
|
|
106
|
+
: ""
|
|
107
|
+
|
|
108
|
+
return `${runtimeTarget === "react" ? 'import { createActorHooks } from "@ic-reactor/react"\n' : ""}import { ${reactorClass} } from "${reactorImportSource}"
|
|
89
109
|
import { clientManager } from "${clientManagerPath}"
|
|
90
110
|
import { idlFactory, type _SERVICE } from "${declarationsPath}"
|
|
91
111
|
|
|
@@ -101,17 +121,7 @@ export const ${reactorName} = new ${reactorClass}<${serviceName}>({
|
|
|
101
121
|
clientManager,
|
|
102
122
|
idlFactory,
|
|
103
123
|
${canisterIdLine} name: "${canisterName}",
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
export const {
|
|
107
|
-
useActorQuery: use${pascalName}Query,
|
|
108
|
-
useActorSuspenseQuery: use${pascalName}SuspenseQuery,
|
|
109
|
-
useActorInfiniteQuery: use${pascalName}InfiniteQuery,
|
|
110
|
-
useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
|
|
111
|
-
useActorMutation: use${pascalName}Mutation,
|
|
112
|
-
useActorMethod: use${pascalName}Method,
|
|
113
|
-
} = createActorHooks(${reactorName})
|
|
114
|
-
`
|
|
124
|
+
})${hookExports || "\n"}`
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
/**
|
|
@@ -122,7 +132,7 @@ export function generateReactorEntryFile(): string {
|
|
|
122
132
|
* Canister entrypoint.
|
|
123
133
|
*
|
|
124
134
|
* Created once by @ic-reactor/codegen and safe to customize.
|
|
125
|
-
* Keep the re-export below if you want generated
|
|
135
|
+
* Keep the re-export below if you want generated exports and types to stay in sync.
|
|
126
136
|
*/
|
|
127
137
|
export * from "./index.generated"
|
|
128
138
|
`
|
package/src/index.ts
CHANGED
package/src/pipeline.test.ts
CHANGED
|
@@ -95,6 +95,136 @@ describe("Codegen pipeline", () => {
|
|
|
95
95
|
expect(generated).toContain('name: "workflow"')
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
+
it("can generate declarations without creating reactor files", async () => {
|
|
99
|
+
const projectRoot = createTempProject()
|
|
100
|
+
writeDid(projectRoot, "backend.did")
|
|
101
|
+
|
|
102
|
+
const result = await runCanisterPipeline({
|
|
103
|
+
canisterConfig: {
|
|
104
|
+
name: "backend",
|
|
105
|
+
didFile: "backend.did",
|
|
106
|
+
},
|
|
107
|
+
projectRoot,
|
|
108
|
+
globalConfig: {
|
|
109
|
+
outDir: "src/declarations",
|
|
110
|
+
clientManagerPath: "../../clients",
|
|
111
|
+
},
|
|
112
|
+
generateReactor: false,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
expect(result.success).toBe(true)
|
|
116
|
+
expect(
|
|
117
|
+
fs.existsSync(
|
|
118
|
+
path.join(
|
|
119
|
+
projectRoot,
|
|
120
|
+
"src/declarations/backend/declarations/backend.js"
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
).toBe(true)
|
|
124
|
+
expect(
|
|
125
|
+
fs.existsSync(
|
|
126
|
+
path.join(
|
|
127
|
+
projectRoot,
|
|
128
|
+
"src/declarations/backend/declarations/backend.d.ts"
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
).toBe(true)
|
|
132
|
+
expect(
|
|
133
|
+
fs.existsSync(
|
|
134
|
+
path.join(
|
|
135
|
+
projectRoot,
|
|
136
|
+
"src/declarations/backend/declarations/backend.did"
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
).toBe(true)
|
|
140
|
+
expect(
|
|
141
|
+
fs.existsSync(
|
|
142
|
+
path.join(projectRoot, "src/declarations/backend/index.generated.ts")
|
|
143
|
+
)
|
|
144
|
+
).toBe(false)
|
|
145
|
+
expect(
|
|
146
|
+
fs.existsSync(path.join(projectRoot, "src/declarations/backend/index.ts"))
|
|
147
|
+
).toBe(false)
|
|
148
|
+
expect(
|
|
149
|
+
result.files.some((file) => file.filePath.endsWith("index.generated.ts"))
|
|
150
|
+
).toBe(false)
|
|
151
|
+
expect(
|
|
152
|
+
result.files.some((file) => file.filePath.endsWith("index.ts"))
|
|
153
|
+
).toBe(false)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it("leaves existing reactor files untouched when reactor generation is disabled", async () => {
|
|
157
|
+
const projectRoot = createTempProject()
|
|
158
|
+
writeDid(projectRoot, "backend.did")
|
|
159
|
+
|
|
160
|
+
const canisterOutDir = path.join(projectRoot, "src/declarations/backend")
|
|
161
|
+
fs.mkdirSync(canisterOutDir, { recursive: true })
|
|
162
|
+
|
|
163
|
+
const existingGenerated = "// existing generated reactor"
|
|
164
|
+
const existingEntry = "// existing entry wrapper"
|
|
165
|
+
|
|
166
|
+
fs.writeFileSync(
|
|
167
|
+
path.join(canisterOutDir, "index.generated.ts"),
|
|
168
|
+
existingGenerated
|
|
169
|
+
)
|
|
170
|
+
fs.writeFileSync(path.join(canisterOutDir, "index.ts"), existingEntry)
|
|
171
|
+
|
|
172
|
+
const result = await runCanisterPipeline({
|
|
173
|
+
canisterConfig: {
|
|
174
|
+
name: "backend",
|
|
175
|
+
didFile: "backend.did",
|
|
176
|
+
},
|
|
177
|
+
projectRoot,
|
|
178
|
+
globalConfig: {
|
|
179
|
+
outDir: "src/declarations",
|
|
180
|
+
clientManagerPath: "../../clients",
|
|
181
|
+
},
|
|
182
|
+
generateReactor: false,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
expect(result.success).toBe(true)
|
|
186
|
+
expect(
|
|
187
|
+
fs.readFileSync(path.join(canisterOutDir, "index.generated.ts"), "utf-8")
|
|
188
|
+
).toBe(existingGenerated)
|
|
189
|
+
expect(
|
|
190
|
+
fs.readFileSync(path.join(canisterOutDir, "index.ts"), "utf-8")
|
|
191
|
+
).toBe(existingEntry)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it("supports a core target without generating React hooks", async () => {
|
|
195
|
+
const projectRoot = createTempProject()
|
|
196
|
+
writeDid(projectRoot, "backend.did")
|
|
197
|
+
|
|
198
|
+
const result = await runCanisterPipeline({
|
|
199
|
+
canisterConfig: {
|
|
200
|
+
name: "backend",
|
|
201
|
+
didFile: "backend.did",
|
|
202
|
+
},
|
|
203
|
+
projectRoot,
|
|
204
|
+
globalConfig: {
|
|
205
|
+
outDir: "src/declarations",
|
|
206
|
+
clientManagerPath: "../../clients",
|
|
207
|
+
target: "core",
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
expect(result.success).toBe(true)
|
|
212
|
+
|
|
213
|
+
const indexPath = path.join(
|
|
214
|
+
projectRoot,
|
|
215
|
+
"src/declarations/backend/index.generated.ts"
|
|
216
|
+
)
|
|
217
|
+
const generated = fs.readFileSync(indexPath, "utf-8")
|
|
218
|
+
|
|
219
|
+
expect(generated).toContain(
|
|
220
|
+
'import { DisplayReactor } from "@ic-reactor/core"'
|
|
221
|
+
)
|
|
222
|
+
expect(generated).not.toContain(
|
|
223
|
+
'import { createActorHooks } from "@ic-reactor/react"'
|
|
224
|
+
)
|
|
225
|
+
expect(generated).not.toContain("useBackendQuery")
|
|
226
|
+
})
|
|
227
|
+
|
|
98
228
|
it("does not overwrite user-modified index.ts on regenerate", async () => {
|
|
99
229
|
const projectRoot = createTempProject()
|
|
100
230
|
writeDid(projectRoot, "backend.did")
|
package/src/pipeline.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* Pipeline steps (in order):
|
|
8
8
|
* 1. Resolve paths (didFile, outDir)
|
|
9
9
|
* 2. Generate declarations (JS + .d.ts + .did copy)
|
|
10
|
-
* 3.
|
|
11
|
-
* 4.
|
|
10
|
+
* 3. Optionally generate reactor implementation (`index.generated.ts`)
|
|
11
|
+
* 4. Optionally create or migrate the user entry (`index.ts`)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import fs from "node:fs"
|
|
@@ -16,6 +16,7 @@ import path from "node:path"
|
|
|
16
16
|
import type {
|
|
17
17
|
CanisterConfig,
|
|
18
18
|
CodegenConfig,
|
|
19
|
+
CodegenTarget,
|
|
19
20
|
GeneratorResult,
|
|
20
21
|
ReactorClassName,
|
|
21
22
|
} from "./types.js"
|
|
@@ -36,13 +37,25 @@ export interface PipelineOptions {
|
|
|
36
37
|
/**
|
|
37
38
|
* Global codegen config (for fallback outDir and clientManagerPath).
|
|
38
39
|
*/
|
|
39
|
-
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">
|
|
40
|
+
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath" | "target">
|
|
41
|
+
/**
|
|
42
|
+
* Whether the managed reactor files should be generated.
|
|
43
|
+
* Defaults to true.
|
|
44
|
+
*/
|
|
45
|
+
generateReactor?: boolean
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
function resolveReactorClass(canisterConfig: CanisterConfig): ReactorClassName {
|
|
43
49
|
return canisterConfig.mode ?? "DisplayReactor"
|
|
44
50
|
}
|
|
45
51
|
|
|
52
|
+
function resolveRuntimeTarget(
|
|
53
|
+
canisterConfig: CanisterConfig,
|
|
54
|
+
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath" | "target">
|
|
55
|
+
): CodegenTarget {
|
|
56
|
+
return canisterConfig.target ?? globalConfig.target ?? "react"
|
|
57
|
+
}
|
|
58
|
+
|
|
46
59
|
function normalizeFileContent(content: string): string {
|
|
47
60
|
return content.replace(/\r\n/g, "\n").trim()
|
|
48
61
|
}
|
|
@@ -79,7 +92,12 @@ export interface PipelineResult {
|
|
|
79
92
|
export async function runCanisterPipeline(
|
|
80
93
|
options: PipelineOptions
|
|
81
94
|
): Promise<PipelineResult> {
|
|
82
|
-
const {
|
|
95
|
+
const {
|
|
96
|
+
canisterConfig,
|
|
97
|
+
projectRoot,
|
|
98
|
+
globalConfig,
|
|
99
|
+
generateReactor = true,
|
|
100
|
+
} = options
|
|
83
101
|
const { name, didFile, clientManagerPath } = canisterConfig
|
|
84
102
|
|
|
85
103
|
const files: GeneratorResult[] = []
|
|
@@ -139,11 +157,20 @@ export async function runCanisterPipeline(
|
|
|
139
157
|
}
|
|
140
158
|
}
|
|
141
159
|
|
|
160
|
+
if (!generateReactor) {
|
|
161
|
+
return {
|
|
162
|
+
canisterName: name,
|
|
163
|
+
success: true,
|
|
164
|
+
files,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
// ── Step 2: Reactor file ───────────────────────────────────────────────────
|
|
143
169
|
|
|
144
170
|
const reactorPath = path.join(canisterOutDir, "index.generated.ts")
|
|
145
171
|
const entryPath = path.join(canisterOutDir, "index.ts")
|
|
146
172
|
const reactorClass = resolveReactorClass(canisterConfig)
|
|
173
|
+
const runtimeTarget = resolveRuntimeTarget(canisterConfig, globalConfig)
|
|
147
174
|
|
|
148
175
|
try {
|
|
149
176
|
const reactorContent = generateReactorFile({
|
|
@@ -151,6 +178,7 @@ export async function runCanisterPipeline(
|
|
|
151
178
|
didFile: resolvedDidFile,
|
|
152
179
|
clientManagerPath: resolvedClientManagerPath,
|
|
153
180
|
canisterId: canisterConfig.canisterId,
|
|
181
|
+
runtimeTarget,
|
|
154
182
|
reactorClass,
|
|
155
183
|
})
|
|
156
184
|
const entryContent = generateReactorEntryFile()
|
package/src/reactor.test.ts
CHANGED
|
@@ -43,6 +43,24 @@ describe("Reactor generator", () => {
|
|
|
43
43
|
expect(content).toContain("new MetadataDisplayReactor<LedgerService>")
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
+
it("supports core target generation without React hooks", () => {
|
|
47
|
+
const content = generateReactorFile({
|
|
48
|
+
canisterName: "backend",
|
|
49
|
+
didFile: "mock/backend.did",
|
|
50
|
+
runtimeTarget: "core",
|
|
51
|
+
reactorClass: "DisplayReactor",
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(content).toMatchSnapshot("core-display-reactor-index")
|
|
55
|
+
expect(content).toContain(
|
|
56
|
+
'import { DisplayReactor } from "@ic-reactor/core"'
|
|
57
|
+
)
|
|
58
|
+
expect(content).not.toContain(
|
|
59
|
+
'import { createActorHooks } from "@ic-reactor/react"'
|
|
60
|
+
)
|
|
61
|
+
expect(content).not.toContain("useBackendQuery")
|
|
62
|
+
})
|
|
63
|
+
|
|
46
64
|
it("writes a fixed canisterId when configured", () => {
|
|
47
65
|
const content = generateReactorFile({
|
|
48
66
|
canisterName: "workflow",
|
package/src/types.ts
CHANGED
|
@@ -29,6 +29,11 @@ export interface CanisterConfig {
|
|
|
29
29
|
* Defaults to DisplayReactor for backward compatibility.
|
|
30
30
|
*/
|
|
31
31
|
mode?: ReactorClassName
|
|
32
|
+
/**
|
|
33
|
+
* Generated runtime target.
|
|
34
|
+
* `react` emits bound React hooks, `core` emits only the typed reactor exports.
|
|
35
|
+
*/
|
|
36
|
+
target?: CodegenTarget
|
|
32
37
|
/** Optional fixed canister ID */
|
|
33
38
|
canisterId?: string
|
|
34
39
|
}
|
|
@@ -40,6 +45,8 @@ export type ReactorClassName =
|
|
|
40
45
|
| "CandidDisplayReactor"
|
|
41
46
|
| "MetadataDisplayReactor"
|
|
42
47
|
|
|
48
|
+
export type CodegenTarget = "react" | "core"
|
|
49
|
+
|
|
43
50
|
/**
|
|
44
51
|
* Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
|
|
45
52
|
*/
|
|
@@ -56,6 +63,11 @@ export interface CodegenConfig {
|
|
|
56
63
|
* Individual canisters can override via `CanisterConfig.clientManagerPath`.
|
|
57
64
|
*/
|
|
58
65
|
clientManagerPath?: string
|
|
66
|
+
/**
|
|
67
|
+
* Default generated runtime target.
|
|
68
|
+
* Individual canisters can override via `CanisterConfig.target`.
|
|
69
|
+
*/
|
|
70
|
+
target?: CodegenTarget
|
|
59
71
|
/** Canister configurations, keyed by canister name */
|
|
60
72
|
canisters: Record<string, CanisterConfig>
|
|
61
73
|
}
|