@ic-reactor/codegen 0.4.0 → 0.5.0

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.
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Declarations Generator
3
+ *
4
+ * Generates TypeScript declaration files (.js IDL factory + .d.ts types)
5
+ * from a Candid .did file using @ic-reactor/parser.
6
+ *
7
+ * Output structure:
8
+ * <outDir>/declarations/<name>.js — IDL factory
9
+ * <outDir>/declarations/<name>.d.ts — TypeScript types
10
+ * <outDir>/declarations/<name>.did — Copy of the source .did file
11
+ */
12
+
13
+ import { didToJs, didToTs } from "@ic-reactor/parser"
14
+ import path from "node:path"
15
+ import fs from "node:fs"
16
+ import type { GeneratorResult } from "../types.js"
17
+
18
+ export interface DeclarationsGeneratorOptions {
19
+ /** Absolute path to the .did file */
20
+ didFile: string
21
+ /** Absolute path to the output directory (declarations/ will be created inside) */
22
+ outDir: string
23
+ /** Canister name (used only for error messages) */
24
+ canisterName: string
25
+ }
26
+
27
+ export interface DeclarationsGeneratorResult {
28
+ success: boolean
29
+ declarationsDir: string
30
+ files: GeneratorResult[]
31
+ error?: string
32
+ }
33
+
34
+ /**
35
+ * Generate TypeScript declarations from a Candid file.
36
+ *
37
+ * Always cleans and regenerates the declarations directory to ensure
38
+ * it's in sync with the source .did file.
39
+ */
40
+ export async function generateDeclarations(
41
+ options: DeclarationsGeneratorOptions
42
+ ): Promise<DeclarationsGeneratorResult> {
43
+ const { didFile, outDir, canisterName } = options
44
+
45
+ if (!fs.existsSync(didFile)) {
46
+ return {
47
+ success: false,
48
+ declarationsDir: "",
49
+ files: [],
50
+ error: `DID file not found: ${didFile}`,
51
+ }
52
+ }
53
+
54
+ const declarationsDir = path.join(outDir, "declarations")
55
+ const baseName = path.basename(didFile, ".did") // e.g. "backend" from "backend.did"
56
+
57
+ try {
58
+ // Read the DID content before any directory manipulation
59
+ const didContent = fs.readFileSync(didFile, "utf-8")
60
+
61
+ // Ensure output dir exists
62
+ if (!fs.existsSync(outDir)) {
63
+ fs.mkdirSync(outDir, { recursive: true })
64
+ }
65
+
66
+ // Clean and recreate declarations dir for a fresh generation
67
+ if (fs.existsSync(declarationsDir)) {
68
+ fs.rmSync(declarationsDir, { recursive: true, force: true })
69
+ }
70
+ fs.mkdirSync(declarationsDir, { recursive: true })
71
+
72
+ const jsContent = didToJs(didContent)
73
+ const tsContent = didToTs(didContent)
74
+
75
+ const jsPath = path.join(declarationsDir, `${baseName}.js`)
76
+ const dtsPath = path.join(declarationsDir, `${baseName}.d.ts`)
77
+ const didCopyPath = path.join(declarationsDir, `${baseName}.did`)
78
+
79
+ fs.writeFileSync(jsPath, jsContent)
80
+ fs.writeFileSync(dtsPath, tsContent)
81
+ fs.writeFileSync(didCopyPath, didContent)
82
+
83
+ return {
84
+ success: true,
85
+ declarationsDir,
86
+ files: [
87
+ { success: true, filePath: jsPath },
88
+ { success: true, filePath: dtsPath },
89
+ { success: true, filePath: didCopyPath },
90
+ ],
91
+ }
92
+ } catch (error) {
93
+ const message = error instanceof Error ? error.message : String(error)
94
+ return {
95
+ success: false,
96
+ declarationsDir,
97
+ files: [],
98
+ error: `[${canisterName}] Failed to generate declarations: ${message}`,
99
+ }
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Check if declarations already exist for a canister.
105
+ */
106
+ export function declarationsExist(
107
+ outDir: string,
108
+ canisterName: string
109
+ ): boolean {
110
+ const dtsPath = path.join(outDir, "declarations", `${canisterName}.d.ts`)
111
+ return fs.existsSync(dtsPath)
112
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Generator exports
3
+ *
4
+ * All individual generators are exported here.
5
+ * The pipeline composes them; CLI and vite-plugin use them via the pipeline.
6
+ */
7
+
8
+ export { generateDeclarations, declarationsExist } from "./declarations.js"
9
+ export type {
10
+ DeclarationsGeneratorOptions,
11
+ DeclarationsGeneratorResult,
12
+ } from "./declarations.js"
13
+
14
+ export { generateReactorFile } from "./reactor.js"
15
+ export type { ReactorGeneratorOptions } from "./reactor.js"
16
+
17
+ export { generateClientFile } from "./client.js"
18
+ export type { ClientGeneratorOptions } from "./client.js"
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Reactor File Generator
3
+ *
4
+ * Generates the main `index.ts` for a canister — a DisplayReactor instance
5
+ * plus the full set of typed hooks via `createActorHooks`.
6
+ *
7
+ * Generated output example (for canister "backend"):
8
+ *
9
+ * import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
10
+ * import { clientManager } from "../../clients"
11
+ * import { idlFactory, type _SERVICE } from "./declarations/backend"
12
+ *
13
+ * export type BackendService = _SERVICE
14
+ *
15
+ * export const backendReactor = new DisplayReactor<BackendService>({ ... })
16
+ *
17
+ * export const {
18
+ * useActorQuery: useBackendQuery,
19
+ * ...
20
+ * } = createActorHooks(backendReactor)
21
+ */
22
+
23
+ import path from "node:path"
24
+ import { toPascalCase, getReactorName, getServiceTypeName } from "../naming.js"
25
+
26
+ export interface ReactorGeneratorOptions {
27
+ /** Canister name (e.g. "backend") */
28
+ canisterName: string
29
+ /**
30
+ * Path to the .did file. Used to derive the declarations import path.
31
+ * Can be relative or absolute — only the basename is used.
32
+ */
33
+ didFile: string
34
+ /**
35
+ * Import path for the client manager, relative from the generated reactor file.
36
+ * Default: "../../clients"
37
+ */
38
+ clientManagerPath?: string
39
+ }
40
+
41
+ /**
42
+ * Generate the content of a canister's `index.ts` reactor file.
43
+ */
44
+ export function generateReactorFile(options: ReactorGeneratorOptions): string {
45
+ const { canisterName, didFile, clientManagerPath = "../../clients" } = options
46
+
47
+ const pascalName = toPascalCase(canisterName)
48
+ const reactorName = getReactorName(canisterName)
49
+ const serviceName = getServiceTypeName(canisterName)
50
+
51
+ // Derive the declarations import path from the .did filename
52
+ const baseName = path.basename(didFile, ".did")
53
+ const declarationsPath = `./declarations/${baseName}`
54
+
55
+ return `import { DisplayReactor, createActorHooks } from "@ic-reactor/react"
56
+ import { clientManager } from "${clientManagerPath}"
57
+ import { idlFactory, type _SERVICE } from "${declarationsPath}"
58
+
59
+ export type ${serviceName} = _SERVICE
60
+
61
+ /**
62
+ * ${pascalName} Reactor
63
+ *
64
+ * Auto-generated by @ic-reactor/codegen — do not edit.
65
+ * Re-run \`ic-reactor generate\` (or the Vite plugin) to regenerate.
66
+ */
67
+ export const ${reactorName} = new DisplayReactor<${serviceName}>({
68
+ clientManager,
69
+ idlFactory,
70
+ name: "${canisterName}",
71
+ })
72
+
73
+ export const {
74
+ useActorQuery: use${pascalName}Query,
75
+ useActorSuspenseQuery: use${pascalName}SuspenseQuery,
76
+ useActorInfiniteQuery: use${pascalName}InfiniteQuery,
77
+ useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
78
+ useActorMutation: use${pascalName}Mutation,
79
+ useActorMethod: use${pascalName}Method,
80
+ } = createActorHooks(${reactorName})
81
+ `
82
+ }
package/src/index.ts CHANGED
@@ -2,19 +2,21 @@
2
2
  * @ic-reactor/codegen
3
3
  *
4
4
  * Shared code generation utilities for IC Reactor.
5
- * Used by both @ic-reactor/vite-plugin and @ic-reactor/cli.
5
+ * Used by @ic-reactor/cli and @ic-reactor/vite-plugin.
6
6
  */
7
7
 
8
- // Naming utilities
9
- export { toPascalCase, getReactorName, getServiceTypeName } from "./naming.js"
8
+ // Core Types
9
+ export type { CanisterConfig, CodegenConfig, GeneratorResult } from "./types.js"
10
+
11
+ // Pipeline (Primary Entry Point)
12
+ export { runCanisterPipeline } from "./pipeline.js"
13
+ export type { PipelineOptions, PipelineResult } from "./pipeline.js"
10
14
 
11
- // Bindgen utilities
12
- export { generateDeclarations, declarationsExist } from "./bindgen.js"
13
- export type { BindgenOptions, BindgenResult } from "./bindgen.js"
15
+ // Utilities
16
+ export { toPascalCase, getReactorName, getServiceTypeName } from "./naming.js"
14
17
 
15
- // Template generators
16
- export { generateReactorFile } from "./templates/reactor.js"
17
- export type { ReactorGeneratorOptions } from "./templates/reactor.js"
18
+ export { parseDIDFile, extractMethods } from "./parser.js"
19
+ export type { MethodInfo, MethodType } from "./parser.js"
18
20
 
19
- export { generateClientFile } from "./templates/client.js"
20
- export type { ClientGeneratorOptions } from "./templates/client.js"
21
+ // Individual Generators (Advanced Usage)
22
+ export * from "./generators/index.js"
@@ -2,9 +2,6 @@ import { describe, it, expect } from "vitest"
2
2
  import {
3
3
  toPascalCase,
4
4
  toCamelCase,
5
- getHookFileName,
6
- getHookExportName,
7
- getReactHookName,
8
5
  getReactorName,
9
6
  getServiceTypeName,
10
7
  } from "./naming"
@@ -25,25 +22,14 @@ describe("Naming Utilities", () => {
25
22
  })
26
23
 
27
24
  describe("Domain-Specific Naming", () => {
28
- it("generates hook file names", () => {
29
- expect(getHookFileName("get_user", "query")).toBe("getUserQuery.ts")
30
- expect(getHookFileName("update_item", "mutation")).toBe(
31
- "updateItemMutation.ts"
32
- )
33
- })
34
-
35
- it("generates hook export names", () => {
36
- expect(getHookExportName("get_user", "query")).toBe("getUserQuery")
37
- expect(getHookExportName("update_item", "mutation")).toBe(
38
- "updateItemMutation"
39
- )
25
+ it("generates reactor names", () => {
26
+ expect(getReactorName("backend")).toBe("backendReactor")
27
+ expect(getReactorName("my-canister")).toBe("myCanisterReactor")
40
28
  })
41
29
 
42
- it("generates React hook names", () => {
43
- expect(getReactHookName("get_user", "query")).toBe("useGetUserQuery")
44
- expect(getReactHookName("update_item", "mutation")).toBe(
45
- "useUpdateItemMutation"
46
- )
30
+ it("generates service type names", () => {
31
+ expect(getServiceTypeName("backend")).toBe("BackendService")
32
+ expect(getServiceTypeName("update_item")).toBe("UpdateItemService")
47
33
  })
48
34
 
49
35
  it("generates reactor names", () => {
package/src/naming.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * Naming utilities for code generation
3
3
  *
4
- * Uses the `change-case` library for base transformations,
5
- * with domain-specific helpers for IC Reactor patterns.
4
+ * Pure functions that convert canister/method names to correctly cased
5
+ * identifiers used throughout generated code.
6
6
  */
7
7
 
8
8
  import { camelCase, pascalCase } from "change-case"
9
9
 
10
- // ═══════════════════════════════════════════════════════════════════════════
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
11
  // BASE CASE CONVERSIONS
12
- // ═══════════════════════════════════════════════════════════════════════════
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
13
 
14
14
  /**
15
- * Convert string to PascalCase
15
+ * Convert string to PascalCase.
16
16
  * @example toPascalCase("get_message") → "GetMessage"
17
17
  * @example toPascalCase("my-canister") → "MyCanister"
18
18
  */
@@ -21,7 +21,7 @@ export function toPascalCase(str: string): string {
21
21
  }
22
22
 
23
23
  /**
24
- * Convert string to camelCase
24
+ * Convert string to camelCase.
25
25
  * @example toCamelCase("get_message") → "getMessage"
26
26
  * @example toCamelCase("my-canister") → "myCanister"
27
27
  */
@@ -29,55 +29,33 @@ export function toCamelCase(str: string): string {
29
29
  return camelCase(str)
30
30
  }
31
31
 
32
- // ═══════════════════════════════════════════════════════════════════════════
32
+ // ─────────────────────────────────────────────────────────────────────────────
33
33
  // DOMAIN-SPECIFIC NAMING
34
- // ═══════════════════════════════════════════════════════════════════════════
34
+ // ─────────────────────────────────────────────────────────────────────────────
35
35
 
36
36
  /**
37
- * Generate hook file name
38
- * @example getHookFileName("get_message", "query") → "getMessageQuery.ts"
39
- */
40
- export function getHookFileName(methodName: string, hookType: string): string {
41
- const camelMethod = toCamelCase(methodName)
42
- const pascalType = toPascalCase(hookType)
43
- return `${camelMethod}${pascalType}.ts`
44
- }
45
-
46
- /**
47
- * Generate hook export name
48
- * @example getHookExportName("get_message", "query") → "getMessageQuery"
49
- */
50
- export function getHookExportName(
51
- methodName: string,
52
- hookType: string
53
- ): string {
54
- const camelMethod = toCamelCase(methodName)
55
- const pascalType = toPascalCase(hookType)
56
- return `${camelMethod}${pascalType}`
57
- }
58
-
59
- /**
60
- * Generate React hook name (with use prefix)
61
- * @example getReactHookName("get_message", "query") → "useGetMessageQuery"
62
- */
63
- export function getReactHookName(methodName: string, hookType: string): string {
64
- const pascalMethod = toPascalCase(methodName)
65
- const pascalType = toPascalCase(hookType)
66
- return `use${pascalMethod}${pascalType}`
67
- }
68
-
69
- /**
70
- * Generate reactor variable name
37
+ * Generate the reactor variable name for a canister.
71
38
  * @example getReactorName("backend") → "backendReactor"
39
+ * @example getReactorName("my_canister") → "myCanisterReactor"
72
40
  */
73
41
  export function getReactorName(canisterName: string): string {
74
42
  return `${toCamelCase(canisterName)}Reactor`
75
43
  }
76
44
 
77
45
  /**
78
- * Generate service type name
46
+ * Generate the service type name for a canister.
79
47
  * @example getServiceTypeName("backend") → "BackendService"
48
+ * @example getServiceTypeName("my_canister") → "MyCanisterService"
80
49
  */
81
50
  export function getServiceTypeName(canisterName: string): string {
82
51
  return `${toPascalCase(canisterName)}Service`
83
52
  }
53
+
54
+ /**
55
+ * Generate the hook name prefix (PascalCase canister name).
56
+ * Used when naming the destructured hooks: `use<Prefix>Query`, etc.
57
+ * @example getHookPrefix("my_canister") → "MyCanister"
58
+ */
59
+ export function getHookPrefix(canisterName: string): string {
60
+ return toPascalCase(canisterName)
61
+ }
package/src/parser.ts ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Candid Parser Utilities
3
+ *
4
+ * Parses Candid .did files to extract service method signatures.
5
+ * Used by the CLI for listing methods and by advanced generators.
6
+ */
7
+
8
+ import { didToJs } from "@ic-reactor/parser"
9
+ import fs from "node:fs"
10
+
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+ // TYPES
13
+ // ─────────────────────────────────────────────────────────────────────────────
14
+
15
+ export type MethodType = "query" | "mutation"
16
+
17
+ export interface MethodInfo {
18
+ /** Method name as it appears in the Candid service */
19
+ name: string
20
+ /** "query" for read-only calls, "mutation" for update calls */
21
+ type: MethodType
22
+ /** True if the method takes at least one argument */
23
+ hasArgs: boolean
24
+ }
25
+
26
+ // ─────────────────────────────────────────────────────────────────────────────
27
+ // PARSERS
28
+ // ─────────────────────────────────────────────────────────────────────────────
29
+
30
+ /**
31
+ * Extract method information from raw Candid source text.
32
+ *
33
+ * Compiles the Candid to JS and inspects the IDL.Service definition.
34
+ */
35
+ export function extractMethods(didContent: string): MethodInfo[] {
36
+ try {
37
+ const jsContent = didToJs(didContent)
38
+
39
+ const methods: MethodInfo[] = []
40
+
41
+ // Find the IDL.Service({...}) body
42
+ const serviceMatch = /IDL\.Service\(\{([\s\S]*?)\}\)/.exec(jsContent)
43
+ if (!serviceMatch) return methods
44
+
45
+ const serviceBody = serviceMatch[1]
46
+
47
+ // Match each method: 'methodName': IDL.Func([args], [rets], [annotations])
48
+ const methodRegex =
49
+ /['"]([\w]+)['"]\s*:\s*IDL\.Func\(\s*\[(.*?)\],\s*\[.*?\],\s*\[(.*?)\]\)/g
50
+
51
+ let match: RegExpExecArray | null
52
+ while ((match = methodRegex.exec(serviceBody)) !== null) {
53
+ const [, name, args, annotations] = match
54
+ methods.push({
55
+ name,
56
+ type: annotations.includes("'query'") ? "query" : "mutation",
57
+ hasArgs: args.trim().length > 0,
58
+ })
59
+ }
60
+
61
+ return methods
62
+ } catch (error) {
63
+ const msg = error instanceof Error ? error.message : String(error)
64
+ throw new Error(`Failed to parse Candid: ${msg}`)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Parse a .did file from disk and return its methods.
70
+ *
71
+ * @throws if the file does not exist or fails to parse
72
+ */
73
+ export function parseDIDFile(didFilePath: string): MethodInfo[] {
74
+ if (!fs.existsSync(didFilePath)) {
75
+ throw new Error(`DID file not found: ${didFilePath}`)
76
+ }
77
+ const content = fs.readFileSync(didFilePath, "utf-8")
78
+ return extractMethods(content)
79
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Codegen Pipeline
3
+ *
4
+ * Orchestrates all generators for a single canister.
5
+ * This is the primary entry point used by @ic-reactor/cli and @ic-reactor/vite-plugin.
6
+ *
7
+ * Pipeline steps (in order):
8
+ * 1. Resolve paths (didFile, outDir)
9
+ * 2. Generate declarations (JS + .d.ts + .did copy)
10
+ * 3. Generate reactor file (index.ts)
11
+ */
12
+
13
+ import fs from "node:fs"
14
+ import path from "node:path"
15
+ import type { CanisterConfig, CodegenConfig, GeneratorResult } from "./types.js"
16
+ import { generateDeclarations } from "./generators/declarations.js"
17
+ import { generateReactorFile } from "./generators/reactor.js"
18
+
19
+ export interface PipelineOptions {
20
+ /** Canister name and config */
21
+ canisterConfig: CanisterConfig
22
+ /**
23
+ * Absolute path to the project root.
24
+ * Relative paths in `CanisterConfig` are resolved from here.
25
+ */
26
+ projectRoot: string
27
+ /**
28
+ * Global codegen config (for fallback outDir and clientManagerPath).
29
+ */
30
+ globalConfig: Pick<CodegenConfig, "outDir" | "clientManagerPath">
31
+ }
32
+
33
+ export interface PipelineResult {
34
+ canisterName: string
35
+ success: boolean
36
+ /** All files the pipeline attempted to write */
37
+ files: GeneratorResult[]
38
+ error?: string
39
+ }
40
+
41
+ /**
42
+ * Run the full codegen pipeline for a single canister.
43
+ *
44
+ * @returns PipelineResult with per-file details
45
+ */
46
+ export async function runCanisterPipeline(
47
+ options: PipelineOptions
48
+ ): Promise<PipelineResult> {
49
+ const { canisterConfig, projectRoot, globalConfig } = options
50
+ const { name, didFile, clientManagerPath } = canisterConfig
51
+
52
+ const files: GeneratorResult[] = []
53
+
54
+ // ── Resolve paths ──────────────────────────────────────────────────────────
55
+
56
+ const resolvedDidFile = path.isAbsolute(didFile)
57
+ ? didFile
58
+ : path.resolve(projectRoot, didFile)
59
+
60
+ if (!fs.existsSync(resolvedDidFile)) {
61
+ return {
62
+ canisterName: name,
63
+ success: false,
64
+ files: [],
65
+ error: `DID file not found: ${resolvedDidFile}`,
66
+ }
67
+ }
68
+
69
+ // Per-canister outDir overrides global outDir
70
+ const canisterOutDir =
71
+ canisterConfig.outDir != null
72
+ ? path.isAbsolute(canisterConfig.outDir)
73
+ ? canisterConfig.outDir
74
+ : path.resolve(projectRoot, canisterConfig.outDir)
75
+ : path.resolve(projectRoot, globalConfig.outDir, name)
76
+
77
+ // clientManagerPath falls back to global, then a safe default
78
+ const resolvedClientManagerPath =
79
+ clientManagerPath ?? globalConfig.clientManagerPath ?? "../../clients"
80
+
81
+ // ── Step 1: Declarations ───────────────────────────────────────────────────
82
+
83
+ try {
84
+ const declResult = await generateDeclarations({
85
+ didFile: resolvedDidFile,
86
+ outDir: canisterOutDir,
87
+ canisterName: name,
88
+ })
89
+
90
+ if (!declResult.success) {
91
+ return {
92
+ canisterName: name,
93
+ success: false,
94
+ files,
95
+ error: declResult.error,
96
+ }
97
+ }
98
+
99
+ files.push(...declResult.files)
100
+ } catch (err) {
101
+ return {
102
+ canisterName: name,
103
+ success: false,
104
+ files,
105
+ error: `Declarations step failed: ${err instanceof Error ? err.message : String(err)}`,
106
+ }
107
+ }
108
+
109
+ // ── Step 2: Reactor file ───────────────────────────────────────────────────
110
+
111
+ const reactorPath = path.join(canisterOutDir, "index.ts")
112
+
113
+ try {
114
+ const reactorContent = generateReactorFile({
115
+ canisterName: name,
116
+ didFile: resolvedDidFile,
117
+ clientManagerPath: resolvedClientManagerPath,
118
+ })
119
+
120
+ fs.mkdirSync(canisterOutDir, { recursive: true })
121
+ fs.writeFileSync(reactorPath, reactorContent)
122
+
123
+ files.push({ success: true, filePath: reactorPath })
124
+ } catch (err) {
125
+ files.push({
126
+ success: false,
127
+ filePath: reactorPath,
128
+ error: `Reactor generation failed: ${err instanceof Error ? err.message : String(err)}`,
129
+ })
130
+
131
+ return {
132
+ canisterName: name,
133
+ success: false,
134
+ files,
135
+ error: `Reactor step failed: ${err instanceof Error ? err.message : String(err)}`,
136
+ }
137
+ }
138
+
139
+ return {
140
+ canisterName: name,
141
+ success: true,
142
+ files,
143
+ }
144
+ }
package/src/types.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @ic-reactor/codegen — Core Types
3
+ *
4
+ * All shared types used across generators, pipeline, CLI, and vite-plugin.
5
+ */
6
+
7
+ // ═══════════════════════════════════════════════════════════════════════════
8
+ // CONFIGURATION TYPES
9
+ // ═══════════════════════════════════════════════════════════════════════════
10
+
11
+ /**
12
+ * Per-canister configuration.
13
+ * `name` is always required — it drives all naming in generators.
14
+ */
15
+ export interface CanisterConfig {
16
+ /** Canister name (used for variable names, file names, etc.) */
17
+ name: string
18
+ /** Path to the .did file (relative to project root, or absolute) */
19
+ didFile: string
20
+ /** Override output dir for this specific canister */
21
+ outDir?: string
22
+ /**
23
+ * Import path to the ClientManager (relative from the generated reactor file).
24
+ * Example: "../../clients" → `import { clientManager } from "../../clients"`
25
+ */
26
+ clientManagerPath?: string
27
+ /** Optional fixed canister ID */
28
+ canisterId?: string
29
+ }
30
+
31
+ /**
32
+ * Top-level codegen / CLI configuration (stored in `ic-reactor.json`).
33
+ */
34
+ export interface CodegenConfig {
35
+ /** JSON Schema reference */
36
+ $schema?: string
37
+ /**
38
+ * Default output directory for all canisters (relative to project root).
39
+ * Individual canisters can override via `CanisterConfig.outDir`.
40
+ */
41
+ outDir: string
42
+ /**
43
+ * Default import path for the ClientManager (relative from generated files).
44
+ * Individual canisters can override via `CanisterConfig.clientManagerPath`.
45
+ */
46
+ clientManagerPath?: string
47
+ /** Canister configurations, keyed by canister name */
48
+ canisters: Record<string, CanisterConfig>
49
+ }
50
+
51
+ // ═══════════════════════════════════════════════════════════════════════════
52
+ // GENERATOR TYPES
53
+ // ═══════════════════════════════════════════════════════════════════════════
54
+
55
+ /**
56
+ * Result of a single file-writing generator step.
57
+ */
58
+ export interface GeneratorResult {
59
+ success: boolean
60
+ /** Absolute path of the file that was (or would have been) written */
61
+ filePath: string
62
+ /** If true, skipped because the file already existed */
63
+ skipped?: boolean
64
+ error?: string
65
+ }