Skip to content

feat(tools/compiler): introduce artifacts manifest, and strict the types#42

Draft
0xisk wants to merge 8 commits intomainfrom
feat/artifacts-structure
Draft

feat(tools/compiler): introduce artifacts manifest, and strict the types#42
0xisk wants to merge 8 commits intomainfrom
feat/artifacts-structure

Conversation

@0xisk
Copy link
Copy Markdown
Member

@0xisk 0xisk commented Dec 11, 2025

Types of changes

What types of changes does your code introduce to OpenZeppelin Midnight Contracts?
Put an x in the boxes that apply

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update (if none of the other choices apply)

Fixes #41

  • Introduce artifacts/manifest.json file that describes the compilation output details.
  • Strict the types for the expected arguments for the Compiler
  • Split the Compiler serivices

PR Checklist

  • I have read the Contributing Guide
  • I have added tests that prove my fix is effective or that my feature works
  • I have added documentation of new methods and any new behavior or changes to existing behavior
  • CI Workflows Are Passing

Further comments

If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc...

Summary by CodeRabbit

  • New Features

    • Added --force/-f flag to automatically clean conflicting artifacts on structure mismatches
    • Interactive prompts guide users when existing artifact structures conflict with compilation requests
    • Build manifests now generated with compilation metadata (duration, environment info, compiler flags)
    • Compiler API updated with strongly-typed flags and version parameters
  • Documentation

    • Updated API examples and usage guides to reflect new parameter types

✏️ Tip: You can customize this high-level summary in your review settings.

@0xisk 0xisk linked an issue Dec 11, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 11, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The PR introduces an artifact manifest system for the Compact CLI to detect and handle structure mismatches when switching between flattened and hierarchical artifact output layouts. It refactors compiler logic into modular services, strengthens type safety with enums for compiler versions and flags, and adds interactive prompting for resolving mismatched structures or a --force flag for non-interactive environments.

Changes

Cohort / File(s) Summary
Configuration & Types
packages/cli/src/config.ts, packages/cli/src/types/manifest.ts
New configuration module defining default directories (src, artifacts) and version catalogs (compactc, compact-tools). New manifest types including ArtifactManifest, ArtifactStructure, CompilerFlag, Platform, NodeVersion; StructureMismatchError class for structure mismatch signaling.
Service Layer
packages/cli/src/services/EnvironmentValidator.ts, packages/cli/src/services/FileDiscovery.ts, packages/cli/src/services/ManifestService.ts, packages/cli/src/services/UIService.ts, packages/cli/src/services/CompilerService.ts
Five new services: EnvironmentValidator validates CLI availability and versions; FileDiscovery recursively locates .compact files; ManifestService reads/writes manifests and detects structure mismatches; UIService centralizes output formatting; CompilerService encapsulates file-level compilation logic.
Core Compiler
packages/cli/src/Compiler.ts
Refactored to use new services; accepts structured CompilerFlag[] and CompactcVersion types; implements manifest write-after-compile, structure mismatch detection, and optional auto-cleanup with --force flag; adds cleanOutputDirectory() and cleanAndCompile() helpers.
CLI Entry & Error Handling
packages/cli/src/runCompiler.ts
Added StructureMismatchError handling with interactive readline-based confirmation prompt; non-TTY mode instructs user to use --force; supports --force/-f flag in help/usage.
Documentation & Samples
packages/cli/README.md
Updated code examples and API documentation to reflect new types (CompilerFlag[], CompactcVersion); documented Structure Mismatch Detection, Manifest File contents, and --force flag behavior.
Test Fixtures
packages/cli/test/Compiler.test.ts, packages/cli/test/runCompiler.test.ts
Updated tests to reflect new services, array-based flags, force flag support; added ManifestService and StructureMismatchError test coverage; renamed version methods (getDevToolsVersion → getCompactToolVersion).
Sample Contracts
packages/simulator/test/fixtures/sample-contracts/SampleZOwnable.compact, packages/simulator/test/fixtures/sample-contracts/Simple.compact, packages/simulator/test/fixtures/sample-contracts/Witness.compact
Added public ledger exports; minor signature and formatting updates to maintain consistency with language evolution.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as User/CLI
    participant Compiler as CompactCompiler
    participant EnvVal as EnvironmentValidator
    participant FileDisc as FileDiscovery
    participant ManifestSvc as ManifestService
    participant CompilerSvc as CompilerService
    participant UI as UIService

    CLI->>Compiler: compile(options)
    
    rect rgb(200, 220, 255)
    note over Compiler,UI: Environment Validation
    Compiler->>EnvVal: validate(version)
    EnvVal-->>Compiler: { compactToolVersion, compactcVersion }
    Compiler->>UI: displayEnvInfo(...)
    end
    
    rect rgb(200, 235, 200)
    note over Compiler,ManifestSvc: Structure Mismatch Check
    Compiler->>ManifestSvc: checkMismatch(requestedStructure)
    ManifestSvc-->>Compiler: existingManifest | null
    alt Mismatch Detected
        Compiler-->>CLI: throw StructureMismatchError
        CLI->>CLI: promptConfirmation()
        alt User Confirms
            CLI->>Compiler: cleanAndCompile()
            Compiler->>ManifestSvc: cleanOutputDirectory()
        else User Declines
            CLI-->>CLI: exit
        end
    end
    end
    
    rect rgb(255, 240, 200)
    note over Compiler,FileDisc: File Discovery & Compilation
    Compiler->>FileDisc: getCompactFiles(srcDir)
    FileDisc-->>Compiler: [ file1.compact, file2.compact ]
    Compiler->>UI: showCompilationStart(fileCount)
    
    loop For Each File
        Compiler->>CompilerSvc: compileFile(file, flags, version)
        CompilerSvc-->>Compiler: { stdout, stderr }
        Compiler->>Compiler: trackArtifacts()
    end
    end
    
    rect rgb(240, 200, 255)
    note over Compiler,ManifestSvc: Manifest Generation & Write
    Compiler->>Compiler: buildManifest(artifacts, timing, metadata)
    Compiler->>ManifestSvc: write(manifest)
    ManifestSvc-->>Compiler: void
    Compiler-->>CLI: success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • packages/cli/src/Compiler.ts: Dense integration of manifest read/write, structure mismatch detection, service orchestration, and cleanup logic; error handling flow for StructureMismatchError
  • packages/cli/src/services/ManifestService.ts: Mismatch detection logic and cleanOutputDirectory implementation; potential edge cases with missing or malformed manifests
  • packages/cli/src/runCompiler.ts: Interactive prompt flow and StructureMismatchError handling; readline-based confirmation and non-TTY fallback behavior
  • packages/cli/test/Compiler.test.ts: Extensive mock setup and service injection changes; comprehensive coverage of mismatch scenarios and force flag combinations
  • Type definitions in packages/cli/src/types/manifest.ts: Verify StructureMismatchError constructor matches usage sites and that CompilerFlag union correctly captures all flags

Possibly related PRs

Suggested reviewers

  • emnul
  • andrew-fleming

Poem

🐰 A manifest of hope, now neatly penned,
With structures matched from start to end,
Where flags array true and versions aligned,
And services compose with purpose refined—
--force awaits when cleanup is due,
The compiler dances with prompts fresh and new! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to test fixture files (SampleZOwnable.compact, Simple.compact, Witness.compact) add new public ledger/witness exports unrelated to manifest or type strictness objectives. Remove or move changes to test fixtures that are outside the scope of artifact manifest and type strictness work, or clarify their necessity in the PR description.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #41: artifact manifest file creation, structure mismatch detection, interactive and non-interactive (--force) handling, and comprehensive integration across services.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title 'feat(tools/compiler): introduce artifacts manifest, and strict the types' accurately reflects the two main changes in the PR: introducing a manifest system and strengthening type definitions.

Comment @coderabbitai help to get the list of available commands and usage tips.

@0xisk 0xisk force-pushed the feat/artifacts-structure branch from 2a4b976 to 61a9922 Compare December 15, 2025 10:25
@0xisk 0xisk marked this pull request as ready for review December 15, 2025 10:27
@0xisk 0xisk requested review from a team as code owners December 15, 2025 10:27
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (10)
packages/cli/test/runCompiler.test.ts (1)

12-20: Missing test coverage for StructureMismatchError handling.

The mock correctly re-exports StructureMismatchError, but there are no tests verifying the handleStructureMismatch behavior introduced in runCompiler.ts. Consider adding tests for:

  • Interactive prompt flow when process.stdin.isTTY is true
  • Non-TTY exit with --force guidance
  • User confirmation leading to cleanAndCompile
  • User rejection leading to graceful exit
packages/cli/src/services/FileDiscovery.ts (1)

64-68: Silent failure on missing directory may mask configuration errors.

When the source directory doesn't exist or is inaccessible, this returns an empty array and logs to stderr. This could mask misconfiguration issues (e.g., typo in srcDir). Consider throwing an error for the top-level directory failure while keeping graceful handling for subdirectories.

     } catch (err) {
       // biome-ignore lint/suspicious/noConsole: Needed to display error and dir path
       console.error(`Failed to read dir: ${dir}`, err);
-      return [];
+      throw err;
     }

Alternatively, if silent failure is intentional for flexibility, document this behavior explicitly in the JSDoc.

packages/cli/src/services/UIService.ts (2)

28-34: Direct console.log usage without lint suppression.

Other services in this PR use // biome-ignore lint/suspicious/noConsole: comments. For consistency, either add the suppression comment here or consider whether this is intentional for UIService (since it's explicitly a UI output service).

  printOutput(output: string, colorFn: (text: string) => string): void {
    const lines = output
      .split('\n')
      .filter((line) => line.trim() !== '')
      .map((line) => `    ${line}`);
+   // biome-ignore lint/suspicious/noConsole: UIService is responsible for console output
    console.log(colorFn(lines.join('\n')));
  },

55-66: Consider reusing ora instance or using simpler output.

Each method creates a new ora() instance just to call .info() or .warn() once. While functionally correct, this is slightly inefficient. Consider either:

  1. Creating a single shared spinner instance
  2. Using console.log with chalk directly for non-spinner output

This is a minor optimization and the current approach works fine. Defer if preferred.

packages/cli/src/services/ManifestService.ts (2)

45-55: Unsafe JSON parsing and silent error swallowing.

Two concerns:

  1. JSON.parse result is cast to ArtifactManifest without validation. Malformed or tampered manifests could cause downstream issues.
  2. The catch block silently returns null for all errors (parse errors, permission errors), making debugging difficult.

Consider at minimum logging parse errors:

  async read(): Promise<ArtifactManifest | null> {
    try {
      if (!existsSync(this.manifestPath)) {
        return null;
      }
      const content = await readFile(this.manifestPath, 'utf-8');
      return JSON.parse(content) as ArtifactManifest;
-   } catch {
+   } catch (error) {
+     // Log parse/read errors for debugging; return null to allow fresh compilation
+     console.warn(`Failed to read manifest: ${error}`);
      return null;
    }
  }

For validation, consider adding a schema check (e.g., with zod) or at least verifying structure field exists.


85-89: Redundant existence check before rm.

The { force: true } option already handles non-existent paths gracefully, making the existsSync check unnecessary.

  async cleanOutputDirectory(): Promise<void> {
-   if (existsSync(this.outDir)) {
-     await rm(this.outDir, { recursive: true, force: true });
-   }
+   await rm(this.outDir, { recursive: true, force: true });
  }
packages/cli/src/services/EnvironmentValidator.ts (1)

117-123: Command construction produces double space when version is omitted.

When version is undefined, versionFlag becomes an empty string, resulting in "compact compile --version" (note the double space). While this likely works, it's inconsistent with typical CLI invocation patterns.

  async getCompactcVersion(version?: string): Promise<string> {
-   const versionFlag = version ? `+${version}` : '';
-   const { stdout } = await this.execFn(
-     `compact compile ${versionFlag} --version`,
-   );
+   const command = version
+     ? `compact compile +${version} --version`
+     : `compact compile --version`;
+   const { stdout } = await this.execFn(command);
    return stdout.trim();
  }
packages/cli/src/types/manifest.ts (1)

25-28: NodeVersion type will require maintenance as new Node.js versions release.

The hardcoded union '18' | '20' | '21' | '22' | '23' | '24' | '25' will become stale. Consider deriving this from a const array (similar to COMPACTC_VERSIONS in config.ts) for easier maintenance, or document that this list needs periodic updates.

+// In config.ts or here
+export const NODE_VERSIONS = ['18', '20', '21', '22', '23', '24', '25'] as const;
+
 /**
  * Supported Node.js major versions.
  */
-export type NodeVersion = '18' | '20' | '21' | '22' | '23' | '24' | '25';
+export type NodeVersion = (typeof NODE_VERSIONS)[number];
packages/cli/src/Compiler.ts (2)

456-462: Platform and nodeVersion may produce values outside the defined types.

  • nodeVersion: The regex ^v(\d+) captures the major version, but platforms like Deno or Bun would have different version formats. Additionally, future Node.js versions (e.g., v26) aren't in the NodeVersion type.
  • platform: Combinations like 'freebsd-x64' or 'linux-riscv64' would be cast to Platform despite not being in the union.

Since these are metadata fields, this may be acceptable, but the type assertions could mask unexpected values.

Consider making these optional with runtime validation:

    // Get Node.js major version
-   const nodeVersion = process.version.match(/^v(\d+)/)?.[1] as
-     | NodeVersion
-     | undefined;
+   const nodeVersionMatch = process.version.match(/^v(\d+)/)?.[1];
+   const nodeVersion = nodeVersionMatch as NodeVersion | undefined;

    // Get platform identifier
-   const platform = `${process.platform}-${process.arch}` as Platform;
+   const platformId = `${process.platform}-${process.arch}`;
+   const platform = platformId as Platform | undefined;

Or validate against allowed values before assigning.


550-555: cleanAndCompile calls compile() which validates environment redundantly.

When cleanAndCompile() is called (after catching StructureMismatchError from an initial compile() attempt), the second compile() call will re-run validateEnvironment(). This is wasteful since the environment was just validated.

Consider extracting the core compilation logic or passing validated version info:

  async cleanAndCompile(): Promise<void> {
    const spinner = ora();
    spinner.info(chalk.yellow('[COMPILE] Cleaning existing artifacts...'));
    await this.cleanOutputDirectory();
-   await this.compile();
+   // Consider: pass previously validated versions or extract inner compilation logic
+   await this.compile();
  }

Alternatively, this may be acceptable if compilation is infrequent and the overhead is minimal.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed4131c and 61a9922.

📒 Files selected for processing (15)
  • packages/cli/README.md (7 hunks)
  • packages/cli/src/Compiler.ts (13 hunks)
  • packages/cli/src/config.ts (1 hunks)
  • packages/cli/src/runCompiler.ts (3 hunks)
  • packages/cli/src/services/CompilerService.ts (1 hunks)
  • packages/cli/src/services/EnvironmentValidator.ts (1 hunks)
  • packages/cli/src/services/FileDiscovery.ts (1 hunks)
  • packages/cli/src/services/ManifestService.ts (1 hunks)
  • packages/cli/src/services/UIService.ts (1 hunks)
  • packages/cli/src/types/manifest.ts (1 hunks)
  • packages/cli/test/Compiler.test.ts (28 hunks)
  • packages/cli/test/runCompiler.test.ts (1 hunks)
  • packages/simulator/test/fixtures/sample-contracts/SampleZOwnable.compact (2 hunks)
  • packages/simulator/test/fixtures/sample-contracts/Simple.compact (1 hunks)
  • packages/simulator/test/fixtures/sample-contracts/Witness.compact (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
packages/cli/src/runCompiler.ts (1)
packages/cli/src/types/manifest.ts (1)
  • StructureMismatchError (115-130)
packages/cli/src/services/ManifestService.ts (2)
packages/cli/src/config.ts (1)
  • DEFAULT_OUT_DIR (10-10)
packages/cli/src/types/manifest.ts (2)
  • MANIFEST_FILENAME (106-106)
  • ArtifactManifest (68-103)
packages/cli/src/services/UIService.ts (1)
packages/cli/src/config.ts (2)
  • CompactToolVersion (41-41)
  • CompactcVersion (38-38)
packages/cli/src/services/CompilerService.ts (4)
packages/cli/src/services/EnvironmentValidator.ts (1)
  • ExecFunction (39-41)
packages/cli/src/config.ts (2)
  • DEFAULT_SRC_DIR (7-7)
  • DEFAULT_OUT_DIR (10-10)
packages/cli/src/types/manifest.ts (1)
  • CompilerFlag (55-60)
packages/cli/src/types/errors.ts (1)
  • CompilationError (58-73)
packages/cli/src/services/EnvironmentValidator.ts (2)
packages/cli/src/config.ts (4)
  • CompactToolVersion (41-41)
  • CompactcVersion (38-38)
  • MIN_COMPACT_TOOL_VERSION (30-31)
  • MAX_COMPACTC_VERSION (34-35)
packages/cli/src/types/errors.ts (1)
  • CompactCliNotFoundError (38-48)
packages/cli/src/services/FileDiscovery.ts (1)
packages/cli/src/config.ts (1)
  • DEFAULT_SRC_DIR (7-7)
packages/cli/src/Compiler.ts (4)
packages/cli/src/types/manifest.ts (4)
  • CompilerFlag (55-60)
  • StructureMismatchError (115-130)
  • NodeVersion (28-28)
  • Platform (34-40)
packages/cli/src/config.ts (4)
  • CompactcVersion (38-38)
  • DEFAULT_SRC_DIR (7-7)
  • DEFAULT_OUT_DIR (10-10)
  • CompactToolVersion (41-41)
packages/cli/src/services/ManifestService.ts (1)
  • ManifestService (21-90)
packages/cli/src/services/UIService.ts (1)
  • UIService (15-107)
packages/cli/src/types/manifest.ts (1)
packages/cli/src/config.ts (2)
  • CompactcVersion (38-38)
  • CompactToolVersion (41-41)
🔇 Additional comments (28)
packages/simulator/test/fixtures/sample-contracts/Simple.compact (1)

7-7: LGTM!

The exported ledger declaration properly exposes _val for the setVal and getVal circuits. This aligns with the test fixture's purpose of providing observable state.

packages/simulator/test/fixtures/sample-contracts/SampleZOwnable.compact (2)

9-14: LGTM!

The ledger exports (_ownerCommitment, _counter, _instanceSalt) are appropriately exposed for test fixture purposes, enabling external verification of ownership state.


41-44: Formatting changes look fine.

These are whitespace/style adjustments with no behavioral impact.

Also applies to: 49-49, 54-54, 57-58

packages/cli/src/config.ts (1)

16-21: LGTM!

Good use of as const for type derivation, creating a single source of truth for supported versions. The derived types CompactcVersion and CompactToolVersion enable compile-time validation of version strings.

Also applies to: 33-35, 37-41

packages/cli/README.md (2)

90-132: LGTM!

Comprehensive documentation of the new structure mismatch detection feature, including the manifest file format, interactive prompts, and --force flag behavior for CI/CD environments.


244-264: LGTM!

The updated type documentation accurately reflects the new CompilerFlag[] array type, CompactcVersion union type, and force option in CompilerOptions.

packages/cli/src/runCompiler.ts (4)

125-138: LGTM!

The promptConfirmation utility correctly implements a simple yes/no prompt using readline. It properly closes the interface after receiving input and normalizes the response case-insensitively.


56-60: LGTM!

Good placement of StructureMismatchError handling before the generic error handler, allowing specialized interactive handling while falling through to standard error paths for other cases.


269-273: LGTM!

The --force, -f flag is correctly documented in the usage help, matching the README and issue requirements.


104-112: The review comment is unnecessary. cleanAndCompile() explicitly calls cleanOutputDirectory() before compile(), which removes the entire output directory including the manifest file. When compile() subsequently calls checkMismatch(), no manifest exists to check, so no StructureMismatchError is thrown. The design prevents re-checking without requiring the --force flag to be added.

Likely an incorrect or invalid review comment.

packages/simulator/test/fixtures/sample-contracts/Witness.compact (1)

1-32: LGTM!

The test fixture additions are well-structured. The new witness declarations with arguments (wit_secretFieldPlusArg, wit_secretUintPlusArgs) correctly match the signatures expected by the existing circuits, expanding test coverage for parameterized witnesses.

packages/cli/src/services/FileDiscovery.ts (1)

41-63: Clean recursive implementation with parallel processing.

The use of readdir with withFileTypes, Promise.all for parallelism, and flat() for result aggregation is idiomatic and efficient.

packages/cli/src/services/CompilerService.ts (1)

110-127: Solid error handling with cause preservation.

The error handling correctly extracts the message from both Error instances and non-Error values, and wraps them in CompilationError with the file path and original cause for debugging.

packages/cli/src/services/ManifestService.ts (2)

62-64: write() assumes output directory exists.

If outDir doesn't exist, writeFile will throw ENOENT. The compilation process likely creates the directory, but ManifestService.write() could be called independently.

Verify that callers always ensure the directory exists before calling write(), or consider adding:

+ import { mkdir } from 'node:fs/promises';
+
  async write(manifest: ArtifactManifest): Promise<void> {
+   await mkdir(this.outDir, { recursive: true });
    await writeFile(this.manifestPath, JSON.stringify(manifest, null, 2));
  }

72-80: Clean mismatch detection logic.

The method correctly reads the existing manifest and returns it only when a structure mismatch is detected, enabling the caller to handle the mismatch scenario (prompt user, force clean, etc.).

packages/cli/src/services/EnvironmentValidator.ts (4)

178-181: Unsafe type assertion for version strings.

The returned version strings are cast to CompactToolVersion and CompactcVersion without validating they're actually in the allowed set. If the CLI returns a version not in the predefined lists (e.g., a newer patch version), consumers may receive unexpected values while TypeScript assumes they're valid.

Consider either:

  1. Validating against the allowed versions before casting
  2. Changing return types to string and letting consumers handle validation
  3. Documenting this as intentional (versions outside the list are allowed at runtime)

19-30: LGTM!

The compareVersions helper correctly handles semver comparison with support for versions of different lengths by defaulting missing parts to 0.


56-66: LGTM!

Good use of dependency injection for the exec function, enabling testability while providing a sensible default.


158-176: LGTM!

The version validation logic appropriately warns for outdated tools and errors for unsupported compiler versions. The error messages provide clear guidance on resolution steps.

packages/cli/src/types/manifest.ts (2)

68-103: LGTM!

The ArtifactManifest interface is well-documented with JSDoc comments and appropriate use of optional fields for backward compatibility. The discriminated union for artifacts based on structure type is a good design choice.


115-129: LGTM!

StructureMismatchError correctly extends Error, sets the name property for proper error identification, and exposes both structure values as readonly properties for programmatic handling.

packages/cli/test/Compiler.test.ts (3)

1140-1168: LGTM!

Good test coverage for the new --force and -f flags, including interaction with other flags and the default behavior.


1257-1293: LGTM!

The checkMismatch tests properly cover the three cases: no manifest, matching structure, and mismatching structure.


1295-1316: LGTM!

The cleanOutputDirectory tests correctly verify both the happy path (directory exists) and edge case (directory doesn't exist).

packages/cli/src/Compiler.ts (4)

235-248: Type assertions bypass type safety.

Both version (line 238) and flags (line 247) are cast to their strict types without validation. If a user passes an invalid flag like --invalid-flag, it will be typed as CompilerFlag despite not being a valid value.

Consider either:

  1. Validating inputs against allowed values and throwing on invalid flags
  2. Using looser types (string[]) for the parsed values
  3. Accepting this as intentional to allow pass-through of unknown flags to the underlying compiler

375-404: LGTM!

The structure mismatch handling logic correctly:

  1. Checks for existing manifest with different structure
  2. Auto-cleans when --force is specified
  3. Throws StructureMismatchError for CLI to handle interactively

This aligns well with the PR objectives from Issue #41.


427-447: LGTM!

Artifact tracking correctly handles both hierarchical (grouped by subdirectory) and flattened (simple array) structures, building the appropriate data shape for the manifest.


464-479: LGTM!

The manifest is written with comprehensive metadata including timing, platform info, compiler flags, and the compiled artifacts list. The conditional structure for artifacts correctly matches the requested structure type.

@0xisk 0xisk force-pushed the feat/artifacts-structure branch from c72b7a6 to 95c03e0 Compare December 15, 2025 14:40
@0xisk 0xisk changed the title feat(cli): introduce artifacts manifest, and strict the types feat(compiler): introduce artifacts manifest, and strict the types Dec 15, 2025
@0xisk 0xisk changed the title feat(compiler): introduce artifacts manifest, and strict the types feat(tools/compiler): introduce artifacts manifest, and strict the types Dec 15, 2025
Comment on lines +52 to +58
// Check for shell metacharacters and embedded quotes
if (/[;&|`$(){}[\]<>'"\\]/.test(normalized)) {
throw new CompilationError(
`Invalid file path: contains unsafe characters: ${filePath}`,
filePath,
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unnecessary when using the execFile API (which is the default). No shell is spawned that can interpret these characters.

Suggested change
// Check for shell metacharacters and embedded quotes
if (/[;&|`$(){}[\]<>'"\\]/.test(normalized)) {
throw new CompilationError(
`Invalid file path: contains unsafe characters: ${filePath}`,
filePath,
);
}

args.push(validatedInputPath, normalizedOutputDir);

try {
return await this.execFileFn('compact', args);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's safer if we use absolute executable paths. The standard install paths for compact can be found here https://github.com/midnightntwrk/compact-export/blob/bc6c7b94a25305821c4f66460fa25dd423f838ea/dist-workspace.toml#L42C1-L43C75

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point actually, could you review it? 39ea434 (#42)

@0xisk 0xisk requested a review from emnul December 22, 2025 14:21
@0xisk
Copy link
Copy Markdown
Member Author

0xisk commented Jan 22, 2026

@emnul could you please review this PR after the fixes?

Comment on lines +51 to +58
* Standard install paths for the Compact CLI.
* Based on the dist-workspace.toml install-path configuration:
* - $XDG_BIN_HOME/
* - $XDG_DATA_HOME/../bin (typically ~/.local/bin)
* - ~/.local/bin
*
* @see https://github.com/midnightntwrk/compact-export/blob/main/dist-workspace.toml
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great documentation!

| `createdAt` | `string` | ISO 8601 timestamp (e.g., `"2025-12-11T10:09:46.023Z"`) |
| `buildDuration` | `number` | Total compilation duration in milliseconds |
| `nodeVersion` | `NodeVersion` | Node.js major version (`"18"` \| `"20"` \| `"21"` \| `"22"` \| `"23"` \| `"24"` \| `"25"`) |
| `platform` | `Platform` | Platform identifier (`"linux-x64"` \| `"linux-arm64"` \| `"darwin-x64"` \| `"darwin-arm64"` \| `"win32-x64"` \| `"win32-arm64"`) |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest only listing supported platforms to avoid confusion.

The supported platforms are "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl"

"createdAt": "2025-12-11T10:35:09.916Z",
"buildDuration": 2445,
"nodeVersion": "22",
"platform": "linux-x64",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above on supported platforms

Copy link
Copy Markdown
Contributor

@emnul emnul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just a few minor comments

@0xisk 0xisk marked this pull request as draft February 24, 2026 10:10
@0xisk 0xisk moved this from Backlog to In progress in OZ Development for Midnight Feb 24, 2026
@0xisk 0xisk self-assigned this Feb 25, 2026
@0xisk 0xisk added the enhancement New feature or request label Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

Artifact Structure Mismatch Detection

2 participants