@ic-reactor/cli 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/dist/index.js CHANGED
@@ -12,10 +12,10 @@ import pc from "picocolors";
12
12
  // src/utils/config.ts
13
13
  import fs from "fs";
14
14
  import path from "path";
15
- var CONFIG_FILE_NAME = "reactor.config.json";
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/canisters",
18
+ outDir: "src/lib/canisters",
19
19
  canisters: {},
20
20
  generatedHooks: {}
21
21
  };
@@ -57,9 +57,6 @@ function ensureDir(dirPath) {
57
57
  fs.mkdirSync(dirPath, { recursive: true });
58
58
  }
59
59
  }
60
- function fileExists(filePath) {
61
- return fs.existsSync(filePath);
62
- }
63
60
 
64
61
  // src/commands/init.ts
65
62
  async function initCommand(options) {
@@ -86,8 +83,8 @@ async function initCommand(options) {
86
83
  } else {
87
84
  const outDir = await p.text({
88
85
  message: "Where should generated hooks be placed?",
89
- placeholder: "src/canisters",
90
- defaultValue: "src/canisters",
86
+ placeholder: "src/lib/canisters",
87
+ defaultValue: "src/lib/canisters",
91
88
  validate: (value) => {
92
89
  if (!value) return "Output directory is required";
93
90
  return void 0;
@@ -120,16 +117,16 @@ async function initCommand(options) {
120
117
  saveConfig(config, configPath);
121
118
  const fullOutDir = path2.join(projectRoot, config.outDir);
122
119
  ensureDir(fullOutDir);
123
- const clientManagerPath = path2.join(projectRoot, "src/lib/client.ts");
120
+ const clientManagerPath = path2.join(projectRoot, "src/lib/clients.ts");
124
121
  if (!fs2.existsSync(clientManagerPath)) {
125
122
  const createClient = await p.confirm({
126
- message: "Create a sample client manager at src/lib/client.ts?",
123
+ message: "Create a sample client manager at src/lib/clients.ts?",
127
124
  initialValue: true
128
125
  });
129
126
  if (!p.isCancel(createClient) && createClient) {
130
127
  ensureDir(path2.dirname(clientManagerPath));
131
128
  fs2.writeFileSync(clientManagerPath, getClientManagerTemplate());
132
- p.log.success(`Created ${pc.green("src/lib/client.ts")}`);
129
+ p.log.success(`Created ${pc.green("src/lib/clients.ts")}`);
133
130
  }
134
131
  }
135
132
  p.log.success(`Created ${pc.green(CONFIG_FILE_NAME)}`);
@@ -178,8 +175,8 @@ async function promptForCanister(projectRoot) {
178
175
  if (p.isCancel(didFile)) return null;
179
176
  const clientManagerPath = await p.text({
180
177
  message: "Import path to your client manager (relative from generated hooks)",
181
- placeholder: "../../lib/client",
182
- defaultValue: "../../lib/client"
178
+ placeholder: "../../clients",
179
+ defaultValue: "../../clients"
183
180
  });
184
181
  if (p.isCancel(clientManagerPath)) return null;
185
182
  const useDisplayReactor = await p.confirm({
@@ -225,79 +222,30 @@ export const clientManager = new ClientManager({
225
222
  `;
226
223
  }
227
224
 
228
- // src/commands/add.ts
225
+ // src/commands/sync.ts
229
226
  import * as p2 from "@clack/prompts";
230
227
  import fs3 from "fs";
231
228
  import path3 from "path";
232
229
  import pc2 from "picocolors";
233
- import { parseDIDFile, formatMethodForDisplay } from "@ic-reactor/codegen";
234
230
 
235
231
  // src/generators/reactor.ts
236
232
  import {
237
233
  generateReactorFile as generateReactorFileFromCodegen
238
234
  } from "@ic-reactor/codegen";
239
235
  function generateReactorFile(options) {
240
- const {
241
- canisterName,
242
- canisterConfig,
243
- config,
244
- hasDeclarations = true
245
- } = options;
236
+ const { canisterName, canisterConfig, config } = options;
246
237
  return generateReactorFileFromCodegen({
247
238
  canisterName,
248
239
  canisterConfig,
249
- hasDeclarations,
250
- globalClientManagerPath: config.clientManagerPath,
251
- // CLI doesn't currently expose advanced mode per-canister, but we default to false (simple mode)
252
- // If we want to support it, we'd add 'advanced' to CanisterConfig or ReactorGeneratorOptions
253
- advanced: false
254
- });
255
- }
256
-
257
- // src/generators/query.ts
258
- import {
259
- generateQueryHook as generateQueryHookFromCodegen
260
- } from "@ic-reactor/codegen";
261
- function generateQueryHook(options) {
262
- const { canisterName, method, type } = options;
263
- return generateQueryHookFromCodegen({
264
- canisterName,
265
- method,
266
- type
267
- });
268
- }
269
-
270
- // src/generators/mutation.ts
271
- import {
272
- generateMutationHook as generateMutationHookFromCodegen
273
- } from "@ic-reactor/codegen";
274
- function generateMutationHook(options) {
275
- const { canisterName, method } = options;
276
- return generateMutationHookFromCodegen({
277
- canisterName,
278
- method
279
- });
280
- }
281
-
282
- // src/generators/infiniteQuery.ts
283
- import {
284
- generateInfiniteQueryHook as generateInfiniteQueryHookFromCodegen
285
- } from "@ic-reactor/codegen";
286
- function generateInfiniteQueryHook(options) {
287
- const { canisterName, method, type } = options;
288
- return generateInfiniteQueryHookFromCodegen({
289
- canisterName,
290
- method,
291
- type
240
+ globalClientManagerPath: config.clientManagerPath
292
241
  });
293
242
  }
294
243
 
295
- // src/commands/add.ts
296
- import { getHookFileName } from "@ic-reactor/codegen";
244
+ // src/commands/sync.ts
297
245
  import { generateDeclarations } from "@ic-reactor/codegen";
298
- async function addCommand(options) {
246
+ async function syncCommand(options) {
299
247
  console.log();
300
- p2.intro(pc2.cyan("\u{1F527} Add Canister Hooks"));
248
+ p2.intro(pc2.cyan("\u{1F504} Sync Canister Hooks"));
301
249
  const configPath = findConfigFile();
302
250
  if (!configPath) {
303
251
  p2.log.error(
@@ -313,515 +261,94 @@ async function addCommand(options) {
313
261
  const projectRoot = getProjectRoot();
314
262
  const canisterNames = Object.keys(config.canisters);
315
263
  if (canisterNames.length === 0) {
316
- p2.log.error(
317
- `No canisters configured. Add a canister to ${pc2.yellow("reactor.config.json")} first.`
318
- );
319
- const addNow = await p2.confirm({
320
- message: "Would you like to add a canister now?",
321
- initialValue: true
322
- });
323
- if (p2.isCancel(addNow) || !addNow) {
324
- process.exit(1);
325
- }
326
- const canisterInfo = await promptForNewCanister(projectRoot);
327
- if (!canisterInfo) {
328
- p2.cancel("Cancelled.");
329
- process.exit(0);
330
- }
331
- config.canisters[canisterInfo.name] = canisterInfo.config;
332
- saveConfig(config, configPath);
333
- canisterNames.push(canisterInfo.name);
334
- }
335
- let selectedCanister = options.canister;
336
- if (!selectedCanister) {
337
- if (canisterNames.length === 1) {
338
- selectedCanister = canisterNames[0];
339
- } else {
340
- const result = await p2.select({
341
- message: "Select a canister",
342
- options: canisterNames.map((name) => ({
343
- value: name,
344
- label: name
345
- }))
346
- });
347
- if (p2.isCancel(result)) {
348
- p2.cancel("Cancelled.");
349
- process.exit(0);
350
- }
351
- selectedCanister = result;
352
- }
353
- }
354
- const canisterConfig = config.canisters[selectedCanister];
355
- if (!canisterConfig) {
356
- p2.log.error(`Canister ${pc2.yellow(selectedCanister)} not found in config.`);
357
- process.exit(1);
358
- }
359
- const didFilePath = path3.resolve(projectRoot, canisterConfig.didFile);
360
- let methods;
361
- try {
362
- methods = parseDIDFile(didFilePath);
363
- } catch (error) {
364
- p2.log.error(
365
- `Failed to parse DID file: ${pc2.yellow(didFilePath)}
366
- ${error.message}`
367
- );
368
- process.exit(1);
369
- }
370
- if (methods.length === 0) {
371
- p2.log.warn(`No methods found in ${pc2.yellow(didFilePath)}`);
372
- process.exit(0);
373
- }
374
- p2.log.info(
375
- `Found ${pc2.green(methods.length.toString())} methods in ${pc2.dim(selectedCanister)}`
376
- );
377
- let selectedMethods;
378
- if (options.all) {
379
- selectedMethods = methods;
380
- } else if (options.methods && options.methods.length > 0) {
381
- const requestedMethods = options.methods.flatMap((m) => m.split(",")).map((m) => m.trim()).filter((m) => m.length > 0);
382
- selectedMethods = methods.filter((m) => requestedMethods.includes(m.name));
383
- const notFound = requestedMethods.filter(
384
- (name) => !methods.some((m) => m.name === name)
385
- );
386
- if (notFound.length > 0) {
387
- p2.log.warn(`Methods not found: ${pc2.yellow(notFound.join(", "))}`);
388
- }
389
- } else {
390
- const alreadyGenerated = config.generatedHooks[selectedCanister] ?? [];
391
- const result = await p2.multiselect({
392
- message: "Select methods to add hooks for",
393
- options: methods.map((method) => {
394
- const isGenerated = alreadyGenerated.includes(method.name);
395
- return {
396
- value: method.name,
397
- label: formatMethodForDisplay(method),
398
- hint: isGenerated ? pc2.dim("(already generated)") : void 0
399
- };
400
- }),
401
- required: true
402
- });
403
- if (p2.isCancel(result)) {
404
- p2.cancel("Cancelled.");
405
- process.exit(0);
406
- }
407
- selectedMethods = methods.filter(
408
- (m) => result.includes(m.name)
409
- );
410
- }
411
- if (selectedMethods.length === 0) {
412
- p2.log.warn("No methods selected.");
413
- process.exit(0);
414
- }
415
- const methodsWithHookTypes = [];
416
- for (const method of selectedMethods) {
417
- if (method.type === "query") {
418
- const hookType = await p2.select({
419
- message: `Hook type for ${pc2.cyan(method.name)}`,
420
- options: [
421
- { value: "query", label: "Query", hint: "Standard query hook" },
422
- {
423
- value: "suspenseQuery",
424
- label: "Suspense Query",
425
- hint: "For React Suspense"
426
- },
427
- {
428
- value: "infiniteQuery",
429
- label: "Infinite Query",
430
- hint: "Paginated/infinite scroll"
431
- },
432
- {
433
- value: "suspenseInfiniteQuery",
434
- label: "Suspense Infinite Query",
435
- hint: "Paginated with Suspense"
436
- },
437
- {
438
- value: "skip",
439
- label: "Skip",
440
- hint: "Don't generate hook for this method"
441
- }
442
- ]
443
- });
444
- if (p2.isCancel(hookType)) {
445
- p2.cancel("Cancelled.");
446
- process.exit(0);
447
- }
448
- if (hookType === "skip") {
449
- p2.log.info(`Skipping ${pc2.dim(method.name)}`);
450
- continue;
451
- }
452
- methodsWithHookTypes.push({ method, hookType });
453
- } else {
454
- const hookType = await p2.select({
455
- message: `Hook type for ${pc2.yellow(method.name)} (mutation)`,
456
- options: [
457
- {
458
- value: "mutation",
459
- label: "Mutation",
460
- hint: "Standard mutation hook"
461
- },
462
- {
463
- value: "skip",
464
- label: "Skip",
465
- hint: "Don't generate hook for this method"
466
- }
467
- ]
468
- });
469
- if (p2.isCancel(hookType)) {
470
- p2.cancel("Cancelled.");
471
- process.exit(0);
472
- }
473
- if (hookType === "skip") {
474
- p2.log.info(`Skipping ${pc2.dim(method.name)}`);
475
- continue;
476
- }
477
- methodsWithHookTypes.push({ method, hookType: "mutation" });
478
- }
479
- }
480
- if (methodsWithHookTypes.length === 0) {
481
- p2.log.warn("All methods were skipped. Nothing to generate.");
482
- process.exit(0);
483
- }
484
- const canisterOutDir = path3.join(projectRoot, config.outDir, selectedCanister);
485
- const hooksOutDir = path3.join(canisterOutDir, "hooks");
486
- ensureDir(hooksOutDir);
487
- const spinner4 = p2.spinner();
488
- spinner4.start("Generating hooks...");
489
- const generatedFiles = [];
490
- const reactorPath = path3.join(canisterOutDir, "reactor.ts");
491
- if (!fileExists(reactorPath)) {
492
- spinner4.message("Generating TypeScript declarations...");
493
- const bindgenResult = await generateDeclarations({
494
- didFile: didFilePath,
495
- outDir: canisterOutDir,
496
- canisterName: selectedCanister
497
- });
498
- if (bindgenResult.success) {
499
- generatedFiles.push("declarations/");
500
- } else {
501
- p2.log.warn(`Could not generate declarations: ${bindgenResult.error}`);
502
- p2.log.info(
503
- `You can manually run: npx @icp-sdk/bindgen --input ${didFilePath} --output ${canisterOutDir}/declarations`
504
- );
505
- }
506
- spinner4.message("Generating reactor...");
507
- const reactorContent = generateReactorFile({
508
- canisterName: selectedCanister,
509
- canisterConfig,
510
- config,
511
- outDir: canisterOutDir,
512
- hasDeclarations: bindgenResult.success
513
- });
514
- fs3.writeFileSync(reactorPath, reactorContent);
515
- generatedFiles.push("reactor.ts");
516
- }
517
- for (const { method, hookType } of methodsWithHookTypes) {
518
- const fileName = getHookFileName(method.name, hookType);
519
- const filePath = path3.join(hooksOutDir, fileName);
520
- let content;
521
- switch (hookType) {
522
- case "query":
523
- case "suspenseQuery":
524
- content = generateQueryHook({
525
- canisterName: selectedCanister,
526
- method,
527
- config
528
- });
529
- break;
530
- case "infiniteQuery":
531
- case "suspenseInfiniteQuery":
532
- content = generateInfiniteQueryHook({
533
- canisterName: selectedCanister,
534
- method,
535
- config
536
- });
537
- break;
538
- case "mutation":
539
- content = generateMutationHook({
540
- canisterName: selectedCanister,
541
- method,
542
- config
543
- });
544
- break;
545
- }
546
- fs3.writeFileSync(filePath, content);
547
- generatedFiles.push(path3.join("hooks", fileName));
548
- }
549
- const indexPath = path3.join(hooksOutDir, "index.ts");
550
- let existingExports = [];
551
- if (fs3.existsSync(indexPath)) {
552
- const content = fs3.readFileSync(indexPath, "utf-8");
553
- existingExports = content.split("\n").filter((line) => line.trim().startsWith("export * from")).map((line) => line.trim());
554
- }
555
- const newExports = methodsWithHookTypes.map(({ method, hookType }) => {
556
- const fileName = getHookFileName(method.name, hookType).replace(".ts", "");
557
- return `export * from "./${fileName}"`;
558
- });
559
- const allExports = [.../* @__PURE__ */ new Set([...existingExports, ...newExports])];
560
- const indexContent = `/**
561
- * Hook barrel exports
562
- *
563
- * Auto-generated by @ic-reactor/cli
564
- */
565
-
566
- ${allExports.join("\n")}
567
- `;
568
- fs3.writeFileSync(indexPath, indexContent);
569
- generatedFiles.push("hooks/index.ts");
570
- const existingHooks = config.generatedHooks[selectedCanister] ?? [];
571
- const newHookConfigs = methodsWithHookTypes.map(({ method, hookType }) => ({
572
- name: method.name,
573
- type: hookType
574
- }));
575
- const filteredExisting = existingHooks.filter((h) => {
576
- const name = typeof h === "string" ? h : h.name;
577
- return !newHookConfigs.some((n) => n.name === name);
578
- });
579
- config.generatedHooks[selectedCanister] = [
580
- ...filteredExisting,
581
- ...newHookConfigs
582
- ];
583
- saveConfig(config, configPath);
584
- spinner4.stop("Hooks generated!");
585
- console.log();
586
- p2.note(
587
- generatedFiles.map((f) => pc2.green(`\u2713 ${f}`)).join("\n"),
588
- `Generated in ${pc2.dim(path3.relative(projectRoot, canisterOutDir))}`
589
- );
590
- p2.outro(pc2.green("\u2713 Done!"));
591
- }
592
- async function promptForNewCanister(projectRoot) {
593
- const name = await p2.text({
594
- message: "Canister name",
595
- placeholder: "backend",
596
- validate: (value) => {
597
- if (!value) return "Canister name is required";
598
- return void 0;
599
- }
600
- });
601
- if (p2.isCancel(name)) return null;
602
- const didFile = await p2.text({
603
- message: "Path to .did file",
604
- placeholder: "./backend.did",
605
- validate: (value) => {
606
- if (!value) return "DID file path is required";
607
- const fullPath = path3.resolve(projectRoot, value);
608
- if (!fs3.existsSync(fullPath)) {
609
- return `File not found: ${value}`;
610
- }
611
- return void 0;
612
- }
613
- });
614
- if (p2.isCancel(didFile)) return null;
615
- return {
616
- name,
617
- config: {
618
- didFile,
619
- useDisplayReactor: true
620
- }
621
- };
622
- }
623
-
624
- // src/commands/sync.ts
625
- import * as p3 from "@clack/prompts";
626
- import fs4 from "fs";
627
- import path4 from "path";
628
- import pc3 from "picocolors";
629
- import { parseDIDFile as parseDIDFile2 } from "@ic-reactor/codegen";
630
- import { getHookFileName as getHookFileName2 } from "@ic-reactor/codegen";
631
- import { generateDeclarations as generateDeclarations2 } from "@ic-reactor/codegen";
632
- async function syncCommand(options) {
633
- console.log();
634
- p3.intro(pc3.cyan("\u{1F504} Sync Canister Hooks"));
635
- const configPath = findConfigFile();
636
- if (!configPath) {
637
- p3.log.error(
638
- `No ${pc3.yellow("reactor.config.json")} found. Run ${pc3.cyan("npx @ic-reactor/cli init")} first.`
639
- );
640
- process.exit(1);
641
- }
642
- const config = loadConfig(configPath);
643
- if (!config) {
644
- p3.log.error(`Failed to load config from ${pc3.yellow(configPath)}`);
645
- process.exit(1);
646
- }
647
- const projectRoot = getProjectRoot();
648
- const canisterNames = Object.keys(config.canisters);
649
- if (canisterNames.length === 0) {
650
- p3.log.error("No canisters configured.");
264
+ p2.log.error("No canisters configured.");
651
265
  process.exit(1);
652
266
  }
653
267
  let canistersToSync;
654
268
  if (options.canister) {
655
269
  if (!config.canisters[options.canister]) {
656
- p3.log.error(
657
- `Canister ${pc3.yellow(options.canister)} not found in config.`
270
+ p2.log.error(
271
+ `Canister ${pc2.yellow(options.canister)} not found in config.`
658
272
  );
659
273
  process.exit(1);
660
274
  }
661
275
  canistersToSync = [options.canister];
662
276
  } else {
663
- canistersToSync = canisterNames.filter(
664
- (name) => (config.generatedHooks[name]?.length ?? 0) > 0
665
- );
666
- if (canistersToSync.length === 0) {
667
- p3.log.warn("No hooks have been generated yet. Run `add` first.");
668
- process.exit(0);
669
- }
277
+ canistersToSync = canisterNames;
670
278
  }
671
- const spinner4 = p3.spinner();
672
- spinner4.start("Syncing hooks...");
279
+ const spinner2 = p2.spinner();
280
+ spinner2.start("Syncing hooks...");
673
281
  let totalUpdated = 0;
674
- let totalSkipped = 0;
675
282
  const errors = [];
676
283
  for (const canisterName of canistersToSync) {
677
284
  const canisterConfig = config.canisters[canisterName];
678
- const generatedMethods = config.generatedHooks[canisterName] ?? [];
679
- if (generatedMethods.length === 0) {
680
- continue;
681
- }
682
- const didFilePath = path4.resolve(projectRoot, canisterConfig.didFile);
683
- let methods;
285
+ const canisterOutDir = path3.join(projectRoot, config.outDir, canisterName);
286
+ const didFilePath = path3.resolve(projectRoot, canisterConfig.didFile);
287
+ spinner2.message(`Regenerating declarations for ${canisterName}...`);
684
288
  try {
685
- methods = parseDIDFile2(didFilePath);
686
- } catch (error) {
687
- errors.push(
688
- `${canisterName}: Failed to parse DID file - ${error.message}`
689
- );
690
- continue;
691
- }
692
- const hooks = generatedMethods.map(
693
- (h) => typeof h === "string" ? { name: h } : h
694
- );
695
- const currentMethodNames = methods.map((m) => m.name);
696
- const removedMethods = hooks.filter((h) => !currentMethodNames.includes(h.name)).map((h) => h.name);
697
- if (removedMethods.length > 0) {
698
- p3.log.warn(
699
- `${canisterName}: Methods removed from DID: ${pc3.yellow(removedMethods.join(", "))}`
700
- );
701
- }
702
- const generatedNames = hooks.map((h) => h.name);
703
- const newMethods = methods.filter((m) => !generatedNames.includes(m.name));
704
- if (newMethods.length > 0) {
705
- p3.log.info(
706
- `${canisterName}: New methods available: ${pc3.cyan(newMethods.map((m) => m.name).join(", "))}`
707
- );
708
- }
709
- const canisterOutDir = path4.join(projectRoot, config.outDir, canisterName);
710
- const declarationsDir = path4.join(canisterOutDir, "declarations");
711
- const declarationsExist = fs4.existsSync(declarationsDir) && fs4.readdirSync(declarationsDir).some((f) => f.endsWith(".ts") || f.endsWith(".js"));
712
- if (!declarationsExist) {
713
- spinner4.message(`Regenerating declarations for ${canisterName}...`);
714
- const bindgenResult = await generateDeclarations2({
289
+ const bindgenResult = await generateDeclarations({
715
290
  didFile: didFilePath,
716
291
  outDir: canisterOutDir,
717
292
  canisterName
718
293
  });
719
- if (bindgenResult.success) {
720
- totalUpdated++;
721
- } else {
722
- p3.log.warn(
294
+ if (!bindgenResult.success) {
295
+ errors.push(`${canisterName}: ${bindgenResult.error}`);
296
+ p2.log.warn(
723
297
  `Could not regenerate declarations for ${canisterName}: ${bindgenResult.error}`
724
298
  );
725
- }
726
- }
727
- const reactorPath = path4.join(canisterOutDir, "reactor.ts");
728
- const reactorContent = generateReactorFile({
729
- canisterName,
730
- canisterConfig,
731
- config,
732
- outDir: canisterOutDir
733
- });
734
- fs4.writeFileSync(reactorPath, reactorContent);
735
- totalUpdated++;
736
- const hooksOutDir = path4.join(canisterOutDir, "hooks");
737
- ensureDir(hooksOutDir);
738
- for (const hookConfig of hooks) {
739
- const methodName = hookConfig.name;
740
- const method = methods.find((m) => m.name === methodName);
741
- if (!method) {
742
- totalSkipped++;
743
299
  continue;
744
300
  }
745
- let hookType = hookConfig.type || method.type;
746
- if (!hookConfig.type) {
747
- const infiniteQueryFileName = getHookFileName2(
748
- methodName,
749
- "infiniteQuery"
750
- );
751
- if (fs4.existsSync(path4.join(hooksOutDir, infiniteQueryFileName))) {
752
- hookType = "infiniteQuery";
753
- }
754
- }
755
- const fileName = getHookFileName2(methodName, hookType);
756
- let content;
757
- if (hookType.includes("Query")) {
758
- content = generateQueryHook({
759
- canisterName,
760
- method,
761
- config,
762
- type: hookType
763
- });
764
- } else {
765
- content = generateMutationHook({
766
- canisterName,
767
- method,
768
- config
769
- });
770
- }
771
- const filePath = path4.join(hooksOutDir, fileName);
772
- if (fs4.existsSync(filePath)) {
773
- const existingContent = fs4.readFileSync(filePath, "utf-8");
774
- if (existingContent !== content) {
775
- totalSkipped++;
776
- continue;
777
- }
778
- }
779
- fs4.writeFileSync(filePath, content);
301
+ const reactorPath = path3.join(canisterOutDir, "index.ts");
302
+ const reactorContent = generateReactorFile({
303
+ canisterName,
304
+ canisterConfig,
305
+ config
306
+ });
307
+ fs3.writeFileSync(reactorPath, reactorContent);
780
308
  totalUpdated++;
309
+ } catch (error) {
310
+ errors.push(`${canisterName}: ${error.message}`);
311
+ p2.log.error(`Failed to sync ${canisterName}: ${error.message}`);
781
312
  }
782
313
  }
783
- spinner4.stop("Sync complete!");
314
+ spinner2.stop("Sync complete!");
784
315
  if (errors.length > 0) {
785
316
  console.log();
786
- p3.log.error("Errors encountered:");
317
+ p2.log.error("Errors encountered:");
787
318
  for (const error of errors) {
788
- console.log(` ${pc3.red("\u2022")} ${error}`);
319
+ console.log(` ${pc2.red("\u2022")} ${error}`);
789
320
  }
790
321
  }
791
322
  console.log();
792
- p3.note(
793
- `Updated: ${pc3.green(totalUpdated.toString())} files
794
- Skipped: ${pc3.dim(totalSkipped.toString())} files (preserved customizations)`,
795
- "Summary"
796
- );
797
- p3.outro(pc3.green("\u2713 Sync complete!"));
323
+ p2.note(`Updated: ${pc2.green(totalUpdated.toString())} canisters`, "Summary");
324
+ p2.outro(pc2.green("\u2713 Sync complete!"));
798
325
  }
799
326
 
800
327
  // src/commands/list.ts
801
- import * as p4 from "@clack/prompts";
802
- import path5 from "path";
803
- import pc4 from "picocolors";
804
- import { parseDIDFile as parseDIDFile3 } from "@ic-reactor/codegen";
328
+ import * as p3 from "@clack/prompts";
329
+ import path4 from "path";
330
+ import pc3 from "picocolors";
331
+ import { parseDIDFile } from "@ic-reactor/codegen";
805
332
  async function listCommand(options) {
806
333
  console.log();
807
- p4.intro(pc4.cyan("\u{1F4CB} List Canister Methods"));
334
+ p3.intro(pc3.cyan("\u{1F4CB} List Canister Methods"));
808
335
  const configPath = findConfigFile();
809
336
  if (!configPath) {
810
- p4.log.error(
811
- `No ${pc4.yellow("reactor.config.json")} found. Run ${pc4.cyan("npx @ic-reactor/cli init")} first.`
337
+ p3.log.error(
338
+ `No ${pc3.yellow("reactor.config.json")} found. Run ${pc3.cyan("npx @ic-reactor/cli init")} first.`
812
339
  );
813
340
  process.exit(1);
814
341
  }
815
342
  const config = loadConfig(configPath);
816
343
  if (!config) {
817
- p4.log.error(`Failed to load config from ${pc4.yellow(configPath)}`);
344
+ p3.log.error(`Failed to load config from ${pc3.yellow(configPath)}`);
818
345
  process.exit(1);
819
346
  }
820
347
  const projectRoot = getProjectRoot();
821
348
  const canisterNames = Object.keys(config.canisters);
822
349
  if (canisterNames.length === 0) {
823
- p4.log.error(
824
- `No canisters configured. Add a canister to ${pc4.yellow("reactor.config.json")} first.`
350
+ p3.log.error(
351
+ `No canisters configured. Add a canister to ${pc3.yellow("reactor.config.json")} first.`
825
352
  );
826
353
  process.exit(1);
827
354
  }
@@ -830,15 +357,15 @@ async function listCommand(options) {
830
357
  if (canisterNames.length === 1) {
831
358
  selectedCanister = canisterNames[0];
832
359
  } else {
833
- const result = await p4.select({
360
+ const result = await p3.select({
834
361
  message: "Select a canister",
835
362
  options: canisterNames.map((name) => ({
836
363
  value: name,
837
364
  label: name
838
365
  }))
839
366
  });
840
- if (p4.isCancel(result)) {
841
- p4.cancel("Cancelled.");
367
+ if (p3.isCancel(result)) {
368
+ p3.cancel("Cancelled.");
842
369
  process.exit(0);
843
370
  }
844
371
  selectedCanister = result;
@@ -846,14 +373,14 @@ async function listCommand(options) {
846
373
  }
847
374
  const canisterConfig = config.canisters[selectedCanister];
848
375
  if (!canisterConfig) {
849
- p4.log.error(`Canister ${pc4.yellow(selectedCanister)} not found in config.`);
376
+ p3.log.error(`Canister ${pc3.yellow(selectedCanister)} not found in config.`);
850
377
  process.exit(1);
851
378
  }
852
- const didFilePath = path5.resolve(projectRoot, canisterConfig.didFile);
379
+ const didFilePath = path4.resolve(projectRoot, canisterConfig.didFile);
853
380
  try {
854
- const methods = parseDIDFile3(didFilePath);
381
+ const methods = parseDIDFile(didFilePath);
855
382
  if (methods.length === 0) {
856
- p4.log.warn(`No methods found in ${pc4.yellow(didFilePath)}`);
383
+ p3.log.warn(`No methods found in ${pc3.yellow(didFilePath)}`);
857
384
  process.exit(0);
858
385
  }
859
386
  const queries = methods.filter((m) => m.type === "query");
@@ -861,46 +388,46 @@ async function listCommand(options) {
861
388
  const generatedMethods = config.generatedHooks[selectedCanister] ?? [];
862
389
  if (queries.length > 0) {
863
390
  console.log();
864
- console.log(pc4.bold(pc4.cyan(" Queries:")));
391
+ console.log(pc3.bold(pc3.cyan(" Queries:")));
865
392
  for (const method of queries) {
866
393
  const isGenerated = generatedMethods.includes(method.name);
867
- const status = isGenerated ? pc4.green("\u2713") : pc4.dim("\u25CB");
868
- const argsHint = method.hasArgs ? pc4.dim("(args)") : pc4.dim("()");
394
+ const status = isGenerated ? pc3.green("\u2713") : pc3.dim("\u25CB");
395
+ const argsHint = method.hasArgs ? pc3.dim("(args)") : pc3.dim("()");
869
396
  console.log(` ${status} ${method.name} ${argsHint}`);
870
397
  }
871
398
  }
872
399
  if (mutations.length > 0) {
873
400
  console.log();
874
- console.log(pc4.bold(pc4.yellow(" Mutations (Updates):")));
401
+ console.log(pc3.bold(pc3.yellow(" Mutations (Updates):")));
875
402
  for (const method of mutations) {
876
403
  const isGenerated = generatedMethods.includes(method.name);
877
- const status = isGenerated ? pc4.green("\u2713") : pc4.dim("\u25CB");
878
- const argsHint = method.hasArgs ? pc4.dim("(args)") : pc4.dim("()");
404
+ const status = isGenerated ? pc3.green("\u2713") : pc3.dim("\u25CB");
405
+ const argsHint = method.hasArgs ? pc3.dim("(args)") : pc3.dim("()");
879
406
  console.log(` ${status} ${method.name} ${argsHint}`);
880
407
  }
881
408
  }
882
409
  console.log();
883
410
  const generatedCount = generatedMethods.length;
884
411
  const totalCount = methods.length;
885
- p4.note(
886
- `Total: ${pc4.bold(totalCount.toString())} methods
887
- Generated: ${pc4.green(generatedCount.toString())} / ${totalCount}
412
+ p3.note(
413
+ `Total: ${pc3.bold(totalCount.toString())} methods
414
+ Generated: ${pc3.green(generatedCount.toString())} / ${totalCount}
888
415
 
889
- ${pc4.green("\u2713")} = hook generated
890
- ${pc4.dim("\u25CB")} = not yet generated`,
416
+ ${pc3.green("\u2713")} = hook generated
417
+ ${pc3.dim("\u25CB")} = not yet generated`,
891
418
  selectedCanister
892
419
  );
893
420
  if (generatedCount < totalCount) {
894
421
  console.log();
895
422
  console.log(
896
- pc4.dim(
897
- ` Run ${pc4.cyan(`npx @ic-reactor/cli add -c ${selectedCanister}`)} to add hooks`
423
+ pc3.dim(
424
+ ` Run ${pc3.cyan(`npx @ic-reactor/cli add -c ${selectedCanister}`)} to add hooks`
898
425
  )
899
426
  );
900
427
  }
901
428
  } catch (error) {
902
- p4.log.error(
903
- `Failed to parse DID file: ${pc4.yellow(didFilePath)}
429
+ p3.log.error(
430
+ `Failed to parse DID file: ${pc3.yellow(didFilePath)}
904
431
  ${error.message}`
905
432
  );
906
433
  process.exit(1);
@@ -908,543 +435,13 @@ ${error.message}`
908
435
  console.log();
909
436
  }
910
437
 
911
- // src/commands/fetch.ts
912
- import * as p5 from "@clack/prompts";
913
- import fs5 from "fs";
914
- import path6 from "path";
915
- import pc5 from "picocolors";
916
- import { formatMethodForDisplay as formatMethodForDisplay2 } from "@ic-reactor/codegen";
917
- import { getHookFileName as getHookFileName3, toCamelCase } from "@ic-reactor/codegen";
918
-
919
- // src/utils/network.ts
920
- import { HttpAgent, Actor } from "@icp-sdk/core/agent";
921
- import { Principal } from "@icp-sdk/core/principal";
922
- import { IDL } from "@icp-sdk/core/candid";
923
- import { extractMethods } from "@ic-reactor/codegen";
924
- var IC_HOST = "https://icp-api.io";
925
- var LOCAL_HOST = "http://127.0.0.1:4943";
926
- async function fetchCandidFromCanister(options) {
927
- const { canisterId, network = "ic", host } = options;
928
- let principal;
929
- try {
930
- principal = Principal.fromText(canisterId);
931
- } catch {
932
- throw new Error(`Invalid canister ID: ${canisterId}`);
933
- }
934
- const agentHost = host ?? (network === "local" ? LOCAL_HOST : IC_HOST);
935
- const agent = await HttpAgent.create({
936
- host: agentHost,
937
- // Don't verify signatures for CLI queries
938
- verifyQuerySignatures: false
939
- });
940
- if (network === "local") {
941
- try {
942
- await agent.fetchRootKey();
943
- } catch {
944
- throw new Error(
945
- `Failed to connect to local replica at ${agentHost}. Is it running?`
946
- );
947
- }
948
- }
949
- const candidInterface = IDL.Service({
950
- __get_candid_interface_tmp_hack: IDL.Func([], [IDL.Text], ["query"])
951
- });
952
- const actor = Actor.createActor(() => candidInterface, {
953
- agent,
954
- canisterId: principal
955
- });
956
- try {
957
- const candidSource = await actor.__get_candid_interface_tmp_hack();
958
- if (!candidSource || candidSource.trim() === "") {
959
- throw new Error("Canister returned empty Candid interface");
960
- }
961
- const methods = extractMethods(candidSource);
962
- return {
963
- candidSource,
964
- methods,
965
- canisterId,
966
- network
967
- };
968
- } catch (error) {
969
- const message = error instanceof Error ? error.message : String(error);
970
- if (message.includes("Replica Error")) {
971
- throw new Error(
972
- `Canister ${canisterId} does not expose a Candid interface. The canister may not support the __get_candid_interface_tmp_hack method.`
973
- );
974
- }
975
- if (message.includes("not found") || message.includes("404")) {
976
- throw new Error(`Canister ${canisterId} not found on ${network} network.`);
977
- }
978
- throw new Error(`Failed to fetch Candid from canister: ${message}`);
979
- }
980
- }
981
- function isValidCanisterId(canisterId) {
982
- try {
983
- Principal.fromText(canisterId);
984
- return true;
985
- } catch {
986
- return false;
987
- }
988
- }
989
- function shortenCanisterId(canisterId) {
990
- if (canisterId.length <= 15) return canisterId;
991
- return `${canisterId.slice(0, 5)}...${canisterId.slice(-5)}`;
992
- }
993
-
994
- // src/commands/fetch.ts
995
- import { generateDeclarations as generateDeclarations3 } from "@ic-reactor/codegen";
996
- async function fetchCommand(options) {
997
- console.log();
998
- p5.intro(pc5.cyan("\u{1F310} Fetch from Live Canister"));
999
- const projectRoot = getProjectRoot();
1000
- let canisterId = options.canisterId;
1001
- if (!canisterId) {
1002
- const input = await p5.text({
1003
- message: "Enter canister ID",
1004
- placeholder: "ryjl3-tyaaa-aaaaa-aaaba-cai",
1005
- validate: (value) => {
1006
- if (!value) return "Canister ID is required";
1007
- if (!isValidCanisterId(value)) {
1008
- return "Invalid canister ID format";
1009
- }
1010
- return void 0;
1011
- }
1012
- });
1013
- if (p5.isCancel(input)) {
1014
- p5.cancel("Cancelled.");
1015
- process.exit(0);
1016
- }
1017
- canisterId = input;
1018
- }
1019
- if (!isValidCanisterId(canisterId)) {
1020
- p5.log.error(`Invalid canister ID: ${pc5.yellow(canisterId)}`);
1021
- process.exit(1);
1022
- }
1023
- let network = options.network;
1024
- if (!network) {
1025
- const result = await p5.select({
1026
- message: "Select network",
1027
- options: [
1028
- { value: "ic", label: "IC Mainnet", hint: "icp-api.io" },
1029
- { value: "local", label: "Local Replica", hint: "localhost:4943" }
1030
- ]
1031
- });
1032
- if (p5.isCancel(result)) {
1033
- p5.cancel("Cancelled.");
1034
- process.exit(0);
1035
- }
1036
- network = result;
1037
- }
1038
- const spinner4 = p5.spinner();
1039
- spinner4.start(`Fetching Candid from ${shortenCanisterId(canisterId)}...`);
1040
- let candidResult;
1041
- try {
1042
- candidResult = await fetchCandidFromCanister({
1043
- canisterId,
1044
- network
1045
- });
1046
- spinner4.stop(
1047
- `Found ${pc5.green(candidResult.methods.length.toString())} methods`
1048
- );
1049
- } catch (error) {
1050
- spinner4.stop("Failed to fetch Candid");
1051
- p5.log.error(error.message);
1052
- process.exit(1);
1053
- }
1054
- const { methods, candidSource } = candidResult;
1055
- if (methods.length === 0) {
1056
- p5.log.warn("No methods found in canister interface");
1057
- process.exit(0);
1058
- }
1059
- let canisterName = options.name;
1060
- if (!canisterName) {
1061
- const input = await p5.text({
1062
- message: "Name for this canister (used in generated code)",
1063
- placeholder: toCamelCase(canisterId.split("-")[0]),
1064
- defaultValue: toCamelCase(canisterId.split("-")[0]),
1065
- validate: (value) => {
1066
- if (!value) return "Name is required";
1067
- if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(value)) {
1068
- return "Name must start with a letter and contain only letters, numbers, hyphens, and underscores";
1069
- }
1070
- return void 0;
1071
- }
1072
- });
1073
- if (p5.isCancel(input)) {
1074
- p5.cancel("Cancelled.");
1075
- process.exit(0);
1076
- }
1077
- canisterName = input;
1078
- }
1079
- let selectedMethods;
1080
- if (options.all) {
1081
- selectedMethods = methods;
1082
- } else if (options.methods && options.methods.length > 0) {
1083
- const requestedMethods = options.methods.flatMap((m) => m.split(",")).map((m) => m.trim()).filter((m) => m.length > 0);
1084
- selectedMethods = methods.filter((m) => requestedMethods.includes(m.name));
1085
- const notFound = requestedMethods.filter(
1086
- (name) => !methods.some((m) => m.name === name)
1087
- );
1088
- if (notFound.length > 0) {
1089
- p5.log.warn(`Methods not found: ${pc5.yellow(notFound.join(", "))}`);
1090
- }
1091
- } else {
1092
- const result = await p5.multiselect({
1093
- message: "Select methods to generate hooks for",
1094
- options: methods.map((method) => ({
1095
- value: method.name,
1096
- label: formatMethodForDisplay2(method)
1097
- })),
1098
- required: true
1099
- });
1100
- if (p5.isCancel(result)) {
1101
- p5.cancel("Cancelled.");
1102
- process.exit(0);
1103
- }
1104
- selectedMethods = methods.filter(
1105
- (m) => result.includes(m.name)
1106
- );
1107
- }
1108
- if (selectedMethods.length === 0) {
1109
- p5.log.warn("No methods selected.");
1110
- process.exit(0);
1111
- }
1112
- const methodsWithHookTypes = [];
1113
- for (const method of selectedMethods) {
1114
- if (method.type === "query") {
1115
- const hookType = await p5.select({
1116
- message: `Hook type for ${pc5.cyan(method.name)}`,
1117
- options: [
1118
- {
1119
- value: "skip",
1120
- label: "Skip",
1121
- hint: "Don't generate hook for this method"
1122
- },
1123
- { value: "query", label: "Query", hint: "Standard query hook" },
1124
- {
1125
- value: "suspenseQuery",
1126
- label: "Suspense Query",
1127
- hint: "For React Suspense"
1128
- },
1129
- {
1130
- value: "infiniteQuery",
1131
- label: "Infinite Query",
1132
- hint: "Paginated/infinite scroll"
1133
- },
1134
- {
1135
- value: "suspenseInfiniteQuery",
1136
- label: "Suspense Infinite Query",
1137
- hint: "Paginated with Suspense"
1138
- }
1139
- ]
1140
- });
1141
- if (p5.isCancel(hookType)) {
1142
- p5.cancel("Cancelled.");
1143
- process.exit(0);
1144
- }
1145
- if (hookType === "skip") {
1146
- p5.log.info(`Skipping ${pc5.dim(method.name)}`);
1147
- continue;
1148
- }
1149
- methodsWithHookTypes.push({ method, hookType });
1150
- } else {
1151
- const hookType = await p5.select({
1152
- message: `Hook type for ${pc5.yellow(method.name)} (mutation)`,
1153
- options: [
1154
- {
1155
- value: "mutation",
1156
- label: "Mutation",
1157
- hint: "Standard mutation hook"
1158
- },
1159
- {
1160
- value: "skip",
1161
- label: "Skip",
1162
- hint: "Don't generate hook for this method"
1163
- }
1164
- ]
1165
- });
1166
- if (p5.isCancel(hookType)) {
1167
- p5.cancel("Cancelled.");
1168
- process.exit(0);
1169
- }
1170
- if (hookType === "skip") {
1171
- p5.log.info(`Skipping ${pc5.dim(method.name)}`);
1172
- continue;
1173
- }
1174
- methodsWithHookTypes.push({ method, hookType: "mutation" });
1175
- }
1176
- }
1177
- if (methodsWithHookTypes.length === 0) {
1178
- p5.log.warn("All methods were skipped. Nothing to generate.");
1179
- process.exit(0);
1180
- }
1181
- let configPath = findConfigFile();
1182
- let config;
1183
- if (!configPath) {
1184
- configPath = path6.join(projectRoot, CONFIG_FILE_NAME);
1185
- config = { ...DEFAULT_CONFIG };
1186
- p5.log.info(`Creating ${pc5.yellow(CONFIG_FILE_NAME)}`);
1187
- } else {
1188
- config = loadConfig(configPath) ?? { ...DEFAULT_CONFIG };
1189
- }
1190
- const canisterConfig = {
1191
- didFile: `./candid/${canisterName}.did`,
1192
- useDisplayReactor: true,
1193
- canisterId
1194
- };
1195
- config.canisters[canisterName] = canisterConfig;
1196
- const canisterOutDir = path6.join(projectRoot, config.outDir, canisterName);
1197
- const hooksOutDir = path6.join(canisterOutDir, "hooks");
1198
- const candidDir = path6.join(projectRoot, "candid");
1199
- ensureDir(hooksOutDir);
1200
- ensureDir(candidDir);
1201
- const genSpinner = p5.spinner();
1202
- genSpinner.start("Generating hooks...");
1203
- const generatedFiles = [];
1204
- const candidPath = path6.join(candidDir, `${canisterName}.did`);
1205
- fs5.writeFileSync(candidPath, candidSource);
1206
- generatedFiles.push(`candid/${canisterName}.did`);
1207
- genSpinner.message("Generating TypeScript declarations...");
1208
- const bindgenResult = await generateDeclarations3({
1209
- didFile: candidPath,
1210
- outDir: canisterOutDir,
1211
- canisterName
1212
- });
1213
- if (bindgenResult.success) {
1214
- generatedFiles.push("declarations/");
1215
- } else {
1216
- p5.log.warn(`Could not generate declarations: ${bindgenResult.error}`);
1217
- p5.log.info(
1218
- `You can manually run: npx @icp-sdk/bindgen --input ${candidPath} --output ${canisterOutDir}/declarations`
1219
- );
1220
- }
1221
- genSpinner.message("Generating reactor...");
1222
- const reactorPath = path6.join(canisterOutDir, "reactor.ts");
1223
- const reactorContent = generateReactorFileForFetch({
1224
- canisterName,
1225
- canisterConfig,
1226
- config,
1227
- canisterId,
1228
- hasDeclarations: bindgenResult.success
1229
- });
1230
- fs5.writeFileSync(reactorPath, reactorContent);
1231
- generatedFiles.push("reactor.ts");
1232
- for (const { method, hookType } of methodsWithHookTypes) {
1233
- const fileName = getHookFileName3(method.name, hookType);
1234
- const filePath = path6.join(hooksOutDir, fileName);
1235
- let content;
1236
- switch (hookType) {
1237
- case "query":
1238
- case "suspenseQuery":
1239
- content = generateQueryHook({
1240
- canisterName,
1241
- method,
1242
- config
1243
- });
1244
- break;
1245
- case "infiniteQuery":
1246
- case "suspenseInfiniteQuery":
1247
- content = generateInfiniteQueryHook({
1248
- canisterName,
1249
- method,
1250
- config
1251
- });
1252
- break;
1253
- case "mutation":
1254
- content = generateMutationHook({
1255
- canisterName,
1256
- method,
1257
- config
1258
- });
1259
- break;
1260
- }
1261
- fs5.writeFileSync(filePath, content);
1262
- generatedFiles.push(path6.join("hooks", fileName));
1263
- }
1264
- const indexPath = path6.join(hooksOutDir, "index.ts");
1265
- const indexContent = generateIndexFile(methodsWithHookTypes);
1266
- fs5.writeFileSync(indexPath, indexContent);
1267
- generatedFiles.push("hooks/index.ts");
1268
- config.generatedHooks[canisterName] = [
1269
- .../* @__PURE__ */ new Set([
1270
- ...config.generatedHooks[canisterName] ?? [],
1271
- ...selectedMethods.map((m) => m.name)
1272
- ])
1273
- ];
1274
- saveConfig(config, configPath);
1275
- genSpinner.stop("Hooks generated!");
1276
- console.log();
1277
- p5.note(
1278
- generatedFiles.map((f) => pc5.green(`\u2713 ${f}`)).join("\n"),
1279
- `Generated in ${pc5.dim(path6.relative(projectRoot, canisterOutDir))}`
1280
- );
1281
- console.log();
1282
- p5.note(
1283
- `Canister ID: ${pc5.cyan(canisterId)}
1284
- Network: ${pc5.yellow(network)}
1285
- Name: ${pc5.green(canisterName)}
1286
- Methods: ${pc5.dim(selectedMethods.map((m) => m.name).join(", "))}`,
1287
- "Canister Info"
1288
- );
1289
- p5.outro(pc5.green("\u2713 Done!"));
1290
- }
1291
- function generateReactorFileForFetch(options) {
1292
- const { canisterName, canisterConfig, config, canisterId, hasDeclarations } = options;
1293
- const pascalName = canisterName.charAt(0).toUpperCase() + canisterName.slice(1);
1294
- const reactorName = `${canisterName}Reactor`;
1295
- const serviceName = `${pascalName}Service`;
1296
- const reactorType = canisterConfig.useDisplayReactor !== false ? "DisplayReactor" : "Reactor";
1297
- const clientManagerPath = canisterConfig.clientManagerPath ?? config.clientManagerPath ?? "../../lib/client";
1298
- const didFileName = path6.basename(canisterConfig.didFile);
1299
- if (hasDeclarations) {
1300
- return `/**
1301
- * ${pascalName} Reactor
1302
- *
1303
- * Auto-generated by @ic-reactor/cli
1304
- * Fetched from canister: ${canisterId}
1305
- *
1306
- * You can customize this file to add global configuration.
1307
- */
1308
-
1309
- import { ${reactorType}, createActorHooks } from "@ic-reactor/react"
1310
- import { clientManager } from "${clientManagerPath}"
1311
-
1312
- // Import generated declarations
1313
- import { idlFactory, type _SERVICE as ${serviceName} } from "./declarations/${didFileName}"
1314
-
1315
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1316
- // REACTOR INSTANCE
1317
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1318
-
1319
- /**
1320
- * ${pascalName} Reactor
1321
- *
1322
- * Canister ID: ${canisterId}
1323
- */
1324
- export const ${reactorName} = new ${reactorType}<${serviceName}>({
1325
- clientManager,
1326
- idlFactory,
1327
- canisterId: "${canisterId}",
1328
- name: "${canisterName}",
1329
- })
1330
-
1331
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1332
- // ACTOR HOOKS
1333
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1334
-
1335
- /**
1336
- * Actor hooks for ${canisterName} - use these directly or import method-specific hooks.
1337
- */
1338
- export const {
1339
- useActorQuery,
1340
- useActorMutation,
1341
- useActorSuspenseQuery,
1342
- useActorInfiniteQuery,
1343
- useActorSuspenseInfiniteQuery,
1344
- useActorMethod,
1345
- } = createActorHooks(${reactorName})
1346
-
1347
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1348
- // RE-EXPORTS
1349
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1350
-
1351
- export { idlFactory }
1352
- export type { ${serviceName} }
1353
- `;
1354
- }
1355
- return `/**
1356
- * ${pascalName} Reactor
1357
- *
1358
- * Auto-generated by @ic-reactor/cli
1359
- * Fetched from canister: ${canisterId}
1360
- *
1361
- * You can customize this file to add global configuration.
1362
- */
1363
-
1364
- import { ${reactorType}, createActorHooks } from "@ic-reactor/react"
1365
- import { clientManager } from "${clientManagerPath}"
1366
-
1367
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1368
- // DECLARATIONS
1369
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1370
-
1371
- // TODO: Generate proper types by running:
1372
- // npx @icp-sdk/bindgen --input ./candid/${canisterName}.did --output ./${canisterName}/declarations
1373
-
1374
- // For now, import just the IDL factory (you may need to create this manually)
1375
- // import { idlFactory, type _SERVICE as ${serviceName} } from "./declarations/${didFileName}"
1376
-
1377
- // Fallback generic type - replace with generated types
1378
- type ${serviceName} = Record<string, (...args: unknown[]) => Promise<unknown>>
1379
-
1380
- // You'll need to define idlFactory here or import from declarations
1381
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1382
- const idlFactory = ({ IDL }: { IDL: any }) => IDL.Service({})
1383
-
1384
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1385
- // REACTOR INSTANCE
1386
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1387
-
1388
- /**
1389
- * ${pascalName} Reactor
1390
- *
1391
- * Canister ID: ${canisterId}
1392
- */
1393
- export const ${reactorName} = new ${reactorType}<${serviceName}>({
1394
- clientManager,
1395
- idlFactory,
1396
- canisterId: "${canisterId}",
1397
- name: "${canisterName}",
1398
- })
1399
-
1400
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1401
- // ACTOR HOOKS
1402
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1403
-
1404
- /**
1405
- * Actor hooks for ${canisterName} - use these directly or import method-specific hooks.
1406
- */
1407
- export const {
1408
- useActorQuery,
1409
- useActorMutation,
1410
- useActorSuspenseQuery,
1411
- useActorInfiniteQuery,
1412
- useActorSuspenseInfiniteQuery,
1413
- useActorMethod,
1414
- } = createActorHooks(${reactorName})
1415
-
1416
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1417
- // RE-EXPORTS
1418
- // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1419
-
1420
- export { idlFactory }
1421
- export type { ${serviceName} }
1422
- `;
1423
- }
1424
- function generateIndexFile(methods) {
1425
- const exports = methods.map(({ method, hookType }) => {
1426
- const fileName = getHookFileName3(method.name, hookType).replace(".ts", "");
1427
- return `export * from "./${fileName}"`;
1428
- });
1429
- return `/**
1430
- * Hook barrel exports
1431
- *
1432
- * Auto-generated by @ic-reactor/cli
1433
- */
1434
-
1435
- ${exports.join("\n")}
1436
- `;
1437
- }
1438
-
1439
438
  // src/index.ts
1440
- import pc6 from "picocolors";
439
+ import pc4 from "picocolors";
1441
440
  var program = new Command();
1442
441
  program.name("ic-reactor").description(
1443
- pc6.cyan("\u{1F527} Generate shadcn-style React hooks for ICP canisters")
442
+ pc4.cyan("\u{1F527} Generate shadcn-style React hooks for ICP canisters")
1444
443
  ).version("3.0.0");
1445
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);
1446
- program.command("add").description("Add hooks for canister methods (from local .did file)").option("-c, --canister <name>", "Canister name to add hooks for").option("-m, --methods <methods...>", "Method names to generate hooks for").option("-a, --all", "Add hooks for all methods").action(addCommand);
1447
- program.command("fetch").description("Fetch Candid from a live canister and generate hooks").option("-i, --canister-id <id>", "Canister ID to fetch from").option("-n, --network <network>", "Network: 'ic' or 'local'", "ic").option("--name <name>", "Name for the canister in generated code").option("-m, --methods <methods...>", "Method names to generate hooks for").option("-a, --all", "Add hooks for all methods").action(fetchCommand);
1448
445
  program.command("sync").description("Sync hooks with .did file changes").option("-c, --canister <name>", "Canister to sync").action(syncCommand);
1449
446
  program.command("list").description("List available methods from a canister").option("-c, --canister <name>", "Canister to list methods from").action(listCommand);
1450
447
  program.parse();