@ic-reactor/codegen 0.12.0 → 0.13.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.
@@ -1,355 +0,0 @@
1
- import fs from "node:fs"
2
- import os from "node:os"
3
- import path from "node:path"
4
- import { afterEach, describe, expect, it } from "vitest"
5
- import { runCanisterPipeline } from "./pipeline.js"
6
-
7
- describe("Codegen pipeline", () => {
8
- const tempDirs: string[] = []
9
-
10
- afterEach(() => {
11
- for (const dir of tempDirs) {
12
- fs.rmSync(dir, { recursive: true, force: true })
13
- }
14
- tempDirs.length = 0
15
- })
16
-
17
- function createTempProject() {
18
- const projectRoot = fs.mkdtempSync(
19
- path.join(os.tmpdir(), "ic-reactor-codegen-pipeline-")
20
- )
21
- tempDirs.push(projectRoot)
22
- return projectRoot
23
- }
24
-
25
- function writeDid(projectRoot: string, fileName: string) {
26
- fs.writeFileSync(
27
- path.join(projectRoot, fileName),
28
- `service : {
29
- greet: (text) -> (text) query;
30
- }`
31
- )
32
- }
33
-
34
- it("uses per-canister mode to generate Reactor-based hooks", async () => {
35
- const projectRoot = createTempProject()
36
- writeDid(projectRoot, "workflow_engine.did")
37
-
38
- const result = await runCanisterPipeline({
39
- canisterConfig: {
40
- name: "workflow_engine",
41
- didFile: "workflow_engine.did",
42
- mode: "Reactor",
43
- },
44
- projectRoot,
45
- globalConfig: {
46
- outDir: "src/declarations",
47
- clientManagerPath: "../../clients",
48
- },
49
- })
50
-
51
- expect(result.success).toBe(true)
52
-
53
- const indexPath = path.join(
54
- projectRoot,
55
- "src/declarations/workflow_engine/index.generated.ts"
56
- )
57
- const generated = fs.readFileSync(indexPath, "utf-8")
58
-
59
- expect(generated).toContain("new Reactor<WorkflowEngineService>")
60
- expect(generated).not.toContain("new DisplayReactor<WorkflowEngineService>")
61
- expect(
62
- fs.readFileSync(
63
- path.join(projectRoot, "src/declarations/workflow_engine/index.ts"),
64
- "utf-8"
65
- )
66
- ).toContain('export * from "./index.generated"')
67
- })
68
-
69
- it("writes a configured canisterId into the generated reactor", async () => {
70
- const projectRoot = createTempProject()
71
- writeDid(projectRoot, "workflow.did")
72
-
73
- const result = await runCanisterPipeline({
74
- canisterConfig: {
75
- name: "workflow",
76
- didFile: "workflow.did",
77
- canisterId: "yq4ns-hyaaa-aaaap-akbna-cai",
78
- },
79
- projectRoot,
80
- globalConfig: {
81
- outDir: "src/declarations",
82
- clientManagerPath: "../../clients",
83
- },
84
- })
85
-
86
- expect(result.success).toBe(true)
87
-
88
- const indexPath = path.join(
89
- projectRoot,
90
- "src/declarations/workflow/index.generated.ts"
91
- )
92
- const generated = fs.readFileSync(indexPath, "utf-8")
93
-
94
- expect(generated).toContain('canisterId: "yq4ns-hyaaa-aaaap-akbna-cai"')
95
- expect(generated).toContain('name: "workflow"')
96
- })
97
-
98
- it("can generate declarations without creating reactor files", async () => {
99
- const projectRoot = createTempProject()
100
- writeDid(projectRoot, "backend.did")
101
-
102
- const result = await runCanisterPipeline({
103
- canisterConfig: {
104
- name: "backend",
105
- didFile: "backend.did",
106
- },
107
- projectRoot,
108
- globalConfig: {
109
- outDir: "src/declarations",
110
- clientManagerPath: "../../clients",
111
- },
112
- generateReactor: false,
113
- })
114
-
115
- expect(result.success).toBe(true)
116
- expect(
117
- fs.existsSync(
118
- path.join(
119
- projectRoot,
120
- "src/declarations/backend/declarations/backend.js"
121
- )
122
- )
123
- ).toBe(true)
124
- expect(
125
- fs.existsSync(
126
- path.join(
127
- projectRoot,
128
- "src/declarations/backend/declarations/backend.d.ts"
129
- )
130
- )
131
- ).toBe(true)
132
- expect(
133
- fs.existsSync(
134
- path.join(
135
- projectRoot,
136
- "src/declarations/backend/declarations/backend.did"
137
- )
138
- )
139
- ).toBe(true)
140
- expect(
141
- fs.existsSync(
142
- path.join(projectRoot, "src/declarations/backend/index.generated.ts")
143
- )
144
- ).toBe(false)
145
- expect(
146
- fs.existsSync(path.join(projectRoot, "src/declarations/backend/index.ts"))
147
- ).toBe(false)
148
- expect(
149
- result.files.some((file) => file.filePath.endsWith("index.generated.ts"))
150
- ).toBe(false)
151
- expect(
152
- result.files.some((file) => file.filePath.endsWith("index.ts"))
153
- ).toBe(false)
154
- })
155
-
156
- it("leaves existing reactor files untouched when reactor generation is disabled", async () => {
157
- const projectRoot = createTempProject()
158
- writeDid(projectRoot, "backend.did")
159
-
160
- const canisterOutDir = path.join(projectRoot, "src/declarations/backend")
161
- fs.mkdirSync(canisterOutDir, { recursive: true })
162
-
163
- const existingGenerated = "// existing generated reactor"
164
- const existingEntry = "// existing entry wrapper"
165
-
166
- fs.writeFileSync(
167
- path.join(canisterOutDir, "index.generated.ts"),
168
- existingGenerated
169
- )
170
- fs.writeFileSync(path.join(canisterOutDir, "index.ts"), existingEntry)
171
-
172
- const result = await runCanisterPipeline({
173
- canisterConfig: {
174
- name: "backend",
175
- didFile: "backend.did",
176
- },
177
- projectRoot,
178
- globalConfig: {
179
- outDir: "src/declarations",
180
- clientManagerPath: "../../clients",
181
- },
182
- generateReactor: false,
183
- })
184
-
185
- expect(result.success).toBe(true)
186
- expect(
187
- fs.readFileSync(path.join(canisterOutDir, "index.generated.ts"), "utf-8")
188
- ).toBe(existingGenerated)
189
- expect(
190
- fs.readFileSync(path.join(canisterOutDir, "index.ts"), "utf-8")
191
- ).toBe(existingEntry)
192
- })
193
-
194
- it("supports a core target without generating React hooks", async () => {
195
- const projectRoot = createTempProject()
196
- writeDid(projectRoot, "backend.did")
197
-
198
- const result = await runCanisterPipeline({
199
- canisterConfig: {
200
- name: "backend",
201
- didFile: "backend.did",
202
- },
203
- projectRoot,
204
- globalConfig: {
205
- outDir: "src/declarations",
206
- clientManagerPath: "../../clients",
207
- target: "core",
208
- },
209
- })
210
-
211
- expect(result.success).toBe(true)
212
-
213
- const indexPath = path.join(
214
- projectRoot,
215
- "src/declarations/backend/index.generated.ts"
216
- )
217
- const generated = fs.readFileSync(indexPath, "utf-8")
218
-
219
- expect(generated).toContain(
220
- 'import { DisplayReactor } from "@ic-reactor/core"'
221
- )
222
- expect(generated).not.toContain(
223
- 'import { createActorHooks } from "@ic-reactor/react"'
224
- )
225
- expect(generated).not.toContain("useBackendQuery")
226
- })
227
-
228
- it("does not overwrite user-modified index.ts on regenerate", async () => {
229
- const projectRoot = createTempProject()
230
- writeDid(projectRoot, "backend.did")
231
-
232
- const options = {
233
- canisterConfig: {
234
- name: "backend",
235
- didFile: "backend.did",
236
- },
237
- projectRoot,
238
- globalConfig: {
239
- outDir: "src/declarations",
240
- clientManagerPath: "../../clients",
241
- },
242
- } as const
243
-
244
- const first = await runCanisterPipeline(options)
245
- expect(first.success).toBe(true)
246
-
247
- const indexPath = path.join(
248
- projectRoot,
249
- "src/declarations/backend/index.ts"
250
- )
251
- fs.writeFileSync(
252
- indexPath,
253
- `// user custom canister file
254
- export const customBackendIndex = true
255
- `
256
- )
257
-
258
- const second = await runCanisterPipeline({
259
- ...options,
260
- canisterConfig: {
261
- ...options.canisterConfig,
262
- canisterId: "yq4ns-hyaaa-aaaap-akbna-cai",
263
- },
264
- })
265
- expect(second.success).toBe(true)
266
-
267
- const wrapper = fs.readFileSync(indexPath, "utf-8")
268
- expect(wrapper).toContain("customBackendIndex = true")
269
- const generatedImpl = fs.readFileSync(
270
- path.join(projectRoot, "src/declarations/backend/index.generated.ts"),
271
- "utf-8"
272
- )
273
- expect(generatedImpl).toContain('canisterId: "yq4ns-hyaaa-aaaap-akbna-cai"')
274
- expect(second.files).toEqual(
275
- expect.arrayContaining([
276
- expect.objectContaining({
277
- filePath: indexPath,
278
- skipped: true,
279
- success: true,
280
- }),
281
- ])
282
- )
283
- })
284
-
285
- it("migrates a legacy generated index.ts to the managed wrapper", async () => {
286
- const projectRoot = createTempProject()
287
- writeDid(projectRoot, "backend.did")
288
-
289
- const canisterOutDir = path.join(projectRoot, "src/declarations/backend")
290
- fs.mkdirSync(canisterOutDir, { recursive: true })
291
-
292
- const existingGeneratedIndex = `import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
293
- import { clientManager } from "../../clients"
294
- import { idlFactory, type _SERVICE } from "./declarations/backend"
295
-
296
- export type BackendService = _SERVICE
297
-
298
- /**
299
- * Backend Reactor
300
- *
301
- * Auto-generated by @ic-reactor/codegen — do not edit.
302
- */
303
- export const backendReactor = new DisplayReactor<BackendService>({
304
- clientManager,
305
- idlFactory,
306
- name: "backend",
307
- })
308
-
309
- export const {
310
- useActorQuery: useBackendQuery,
311
- } = createActorHooks(backendReactor)
312
- `
313
- fs.writeFileSync(
314
- path.join(canisterOutDir, "index.ts"),
315
- existingGeneratedIndex
316
- )
317
-
318
- const result = await runCanisterPipeline({
319
- canisterConfig: {
320
- name: "backend",
321
- didFile: "backend.did",
322
- },
323
- projectRoot,
324
- globalConfig: {
325
- outDir: "src/declarations",
326
- clientManagerPath: "../../clients",
327
- },
328
- })
329
-
330
- expect(result.success).toBe(true)
331
-
332
- const indexPath = path.join(canisterOutDir, "index.ts")
333
- const entry = fs.readFileSync(indexPath, "utf-8")
334
- const generated = fs.readFileSync(
335
- path.join(canisterOutDir, "index.generated.ts"),
336
- "utf-8"
337
- )
338
- expect(entry).toContain('export * from "./index.generated"')
339
- expect(entry).not.toBe(existingGeneratedIndex)
340
- expect(generated).toContain("new DisplayReactor<BackendService>")
341
- expect(generated).toContain("useBackendMutation")
342
- expect(result.files).toEqual(
343
- expect.arrayContaining([
344
- expect.objectContaining({
345
- filePath: path.join(canisterOutDir, "index.generated.ts"),
346
- success: true,
347
- }),
348
- expect.objectContaining({
349
- filePath: indexPath,
350
- success: true,
351
- }),
352
- ])
353
- )
354
- })
355
- })
@@ -1,84 +0,0 @@
1
- import { describe, expect, it } from "vitest"
2
- import {
3
- generateReactorEntryFile,
4
- generateReactorFile,
5
- } from "./generators/index.js"
6
-
7
- describe("Reactor generator", () => {
8
- it("keeps default behavior as DisplayReactor", () => {
9
- const content = generateReactorFile({
10
- canisterName: "backend",
11
- didFile: "mock/backend.did",
12
- clientManagerPath: "../../clients",
13
- })
14
-
15
- expect(content).toMatchSnapshot("display-reactor-index")
16
- expect(content).toContain("new DisplayReactor<BackendService>")
17
- expect(content).not.toContain("export const BackendReactorMode")
18
- })
19
-
20
- it("supports Reactor mode generation", () => {
21
- const content = generateReactorFile({
22
- canisterName: "workflow_engine",
23
- didFile: "mock/workflow_engine.did",
24
- reactorClass: "Reactor",
25
- })
26
-
27
- expect(content).toMatchSnapshot("reactor-index")
28
- expect(content).toContain("new Reactor<WorkflowEngineService>")
29
- expect(content).not.toContain("createWorkflowEngineDisplayReactor")
30
- })
31
-
32
- it("supports candid reactor subclasses", () => {
33
- const content = generateReactorFile({
34
- canisterName: "ledger",
35
- didFile: "mock/ledger.did",
36
- reactorClass: "MetadataDisplayReactor",
37
- })
38
-
39
- expect(content).toMatchSnapshot("metadata-display-reactor-index")
40
- expect(content).toContain(
41
- 'import { createActorHooks } from "@ic-reactor/react"'
42
- )
43
- expect(content).toContain(
44
- 'import { MetadataDisplayReactor } from "@ic-reactor/candid"'
45
- )
46
- expect(content).toContain("new MetadataDisplayReactor<LedgerService>")
47
- })
48
-
49
- it("supports core target generation without React hooks", () => {
50
- const content = generateReactorFile({
51
- canisterName: "backend",
52
- didFile: "mock/backend.did",
53
- runtimeTarget: "core",
54
- reactorClass: "DisplayReactor",
55
- })
56
-
57
- expect(content).toMatchSnapshot("core-display-reactor-index")
58
- expect(content).toContain(
59
- 'import { DisplayReactor } from "@ic-reactor/core"'
60
- )
61
- expect(content).not.toContain(
62
- 'import { createActorHooks } from "@ic-reactor/react"'
63
- )
64
- expect(content).not.toContain("useBackendQuery")
65
- })
66
-
67
- it("writes a fixed canisterId when configured", () => {
68
- const content = generateReactorFile({
69
- canisterName: "workflow",
70
- didFile: "mock/workflow.did",
71
- canisterId: "yq4ns-hyaaa-aaaap-akbna-cai",
72
- })
73
-
74
- expect(content).toContain('canisterId: "yq4ns-hyaaa-aaaap-akbna-cai"')
75
- expect(content).toContain('name: "workflow"')
76
- })
77
-
78
- it("generates a stable entry wrapper", () => {
79
- const content = generateReactorEntryFile()
80
-
81
- expect(content).toContain('export * from "./index.generated"')
82
- expect(content).toContain("safe to customize")
83
- })
84
- })