@ic-reactor/vite-plugin 0.2.0 → 0.3.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 +28 -147
- package/dist/index.cjs +114 -66
- package/dist/index.d.cts +15 -19
- package/dist/index.d.ts +15 -19
- package/dist/index.js +116 -67
- package/package.json +7 -5
- package/src/index.test.ts +273 -0
- package/src/index.ts +148 -107
package/dist/index.js
CHANGED
|
@@ -1,72 +1,140 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
4
5
|
import {
|
|
5
6
|
generateDeclarations,
|
|
6
|
-
generateReactorFile
|
|
7
|
+
generateReactorFile,
|
|
8
|
+
generateClientFile
|
|
7
9
|
} from "@ic-reactor/codegen";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function loadLocalCanisterIds(rootDir) {
|
|
11
|
-
const idsPath = path.resolve(rootDir, ICP_LOCAL_IDS_PATH);
|
|
10
|
+
function getIcEnvironmentInfo(canisterNames) {
|
|
11
|
+
const environment = process.env.ICP_ENVIRONMENT || "local";
|
|
12
12
|
try {
|
|
13
|
-
|
|
13
|
+
const networkStatus = JSON.parse(
|
|
14
|
+
execFileSync("icp", ["network", "status", "-e", environment, "--json"], {
|
|
15
|
+
encoding: "utf-8"
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
const rootKey = networkStatus.root_key;
|
|
19
|
+
const proxyTarget = `http://127.0.0.1:${networkStatus.port}`;
|
|
20
|
+
const canisterIds = {};
|
|
21
|
+
for (const name of canisterNames) {
|
|
22
|
+
try {
|
|
23
|
+
const canisterId = execFileSync(
|
|
24
|
+
"icp",
|
|
25
|
+
["canister", "status", name, "-e", environment, "-i"],
|
|
26
|
+
{
|
|
27
|
+
encoding: "utf-8"
|
|
28
|
+
}
|
|
29
|
+
).trim();
|
|
30
|
+
canisterIds[name] = canisterId;
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { environment, rootKey, proxyTarget, canisterIds };
|
|
14
35
|
} catch {
|
|
15
36
|
return null;
|
|
16
37
|
}
|
|
17
38
|
}
|
|
18
|
-
function buildIcEnvCookie(canisterIds) {
|
|
19
|
-
const envParts = [`ic_root_key=${
|
|
39
|
+
function buildIcEnvCookie(canisterIds, rootKey) {
|
|
40
|
+
const envParts = [`ic_root_key=${rootKey}`];
|
|
20
41
|
for (const [name, id] of Object.entries(canisterIds)) {
|
|
21
42
|
envParts.push(`PUBLIC_CANISTER_ID:${name}=${id}`);
|
|
22
43
|
}
|
|
23
44
|
return encodeURIComponent(envParts.join("&"));
|
|
24
45
|
}
|
|
25
|
-
function addOrReplaceSetCookie(existing, cookie) {
|
|
26
|
-
const cookieEntries = typeof existing === "string" ? [existing] : Array.isArray(existing) ? existing.filter((value) => typeof value === "string") : [];
|
|
27
|
-
const nonIcEnvCookies = cookieEntries.filter(
|
|
28
|
-
(entry) => !entry.trim().startsWith("ic_env=")
|
|
29
|
-
);
|
|
30
|
-
return [...nonIcEnvCookies, cookie];
|
|
31
|
-
}
|
|
32
|
-
function setupIcEnvMiddleware(server) {
|
|
33
|
-
const rootDir = server.config.root || process.cwd();
|
|
34
|
-
const idsPath = path.resolve(rootDir, ICP_LOCAL_IDS_PATH);
|
|
35
|
-
let hasLoggedHint = false;
|
|
36
|
-
server.middlewares.use((req, res, next) => {
|
|
37
|
-
const canisterIds = loadLocalCanisterIds(rootDir);
|
|
38
|
-
if (!canisterIds) {
|
|
39
|
-
if (!hasLoggedHint) {
|
|
40
|
-
server.config.logger.info(
|
|
41
|
-
`[ic-reactor] icp-cli local IDs not found at ${idsPath}. Run \`icp deploy\` to enable automatic ic_env cookie injection.`
|
|
42
|
-
);
|
|
43
|
-
hasLoggedHint = true;
|
|
44
|
-
}
|
|
45
|
-
return next();
|
|
46
|
-
}
|
|
47
|
-
const cookie = `ic_env=${buildIcEnvCookie(canisterIds)}; Path=/; SameSite=Lax;`;
|
|
48
|
-
const current = res.getHeader("Set-Cookie");
|
|
49
|
-
res.setHeader("Set-Cookie", addOrReplaceSetCookie(current, cookie));
|
|
50
|
-
next();
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
46
|
function icReactorPlugin(options) {
|
|
54
|
-
const baseOutDir = options.outDir ?? "./src/canisters";
|
|
47
|
+
const baseOutDir = options.outDir ?? "./src/lib/canisters";
|
|
55
48
|
return {
|
|
56
49
|
name: "ic-reactor-plugin",
|
|
57
|
-
|
|
58
|
-
if (options.
|
|
59
|
-
|
|
50
|
+
config(_config, { command }) {
|
|
51
|
+
if (command !== "serve" || !(options.injectEnvironment ?? true)) {
|
|
52
|
+
return {};
|
|
60
53
|
}
|
|
54
|
+
const canisterNames = options.canisters.map((c) => c.name);
|
|
55
|
+
const icEnv = getIcEnvironmentInfo(canisterNames);
|
|
56
|
+
if (!icEnv) {
|
|
57
|
+
return {
|
|
58
|
+
server: {
|
|
59
|
+
proxy: {
|
|
60
|
+
"/api": {
|
|
61
|
+
target: "http://127.0.0.1:4943",
|
|
62
|
+
changeOrigin: true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const cookieValue = buildIcEnvCookie(icEnv.canisterIds, icEnv.rootKey);
|
|
69
|
+
return {
|
|
70
|
+
server: {
|
|
71
|
+
headers: {
|
|
72
|
+
"Set-Cookie": `ic_env=${cookieValue}; Path=/; SameSite=Lax;`
|
|
73
|
+
},
|
|
74
|
+
proxy: {
|
|
75
|
+
"/api": {
|
|
76
|
+
target: icEnv.proxyTarget,
|
|
77
|
+
changeOrigin: true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
61
82
|
},
|
|
62
83
|
async buildStart() {
|
|
84
|
+
const defaultClientPath = path.resolve(
|
|
85
|
+
process.cwd(),
|
|
86
|
+
"src/lib/clients.ts"
|
|
87
|
+
);
|
|
88
|
+
if (!fs.existsSync(defaultClientPath)) {
|
|
89
|
+
console.log(
|
|
90
|
+
`[ic-reactor] Default client manager not found. Creating at ${defaultClientPath}`
|
|
91
|
+
);
|
|
92
|
+
const clientContent = generateClientFile();
|
|
93
|
+
fs.mkdirSync(path.dirname(defaultClientPath), { recursive: true });
|
|
94
|
+
fs.writeFileSync(defaultClientPath, clientContent);
|
|
95
|
+
}
|
|
63
96
|
for (const canister of options.canisters) {
|
|
97
|
+
let didFile = canister.didFile;
|
|
64
98
|
const outDir = canister.outDir ?? path.join(baseOutDir, canister.name);
|
|
99
|
+
if (!didFile) {
|
|
100
|
+
const environment = process.env.ICP_ENVIRONMENT || "local";
|
|
101
|
+
console.log(
|
|
102
|
+
`[ic-reactor] didFile not specified for "${canister.name}". Attempting to download from canister...`
|
|
103
|
+
);
|
|
104
|
+
try {
|
|
105
|
+
const candidContent = execFileSync(
|
|
106
|
+
"icp",
|
|
107
|
+
[
|
|
108
|
+
"canister",
|
|
109
|
+
"metadata",
|
|
110
|
+
canister.name,
|
|
111
|
+
"candid:service",
|
|
112
|
+
"-e",
|
|
113
|
+
environment
|
|
114
|
+
],
|
|
115
|
+
{ encoding: "utf-8" }
|
|
116
|
+
).trim();
|
|
117
|
+
const declarationsDir = path.join(outDir, "declarations");
|
|
118
|
+
if (!fs.existsSync(declarationsDir)) {
|
|
119
|
+
fs.mkdirSync(declarationsDir, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
didFile = path.join(declarationsDir, `${canister.name}.did`);
|
|
122
|
+
fs.writeFileSync(didFile, candidContent);
|
|
123
|
+
console.log(
|
|
124
|
+
`[ic-reactor] Candid downloaded and saved to ${didFile}`
|
|
125
|
+
);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(
|
|
128
|
+
`[ic-reactor] Failed to download candid for ${canister.name}: ${error}`
|
|
129
|
+
);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
65
133
|
console.log(
|
|
66
|
-
`[ic-reactor] Generating hooks for ${canister.name} from ${
|
|
134
|
+
`[ic-reactor] Generating hooks for ${canister.name} from ${didFile}`
|
|
67
135
|
);
|
|
68
136
|
const result = await generateDeclarations({
|
|
69
|
-
didFile
|
|
137
|
+
didFile,
|
|
70
138
|
outDir,
|
|
71
139
|
canisterName: canister.name
|
|
72
140
|
});
|
|
@@ -76,28 +144,10 @@ function icReactorPlugin(options) {
|
|
|
76
144
|
);
|
|
77
145
|
continue;
|
|
78
146
|
}
|
|
79
|
-
console.log(
|
|
80
|
-
`[ic-reactor] Declarations generated at ${result.declarationsDir}`
|
|
81
|
-
);
|
|
82
|
-
const useAdvanced = canister.advanced ?? options.advanced ?? false;
|
|
83
|
-
let didContent;
|
|
84
|
-
if (useAdvanced) {
|
|
85
|
-
try {
|
|
86
|
-
didContent = fs.readFileSync(canister.didFile, "utf-8");
|
|
87
|
-
} catch (e) {
|
|
88
|
-
console.warn(
|
|
89
|
-
`[ic-reactor] Could not read DID file at ${canister.didFile}, skipping advanced hook generation for ${canister.name}.`
|
|
90
|
-
);
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
147
|
const reactorContent = generateReactorFile({
|
|
95
148
|
canisterName: canister.name,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
hasDeclarations: true,
|
|
99
|
-
advanced: useAdvanced,
|
|
100
|
-
didContent
|
|
149
|
+
didFile,
|
|
150
|
+
clientManagerPath: canister.clientManagerPath ?? options.clientManagerPath
|
|
101
151
|
});
|
|
102
152
|
const reactorPath = path.join(outDir, "index.ts");
|
|
103
153
|
fs.mkdirSync(outDir, { recursive: true });
|
|
@@ -107,9 +157,10 @@ function icReactorPlugin(options) {
|
|
|
107
157
|
},
|
|
108
158
|
handleHotUpdate({ file, server }) {
|
|
109
159
|
if (file.endsWith(".did")) {
|
|
110
|
-
const canister = options.canisters.find(
|
|
111
|
-
|
|
112
|
-
|
|
160
|
+
const canister = options.canisters.find((c) => {
|
|
161
|
+
if (!c.didFile) return false;
|
|
162
|
+
return path.resolve(c.didFile) === file;
|
|
163
|
+
});
|
|
113
164
|
if (canister) {
|
|
114
165
|
console.log(
|
|
115
166
|
`[ic-reactor] Detected change in ${file}, regenerating...`
|
|
@@ -120,8 +171,6 @@ function icReactorPlugin(options) {
|
|
|
120
171
|
}
|
|
121
172
|
};
|
|
122
173
|
}
|
|
123
|
-
var index_default = icReactorPlugin;
|
|
124
174
|
export {
|
|
125
|
-
index_default as default,
|
|
126
175
|
icReactorPlugin
|
|
127
176
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ic-reactor/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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,20 +37,22 @@
|
|
|
37
37
|
"url": "https://github.com/B3Pay/ic-reactor/issues"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@ic-reactor/codegen": "0.
|
|
40
|
+
"@ic-reactor/codegen": "0.3.1"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@icp-sdk/bindgen": "^0.2.1",
|
|
47
|
-
"@types/node": "^25.2.
|
|
47
|
+
"@types/node": "^25.2.3",
|
|
48
48
|
"tsup": "^8.5.1",
|
|
49
49
|
"typescript": "^5.9.3",
|
|
50
|
-
"vite": "^7.3.1"
|
|
50
|
+
"vite": "^7.3.1",
|
|
51
|
+
"vitest": "^4.0.18"
|
|
51
52
|
},
|
|
52
53
|
"scripts": {
|
|
53
54
|
"build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.json",
|
|
54
|
-
"dev": "tsup src/index.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json"
|
|
55
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json",
|
|
56
|
+
"test": "vitest run"
|
|
55
57
|
}
|
|
56
58
|
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest"
|
|
2
|
+
import { icReactorPlugin, type IcReactorPluginOptions } from "./index"
|
|
3
|
+
import fs from "fs"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import { execFileSync } from "child_process"
|
|
6
|
+
import {
|
|
7
|
+
generateDeclarations,
|
|
8
|
+
generateReactorFile,
|
|
9
|
+
generateClientFile,
|
|
10
|
+
} from "@ic-reactor/codegen"
|
|
11
|
+
|
|
12
|
+
// Mock internal dependencies
|
|
13
|
+
vi.mock("@ic-reactor/codegen", () => ({
|
|
14
|
+
generateDeclarations: vi.fn(),
|
|
15
|
+
generateReactorFile: vi.fn(),
|
|
16
|
+
generateClientFile: vi.fn(),
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
// Mock child_process
|
|
20
|
+
vi.mock("child_process", () => ({
|
|
21
|
+
execFileSync: vi.fn(),
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
// Mock fs
|
|
25
|
+
vi.mock("fs", () => ({
|
|
26
|
+
default: {
|
|
27
|
+
existsSync: vi.fn(() => true),
|
|
28
|
+
readFileSync: vi.fn(),
|
|
29
|
+
mkdirSync: vi.fn(),
|
|
30
|
+
writeFileSync: vi.fn(),
|
|
31
|
+
},
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
describe("icReactorPlugin", () => {
|
|
35
|
+
const mockOptions: IcReactorPluginOptions = {
|
|
36
|
+
canisters: [
|
|
37
|
+
{
|
|
38
|
+
name: "test_canister",
|
|
39
|
+
didFile: "src/declarations/test.did",
|
|
40
|
+
outDir: "src/declarations/test_canister",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
outDir: "src/declarations",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const mockServer: any = {
|
|
47
|
+
config: {
|
|
48
|
+
root: "/mock/root",
|
|
49
|
+
logger: {
|
|
50
|
+
info: vi.fn(),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
middlewares: {
|
|
54
|
+
use: vi.fn(),
|
|
55
|
+
},
|
|
56
|
+
restart: vi.fn(),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
vi.resetAllMocks()
|
|
61
|
+
;(generateDeclarations as any).mockResolvedValue({
|
|
62
|
+
success: true,
|
|
63
|
+
declarationsDir: "/mock/declarations",
|
|
64
|
+
})
|
|
65
|
+
;(generateReactorFile as any).mockReturnValue("export const reactor = {}")
|
|
66
|
+
;(generateClientFile as any).mockReturnValue("export const client = {}")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("should return correct plugin structure", () => {
|
|
70
|
+
const plugin = icReactorPlugin(mockOptions)
|
|
71
|
+
expect(plugin.name).toBe("ic-reactor-plugin")
|
|
72
|
+
expect(plugin.buildStart).toBeDefined()
|
|
73
|
+
expect(plugin.handleHotUpdate).toBeDefined()
|
|
74
|
+
expect((plugin as any).config).toBeDefined()
|
|
75
|
+
// configureServer is no longer used for middleware
|
|
76
|
+
expect(plugin.configureServer).toBeUndefined()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe("config", () => {
|
|
80
|
+
it("should set up API proxy and headers when icp-cli is available", () => {
|
|
81
|
+
;(execFileSync as any).mockImplementation(
|
|
82
|
+
(command: string, args: string[], options: any) => {
|
|
83
|
+
if (
|
|
84
|
+
command === "icp" &&
|
|
85
|
+
args.includes("network") &&
|
|
86
|
+
args.includes("status")
|
|
87
|
+
) {
|
|
88
|
+
return JSON.stringify({ root_key: "mock-root-key", port: 4943 })
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
command === "icp" &&
|
|
92
|
+
args.includes("canister") &&
|
|
93
|
+
args.includes("status")
|
|
94
|
+
) {
|
|
95
|
+
return "mock-canister-id"
|
|
96
|
+
}
|
|
97
|
+
return ""
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const plugin = icReactorPlugin(mockOptions)
|
|
102
|
+
const config = (plugin as any).config({}, { command: "serve" })
|
|
103
|
+
|
|
104
|
+
expect(config.server.headers["Set-Cookie"]).toContain("ic_env=")
|
|
105
|
+
expect(config.server.headers["Set-Cookie"]).toContain(
|
|
106
|
+
"PUBLIC_CANISTER_ID%3Atest_canister%3Dmock-canister-id"
|
|
107
|
+
)
|
|
108
|
+
expect(config.server.headers["Set-Cookie"]).toContain(
|
|
109
|
+
"ic_root_key%3Dmock-root-key"
|
|
110
|
+
)
|
|
111
|
+
expect(config.server.proxy["/api"].target).toBe("http://127.0.0.1:4943")
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it("should fallback to default proxy when icp-cli fails", () => {
|
|
115
|
+
;(execFileSync as any).mockImplementation(() => {
|
|
116
|
+
throw new Error("Command not found")
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const plugin = icReactorPlugin(mockOptions)
|
|
120
|
+
const config = (plugin as any).config({}, { command: "serve" })
|
|
121
|
+
|
|
122
|
+
expect(config.server.headers).toBeUndefined()
|
|
123
|
+
expect(config.server.proxy["/api"].target).toBe("http://127.0.0.1:4943")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it("should return empty config for build command", () => {
|
|
127
|
+
const plugin = icReactorPlugin(mockOptions)
|
|
128
|
+
const config = (plugin as any).config({}, { command: "build" })
|
|
129
|
+
|
|
130
|
+
expect(config).toEqual({})
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe("buildStart", () => {
|
|
135
|
+
it("should generate declarations and reactor file", async () => {
|
|
136
|
+
const plugin = icReactorPlugin(mockOptions)
|
|
137
|
+
await (plugin.buildStart as any)()
|
|
138
|
+
|
|
139
|
+
expect(generateDeclarations).toHaveBeenCalledWith({
|
|
140
|
+
didFile: "src/declarations/test.did",
|
|
141
|
+
outDir: "src/declarations/test_canister",
|
|
142
|
+
canisterName: "test_canister",
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
expect(generateReactorFile).toHaveBeenCalledWith({
|
|
146
|
+
canisterName: "test_canister",
|
|
147
|
+
didFile: "src/declarations/test.did",
|
|
148
|
+
clientManagerPath: undefined,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
152
|
+
path.join("src/declarations/test_canister", "index.ts"),
|
|
153
|
+
"export const reactor = {}"
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("should download candid if not specified", async () => {
|
|
158
|
+
const optionsWithMissingDid: IcReactorPluginOptions = {
|
|
159
|
+
canisters: [
|
|
160
|
+
{
|
|
161
|
+
name: "missing_did",
|
|
162
|
+
outDir: "src/declarations/missing_did",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
outDir: "src/declarations",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
;(execFileSync as any).mockImplementation(
|
|
169
|
+
(command: string, args: string[]) => {
|
|
170
|
+
if (command === "icp" && args.includes("metadata")) {
|
|
171
|
+
return "service : { greet: (text) -> (text) query }"
|
|
172
|
+
}
|
|
173
|
+
return ""
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const plugin = icReactorPlugin(optionsWithMissingDid)
|
|
178
|
+
await (plugin.buildStart as any)()
|
|
179
|
+
|
|
180
|
+
expect(execFileSync).toHaveBeenCalledWith(
|
|
181
|
+
"icp",
|
|
182
|
+
expect.arrayContaining([
|
|
183
|
+
"canister",
|
|
184
|
+
"metadata",
|
|
185
|
+
"missing_did",
|
|
186
|
+
"candid:service",
|
|
187
|
+
]),
|
|
188
|
+
expect.any(Object)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
192
|
+
path.join(
|
|
193
|
+
"src/declarations/missing_did/declarations",
|
|
194
|
+
"missing_did.did"
|
|
195
|
+
),
|
|
196
|
+
"service : { greet: (text) -> (text) query }"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
expect(generateDeclarations).toHaveBeenCalledWith({
|
|
200
|
+
didFile: path.join(
|
|
201
|
+
"src/declarations/missing_did/declarations",
|
|
202
|
+
"missing_did.did"
|
|
203
|
+
),
|
|
204
|
+
outDir: "src/declarations/missing_did",
|
|
205
|
+
canisterName: "missing_did",
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
expect(generateReactorFile).toHaveBeenCalledWith({
|
|
209
|
+
canisterName: "missing_did",
|
|
210
|
+
didFile: path.join(
|
|
211
|
+
"src/declarations/missing_did/declarations",
|
|
212
|
+
"missing_did.did"
|
|
213
|
+
),
|
|
214
|
+
clientManagerPath: undefined,
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it("should handle generation errors", async () => {
|
|
219
|
+
const consoleErrorSpy = vi
|
|
220
|
+
.spyOn(console, "error")
|
|
221
|
+
.mockImplementation(() => {})
|
|
222
|
+
|
|
223
|
+
;(generateDeclarations as any).mockResolvedValue({
|
|
224
|
+
success: false,
|
|
225
|
+
error: "Failed to generate",
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const plugin = icReactorPlugin(mockOptions)
|
|
229
|
+
await (plugin.buildStart as any)()
|
|
230
|
+
|
|
231
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
232
|
+
expect.stringContaining("Failed to generate declarations")
|
|
233
|
+
)
|
|
234
|
+
expect(generateReactorFile).not.toHaveBeenCalled()
|
|
235
|
+
|
|
236
|
+
consoleErrorSpy.mockRestore()
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
describe("handleHotUpdate", () => {
|
|
241
|
+
it("should restart server when .did file changes", () => {
|
|
242
|
+
const plugin = icReactorPlugin(mockOptions)
|
|
243
|
+
const ctx = {
|
|
244
|
+
file: "/absolute/path/to/src/declarations/test.did",
|
|
245
|
+
server: mockServer,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Mock path.resolve to match the test case
|
|
249
|
+
const originalResolve = path.resolve
|
|
250
|
+
vi.spyOn(path, "resolve").mockImplementation((...args) => {
|
|
251
|
+
if (args.some((a) => a.includes("test.did"))) {
|
|
252
|
+
return "/absolute/path/to/src/declarations/test.did"
|
|
253
|
+
}
|
|
254
|
+
return originalResolve(...args)
|
|
255
|
+
})
|
|
256
|
+
;(plugin.handleHotUpdate as any)(ctx)
|
|
257
|
+
|
|
258
|
+
expect(mockServer.restart).toHaveBeenCalled()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it("should ignore other files", () => {
|
|
262
|
+
const plugin = icReactorPlugin(mockOptions)
|
|
263
|
+
const ctx = {
|
|
264
|
+
file: "/some/other/file.ts",
|
|
265
|
+
server: mockServer,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
;(plugin.handleHotUpdate as any)(ctx)
|
|
269
|
+
|
|
270
|
+
expect(mockServer.restart).not.toHaveBeenCalled()
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
})
|