diff --git a/README.md b/README.md index 162ba0d..020b5f5 100644 --- a/README.md +++ b/README.md @@ -86,58 +86,43 @@ yarn clean ## Packages -### `@openzeppelin/compact-tools-cli` (packages/cli) +### `@openzeppelin/compact-tools-cli` ([packages/cli](./packages/cli)) -Utilities and CLIs around the Compact compiler and builder. +CLI utilities for compiling and building Compact smart contracts. -- Binaries provided: - - `compact-compiler` → `packages/cli/dist/runCompiler.js` - - `compact-builder` → `packages/cli/dist/runBuilder.js` - -Useful commands: +**Quickstart:** ```bash -# From repo root (via Turbo filters) -yarn compact - -# Or inside the package -cd packages/cli -yarn build # compile TypeScript -yarn test # run unit tests -yarn types # type-check only -``` +# Compile all .compact files +compact-compiler -After building, you can invoke the CLIs directly, for example: +# Skip ZK proofs for faster development builds +compact-compiler --skip-zk -```bash -node dist/runCompiler.js --help -node dist/runBuilder.js --help -``` +# Compile specific directory +compact-compiler --dir security -### `@openzeppelin/compact-tools-simulator` (packages/simulator) +# Full build (compile + TypeScript + copy artifacts) +compact-builder +``` -A local simulator to execute Compact contracts in tests. +See [packages/cli/README.md](./packages/cli/README.md) for full documentation including all options, programmatic API, and examples. -Build and test: +### `@openzeppelin/compact-tools-simulator` ([packages/simulator](./packages/simulator)) -```bash -cd packages/simulator -yarn build -yarn test -``` +TypeScript simulator for testing Compact contracts locally. -Minimal usage example: +**Quickstart:** ```ts import { createSimulator } from '@openzeppelin/compact-tools-simulator'; -// Create a simulator instance (see package docs and tests for full examples) const simulator = createSimulator({}); - -// Use simulator to deploy/execute contract circuits, inspect state, etc. -// (Refer to `packages/simulator/src/integration` and `src/unit` tests.) +// Deploy and execute contract circuits, inspect state, etc. ``` +See package tests in `packages/simulator/src/integration` and `src/unit` for full examples. + ## Contributing Before opening a PR, please read `CODE_OF_CONDUCT.md`. Use the root scripts to build, test, and format. For targeted work inside a package, run the scripts in that package directory. @@ -145,5 +130,3 @@ Before opening a PR, please read `CODE_OF_CONDUCT.md`. Use the root scripts to b ## License MIT - - diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..9c76f81 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,250 @@ +# @openzeppelin/compact-tools-cli + +CLI utilities for compiling and building Compact smart contracts. + +## Installation + +Until published to npm, use via git submodule or local path: + +```bash +# As a local dependency +yarn add @openzeppelin/compact-tools-cli@file:./compact-tools/packages/cli + +# Or invoke directly after building +node compact-tools/packages/cli/dist/runCompiler.js +``` + +## Requirements + +- Node.js >= 20 +- Midnight Compact toolchain installed and available in `PATH` + +Verify your Compact installation: + +```bash +$ compact compile --version +Compactc version: 0.26.0 +``` + +## Binaries + +This package provides two CLI binaries: + +| Binary | Script | Description | +|--------|--------|-------------| +| `compact-compiler` | `dist/runCompiler.js` | Compile `.compact` files to artifacts | +| `compact-builder` | `dist/runBuilder.js` | Compile + build TypeScript + copy artifacts | + +## Compiler CLI + +### Usage + +```bash +compact-compiler [options] +``` + +### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--dir ` | Compile specific subdirectory within src | (all) | +| `--src ` | Source directory containing `.compact` files | `src` | +| `--out ` | Output directory for compiled artifacts | `artifacts` | +| `--hierarchical` | Preserve source directory structure in output | `false` | +| `--skip-zk` | Skip zero-knowledge proof generation | `false` | +| `+` | Use specific toolchain version (e.g., `+0.26.0`) | (default) | + +### Environment Variables + +| Variable | Description | +|----------|-------------| +| `SKIP_ZK=true` | Equivalent to `--skip-zk` flag | + +### Artifact Output Structure + +**Default (flattened):** All contract artifacts go directly under the output directory. + +``` +src/ + access/ + AccessControl.compact + token/ + Token.compact + +artifacts/ # Flattened output + AccessControl/ + Token/ +``` + +**Hierarchical (`--hierarchical`):** Preserves source directory structure. + +``` +artifacts/ # Hierarchical output + access/ + AccessControl/ + token/ + Token/ +``` + +### Examples + +```bash +# Compile all contracts (flattened output) +compact-compiler + +# Compile with hierarchical artifact structure +compact-compiler --hierarchical + +# Compile specific directory only +compact-compiler --dir security + +# Skip ZK proof generation (faster, for development) +compact-compiler --skip-zk + +# Use specific toolchain version +compact-compiler +0.26.0 + +# Custom source and output directories +compact-compiler --src contracts --out build + +# Combine options +compact-compiler --dir access --skip-zk --hierarchical + +# Use environment variable +SKIP_ZK=true compact-compiler +``` + +## Builder CLI + +The builder runs the compiler as a prerequisite, then executes additional build steps: + +1. Compile `.compact` files (via `compact-compiler`) +2. Compile TypeScript (`tsc --project tsconfig.build.json`) +3. Copy artifacts to `dist/artifacts/` +4. Copy and clean `.compact` files to `dist/` + +### Usage + +```bash +compact-builder [options] +``` + +Accepts all compiler options except `--skip-zk` (builds always include ZK proofs). + +### Examples + +```bash +# Full build +compact-builder + +# Build specific directory +compact-builder --dir token + +# Build with custom directories +compact-builder --src contracts --out build +``` + +## Programmatic API + +The compiler can be used programmatically: + +```typescript +import { CompactCompiler } from '@openzeppelin/compact-tools-cli'; + +// Using options object +const compiler = new CompactCompiler({ + flags: '--skip-zk', + targetDir: 'security', + version: '0.26.0', + hierarchical: true, + srcDir: 'src', + outDir: 'artifacts', +}); + +await compiler.compile(); + +// Using factory method (parses CLI-style args) +const compiler = CompactCompiler.fromArgs([ + '--dir', 'security', + '--skip-zk', + '+0.26.0' +]); + +await compiler.compile(); +``` + +### Classes and Types + +```typescript +// Main compiler class +class CompactCompiler { + constructor(options?: CompilerOptions, execFn?: ExecFunction); + static fromArgs(args: string[], env?: NodeJS.ProcessEnv): CompactCompiler; + static parseArgs(args: string[], env?: NodeJS.ProcessEnv): CompilerOptions; + compile(): Promise; + validateEnvironment(): Promise; +} + +// Builder class +class CompactBuilder { + constructor(options?: CompilerOptions); + static fromArgs(args: string[], env?: NodeJS.ProcessEnv): CompactBuilder; + build(): Promise; +} + +// Options interface +interface CompilerOptions { + flags?: string; // Compiler flags (e.g., '--skip-zk --verbose') + targetDir?: string; // Subdirectory within srcDir to compile + version?: string; // Toolchain version (e.g., '0.26.0') + hierarchical?: boolean; // Preserve directory structure in output + srcDir?: string; // Source directory (default: 'src') + outDir?: string; // Output directory (default: 'artifacts') +} +``` + +### Error Types + +```typescript +import { + CompactCliNotFoundError, // Compact CLI not in PATH + CompilationError, // Compilation failed (includes file path) + DirectoryNotFoundError, // Target directory doesn't exist +} from '@openzeppelin/compact-tools-cli'; +``` + +## Development + +```bash +cd packages/cli + +# Build +yarn build + +# Type-check only +yarn types + +# Run tests +yarn test + +# Clean +yarn clean +``` + +## Output Example + +``` +ℹ [COMPILE] Compact compiler started +ℹ [COMPILE] Compact developer tools: compact 0.1.0 +ℹ [COMPILE] Compact toolchain: Compactc version: 0.26.0 +ℹ [COMPILE] Found 2 .compact file(s) to compile +✔ [COMPILE] [1/2] Compiled AccessControl.compact + Compactc version: 0.26.0 +✔ [COMPILE] [2/2] Compiled Token.compact + Compactc version: 0.26.0 +``` + +## License + +MIT + diff --git a/packages/cli/src/Builder.ts b/packages/cli/src/Builder.ts index 6b0204e..f94027f 100755 --- a/packages/cli/src/Builder.ts +++ b/packages/cli/src/Builder.ts @@ -4,12 +4,19 @@ import { exec } from 'node:child_process'; import { promisify } from 'node:util'; import chalk from 'chalk'; import ora, { type Ora } from 'ora'; -import { CompactCompiler } from './Compiler.ts'; +import { CompactCompiler, type CompilerOptions } from './Compiler.ts'; import { isPromisifiedChildProcessError } from './types/errors.ts'; // Promisified exec for async execution const execAsync = promisify(exec); +/** + * Configuration options for the Builder CLI. + * Inherits from CompilerOptions but excludes `flags` (which would allow --skip-zk). + * Builds should always include ZK proofs. + */ +export type BuilderOptions = Omit; + /** * A class to handle the build process for a project. * Runs CompactCompiler as a prerequisite, then executes build steps (TypeScript compilation, @@ -21,7 +28,7 @@ const execAsync = promisify(exec); * * @example * ```typescript - * const builder = new ProjectBuilder('--skip-zk'); // Optional flags for compactc + * const builder = new CompactBuilder({ flags: '--skip-zk' }); * builder.build().catch(err => console.error(err)); * ``` * @@ -56,7 +63,7 @@ const execAsync = promisify(exec); * ``` */ export class CompactBuilder { - private readonly compilerFlags: string; + private readonly options: BuilderOptions; private readonly steps: Array<{ cmd: string; msg: string; shell?: string }> = [ { @@ -76,11 +83,27 @@ export class CompactBuilder { ]; /** - * Constructs a new ProjectBuilder instance. - * @param compilerFlags - Optional space-separated string of `compactc` flags (e.g., "--skip-zk") + * Constructs a new CompactBuilder instance. + * @param options - Compiler options (flags, srcDir, outDir, hierarchical, etc.) + */ + constructor(options: CompilerOptions = {}) { + this.options = options; + } + + /** + * Factory method to create a CompactBuilder from command-line arguments. + * Reuses CompactCompiler.parseArgs for consistent argument parsing. + * + * @param args - Array of command-line arguments + * @param env - Environment variables (defaults to process.env) + * @returns New CompactBuilder instance configured from arguments */ - constructor(compilerFlags = '') { - this.compilerFlags = compilerFlags; + static fromArgs( + args: string[], + env: NodeJS.ProcessEnv = process.env, + ): CompactBuilder { + const options = CompactCompiler.parseArgs(args, env); + return new CompactBuilder(options); } /** @@ -92,7 +115,7 @@ export class CompactBuilder { */ public async build(): Promise { // Run compact compilation as a prerequisite - const compiler = new CompactCompiler(this.compilerFlags); + const compiler = new CompactCompiler(this.options); await compiler.compile(); // Proceed with build steps diff --git a/packages/cli/src/Compiler.ts b/packages/cli/src/Compiler.ts index de57236..e20d0fb 100755 --- a/packages/cli/src/Compiler.ts +++ b/packages/cli/src/Compiler.ts @@ -3,7 +3,7 @@ import { exec as execCallback } from 'node:child_process'; import { existsSync } from 'node:fs'; import { readdir } from 'node:fs/promises'; -import { basename, join, relative } from 'node:path'; +import { basename, dirname, join, relative } from 'node:path'; import { promisify } from 'node:util'; import chalk from 'chalk'; import ora from 'ora'; @@ -14,10 +14,10 @@ import { isPromisifiedChildProcessError, } from './types/errors.ts'; -/** Source directory containing .compact files */ -const SRC_DIR: string = 'src'; -/** Output directory for compiled artifacts */ -const ARTIFACTS_DIR: string = 'artifacts'; +/** Default source directory containing .compact files */ +const DEFAULT_SRC_DIR = 'src'; +/** Default output directory for compiled artifacts */ +const DEFAULT_OUT_DIR = 'artifacts'; /** * Function type for executing shell commands. @@ -30,6 +30,45 @@ export type ExecFunction = ( command: string, ) => Promise<{ stdout: string; stderr: string }>; +/** + * Configuration options for the Compact compiler CLI. + * + * @interface CompilerOptions + * @example + * ```typescript + * const options: CompilerOptions = { + * flags: '--skip-zk --verbose', + * targetDir: 'security', + * version: '0.26.0', + * hierarchical: false, + * }; + * ``` + */ +export interface CompilerOptions { + /** Compiler flags to pass to the Compact CLI (e.g., '--skip-zk --verbose') */ + flags?: string; + /** Optional subdirectory within srcDir to compile (e.g., 'security', 'token') */ + targetDir?: string; + /** Optional toolchain version to use (e.g., '0.26.0') */ + version?: string; + /** + * Whether to preserve directory structure in artifacts output. + * - `false` (default): Flattened output - `//` + * - `true`: Hierarchical output - `///` + */ + hierarchical?: boolean; + /** Source directory containing .compact files (default: 'src') */ + srcDir?: string; + /** Output directory for compiled artifacts (default: 'artifacts') */ + outDir?: string; +} + +/** Resolved compiler options with defaults applied */ +type ResolvedCompilerOptions = Required< + Pick +> & + Pick; + /** * Service responsible for validating the Compact CLI environment. * Checks CLI availability, retrieves version information, and ensures @@ -155,15 +194,26 @@ export class EnvironmentValidator { * @class FileDiscovery * @example * ```typescript - * const discovery = new FileDiscovery(); + * const discovery = new FileDiscovery('src'); * const files = await discovery.getCompactFiles('src/security'); * console.log(`Found ${files.length} .compact files`); * ``` */ export class FileDiscovery { + private srcDir: string; + + /** + * Creates a new FileDiscovery instance. + * + * @param srcDir - Base source directory for relative path calculation (default: 'src') + */ + constructor(srcDir: string = DEFAULT_SRC_DIR) { + this.srcDir = srcDir; + } + /** * Recursively discovers all .compact files in a directory. - * Returns relative paths from the SRC_DIR for consistent processing. + * Returns relative paths from the srcDir for consistent processing. * * @param dir - Directory path to search (relative or absolute) * @returns Promise resolving to array of relative file paths @@ -184,7 +234,7 @@ export class FileDiscovery { } if (entry.isFile() && fullPath.endsWith('.compact')) { - return [relative(SRC_DIR, fullPath)]; + return [relative(this.srcDir, fullPath)]; } return []; } catch (err) { @@ -204,6 +254,18 @@ export class FileDiscovery { } } +/** + * Options for configuring the CompilerService. + * A subset of CompilerOptions relevant to the compilation process. + */ +export type CompilerServiceOptions = Pick< + CompilerOptions, + 'hierarchical' | 'srcDir' | 'outDir' +>; + +/** Resolved options for CompilerService with defaults applied */ +type ResolvedCompilerServiceOptions = Required; + /** * Service responsible for compiling individual .compact files. * Handles command construction, execution, and error processing. @@ -222,21 +284,34 @@ export class FileDiscovery { */ export class CompilerService { private execFn: ExecFunction; + private options: ResolvedCompilerServiceOptions; /** * Creates a new CompilerService instance. * * @param execFn - Function to execute shell commands (defaults to promisified child_process.exec) + * @param options - Compiler service options */ - constructor(execFn: ExecFunction = promisify(execCallback)) { + constructor( + execFn: ExecFunction = promisify(execCallback), + options: CompilerServiceOptions = {}, + ) { this.execFn = execFn; + this.options = { + hierarchical: options.hierarchical ?? false, + srcDir: options.srcDir ?? DEFAULT_SRC_DIR, + outDir: options.outDir ?? DEFAULT_OUT_DIR, + }; } /** * Compiles a single .compact file using the Compact CLI. * Constructs the appropriate command with flags and version, then executes it. * - * @param file - Relative path to the .compact file from SRC_DIR + * By default, uses flattened output structure where all artifacts go to `//`. + * When `hierarchical` is true, preserves source directory structure: `///`. + * + * @param file - Relative path to the .compact file from srcDir * @param flags - Space-separated compiler flags (e.g., '--skip-zk --verbose') * @param version - Optional specific toolchain version to use * @returns Promise resolving to compilation output (stdout/stderr) @@ -262,8 +337,16 @@ export class CompilerService { flags: string, version?: string, ): Promise<{ stdout: string; stderr: string }> { - const inputPath = join(SRC_DIR, file); - const outputDir = join(ARTIFACTS_DIR, basename(file, '.compact')); + const inputPath = join(this.options.srcDir, file); + const fileDir = dirname(file); + const fileName = basename(file, '.compact'); + + // Flattened (default): // + // Hierarchical: /// + const outputDir = + this.options.hierarchical && fileDir !== '.' + ? join(this.options.outDir, fileDir, fileName) + : join(this.options.outDir, fileName); const versionFlag = version ? `+${version}` : ''; const flagsStr = flags ? ` ${flags}` : ''; @@ -414,18 +497,27 @@ export const UIService = { * - Progress reporting and user feedback * - Support for compiler flags and toolchain versions * - Environment variable integration + * - Configurable artifact output structure (flattened or hierarchical) * * @class CompactCompiler * @example * ```typescript - * // Basic usage - * const compiler = new CompactCompiler('--skip-zk', 'security', '0.26.0'); + * // Basic usage with options object (flattened artifacts by default) + * const compiler = new CompactCompiler({ + * flags: '--skip-zk', + * targetDir: 'security', + * version: '0.26.0', + * }); * await compiler.compile(); * * // Factory method usage * const compiler = CompactCompiler.fromArgs(['--dir', 'security', '--skip-zk']); * await compiler.compile(); * + * // With hierarchical artifacts structure + * const compiler = CompactCompiler.fromArgs(['--hierarchical', '--skip-zk']); + * await compiler.compile(); + * * // With environment variables * process.env.SKIP_ZK = 'true'; * const compiler = CompactCompiler.fromArgs(['--dir', 'token']); @@ -439,49 +531,123 @@ export class CompactCompiler { private readonly fileDiscovery: FileDiscovery; /** Compilation execution service */ private readonly compilerService: CompilerService; - - /** Compiler flags to pass to the Compact CLI */ - private readonly flags: string; - /** Optional target directory to limit compilation scope */ - private readonly targetDir?: string; - /** Optional specific toolchain version to use */ - private readonly version?: string; + /** Compiler options */ + private readonly options: ResolvedCompilerOptions; /** * Creates a new CompactCompiler instance with specified configuration. * - * @param flags - Space-separated compiler flags (e.g., '--skip-zk --verbose') - * @param targetDir - Optional subdirectory within src/ to compile (e.g., 'security', 'token') - * @param version - Optional toolchain version to use (e.g., '0.26.0') + * @param options - Compiler configuration options * @param execFn - Optional custom exec function for dependency injection * @example * ```typescript - * // Compile all files with flags - * const compiler = new CompactCompiler('--skip-zk --verbose'); + * // Compile all files with flags (flattened artifacts) + * const compiler = new CompactCompiler({ flags: '--skip-zk --verbose' }); * * // Compile specific directory - * const compiler = new CompactCompiler('', 'security'); + * const compiler = new CompactCompiler({ targetDir: 'security' }); * * // Compile with specific version - * const compiler = new CompactCompiler('--skip-zk', undefined, '0.26.0'); + * const compiler = new CompactCompiler({ flags: '--skip-zk', version: '0.26.0' }); + * + * // Compile with hierarchical artifacts structure + * const compiler = new CompactCompiler({ flags: '--skip-zk', hierarchical: true }); * * // For testing with custom exec function * const mockExec = vi.fn(); - * const compiler = new CompactCompiler('', undefined, undefined, mockExec); + * const compiler = new CompactCompiler({}, mockExec); * ``` */ - constructor( - flags = '', - targetDir?: string, - version?: string, - execFn?: ExecFunction, - ) { - this.flags = flags.trim(); - this.targetDir = targetDir; - this.version = version; + constructor(options: CompilerOptions = {}, execFn?: ExecFunction) { + this.options = { + flags: (options.flags ?? '').trim(), + targetDir: options.targetDir, + version: options.version, + hierarchical: options.hierarchical ?? false, + srcDir: options.srcDir ?? DEFAULT_SRC_DIR, + outDir: options.outDir ?? DEFAULT_OUT_DIR, + }; this.environmentValidator = new EnvironmentValidator(execFn); - this.fileDiscovery = new FileDiscovery(); - this.compilerService = new CompilerService(execFn); + this.fileDiscovery = new FileDiscovery(this.options.srcDir); + this.compilerService = new CompilerService(execFn, { + hierarchical: this.options.hierarchical, + srcDir: this.options.srcDir, + outDir: this.options.outDir, + }); + } + + /** + * Parses command-line arguments into a CompilerOptions object. + * + * Supported argument patterns: + * - `--dir ` - Target specific subdirectory within srcDir + * - `--src ` - Source directory containing .compact files (default: 'src') + * - `--out ` - Output directory for artifacts (default: 'artifacts') + * - `--hierarchical` - Preserve source directory structure in artifacts output + * - `+` - Use specific toolchain version + * - Other arguments - Treated as compiler flags + * - `SKIP_ZK=true` environment variable - Adds --skip-zk flag + * + * @param args - Array of command-line arguments + * @param env - Environment variables (defaults to process.env) + * @returns Parsed CompilerOptions object + * @throws {Error} If --dir, --src, or --out flag is provided without a value + */ + static parseArgs( + args: string[], + env: NodeJS.ProcessEnv = process.env, + ): CompilerOptions { + const options: CompilerOptions = { + hierarchical: false, + }; + const flags: string[] = []; + + if (env.SKIP_ZK === 'true') { + flags.push('--skip-zk'); + } + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--dir') { + const valueExists = + i + 1 < args.length && !args[i + 1].startsWith('--'); + if (valueExists) { + options.targetDir = args[i + 1]; + i++; + } else { + throw new Error('--dir flag requires a directory name'); + } + } else if (args[i] === '--src') { + const valueExists = + i + 1 < args.length && !args[i + 1].startsWith('--'); + if (valueExists) { + options.srcDir = args[i + 1]; + i++; + } else { + throw new Error('--src flag requires a directory path'); + } + } else if (args[i] === '--out') { + const valueExists = + i + 1 < args.length && !args[i + 1].startsWith('--'); + if (valueExists) { + options.outDir = args[i + 1]; + i++; + } else { + throw new Error('--out flag requires a directory path'); + } + } else if (args[i] === '--hierarchical') { + options.hierarchical = true; + } else if (args[i].startsWith('+')) { + options.version = args[i].slice(1); + } else { + // Only add flag if it's not already present + if (!flags.includes(args[i])) { + flags.push(args[i]); + } + } + } + + options.flags = flags.join(' '); + return options; } /** @@ -489,7 +655,10 @@ export class CompactCompiler { * Parses various argument formats including flags, directories, versions, and environment variables. * * Supported argument patterns: - * - `--dir ` - Target specific directory + * - `--dir ` - Target specific subdirectory within srcDir + * - `--src ` - Source directory containing .compact files (default: 'src') + * - `--out ` - Output directory for artifacts (default: 'artifacts') + * - `--hierarchical` - Preserve source directory structure in artifacts output * - `+` - Use specific toolchain version * - Other arguments - Treated as compiler flags * - `SKIP_ZK=true` environment variable - Adds --skip-zk flag @@ -497,7 +666,7 @@ export class CompactCompiler { * @param args - Array of command-line arguments * @param env - Environment variables (defaults to process.env) * @returns New CompactCompiler instance configured from arguments - * @throws {Error} If --dir flag is provided without a directory name + * @throws {Error} If --dir, --src, or --out flag is provided without a value * @example * ```typescript * // Parse command line: compact-compiler --dir security --skip-zk +0.26.0 @@ -507,6 +676,19 @@ export class CompactCompiler { * '+0.26.0' * ]); * + * // With custom source and output directories + * const compiler = CompactCompiler.fromArgs([ + * '--src', 'contracts', + * '--out', 'build/artifacts', + * '--skip-zk' + * ]); + * + * // With hierarchical artifacts structure + * const compiler = CompactCompiler.fromArgs([ + * '--hierarchical', + * '--skip-zk' + * ]); + * * // With environment variable * const compiler = CompactCompiler.fromArgs( * ['--dir', 'token'], @@ -521,35 +703,8 @@ export class CompactCompiler { args: string[], env: NodeJS.ProcessEnv = process.env, ): CompactCompiler { - let targetDir: string | undefined; - const flags: string[] = []; - let version: string | undefined; - - if (env.SKIP_ZK === 'true') { - flags.push('--skip-zk'); - } - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--dir') { - const dirNameExists = - i + 1 < args.length && !args[i + 1].startsWith('--'); - if (dirNameExists) { - targetDir = args[i + 1]; - i++; - } else { - throw new Error('--dir flag requires a directory name'); - } - } else if (args[i].startsWith('+')) { - version = args[i].slice(1); - } else { - // Only add flag if it's not already present - if (!flags.includes(args[i])) { - flags.push(args[i]); - } - } - } - - return new CompactCompiler(flags.join(' '), targetDir, version); + const options = CompactCompiler.parseArgs(args, env); + return new CompactCompiler(options); } /** @@ -578,12 +733,12 @@ export class CompactCompiler { */ async validateEnvironment(): Promise { const { devToolsVersion, toolchainVersion } = - await this.environmentValidator.validate(this.version); + await this.environmentValidator.validate(this.options.version); UIService.displayEnvInfo( devToolsVersion, toolchainVersion, - this.targetDir, - this.version, + this.options.targetDir, + this.options.version, ); } @@ -618,10 +773,12 @@ export class CompactCompiler { async compile(): Promise { await this.validateEnvironment(); - const searchDir = this.targetDir ? join(SRC_DIR, this.targetDir) : SRC_DIR; + const searchDir = this.options.targetDir + ? join(this.options.srcDir, this.options.targetDir) + : this.options.srcDir; // Validate target directory exists - if (this.targetDir && !existsSync(searchDir)) { + if (this.options.targetDir && !existsSync(searchDir)) { throw new DirectoryNotFoundError( `Target directory ${searchDir} does not exist`, searchDir, @@ -631,11 +788,11 @@ export class CompactCompiler { const compactFiles = await this.fileDiscovery.getCompactFiles(searchDir); if (compactFiles.length === 0) { - UIService.showNoFiles(this.targetDir); + UIService.showNoFiles(this.options.targetDir); return; } - UIService.showCompilationStart(compactFiles.length, this.targetDir); + UIService.showCompilationStart(compactFiles.length, this.options.targetDir); for (const [index, file] of compactFiles.entries()) { await this.compileFile(file, index, compactFiles.length); @@ -665,8 +822,8 @@ export class CompactCompiler { try { const result = await this.compilerService.compileFile( file, - this.flags, - this.version, + this.options.flags, + this.options.version, ); spinner.succeed(chalk.green(`[COMPILE] ${step} Compiled ${file}`)); @@ -699,15 +856,9 @@ export class CompactCompiler { } /** - * For testing + * For testing - returns the resolved options object */ - get testFlags(): string { - return this.flags; - } - get testTargetDir(): string | undefined { - return this.targetDir; - } - get testVersion(): string | undefined { - return this.version; + get testOptions(): ResolvedCompilerOptions { + return this.options; } } diff --git a/packages/cli/src/runBuilder.ts b/packages/cli/src/runBuilder.ts index c65e000..09e589a 100644 --- a/packages/cli/src/runBuilder.ts +++ b/packages/cli/src/runBuilder.ts @@ -6,17 +6,23 @@ import { CompactBuilder } from './Builder.ts'; /** * Executes the Compact builder CLI. - * Builds projects using the `CompactBuilder` class with provided flags, including compilation and additional steps. + * Builds projects using the `CompactBuilder` class with provided options, including compilation and additional steps. + * + * Supports compiler options: + * - `--dir ` - Compile specific subdirectory + * - `--src ` - Source directory (default: src) + * - `--out ` - Output directory (default: artifacts) + * - `--hierarchical` - Preserve directory structure in output + * - `+` - Use specific toolchain version * * @example * ```bash - * npx compact-builder --skip-zk + * npx compact-builder + * npx compact-builder --src contracts --out build * ``` * Expected output: * ``` * ℹ [BUILD] Compact builder started - * ℹ [COMPILE] COMPACT_HOME: /path/to/compactc - * ℹ [COMPILE] COMPACTC_PATH: /path/to/compactc/compactc * ℹ [COMPILE] Found 1 .compact file(s) to compile * ✔ [COMPILE] [1/1] Compiled Foo.compact * Compactc version: 0.26.0 @@ -29,8 +35,8 @@ async function runBuilder(): Promise { const spinner = ora(chalk.blue('[BUILD] Compact Builder started')).info(); try { - const compilerFlags = process.argv.slice(2).join(' '); - const builder = new CompactBuilder(compilerFlags); + const args = process.argv.slice(2); + const builder = CompactBuilder.fromArgs(args); await builder.build(); } catch (err) { spinner.fail( diff --git a/packages/cli/src/runCompiler.ts b/packages/cli/src/runCompiler.ts index b97200f..fda1a77 100644 --- a/packages/cli/src/runCompiler.ts +++ b/packages/cli/src/runCompiler.ts @@ -171,7 +171,18 @@ function showUsageHelp(): void { console.log(chalk.yellow('\nOptions:')); console.log( chalk.yellow( - ' --dir Compile specific directory (access, archive, security, token, utils)', + ' --dir Compile specific subdirectory within src', + ), + ); + console.log( + chalk.yellow(' --src Source directory (default: src)'), + ); + console.log( + chalk.yellow(' --out Output directory (default: artifacts)'), + ); + console.log( + chalk.yellow( + ' --hierarchical Preserve source directory structure in artifacts output', ), ); console.log( @@ -182,10 +193,20 @@ function showUsageHelp(): void { ' + Use specific toolchain version (e.g., +0.26.0)', ), ); + console.log(chalk.yellow('\nArtifact Output Structure:')); + console.log(chalk.yellow(' Default (flattened): //')); + console.log( + chalk.yellow(' With --hierarchical: ///'), + ); console.log(chalk.yellow('\nExamples:')); console.log( chalk.yellow( - ' compact-compiler # Compile all files', + ' compact-compiler # Compile all files (flattened)', + ), + ); + console.log( + chalk.yellow( + ' compact-compiler --hierarchical # Compile with nested structure', ), ); console.log( @@ -198,6 +219,11 @@ function showUsageHelp(): void { ' compact-compiler --dir access --skip-zk # Compile access with flags', ), ); + console.log( + chalk.yellow( + ' compact-compiler --src contracts --out build # Custom directories', + ), + ); console.log( chalk.yellow( ' SKIP_ZK=true compact-compiler --dir token # Use environment variable', diff --git a/packages/cli/test/Compiler.test.ts b/packages/cli/test/Compiler.test.ts index f3cd4d9..3913df8 100644 --- a/packages/cli/test/Compiler.test.ts +++ b/packages/cli/test/Compiler.test.ts @@ -297,6 +297,40 @@ describe('CompilerService', () => { ); }); + it('should use flattened artifacts output by default', async () => { + mockExec.mockResolvedValue({ + stdout: 'Compilation successful', + stderr: '', + }); + + const result = await service.compileFile( + 'access/AccessControl.compact', + '--skip-zk', + ); + + expect(result).toEqual({ stdout: 'Compilation successful', stderr: '' }); + expect(mockExec).toHaveBeenCalledWith( + 'compact compile --skip-zk "src/access/AccessControl.compact" "artifacts/AccessControl"', + ); + }); + + it('should flatten nested directory structure by default', async () => { + mockExec.mockResolvedValue({ + stdout: 'Compilation successful', + stderr: '', + }); + + const result = await service.compileFile( + 'access/test/AccessControl.mock.compact', + '--skip-zk', + ); + + expect(result).toEqual({ stdout: 'Compilation successful', stderr: '' }); + expect(mockExec).toHaveBeenCalledWith( + 'compact compile --skip-zk "src/access/test/AccessControl.mock.compact" "artifacts/AccessControl.mock"', + ); + }); + it('should throw CompilationError when compilation fails', async () => { mockExec.mockRejectedValue(new Error('Syntax error on line 10')); @@ -328,6 +362,105 @@ describe('CompilerService', () => { } }); }); + + describe('compileFile with hierarchical option', () => { + beforeEach(() => { + service = new CompilerService(mockExec, { hierarchical: true }); + }); + + it('should preserve directory structure in artifacts output when hierarchical is true', async () => { + mockExec.mockResolvedValue({ + stdout: 'Compilation successful', + stderr: '', + }); + + const result = await service.compileFile( + 'access/AccessControl.compact', + '--skip-zk', + ); + + expect(result).toEqual({ stdout: 'Compilation successful', stderr: '' }); + expect(mockExec).toHaveBeenCalledWith( + 'compact compile --skip-zk "src/access/AccessControl.compact" "artifacts/access/AccessControl"', + ); + }); + + it('should preserve nested directory structure when hierarchical is true', async () => { + mockExec.mockResolvedValue({ + stdout: 'Compilation successful', + stderr: '', + }); + + const result = await service.compileFile( + 'access/test/AccessControl.mock.compact', + '--skip-zk', + ); + + expect(result).toEqual({ stdout: 'Compilation successful', stderr: '' }); + expect(mockExec).toHaveBeenCalledWith( + 'compact compile --skip-zk "src/access/test/AccessControl.mock.compact" "artifacts/access/test/AccessControl.mock"', + ); + }); + + it('should use flattened output for root-level files even when hierarchical is true', async () => { + mockExec.mockResolvedValue({ + stdout: 'Compilation successful', + stderr: '', + }); + + const result = await service.compileFile('MyToken.compact', '--skip-zk'); + + expect(result).toEqual({ stdout: 'Compilation successful', stderr: '' }); + expect(mockExec).toHaveBeenCalledWith( + 'compact compile --skip-zk "src/MyToken.compact" "artifacts/MyToken"', + ); + }); + }); + + describe('compileFile with custom srcDir and outDir', () => { + beforeEach(() => { + service = new CompilerService(mockExec, { + srcDir: 'contracts', + outDir: 'build', + }); + }); + + it('should use custom srcDir and outDir', async () => { + mockExec.mockResolvedValue({ + stdout: 'Compilation successful', + stderr: '', + }); + + const result = await service.compileFile('MyToken.compact', '--skip-zk'); + + expect(result).toEqual({ stdout: 'Compilation successful', stderr: '' }); + expect(mockExec).toHaveBeenCalledWith( + 'compact compile --skip-zk "contracts/MyToken.compact" "build/MyToken"', + ); + }); + + it('should use custom directories with hierarchical option', async () => { + service = new CompilerService(mockExec, { + srcDir: 'contracts', + outDir: 'dist/artifacts', + hierarchical: true, + }); + mockExec.mockResolvedValue({ + stdout: 'Compilation successful', + stderr: '', + }); + + const result = await service.compileFile( + 'access/AccessControl.compact', + '--skip-zk', + ); + + expect(result).toEqual({ stdout: 'Compilation successful', stderr: '' }); + expect(mockExec).toHaveBeenCalledWith( + 'compact compile --skip-zk "contracts/access/AccessControl.compact" "dist/artifacts/access/AccessControl"', + ); + }); + }); }); describe('UIService', () => { @@ -454,22 +587,39 @@ describe('CompactCompiler', () => { compiler = new CompactCompiler(); expect(compiler).toBeInstanceOf(CompactCompiler); + expect(compiler.testOptions.flags).toBe(''); + expect(compiler.testOptions.targetDir).toBeUndefined(); + expect(compiler.testOptions.version).toBeUndefined(); + expect(compiler.testOptions.hierarchical).toBe(false); + expect(compiler.testOptions.srcDir).toBe('src'); + expect(compiler.testOptions.outDir).toBe('artifacts'); }); it('should create instance with all parameters', () => { compiler = new CompactCompiler( - '--skip-zk', - 'security', - '0.26.0', + { + flags: '--skip-zk', + targetDir: 'security', + version: '0.26.0', + hierarchical: true, + srcDir: 'contracts', + outDir: 'build', + }, mockExec, ); expect(compiler).toBeInstanceOf(CompactCompiler); + expect(compiler.testOptions.flags).toBe('--skip-zk'); + expect(compiler.testOptions.targetDir).toBe('security'); + expect(compiler.testOptions.version).toBe('0.26.0'); + expect(compiler.testOptions.hierarchical).toBe(true); + expect(compiler.testOptions.srcDir).toBe('contracts'); + expect(compiler.testOptions.outDir).toBe('build'); }); it('should trim flags', () => { - compiler = new CompactCompiler(' --skip-zk --verbose '); - expect(compiler.testFlags).toBe('--skip-zk --verbose'); + compiler = new CompactCompiler({ flags: ' --skip-zk --verbose ' }); + expect(compiler.testOptions.flags).toBe('--skip-zk --verbose'); }); }); @@ -477,28 +627,29 @@ describe('CompactCompiler', () => { it('should parse empty arguments', () => { compiler = CompactCompiler.fromArgs([]); - expect(compiler.testFlags).toBe(''); - expect(compiler.testTargetDir).toBeUndefined(); - expect(compiler.testVersion).toBeUndefined(); + expect(compiler.testOptions.flags).toBe(''); + expect(compiler.testOptions.targetDir).toBeUndefined(); + expect(compiler.testOptions.version).toBeUndefined(); + expect(compiler.testOptions.hierarchical).toBe(false); }); it('should handle SKIP_ZK environment variable', () => { compiler = CompactCompiler.fromArgs([], { SKIP_ZK: 'true' }); - expect(compiler.testFlags).toBe('--skip-zk'); + expect(compiler.testOptions.flags).toBe('--skip-zk'); }); it('should ignore SKIP_ZK when not "true"', () => { compiler = CompactCompiler.fromArgs([], { SKIP_ZK: 'false' }); - expect(compiler.testFlags).toBe(''); + expect(compiler.testOptions.flags).toBe(''); }); it('should parse --dir flag', () => { compiler = CompactCompiler.fromArgs(['--dir', 'security']); - expect(compiler.testTargetDir).toBe('security'); - expect(compiler.testFlags).toBe(''); + expect(compiler.testOptions.targetDir).toBe('security'); + expect(compiler.testOptions.flags).toBe(''); }); it('should parse --dir flag with additional flags', () => { @@ -509,15 +660,15 @@ describe('CompactCompiler', () => { '--verbose', ]); - expect(compiler.testTargetDir).toBe('security'); - expect(compiler.testFlags).toBe('--skip-zk --verbose'); + expect(compiler.testOptions.targetDir).toBe('security'); + expect(compiler.testOptions.flags).toBe('--skip-zk --verbose'); }); it('should parse version flag', () => { compiler = CompactCompiler.fromArgs(['+0.26.0']); - expect(compiler.testVersion).toBe('0.26.0'); - expect(compiler.testFlags).toBe(''); + expect(compiler.testOptions.version).toBe('0.26.0'); + expect(compiler.testOptions.flags).toBe(''); }); it('should parse complex arguments', () => { @@ -529,9 +680,9 @@ describe('CompactCompiler', () => { '+0.26.0', ]); - expect(compiler.testTargetDir).toBe('security'); - expect(compiler.testFlags).toBe('--skip-zk --verbose'); - expect(compiler.testVersion).toBe('0.26.0'); + expect(compiler.testOptions.targetDir).toBe('security'); + expect(compiler.testOptions.flags).toBe('--skip-zk --verbose'); + expect(compiler.testOptions.version).toBe('0.26.0'); }); it('should combine environment variables with CLI flags', () => { @@ -539,8 +690,8 @@ describe('CompactCompiler', () => { SKIP_ZK: 'true', }); - expect(compiler.testTargetDir).toBe('access'); - expect(compiler.testFlags).toBe('--skip-zk --verbose'); + expect(compiler.testOptions.targetDir).toBe('access'); + expect(compiler.testOptions.flags).toBe('--skip-zk --verbose'); }); it('should deduplicate flags when both env var and CLI flag are present', () => { @@ -548,7 +699,7 @@ describe('CompactCompiler', () => { SKIP_ZK: 'true', }); - expect(compiler.testFlags).toBe('--skip-zk --verbose'); + expect(compiler.testOptions.flags).toBe('--skip-zk --verbose'); }); it('should throw error for --dir without argument', () => { @@ -562,6 +713,91 @@ describe('CompactCompiler', () => { '--dir flag requires a directory name', ); }); + + it('should parse --hierarchical flag', () => { + compiler = CompactCompiler.fromArgs(['--hierarchical']); + + expect(compiler.testOptions.hierarchical).toBe(true); + expect(compiler.testOptions.flags).toBe(''); + }); + + it('should parse --hierarchical flag with other options', () => { + compiler = CompactCompiler.fromArgs([ + '--hierarchical', + '--dir', + 'security', + '--skip-zk', + '+0.26.0', + ]); + + expect(compiler.testOptions.hierarchical).toBe(true); + expect(compiler.testOptions.targetDir).toBe('security'); + expect(compiler.testOptions.flags).toBe('--skip-zk'); + expect(compiler.testOptions.version).toBe('0.26.0'); + }); + + it('should default to flattened output (hierarchical = false)', () => { + compiler = CompactCompiler.fromArgs(['--skip-zk']); + + expect(compiler.testOptions.hierarchical).toBe(false); + }); + + it('should parse --src flag', () => { + compiler = CompactCompiler.fromArgs(['--src', 'contracts']); + + expect(compiler.testOptions.srcDir).toBe('contracts'); + }); + + it('should parse --out flag', () => { + compiler = CompactCompiler.fromArgs(['--out', 'build']); + + expect(compiler.testOptions.outDir).toBe('build'); + }); + + it('should parse --src and --out flags together', () => { + compiler = CompactCompiler.fromArgs([ + '--src', + 'contracts', + '--out', + 'dist/artifacts', + '--skip-zk', + ]); + + expect(compiler.testOptions.srcDir).toBe('contracts'); + expect(compiler.testOptions.outDir).toBe('dist/artifacts'); + expect(compiler.testOptions.flags).toBe('--skip-zk'); + }); + + it('should use default srcDir and outDir when not specified', () => { + compiler = CompactCompiler.fromArgs([]); + + expect(compiler.testOptions.srcDir).toBe('src'); + expect(compiler.testOptions.outDir).toBe('artifacts'); + }); + + it('should throw error for --src without argument', () => { + expect(() => CompactCompiler.fromArgs(['--src'])).toThrow( + '--src flag requires a directory path', + ); + }); + + it('should throw error for --src followed by another flag', () => { + expect(() => CompactCompiler.fromArgs(['--src', '--skip-zk'])).toThrow( + '--src flag requires a directory path', + ); + }); + + it('should throw error for --out without argument', () => { + expect(() => CompactCompiler.fromArgs(['--out'])).toThrow( + '--out flag requires a directory path', + ); + }); + + it('should throw error for --out followed by another flag', () => { + expect(() => CompactCompiler.fromArgs(['--out', '--skip-zk'])).toThrow( + '--out flag requires a directory path', + ); + }); }); describe('validateEnvironment', () => { @@ -575,9 +811,11 @@ describe('CompactCompiler', () => { }); // getToolchainVersion compiler = new CompactCompiler( - '--skip-zk', - 'security', - '0.26.0', + { + flags: '--skip-zk', + targetDir: 'security', + version: '0.26.0', + }, mockExec, ); const displaySpy = vi @@ -608,7 +846,7 @@ describe('CompactCompiler', () => { it('should handle CompactCliNotFoundError with installation instructions', async () => { mockExec.mockRejectedValue(new Error('Command not found')); - compiler = new CompactCompiler('', undefined, undefined, mockExec); + compiler = new CompactCompiler({}, mockExec); await expect(compiler.validateEnvironment()).rejects.toThrow( CompactCliNotFoundError, @@ -620,7 +858,7 @@ describe('CompactCompiler', () => { .mockResolvedValueOnce({ stdout: 'compact 0.1.0', stderr: '' }) // validate() succeeds .mockRejectedValueOnce(new Error('Version command failed')); // getDevToolsVersion() fails - compiler = new CompactCompiler('', undefined, undefined, mockExec); + compiler = new CompactCompiler({}, mockExec); await expect(compiler.validateEnvironment()).rejects.toThrow( 'Version command failed', @@ -633,7 +871,7 @@ describe('CompactCompiler', () => { childProcessError.stderr = 'some error'; mockExec.mockRejectedValue(childProcessError); - compiler = new CompactCompiler('', undefined, undefined, mockExec); + compiler = new CompactCompiler({}, mockExec); await expect(compiler.validateEnvironment()).rejects.toThrow( "'compact' CLI not found in PATH. Please install the Compact developer tools.", @@ -642,7 +880,7 @@ describe('CompactCompiler', () => { it('should handle non-Error exceptions gracefully', async () => { mockExec.mockRejectedValue('String error message'); - compiler = new CompactCompiler('', undefined, undefined, mockExec); + compiler = new CompactCompiler({}, mockExec); await expect(compiler.validateEnvironment()).rejects.toThrow( CompactCliNotFoundError, @@ -658,7 +896,7 @@ describe('CompactCompiler', () => { stderr: '', }); - compiler = new CompactCompiler('', undefined, '0.26.0', mockExec); + compiler = new CompactCompiler({ version: '0.26.0' }, mockExec); const displaySpy = vi .spyOn(UIService, 'displayEnvInfo') .mockImplementation(() => {}); @@ -689,7 +927,7 @@ describe('CompactCompiler', () => { stderr: '', }); - compiler = new CompactCompiler('', undefined, undefined, mockExec); + compiler = new CompactCompiler({}, mockExec); const displaySpy = vi .spyOn(UIService, 'displayEnvInfo') .mockImplementation(() => {}); @@ -712,14 +950,14 @@ describe('CompactCompiler', () => { describe('compile', () => { it('should handle empty source directory', async () => { mockReaddir.mockResolvedValue([]); - compiler = new CompactCompiler('', undefined, undefined, mockExec); + compiler = new CompactCompiler({}, mockExec); await expect(compiler.compile()).resolves.not.toThrow(); }); it('should throw error if target directory does not exist', async () => { mockExistsSync.mockReturnValue(false); - compiler = new CompactCompiler('', 'nonexistent', undefined, mockExec); + compiler = new CompactCompiler({ targetDir: 'nonexistent' }, mockExec); await expect(compiler.compile()).rejects.toThrow(DirectoryNotFoundError); }); @@ -738,12 +976,7 @@ describe('CompactCompiler', () => { }, ]; mockReaddir.mockResolvedValue(mockDirents as any); - compiler = new CompactCompiler( - '--skip-zk', - undefined, - undefined, - mockExec, - ); + compiler = new CompactCompiler({ flags: '--skip-zk' }, mockExec); await compiler.compile(); @@ -770,7 +1003,7 @@ describe('CompactCompiler', () => { .mockResolvedValueOnce({ stdout: 'Compactc 0.26.0', stderr: '' }) // getToolchainVersion .mockRejectedValueOnce(new Error('Compilation failed')); // compileFile execution - compiler = new CompactCompiler('', undefined, undefined, testMockExec); + compiler = new CompactCompiler({}, testMockExec); // Test that compilation errors are properly propagated let thrownError: unknown; @@ -804,34 +1037,34 @@ describe('CompactCompiler', () => { it('should handle turbo compact command', () => { compiler = CompactCompiler.fromArgs([]); - expect(compiler.testFlags).toBe(''); - expect(compiler.testTargetDir).toBeUndefined(); + expect(compiler.testOptions.flags).toBe(''); + expect(compiler.testOptions.targetDir).toBeUndefined(); }); it('should handle SKIP_ZK=true turbo compact command', () => { compiler = CompactCompiler.fromArgs([], { SKIP_ZK: 'true' }); - expect(compiler.testFlags).toBe('--skip-zk'); + expect(compiler.testOptions.flags).toBe('--skip-zk'); }); it('should handle turbo compact:access command', () => { compiler = CompactCompiler.fromArgs(['--dir', 'access']); - expect(compiler.testFlags).toBe(''); - expect(compiler.testTargetDir).toBe('access'); + expect(compiler.testOptions.flags).toBe(''); + expect(compiler.testOptions.targetDir).toBe('access'); }); it('should handle turbo compact:security -- --skip-zk command', () => { compiler = CompactCompiler.fromArgs(['--dir', 'security', '--skip-zk']); - expect(compiler.testFlags).toBe('--skip-zk'); - expect(compiler.testTargetDir).toBe('security'); + expect(compiler.testOptions.flags).toBe('--skip-zk'); + expect(compiler.testOptions.targetDir).toBe('security'); }); it('should handle version specification', () => { compiler = CompactCompiler.fromArgs(['+0.26.0']); - expect(compiler.testVersion).toBe('0.26.0'); + expect(compiler.testOptions.version).toBe('0.26.0'); }); it.each([ @@ -870,11 +1103,11 @@ describe('CompactCompiler', () => { ])('should handle complex command $name', ({ args, env }) => { compiler = CompactCompiler.fromArgs(args, env); - expect(compiler.testFlags).toBe( + expect(compiler.testOptions.flags).toBe( '--skip-zk --no-communications-commitment', ); - expect(compiler.testTargetDir).toBe('security'); - expect(compiler.testVersion).toBe('0.26.0'); + expect(compiler.testOptions.targetDir).toBe('security'); + expect(compiler.testOptions.version).toBe('0.26.0'); }); }); }); diff --git a/packages/cli/test/runCompiler.test.ts b/packages/cli/test/runCompiler.test.ts index 3110f43..58f064a 100644 --- a/packages/cli/test/runCompiler.test.ts +++ b/packages/cli/test/runCompiler.test.ts @@ -323,7 +323,16 @@ describe('runCompiler CLI', () => { ); expect(mockConsoleLog).toHaveBeenCalledWith('\nOptions:'); expect(mockConsoleLog).toHaveBeenCalledWith( - ' --dir Compile specific directory (access, archive, security, token, utils)', + ' --dir Compile specific subdirectory within src', + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + ' --src Source directory (default: src)', + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + ' --out Output directory (default: artifacts)', + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + ' --hierarchical Preserve source directory structure in artifacts output', ); expect(mockConsoleLog).toHaveBeenCalledWith( ' --skip-zk Skip zero-knowledge proof generation', @@ -331,9 +340,21 @@ describe('runCompiler CLI', () => { expect(mockConsoleLog).toHaveBeenCalledWith( ' + Use specific toolchain version (e.g., +0.26.0)', ); + expect(mockConsoleLog).toHaveBeenCalledWith( + '\nArtifact Output Structure:', + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + ' Default (flattened): //', + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + ' With --hierarchical: ///', + ); expect(mockConsoleLog).toHaveBeenCalledWith('\nExamples:'); expect(mockConsoleLog).toHaveBeenCalledWith( - ' compact-compiler # Compile all files', + ' compact-compiler # Compile all files (flattened)', + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + ' compact-compiler --hierarchical # Compile with nested structure', ); expect(mockConsoleLog).toHaveBeenCalledWith( ' compact-compiler --dir security # Compile security directory', @@ -341,6 +362,9 @@ describe('runCompiler CLI', () => { expect(mockConsoleLog).toHaveBeenCalledWith( ' compact-compiler --dir access --skip-zk # Compile access with flags', ); + expect(mockConsoleLog).toHaveBeenCalledWith( + ' compact-compiler --src contracts --out build # Custom directories', + ); expect(mockConsoleLog).toHaveBeenCalledWith( ' SKIP_ZK=true compact-compiler --dir token # Use environment variable', );