@ic-reactor/vite-plugin 0.6.0 → 0.7.1

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 CHANGED
@@ -38,12 +38,12 @@ pnpm add @ic-reactor/react @tanstack/react-query @icp-sdk/core
38
38
  // vite.config.ts
39
39
  import { defineConfig } from "vite"
40
40
  import react from "@vitejs/plugin-react"
41
- import { icReactor } from "@ic-reactor/vite-plugin"
41
+ import { icReactorPlugin } from "@ic-reactor/vite-plugin"
42
42
 
43
43
  export default defineConfig({
44
44
  plugins: [
45
45
  react(),
46
- icReactor({
46
+ icReactorPlugin({
47
47
  canisters: [
48
48
  {
49
49
  name: "backend",
@@ -87,56 +87,6 @@ function MyComponent() {
87
87
  }
88
88
  ```
89
89
 
90
- ### Reactor Mode (raw vs display)
91
-
92
- By default, generated hooks use `DisplayReactor` (backward compatible). You can switch the default globally, and override specific canisters that need raw Candid shapes.
93
-
94
- ```ts
95
- icReactor({
96
- canisters: [
97
- { name: "backend", didFile: "./backend/backend.did" },
98
- { name: "workflow_engine", didFile: "./workflow/workflow_engine.did" },
99
- ],
100
- reactor: {
101
- defaultMode: "display",
102
- canisters: {
103
- workflow_engine: "raw",
104
- },
105
- },
106
- })
107
- ```
108
-
109
- Generated canister folders now contain:
110
-
111
- - `index.generated.ts` (overwritten on regenerate)
112
- - `index.ts` (created once if missing, never overwritten)
113
-
114
- The generated implementation exports both factories so apps can opt in explicitly without editing generated files:
115
-
116
- ```ts
117
- // display-default canister (generated)
118
- export function createBackendRawReactor() {
119
- /* ... */
120
- }
121
- export function createBackendDisplayReactor() {
122
- /* ... */
123
- }
124
- export const backendReactor = createBackendDisplayReactor()
125
- export const BackendReactorMode = "display" as const
126
- ```
127
-
128
- ```ts
129
- // raw-default canister (generated)
130
- export function createWorkflowEngineRawReactor() {
131
- /* ... */
132
- }
133
- export function createWorkflowEngineDisplayReactor() {
134
- /* ... */
135
- }
136
- export const workflowEngineReactor = createWorkflowEngineRawReactor()
137
- export const WorkflowEngineReactorMode = "raw" as const
138
- ```
139
-
140
90
  ## Configuration
141
91
 
142
92
  ### Plugin Options
@@ -147,7 +97,6 @@ export const WorkflowEngineReactorMode = "raw" as const
147
97
  | `outDir` | `string` | Base output directory for generated files. | `"src/declarations"` |
148
98
  | `clientManagerPath` | `string` | Path to client manager import. | `"../../clients"` |
149
99
  | `injectEnvironment` | `boolean` | Inject `ic_env` cookie for local development. | `true` |
150
- | `reactor` | `ReactorConfig` | Default/per-canister raw vs display reactor mode. | display default |
151
100
 
152
101
  ### Canister Config
153
102
 
@@ -159,13 +108,6 @@ export const WorkflowEngineReactorMode = "raw" as const
159
108
  | `clientManagerPath` | `string` | Override client manager path. | No |
160
109
  | `canisterId` | `string` | Optional fixed canister ID. | No |
161
110
 
162
- ### ReactorConfig
163
-
164
- | Option | Type | Description | Required |
165
- | :------------ | :----------------------------------- | :--------------------------------------- | :------- |
166
- | `defaultMode` | `"raw" \| "display"` | Global default mode for generated hooks. | No |
167
- | `canisters` | `Record<string, "raw" \| "display">` | Per-canister overrides keyed by name. | No |
168
-
169
111
  ## Local Development (Environment Injection)
170
112
 
171
113
  When running `vite dev`, the plugin automatically handles local canister environment connection:
package/dist/index.cjs CHANGED
@@ -30,7 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- icReactor: () => icReactor
33
+ icReactorPlugin: () => icReactorPlugin
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
36
  var import_node_path = __toESM(require("path"), 1);
@@ -78,21 +78,21 @@ function buildIcEnvCookie(canisterIds, rootKey) {
78
78
  }
79
79
 
80
80
  // src/index.ts
81
- function icReactor(options) {
81
+ function icReactorPlugin(options) {
82
82
  const {
83
83
  canisters,
84
84
  outDir = "src/declarations",
85
85
  clientManagerPath = "../../clients",
86
- injectEnvironment = true,
87
- reactor
86
+ injectEnvironment = true
88
87
  } = options;
89
88
  const globalConfig = {
90
89
  outDir,
91
90
  clientManagerPath
92
91
  };
93
- if (reactor != null) {
94
- globalConfig.reactor = reactor;
95
- }
92
+ const projectRoot = process.cwd();
93
+ const resolveDidPath = (didFile) => import_node_path.default.normalize(
94
+ import_node_path.default.isAbsolute(didFile) ? didFile : import_node_path.default.resolve(projectRoot, didFile)
95
+ );
96
96
  const plugin = {
97
97
  name: "ic-reactor-plugin",
98
98
  enforce: "pre",
@@ -133,8 +133,11 @@ function icReactor(options) {
133
133
  }
134
134
  };
135
135
  },
136
+ configureServer(server) {
137
+ const didFiles = canisters.map((c) => resolveDidPath(c.didFile));
138
+ server.watcher.add(didFiles);
139
+ },
136
140
  async buildStart() {
137
- const projectRoot = process.cwd();
138
141
  console.log(
139
142
  `[ic-reactor] Generating hooks for ${canisters.length} canisters...`
140
143
  );
@@ -162,14 +165,13 @@ function icReactor(options) {
162
165
  handleHotUpdate({ file, server }) {
163
166
  if (file.endsWith(".did")) {
164
167
  const affectedCanister = canisters.find((c) => {
165
- const configPath = import_node_path.default.resolve(process.cwd(), c.didFile);
166
- return configPath === file;
168
+ const configPath = resolveDidPath(c.didFile);
169
+ return configPath === import_node_path.default.normalize(file);
167
170
  });
168
171
  if (affectedCanister) {
169
172
  console.log(
170
173
  `[ic-reactor] .did file changed: ${affectedCanister.name}. Regenerating...`
171
174
  );
172
- const projectRoot = process.cwd();
173
175
  (0, import_codegen.runCanisterPipeline)({
174
176
  canisterConfig: affectedCanister,
175
177
  projectRoot,
@@ -189,5 +191,5 @@ function icReactor(options) {
189
191
  }
190
192
  // Annotate the CommonJS export names for ESM import in node:
191
193
  0 && (module.exports = {
192
- icReactor
194
+ icReactorPlugin
193
195
  });
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { CanisterConfig, ReactorGenerationConfig } from '@ic-reactor/codegen';
1
+ import { CanisterConfig } from '@ic-reactor/codegen';
2
2
 
3
3
  /**
4
4
  * @ic-reactor/vite-plugin
@@ -30,12 +30,7 @@ interface IcReactorPluginOptions {
30
30
  * Default: true
31
31
  */
32
32
  injectEnvironment?: boolean;
33
- /**
34
- * Reactor generation behavior.
35
- * Controls whether generated default hooks use raw (Reactor) or display (DisplayReactor).
36
- */
37
- reactor?: ReactorGenerationConfig;
38
33
  }
39
- declare function icReactor(options: IcReactorPluginOptions): any;
34
+ declare function icReactorPlugin(options: IcReactorPluginOptions): any;
40
35
 
41
- export { type IcReactorPluginOptions, icReactor };
36
+ export { type IcReactorPluginOptions, icReactorPlugin };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CanisterConfig, ReactorGenerationConfig } from '@ic-reactor/codegen';
1
+ import { CanisterConfig } from '@ic-reactor/codegen';
2
2
 
3
3
  /**
4
4
  * @ic-reactor/vite-plugin
@@ -30,12 +30,7 @@ interface IcReactorPluginOptions {
30
30
  * Default: true
31
31
  */
32
32
  injectEnvironment?: boolean;
33
- /**
34
- * Reactor generation behavior.
35
- * Controls whether generated default hooks use raw (Reactor) or display (DisplayReactor).
36
- */
37
- reactor?: ReactorGenerationConfig;
38
33
  }
39
- declare function icReactor(options: IcReactorPluginOptions): any;
34
+ declare function icReactorPlugin(options: IcReactorPluginOptions): any;
40
35
 
41
- export { type IcReactorPluginOptions, icReactor };
36
+ export { type IcReactorPluginOptions, icReactorPlugin };
package/dist/index.js CHANGED
@@ -46,21 +46,21 @@ function buildIcEnvCookie(canisterIds, rootKey) {
46
46
  }
47
47
 
48
48
  // src/index.ts
49
- function icReactor(options) {
49
+ function icReactorPlugin(options) {
50
50
  const {
51
51
  canisters,
52
52
  outDir = "src/declarations",
53
53
  clientManagerPath = "../../clients",
54
- injectEnvironment = true,
55
- reactor
54
+ injectEnvironment = true
56
55
  } = options;
57
56
  const globalConfig = {
58
57
  outDir,
59
58
  clientManagerPath
60
59
  };
61
- if (reactor != null) {
62
- globalConfig.reactor = reactor;
63
- }
60
+ const projectRoot = process.cwd();
61
+ const resolveDidPath = (didFile) => path.normalize(
62
+ path.isAbsolute(didFile) ? didFile : path.resolve(projectRoot, didFile)
63
+ );
64
64
  const plugin = {
65
65
  name: "ic-reactor-plugin",
66
66
  enforce: "pre",
@@ -101,8 +101,11 @@ function icReactor(options) {
101
101
  }
102
102
  };
103
103
  },
104
+ configureServer(server) {
105
+ const didFiles = canisters.map((c) => resolveDidPath(c.didFile));
106
+ server.watcher.add(didFiles);
107
+ },
104
108
  async buildStart() {
105
- const projectRoot = process.cwd();
106
109
  console.log(
107
110
  `[ic-reactor] Generating hooks for ${canisters.length} canisters...`
108
111
  );
@@ -130,14 +133,13 @@ function icReactor(options) {
130
133
  handleHotUpdate({ file, server }) {
131
134
  if (file.endsWith(".did")) {
132
135
  const affectedCanister = canisters.find((c) => {
133
- const configPath = path.resolve(process.cwd(), c.didFile);
134
- return configPath === file;
136
+ const configPath = resolveDidPath(c.didFile);
137
+ return configPath === path.normalize(file);
135
138
  });
136
139
  if (affectedCanister) {
137
140
  console.log(
138
141
  `[ic-reactor] .did file changed: ${affectedCanister.name}. Regenerating...`
139
142
  );
140
- const projectRoot = process.cwd();
141
143
  runCanisterPipeline({
142
144
  canisterConfig: affectedCanister,
143
145
  projectRoot,
@@ -156,5 +158,5 @@ function icReactor(options) {
156
158
  return plugin;
157
159
  }
158
160
  export {
159
- icReactor
161
+ icReactorPlugin
160
162
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/vite-plugin",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Vite plugin for zero-config IC reactor generation from Candid files",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -37,7 +37,7 @@
37
37
  "url": "https://github.com/B3Pay/ic-reactor/issues"
38
38
  },
39
39
  "dependencies": {
40
- "@ic-reactor/codegen": "0.6.0"
40
+ "@ic-reactor/codegen": "0.7.1"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
package/src/index.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest"
2
- import { icReactor, type IcReactorPluginOptions } from "./index"
2
+ import { icReactorPlugin, type IcReactorPluginOptions } from "./index"
3
3
  import path from "path"
4
4
  import { execFileSync } from "child_process"
5
5
  import { runCanisterPipeline } from "@ic-reactor/codegen"
@@ -24,7 +24,7 @@ vi.mock("fs", () => ({
24
24
  },
25
25
  }))
26
26
 
27
- describe("icReactor", () => {
27
+ describe("icReactorPlugin", () => {
28
28
  const mockOptions: IcReactorPluginOptions = {
29
29
  canisters: [
30
30
  {
@@ -50,6 +50,9 @@ describe("icReactor", () => {
50
50
  ws: {
51
51
  send: vi.fn(),
52
52
  },
53
+ watcher: {
54
+ add: vi.fn(),
55
+ },
53
56
  }
54
57
 
55
58
  beforeEach(() => {
@@ -60,7 +63,7 @@ describe("icReactor", () => {
60
63
  })
61
64
 
62
65
  it("should return correct plugin structure", () => {
63
- const plugin = icReactor(mockOptions)
66
+ const plugin = icReactorPlugin(mockOptions)
64
67
  expect(plugin.name).toBe("ic-reactor-plugin")
65
68
  expect(plugin.buildStart).toBeDefined()
66
69
  expect(plugin.handleHotUpdate).toBeDefined()
@@ -89,7 +92,7 @@ describe("icReactor", () => {
89
92
  }
90
93
  )
91
94
 
92
- const plugin = icReactor(mockOptions)
95
+ const plugin = icReactorPlugin(mockOptions)
93
96
  const config = (plugin as any).config({}, { command: "serve" })
94
97
 
95
98
  expect(config.server.headers["Set-Cookie"]).toContain("ic_env=")
@@ -107,7 +110,7 @@ describe("icReactor", () => {
107
110
  throw new Error("Command not found")
108
111
  })
109
112
 
110
- const plugin = icReactor(mockOptions)
113
+ const plugin = icReactorPlugin(mockOptions)
111
114
  const config = (plugin as any).config({}, { command: "serve" })
112
115
 
113
116
  expect(config.server.headers).toBeUndefined()
@@ -115,7 +118,7 @@ describe("icReactor", () => {
115
118
  })
116
119
 
117
120
  it("should return empty config for build command", () => {
118
- const plugin = icReactor(mockOptions)
121
+ const plugin = icReactorPlugin(mockOptions)
119
122
  const config = (plugin as any).config({}, { command: "build" })
120
123
 
121
124
  expect(config).toEqual({})
@@ -124,7 +127,7 @@ describe("icReactor", () => {
124
127
 
125
128
  describe("buildStart", () => {
126
129
  it("should generate declarations and reactor file", async () => {
127
- const plugin = icReactor(mockOptions)
130
+ const plugin = icReactorPlugin(mockOptions)
128
131
  await (plugin.buildStart as any)()
129
132
 
130
133
  expect(runCanisterPipeline).toHaveBeenCalledWith({
@@ -147,7 +150,7 @@ describe("icReactor", () => {
147
150
  error: "Failed to generate",
148
151
  })
149
152
 
150
- const plugin = icReactor(mockOptions)
153
+ const plugin = icReactorPlugin(mockOptions)
151
154
  await (plugin.buildStart as any)()
152
155
 
153
156
  expect(consoleErrorSpy).toHaveBeenCalledWith(
@@ -156,40 +159,20 @@ describe("icReactor", () => {
156
159
 
157
160
  consoleErrorSpy.mockRestore()
158
161
  })
162
+ })
159
163
 
160
- it("should pass reactor mode config to codegen", async () => {
161
- const plugin = icReactor({
162
- ...mockOptions,
163
- reactor: {
164
- defaultMode: "display",
165
- canisters: {
166
- test_canister: "raw",
167
- },
168
- },
169
- })
170
-
171
- await (plugin.buildStart as any)()
164
+ describe("handleHotUpdate", () => {
165
+ it("should register configured .did files with the watcher", () => {
166
+ const plugin = icReactorPlugin(mockOptions)
167
+ ;(plugin.configureServer as any)(mockServer)
172
168
 
173
- expect(runCanisterPipeline).toHaveBeenCalledWith({
174
- canisterConfig: mockOptions.canisters[0],
175
- projectRoot: expect.any(String),
176
- globalConfig: {
177
- outDir: "src/declarations",
178
- clientManagerPath: "../../clients",
179
- reactor: {
180
- defaultMode: "display",
181
- canisters: {
182
- test_canister: "raw",
183
- },
184
- },
185
- },
186
- })
169
+ expect(mockServer.watcher.add).toHaveBeenCalledWith([
170
+ expect.stringContaining("src/declarations/test.did"),
171
+ ])
187
172
  })
188
- })
189
173
 
190
- describe("handleHotUpdate", () => {
191
174
  it("should restart server when .did file changes", async () => {
192
- const plugin = icReactor(mockOptions)
175
+ const plugin = icReactorPlugin(mockOptions)
193
176
  const ctx = {
194
177
  file: "/absolute/path/to/src/declarations/test.did",
195
178
  server: mockServer,
@@ -210,7 +193,7 @@ describe("icReactor", () => {
210
193
  })
211
194
 
212
195
  it("should ignore other files", () => {
213
- const plugin = icReactor(mockOptions)
196
+ const plugin = icReactorPlugin(mockOptions)
214
197
  const ctx = {
215
198
  file: "/some/other/file.ts",
216
199
  server: mockServer,
package/src/index.ts CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  runCanisterPipeline,
14
14
  type CanisterConfig,
15
15
  type CodegenConfig,
16
- type ReactorGenerationConfig,
17
16
  } from "@ic-reactor/codegen"
18
17
  import { getIcEnvironmentInfo, buildIcEnvCookie } from "./env.js"
19
18
 
@@ -38,33 +37,26 @@ export interface IcReactorPluginOptions {
38
37
  * Default: true
39
38
  */
40
39
  injectEnvironment?: boolean
41
- /**
42
- * Reactor generation behavior.
43
- * Controls whether generated default hooks use raw (Reactor) or display (DisplayReactor).
44
- */
45
- reactor?: ReactorGenerationConfig
46
40
  }
47
41
 
48
- export function icReactor(options: IcReactorPluginOptions): any {
42
+ export function icReactorPlugin(options: IcReactorPluginOptions): any {
49
43
  const {
50
44
  canisters,
51
45
  outDir = "src/declarations",
52
46
  clientManagerPath = "../../clients",
53
47
  injectEnvironment = true,
54
- reactor,
55
48
  } = options
56
49
 
57
50
  // Construct a partial CodegenConfig to pass to the pipeline
58
- const globalConfig: Pick<
59
- CodegenConfig,
60
- "outDir" | "clientManagerPath" | "reactor"
61
- > = {
51
+ const globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath"> = {
62
52
  outDir,
63
53
  clientManagerPath,
64
54
  }
65
- if (reactor != null) {
66
- globalConfig.reactor = reactor
67
- }
55
+ const projectRoot = process.cwd()
56
+ const resolveDidPath = (didFile: string) =>
57
+ path.normalize(
58
+ path.isAbsolute(didFile) ? didFile : path.resolve(projectRoot, didFile)
59
+ )
68
60
 
69
61
  const plugin: Plugin = {
70
62
  name: "ic-reactor-plugin",
@@ -118,10 +110,15 @@ export function icReactor(options: IcReactorPluginOptions): any {
118
110
  }
119
111
  },
120
112
 
113
+ configureServer(server) {
114
+ // Explicitly watch configured DID files so HMR works even when they are not in the module graph.
115
+ const didFiles = canisters.map((c) => resolveDidPath(c.didFile))
116
+ server.watcher.add(didFiles)
117
+ },
118
+
121
119
  async buildStart() {
122
120
  // ── Code Generation ──────────────────────────────────────────────────
123
121
 
124
- const projectRoot = process.cwd()
125
122
  console.log(
126
123
  `[ic-reactor] Generating hooks for ${canisters.length} canisters...`
127
124
  )
@@ -161,8 +158,8 @@ export function icReactor(options: IcReactorPluginOptions): any {
161
158
  const affectedCanister = canisters.find((c) => {
162
159
  // Check if changed file matches configured didFile
163
160
  // Cast is safe because didFile is required in CanisterConfig
164
- const configPath = path.resolve(process.cwd(), c.didFile)
165
- return configPath === file
161
+ const configPath = resolveDidPath(c.didFile)
162
+ return configPath === path.normalize(file)
166
163
  })
167
164
 
168
165
  if (affectedCanister) {
@@ -171,7 +168,6 @@ export function icReactor(options: IcReactorPluginOptions): any {
171
168
  )
172
169
 
173
170
  // Re-run pipeline for this canister
174
- const projectRoot = process.cwd()
175
171
  runCanisterPipeline({
176
172
  canisterConfig: affectedCanister,
177
173
  projectRoot,