@ic-reactor/cli 0.0.0-dev2 → 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 +264 -54
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -227,8 +227,8 @@ export const clientManager = new ClientManager({
227
227
 
228
228
  // src/commands/add.ts
229
229
  import * as p2 from "@clack/prompts";
230
- import fs4 from "fs";
231
- import path3 from "path";
230
+ import fs5 from "fs";
231
+ import path4 from "path";
232
232
  import pc2 from "picocolors";
233
233
 
234
234
  // src/parsers/did.ts
@@ -299,14 +299,15 @@ function getServiceTypeName(canisterName) {
299
299
 
300
300
  // src/generators/reactor.ts
301
301
  function generateReactorFile(options) {
302
- const { canisterName, canisterConfig } = options;
302
+ const { canisterName, canisterConfig, hasDeclarations = true } = options;
303
303
  const pascalName = toPascalCase(canisterName);
304
304
  const reactorName = getReactorName(canisterName);
305
305
  const serviceName = getServiceTypeName(canisterName);
306
306
  const reactorType = canisterConfig.useDisplayReactor !== false ? "DisplayReactor" : "Reactor";
307
307
  const clientManagerPath = canisterConfig.clientManagerPath ?? "../../lib/client";
308
308
  const declarationsPath = `./declarations/${canisterName}.did`;
309
- return `/**
309
+ if (hasDeclarations) {
310
+ return `/**
310
311
  * ${pascalName} Reactor
311
312
  *
312
313
  * Auto-generated by @ic-reactor/cli
@@ -365,6 +366,78 @@ export const { useAuth, useAgentState, useUserPrincipal } = createAuthHooks(clie
365
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
366
367
 
367
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} }
368
441
  `;
369
442
  }
370
443
 
@@ -759,6 +832,46 @@ export const invalidate${pascalMethod}Pages = ${camelMethod}InfiniteQuery.invali
759
832
  `;
760
833
  }
761
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
+
762
875
  // src/commands/add.ts
763
876
  async function addCommand(options) {
764
877
  console.log();
@@ -821,7 +934,7 @@ async function addCommand(options) {
821
934
  p2.log.error(`Canister ${pc2.yellow(selectedCanister)} not found in config.`);
822
935
  process.exit(1);
823
936
  }
824
- const didFilePath = path3.resolve(projectRoot, canisterConfig.didFile);
937
+ const didFilePath = path4.resolve(projectRoot, canisterConfig.didFile);
825
938
  let methods;
826
939
  try {
827
940
  methods = parseDIDFile(didFilePath);
@@ -946,26 +1059,42 @@ ${error.message}`
946
1059
  p2.log.warn("All methods were skipped. Nothing to generate.");
947
1060
  process.exit(0);
948
1061
  }
949
- const canisterOutDir = path3.join(projectRoot, config.outDir, selectedCanister);
950
- const hooksOutDir = path3.join(canisterOutDir, "hooks");
1062
+ const canisterOutDir = path4.join(projectRoot, config.outDir, selectedCanister);
1063
+ const hooksOutDir = path4.join(canisterOutDir, "hooks");
951
1064
  ensureDir(hooksOutDir);
952
1065
  const spinner4 = p2.spinner();
953
1066
  spinner4.start("Generating hooks...");
954
1067
  const generatedFiles = [];
955
- const reactorPath = path3.join(canisterOutDir, "reactor.ts");
1068
+ const reactorPath = path4.join(canisterOutDir, "reactor.ts");
956
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...");
957
1085
  const reactorContent = generateReactorFile({
958
1086
  canisterName: selectedCanister,
959
1087
  canisterConfig,
960
1088
  config,
961
- outDir: canisterOutDir
1089
+ outDir: canisterOutDir,
1090
+ hasDeclarations: bindgenResult.success
962
1091
  });
963
- fs4.writeFileSync(reactorPath, reactorContent);
1092
+ fs5.writeFileSync(reactorPath, reactorContent);
964
1093
  generatedFiles.push("reactor.ts");
965
1094
  }
966
1095
  for (const { method, hookType } of methodsWithHookTypes) {
967
1096
  const fileName = getHookFileName(method.name, hookType);
968
- const filePath = path3.join(hooksOutDir, fileName);
1097
+ const filePath = path4.join(hooksOutDir, fileName);
969
1098
  let content;
970
1099
  switch (hookType) {
971
1100
  case "query":
@@ -992,12 +1121,12 @@ ${error.message}`
992
1121
  });
993
1122
  break;
994
1123
  }
995
- fs4.writeFileSync(filePath, content);
996
- generatedFiles.push(path3.join("hooks", fileName));
1124
+ fs5.writeFileSync(filePath, content);
1125
+ generatedFiles.push(path4.join("hooks", fileName));
997
1126
  }
998
- const indexPath = path3.join(hooksOutDir, "index.ts");
1127
+ const indexPath = path4.join(hooksOutDir, "index.ts");
999
1128
  const indexContent = generateIndexFile(methodsWithHookTypes);
1000
- fs4.writeFileSync(indexPath, indexContent);
1129
+ fs5.writeFileSync(indexPath, indexContent);
1001
1130
  generatedFiles.push("hooks/index.ts");
1002
1131
  config.generatedHooks[selectedCanister] = [
1003
1132
  .../* @__PURE__ */ new Set([
@@ -1010,7 +1139,7 @@ ${error.message}`
1010
1139
  console.log();
1011
1140
  p2.note(
1012
1141
  generatedFiles.map((f) => pc2.green(`\u2713 ${f}`)).join("\n"),
1013
- `Generated in ${pc2.dim(path3.relative(projectRoot, canisterOutDir))}`
1142
+ `Generated in ${pc2.dim(path4.relative(projectRoot, canisterOutDir))}`
1014
1143
  );
1015
1144
  p2.outro(pc2.green("\u2713 Done!"));
1016
1145
  }
@@ -1029,8 +1158,8 @@ async function promptForNewCanister(projectRoot) {
1029
1158
  placeholder: "./backend.did",
1030
1159
  validate: (value) => {
1031
1160
  if (!value) return "DID file path is required";
1032
- const fullPath = path3.resolve(projectRoot, value);
1033
- if (!fs4.existsSync(fullPath)) {
1161
+ const fullPath = path4.resolve(projectRoot, value);
1162
+ if (!fs5.existsSync(fullPath)) {
1034
1163
  return `File not found: ${value}`;
1035
1164
  }
1036
1165
  return void 0;
@@ -1063,8 +1192,8 @@ ${exports.join("\n")}
1063
1192
 
1064
1193
  // src/commands/sync.ts
1065
1194
  import * as p3 from "@clack/prompts";
1066
- import fs5 from "fs";
1067
- import path4 from "path";
1195
+ import fs6 from "fs";
1196
+ import path5 from "path";
1068
1197
  import pc3 from "picocolors";
1069
1198
  async function syncCommand(options) {
1070
1199
  console.log();
@@ -1116,7 +1245,7 @@ async function syncCommand(options) {
1116
1245
  if (generatedMethods.length === 0) {
1117
1246
  continue;
1118
1247
  }
1119
- const didFilePath = path4.resolve(projectRoot, canisterConfig.didFile);
1248
+ const didFilePath = path5.resolve(projectRoot, canisterConfig.didFile);
1120
1249
  let methods;
1121
1250
  try {
1122
1251
  methods = parseDIDFile(didFilePath);
@@ -1141,17 +1270,17 @@ async function syncCommand(options) {
1141
1270
  `${canisterName}: New methods available: ${pc3.cyan(newMethods.map((m) => m.name).join(", "))}`
1142
1271
  );
1143
1272
  }
1144
- const canisterOutDir = path4.join(projectRoot, config.outDir, canisterName);
1145
- const reactorPath = path4.join(canisterOutDir, "reactor.ts");
1273
+ const canisterOutDir = path5.join(projectRoot, config.outDir, canisterName);
1274
+ const reactorPath = path5.join(canisterOutDir, "reactor.ts");
1146
1275
  const reactorContent = generateReactorFile({
1147
1276
  canisterName,
1148
1277
  canisterConfig,
1149
1278
  config,
1150
1279
  outDir: canisterOutDir
1151
1280
  });
1152
- fs5.writeFileSync(reactorPath, reactorContent);
1281
+ fs6.writeFileSync(reactorPath, reactorContent);
1153
1282
  totalUpdated++;
1154
- const hooksOutDir = path4.join(canisterOutDir, "hooks");
1283
+ const hooksOutDir = path5.join(canisterOutDir, "hooks");
1155
1284
  ensureDir(hooksOutDir);
1156
1285
  for (const methodName of generatedMethods) {
1157
1286
  const method = methods.find((m) => m.name === methodName);
@@ -1164,7 +1293,7 @@ async function syncCommand(options) {
1164
1293
  const infiniteQueryFileName = getHookFileName(methodName, "infiniteQuery");
1165
1294
  let content;
1166
1295
  let fileName;
1167
- if (fs5.existsSync(path4.join(hooksOutDir, infiniteQueryFileName))) {
1296
+ if (fs6.existsSync(path5.join(hooksOutDir, infiniteQueryFileName))) {
1168
1297
  fileName = infiniteQueryFileName;
1169
1298
  totalSkipped++;
1170
1299
  continue;
@@ -1183,7 +1312,7 @@ async function syncCommand(options) {
1183
1312
  config
1184
1313
  });
1185
1314
  }
1186
- fs5.writeFileSync(path4.join(hooksOutDir, fileName), content);
1315
+ fs6.writeFileSync(path5.join(hooksOutDir, fileName), content);
1187
1316
  totalUpdated++;
1188
1317
  }
1189
1318
  }
@@ -1206,7 +1335,7 @@ Skipped: ${pc3.dim(totalSkipped.toString())} files (preserved customizations)`,
1206
1335
 
1207
1336
  // src/commands/list.ts
1208
1337
  import * as p4 from "@clack/prompts";
1209
- import path5 from "path";
1338
+ import path6 from "path";
1210
1339
  import pc4 from "picocolors";
1211
1340
  async function listCommand(options) {
1212
1341
  console.log();
@@ -1255,7 +1384,7 @@ async function listCommand(options) {
1255
1384
  p4.log.error(`Canister ${pc4.yellow(selectedCanister)} not found in config.`);
1256
1385
  process.exit(1);
1257
1386
  }
1258
- const didFilePath = path5.resolve(projectRoot, canisterConfig.didFile);
1387
+ const didFilePath = path6.resolve(projectRoot, canisterConfig.didFile);
1259
1388
  try {
1260
1389
  const methods = parseDIDFile(didFilePath);
1261
1390
  if (methods.length === 0) {
@@ -1316,8 +1445,8 @@ ${error.message}`
1316
1445
 
1317
1446
  // src/commands/fetch.ts
1318
1447
  import * as p5 from "@clack/prompts";
1319
- import fs6 from "fs";
1320
- import path6 from "path";
1448
+ import fs7 from "fs";
1449
+ import path7 from "path";
1321
1450
  import pc5 from "picocolors";
1322
1451
 
1323
1452
  // src/utils/network.ts
@@ -1583,7 +1712,7 @@ async function fetchCommand(options) {
1583
1712
  let configPath = findConfigFile();
1584
1713
  let config;
1585
1714
  if (!configPath) {
1586
- configPath = path6.join(projectRoot, CONFIG_FILE_NAME);
1715
+ configPath = path7.join(projectRoot, CONFIG_FILE_NAME);
1587
1716
  config = { ...DEFAULT_CONFIG };
1588
1717
  p5.log.info(`Creating ${pc5.yellow(CONFIG_FILE_NAME)}`);
1589
1718
  } else {
@@ -1596,28 +1725,44 @@ async function fetchCommand(options) {
1596
1725
  canisterId
1597
1726
  };
1598
1727
  config.canisters[canisterName] = canisterConfig;
1599
- const canisterOutDir = path6.join(projectRoot, config.outDir, canisterName);
1600
- const hooksOutDir = path6.join(canisterOutDir, "hooks");
1601
- 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");
1602
1731
  ensureDir(hooksOutDir);
1603
1732
  ensureDir(candidDir);
1604
1733
  const genSpinner = p5.spinner();
1605
1734
  genSpinner.start("Generating hooks...");
1606
1735
  const generatedFiles = [];
1607
- const candidPath = path6.join(candidDir, `${canisterName}.did`);
1608
- fs6.writeFileSync(candidPath, candidSource);
1736
+ const candidPath = path7.join(candidDir, `${canisterName}.did`);
1737
+ fs7.writeFileSync(candidPath, candidSource);
1609
1738
  generatedFiles.push(`candid/${canisterName}.did`);
1610
- 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");
1611
1755
  const reactorContent = generateReactorFileForFetch({
1612
1756
  canisterName,
1613
1757
  canisterConfig,
1614
- canisterId
1758
+ canisterId,
1759
+ hasDeclarations: bindgenResult.success
1615
1760
  });
1616
- fs6.writeFileSync(reactorPath, reactorContent);
1761
+ fs7.writeFileSync(reactorPath, reactorContent);
1617
1762
  generatedFiles.push("reactor.ts");
1618
1763
  for (const { method, hookType } of methodsWithHookTypes) {
1619
1764
  const fileName = getHookFileName(method.name, hookType);
1620
- const filePath = path6.join(hooksOutDir, fileName);
1765
+ const filePath = path7.join(hooksOutDir, fileName);
1621
1766
  let content;
1622
1767
  switch (hookType) {
1623
1768
  case "query":
@@ -1644,12 +1789,12 @@ async function fetchCommand(options) {
1644
1789
  });
1645
1790
  break;
1646
1791
  }
1647
- fs6.writeFileSync(filePath, content);
1648
- generatedFiles.push(path6.join("hooks", fileName));
1792
+ fs7.writeFileSync(filePath, content);
1793
+ generatedFiles.push(path7.join("hooks", fileName));
1649
1794
  }
1650
- const indexPath = path6.join(hooksOutDir, "index.ts");
1795
+ const indexPath = path7.join(hooksOutDir, "index.ts");
1651
1796
  const indexContent = generateIndexFile2(methodsWithHookTypes);
1652
- fs6.writeFileSync(indexPath, indexContent);
1797
+ fs7.writeFileSync(indexPath, indexContent);
1653
1798
  generatedFiles.push("hooks/index.ts");
1654
1799
  config.generatedHooks[canisterName] = [
1655
1800
  .../* @__PURE__ */ new Set([
@@ -1662,7 +1807,7 @@ async function fetchCommand(options) {
1662
1807
  console.log();
1663
1808
  p5.note(
1664
1809
  generatedFiles.map((f) => pc5.green(`\u2713 ${f}`)).join("\n"),
1665
- `Generated in ${pc5.dim(path6.relative(projectRoot, canisterOutDir))}`
1810
+ `Generated in ${pc5.dim(path7.relative(projectRoot, canisterOutDir))}`
1666
1811
  );
1667
1812
  console.log();
1668
1813
  p5.note(
@@ -1675,13 +1820,14 @@ Methods: ${pc5.dim(selectedMethods.map((m) => m.name).join(", "))}`,
1675
1820
  p5.outro(pc5.green("\u2713 Done!"));
1676
1821
  }
1677
1822
  function generateReactorFileForFetch(options) {
1678
- const { canisterName, canisterConfig, canisterId } = options;
1823
+ const { canisterName, canisterConfig, canisterId, hasDeclarations } = options;
1679
1824
  const pascalName = canisterName.charAt(0).toUpperCase() + canisterName.slice(1);
1680
1825
  const reactorName = `${canisterName}Reactor`;
1681
1826
  const serviceName = `${pascalName}Service`;
1682
1827
  const reactorType = canisterConfig.useDisplayReactor !== false ? "DisplayReactor" : "Reactor";
1683
1828
  const clientManagerPath = canisterConfig.clientManagerPath ?? "../../lib/client";
1684
- return `/**
1829
+ if (hasDeclarations) {
1830
+ return `/**
1685
1831
  * ${pascalName} Reactor
1686
1832
  *
1687
1833
  * Auto-generated by @ic-reactor/cli
@@ -1694,18 +1840,82 @@ import { ${reactorType}, createActorHooks, createAuthHooks } from "@ic-reactor/r
1694
1840
  import { clientManager } from "${clientManagerPath}"
1695
1841
 
1696
1842
  // Import generated declarations
1697
- // Note: You may need to run 'npx @icp-sdk/bindgen' to generate TypeScript types
1698
- // For now, we use a generic service type
1699
- import { idlFactory } from "./declarations/${canisterName}.did"
1843
+ import { idlFactory, type _SERVICE as ${serviceName} } from "./declarations/${canisterName}.did"
1700
1844
 
1701
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
1702
- // 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
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
1864
+
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
1703
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
1704
1905
 
1705
- // TODO: Generate proper types using @icp-sdk/bindgen
1706
- // npx @icp-sdk/bindgen --input ./candid/${canisterName}.did --output ./src/canisters/${canisterName}/declarations
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
1707
1913
  type ${serviceName} = Record<string, (...args: unknown[]) => Promise<unknown>>
1708
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
+
1709
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
1710
1920
  // REACTOR INSTANCE
1711
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-dev2",
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"