@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.
- package/dist/index.js +264 -54
- 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
|
|
231
|
-
import
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
950
|
-
const hooksOutDir =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
996
|
-
generatedFiles.push(
|
|
1124
|
+
fs5.writeFileSync(filePath, content);
|
|
1125
|
+
generatedFiles.push(path4.join("hooks", fileName));
|
|
997
1126
|
}
|
|
998
|
-
const indexPath =
|
|
1127
|
+
const indexPath = path4.join(hooksOutDir, "index.ts");
|
|
999
1128
|
const indexContent = generateIndexFile(methodsWithHookTypes);
|
|
1000
|
-
|
|
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(
|
|
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 =
|
|
1033
|
-
if (!
|
|
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
|
|
1067
|
-
import
|
|
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 =
|
|
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 =
|
|
1145
|
-
const reactorPath =
|
|
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
|
-
|
|
1281
|
+
fs6.writeFileSync(reactorPath, reactorContent);
|
|
1153
1282
|
totalUpdated++;
|
|
1154
|
-
const hooksOutDir =
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
1320
|
-
import
|
|
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 =
|
|
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 =
|
|
1600
|
-
const hooksOutDir =
|
|
1601
|
-
const candidDir =
|
|
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 =
|
|
1608
|
-
|
|
1736
|
+
const candidPath = path7.join(candidDir, `${canisterName}.did`);
|
|
1737
|
+
fs7.writeFileSync(candidPath, candidSource);
|
|
1609
1738
|
generatedFiles.push(`candid/${canisterName}.did`);
|
|
1610
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1648
|
-
generatedFiles.push(
|
|
1792
|
+
fs7.writeFileSync(filePath, content);
|
|
1793
|
+
generatedFiles.push(path7.join("hooks", fileName));
|
|
1649
1794
|
}
|
|
1650
|
-
const indexPath =
|
|
1795
|
+
const indexPath = path7.join(hooksOutDir, "index.ts");
|
|
1651
1796
|
const indexContent = generateIndexFile2(methodsWithHookTypes);
|
|
1652
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
1706
|
-
// npx @icp-sdk/bindgen --input ./candid/${canisterName}.did --output
|
|
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-
|
|
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"
|