@ic-reactor/vite-plugin 0.11.1 → 0.12.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
@@ -31,17 +31,25 @@ export default defineConfig({
31
31
 
32
32
  ```ts
33
33
  // src/clients.ts
34
- import { ClientManager } from "@ic-reactor/core"
34
+ import { ClientManager } from "@ic-reactor/react"
35
35
  import { QueryClient } from "@tanstack/react-query"
36
36
 
37
37
  export const queryClient = new QueryClient()
38
38
  export const clientManager = new ClientManager({
39
39
  queryClient,
40
- withCanisterEnv: true,
41
40
  })
42
41
  ```
43
42
 
44
- The plugin generates files under `src/declarations/<canister>/` by default.
43
+ No opt-in flag is needed to pick up the plugin's environment: the plugin sets
44
+ the `ic_env` cookie and `ClientManager` reads it automatically in the browser.
45
+
46
+ The plugin generates files under `src/declarations/<canister>/` by default —
47
+ `declarations/<did-basename>.{js,d.ts,did}` plus a managed `index.generated.ts`
48
+ and a stable `index.ts` wrapper. With `target: "react"`, `index.generated.ts`
49
+ exports the reactor and six hooks named after the canister
50
+ (`use<Canister>Query`, `use<Canister>SuspenseQuery`,
51
+ `use<Canister>InfiniteQuery`, `use<Canister>SuspenseInfiniteQuery`,
52
+ `use<Canister>Mutation`, `use<Canister>Method`).
45
53
 
46
54
  If you want non-React output, set `target: "core"` and install the matching
47
55
  runtime package instead of `@ic-reactor/react`.
package/dist/index.cjs CHANGED
@@ -49,7 +49,10 @@ function getIcEnvironmentInfo(canisterNames) {
49
49
  })
50
50
  );
51
51
  const rootKey = networkStatus.root_key;
52
- const proxyTarget = `http://127.0.0.1:${networkStatus.port}`;
52
+ const proxyTarget = networkStatus.api_url || networkStatus.gateway_url || (networkStatus.port ? `http://127.0.0.1:${networkStatus.port}` : void 0);
53
+ if (!proxyTarget) {
54
+ return null;
55
+ }
53
56
  const canisterIds = {};
54
57
  for (const name of canisterNames) {
55
58
  try {
@@ -64,18 +67,41 @@ function getIcEnvironmentInfo(canisterNames) {
64
67
  } catch {
65
68
  }
66
69
  }
67
- return { environment, rootKey, proxyTarget, canisterIds };
70
+ const internetIdentityProvider = !canisterIds.internet_identity && isLocalhostGateway(proxyTarget) && environment !== "ic" ? localInternetIdentityProvider(proxyTarget) : void 0;
71
+ return {
72
+ environment,
73
+ rootKey,
74
+ proxyTarget,
75
+ canisterIds,
76
+ internetIdentityProvider
77
+ };
68
78
  } catch (error) {
69
79
  return null;
70
80
  }
71
81
  }
72
- function buildIcEnvCookie(canisterIds, rootKey) {
73
- const parts = [`ic_root_key=${rootKey}`];
82
+ function buildIcEnvCookie(canisterIds, rootKey, internetIdentityProvider) {
83
+ const parts = rootKey ? [`ic_root_key=${rootKey}`] : [];
74
84
  for (const [name, id] of Object.entries(canisterIds)) {
75
85
  parts.push(`PUBLIC_CANISTER_ID:${name}=${id}`);
76
86
  }
87
+ if (internetIdentityProvider) {
88
+ parts.push(`INTERNET_IDENTITY_PROVIDER=${internetIdentityProvider}`);
89
+ }
77
90
  return encodeURIComponent(parts.join("&"));
78
91
  }
92
+ function isLocalhostGateway(proxyTarget) {
93
+ try {
94
+ const { hostname } = new URL(proxyTarget);
95
+ return hostname === "localhost" || hostname === "127.0.0.1";
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+ function localInternetIdentityProvider(proxyTarget) {
101
+ const { protocol, port } = new URL(proxyTarget);
102
+ const portSuffix = port ? `:${port}` : "";
103
+ return `${protocol}//id.ai.localhost${portSuffix}/authorize`;
104
+ }
79
105
 
80
106
  // src/index.ts
81
107
  function icReactor(options) {
@@ -112,8 +138,16 @@ function icReactor(options) {
112
138
  }
113
139
  const icEnv = getIcEnvironmentInfo(canisterNames);
114
140
  if (!icEnv) {
141
+ const envOnlyCookie = canisters.length === 0 ? buildIcEnvCookie(
142
+ {},
143
+ void 0,
144
+ "http://id.ai.localhost:8000/authorize"
145
+ ) : void 0;
115
146
  return {
116
147
  server: {
148
+ headers: envOnlyCookie ? {
149
+ "Set-Cookie": `ic_env=${envOnlyCookie}; Path=/; SameSite=Lax;`
150
+ } : void 0,
117
151
  proxy: {
118
152
  "/api": {
119
153
  target: "http://127.0.0.1:4943",
@@ -128,7 +162,8 @@ function icReactor(options) {
128
162
  ...icEnv.canisterIds,
129
163
  ...configuredCanisterIds
130
164
  },
131
- icEnv.rootKey
165
+ icEnv.rootKey,
166
+ icEnv.internetIdentityProvider
132
167
  );
133
168
  return {
134
169
  server: {
package/dist/index.js CHANGED
@@ -17,7 +17,10 @@ function getIcEnvironmentInfo(canisterNames) {
17
17
  })
18
18
  );
19
19
  const rootKey = networkStatus.root_key;
20
- const proxyTarget = `http://127.0.0.1:${networkStatus.port}`;
20
+ const proxyTarget = networkStatus.api_url || networkStatus.gateway_url || (networkStatus.port ? `http://127.0.0.1:${networkStatus.port}` : void 0);
21
+ if (!proxyTarget) {
22
+ return null;
23
+ }
21
24
  const canisterIds = {};
22
25
  for (const name of canisterNames) {
23
26
  try {
@@ -32,18 +35,41 @@ function getIcEnvironmentInfo(canisterNames) {
32
35
  } catch {
33
36
  }
34
37
  }
35
- return { environment, rootKey, proxyTarget, canisterIds };
38
+ const internetIdentityProvider = !canisterIds.internet_identity && isLocalhostGateway(proxyTarget) && environment !== "ic" ? localInternetIdentityProvider(proxyTarget) : void 0;
39
+ return {
40
+ environment,
41
+ rootKey,
42
+ proxyTarget,
43
+ canisterIds,
44
+ internetIdentityProvider
45
+ };
36
46
  } catch (error) {
37
47
  return null;
38
48
  }
39
49
  }
40
- function buildIcEnvCookie(canisterIds, rootKey) {
41
- const parts = [`ic_root_key=${rootKey}`];
50
+ function buildIcEnvCookie(canisterIds, rootKey, internetIdentityProvider) {
51
+ const parts = rootKey ? [`ic_root_key=${rootKey}`] : [];
42
52
  for (const [name, id] of Object.entries(canisterIds)) {
43
53
  parts.push(`PUBLIC_CANISTER_ID:${name}=${id}`);
44
54
  }
55
+ if (internetIdentityProvider) {
56
+ parts.push(`INTERNET_IDENTITY_PROVIDER=${internetIdentityProvider}`);
57
+ }
45
58
  return encodeURIComponent(parts.join("&"));
46
59
  }
60
+ function isLocalhostGateway(proxyTarget) {
61
+ try {
62
+ const { hostname } = new URL(proxyTarget);
63
+ return hostname === "localhost" || hostname === "127.0.0.1";
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+ function localInternetIdentityProvider(proxyTarget) {
69
+ const { protocol, port } = new URL(proxyTarget);
70
+ const portSuffix = port ? `:${port}` : "";
71
+ return `${protocol}//id.ai.localhost${portSuffix}/authorize`;
72
+ }
47
73
 
48
74
  // src/index.ts
49
75
  function icReactor(options) {
@@ -80,8 +106,16 @@ function icReactor(options) {
80
106
  }
81
107
  const icEnv = getIcEnvironmentInfo(canisterNames);
82
108
  if (!icEnv) {
109
+ const envOnlyCookie = canisters.length === 0 ? buildIcEnvCookie(
110
+ {},
111
+ void 0,
112
+ "http://id.ai.localhost:8000/authorize"
113
+ ) : void 0;
83
114
  return {
84
115
  server: {
116
+ headers: envOnlyCookie ? {
117
+ "Set-Cookie": `ic_env=${envOnlyCookie}; Path=/; SameSite=Lax;`
118
+ } : void 0,
85
119
  proxy: {
86
120
  "/api": {
87
121
  target: "http://127.0.0.1:4943",
@@ -96,7 +130,8 @@ function icReactor(options) {
96
130
  ...icEnv.canisterIds,
97
131
  ...configuredCanisterIds
98
132
  },
99
- icEnv.rootKey
133
+ icEnv.rootKey,
134
+ icEnv.internetIdentityProvider
100
135
  );
101
136
  return {
102
137
  server: {
package/llms.txt ADDED
@@ -0,0 +1,41 @@
1
+ # @ic-reactor/vite-plugin — AI Quick Guide
2
+
3
+ Vite integration for automatic IC Reactor generation from `.did` files.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add -D @ic-reactor/vite-plugin
9
+ pnpm add @ic-reactor/react @tanstack/react-query @icp-sdk/core
10
+ ```
11
+
12
+ ## Minimal Setup
13
+
14
+ ```ts
15
+ import { icReactor } from "@ic-reactor/vite-plugin"
16
+
17
+ icReactor({
18
+ canisters: [{ name: "backend", didFile: "./backend/backend.did" }],
19
+ })
20
+ ```
21
+
22
+ ## Behavior
23
+
24
+ - Runs codegen on startup and `.did` changes
25
+ - Regenerates declarations + `index.generated.ts`
26
+ - Preserves user-facing `index.ts` wrapper after initial creation
27
+
28
+ ## Key Options
29
+
30
+ - `canisters`, `outDir`, `clientManagerPath`, `target`, `mode`, `canisterId`
31
+ - `injectEnvironment` for local `ic_env` cookie + `/api` proxy behavior
32
+
33
+ ## Best Practices
34
+
35
+ - Keep custom code in stable wrapper/factory modules
36
+ - Do not hand-edit generated `index.generated.ts`
37
+
38
+ ## Full References
39
+
40
+ - Full AI guide: https://ic-reactor.b3pay.net/llms-full.txt
41
+ - Vite plugin docs: https://ic-reactor.b3pay.net/v3/packages/vite-plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/vite-plugin",
3
- "version": "0.11.1",
3
+ "version": "0.12.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",
@@ -8,14 +8,22 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
14
- }
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "./package.json": "./package.json"
15
21
  },
16
22
  "files": [
17
23
  "dist",
18
- "src"
24
+ "src",
25
+ "README.md",
26
+ "llms.txt"
19
27
  ],
20
28
  "keywords": [
21
29
  "vite",
@@ -24,7 +32,9 @@
24
32
  "candid",
25
33
  "ic-reactor",
26
34
  "dfinity",
27
- "icp"
35
+ "icp",
36
+ "llms",
37
+ "ai-friendly"
28
38
  ],
29
39
  "author": "Behrad Deylami",
30
40
  "license": "MIT",
@@ -36,22 +46,24 @@
36
46
  "bugs": {
37
47
  "url": "https://github.com/B3Pay/ic-reactor/issues"
38
48
  },
49
+ "homepage": "https://ic-reactor.b3pay.net/v3/packages/vite-plugin",
39
50
  "dependencies": {
40
- "@ic-reactor/codegen": "0.11.1"
51
+ "@ic-reactor/codegen": "0.12.1"
41
52
  },
42
53
  "peerDependencies": {
43
54
  "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
44
55
  },
45
56
  "devDependencies": {
46
- "@icp-sdk/bindgen": "^0.3.0",
47
- "@types/node": "^25.5.2",
57
+ "@icp-sdk/bindgen": "^0.4.0",
58
+ "@types/node": "^26.1.1",
48
59
  "tsup": "^8.5.1",
49
- "typescript": "^5.9.3",
50
- "vitest": "^4.1.2"
60
+ "typescript": "^6.0.3",
61
+ "vitest": "^4.1.10"
51
62
  },
52
63
  "scripts": {
53
64
  "build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.json",
54
65
  "dev": "tsup src/index.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json",
55
- "test": "vitest run"
66
+ "test": "vitest run",
67
+ "typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
56
68
  }
57
69
  }
package/src/env.ts CHANGED
@@ -12,6 +12,7 @@ export interface IcEnvironment {
12
12
  rootKey: string
13
13
  proxyTarget: string
14
14
  canisterIds: Record<string, string>
15
+ internetIdentityProvider?: string
15
16
  }
16
17
 
17
18
  /**
@@ -31,8 +32,16 @@ export function getIcEnvironmentInfo(
31
32
  )
32
33
 
33
34
  const rootKey = networkStatus.root_key
34
- // Default to localhost:4943 if port strictly needed, but `icp` gives us the port
35
- const proxyTarget = `http://127.0.0.1:${networkStatus.port}`
35
+ const proxyTarget =
36
+ networkStatus.api_url ||
37
+ networkStatus.gateway_url ||
38
+ (networkStatus.port
39
+ ? `http://127.0.0.1:${networkStatus.port}`
40
+ : undefined)
41
+
42
+ if (!proxyTarget) {
43
+ return null
44
+ }
36
45
 
37
46
  const canisterIds: Record<string, string> = {}
38
47
 
@@ -52,7 +61,20 @@ export function getIcEnvironmentInfo(
52
61
  }
53
62
  }
54
63
 
55
- return { environment, rootKey, proxyTarget, canisterIds }
64
+ const internetIdentityProvider =
65
+ !canisterIds.internet_identity &&
66
+ isLocalhostGateway(proxyTarget) &&
67
+ environment !== "ic"
68
+ ? localInternetIdentityProvider(proxyTarget)
69
+ : undefined
70
+
71
+ return {
72
+ environment,
73
+ rootKey,
74
+ proxyTarget,
75
+ canisterIds,
76
+ internetIdentityProvider,
77
+ }
56
78
  } catch (error) {
57
79
  // CLI not found or failed
58
80
  return null
@@ -65,13 +87,33 @@ export function getIcEnvironmentInfo(
65
87
  */
66
88
  export function buildIcEnvCookie(
67
89
  canisterIds: Record<string, string>,
68
- rootKey: string
90
+ rootKey?: string,
91
+ internetIdentityProvider?: string
69
92
  ): string {
70
- const parts = [`ic_root_key=${rootKey}`]
93
+ const parts = rootKey ? [`ic_root_key=${rootKey}`] : []
71
94
 
72
95
  for (const [name, id] of Object.entries(canisterIds)) {
73
96
  parts.push(`PUBLIC_CANISTER_ID:${name}=${id}`)
74
97
  }
75
98
 
99
+ if (internetIdentityProvider) {
100
+ parts.push(`INTERNET_IDENTITY_PROVIDER=${internetIdentityProvider}`)
101
+ }
102
+
76
103
  return encodeURIComponent(parts.join("&"))
77
104
  }
105
+
106
+ function isLocalhostGateway(proxyTarget: string): boolean {
107
+ try {
108
+ const { hostname } = new URL(proxyTarget)
109
+ return hostname === "localhost" || hostname === "127.0.0.1"
110
+ } catch {
111
+ return false
112
+ }
113
+ }
114
+
115
+ function localInternetIdentityProvider(proxyTarget: string): string {
116
+ const { protocol, port } = new URL(proxyTarget)
117
+ const portSuffix = port ? `:${port}` : ""
118
+ return `${protocol}//id.ai.localhost${portSuffix}/authorize`
119
+ }
package/src/index.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest"
2
- import * as vitePluginModule from "./index"
3
- import type { IcReactorPluginOptions } from "./index"
2
+ import * as vitePluginModule from "./index.js"
3
+ import type { IcReactorPluginOptions } from "./index.js"
4
4
  import path from "path"
5
5
  import { execFileSync } from "child_process"
6
6
  import { runCanisterPipeline } from "@ic-reactor/codegen"
@@ -150,6 +150,42 @@ describe("icReactor", () => {
150
150
  )
151
151
  })
152
152
 
153
+ it("should inject built-in local Internet Identity provider when no project II canister is found", () => {
154
+ ;(execFileSync as any).mockImplementation(
155
+ (command: string, args: string[], _options: any) => {
156
+ if (
157
+ command === "icp" &&
158
+ args.includes("network") &&
159
+ args.includes("status")
160
+ ) {
161
+ return JSON.stringify({
162
+ root_key: "mock-root-key",
163
+ api_url: "http://localhost:8000/",
164
+ })
165
+ }
166
+ if (
167
+ command === "icp" &&
168
+ args.includes("canister") &&
169
+ args.includes("status")
170
+ ) {
171
+ if (args.includes("internet_identity")) {
172
+ throw new Error("system canister is not a project canister")
173
+ }
174
+ return "mock-canister-id"
175
+ }
176
+ return ""
177
+ }
178
+ )
179
+
180
+ const plugin = createVitePlugin(mockOptions)
181
+ const config = (plugin as any).config({}, { command: "serve" })
182
+
183
+ expect(config.server.headers["Set-Cookie"]).toContain(
184
+ "INTERNET_IDENTITY_PROVIDER%3Dhttp%3A%2F%2Fid.ai.localhost%3A8000%2Fauthorize"
185
+ )
186
+ expect(config.server.proxy["/api"].target).toBe("http://localhost:8000/")
187
+ })
188
+
153
189
  it("should fallback to default proxy when icp-cli fails", () => {
154
190
  ;(execFileSync as any).mockImplementation(() => {
155
191
  throw new Error("Command not found")
@@ -162,6 +198,20 @@ describe("icReactor", () => {
162
198
  expect(config.server.proxy["/api"].target).toBe("http://127.0.0.1:4943")
163
199
  })
164
200
 
201
+ it("should inject default local II provider in env-only mode when icp-cli project detection fails", () => {
202
+ ;(execFileSync as any).mockImplementation(() => {
203
+ throw new Error("project manifest not found")
204
+ })
205
+
206
+ const plugin = createVitePlugin({ canisters: [] })
207
+ const config = (plugin as any).config({}, { command: "serve" })
208
+
209
+ expect(config.server.headers["Set-Cookie"]).toContain(
210
+ "INTERNET_IDENTITY_PROVIDER%3Dhttp%3A%2F%2Fid.ai.localhost%3A8000%2Fauthorize"
211
+ )
212
+ expect(config.server.proxy["/api"].target).toBe("http://127.0.0.1:4943")
213
+ })
214
+
165
215
  it("should return empty config for build command", () => {
166
216
  const plugin = createVitePlugin(mockOptions)
167
217
  const config = (plugin as any).config({}, { command: "build" })
package/src/index.ts CHANGED
@@ -96,9 +96,24 @@ export function icReactor(options: IcReactorPluginOptions): any {
96
96
  const icEnv = getIcEnvironmentInfo(canisterNames)
97
97
 
98
98
  if (!icEnv) {
99
- // Fallback: just proxy /api to default local replica
99
+ const envOnlyCookie =
100
+ canisters.length === 0
101
+ ? buildIcEnvCookie(
102
+ {},
103
+ undefined,
104
+ "http://id.ai.localhost:8000/authorize"
105
+ )
106
+ : undefined
107
+
108
+ // Fallback: proxy /api to default local replica. In env-only mode,
109
+ // still provide the standard ICP CLI built-in local II URL.
100
110
  return {
101
111
  server: {
112
+ headers: envOnlyCookie
113
+ ? {
114
+ "Set-Cookie": `ic_env=${envOnlyCookie}; Path=/; SameSite=Lax;`,
115
+ }
116
+ : undefined,
102
117
  proxy: {
103
118
  "/api": {
104
119
  target: "http://127.0.0.1:4943",
@@ -114,7 +129,8 @@ export function icReactor(options: IcReactorPluginOptions): any {
114
129
  ...icEnv.canisterIds,
115
130
  ...configuredCanisterIds,
116
131
  },
117
- icEnv.rootKey
132
+ icEnv.rootKey,
133
+ icEnv.internetIdentityProvider
118
134
  )
119
135
 
120
136
  return {