@ic-reactor/vite-plugin 0.1.0 → 0.3.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/dist/index.d.ts CHANGED
@@ -1,18 +1,10 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- declare function icReactorAdvancedPlugin(options: IcReactorPluginOptions): Plugin;
4
-
5
3
  /**
6
4
  * IC-Reactor Vite Plugin
7
5
  *
8
6
  * A Vite plugin that generates ic-reactor hooks from Candid .did files.
9
- *
10
- * ⚠️ IMPORTANT: This plugin ONLY generates the reactor and hooks.
11
- * The user is responsible for creating and configuring:
12
- * - ClientManager
13
- * - QueryClient
14
- *
15
- * The generated file will import the clientManager from a user-specified path.
7
+ * Uses @ic-reactor/codegen for all code generation logic.
16
8
  *
17
9
  * Usage:
18
10
  * ```ts
@@ -25,7 +17,7 @@ declare function icReactorAdvancedPlugin(options: IcReactorPluginOptions): Plugi
25
17
  * {
26
18
  * name: "backend",
27
19
  * didFile: "../backend/backend.did",
28
- * clientManagerPath: "../lib/client" // User provides their own ClientManager
20
+ * clientManagerPath: "../lib/client"
29
21
  * }
30
22
  * ]
31
23
  * })
@@ -35,33 +27,29 @@ declare function icReactorAdvancedPlugin(options: IcReactorPluginOptions): Plugi
35
27
  */
36
28
 
37
29
  interface CanisterConfig {
38
- /** Name of the canister (used for variable naming) */
39
30
  name: string;
40
- /** Path to the .did file */
41
- didFile: string;
42
- /** Output directory (default: ./src/canisters/<name>) */
43
31
  outDir?: string;
44
- /** Use DisplayReactor for React-friendly types (default: true) */
45
- useDisplayReactor?: boolean;
46
- /**
47
- * Path to import ClientManager from (relative to generated file).
48
- * The file at this path should export: { clientManager: ClientManager }
49
- * Default: "../../lib/client"
50
- */
32
+ didFile?: string;
51
33
  clientManagerPath?: string;
52
34
  }
53
35
  interface IcReactorPluginOptions {
54
36
  /** List of canisters to generate hooks for */
55
37
  canisters: CanisterConfig[];
56
- /** Base output directory (default: ./src/canisters) */
38
+ /** Base output directory (default: ./src/lib/canisters) */
57
39
  outDir?: string;
58
40
  /**
59
41
  * Path to import ClientManager from (relative to generated file).
60
- * The file at this path should export: { clientManager: ClientManager }
61
- * Default: "../../lib/client"
42
+ * Default: "../../clients"
62
43
  */
63
44
  clientManagerPath?: string;
45
+ /**
46
+ * Automatically inject the IC environment (canister IDs and root key)
47
+ * into the browser using an `ic_env` cookie. (default: true)
48
+ *
49
+ * This is useful for local development with `icp`.
50
+ */
51
+ injectEnvironment?: boolean;
64
52
  }
65
53
  declare function icReactorPlugin(options: IcReactorPluginOptions): Plugin;
66
54
 
67
- export { type CanisterConfig, type IcReactorPluginOptions, icReactorAdvancedPlugin, icReactorPlugin };
55
+ export { type CanisterConfig, type IcReactorPluginOptions, icReactorPlugin };
package/dist/index.js CHANGED
@@ -1,373 +1,166 @@
1
- // src/simple.ts
2
- import { generate as generate2 } from "@icp-sdk/bindgen/core";
3
- import fs2 from "fs";
4
- import path2 from "path";
5
-
6
- // src/advanced.ts
1
+ // src/index.ts
7
2
  import fs from "fs";
8
3
  import path from "path";
9
- import { generate } from "@icp-sdk/bindgen/core";
10
- function toPascalCase(str) {
11
- return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
12
- }
13
- function toCamelCase(str) {
14
- const pascal = toPascalCase(str);
15
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
16
- }
17
- function extractMethods(didContent) {
18
- const methodRegex = /([a-zA-Z0-9_]+)\s*:\s*(?:func\s*)?\(([\s\S]*?)\)\s*->\s*\(([\s\S]*?)\)\s*(query|composite_query)?/g;
19
- const methods = [];
20
- let match;
21
- while ((match = methodRegex.exec(didContent)) !== null) {
22
- const name = match[1];
23
- const args = match[2].trim();
24
- const isQuery = !!match[4];
25
- methods.push({
26
- name,
27
- type: isQuery ? "query" : "mutation",
28
- hasArgs: args.length > 0
29
- });
4
+ import { execFileSync } from "child_process";
5
+ import {
6
+ generateDeclarations,
7
+ generateReactorFile,
8
+ generateClientFile
9
+ } from "@ic-reactor/codegen";
10
+ function getIcEnvironmentInfo(canisterNames) {
11
+ const environment = process.env.ICP_ENVIRONMENT || "local";
12
+ try {
13
+ const networkStatus = JSON.parse(
14
+ execFileSync("icp", ["network", "status", "-e", environment, "--json"], {
15
+ encoding: "utf-8"
16
+ })
17
+ );
18
+ const rootKey = networkStatus.root_key;
19
+ const proxyTarget = `http://127.0.0.1:${networkStatus.port}`;
20
+ const canisterIds = {};
21
+ for (const name of canisterNames) {
22
+ try {
23
+ const canisterId = execFileSync(
24
+ "icp",
25
+ ["canister", "status", name, "-e", environment, "-i"],
26
+ {
27
+ encoding: "utf-8"
28
+ }
29
+ ).trim();
30
+ canisterIds[name] = canisterId;
31
+ } catch {
32
+ }
33
+ }
34
+ return { environment, rootKey, proxyTarget, canisterIds };
35
+ } catch {
36
+ return null;
30
37
  }
31
- return methods;
32
38
  }
33
- function generateAdvancedReactorFile(canisterName, useDisplayReactor, clientManagerPath, didContent) {
34
- const pascalName = toPascalCase(canisterName);
35
- const camelName = toCamelCase(canisterName);
36
- const reactorType = useDisplayReactor ? "DisplayReactor" : "Reactor";
37
- const methods = extractMethods(didContent);
38
- const hooks = methods.map(({ name, type, hasArgs }) => {
39
- const pascalMethod = toPascalCase(name);
40
- const camelMethod = toCamelCase(name);
41
- if (type === "query") {
42
- const hook = `
43
- export const use${pascalMethod}Query = (
44
- args: Parameters<${pascalName}Service["${name}"]>,
45
- options?: any
46
- ) =>
47
- useActorQuery({
48
- functionName: "${name}",
49
- args,
50
- ...options,
51
- })
52
- `;
53
- const staticQuery = !hasArgs ? `
54
- export const ${camelMethod}Query = createQuery(${camelName}Reactor, {
55
- functionName: "${name}",
56
- })
57
- ` : "";
58
- return hook + staticQuery;
59
- } else {
60
- const hook = `
61
- export const use${pascalMethod}Mutation = (
62
- options?: any
63
- ) =>
64
- useActorMutation({
65
- functionName: "${name}",
66
- ...options,
67
- })
68
- `;
69
- const staticMutation = !hasArgs ? `
70
- export const ${camelMethod}Mutation = createMutation(${camelName}Reactor, {
71
- functionName: "${name}",
72
- })
73
- ` : "";
74
- return hook + staticMutation;
75
- }
76
- });
77
- return `/**
78
- * AUTO-GENERATED BY @ic-reactor/vite-plugin
79
- * DO NOT EDIT MANUALLY
80
- *
81
- * Canister: ${canisterName}
82
- * Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
83
- *
84
- * This file provides type-safe React hooks for interacting with the
85
- * ${canisterName} canister using ic-reactor.
86
- */
87
-
88
- import {
89
- ${reactorType},
90
- createActorHooks,
91
- createAuthHooks,
92
- createQuery,
93
- createMutation,
94
- } from "@ic-reactor/react"
95
-
96
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
97
- // USER-PROVIDED CLIENT MANAGER
98
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
99
- import { clientManager } from "${clientManagerPath}"
100
-
101
- // Import generated declarations from @icp-sdk/bindgen
102
- import {
103
- idlFactory,
104
- type _SERVICE,
105
- } from "./declarations/${canisterName}.did"
106
-
107
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
108
- // REACTOR INSTANCE
109
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
110
- type ${pascalName}Service = _SERVICE
111
-
112
- /**
113
- * ${pascalName} Reactor with ${useDisplayReactor ? "Display" : "Candid"} type transformations.
114
- * ${useDisplayReactor ? "Automatically converts bigint \u2192 string, Principal \u2192 string, etc." : "Uses raw Candid types."}
115
- */
116
- export const ${camelName}Reactor = new ${reactorType}<${pascalName}Service>({
117
- clientManager,
118
- idlFactory,
119
- name: "${canisterName}",
120
- })
121
-
122
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
123
- // ACTOR & AUTH HOOKS
124
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
125
- export const {
126
- useActorQuery,
127
- useActorMutation,
128
- useActorSuspenseQuery,
129
- useActorInfiniteQuery,
130
- useActorSuspenseInfiniteQuery,
131
- useActorMethod,
132
- } = createActorHooks(${camelName}Reactor)
133
-
134
- export const use${pascalName}Query = useActorQuery
135
- export const use${pascalName}Mutation = useActorMutation
136
- export const use${pascalName}SuspenseQuery = useActorSuspenseQuery
137
- export const use${pascalName}InfiniteQuery = useActorInfiniteQuery
138
- export const use${pascalName}SuspenseInfiniteQuery = useActorSuspenseInfiniteQuery
139
- export const use${pascalName}Method = useActorMethod
140
-
141
- export const { useAuth, useAgentState, useUserPrincipal } = createAuthHooks(
142
- clientManager
143
- )
144
-
145
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
146
- // METHOD HOOKS
147
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
148
- ${hooks.join("")}
149
-
150
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
151
- // RE-EXPORTS
152
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
153
- export { idlFactory }
154
- export type { ${pascalName}Service }
155
- `;
39
+ function buildIcEnvCookie(canisterIds, rootKey) {
40
+ const envParts = [`ic_root_key=${rootKey}`];
41
+ for (const [name, id] of Object.entries(canisterIds)) {
42
+ envParts.push(`PUBLIC_CANISTER_ID:${name}=${id}`);
43
+ }
44
+ return encodeURIComponent(envParts.join("&"));
156
45
  }
157
- function icReactorAdvancedPlugin(options) {
158
- const baseOutDir = options.outDir ?? "./src/canisters";
46
+ function icReactorPlugin(options) {
47
+ const baseOutDir = options.outDir ?? "./src/lib/canisters";
159
48
  return {
160
- name: "ic-reactor-advanced-plugin",
161
- async buildStart() {
162
- for (const canister of options.canisters) {
163
- const outDir = canister.outDir ?? path.join(baseOutDir, canister.name);
164
- if (!fs.existsSync(outDir)) {
165
- fs.mkdirSync(outDir, { recursive: true });
166
- }
167
- console.log(
168
- `[ic-reactor] Generating advanced hooks for ${canister.name} from ${canister.didFile}`
169
- );
170
- try {
171
- await generate({
172
- didFile: canister.didFile,
173
- outDir,
174
- output: {
175
- actor: {
176
- disabled: true
177
- },
178
- force: true
49
+ name: "ic-reactor-plugin",
50
+ config(_config, { command }) {
51
+ if (command !== "serve" || !(options.injectEnvironment ?? true)) {
52
+ return {};
53
+ }
54
+ const canisterNames = options.canisters.map((c) => c.name);
55
+ const icEnv = getIcEnvironmentInfo(canisterNames);
56
+ if (!icEnv) {
57
+ return {
58
+ server: {
59
+ proxy: {
60
+ "/api": {
61
+ target: "http://127.0.0.1:4943",
62
+ changeOrigin: true
63
+ }
179
64
  }
180
- });
181
- console.log(
182
- `[ic-reactor] Declarations generated at ${path.join(
183
- outDir,
184
- "declarations"
185
- )}`
186
- );
187
- } catch (error) {
188
- console.error(`[ic-reactor] Failed to generate declarations:`, error);
189
- continue;
190
- }
191
- const clientManagerPath = canister.clientManagerPath ?? options.clientManagerPath ?? "../../lib/client";
192
- let didContent = "";
193
- try {
194
- didContent = fs.readFileSync(canister.didFile, "utf-8");
195
- } catch (e) {
196
- console.warn(
197
- `[ic-reactor] Could not read DID file at ${canister.didFile}, skipping hook generation.`
198
- );
199
- continue;
65
+ }
66
+ };
67
+ }
68
+ const cookieValue = buildIcEnvCookie(icEnv.canisterIds, icEnv.rootKey);
69
+ return {
70
+ server: {
71
+ headers: {
72
+ "Set-Cookie": `ic_env=${cookieValue}; Path=/; SameSite=Lax;`
73
+ },
74
+ proxy: {
75
+ "/api": {
76
+ target: icEnv.proxyTarget,
77
+ changeOrigin: true
78
+ }
79
+ }
200
80
  }
201
- const reactorContent = generateAdvancedReactorFile(
202
- canister.name,
203
- canister.useDisplayReactor ?? true,
204
- clientManagerPath,
205
- didContent
206
- );
207
- const reactorPath = path.join(outDir, "index.ts");
208
- fs.writeFileSync(reactorPath, reactorContent);
81
+ };
82
+ },
83
+ async buildStart() {
84
+ const defaultClientPath = path.resolve(
85
+ process.cwd(),
86
+ "src/lib/clients.ts"
87
+ );
88
+ if (!fs.existsSync(defaultClientPath)) {
209
89
  console.log(
210
- `[ic-reactor] Advanced reactor hooks generated at ${reactorPath}`
90
+ `[ic-reactor] Default client manager not found. Creating at ${defaultClientPath}`
211
91
  );
92
+ const clientContent = generateClientFile();
93
+ fs.mkdirSync(path.dirname(defaultClientPath), { recursive: true });
94
+ fs.writeFileSync(defaultClientPath, clientContent);
212
95
  }
213
- },
214
- handleHotUpdate({ file, server }) {
215
- if (file.endsWith(".did")) {
216
- const canister = options.canisters.find(
217
- (c) => path.resolve(c.didFile) === file
218
- );
219
- if (canister) {
96
+ for (const canister of options.canisters) {
97
+ let didFile = canister.didFile;
98
+ const outDir = canister.outDir ?? path.join(baseOutDir, canister.name);
99
+ if (!didFile) {
100
+ const environment = process.env.ICP_ENVIRONMENT || "local";
220
101
  console.log(
221
- `[ic-reactor] Detected change in ${file}, regenerating...`
102
+ `[ic-reactor] didFile not specified for "${canister.name}". Attempting to download from canister...`
222
103
  );
223
- server.restart();
104
+ try {
105
+ const candidContent = execFileSync(
106
+ "icp",
107
+ [
108
+ "canister",
109
+ "metadata",
110
+ canister.name,
111
+ "candid:service",
112
+ "-e",
113
+ environment
114
+ ],
115
+ { encoding: "utf-8" }
116
+ ).trim();
117
+ const declarationsDir = path.join(outDir, "declarations");
118
+ if (!fs.existsSync(declarationsDir)) {
119
+ fs.mkdirSync(declarationsDir, { recursive: true });
120
+ }
121
+ didFile = path.join(declarationsDir, `${canister.name}.did`);
122
+ fs.writeFileSync(didFile, candidContent);
123
+ console.log(
124
+ `[ic-reactor] Candid downloaded and saved to ${didFile}`
125
+ );
126
+ } catch (error) {
127
+ console.error(
128
+ `[ic-reactor] Failed to download candid for ${canister.name}: ${error}`
129
+ );
130
+ continue;
131
+ }
224
132
  }
225
- }
226
- }
227
- };
228
- }
229
-
230
- // src/simple.ts
231
- function toPascalCase2(str) {
232
- return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
233
- }
234
- function toCamelCase2(str) {
235
- const pascal = toPascalCase2(str);
236
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
237
- }
238
- function generateReactorFile(canisterName, useDisplayReactor, clientManagerPath) {
239
- const pascalName = toPascalCase2(canisterName);
240
- const camelName = toCamelCase2(canisterName);
241
- const reactorType = useDisplayReactor ? "DisplayReactor" : "Reactor";
242
- return `/**
243
- * AUTO-GENERATED BY @ic-reactor/vite-plugin
244
- *
245
- * Canister: ${canisterName}
246
- * Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
247
- *
248
- * This file provides type-safe React hooks for interacting with the
249
- * ${canisterName} canister using ic-reactor.
250
- */
251
-
252
- import {
253
- ${reactorType},
254
- createActorHooks,
255
- } from "@ic-reactor/react"
256
-
257
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
258
- // USER-PROVIDED CLIENT MANAGER
259
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
260
- // The clientManager is imported from the user's own configuration file.
261
- // This allows full customization of agent options, network settings, etc.
262
- import { clientManager } from "${clientManagerPath}"
263
-
264
- // Import generated declarations from @icp-sdk/bindgen
265
- import {
266
- idlFactory,
267
- type _SERVICE,
268
- } from "./declarations/${canisterName}.did"
269
-
270
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
271
- // REACTOR INSTANCE
272
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
273
-
274
- /**
275
- * ${pascalName} Reactor with ${useDisplayReactor ? "Display" : "Candid"} type transformations.
276
- * ${useDisplayReactor ? "Automatically converts bigint \u2192 string, Principal \u2192 string, etc." : "Uses raw Candid types."}
277
- */
278
- export const ${camelName}Reactor = new ${reactorType}<_SERVICE>({
279
- clientManager,
280
- idlFactory,
281
- name: "${canisterName}",
282
- })
283
-
284
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
285
- // HOOKS
286
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
287
-
288
- /**
289
- * React hooks for the ${canisterName} canister.
290
- */
291
- const {
292
- useActorQuery: use${pascalName}Query,
293
- useActorSuspenseQuery: use${pascalName}SuspenseQuery,
294
- useActorInfiniteQuery: use${pascalName}InfiniteQuery,
295
- useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
296
- useActorMutation: use${pascalName}Mutation,
297
- useActorMethod: use${pascalName}Method,
298
- } = createActorHooks(${camelName}Reactor)
299
-
300
- export {
301
- use${pascalName}Query,
302
- use${pascalName}SuspenseQuery,
303
- use${pascalName}InfiniteQuery,
304
- use${pascalName}SuspenseInfiniteQuery,
305
- use${pascalName}Mutation,
306
- use${pascalName}Method,
307
- }
308
-
309
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
310
- // RE-EXPORTS
311
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
312
-
313
- export { idlFactory }
314
- export type { _SERVICE as ${pascalName}Service }
315
- `;
316
- }
317
- function icReactorPlugin(options) {
318
- const baseOutDir = options.outDir ?? "./src/canisters";
319
- return {
320
- name: "ic-reactor-plugin",
321
- async buildStart() {
322
- for (const canister of options.canisters) {
323
- const outDir = canister.outDir ?? path2.join(baseOutDir, canister.name);
324
- const declarationsDir = path2.join(outDir, "declarations");
325
133
  console.log(
326
- `[ic-reactor] Generating hooks for ${canister.name} from ${canister.didFile}`
134
+ `[ic-reactor] Generating hooks for ${canister.name} from ${didFile}`
327
135
  );
328
- if (fs2.existsSync(declarationsDir)) {
329
- fs2.rmSync(declarationsDir, { recursive: true, force: true });
330
- }
331
- fs2.mkdirSync(declarationsDir, { recursive: true });
332
- try {
333
- if (!fs2.existsSync(outDir)) {
334
- fs2.mkdirSync(outDir, { recursive: true });
335
- }
336
- await generate2({
337
- didFile: canister.didFile,
338
- outDir,
339
- // Pass the parent directory; bindgen appends "declarations"
340
- output: {
341
- actor: {
342
- disabled: true
343
- },
344
- force: true
345
- }
346
- });
347
- console.log(
348
- `[ic-reactor] Declarations generated at ${declarationsDir}`
136
+ const result = await generateDeclarations({
137
+ didFile,
138
+ outDir,
139
+ canisterName: canister.name
140
+ });
141
+ if (!result.success) {
142
+ console.error(
143
+ `[ic-reactor] Failed to generate declarations: ${result.error}`
349
144
  );
350
- } catch (error) {
351
- console.error(`[ic-reactor] Failed to generate declarations:`, error);
352
145
  continue;
353
146
  }
354
- const clientManagerPath = canister.clientManagerPath ?? options.clientManagerPath ?? "../../lib/client";
355
- const reactorContent = generateReactorFile(
356
- canister.name,
357
- canister.useDisplayReactor ?? true,
358
- clientManagerPath
359
- );
360
- const reactorPath = path2.join(outDir, "index.ts");
361
- fs2.mkdirSync(outDir, { recursive: true });
362
- fs2.writeFileSync(reactorPath, reactorContent);
147
+ const reactorContent = generateReactorFile({
148
+ canisterName: canister.name,
149
+ didFile,
150
+ clientManagerPath: canister.clientManagerPath ?? options.clientManagerPath
151
+ });
152
+ const reactorPath = path.join(outDir, "index.ts");
153
+ fs.mkdirSync(outDir, { recursive: true });
154
+ fs.writeFileSync(reactorPath, reactorContent);
363
155
  console.log(`[ic-reactor] Reactor hooks generated at ${reactorPath}`);
364
156
  }
365
157
  },
366
158
  handleHotUpdate({ file, server }) {
367
159
  if (file.endsWith(".did")) {
368
- const canister = options.canisters.find(
369
- (c) => path2.resolve(c.didFile) === file
370
- );
160
+ const canister = options.canisters.find((c) => {
161
+ if (!c.didFile) return false;
162
+ return path.resolve(c.didFile) === file;
163
+ });
371
164
  if (canister) {
372
165
  console.log(
373
166
  `[ic-reactor] Detected change in ${file}, regenerating...`
@@ -379,6 +172,5 @@ function icReactorPlugin(options) {
379
172
  };
380
173
  }
381
174
  export {
382
- icReactorAdvancedPlugin,
383
175
  icReactorPlugin
384
176
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/vite-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Vite plugin for zero-config IC reactor generation from Candid files",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -14,12 +14,9 @@
14
14
  }
15
15
  },
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "src"
18
19
  ],
19
- "scripts": {
20
- "build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.json",
21
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json"
22
- },
23
20
  "keywords": [
24
21
  "vite",
25
22
  "vite-plugin",
@@ -31,22 +28,31 @@
31
28
  ],
32
29
  "author": "Behrad Deylami",
33
30
  "license": "MIT",
34
- "peerDependencies": {
35
- "@icp-sdk/bindgen": "^0.2.0",
36
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/B3Pay/ic-reactor",
34
+ "directory": "packages/vite-plugin"
37
35
  },
38
- "peerDependenciesMeta": {
39
- "@icp-sdk/bindgen": {
40
- "optional": true
41
- }
36
+ "bugs": {
37
+ "url": "https://github.com/B3Pay/ic-reactor/issues"
42
38
  },
43
39
  "dependencies": {
44
- "chokidar": "^4.0.0"
40
+ "@ic-reactor/codegen": "0.3.0"
41
+ },
42
+ "peerDependencies": {
43
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
45
44
  },
46
45
  "devDependencies": {
47
- "@types/node": "^25.0.3",
46
+ "@icp-sdk/bindgen": "^0.2.1",
47
+ "@types/node": "^25.2.3",
48
48
  "tsup": "^8.5.1",
49
- "typescript": "^5.9.0",
50
- "vite": "^7.3.1"
49
+ "typescript": "^5.9.3",
50
+ "vite": "^7.3.1",
51
+ "vitest": "^4.0.18"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.json",
55
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json",
56
+ "test": "vitest run"
51
57
  }
52
- }
58
+ }