@ic-reactor/codegen 0.12.0 → 0.13.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.
package/dist/index.d.cts CHANGED
@@ -1,6 +1,3 @@
1
- export { generateCodecDeclarations } from './renderer.cjs';
2
- import '@ic-reactor/parser';
3
-
4
1
  /**
5
2
  * @ic-reactor/codegen — Core Types
6
3
  *
@@ -144,32 +141,126 @@ declare function getReactorName(canisterName: string): string;
144
141
  declare function getServiceTypeName(canisterName: string): string;
145
142
 
146
143
  /**
147
- * Candid Parser Utilities
144
+ * Config validation for the codegen pipeline.
145
+ *
146
+ * Every value validated here arrives from a project's `ic-reactor.json` or from
147
+ * `@ic-reactor/vite-plugin` options — that is, from a file in a repository the
148
+ * user may have merely cloned. The pipeline turns those values into filesystem
149
+ * paths it recursively deletes and into source text it writes into the user's
150
+ * bundle, so they are treated as untrusted input rather than as configuration.
148
151
  *
149
- * Parses Candid .did files to extract service method signatures.
150
- * Used by the CLI for listing methods and by advanced generators.
152
+ * Validation lives here, at the pipeline's own entry point, rather than in the
153
+ * CLI's interactive prompt: the prompt only covers one of the three entry paths
154
+ * (hand-edited config and plugin options bypass it entirely).
151
155
  */
152
- type MethodType = "query" | "mutation";
153
- interface MethodInfo {
154
- /** Method name as it appears in the Candid service */
155
- name: string;
156
- /** "query" for read-only calls, "mutation" for update calls */
157
- type: MethodType;
158
- /** True if the method takes at least one argument */
159
- hasArgs: boolean;
156
+
157
+ /**
158
+ * Thrown when a canister config would produce an unsafe path or unsafe
159
+ * generated source. Callers convert this into a `PipelineResult` error.
160
+ */
161
+ declare class CodegenConfigError extends Error {
162
+ readonly name = "CodegenConfigError";
163
+ constructor(message: string);
160
164
  }
161
165
  /**
162
- * Extract method information from raw Candid source text.
166
+ * Characters allowed in a canister name.
167
+ *
168
+ * A canister name becomes a directory segment, so this excludes path
169
+ * separators, quotes, whitespace and control characters. It deliberately does
170
+ * NOT require a leading letter: `dfx` places no restriction on canister names,
171
+ * and real projects use `_private` and `my.canister`, both of which derive
172
+ * perfectly good identifiers. Identifier validity is enforced separately, on
173
+ * the *derived* names, by {@link assertSafeCanisterName}.
174
+ */
175
+ declare const CANISTER_NAME_PATTERN: RegExp;
176
+ /**
177
+ * Assert that a canister name is safe to use as a path segment and as the stem
178
+ * of a generated identifier.
179
+ *
180
+ * Rejecting a leading digit here is what stops `2048_game` from reaching the
181
+ * generator, where it would become `export const 2048GameReactor` — invalid
182
+ * TypeScript emitted with a success status.
183
+ */
184
+ declare function assertSafeCanisterName(name: unknown): asserts name is string;
185
+ /**
186
+ * Assert that a module specifier we interpolate into an `import` statement is a
187
+ * relative path or a bare package name — never a URL.
188
+ *
189
+ * A `https://…` specifier here would make the generated module pull code from a
190
+ * remote host at import time, inside the user's own bundle.
191
+ */
192
+ declare function assertSafeModuleSpecifier(label: string, specifier: unknown): asserts specifier is string;
193
+ /**
194
+ * Derive the declarations file stem from a `.did` path and assert it is usable
195
+ * both as a file name and as the tail of the import specifier we emit.
196
+ *
197
+ * `path.basename` is happy to hand back `"."` or `".."`: a `didFile` of
198
+ * `"canisters/.."` makes the generated reactor `import … from
199
+ * "./declarations/.."`, a *directory* reference — reported as a successful
200
+ * generation, rejected by the consumer's bundler. Validating the derived
201
+ * specifier here is the same treatment `clientManagerPath` gets, for the same
202
+ * reason: it is config-supplied text that ends up inside an `import`.
203
+ */
204
+ declare function resolveDeclarationsBaseName(didFile: unknown): string;
205
+ /** Reactor classes the generator knows how to emit an import for. */
206
+ declare const REACTOR_CLASS_NAMES: readonly ReactorClassName[];
207
+ /** Runtime targets the generator knows how to emit. */
208
+ declare const CODEGEN_TARGETS: readonly CodegenTarget[];
209
+ /**
210
+ * Assert a config value is one of a closed set.
211
+ *
212
+ * `mode` and `target` are interpolated into emitted source as bare identifiers
213
+ * and module specifiers, so an unrecognized value is not merely unsupported —
214
+ * it is injected.
215
+ */
216
+ declare function assertOneOf<T extends string>(label: string, value: unknown, allowed: readonly T[]): asserts value is T;
217
+ /**
218
+ * Assert that an already-resolved absolute path lies inside `projectRoot`.
219
+ *
220
+ * Containment is decided on the real on-disk locations: `path.resolve` does not
221
+ * follow symlinks, so a purely lexical comparison can be walked out of via a
222
+ * symlink placed inside the project root.
163
223
  *
164
- * Compiles the Candid to JS and inspects the IDL.Service definition.
224
+ * @param original - the path as the user wrote it, for the error message
165
225
  */
166
- declare function extractMethods(didContent: string): MethodInfo[];
226
+ declare function assertContainedPath(label: string, resolved: string, projectRoot: string, original?: string): void;
167
227
  /**
168
- * Parse a .did file from disk and return its methods.
228
+ * Resolve `outDir` against `projectRoot` and assert the result stays inside it.
169
229
  *
170
- * @throws if the file does not exist or fails to parse
230
+ * The pipeline recursively deletes `<outDir>/declarations` before every
231
+ * generation, so an `outDir` that escapes the project root turns a config file
232
+ * into an arbitrary-directory delete.
171
233
  */
172
- declare function parseDIDFile(didFilePath: string): MethodInfo[];
234
+ declare function resolveContainedOutDir(label: string, outDir: unknown, projectRoot: string): string;
235
+ interface ValidatedCanisterPaths {
236
+ /** The validated canister name. */
237
+ name: string;
238
+ /** Absolute output directory, guaranteed to be inside `projectRoot`. */
239
+ outDir: string;
240
+ /** Validated module specifier for the client manager import. */
241
+ clientManagerPath: string;
242
+ }
243
+ interface ValidateCanisterConfigOptions {
244
+ name: unknown;
245
+ /** Per-canister `outDir`, if the config sets one. */
246
+ canisterOutDir?: unknown;
247
+ /** Global `outDir`; the canister name is appended to it when used. */
248
+ globalOutDir: unknown;
249
+ clientManagerPath: unknown;
250
+ projectRoot: string;
251
+ /** Resolved reactor class (`canisterConfig.mode`), if the config sets one. */
252
+ mode?: unknown;
253
+ /** Resolved runtime target (`canisterConfig.target` / global `target`). */
254
+ target?: unknown;
255
+ }
256
+ /**
257
+ * Validate one canister's config and return the resolved, contained paths the
258
+ * pipeline should use.
259
+ *
260
+ * Call this before any filesystem work: it is the single choke point that all
261
+ * three entry paths (CLI, vite plugin, direct API) share.
262
+ */
263
+ declare function assertSafeCanisterConfig(options: ValidateCanisterConfigOptions): ValidatedCanisterPaths;
173
264
 
174
265
  /**
175
266
  * Declarations Generator
@@ -197,17 +288,17 @@ interface DeclarationsGeneratorResult {
197
288
  files: GeneratorResult[];
198
289
  error?: string;
199
290
  }
200
- /**
201
- * Generate TypeScript declarations from a Candid file.
202
- *
203
- * Always cleans and regenerates the declarations directory to ensure
204
- * it's in sync with the source .did file.
205
- */
206
291
  declare function generateDeclarations(options: DeclarationsGeneratorOptions): Promise<DeclarationsGeneratorResult>;
207
292
  /**
208
293
  * Check if declarations already exist for a canister.
294
+ *
295
+ * Declarations are written under the *.did basename*, which need not equal the
296
+ * canister name — `{ name: "backend", didFile: "service.did" }` writes
297
+ * `declarations/service.d.ts`. Pass `didFile` for an exact answer; without it
298
+ * this falls back to any `.d.ts` in the directory, because a bare
299
+ * `<canisterName>.d.ts` check reports "missing" for perfectly good output.
209
300
  */
210
- declare function declarationsExist(outDir: string, canisterName: string): boolean;
301
+ declare function declarationsExist(outDir: string, canisterName: string, didFile?: string): boolean;
211
302
 
212
303
  /**
213
304
  * Reactor File Generator
@@ -286,4 +377,4 @@ interface ClientGeneratorOptions {
286
377
  */
287
378
  declare function generateClientFile(options?: ClientGeneratorOptions): string;
288
379
 
289
- export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type CodegenTarget, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorClassName, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorEntryFile, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
380
+ export { CANISTER_NAME_PATTERN, CODEGEN_TARGETS, type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, CodegenConfigError, type CodegenTarget, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type PipelineOptions, type PipelineResult, REACTOR_CLASS_NAMES, type ReactorClassName, type ReactorGeneratorOptions, type ValidateCanisterConfigOptions, type ValidatedCanisterPaths, assertContainedPath, assertOneOf, assertSafeCanisterConfig, assertSafeCanisterName, assertSafeModuleSpecifier, declarationsExist, generateClientFile, generateDeclarations, generateReactorEntryFile, generateReactorFile, getReactorName, getServiceTypeName, resolveContainedOutDir, resolveDeclarationsBaseName, runCanisterPipeline, toPascalCase };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,3 @@
1
- export { generateCodecDeclarations } from './renderer.js';
2
- import '@ic-reactor/parser';
3
-
4
1
  /**
5
2
  * @ic-reactor/codegen — Core Types
6
3
  *
@@ -144,32 +141,126 @@ declare function getReactorName(canisterName: string): string;
144
141
  declare function getServiceTypeName(canisterName: string): string;
145
142
 
146
143
  /**
147
- * Candid Parser Utilities
144
+ * Config validation for the codegen pipeline.
145
+ *
146
+ * Every value validated here arrives from a project's `ic-reactor.json` or from
147
+ * `@ic-reactor/vite-plugin` options — that is, from a file in a repository the
148
+ * user may have merely cloned. The pipeline turns those values into filesystem
149
+ * paths it recursively deletes and into source text it writes into the user's
150
+ * bundle, so they are treated as untrusted input rather than as configuration.
148
151
  *
149
- * Parses Candid .did files to extract service method signatures.
150
- * Used by the CLI for listing methods and by advanced generators.
152
+ * Validation lives here, at the pipeline's own entry point, rather than in the
153
+ * CLI's interactive prompt: the prompt only covers one of the three entry paths
154
+ * (hand-edited config and plugin options bypass it entirely).
151
155
  */
152
- type MethodType = "query" | "mutation";
153
- interface MethodInfo {
154
- /** Method name as it appears in the Candid service */
155
- name: string;
156
- /** "query" for read-only calls, "mutation" for update calls */
157
- type: MethodType;
158
- /** True if the method takes at least one argument */
159
- hasArgs: boolean;
156
+
157
+ /**
158
+ * Thrown when a canister config would produce an unsafe path or unsafe
159
+ * generated source. Callers convert this into a `PipelineResult` error.
160
+ */
161
+ declare class CodegenConfigError extends Error {
162
+ readonly name = "CodegenConfigError";
163
+ constructor(message: string);
160
164
  }
161
165
  /**
162
- * Extract method information from raw Candid source text.
166
+ * Characters allowed in a canister name.
167
+ *
168
+ * A canister name becomes a directory segment, so this excludes path
169
+ * separators, quotes, whitespace and control characters. It deliberately does
170
+ * NOT require a leading letter: `dfx` places no restriction on canister names,
171
+ * and real projects use `_private` and `my.canister`, both of which derive
172
+ * perfectly good identifiers. Identifier validity is enforced separately, on
173
+ * the *derived* names, by {@link assertSafeCanisterName}.
174
+ */
175
+ declare const CANISTER_NAME_PATTERN: RegExp;
176
+ /**
177
+ * Assert that a canister name is safe to use as a path segment and as the stem
178
+ * of a generated identifier.
179
+ *
180
+ * Rejecting a leading digit here is what stops `2048_game` from reaching the
181
+ * generator, where it would become `export const 2048GameReactor` — invalid
182
+ * TypeScript emitted with a success status.
183
+ */
184
+ declare function assertSafeCanisterName(name: unknown): asserts name is string;
185
+ /**
186
+ * Assert that a module specifier we interpolate into an `import` statement is a
187
+ * relative path or a bare package name — never a URL.
188
+ *
189
+ * A `https://…` specifier here would make the generated module pull code from a
190
+ * remote host at import time, inside the user's own bundle.
191
+ */
192
+ declare function assertSafeModuleSpecifier(label: string, specifier: unknown): asserts specifier is string;
193
+ /**
194
+ * Derive the declarations file stem from a `.did` path and assert it is usable
195
+ * both as a file name and as the tail of the import specifier we emit.
196
+ *
197
+ * `path.basename` is happy to hand back `"."` or `".."`: a `didFile` of
198
+ * `"canisters/.."` makes the generated reactor `import … from
199
+ * "./declarations/.."`, a *directory* reference — reported as a successful
200
+ * generation, rejected by the consumer's bundler. Validating the derived
201
+ * specifier here is the same treatment `clientManagerPath` gets, for the same
202
+ * reason: it is config-supplied text that ends up inside an `import`.
203
+ */
204
+ declare function resolveDeclarationsBaseName(didFile: unknown): string;
205
+ /** Reactor classes the generator knows how to emit an import for. */
206
+ declare const REACTOR_CLASS_NAMES: readonly ReactorClassName[];
207
+ /** Runtime targets the generator knows how to emit. */
208
+ declare const CODEGEN_TARGETS: readonly CodegenTarget[];
209
+ /**
210
+ * Assert a config value is one of a closed set.
211
+ *
212
+ * `mode` and `target` are interpolated into emitted source as bare identifiers
213
+ * and module specifiers, so an unrecognized value is not merely unsupported —
214
+ * it is injected.
215
+ */
216
+ declare function assertOneOf<T extends string>(label: string, value: unknown, allowed: readonly T[]): asserts value is T;
217
+ /**
218
+ * Assert that an already-resolved absolute path lies inside `projectRoot`.
219
+ *
220
+ * Containment is decided on the real on-disk locations: `path.resolve` does not
221
+ * follow symlinks, so a purely lexical comparison can be walked out of via a
222
+ * symlink placed inside the project root.
163
223
  *
164
- * Compiles the Candid to JS and inspects the IDL.Service definition.
224
+ * @param original - the path as the user wrote it, for the error message
165
225
  */
166
- declare function extractMethods(didContent: string): MethodInfo[];
226
+ declare function assertContainedPath(label: string, resolved: string, projectRoot: string, original?: string): void;
167
227
  /**
168
- * Parse a .did file from disk and return its methods.
228
+ * Resolve `outDir` against `projectRoot` and assert the result stays inside it.
169
229
  *
170
- * @throws if the file does not exist or fails to parse
230
+ * The pipeline recursively deletes `<outDir>/declarations` before every
231
+ * generation, so an `outDir` that escapes the project root turns a config file
232
+ * into an arbitrary-directory delete.
171
233
  */
172
- declare function parseDIDFile(didFilePath: string): MethodInfo[];
234
+ declare function resolveContainedOutDir(label: string, outDir: unknown, projectRoot: string): string;
235
+ interface ValidatedCanisterPaths {
236
+ /** The validated canister name. */
237
+ name: string;
238
+ /** Absolute output directory, guaranteed to be inside `projectRoot`. */
239
+ outDir: string;
240
+ /** Validated module specifier for the client manager import. */
241
+ clientManagerPath: string;
242
+ }
243
+ interface ValidateCanisterConfigOptions {
244
+ name: unknown;
245
+ /** Per-canister `outDir`, if the config sets one. */
246
+ canisterOutDir?: unknown;
247
+ /** Global `outDir`; the canister name is appended to it when used. */
248
+ globalOutDir: unknown;
249
+ clientManagerPath: unknown;
250
+ projectRoot: string;
251
+ /** Resolved reactor class (`canisterConfig.mode`), if the config sets one. */
252
+ mode?: unknown;
253
+ /** Resolved runtime target (`canisterConfig.target` / global `target`). */
254
+ target?: unknown;
255
+ }
256
+ /**
257
+ * Validate one canister's config and return the resolved, contained paths the
258
+ * pipeline should use.
259
+ *
260
+ * Call this before any filesystem work: it is the single choke point that all
261
+ * three entry paths (CLI, vite plugin, direct API) share.
262
+ */
263
+ declare function assertSafeCanisterConfig(options: ValidateCanisterConfigOptions): ValidatedCanisterPaths;
173
264
 
174
265
  /**
175
266
  * Declarations Generator
@@ -197,17 +288,17 @@ interface DeclarationsGeneratorResult {
197
288
  files: GeneratorResult[];
198
289
  error?: string;
199
290
  }
200
- /**
201
- * Generate TypeScript declarations from a Candid file.
202
- *
203
- * Always cleans and regenerates the declarations directory to ensure
204
- * it's in sync with the source .did file.
205
- */
206
291
  declare function generateDeclarations(options: DeclarationsGeneratorOptions): Promise<DeclarationsGeneratorResult>;
207
292
  /**
208
293
  * Check if declarations already exist for a canister.
294
+ *
295
+ * Declarations are written under the *.did basename*, which need not equal the
296
+ * canister name — `{ name: "backend", didFile: "service.did" }` writes
297
+ * `declarations/service.d.ts`. Pass `didFile` for an exact answer; without it
298
+ * this falls back to any `.d.ts` in the directory, because a bare
299
+ * `<canisterName>.d.ts` check reports "missing" for perfectly good output.
209
300
  */
210
- declare function declarationsExist(outDir: string, canisterName: string): boolean;
301
+ declare function declarationsExist(outDir: string, canisterName: string, didFile?: string): boolean;
211
302
 
212
303
  /**
213
304
  * Reactor File Generator
@@ -286,4 +377,4 @@ interface ClientGeneratorOptions {
286
377
  */
287
378
  declare function generateClientFile(options?: ClientGeneratorOptions): string;
288
379
 
289
- export { type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, type CodegenTarget, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type MethodInfo, type MethodType, type PipelineOptions, type PipelineResult, type ReactorClassName, type ReactorGeneratorOptions, declarationsExist, extractMethods, generateClientFile, generateDeclarations, generateReactorEntryFile, generateReactorFile, getReactorName, getServiceTypeName, parseDIDFile, runCanisterPipeline, toPascalCase };
380
+ export { CANISTER_NAME_PATTERN, CODEGEN_TARGETS, type CanisterConfig, type ClientGeneratorOptions, type CodegenConfig, CodegenConfigError, type CodegenTarget, type DeclarationsGeneratorOptions, type DeclarationsGeneratorResult, type GeneratorResult, type PipelineOptions, type PipelineResult, REACTOR_CLASS_NAMES, type ReactorClassName, type ReactorGeneratorOptions, type ValidateCanisterConfigOptions, type ValidatedCanisterPaths, assertContainedPath, assertOneOf, assertSafeCanisterConfig, assertSafeCanisterName, assertSafeModuleSpecifier, declarationsExist, generateClientFile, generateDeclarations, generateReactorEntryFile, generateReactorFile, getReactorName, getServiceTypeName, resolveContainedOutDir, resolveDeclarationsBaseName, runCanisterPipeline, toPascalCase };