@ic-reactor/cli 0.4.1 → 0.5.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 +40 -37
- package/dist/index.js +96 -281
- package/package.json +2 -2
- package/schema.json +21 -46
- package/src/commands/generate.ts +114 -0
- package/src/commands/init.ts +57 -124
- package/src/index.ts +12 -19
- package/src/types.ts +18 -17
- package/src/utils/config.ts +6 -29
- package/src/commands/list.ts +0 -144
- package/src/commands/sync.ts +0 -122
- package/src/generators/index.ts +0 -1
- package/src/generators/reactor.ts +0 -30
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @ic-reactor/cli
|
|
2
2
|
|
|
3
|
-
> 🔧 Command-line tool for generating
|
|
3
|
+
> 🔧 Command-line tool for generating type-safe React hooks for ICP canisters.
|
|
4
4
|
|
|
5
|
-
The `@ic-reactor/cli` helps you generate TypeScript declarations and React hooks from your Candid files. It
|
|
5
|
+
The `@ic-reactor/cli` helps you generate TypeScript declarations and React hooks from your Candid files. It uses the shared `@ic-reactor/codegen` pipeline to ensure consistency with the Vite plugin.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ pnpm add -D @ic-reactor/cli
|
|
|
18
18
|
npx @ic-reactor/cli init
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
This creates an `ic-reactor.json` configuration file in your project root.
|
|
21
|
+
This creates an `ic-reactor.json` configuration file in your project root and optionally sets up a default `ClientManager` at `src/clients.ts`.
|
|
22
22
|
|
|
23
23
|
### 2. Configure your canisters
|
|
24
24
|
|
|
@@ -26,35 +26,36 @@ Update `ic-reactor.json` with the paths to your Candid files:
|
|
|
26
26
|
|
|
27
27
|
```json
|
|
28
28
|
{
|
|
29
|
-
"outDir": "
|
|
29
|
+
"outDir": "src/declarations",
|
|
30
|
+
"clientManagerPath": "../../clients",
|
|
30
31
|
"canisters": {
|
|
31
32
|
"backend": {
|
|
32
|
-
"didFile": "src/
|
|
33
|
+
"didFile": "src/backend/backend.did"
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
### 3.
|
|
39
|
+
### 3. Generate hooks
|
|
39
40
|
|
|
40
41
|
```bash
|
|
41
|
-
npx
|
|
42
|
+
npx ic-reactor generate
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
This command will:
|
|
45
46
|
|
|
46
|
-
1.
|
|
47
|
-
2. Create an `index.ts` file for each canister with fully typed hooks.
|
|
47
|
+
1. Generate TypeScript declarations (`.d.ts`, `.js`, `.did`) for each canister.
|
|
48
|
+
2. Create an `index.ts` reactor file for each canister with fully typed hooks.
|
|
48
49
|
|
|
49
50
|
### 4. Use the generated hooks
|
|
50
51
|
|
|
51
52
|
Import the hooks directly from the generated output folder:
|
|
52
53
|
|
|
53
54
|
```tsx
|
|
54
|
-
import {
|
|
55
|
+
import { useBackendQuery } from "./declarations/backend"
|
|
55
56
|
|
|
56
57
|
function MyComponent() {
|
|
57
|
-
const { data, isPending } =
|
|
58
|
+
const { data, isPending } = useBackendQuery({
|
|
58
59
|
functionName: "get_message",
|
|
59
60
|
})
|
|
60
61
|
|
|
@@ -76,43 +77,45 @@ Options:
|
|
|
76
77
|
-o, --out-dir <path> Output directory for generated hooks
|
|
77
78
|
```
|
|
78
79
|
|
|
79
|
-
### `
|
|
80
|
+
### `generate` (alias: `g`)
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
Generate hooks and declarations based on your configuration and DID files.
|
|
82
83
|
|
|
83
84
|
```bash
|
|
84
|
-
npx
|
|
85
|
+
npx ic-reactor generate [options]
|
|
85
86
|
|
|
86
87
|
Options:
|
|
87
|
-
-c, --canister <name>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
### `list`
|
|
91
|
-
|
|
92
|
-
List all available methods from a canister's Candid definition.
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
npx @ic-reactor/cli list -c <canister_name>
|
|
88
|
+
-c, --canister <name> Generate only for a specific canister
|
|
89
|
+
--clean Clean output directory before generating
|
|
96
90
|
```
|
|
97
91
|
|
|
98
92
|
## Configuration
|
|
99
93
|
|
|
100
|
-
The `ic-reactor.json` file
|
|
94
|
+
The `ic-reactor.json` file schema:
|
|
101
95
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
96
|
+
```typescript
|
|
97
|
+
interface CodegenConfig {
|
|
98
|
+
/** Default output directory (relative to project root) */
|
|
99
|
+
outDir: string
|
|
100
|
+
/** Default import path for the client manager */
|
|
101
|
+
clientManagerPath?: string
|
|
102
|
+
/** Canister configurations */
|
|
103
|
+
canisters: Record<string, CanisterConfig>
|
|
104
|
+
}
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
interface CanisterConfig {
|
|
107
|
+
/** Canister name (required) */
|
|
108
|
+
name: string
|
|
109
|
+
/** Path to the .did file (required) */
|
|
110
|
+
didFile: string
|
|
111
|
+
/** Override output directory for this canister */
|
|
112
|
+
outDir?: string
|
|
113
|
+
/** Override client manager import path */
|
|
114
|
+
clientManagerPath?: string
|
|
115
|
+
/** Optional fixed canister ID */
|
|
116
|
+
canisterId?: string
|
|
117
|
+
}
|
|
118
|
+
```
|
|
116
119
|
|
|
117
120
|
## Requirements
|
|
118
121
|
|
package/dist/index.js
CHANGED
|
@@ -15,9 +15,8 @@ import path from "path";
|
|
|
15
15
|
var CONFIG_FILE_NAME = "ic-reactor.json";
|
|
16
16
|
var DEFAULT_CONFIG = {
|
|
17
17
|
$schema: "https://raw.githubusercontent.com/B3Pay/ic-reactor/main/packages/cli/schema.json",
|
|
18
|
-
outDir: "src/
|
|
19
|
-
canisters: {}
|
|
20
|
-
generatedHooks: {}
|
|
18
|
+
outDir: "src/declarations",
|
|
19
|
+
canisters: {}
|
|
21
20
|
};
|
|
22
21
|
function findConfigFile(startDir = process.cwd()) {
|
|
23
22
|
let currentDir = startDir;
|
|
@@ -59,6 +58,7 @@ function ensureDir(dirPath) {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
// src/commands/init.ts
|
|
61
|
+
import { generateClientFile } from "@ic-reactor/codegen";
|
|
62
62
|
async function initCommand(options) {
|
|
63
63
|
console.log();
|
|
64
64
|
p.intro(pc.cyan("\u{1F527} ic-reactor CLI Setup"));
|
|
@@ -82,174 +82,95 @@ async function initCommand(options) {
|
|
|
82
82
|
}
|
|
83
83
|
} else {
|
|
84
84
|
const outDir = await p.text({
|
|
85
|
-
message: "Where should generated
|
|
86
|
-
placeholder: "src/
|
|
87
|
-
defaultValue: "src/
|
|
88
|
-
validate: (value) => {
|
|
89
|
-
if (!value) return "Output directory is required";
|
|
90
|
-
return void 0;
|
|
91
|
-
}
|
|
85
|
+
message: "Where should generated files be placed?",
|
|
86
|
+
placeholder: "src/declarations",
|
|
87
|
+
defaultValue: "src/declarations"
|
|
92
88
|
});
|
|
93
|
-
if (p.isCancel(outDir))
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
message: "Would you like to add a canister now?",
|
|
99
|
-
initialValue: true
|
|
89
|
+
if (p.isCancel(outDir)) process.exit(0);
|
|
90
|
+
const clientManagerPath = await p.text({
|
|
91
|
+
message: "Relative path for the client manager import?",
|
|
92
|
+
placeholder: "../../clients",
|
|
93
|
+
defaultValue: "../../clients"
|
|
100
94
|
});
|
|
101
|
-
if (p.isCancel(
|
|
102
|
-
p.cancel("Setup cancelled.");
|
|
103
|
-
process.exit(0);
|
|
104
|
-
}
|
|
95
|
+
if (p.isCancel(clientManagerPath)) process.exit(0);
|
|
105
96
|
config = {
|
|
106
97
|
...DEFAULT_CONFIG,
|
|
107
|
-
outDir
|
|
98
|
+
outDir,
|
|
99
|
+
clientManagerPath
|
|
108
100
|
};
|
|
101
|
+
const addCanister = await p.confirm({
|
|
102
|
+
message: "Would you like to configure a canister now?",
|
|
103
|
+
initialValue: true
|
|
104
|
+
});
|
|
105
|
+
if (p.isCancel(addCanister)) process.exit(0);
|
|
109
106
|
if (addCanister) {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
config.canisters[
|
|
107
|
+
const canister = await promptForCanister(projectRoot);
|
|
108
|
+
if (canister) {
|
|
109
|
+
config.canisters[canister.name] = canister;
|
|
113
110
|
}
|
|
114
111
|
}
|
|
115
112
|
}
|
|
116
113
|
const configPath = path2.join(projectRoot, CONFIG_FILE_NAME);
|
|
117
114
|
saveConfig(config, configPath);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
message: "Create a sample client manager at src/lib/clients.ts?",
|
|
115
|
+
ensureDir(path2.join(projectRoot, config.outDir));
|
|
116
|
+
const clientManagerFile = path2.join(projectRoot, "src/clients.ts");
|
|
117
|
+
if (!fs2.existsSync(clientManagerFile)) {
|
|
118
|
+
const createHelpers = await p.confirm({
|
|
119
|
+
message: `Create a default client manager at ${pc.green("src/clients.ts")}?`,
|
|
124
120
|
initialValue: true
|
|
125
121
|
});
|
|
126
|
-
if (
|
|
127
|
-
ensureDir(path2.dirname(
|
|
128
|
-
fs2.writeFileSync(
|
|
129
|
-
p.log.success(`Created ${pc.green("src/
|
|
122
|
+
if (createHelpers === true) {
|
|
123
|
+
ensureDir(path2.dirname(clientManagerFile));
|
|
124
|
+
fs2.writeFileSync(clientManagerFile, generateClientFile());
|
|
125
|
+
p.log.success(`Created ${pc.green("src/clients.ts")}`);
|
|
130
126
|
}
|
|
131
127
|
}
|
|
132
128
|
p.log.success(`Created ${pc.green(CONFIG_FILE_NAME)}`);
|
|
133
|
-
p.log.success(`Created ${pc.green(config.outDir)} directory`);
|
|
134
129
|
console.log();
|
|
135
130
|
p.note(
|
|
136
|
-
`
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
${pc.dim("npx @ic-reactor/cli add")}
|
|
140
|
-
|
|
141
|
-
2. ${pc.cyan("List available methods:")}
|
|
142
|
-
${pc.dim("npx @ic-reactor/cli list -c <canister-name>")}
|
|
143
|
-
|
|
144
|
-
3. ${pc.cyan("Add hooks for specific methods:")}
|
|
145
|
-
${pc.dim("npx @ic-reactor/cli add -c <canister> -m <method>")}`,
|
|
146
|
-
"Getting Started"
|
|
131
|
+
`To generate hooks, run:
|
|
132
|
+
${pc.cyan("npx ic-reactor generate")}`,
|
|
133
|
+
"Next Steps"
|
|
147
134
|
);
|
|
148
|
-
p.outro(pc.green("\u2713
|
|
135
|
+
p.outro(pc.green("\u2713 Setup complete!"));
|
|
149
136
|
}
|
|
150
137
|
async function promptForCanister(projectRoot) {
|
|
151
138
|
const name = await p.text({
|
|
152
139
|
message: "Canister name",
|
|
153
140
|
placeholder: "backend",
|
|
154
|
-
validate: (
|
|
155
|
-
if (!
|
|
156
|
-
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(
|
|
157
|
-
return "Canister name must start with a letter and contain only letters, numbers, hyphens, and underscores";
|
|
158
|
-
}
|
|
159
|
-
return void 0;
|
|
141
|
+
validate: (val) => {
|
|
142
|
+
if (!val) return "Name is required";
|
|
143
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(val)) return "Invalid name format";
|
|
160
144
|
}
|
|
161
145
|
});
|
|
162
146
|
if (p.isCancel(name)) return null;
|
|
163
147
|
const didFile = await p.text({
|
|
164
148
|
message: "Path to .did file",
|
|
165
|
-
placeholder: "./backend.did",
|
|
166
|
-
validate: (
|
|
167
|
-
if (!
|
|
168
|
-
const fullPath = path2.resolve(projectRoot,
|
|
169
|
-
if (!fs2.existsSync(fullPath)) {
|
|
170
|
-
return `File not found: ${value}`;
|
|
171
|
-
}
|
|
172
|
-
return void 0;
|
|
149
|
+
placeholder: "./src/backend/backend.did",
|
|
150
|
+
validate: (val) => {
|
|
151
|
+
if (!val) return "Path is required";
|
|
152
|
+
const fullPath = path2.resolve(projectRoot, val);
|
|
153
|
+
if (!fs2.existsSync(fullPath)) return `File not found: ${val}`;
|
|
173
154
|
}
|
|
174
155
|
});
|
|
175
156
|
if (p.isCancel(didFile)) return null;
|
|
176
|
-
const clientManagerPath = await p.text({
|
|
177
|
-
message: "Import path to your client manager (relative from generated hooks)",
|
|
178
|
-
placeholder: "../../clients",
|
|
179
|
-
defaultValue: "../../clients"
|
|
180
|
-
});
|
|
181
|
-
if (p.isCancel(clientManagerPath)) return null;
|
|
182
|
-
const useDisplayReactor = await p.confirm({
|
|
183
|
-
message: "Use DisplayReactor? (auto-converts bigint \u2192 string, Principal \u2192 string)",
|
|
184
|
-
initialValue: true
|
|
185
|
-
});
|
|
186
|
-
if (p.isCancel(useDisplayReactor)) return null;
|
|
187
157
|
return {
|
|
188
158
|
name,
|
|
189
|
-
|
|
190
|
-
didFile,
|
|
191
|
-
clientManagerPath,
|
|
192
|
-
useDisplayReactor
|
|
193
|
-
}
|
|
159
|
+
didFile
|
|
194
160
|
};
|
|
195
161
|
}
|
|
196
|
-
function getClientManagerTemplate() {
|
|
197
|
-
return `/**
|
|
198
|
-
* IC Client Manager
|
|
199
|
-
*
|
|
200
|
-
* This file configures the IC agent and client manager for your application.
|
|
201
|
-
* Customize the agent options based on your environment.
|
|
202
|
-
*/
|
|
203
|
-
|
|
204
|
-
import { ClientManager } from "@ic-reactor/react"
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* The client manager handles agent lifecycle and authentication.
|
|
208
|
-
*
|
|
209
|
-
* Configuration options:
|
|
210
|
-
* - host: IC network host (defaults to process env or mainnet)
|
|
211
|
-
* - identity: Initial identity (optional, can be set later)
|
|
212
|
-
* - verifyQuerySignatures: Verify query signatures (recommended for production)
|
|
213
|
-
*
|
|
214
|
-
* For local development, the agent will automatically detect local replica.
|
|
215
|
-
*/
|
|
216
|
-
export const clientManager = new ClientManager({
|
|
217
|
-
// Uncomment for explicit host configuration:
|
|
218
|
-
// host: process.env.DFX_NETWORK === "local"
|
|
219
|
-
// ? "http://localhost:4943"
|
|
220
|
-
// : "https://icp-api.io",
|
|
221
|
-
})
|
|
222
|
-
`;
|
|
223
|
-
}
|
|
224
162
|
|
|
225
|
-
// src/commands/
|
|
163
|
+
// src/commands/generate.ts
|
|
226
164
|
import * as p2 from "@clack/prompts";
|
|
227
|
-
import fs3 from "fs";
|
|
228
|
-
import path3 from "path";
|
|
229
165
|
import pc2 from "picocolors";
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
import {
|
|
233
|
-
generateReactorFile as generateReactorFileFromCodegen
|
|
234
|
-
} from "@ic-reactor/codegen";
|
|
235
|
-
function generateReactorFile(options) {
|
|
236
|
-
const { canisterName, canisterConfig, config } = options;
|
|
237
|
-
return generateReactorFileFromCodegen({
|
|
238
|
-
canisterName,
|
|
239
|
-
canisterConfig,
|
|
240
|
-
globalClientManagerPath: config.clientManagerPath
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// src/commands/sync.ts
|
|
245
|
-
import { generateDeclarations } from "@ic-reactor/codegen";
|
|
246
|
-
async function syncCommand(options) {
|
|
166
|
+
import { runCanisterPipeline } from "@ic-reactor/codegen";
|
|
167
|
+
async function generateCommand(options) {
|
|
247
168
|
console.log();
|
|
248
|
-
p2.intro(pc2.cyan("\u{1F504}
|
|
169
|
+
p2.intro(pc2.cyan("\u{1F504} Generate Hooks"));
|
|
249
170
|
const configPath = findConfigFile();
|
|
250
171
|
if (!configPath) {
|
|
251
172
|
p2.log.error(
|
|
252
|
-
`No ${pc2.yellow("reactor.
|
|
173
|
+
`No ${pc2.yellow("ic-reactor.json")} found. Run ${pc2.cyan("npx @ic-reactor/cli init")} first.`
|
|
253
174
|
);
|
|
254
175
|
process.exit(1);
|
|
255
176
|
}
|
|
@@ -264,7 +185,7 @@ async function syncCommand(options) {
|
|
|
264
185
|
p2.log.error("No canisters configured.");
|
|
265
186
|
process.exit(1);
|
|
266
187
|
}
|
|
267
|
-
let
|
|
188
|
+
let canistersToProcess = [];
|
|
268
189
|
if (options.canister) {
|
|
269
190
|
if (!config.canisters[options.canister]) {
|
|
270
191
|
p2.log.error(
|
|
@@ -272,176 +193,70 @@ async function syncCommand(options) {
|
|
|
272
193
|
);
|
|
273
194
|
process.exit(1);
|
|
274
195
|
}
|
|
275
|
-
|
|
196
|
+
canistersToProcess = [options.canister];
|
|
276
197
|
} else {
|
|
277
|
-
|
|
198
|
+
canistersToProcess = canisterNames;
|
|
278
199
|
}
|
|
279
200
|
const spinner2 = p2.spinner();
|
|
280
|
-
spinner2.start(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
201
|
+
spinner2.start(
|
|
202
|
+
`Generating hooks for ${canistersToProcess.length} canisters...`
|
|
203
|
+
);
|
|
204
|
+
let successCount = 0;
|
|
205
|
+
let errorCount = 0;
|
|
206
|
+
const errorMessages = [];
|
|
207
|
+
for (const name of canistersToProcess) {
|
|
208
|
+
const canisterConfig = config.canisters[name];
|
|
209
|
+
spinner2.message(`Processing ${pc2.cyan(name)}...`);
|
|
288
210
|
try {
|
|
289
|
-
const
|
|
290
|
-
didFile: didFilePath,
|
|
291
|
-
outDir: canisterOutDir,
|
|
292
|
-
canisterName
|
|
293
|
-
});
|
|
294
|
-
if (!bindgenResult.success) {
|
|
295
|
-
errors.push(`${canisterName}: ${bindgenResult.error}`);
|
|
296
|
-
p2.log.warn(
|
|
297
|
-
`Could not regenerate declarations for ${canisterName}: ${bindgenResult.error}`
|
|
298
|
-
);
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
const reactorPath = path3.join(canisterOutDir, "index.ts");
|
|
302
|
-
const reactorContent = generateReactorFile({
|
|
303
|
-
canisterName,
|
|
211
|
+
const result = await runCanisterPipeline({
|
|
304
212
|
canisterConfig,
|
|
305
|
-
|
|
213
|
+
projectRoot,
|
|
214
|
+
globalConfig: config
|
|
306
215
|
});
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
216
|
+
if (result.success) {
|
|
217
|
+
successCount++;
|
|
218
|
+
} else {
|
|
219
|
+
errorCount++;
|
|
220
|
+
errorMessages.push(`${name}: ${result.error}`);
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
errorCount++;
|
|
224
|
+
errorMessages.push(
|
|
225
|
+
`${name}: ${err instanceof Error ? err.message : String(err)}`
|
|
226
|
+
);
|
|
312
227
|
}
|
|
313
228
|
}
|
|
314
|
-
spinner2.stop("
|
|
315
|
-
if (
|
|
229
|
+
spinner2.stop("Generation complete");
|
|
230
|
+
if (errorMessages.length > 0) {
|
|
316
231
|
console.log();
|
|
317
232
|
p2.log.error("Errors encountered:");
|
|
318
|
-
for (const
|
|
319
|
-
console.log(` ${pc2.red("\u2022")} ${
|
|
233
|
+
for (const msg of errorMessages) {
|
|
234
|
+
console.log(` ${pc2.red("\u2022")} ${msg}`);
|
|
320
235
|
}
|
|
321
236
|
}
|
|
322
237
|
console.log();
|
|
323
|
-
p2.note(
|
|
324
|
-
|
|
238
|
+
p2.note(
|
|
239
|
+
`Success: ${pc2.green(successCount.toString())}
|
|
240
|
+
Failed: ${pc2.red(errorCount.toString())}`,
|
|
241
|
+
"Summary"
|
|
242
|
+
);
|
|
243
|
+
if (errorCount > 0) {
|
|
244
|
+
p2.outro(pc2.red("\u2716 Generation failed with errors."));
|
|
245
|
+
process.exit(1);
|
|
246
|
+
} else {
|
|
247
|
+
p2.outro(pc2.green("\u2713 All hooks generated successfully!"));
|
|
248
|
+
}
|
|
325
249
|
}
|
|
326
250
|
|
|
327
|
-
// src/
|
|
328
|
-
import * as p3 from "@clack/prompts";
|
|
329
|
-
import path4 from "path";
|
|
251
|
+
// src/index.ts
|
|
330
252
|
import pc3 from "picocolors";
|
|
331
|
-
import { parseDIDFile } from "@ic-reactor/codegen";
|
|
332
|
-
async function listCommand(options) {
|
|
333
|
-
console.log();
|
|
334
|
-
p3.intro(pc3.cyan("\u{1F4CB} List Canister Methods"));
|
|
335
|
-
const configPath = findConfigFile();
|
|
336
|
-
if (!configPath) {
|
|
337
|
-
p3.log.error(
|
|
338
|
-
`No ${pc3.yellow("reactor.config.json")} found. Run ${pc3.cyan("npx @ic-reactor/cli init")} first.`
|
|
339
|
-
);
|
|
340
|
-
process.exit(1);
|
|
341
|
-
}
|
|
342
|
-
const config = loadConfig(configPath);
|
|
343
|
-
if (!config) {
|
|
344
|
-
p3.log.error(`Failed to load config from ${pc3.yellow(configPath)}`);
|
|
345
|
-
process.exit(1);
|
|
346
|
-
}
|
|
347
|
-
const projectRoot = getProjectRoot();
|
|
348
|
-
const canisterNames = Object.keys(config.canisters);
|
|
349
|
-
if (canisterNames.length === 0) {
|
|
350
|
-
p3.log.error(
|
|
351
|
-
`No canisters configured. Add a canister to ${pc3.yellow("reactor.config.json")} first.`
|
|
352
|
-
);
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
let selectedCanister = options.canister;
|
|
356
|
-
if (!selectedCanister) {
|
|
357
|
-
if (canisterNames.length === 1) {
|
|
358
|
-
selectedCanister = canisterNames[0];
|
|
359
|
-
} else {
|
|
360
|
-
const result = await p3.select({
|
|
361
|
-
message: "Select a canister",
|
|
362
|
-
options: canisterNames.map((name) => ({
|
|
363
|
-
value: name,
|
|
364
|
-
label: name
|
|
365
|
-
}))
|
|
366
|
-
});
|
|
367
|
-
if (p3.isCancel(result)) {
|
|
368
|
-
p3.cancel("Cancelled.");
|
|
369
|
-
process.exit(0);
|
|
370
|
-
}
|
|
371
|
-
selectedCanister = result;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const canisterConfig = config.canisters[selectedCanister];
|
|
375
|
-
if (!canisterConfig) {
|
|
376
|
-
p3.log.error(`Canister ${pc3.yellow(selectedCanister)} not found in config.`);
|
|
377
|
-
process.exit(1);
|
|
378
|
-
}
|
|
379
|
-
const didFilePath = path4.resolve(projectRoot, canisterConfig.didFile);
|
|
380
|
-
try {
|
|
381
|
-
const methods = parseDIDFile(didFilePath);
|
|
382
|
-
if (methods.length === 0) {
|
|
383
|
-
p3.log.warn(`No methods found in ${pc3.yellow(didFilePath)}`);
|
|
384
|
-
process.exit(0);
|
|
385
|
-
}
|
|
386
|
-
const queries = methods.filter((m) => m.type === "query");
|
|
387
|
-
const mutations = methods.filter((m) => m.type === "mutation");
|
|
388
|
-
const generatedMethods = config.generatedHooks[selectedCanister] ?? [];
|
|
389
|
-
if (queries.length > 0) {
|
|
390
|
-
console.log();
|
|
391
|
-
console.log(pc3.bold(pc3.cyan(" Queries:")));
|
|
392
|
-
for (const method of queries) {
|
|
393
|
-
const isGenerated = generatedMethods.includes(method.name);
|
|
394
|
-
const status = isGenerated ? pc3.green("\u2713") : pc3.dim("\u25CB");
|
|
395
|
-
const argsHint = method.hasArgs ? pc3.dim("(args)") : pc3.dim("()");
|
|
396
|
-
console.log(` ${status} ${method.name} ${argsHint}`);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
if (mutations.length > 0) {
|
|
400
|
-
console.log();
|
|
401
|
-
console.log(pc3.bold(pc3.yellow(" Mutations (Updates):")));
|
|
402
|
-
for (const method of mutations) {
|
|
403
|
-
const isGenerated = generatedMethods.includes(method.name);
|
|
404
|
-
const status = isGenerated ? pc3.green("\u2713") : pc3.dim("\u25CB");
|
|
405
|
-
const argsHint = method.hasArgs ? pc3.dim("(args)") : pc3.dim("()");
|
|
406
|
-
console.log(` ${status} ${method.name} ${argsHint}`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
console.log();
|
|
410
|
-
const generatedCount = generatedMethods.length;
|
|
411
|
-
const totalCount = methods.length;
|
|
412
|
-
p3.note(
|
|
413
|
-
`Total: ${pc3.bold(totalCount.toString())} methods
|
|
414
|
-
Generated: ${pc3.green(generatedCount.toString())} / ${totalCount}
|
|
415
253
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
selectedCanister
|
|
419
|
-
);
|
|
420
|
-
if (generatedCount < totalCount) {
|
|
421
|
-
console.log();
|
|
422
|
-
console.log(
|
|
423
|
-
pc3.dim(
|
|
424
|
-
` Run ${pc3.cyan(`npx @ic-reactor/cli add -c ${selectedCanister}`)} to add hooks`
|
|
425
|
-
)
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
} catch (error) {
|
|
429
|
-
p3.log.error(
|
|
430
|
-
`Failed to parse DID file: ${pc3.yellow(didFilePath)}
|
|
431
|
-
${error.message}`
|
|
432
|
-
);
|
|
433
|
-
process.exit(1);
|
|
434
|
-
}
|
|
435
|
-
console.log();
|
|
436
|
-
}
|
|
254
|
+
// package.json
|
|
255
|
+
var version = "0.5.1";
|
|
437
256
|
|
|
438
257
|
// src/index.ts
|
|
439
|
-
import pc4 from "picocolors";
|
|
440
258
|
var program = new Command();
|
|
441
|
-
program.name("ic-reactor").description(
|
|
442
|
-
|
|
443
|
-
).
|
|
444
|
-
program.command("init").description("Initialize ic-reactor configuration in your project").option("-y, --yes", "Skip prompts and use defaults").option("-o, --out-dir <path>", "Output directory for generated hooks").action(initCommand);
|
|
445
|
-
program.command("sync").description("Sync hooks with .did file changes").option("-c, --canister <name>", "Canister to sync").action(syncCommand);
|
|
446
|
-
program.command("list").description("List available methods from a canister").option("-c, --canister <name>", "Canister to list methods from").action(listCommand);
|
|
259
|
+
program.name("ic-reactor").description(pc3.cyan("\u{1F527} Generate type-safe React hooks for ICP canisters")).version(version);
|
|
260
|
+
program.command("init").description("Initialize ic-reactor configuration in your project").option("-y, --yes", "Skip prompts and use defaults").option("-o, --out-dir <path>", "Output directory for generated files").action(initCommand);
|
|
261
|
+
program.command("generate").alias("g").description("Generate hooks from .did files").option("-c, --canister <name>", "Generate for a specific canister only").option("--clean", "Clean output directory before generating").action(generateCommand);
|
|
447
262
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ic-reactor/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tool to generate shadcn-style React hooks for ICP canisters",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@icp-sdk/core": "^5.0.0",
|
|
34
34
|
"commander": "^14.0.3",
|
|
35
35
|
"picocolors": "^1.1.1",
|
|
36
|
-
"@ic-reactor/codegen": "0.
|
|
36
|
+
"@ic-reactor/codegen": "0.5.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^25.2.3",
|