@ic-reactor/codegen 0.4.1 → 0.5.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 +45 -0
- package/dist/index.cjs +168 -65
- package/dist/index.d.cts +193 -27
- package/dist/index.d.ts +193 -27
- package/dist/index.js +165 -65
- package/package.json +1 -1
- package/src/bindgen.test.ts +1 -1
- package/src/generators/client.ts +49 -0
- package/src/generators/declarations.ts +112 -0
- package/src/generators/index.ts +18 -0
- package/src/generators/reactor.ts +82 -0
- package/src/index.ts +13 -11
- package/src/naming.test.ts +6 -20
- package/src/naming.ts +21 -43
- package/src/parser.ts +79 -0
- package/src/pipeline.ts +144 -0
- package/src/types.ts +65 -0
- package/src/bindgen.ts +0 -101
- package/src/templates/__snapshots__/client.test.ts.snap +0 -35
- package/src/templates/__snapshots__/reactor.test.ts.snap +0 -28
- package/src/templates/client.test.ts +0 -20
- package/src/templates/client.ts +0 -37
- package/src/templates/reactor.test.ts +0 -23
- package/src/templates/reactor.ts +0 -99
package/dist/index.d.ts
CHANGED
|
@@ -1,83 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ic-reactor/codegen — Core Types
|
|
3
|
+
*
|
|
4
|
+
* All shared types used across generators, pipeline, CLI, and vite-plugin.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Per-canister configuration.
|
|
8
|
+
* `name` is always required — it drives all naming in generators.
|
|
9
|
+
*/
|
|
10
|
+
interface CanisterConfig {
|
|
11
|
+
/** Canister name (used for variable names, file names, etc.) */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Path to the .did file (relative to project root, or absolute) */
|
|
14
|
+
didFile: string;
|
|
15
|
+
/** Override output dir for this specific canister */
|
|
16
|
+
outDir?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Import path to the ClientManager (relative from the generated reactor file).
|
|
19
|
+
* Example: "../../clients" → `import { clientManager } from "../../clients"`
|
|
20
|
+
*/
|
|
21
|
+
clientManagerPath?: string;
|
|
22
|
+
/** Optional fixed canister ID */
|
|
23
|
+
canisterId?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
|
|
27
|
+
*/
|
|
28
|
+
interface CodegenConfig {
|
|
29
|
+
/** JSON Schema reference */
|
|
30
|
+
$schema?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Default output directory for all canisters (relative to project root).
|
|
33
|
+
* Individual canisters can override via `CanisterConfig.outDir`.
|
|
34
|
+
*/
|
|
35
|
+
outDir: string;
|
|
36
|
+
/**
|
|
37
|
+
* Default import path for the ClientManager (relative from generated files).
|
|
38
|
+
* Individual canisters can override via `CanisterConfig.clientManagerPath`.
|
|
39
|
+
*/
|
|
40
|
+
clientManagerPath?: string;
|
|
41
|
+
/** Canister configurations, keyed by canister name */
|
|
42
|
+
canisters: Record<string, CanisterConfig>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Result of a single file-writing generator step.
|
|
46
|
+
*/
|
|
47
|
+
interface GeneratorResult {
|
|
48
|
+
success: boolean;
|
|
49
|
+
/** Absolute path of the file that was (or would have been) written */
|
|
50
|
+
filePath: string;
|
|
51
|
+
/** If true, skipped because the file already existed */
|
|
52
|
+
skipped?: boolean;
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Codegen Pipeline
|
|
58
|
+
*
|
|
59
|
+
* Orchestrates all generators for a single canister.
|
|
60
|
+
* This is the primary entry point used by @ic-reactor/cli and @ic-reactor/vite-plugin.
|
|
61
|
+
*
|
|
62
|
+
* Pipeline steps (in order):
|
|
63
|
+
* 1. Resolve paths (didFile, outDir)
|
|
64
|
+
* 2. Generate declarations (JS + .d.ts + .did copy)
|
|
65
|
+
* 3. Generate reactor file (index.ts)
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
interface PipelineOptions {
|
|
69
|
+
/** Canister name and config */
|
|
70
|
+
canisterConfig: CanisterConfig;
|
|
71
|
+
/**
|
|
72
|
+
* Absolute path to the project root.
|
|
73
|
+
* Relative paths in `CanisterConfig` are resolved from here.
|
|
74
|
+
*/
|
|
75
|
+
projectRoot: string;
|
|
76
|
+
/**
|
|
77
|
+
* Global codegen config (for fallback outDir and clientManagerPath).
|
|
78
|
+
*/
|
|
79
|
+
globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">;
|
|
80
|
+
}
|
|
81
|
+
interface PipelineResult {
|
|
82
|
+
canisterName: string;
|
|
83
|
+
success: boolean;
|
|
84
|
+
/** All files the pipeline attempted to write */
|
|
85
|
+
files: GeneratorResult[];
|
|
86
|
+
error?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run the full codegen pipeline for a single canister.
|
|
90
|
+
*
|
|
91
|
+
* @returns PipelineResult with per-file details
|
|
92
|
+
*/
|
|
93
|
+
declare function runCanisterPipeline(options: PipelineOptions): Promise<PipelineResult>;
|
|
94
|
+
|
|
1
95
|
/**
|
|
2
96
|
* Naming utilities for code generation
|
|
3
97
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
98
|
+
* Pure functions that convert canister/method names to correctly cased
|
|
99
|
+
* identifiers used throughout generated code.
|
|
6
100
|
*/
|
|
7
101
|
/**
|
|
8
|
-
* Convert string to PascalCase
|
|
102
|
+
* Convert string to PascalCase.
|
|
9
103
|
* @example toPascalCase("get_message") → "GetMessage"
|
|
10
104
|
* @example toPascalCase("my-canister") → "MyCanister"
|
|
11
105
|
*/
|
|
12
106
|
declare function toPascalCase(str: string): string;
|
|
13
107
|
/**
|
|
14
|
-
* Generate reactor variable name
|
|
108
|
+
* Generate the reactor variable name for a canister.
|
|
15
109
|
* @example getReactorName("backend") → "backendReactor"
|
|
110
|
+
* @example getReactorName("my_canister") → "myCanisterReactor"
|
|
16
111
|
*/
|
|
17
112
|
declare function getReactorName(canisterName: string): string;
|
|
18
113
|
/**
|
|
19
|
-
* Generate service type name
|
|
114
|
+
* Generate the service type name for a canister.
|
|
20
115
|
* @example getServiceTypeName("backend") → "BackendService"
|
|
116
|
+
* @example getServiceTypeName("my_canister") → "MyCanisterService"
|
|
21
117
|
*/
|
|
22
118
|
declare function getServiceTypeName(canisterName: string): string;
|
|
23
119
|
|
|
24
120
|
/**
|
|
25
|
-
*
|
|
121
|
+
* Candid Parser Utilities
|
|
122
|
+
*
|
|
123
|
+
* Parses Candid .did files to extract service method signatures.
|
|
124
|
+
* Used by the CLI for listing methods and by advanced generators.
|
|
125
|
+
*/
|
|
126
|
+
type MethodType = "query" | "mutation";
|
|
127
|
+
interface MethodInfo {
|
|
128
|
+
/** Method name as it appears in the Candid service */
|
|
129
|
+
name: string;
|
|
130
|
+
/** "query" for read-only calls, "mutation" for update calls */
|
|
131
|
+
type: MethodType;
|
|
132
|
+
/** True if the method takes at least one argument */
|
|
133
|
+
hasArgs: boolean;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Extract method information from raw Candid source text.
|
|
26
137
|
*
|
|
27
|
-
*
|
|
138
|
+
* Compiles the Candid to JS and inspects the IDL.Service definition.
|
|
28
139
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
140
|
+
declare function extractMethods(didContent: string): MethodInfo[];
|
|
141
|
+
/**
|
|
142
|
+
* Parse a .did file from disk and return its methods.
|
|
143
|
+
*
|
|
144
|
+
* @throws if the file does not exist or fails to parse
|
|
145
|
+
*/
|
|
146
|
+
declare function parseDIDFile(didFilePath: string): MethodInfo[];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Declarations Generator
|
|
150
|
+
*
|
|
151
|
+
* Generates TypeScript declaration files (.js IDL factory + .d.ts types)
|
|
152
|
+
* from a Candid .did file using @ic-reactor/parser.
|
|
153
|
+
*
|
|
154
|
+
* Output structure:
|
|
155
|
+
* <outDir>/declarations/<name>.js — IDL factory
|
|
156
|
+
* <outDir>/declarations/<name>.d.ts — TypeScript types
|
|
157
|
+
* <outDir>/declarations/<name>.did — Copy of the source .did file
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
interface DeclarationsGeneratorOptions {
|
|
161
|
+
/** Absolute path to the .did file */
|
|
31
162
|
didFile: string;
|
|
32
|
-
/**
|
|
163
|
+
/** Absolute path to the output directory (declarations/ will be created inside) */
|
|
33
164
|
outDir: string;
|
|
34
|
-
/** Canister name (used for
|
|
165
|
+
/** Canister name (used only for error messages) */
|
|
35
166
|
canisterName: string;
|
|
36
167
|
}
|
|
37
|
-
interface
|
|
168
|
+
interface DeclarationsGeneratorResult {
|
|
38
169
|
success: boolean;
|
|
39
170
|
declarationsDir: string;
|
|
171
|
+
files: GeneratorResult[];
|
|
40
172
|
error?: string;
|
|
41
173
|
}
|
|
42
174
|
/**
|
|
43
|
-
* Generate TypeScript declarations from a Candid file
|
|
175
|
+
* Generate TypeScript declarations from a Candid file.
|
|
44
176
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* - declarations/<canisterName>.d.ts - Types
|
|
177
|
+
* Always cleans and regenerates the declarations directory to ensure
|
|
178
|
+
* it's in sync with the source .did file.
|
|
48
179
|
*/
|
|
49
|
-
declare function generateDeclarations(options:
|
|
180
|
+
declare function generateDeclarations(options: DeclarationsGeneratorOptions): Promise<DeclarationsGeneratorResult>;
|
|
50
181
|
/**
|
|
51
|
-
* Check if declarations already exist for a canister
|
|
182
|
+
* Check if declarations already exist for a canister.
|
|
52
183
|
*/
|
|
53
184
|
declare function declarationsExist(outDir: string, canisterName: string): boolean;
|
|
54
185
|
|
|
55
186
|
/**
|
|
56
|
-
* Reactor
|
|
187
|
+
* Reactor File Generator
|
|
188
|
+
*
|
|
189
|
+
* Generates the main `index.ts` for a canister — a DisplayReactor instance
|
|
190
|
+
* plus the full set of typed hooks via `createActorHooks`.
|
|
191
|
+
*
|
|
192
|
+
* Generated output example (for canister "backend"):
|
|
57
193
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
194
|
+
* import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
|
|
195
|
+
* import { clientManager } from "../../clients"
|
|
196
|
+
* import { idlFactory, type _SERVICE } from "./declarations/backend"
|
|
197
|
+
*
|
|
198
|
+
* export type BackendService = _SERVICE
|
|
199
|
+
*
|
|
200
|
+
* export const backendReactor = new DisplayReactor<BackendService>({ ... })
|
|
201
|
+
*
|
|
202
|
+
* export const {
|
|
203
|
+
* useActorQuery: useBackendQuery,
|
|
204
|
+
* ...
|
|
205
|
+
* } = createActorHooks(backendReactor)
|
|
60
206
|
*/
|
|
61
|
-
|
|
207
|
+
interface ReactorGeneratorOptions {
|
|
208
|
+
/** Canister name (e.g. "backend") */
|
|
62
209
|
canisterName: string;
|
|
210
|
+
/**
|
|
211
|
+
* Path to the .did file. Used to derive the declarations import path.
|
|
212
|
+
* Can be relative or absolute — only the basename is used.
|
|
213
|
+
*/
|
|
63
214
|
didFile: string;
|
|
215
|
+
/**
|
|
216
|
+
* Import path for the client manager, relative from the generated reactor file.
|
|
217
|
+
* Default: "../../clients"
|
|
218
|
+
*/
|
|
64
219
|
clientManagerPath?: string;
|
|
65
|
-
}
|
|
220
|
+
}
|
|
66
221
|
/**
|
|
67
|
-
* Generate the reactor file
|
|
222
|
+
* Generate the content of a canister's `index.ts` reactor file.
|
|
68
223
|
*/
|
|
69
224
|
declare function generateReactorFile(options: ReactorGeneratorOptions): string;
|
|
70
225
|
|
|
71
226
|
/**
|
|
72
227
|
* Client Manager Generator
|
|
228
|
+
*
|
|
229
|
+
* Generates a central `clients.ts` file that creates a `ClientManager` instance.
|
|
230
|
+
* This file is shared across all canisters in a project.
|
|
231
|
+
*
|
|
232
|
+
* Users typically customize this file once to configure:
|
|
233
|
+
* - Custom host (local vs. mainnet)
|
|
234
|
+
* - Identity provider
|
|
235
|
+
* - Query client options
|
|
73
236
|
*/
|
|
74
237
|
interface ClientGeneratorOptions {
|
|
75
|
-
/**
|
|
238
|
+
/**
|
|
239
|
+
* Import path to an existing `queryClient` instance.
|
|
240
|
+
* If omitted, a default `QueryClient` will be created.
|
|
241
|
+
*/
|
|
76
242
|
queryClientPath?: string;
|
|
77
243
|
}
|
|
78
244
|
/**
|
|
79
|
-
* Generate a central client manager file
|
|
245
|
+
* Generate the content for a central client manager file (`clients.ts`).
|
|
80
246
|
*/
|
|
81
247
|
declare function generateClientFile(options?: ClientGeneratorOptions): string;
|
|
82
248
|
|
|
83
|
-
export { type
|
|
249
|
+
export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
|
package/dist/index.js
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
return pascalCase(str);
|
|
5
|
-
}
|
|
6
|
-
function toCamelCase(str) {
|
|
7
|
-
return camelCase(str);
|
|
8
|
-
}
|
|
9
|
-
function getReactorName(canisterName) {
|
|
10
|
-
return `${toCamelCase(canisterName)}Reactor`;
|
|
11
|
-
}
|
|
12
|
-
function getServiceTypeName(canisterName) {
|
|
13
|
-
return `${toPascalCase(canisterName)}Service`;
|
|
14
|
-
}
|
|
1
|
+
// src/pipeline.ts
|
|
2
|
+
import fs2 from "fs";
|
|
3
|
+
import path3 from "path";
|
|
15
4
|
|
|
16
|
-
// src/
|
|
5
|
+
// src/generators/declarations.ts
|
|
17
6
|
import { didToJs, didToTs } from "@ic-reactor/parser";
|
|
18
7
|
import path from "path";
|
|
19
8
|
import fs from "fs";
|
|
20
9
|
async function generateDeclarations(options) {
|
|
21
|
-
const { didFile, outDir } = options;
|
|
10
|
+
const { didFile, outDir, canisterName } = options;
|
|
22
11
|
if (!fs.existsSync(didFile)) {
|
|
23
12
|
return {
|
|
24
13
|
success: false,
|
|
25
14
|
declarationsDir: "",
|
|
15
|
+
files: [],
|
|
26
16
|
error: `DID file not found: ${didFile}`
|
|
27
17
|
};
|
|
28
18
|
}
|
|
29
19
|
const declarationsDir = path.join(outDir, "declarations");
|
|
30
|
-
const
|
|
20
|
+
const baseName = path.basename(didFile, ".did");
|
|
31
21
|
try {
|
|
32
22
|
const didContent = fs.readFileSync(didFile, "utf-8");
|
|
33
23
|
if (!fs.existsSync(outDir)) {
|
|
@@ -39,73 +29,75 @@ async function generateDeclarations(options) {
|
|
|
39
29
|
fs.mkdirSync(declarationsDir, { recursive: true });
|
|
40
30
|
const jsContent = didToJs(didContent);
|
|
41
31
|
const tsContent = didToTs(didContent);
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const didPath = path.join(declarationsDir, didFileName);
|
|
32
|
+
const jsPath = path.join(declarationsDir, `${baseName}.js`);
|
|
33
|
+
const dtsPath = path.join(declarationsDir, `${baseName}.d.ts`);
|
|
34
|
+
const didCopyPath = path.join(declarationsDir, `${baseName}.did`);
|
|
46
35
|
fs.writeFileSync(jsPath, jsContent);
|
|
47
36
|
fs.writeFileSync(dtsPath, tsContent);
|
|
48
|
-
fs.writeFileSync(
|
|
37
|
+
fs.writeFileSync(didCopyPath, didContent);
|
|
49
38
|
return {
|
|
50
39
|
success: true,
|
|
51
|
-
declarationsDir
|
|
40
|
+
declarationsDir,
|
|
41
|
+
files: [
|
|
42
|
+
{ success: true, filePath: jsPath },
|
|
43
|
+
{ success: true, filePath: dtsPath },
|
|
44
|
+
{ success: true, filePath: didCopyPath }
|
|
45
|
+
]
|
|
52
46
|
};
|
|
53
47
|
} catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
54
49
|
return {
|
|
55
50
|
success: false,
|
|
56
51
|
declarationsDir,
|
|
57
|
-
|
|
52
|
+
files: [],
|
|
53
|
+
error: `[${canisterName}] Failed to generate declarations: ${message}`
|
|
58
54
|
};
|
|
59
55
|
}
|
|
60
56
|
}
|
|
61
57
|
function declarationsExist(outDir, canisterName) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
return fs.existsSync(didTsPath);
|
|
58
|
+
const dtsPath = path.join(outDir, "declarations", `${canisterName}.d.ts`);
|
|
59
|
+
return fs.existsSync(dtsPath);
|
|
65
60
|
}
|
|
66
61
|
|
|
67
|
-
// src/
|
|
62
|
+
// src/generators/reactor.ts
|
|
68
63
|
import path2 from "path";
|
|
64
|
+
|
|
65
|
+
// src/naming.ts
|
|
66
|
+
import { camelCase, pascalCase } from "change-case";
|
|
67
|
+
function toPascalCase(str) {
|
|
68
|
+
return pascalCase(str);
|
|
69
|
+
}
|
|
70
|
+
function toCamelCase(str) {
|
|
71
|
+
return camelCase(str);
|
|
72
|
+
}
|
|
73
|
+
function getReactorName(canisterName) {
|
|
74
|
+
return `${toCamelCase(canisterName)}Reactor`;
|
|
75
|
+
}
|
|
76
|
+
function getServiceTypeName(canisterName) {
|
|
77
|
+
return `${toPascalCase(canisterName)}Service`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/generators/reactor.ts
|
|
69
81
|
function generateReactorFile(options) {
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const baseName = didFileName.replace(/\.did$/, "");
|
|
82
|
+
const { canisterName, didFile, clientManagerPath = "../../clients" } = options;
|
|
83
|
+
const pascalName = toPascalCase(canisterName);
|
|
84
|
+
const reactorName = getReactorName(canisterName);
|
|
85
|
+
const serviceName = getServiceTypeName(canisterName);
|
|
86
|
+
const baseName = path2.basename(didFile, ".did");
|
|
76
87
|
const declarationsPath = `./declarations/${baseName}`;
|
|
77
|
-
|
|
78
|
-
const vars = {
|
|
79
|
-
canisterName: options.canisterName,
|
|
80
|
-
pascalName,
|
|
81
|
-
reactorName,
|
|
82
|
-
serviceName,
|
|
83
|
-
reactorType,
|
|
84
|
-
clientManagerPath,
|
|
85
|
-
declarationsPath
|
|
86
|
-
};
|
|
87
|
-
return generateStandardReactorFile(vars);
|
|
88
|
-
}
|
|
89
|
-
function generateStandardReactorFile(vars) {
|
|
90
|
-
const {
|
|
91
|
-
pascalName,
|
|
92
|
-
reactorName,
|
|
93
|
-
serviceName,
|
|
94
|
-
reactorType,
|
|
95
|
-
clientManagerPath,
|
|
96
|
-
declarationsPath,
|
|
97
|
-
canisterName
|
|
98
|
-
} = vars;
|
|
99
|
-
return `import { ${reactorType}, createActorHooks } from "@ic-reactor/react"
|
|
88
|
+
return `import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
|
|
100
89
|
import { clientManager } from "${clientManagerPath}"
|
|
101
90
|
import { idlFactory, type _SERVICE } from "${declarationsPath}"
|
|
102
91
|
|
|
103
92
|
export type ${serviceName} = _SERVICE
|
|
104
93
|
|
|
105
94
|
/**
|
|
106
|
-
* ${pascalName}
|
|
95
|
+
* ${pascalName} Reactor
|
|
96
|
+
*
|
|
97
|
+
* Auto-generated by @ic-reactor/codegen \u2014 do not edit.
|
|
98
|
+
* Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
|
|
107
99
|
*/
|
|
108
|
-
export const ${reactorName} = new
|
|
100
|
+
export const ${reactorName} = new DisplayReactor<${serviceName}>({
|
|
109
101
|
clientManager,
|
|
110
102
|
idlFactory,
|
|
111
103
|
name: "${canisterName}",
|
|
@@ -122,18 +114,123 @@ export const {
|
|
|
122
114
|
`;
|
|
123
115
|
}
|
|
124
116
|
|
|
125
|
-
// src/
|
|
117
|
+
// src/pipeline.ts
|
|
118
|
+
async function runCanisterPipeline(options) {
|
|
119
|
+
const { canisterConfig, projectRoot, globalConfig } = options;
|
|
120
|
+
const { name, didFile, clientManagerPath } = canisterConfig;
|
|
121
|
+
const files = [];
|
|
122
|
+
const resolvedDidFile = path3.isAbsolute(didFile) ? didFile : path3.resolve(projectRoot, didFile);
|
|
123
|
+
if (!fs2.existsSync(resolvedDidFile)) {
|
|
124
|
+
return {
|
|
125
|
+
canisterName: name,
|
|
126
|
+
success: false,
|
|
127
|
+
files: [],
|
|
128
|
+
error: `DID file not found: ${resolvedDidFile}`
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const canisterOutDir = canisterConfig.outDir != null ? path3.isAbsolute(canisterConfig.outDir) ? canisterConfig.outDir : path3.resolve(projectRoot, canisterConfig.outDir) : path3.resolve(projectRoot, globalConfig.outDir, name);
|
|
132
|
+
const resolvedClientManagerPath = clientManagerPath ?? globalConfig.clientManagerPath ?? "../../clients";
|
|
133
|
+
try {
|
|
134
|
+
const declResult = await generateDeclarations({
|
|
135
|
+
didFile: resolvedDidFile,
|
|
136
|
+
outDir: canisterOutDir,
|
|
137
|
+
canisterName: name
|
|
138
|
+
});
|
|
139
|
+
if (!declResult.success) {
|
|
140
|
+
return {
|
|
141
|
+
canisterName: name,
|
|
142
|
+
success: false,
|
|
143
|
+
files,
|
|
144
|
+
error: declResult.error
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
files.push(...declResult.files);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return {
|
|
150
|
+
canisterName: name,
|
|
151
|
+
success: false,
|
|
152
|
+
files,
|
|
153
|
+
error: `Declarations step failed: ${err instanceof Error ? err.message : String(err)}`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const reactorPath = path3.join(canisterOutDir, "index.ts");
|
|
157
|
+
try {
|
|
158
|
+
const reactorContent = generateReactorFile({
|
|
159
|
+
canisterName: name,
|
|
160
|
+
didFile: resolvedDidFile,
|
|
161
|
+
clientManagerPath: resolvedClientManagerPath
|
|
162
|
+
});
|
|
163
|
+
fs2.mkdirSync(canisterOutDir, { recursive: true });
|
|
164
|
+
fs2.writeFileSync(reactorPath, reactorContent);
|
|
165
|
+
files.push({ success: true, filePath: reactorPath });
|
|
166
|
+
} catch (err) {
|
|
167
|
+
files.push({
|
|
168
|
+
success: false,
|
|
169
|
+
filePath: reactorPath,
|
|
170
|
+
error: `Reactor generation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
canisterName: name,
|
|
174
|
+
success: false,
|
|
175
|
+
files,
|
|
176
|
+
error: `Reactor step failed: ${err instanceof Error ? err.message : String(err)}`
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
canisterName: name,
|
|
181
|
+
success: true,
|
|
182
|
+
files
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/parser.ts
|
|
187
|
+
import { didToJs as didToJs2 } from "@ic-reactor/parser";
|
|
188
|
+
import fs3 from "fs";
|
|
189
|
+
function extractMethods(didContent) {
|
|
190
|
+
try {
|
|
191
|
+
const jsContent = didToJs2(didContent);
|
|
192
|
+
const methods = [];
|
|
193
|
+
const serviceMatch = /IDL\.Service\(\{([\s\S]*?)\}\)/.exec(jsContent);
|
|
194
|
+
if (!serviceMatch) return methods;
|
|
195
|
+
const serviceBody = serviceMatch[1];
|
|
196
|
+
const methodRegex = /['"]([\w]+)['"]\s*:\s*IDL\.Func\(\s*\[(.*?)\],\s*\[.*?\],\s*\[(.*?)\]\)/g;
|
|
197
|
+
let match;
|
|
198
|
+
while ((match = methodRegex.exec(serviceBody)) !== null) {
|
|
199
|
+
const [, name, args, annotations] = match;
|
|
200
|
+
methods.push({
|
|
201
|
+
name,
|
|
202
|
+
type: annotations.includes("'query'") ? "query" : "mutation",
|
|
203
|
+
hasArgs: args.trim().length > 0
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return methods;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
209
|
+
throw new Error(`Failed to parse Candid: ${msg}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function parseDIDFile(didFilePath) {
|
|
213
|
+
if (!fs3.existsSync(didFilePath)) {
|
|
214
|
+
throw new Error(`DID file not found: ${didFilePath}`);
|
|
215
|
+
}
|
|
216
|
+
const content = fs3.readFileSync(didFilePath, "utf-8");
|
|
217
|
+
return extractMethods(content);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/generators/client.ts
|
|
126
221
|
function generateClientFile(options = {}) {
|
|
127
222
|
const { queryClientPath } = options;
|
|
128
|
-
|
|
129
|
-
${queryClientPath ? `import { queryClient } from "${queryClientPath}"` : `import { QueryClient } from "@tanstack/react-query"
|
|
223
|
+
const queryClientImport = queryClientPath ? `import { queryClient } from "${queryClientPath}"` : `import { QueryClient } from "@tanstack/react-query"
|
|
130
224
|
|
|
131
|
-
export const queryClient = new QueryClient()
|
|
225
|
+
export const queryClient = new QueryClient()`;
|
|
226
|
+
return `import { ClientManager } from "@ic-reactor/react"
|
|
227
|
+
${queryClientImport}
|
|
132
228
|
|
|
133
229
|
/**
|
|
134
230
|
* IC Reactor Client Manager
|
|
135
|
-
*
|
|
136
|
-
* Auto-generated by @ic-reactor/codegen
|
|
231
|
+
*
|
|
232
|
+
* Auto-generated by @ic-reactor/codegen \u2014 customize as needed.
|
|
233
|
+
* See: https://github.com/B3Pay/ic-reactor#client-manager
|
|
137
234
|
*/
|
|
138
235
|
export const clientManager = new ClientManager({
|
|
139
236
|
queryClient,
|
|
@@ -143,10 +240,13 @@ export const clientManager = new ClientManager({
|
|
|
143
240
|
}
|
|
144
241
|
export {
|
|
145
242
|
declarationsExist,
|
|
243
|
+
extractMethods,
|
|
146
244
|
generateClientFile,
|
|
147
245
|
generateDeclarations,
|
|
148
246
|
generateReactorFile,
|
|
149
247
|
getReactorName,
|
|
150
248
|
getServiceTypeName,
|
|
249
|
+
parseDIDFile,
|
|
250
|
+
runCanisterPipeline,
|
|
151
251
|
toPascalCase
|
|
152
252
|
};
|
package/package.json
CHANGED
package/src/bindgen.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
2
|
-
import { generateDeclarations } from "./bindgen"
|
|
3
2
|
import fs from "node:fs"
|
|
4
3
|
import path from "node:path"
|
|
4
|
+
import { generateDeclarations } from "./generators"
|
|
5
5
|
|
|
6
6
|
describe("Bindgen", () => {
|
|
7
7
|
const mockDidFile = "mock/test.did"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Manager Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates a central `clients.ts` file that creates a `ClientManager` instance.
|
|
5
|
+
* This file is shared across all canisters in a project.
|
|
6
|
+
*
|
|
7
|
+
* Users typically customize this file once to configure:
|
|
8
|
+
* - Custom host (local vs. mainnet)
|
|
9
|
+
* - Identity provider
|
|
10
|
+
* - Query client options
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface ClientGeneratorOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Import path to an existing `queryClient` instance.
|
|
16
|
+
* If omitted, a default `QueryClient` will be created.
|
|
17
|
+
*/
|
|
18
|
+
queryClientPath?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate the content for a central client manager file (`clients.ts`).
|
|
23
|
+
*/
|
|
24
|
+
export function generateClientFile(
|
|
25
|
+
options: ClientGeneratorOptions = {}
|
|
26
|
+
): string {
|
|
27
|
+
const { queryClientPath } = options
|
|
28
|
+
|
|
29
|
+
const queryClientImport = queryClientPath
|
|
30
|
+
? `import { queryClient } from "${queryClientPath}"`
|
|
31
|
+
: `import { QueryClient } from "@tanstack/react-query"
|
|
32
|
+
|
|
33
|
+
export const queryClient = new QueryClient()`
|
|
34
|
+
|
|
35
|
+
return `import { ClientManager } from "@ic-reactor/react"
|
|
36
|
+
${queryClientImport}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* IC Reactor Client Manager
|
|
40
|
+
*
|
|
41
|
+
* Auto-generated by @ic-reactor/codegen — customize as needed.
|
|
42
|
+
* See: https://github.com/B3Pay/ic-reactor#client-manager
|
|
43
|
+
*/
|
|
44
|
+
export const clientManager = new ClientManager({
|
|
45
|
+
queryClient,
|
|
46
|
+
withCanisterEnv: true,
|
|
47
|
+
})
|
|
48
|
+
`
|
|
49
|
+
}
|