@ic-reactor/codegen 0.12.1 → 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.
- package/README.md +14 -3
- package/dist/index.cjs +316 -201
- package/dist/index.d.cts +20 -36
- package/dist/index.d.ts +20 -36
- package/dist/index.js +315 -199
- package/package.json +7 -3
- package/src/generators/declarations.ts +164 -21
- package/src/generators/reactor.ts +5 -3
- package/src/index.ts +1 -3
- package/src/pipeline.ts +155 -10
- package/src/validate.ts +38 -0
- package/src/__snapshots__/bindgen.test.ts.snap +0 -18
- package/src/__snapshots__/reactor.test.ts.snap +0 -127
- package/src/bindgen.test.ts +0 -85
- package/src/naming.test.ts +0 -45
- package/src/parser.ts +0 -79
- package/src/pipeline.test.ts +0 -355
- package/src/reactor.test.ts +0 -84
- package/src/validate.test.ts +0 -393
package/src/bindgen.test.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
2
|
-
import fs from "node:fs"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
import { generateDeclarations } from "./generators/index.js"
|
|
5
|
-
|
|
6
|
-
describe("Bindgen", () => {
|
|
7
|
-
const mockDidFile = "mock/test.did"
|
|
8
|
-
const mockOutDir = "mock/output"
|
|
9
|
-
const mockCanisterName = "test_canister"
|
|
10
|
-
const validDidContent = `service : {
|
|
11
|
-
greet: (text) -> (text) query;
|
|
12
|
-
}`
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
// Mock fs methods
|
|
16
|
-
vi.spyOn(fs, "existsSync").mockImplementation((p) => {
|
|
17
|
-
// Pretend the source DID file exists
|
|
18
|
-
if (p.toString() === mockDidFile) return true
|
|
19
|
-
return false
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined as any)
|
|
23
|
-
vi.spyOn(fs, "rmSync").mockImplementation(() => undefined)
|
|
24
|
-
vi.spyOn(fs, "readFileSync").mockImplementation(() => validDidContent)
|
|
25
|
-
vi.spyOn(fs, "writeFileSync").mockImplementation(() => undefined)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
vi.restoreAllMocks()
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("generateDeclarations creates correct files", async () => {
|
|
33
|
-
const result = await generateDeclarations({
|
|
34
|
-
didFile: mockDidFile,
|
|
35
|
-
outDir: mockOutDir,
|
|
36
|
-
canisterName: mockCanisterName,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
if (!result.success) {
|
|
40
|
-
console.error(result.error)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
expect(result.success).toBe(true)
|
|
44
|
-
expect(result.declarationsDir).toBe(path.join(mockOutDir, "declarations"))
|
|
45
|
-
|
|
46
|
-
// Verify file writes
|
|
47
|
-
const declarationsDir = path.join(mockOutDir, "declarations")
|
|
48
|
-
const jsPath = path.join(declarationsDir, "test.js")
|
|
49
|
-
const dtsPath = path.join(declarationsDir, "test.d.ts")
|
|
50
|
-
|
|
51
|
-
// Should create directory
|
|
52
|
-
expect(fs.mkdirSync).toHaveBeenCalledWith(mockOutDir, { recursive: true })
|
|
53
|
-
expect(fs.mkdirSync).toHaveBeenCalledWith(declarationsDir, {
|
|
54
|
-
recursive: true,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
// Validate exact content using snapshots
|
|
58
|
-
expect(fs.writeFileSync).toHaveBeenCalledWith(jsPath, expect.any(String))
|
|
59
|
-
expect(fs.writeFileSync).toHaveBeenCalledWith(dtsPath, expect.any(String))
|
|
60
|
-
|
|
61
|
-
// Get the arguments of the calls to check content
|
|
62
|
-
const jsCall = vi
|
|
63
|
-
.mocked(fs.writeFileSync)
|
|
64
|
-
.mock.calls.find((call) => call[0] === jsPath)
|
|
65
|
-
const dtsCall = vi
|
|
66
|
-
.mocked(fs.writeFileSync)
|
|
67
|
-
.mock.calls.find((call) => call[0] === dtsPath)
|
|
68
|
-
|
|
69
|
-
expect(jsCall?.[1]).toMatchSnapshot("js-declarations")
|
|
70
|
-
expect(dtsCall?.[1]).toMatchSnapshot("ts-declarations")
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it("returns error if DID file missing", async () => {
|
|
74
|
-
vi.spyOn(fs, "existsSync").mockReturnValue(false)
|
|
75
|
-
|
|
76
|
-
const result = await generateDeclarations({
|
|
77
|
-
didFile: "missing.did",
|
|
78
|
-
outDir: mockOutDir,
|
|
79
|
-
canisterName: mockCanisterName,
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
expect(result.success).toBe(false)
|
|
83
|
-
expect(result.error).toContain("DID file not found")
|
|
84
|
-
})
|
|
85
|
-
})
|
package/src/naming.test.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
|
2
|
-
import {
|
|
3
|
-
toPascalCase,
|
|
4
|
-
toCamelCase,
|
|
5
|
-
getReactorName,
|
|
6
|
-
getServiceTypeName,
|
|
7
|
-
} from "./naming.js"
|
|
8
|
-
|
|
9
|
-
describe("Naming Utilities", () => {
|
|
10
|
-
describe("Base Conversions", () => {
|
|
11
|
-
it("converts to PascalCase", () => {
|
|
12
|
-
expect(toPascalCase("get_user")).toBe("GetUser")
|
|
13
|
-
expect(toPascalCase("my-canister")).toBe("MyCanister")
|
|
14
|
-
expect(toPascalCase("list_items_v2")).toBe("ListItemsV2")
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it("converts to camelCase", () => {
|
|
18
|
-
expect(toCamelCase("get_user")).toBe("getUser")
|
|
19
|
-
expect(toCamelCase("my-canister")).toBe("myCanister")
|
|
20
|
-
expect(toCamelCase("ListItems")).toBe("listItems")
|
|
21
|
-
})
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
describe("Domain-Specific Naming", () => {
|
|
25
|
-
it("generates reactor names", () => {
|
|
26
|
-
expect(getReactorName("backend")).toBe("backendReactor")
|
|
27
|
-
expect(getReactorName("my-canister")).toBe("myCanisterReactor")
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it("generates service type names", () => {
|
|
31
|
-
expect(getServiceTypeName("backend")).toBe("BackendService")
|
|
32
|
-
expect(getServiceTypeName("update_item")).toBe("UpdateItemService")
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it("generates reactor names", () => {
|
|
36
|
-
expect(getReactorName("backend")).toBe("backendReactor")
|
|
37
|
-
expect(getReactorName("my-canister")).toBe("myCanisterReactor")
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it("generates service type names", () => {
|
|
41
|
-
expect(getServiceTypeName("backend")).toBe("BackendService")
|
|
42
|
-
expect(getServiceTypeName("my-canister")).toBe("MyCanisterService")
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
})
|
package/src/parser.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Candid Parser Utilities
|
|
3
|
-
*
|
|
4
|
-
* Parses Candid .did files to extract service method signatures.
|
|
5
|
-
* Used by the CLI for listing methods and by advanced generators.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { didToJs } from "@ic-reactor/parser"
|
|
9
|
-
import fs from "node:fs"
|
|
10
|
-
|
|
11
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
-
// TYPES
|
|
13
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
export type MethodType = "query" | "mutation"
|
|
16
|
-
|
|
17
|
-
export interface MethodInfo {
|
|
18
|
-
/** Method name as it appears in the Candid service */
|
|
19
|
-
name: string
|
|
20
|
-
/** "query" for read-only calls, "mutation" for update calls */
|
|
21
|
-
type: MethodType
|
|
22
|
-
/** True if the method takes at least one argument */
|
|
23
|
-
hasArgs: boolean
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
-
// PARSERS
|
|
28
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Extract method information from raw Candid source text.
|
|
32
|
-
*
|
|
33
|
-
* Compiles the Candid to JS and inspects the IDL.Service definition.
|
|
34
|
-
*/
|
|
35
|
-
export function extractMethods(didContent: string): MethodInfo[] {
|
|
36
|
-
try {
|
|
37
|
-
const jsContent = didToJs(didContent)
|
|
38
|
-
|
|
39
|
-
const methods: MethodInfo[] = []
|
|
40
|
-
|
|
41
|
-
// Find the IDL.Service({...}) body
|
|
42
|
-
const serviceMatch = /IDL\.Service\(\{([\s\S]*?)\}\)/.exec(jsContent)
|
|
43
|
-
if (!serviceMatch) return methods
|
|
44
|
-
|
|
45
|
-
const serviceBody = serviceMatch[1]
|
|
46
|
-
|
|
47
|
-
// Match each method: 'methodName': IDL.Func([args], [rets], [annotations])
|
|
48
|
-
const methodRegex =
|
|
49
|
-
/['"]([\w]+)['"]\s*:\s*IDL\.Func\(\s*\[(.*?)\],\s*\[.*?\],\s*\[(.*?)\]\)/g
|
|
50
|
-
|
|
51
|
-
let match: RegExpExecArray | null
|
|
52
|
-
while ((match = methodRegex.exec(serviceBody)) !== null) {
|
|
53
|
-
const [, name, args, annotations] = match
|
|
54
|
-
methods.push({
|
|
55
|
-
name,
|
|
56
|
-
type: annotations.includes("'query'") ? "query" : "mutation",
|
|
57
|
-
hasArgs: args.trim().length > 0,
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return methods
|
|
62
|
-
} catch (error) {
|
|
63
|
-
const msg = error instanceof Error ? error.message : String(error)
|
|
64
|
-
throw new Error(`Failed to parse Candid: ${msg}`)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Parse a .did file from disk and return its methods.
|
|
70
|
-
*
|
|
71
|
-
* @throws if the file does not exist or fails to parse
|
|
72
|
-
*/
|
|
73
|
-
export function parseDIDFile(didFilePath: string): MethodInfo[] {
|
|
74
|
-
if (!fs.existsSync(didFilePath)) {
|
|
75
|
-
throw new Error(`DID file not found: ${didFilePath}`)
|
|
76
|
-
}
|
|
77
|
-
const content = fs.readFileSync(didFilePath, "utf-8")
|
|
78
|
-
return extractMethods(content)
|
|
79
|
-
}
|
package/src/pipeline.test.ts
DELETED
|
@@ -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
|
-
})
|
package/src/reactor.test.ts
DELETED
|
@@ -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
|
-
})
|