@ic-reactor/vite-plugin 0.1.0 → 0.2.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 +24 -23
- package/dist/index.cjs +80 -343
- package/dist/index.d.cts +21 -29
- package/dist/index.d.ts +21 -29
- package/dist/index.js +84 -341
- package/package.json +21 -17
- package/src/index.ts +226 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IC-Reactor Vite Plugin
|
|
3
|
+
*
|
|
4
|
+
* A Vite plugin that generates ic-reactor hooks from Candid .did files.
|
|
5
|
+
* Uses @ic-reactor/codegen for all code generation logic.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { icReactorPlugin } from "@ic-reactor/vite-plugin"
|
|
10
|
+
*
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* plugins: [
|
|
13
|
+
* icReactorPlugin({
|
|
14
|
+
* canisters: [
|
|
15
|
+
* {
|
|
16
|
+
* name: "backend",
|
|
17
|
+
* didFile: "../backend/backend.did",
|
|
18
|
+
* clientManagerPath: "../lib/client"
|
|
19
|
+
* }
|
|
20
|
+
* ]
|
|
21
|
+
* })
|
|
22
|
+
* ]
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { Plugin, ViteDevServer } from "vite"
|
|
28
|
+
import fs from "fs"
|
|
29
|
+
import path from "path"
|
|
30
|
+
import {
|
|
31
|
+
generateDeclarations,
|
|
32
|
+
generateReactorFile,
|
|
33
|
+
type CanisterConfig,
|
|
34
|
+
} from "@ic-reactor/codegen"
|
|
35
|
+
|
|
36
|
+
const ICP_LOCAL_IDS_PATH = ".icp/cache/mappings/local.ids.json"
|
|
37
|
+
const IC_ROOT_KEY_HEX =
|
|
38
|
+
"308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c050302010361008b52b4994f94c7ce4be1c1542d7c81dc79fea17d49efe8fa42e8566373581d4b969c4a59e96a0ef51b711fe5027ec01601182519d0a788f4bfe388e593b97cd1d7e44904de79422430bca686ac8c21305b3397b5ba4d7037d17877312fb7ee34"
|
|
39
|
+
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
// TYPES
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
export interface IcReactorPluginOptions {
|
|
45
|
+
/** List of canisters to generate hooks for */
|
|
46
|
+
canisters: (CanisterConfig & { name: string; advanced?: boolean })[]
|
|
47
|
+
/** Base output directory (default: ./src/canisters) */
|
|
48
|
+
outDir?: string
|
|
49
|
+
/**
|
|
50
|
+
* Path to import ClientManager from (relative to generated file).
|
|
51
|
+
* Default: "../../lib/client"
|
|
52
|
+
*/
|
|
53
|
+
clientManagerPath?: string
|
|
54
|
+
/**
|
|
55
|
+
* Generate advanced per-method hooks with createQuery/createMutation
|
|
56
|
+
* instead of generic actor hooks (default: false).
|
|
57
|
+
* Can be overridden per-canister by setting `advanced` on the individual canister entry.
|
|
58
|
+
*/
|
|
59
|
+
advanced?: boolean
|
|
60
|
+
/**
|
|
61
|
+
* Automatically set the `ic_env` cookie in Vite dev server from
|
|
62
|
+
* `.icp/cache/mappings/local.ids.json` (default: true).
|
|
63
|
+
*/
|
|
64
|
+
autoInjectIcEnv?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Re-export CanisterConfig for convenience
|
|
68
|
+
export type { CanisterConfig }
|
|
69
|
+
|
|
70
|
+
function loadLocalCanisterIds(rootDir: string): Record<string, string> | null {
|
|
71
|
+
const idsPath = path.resolve(rootDir, ICP_LOCAL_IDS_PATH)
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(fs.readFileSync(idsPath, "utf-8"))
|
|
75
|
+
} catch {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildIcEnvCookie(canisterIds: Record<string, string>): string {
|
|
81
|
+
const envParts = [`ic_root_key=${IC_ROOT_KEY_HEX}`]
|
|
82
|
+
|
|
83
|
+
for (const [name, id] of Object.entries(canisterIds)) {
|
|
84
|
+
envParts.push(`PUBLIC_CANISTER_ID:${name}=${id}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return encodeURIComponent(envParts.join("&"))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function addOrReplaceSetCookie(
|
|
91
|
+
existing: string | string[] | number | undefined,
|
|
92
|
+
cookie: string
|
|
93
|
+
): string[] {
|
|
94
|
+
const cookieEntries =
|
|
95
|
+
typeof existing === "string"
|
|
96
|
+
? [existing]
|
|
97
|
+
: Array.isArray(existing)
|
|
98
|
+
? existing.filter((value): value is string => typeof value === "string")
|
|
99
|
+
: []
|
|
100
|
+
|
|
101
|
+
const nonIcEnvCookies = cookieEntries.filter(
|
|
102
|
+
(entry) => !entry.trim().startsWith("ic_env=")
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return [...nonIcEnvCookies, cookie]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function setupIcEnvMiddleware(server: ViteDevServer): void {
|
|
109
|
+
const rootDir = server.config.root || process.cwd()
|
|
110
|
+
const idsPath = path.resolve(rootDir, ICP_LOCAL_IDS_PATH)
|
|
111
|
+
let hasLoggedHint = false
|
|
112
|
+
|
|
113
|
+
server.middlewares.use((req, res, next) => {
|
|
114
|
+
const canisterIds = loadLocalCanisterIds(rootDir)
|
|
115
|
+
|
|
116
|
+
if (!canisterIds) {
|
|
117
|
+
if (!hasLoggedHint) {
|
|
118
|
+
server.config.logger.info(
|
|
119
|
+
`[ic-reactor] icp-cli local IDs not found at ${idsPath}. Run \`icp deploy\` to enable automatic ic_env cookie injection.`
|
|
120
|
+
)
|
|
121
|
+
hasLoggedHint = true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return next()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const cookie = `ic_env=${buildIcEnvCookie(canisterIds)}; Path=/; SameSite=Lax;`
|
|
128
|
+
const current = res.getHeader("Set-Cookie")
|
|
129
|
+
res.setHeader("Set-Cookie", addOrReplaceSetCookie(current, cookie))
|
|
130
|
+
|
|
131
|
+
next()
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
136
|
+
// VITE PLUGIN
|
|
137
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
138
|
+
|
|
139
|
+
export function icReactorPlugin(options: IcReactorPluginOptions): Plugin {
|
|
140
|
+
const baseOutDir = options.outDir ?? "./src/canisters"
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
name: "ic-reactor-plugin",
|
|
144
|
+
|
|
145
|
+
configureServer(server) {
|
|
146
|
+
if (options.autoInjectIcEnv ?? true) {
|
|
147
|
+
setupIcEnvMiddleware(server)
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
async buildStart() {
|
|
152
|
+
for (const canister of options.canisters) {
|
|
153
|
+
const outDir = canister.outDir ?? path.join(baseOutDir, canister.name)
|
|
154
|
+
|
|
155
|
+
console.log(
|
|
156
|
+
`[ic-reactor] Generating hooks for ${canister.name} from ${canister.didFile}`
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
// Step 1: Generate declarations via @icp-sdk/bindgen
|
|
160
|
+
const result = await generateDeclarations({
|
|
161
|
+
didFile: canister.didFile,
|
|
162
|
+
outDir,
|
|
163
|
+
canisterName: canister.name,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
if (!result.success) {
|
|
167
|
+
console.error(
|
|
168
|
+
`[ic-reactor] Failed to generate declarations: ${result.error}`
|
|
169
|
+
)
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(
|
|
174
|
+
`[ic-reactor] Declarations generated at ${result.declarationsDir}`
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
// Step 2: Determine advanced mode (can be set per-canister; falls back to plugin-level)
|
|
178
|
+
const useAdvanced = canister.advanced ?? options.advanced ?? false
|
|
179
|
+
let didContent: string | undefined
|
|
180
|
+
if (useAdvanced) {
|
|
181
|
+
try {
|
|
182
|
+
didContent = fs.readFileSync(canister.didFile, "utf-8")
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.warn(
|
|
185
|
+
`[ic-reactor] Could not read DID file at ${canister.didFile}, skipping advanced hook generation for ${canister.name}.`
|
|
186
|
+
)
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Step 3: Generate the reactor file using shared codegen
|
|
192
|
+
const reactorContent = generateReactorFile({
|
|
193
|
+
canisterName: canister.name,
|
|
194
|
+
canisterConfig: canister,
|
|
195
|
+
globalClientManagerPath: options.clientManagerPath,
|
|
196
|
+
hasDeclarations: true,
|
|
197
|
+
advanced: useAdvanced,
|
|
198
|
+
didContent,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const reactorPath = path.join(outDir, "index.ts")
|
|
202
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
203
|
+
fs.writeFileSync(reactorPath, reactorContent)
|
|
204
|
+
|
|
205
|
+
console.log(`[ic-reactor] Reactor hooks generated at ${reactorPath}`)
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
handleHotUpdate({ file, server }) {
|
|
210
|
+
// Watch for .did file changes and regenerate
|
|
211
|
+
if (file.endsWith(".did")) {
|
|
212
|
+
const canister = options.canisters.find(
|
|
213
|
+
(c) => path.resolve(c.didFile) === file
|
|
214
|
+
)
|
|
215
|
+
if (canister) {
|
|
216
|
+
console.log(
|
|
217
|
+
`[ic-reactor] Detected change in ${file}, regenerating...`
|
|
218
|
+
)
|
|
219
|
+
server.restart()
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export default icReactorPlugin
|