@ic-reactor/cli 0.0.0-dev1 → 0.0.0-dev3

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.
Files changed (2) hide show
  1. package/dist/index.js +267 -60
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -46,12 +46,9 @@ function saveConfig(config, configPath = path.join(process.cwd(), CONFIG_FILE_NA
46
46
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
47
47
  }
48
48
  function getProjectRoot() {
49
- let currentDir = process.cwd();
50
- while (currentDir !== path.dirname(currentDir)) {
51
- if (fs.existsSync(path.join(currentDir, "package.json"))) {
52
- return currentDir;
53
- }
54
- currentDir = path.dirname(currentDir);
49
+ const configPath = findConfigFile();
50
+ if (configPath) {
51
+ return path.dirname(configPath);
55
52
  }
56
53
  return process.cwd();
57
54
  }
@@ -230,8 +227,8 @@ export const clientManager = new ClientManager({
230
227
 
231
228
  // src/commands/add.ts
232
229
  import * as p2 from "@clack/prompts";
233
- import fs4 from "fs";
234
- import path3 from "path";
230
+ import fs5 from "fs";
231
+ import path4 from "path";
235
232
  import pc2 from "picocolors";
236
233
 
237
234
  // src/parsers/did.ts
@@ -302,14 +299,15 @@ function getServiceTypeName(canisterName) {
302
299
 
303
300
  // src/generators/reactor.ts
304
301
  function generateReactorFile(options) {
305
- const { canisterName, canisterConfig } = options;
302
+ const { canisterName, canisterConfig, hasDeclarations = true } = options;
306
303
  const pascalName = toPascalCase(canisterName);
307
304
  const reactorName = getReactorName(canisterName);
308
305
  const serviceName = getServiceTypeName(canisterName);
309
306
  const reactorType = canisterConfig.useDisplayReactor !== false ? "DisplayReactor" : "Reactor";
310
307
  const clientManagerPath = canisterConfig.clientManagerPath ?? "../../lib/client";
311
308
  const declarationsPath = `./declarations/${canisterName}.did`;
312
- return `/**
309
+ if (hasDeclarations) {
310
+ return `/**
313
311
  * ${pascalName} Reactor
314
312
  *
315
313
  * Auto-generated by @ic-reactor/cli
@@ -368,6 +366,78 @@ export const { useAuth, useAgentState, useUserPrincipal } = createAuthHooks(clie
368
366
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
369
367
 
370
368
  export { idlFactory }
369
+ `;
370
+ }
371
+ return `/**
372
+ * ${pascalName} Reactor
373
+ *
374
+ * Auto-generated by @ic-reactor/cli
375
+ * This file provides the shared reactor instance for the ${canisterName} canister.
376
+ *
377
+ * You can customize this file to add global configuration.
378
+ */
379
+
380
+ import { ${reactorType}, createActorHooks, createAuthHooks } from "@ic-reactor/react"
381
+ import { clientManager } from "${clientManagerPath}"
382
+
383
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
384
+ // DECLARATIONS
385
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
386
+
387
+ // TODO: Generate proper types by running:
388
+ // npx @icp-sdk/bindgen --input <path-to-did> --output ./${canisterName}/declarations
389
+
390
+ // Then uncomment:
391
+ // import { idlFactory, type _SERVICE as ${serviceName} } from "${declarationsPath}"
392
+
393
+ // Fallback generic type - replace with generated types
394
+ type ${serviceName} = Record<string, (...args: unknown[]) => Promise<unknown>>
395
+
396
+ // You'll need to define idlFactory here or import from declarations
397
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
398
+ const idlFactory = ({ IDL }: { IDL: any }) => IDL.Service({})
399
+
400
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
401
+ // REACTOR INSTANCE
402
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
403
+
404
+ /**
405
+ * ${pascalName} Reactor with ${canisterConfig.useDisplayReactor !== false ? "Display" : "Candid"} type transformations.
406
+ * ${canisterConfig.useDisplayReactor !== false ? "Automatically converts bigint \u2192 string, Principal \u2192 string, etc." : "Uses raw Candid types."}
407
+ */
408
+ export const ${reactorName} = new ${reactorType}<${serviceName}>({
409
+ clientManager,
410
+ idlFactory,
411
+ name: "${canisterName}",
412
+ })
413
+
414
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
415
+ // BASE HOOKS
416
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
417
+
418
+ /**
419
+ * Base actor hooks - use these directly or import method-specific hooks.
420
+ */
421
+ export const {
422
+ useActorQuery,
423
+ useActorMutation,
424
+ useActorSuspenseQuery,
425
+ useActorInfiniteQuery,
426
+ useActorSuspenseInfiniteQuery,
427
+ useActorMethod,
428
+ } = createActorHooks(${reactorName})
429
+
430
+ /**
431
+ * Auth hooks for the client manager.
432
+ */
433
+ export const { useAuth, useAgentState, useUserPrincipal } = createAuthHooks(clientManager)
434
+
435
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
436
+ // RE-EXPORTS
437
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
438
+
439
+ export { idlFactory }
440
+ export type { ${serviceName} }
371
441
  `;
372
442
  }
373
443
 
@@ -762,6 +832,46 @@ export const invalidate${pascalMethod}Pages = ${camelMethod}InfiniteQuery.invali
762
832
  `;
763
833
  }
764
834
 
835
+ // src/utils/bindgen.ts
836
+ import { generate } from "@icp-sdk/bindgen/core";
837
+ import path3 from "path";
838
+ import fs4 from "fs";
839
+ async function generateDeclarations(options) {
840
+ const { didFile, outDir } = options;
841
+ if (!fs4.existsSync(didFile)) {
842
+ return {
843
+ success: false,
844
+ declarationsDir: "",
845
+ error: `DID file not found: ${didFile}`
846
+ };
847
+ }
848
+ const declarationsDir = path3.join(outDir, "declarations");
849
+ try {
850
+ await generate({
851
+ didFile,
852
+ outDir: declarationsDir,
853
+ output: {
854
+ actor: {
855
+ disabled: true
856
+ // We don't need actor creation, we use Reactor
857
+ },
858
+ force: true
859
+ // Overwrite existing files
860
+ }
861
+ });
862
+ return {
863
+ success: true,
864
+ declarationsDir
865
+ };
866
+ } catch (error) {
867
+ return {
868
+ success: false,
869
+ declarationsDir,
870
+ error: error instanceof Error ? error.message : String(error)
871
+ };
872
+ }
873
+ }
874
+
765
875
  // src/commands/add.ts
766
876
  async function addCommand(options) {
767
877
  console.log();
@@ -824,7 +934,7 @@ async function addCommand(options) {
824
934
  p2.log.error(`Canister ${pc2.yellow(selectedCanister)} not found in config.`);
825
935
  process.exit(1);
826
936
  }
827
- const didFilePath = path3.resolve(projectRoot, canisterConfig.didFile);
937
+ const didFilePath = path4.resolve(projectRoot, canisterConfig.didFile);
828
938
  let methods;
829
939
  try {
830
940
  methods = parseDIDFile(didFilePath);
@@ -949,26 +1059,42 @@ ${error.message}`
949
1059
  p2.log.warn("All methods were skipped. Nothing to generate.");
950
1060
  process.exit(0);
951
1061
  }
952
- const canisterOutDir = path3.join(projectRoot, config.outDir, selectedCanister);
953
- const hooksOutDir = path3.join(canisterOutDir, "hooks");
1062
+ const canisterOutDir = path4.join(projectRoot, config.outDir, selectedCanister);
1063
+ const hooksOutDir = path4.join(canisterOutDir, "hooks");
954
1064
  ensureDir(hooksOutDir);
955
1065
  const spinner4 = p2.spinner();
956
1066
  spinner4.start("Generating hooks...");
957
1067
  const generatedFiles = [];
958
- const reactorPath = path3.join(canisterOutDir, "reactor.ts");
1068
+ const reactorPath = path4.join(canisterOutDir, "reactor.ts");
959
1069
  if (!fileExists(reactorPath)) {
1070
+ spinner4.message("Generating TypeScript declarations...");
1071
+ const bindgenResult = await generateDeclarations({
1072
+ didFile: didFilePath,
1073
+ outDir: canisterOutDir,
1074
+ canisterName: selectedCanister
1075
+ });
1076
+ if (bindgenResult.success) {
1077
+ generatedFiles.push("declarations/");
1078
+ } else {
1079
+ p2.log.warn(`Could not generate declarations: ${bindgenResult.error}`);
1080
+ p2.log.info(
1081
+ `You can manually run: npx @icp-sdk/bindgen --input ${didFilePath} --output ${canisterOutDir}/declarations`
1082
+ );
1083
+ }
1084
+ spinner4.message("Generating reactor...");
960
1085
  const reactorContent = generateReactorFile({
961
1086
  canisterName: selectedCanister,
962
1087
  canisterConfig,
963
1088
  config,
964
- outDir: canisterOutDir
1089
+ outDir: canisterOutDir,
1090
+ hasDeclarations: bindgenResult.success
965
1091
  });
966
- fs4.writeFileSync(reactorPath, reactorContent);
1092
+ fs5.writeFileSync(reactorPath, reactorContent);
967
1093
  generatedFiles.push("reactor.ts");
968
1094
  }
969
1095
  for (const { method, hookType } of methodsWithHookTypes) {
970
1096
  const fileName = getHookFileName(method.name, hookType);
971
- const filePath = path3.join(hooksOutDir, fileName);
1097
+ const filePath = path4.join(hooksOutDir, fileName);
972
1098
  let content;
973
1099
  switch (hookType) {
974
1100
  case "query":
@@ -995,12 +1121,12 @@ ${error.message}`
995
1121
  });
996
1122
  break;
997
1123
  }
998
- fs4.writeFileSync(filePath, content);
999
- generatedFiles.push(path3.join("hooks", fileName));
1124
+ fs5.writeFileSync(filePath, content);
1125
+ generatedFiles.push(path4.join("hooks", fileName));
1000
1126
  }
1001
- const indexPath = path3.join(hooksOutDir, "index.ts");
1127
+ const indexPath = path4.join(hooksOutDir, "index.ts");
1002
1128
  const indexContent = generateIndexFile(methodsWithHookTypes);
1003
- fs4.writeFileSync(indexPath, indexContent);
1129
+ fs5.writeFileSync(indexPath, indexContent);
1004
1130
  generatedFiles.push("hooks/index.ts");
1005
1131
  config.generatedHooks[selectedCanister] = [
1006
1132
  .../* @__PURE__ */ new Set([
@@ -1013,7 +1139,7 @@ ${error.message}`
1013
1139
  console.log();
1014
1140
  p2.note(
1015
1141
  generatedFiles.map((f) => pc2.green(`\u2713 ${f}`)).join("\n"),
1016
- `Generated in ${pc2.dim(path3.relative(projectRoot, canisterOutDir))}`
1142
+ `Generated in ${pc2.dim(path4.relative(projectRoot, canisterOutDir))}`
1017
1143
  );
1018
1144
  p2.outro(pc2.green("\u2713 Done!"));
1019
1145
  }
@@ -1032,8 +1158,8 @@ async function promptForNewCanister(projectRoot) {
1032
1158
  placeholder: "./backend.did",
1033
1159
  validate: (value) => {
1034
1160
  if (!value) return "DID file path is required";
1035
- const fullPath = path3.resolve(projectRoot, value);
1036
- if (!fs4.existsSync(fullPath)) {
1161
+ const fullPath = path4.resolve(projectRoot, value);
1162
+ if (!fs5.existsSync(fullPath)) {
1037
1163
  return `File not found: ${value}`;
1038
1164
  }
1039
1165
  return void 0;
@@ -1066,8 +1192,8 @@ ${exports.join("\n")}
1066
1192
 
1067
1193
  // src/commands/sync.ts
1068
1194
  import * as p3 from "@clack/prompts";
1069
- import fs5 from "fs";
1070
- import path4 from "path";
1195
+ import fs6 from "fs";
1196
+ import path5 from "path";
1071
1197
  import pc3 from "picocolors";
1072
1198
  async function syncCommand(options) {
1073
1199
  console.log();
@@ -1119,7 +1245,7 @@ async function syncCommand(options) {
1119
1245
  if (generatedMethods.length === 0) {
1120
1246
  continue;
1121
1247
  }
1122
- const didFilePath = path4.resolve(projectRoot, canisterConfig.didFile);
1248
+ const didFilePath = path5.resolve(projectRoot, canisterConfig.didFile);
1123
1249
  let methods;
1124
1250
  try {
1125
1251
  methods = parseDIDFile(didFilePath);
@@ -1144,17 +1270,17 @@ async function syncCommand(options) {
1144
1270
  `${canisterName}: New methods available: ${pc3.cyan(newMethods.map((m) => m.name).join(", "))}`
1145
1271
  );
1146
1272
  }
1147
- const canisterOutDir = path4.join(projectRoot, config.outDir, canisterName);
1148
- const reactorPath = path4.join(canisterOutDir, "reactor.ts");
1273
+ const canisterOutDir = path5.join(projectRoot, config.outDir, canisterName);
1274
+ const reactorPath = path5.join(canisterOutDir, "reactor.ts");
1149
1275
  const reactorContent = generateReactorFile({
1150
1276
  canisterName,
1151
1277
  canisterConfig,
1152
1278
  config,
1153
1279
  outDir: canisterOutDir
1154
1280
  });
1155
- fs5.writeFileSync(reactorPath, reactorContent);
1281
+ fs6.writeFileSync(reactorPath, reactorContent);
1156
1282
  totalUpdated++;
1157
- const hooksOutDir = path4.join(canisterOutDir, "hooks");
1283
+ const hooksOutDir = path5.join(canisterOutDir, "hooks");
1158
1284
  ensureDir(hooksOutDir);
1159
1285
  for (const methodName of generatedMethods) {
1160
1286
  const method = methods.find((m) => m.name === methodName);
@@ -1167,7 +1293,7 @@ async function syncCommand(options) {
1167
1293
  const infiniteQueryFileName = getHookFileName(methodName, "infiniteQuery");
1168
1294
  let content;
1169
1295
  let fileName;
1170
- if (fs5.existsSync(path4.join(hooksOutDir, infiniteQueryFileName))) {
1296
+ if (fs6.existsSync(path5.join(hooksOutDir, infiniteQueryFileName))) {
1171
1297
  fileName = infiniteQueryFileName;
1172
1298
  totalSkipped++;
1173
1299
  continue;
@@ -1186,7 +1312,7 @@ async function syncCommand(options) {
1186
1312
  config
1187
1313
  });
1188
1314
  }
1189
- fs5.writeFileSync(path4.join(hooksOutDir, fileName), content);
1315
+ fs6.writeFileSync(path5.join(hooksOutDir, fileName), content);
1190
1316
  totalUpdated++;
1191
1317
  }
1192
1318
  }
@@ -1209,7 +1335,7 @@ Skipped: ${pc3.dim(totalSkipped.toString())} files (preserved customizations)`,
1209
1335
 
1210
1336
  // src/commands/list.ts
1211
1337
  import * as p4 from "@clack/prompts";
1212
- import path5 from "path";
1338
+ import path6 from "path";
1213
1339
  import pc4 from "picocolors";
1214
1340
  async function listCommand(options) {
1215
1341
  console.log();
@@ -1258,7 +1384,7 @@ async function listCommand(options) {
1258
1384
  p4.log.error(`Canister ${pc4.yellow(selectedCanister)} not found in config.`);
1259
1385
  process.exit(1);
1260
1386
  }
1261
- const didFilePath = path5.resolve(projectRoot, canisterConfig.didFile);
1387
+ const didFilePath = path6.resolve(projectRoot, canisterConfig.didFile);
1262
1388
  try {
1263
1389
  const methods = parseDIDFile(didFilePath);
1264
1390
  if (methods.length === 0) {
@@ -1319,8 +1445,8 @@ ${error.message}`
1319
1445
 
1320
1446
  // src/commands/fetch.ts
1321
1447
  import * as p5 from "@clack/prompts";
1322
- import fs6 from "fs";
1323
- import path6 from "path";
1448
+ import fs7 from "fs";
1449
+ import path7 from "path";
1324
1450
  import pc5 from "picocolors";
1325
1451
 
1326
1452
  // src/utils/network.ts
@@ -1586,7 +1712,7 @@ async function fetchCommand(options) {
1586
1712
  let configPath = findConfigFile();
1587
1713
  let config;
1588
1714
  if (!configPath) {
1589
- configPath = path6.join(projectRoot, CONFIG_FILE_NAME);
1715
+ configPath = path7.join(projectRoot, CONFIG_FILE_NAME);
1590
1716
  config = { ...DEFAULT_CONFIG };
1591
1717
  p5.log.info(`Creating ${pc5.yellow(CONFIG_FILE_NAME)}`);
1592
1718
  } else {
@@ -1599,28 +1725,44 @@ async function fetchCommand(options) {
1599
1725
  canisterId
1600
1726
  };
1601
1727
  config.canisters[canisterName] = canisterConfig;
1602
- const canisterOutDir = path6.join(projectRoot, config.outDir, canisterName);
1603
- const hooksOutDir = path6.join(canisterOutDir, "hooks");
1604
- const candidDir = path6.join(projectRoot, "candid");
1728
+ const canisterOutDir = path7.join(projectRoot, config.outDir, canisterName);
1729
+ const hooksOutDir = path7.join(canisterOutDir, "hooks");
1730
+ const candidDir = path7.join(projectRoot, "candid");
1605
1731
  ensureDir(hooksOutDir);
1606
1732
  ensureDir(candidDir);
1607
1733
  const genSpinner = p5.spinner();
1608
1734
  genSpinner.start("Generating hooks...");
1609
1735
  const generatedFiles = [];
1610
- const candidPath = path6.join(candidDir, `${canisterName}.did`);
1611
- fs6.writeFileSync(candidPath, candidSource);
1736
+ const candidPath = path7.join(candidDir, `${canisterName}.did`);
1737
+ fs7.writeFileSync(candidPath, candidSource);
1612
1738
  generatedFiles.push(`candid/${canisterName}.did`);
1613
- const reactorPath = path6.join(canisterOutDir, "reactor.ts");
1739
+ genSpinner.message("Generating TypeScript declarations...");
1740
+ const bindgenResult = await generateDeclarations({
1741
+ didFile: candidPath,
1742
+ outDir: canisterOutDir,
1743
+ canisterName
1744
+ });
1745
+ if (bindgenResult.success) {
1746
+ generatedFiles.push("declarations/");
1747
+ } else {
1748
+ p5.log.warn(`Could not generate declarations: ${bindgenResult.error}`);
1749
+ p5.log.info(
1750
+ `You can manually run: npx @icp-sdk/bindgen --input ${candidPath} --output ${canisterOutDir}/declarations`
1751
+ );
1752
+ }
1753
+ genSpinner.message("Generating reactor...");
1754
+ const reactorPath = path7.join(canisterOutDir, "reactor.ts");
1614
1755
  const reactorContent = generateReactorFileForFetch({
1615
1756
  canisterName,
1616
1757
  canisterConfig,
1617
- canisterId
1758
+ canisterId,
1759
+ hasDeclarations: bindgenResult.success
1618
1760
  });
1619
- fs6.writeFileSync(reactorPath, reactorContent);
1761
+ fs7.writeFileSync(reactorPath, reactorContent);
1620
1762
  generatedFiles.push("reactor.ts");
1621
1763
  for (const { method, hookType } of methodsWithHookTypes) {
1622
1764
  const fileName = getHookFileName(method.name, hookType);
1623
- const filePath = path6.join(hooksOutDir, fileName);
1765
+ const filePath = path7.join(hooksOutDir, fileName);
1624
1766
  let content;
1625
1767
  switch (hookType) {
1626
1768
  case "query":
@@ -1647,12 +1789,12 @@ async function fetchCommand(options) {
1647
1789
  });
1648
1790
  break;
1649
1791
  }
1650
- fs6.writeFileSync(filePath, content);
1651
- generatedFiles.push(path6.join("hooks", fileName));
1792
+ fs7.writeFileSync(filePath, content);
1793
+ generatedFiles.push(path7.join("hooks", fileName));
1652
1794
  }
1653
- const indexPath = path6.join(hooksOutDir, "index.ts");
1795
+ const indexPath = path7.join(hooksOutDir, "index.ts");
1654
1796
  const indexContent = generateIndexFile2(methodsWithHookTypes);
1655
- fs6.writeFileSync(indexPath, indexContent);
1797
+ fs7.writeFileSync(indexPath, indexContent);
1656
1798
  generatedFiles.push("hooks/index.ts");
1657
1799
  config.generatedHooks[canisterName] = [
1658
1800
  .../* @__PURE__ */ new Set([
@@ -1665,7 +1807,7 @@ async function fetchCommand(options) {
1665
1807
  console.log();
1666
1808
  p5.note(
1667
1809
  generatedFiles.map((f) => pc5.green(`\u2713 ${f}`)).join("\n"),
1668
- `Generated in ${pc5.dim(path6.relative(projectRoot, canisterOutDir))}`
1810
+ `Generated in ${pc5.dim(path7.relative(projectRoot, canisterOutDir))}`
1669
1811
  );
1670
1812
  console.log();
1671
1813
  p5.note(
@@ -1678,13 +1820,14 @@ Methods: ${pc5.dim(selectedMethods.map((m) => m.name).join(", "))}`,
1678
1820
  p5.outro(pc5.green("\u2713 Done!"));
1679
1821
  }
1680
1822
  function generateReactorFileForFetch(options) {
1681
- const { canisterName, canisterConfig, canisterId } = options;
1823
+ const { canisterName, canisterConfig, canisterId, hasDeclarations } = options;
1682
1824
  const pascalName = canisterName.charAt(0).toUpperCase() + canisterName.slice(1);
1683
1825
  const reactorName = `${canisterName}Reactor`;
1684
1826
  const serviceName = `${pascalName}Service`;
1685
1827
  const reactorType = canisterConfig.useDisplayReactor !== false ? "DisplayReactor" : "Reactor";
1686
1828
  const clientManagerPath = canisterConfig.clientManagerPath ?? "../../lib/client";
1687
- return `/**
1829
+ if (hasDeclarations) {
1830
+ return `/**
1688
1831
  * ${pascalName} Reactor
1689
1832
  *
1690
1833
  * Auto-generated by @ic-reactor/cli
@@ -1697,18 +1840,82 @@ import { ${reactorType}, createActorHooks, createAuthHooks } from "@ic-reactor/r
1697
1840
  import { clientManager } from "${clientManagerPath}"
1698
1841
 
1699
1842
  // Import generated declarations
1700
- // Note: You may need to run 'npx @icp-sdk/bindgen' to generate TypeScript types
1701
- // For now, we use a generic service type
1702
- import { idlFactory } from "./declarations/${canisterName}.did"
1843
+ import { idlFactory, type _SERVICE as ${serviceName} } from "./declarations/${canisterName}.did"
1703
1844
 
1704
1845
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1705
- // SERVICE TYPE
1846
+ // REACTOR INSTANCE
1847
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1848
+
1849
+ /**
1850
+ * ${pascalName} Reactor
1851
+ *
1852
+ * Canister ID: ${canisterId}
1853
+ */
1854
+ export const ${reactorName} = new ${reactorType}<${serviceName}>({
1855
+ clientManager,
1856
+ idlFactory,
1857
+ canisterId: "${canisterId}",
1858
+ name: "${canisterName}",
1859
+ })
1860
+
1861
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1862
+ // BASE HOOKS
1706
1863
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1707
1864
 
1708
- // TODO: Generate proper types using @icp-sdk/bindgen
1709
- // npx @icp-sdk/bindgen --input ./candid/${canisterName}.did --output ./src/canisters/${canisterName}/declarations
1865
+ /**
1866
+ * Base actor hooks - use these directly or import method-specific hooks.
1867
+ */
1868
+ export const {
1869
+ useActorQuery,
1870
+ useActorMutation,
1871
+ useActorSuspenseQuery,
1872
+ useActorInfiniteQuery,
1873
+ useActorSuspenseInfiniteQuery,
1874
+ useActorMethod,
1875
+ } = createActorHooks(${reactorName})
1876
+
1877
+ /**
1878
+ * Auth hooks for the client manager.
1879
+ */
1880
+ export const { useAuth, useAgentState, useUserPrincipal } = createAuthHooks(clientManager)
1881
+
1882
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1883
+ // RE-EXPORTS
1884
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1885
+
1886
+ export { idlFactory }
1887
+ export type { ${serviceName} }
1888
+ `;
1889
+ }
1890
+ return `/**
1891
+ * ${pascalName} Reactor
1892
+ *
1893
+ * Auto-generated by @ic-reactor/cli
1894
+ * Fetched from canister: ${canisterId}
1895
+ *
1896
+ * You can customize this file to add global configuration.
1897
+ */
1898
+
1899
+ import { ${reactorType}, createActorHooks, createAuthHooks } from "@ic-reactor/react"
1900
+ import { clientManager } from "${clientManagerPath}"
1901
+
1902
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1903
+ // DECLARATIONS
1904
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1905
+
1906
+ // TODO: Generate proper types by running:
1907
+ // npx @icp-sdk/bindgen --input ./candid/${canisterName}.did --output ./${canisterName}/declarations
1908
+
1909
+ // For now, import just the IDL factory (you may need to create this manually)
1910
+ // import { idlFactory, type _SERVICE as ${serviceName} } from "./declarations/${canisterName}.did"
1911
+
1912
+ // Fallback generic type - replace with generated types
1710
1913
  type ${serviceName} = Record<string, (...args: unknown[]) => Promise<unknown>>
1711
1914
 
1915
+ // You'll need to define idlFactory here or import from declarations
1916
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1917
+ const idlFactory = ({ IDL }: { IDL: any }) => IDL.Service({})
1918
+
1712
1919
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1713
1920
  // REACTOR INSTANCE
1714
1921
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/cli",
3
- "version": "0.0.0-dev1",
3
+ "version": "0.0.0-dev3",
4
4
  "type": "module",
5
5
  "description": "CLI tool to generate shadcn-style React hooks for ICP canisters",
6
6
  "main": "dist/index.js",
@@ -34,6 +34,7 @@
34
34
  "homepage": "https://github.com/B3Pay/ic-reactor#readme",
35
35
  "dependencies": {
36
36
  "@clack/prompts": "^0.9.1",
37
+ "@icp-sdk/bindgen": "^0.2.1",
37
38
  "@icp-sdk/core": "^5.0.0",
38
39
  "commander": "^13.1.0",
39
40
  "picocolors": "^1.1.1"