CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-yeoman-environment

Handles the lifecycle and bootstrapping of generators in a specific environment

Overview
Eval results
Files

filesystem.mddocs/

File System Operations

Memory filesystem operations, conflict resolution, and file transforms for generator output management and package installation tasks.

Capabilities

Commit Shared FileSystem

Commits the memory filesystem to disk with conflict resolution and transforms.

/**
 * Commits the MemFs to disc with conflict resolution
 * @param options - Configuration for commit operation
 */
function commitSharedFsTask(options: {
  /** I/O adapter for user interaction */
  adapter: InputOutputAdapter;
  /** Options for conflict resolution */
  conflicterOptions?: ConflicterOptions;
  /** Memory filesystem store to commit */
  sharedFs: Store<MemFsEditorFile>;
}): Promise<void>;

Usage Example:

import { commitSharedFsTask } from "yeoman-environment";
import { create as createMemFs } from "mem-fs";
import { QueuedAdapter } from "@yeoman/adapter";

// Create memory filesystem and adapter
const memFs = createMemFs();
const adapter = new QueuedAdapter();

// Commit filesystem with conflict resolution
await commitSharedFsTask({
  adapter,
  sharedFs: memFs,
  conflicterOptions: {
    force: false,
    bail: false,
    whitespace: true
  }
});

Package Manager Installation

Execute package manager install with auto-detection and error handling.

/**
 * Executes package manager install with detection and error handling
 * @param options - Configuration for package manager installation
 * @returns Promise resolving to true if installation succeeded
 */
function packageManagerInstallTask(options: PackageManagerInstallTaskOptions): Promise<boolean>;

interface PackageManagerInstallTaskOptions {
  /** Memory filesystem store */
  memFs: Store<MemFsEditorFile>;
  /** Path to directory containing package.json */
  packageJsonLocation: string;
  /** I/O adapter for user interaction */
  adapter: InputOutputAdapter;
  /** Preferred package manager (npm, yarn, pnpm) */
  nodePackageManager?: string;
  /** Custom install task function or boolean to disable */
  customInstallTask?: boolean | ((nodePackageManager: string | undefined, defaultTask: () => Promise<boolean>) => void | Promise<void>);
  /** Skip installation entirely */
  skipInstall?: boolean;
}

Usage Examples:

import { packageManagerInstallTask } from "yeoman-environment";
import { create as createMemFs } from "mem-fs";
import { QueuedAdapter } from "@yeoman/adapter";

const memFs = createMemFs();
const adapter = new QueuedAdapter();

// Basic package manager install
const success = await packageManagerInstallTask({
  memFs,
  packageJsonLocation: "/project/path",
  adapter
});

// With preferred package manager
await packageManagerInstallTask({
  memFs,
  packageJsonLocation: "/project/path", 
  adapter,
  nodePackageManager: "yarn"
});

// With custom install task
await packageManagerInstallTask({
  memFs,
  packageJsonLocation: "/project/path",
  adapter,
  customInstallTask: async (packageManager, defaultTask) => {
    console.log(`Installing with ${packageManager}...`);
    await defaultTask();
    console.log("Installation complete!");
  }
});

// Skip installation
await packageManagerInstallTask({
  memFs,
  packageJsonLocation: "/project/path",
  adapter,
  skipInstall: true
});

Environment FileSystem Integration

File system methods available on Environment instances for generator operations.

/**
 * Watch for package manager install operations
 * Available on Environment and EnvironmentBase instances
 * @param options - Watch configuration options
 */
watchForPackageManagerInstall(options?: any): void;

/**
 * Apply transforms to filesystem
 * Available on Environment and EnvironmentBase instances  
 * @param transformStreams - Transform streams to apply
 * @param options - Transform options
 */
applyTransforms(transformStreams: any[], options?: any): Promise<void>;

/**
 * Install local generator packages
 * Available on Environment instances
 * @param packages - Map of package names to versions
 * @returns True if installation succeeded
 */
installLocalGenerators(packages: Record<string, string | undefined>): Promise<boolean>;

Usage Examples:

import { createEnv } from "yeoman-environment";

const env = createEnv();

// Watch for package manager operations
env.watchForPackageManagerInstall({
  detectExisting: true,
  watchGlob: "**/package.json"
});

// Apply file transforms
await env.applyTransforms([
  myTransformStream,
  anotherTransform
], {
  filter: (file) => file.path.endsWith('.js')
});

// Install local generators
const installed = await env.installLocalGenerators({
  "generator-webapp": "^4.0.1",
  "generator-node": "latest"
});

Conflict Resolution

The commit process includes sophisticated conflict resolution:

Conflict Resolution Options

interface ConflicterOptions {
  /** Override all existing files without prompting */
  force?: boolean;
  /** Fail immediately on first file conflict */
  bail?: boolean;
  /** Treat whitespace-only changes as non-conflicts */
  whitespace?: boolean;
  /** Skip conflict resolution for .yo-resolve files */
  skipYoResolve?: boolean;
  /** Custom conflict resolution adapter */
  adapter?: InputOutputAdapter;
  /** Dry run mode - show conflicts without applying */
  dryRun?: boolean;
}

Conflict Resolution Process

  1. File Scanning: Identify files that will be modified
  2. Existing File Check: Compare with existing files on disk
  3. Conflict Detection: Detect content differences
  4. User Interaction: Prompt for resolution strategy (if not forced)
  5. Resolution Application: Apply chosen resolution
  6. Transform Pipeline: Apply any configured transforms
  7. Disk Commit: Write final files to disk

Conflict Resolution Strategies:

  • Skip: Keep existing file, don't write new content
  • Force: Overwrite existing file with new content
  • Merge: Attempt automatic merge (if possible)
  • Manual: Open editor for manual conflict resolution

File Transform Pipeline

The commit process supports transform streams for file processing:

import { transform } from "@yeoman/transform";

// Example transform usage
await env.applyTransforms([
  // Prettier formatting
  transform.prettierTransform(),
  
  // ESLint fixing
  transform.eslintTransform(),
  
  // Custom transform
  transform((file) => {
    if (file.path.endsWith('.js')) {
      file.contents = Buffer.from(
        file.contents.toString().replace(/var /g, 'const ')
      );
    }
    return file;
  })
]);

Package Manager Detection

The package manager installation system automatically detects the preferred package manager:

Detection Priority

  1. Explicit Option: nodePackageManager option
  2. Lock Files: Presence of yarn.lock, pnpm-lock.yaml, etc.
  3. Environment Variables: npm_config_user_agent
  4. Fallback: npm (default)

Supported Package Managers

  • npm: Default Node.js package manager
  • yarn: Yarn package manager (v1 and v2+)
  • pnpm: Fast, disk space efficient package manager
  • bun: Fast all-in-one JavaScript runtime (if detected)

Custom Package Manager Example:

await packageManagerInstallTask({
  memFs,
  packageJsonLocation: "/project",
  adapter,
  customInstallTask: async (detectedPM, defaultTask) => {
    if (detectedPM === "bun") {
      // Custom bun installation
      const { execa } = await import("execa");
      await execa("bun", ["install"], { cwd: "/project" });
    } else {
      // Use default task for other package managers
      await defaultTask();
    }
  }
});

Advanced FileSystem Operations

Memory FileSystem Integration

import { create as createMemFs } from "mem-fs";
import { create as createEditor } from "mem-fs-editor";

// Create and configure memory filesystem
const memFs = createMemFs();
const editor = createEditor(memFs);

// Write files to memory filesystem
editor.write("/project/src/index.js", "console.log('Hello!');");
editor.copyTpl("/templates/component.js", "/project/src/component.js", {
  name: "MyComponent"
});

// Commit to disk
await commitSharedFsTask({
  adapter: myAdapter,
  sharedFs: memFs,
  conflicterOptions: { force: false }
});

File State Management

// File states in memory filesystem
interface MemFsEditorFile {
  /** File path */
  path: string;
  /** File contents */
  contents: Buffer | null;
  /** File state (created, modified, deleted, etc.) */
  state?: string;
  /** File history */
  history?: string[];
}

File State Checking:

import { isFilePending } from "mem-fs-editor/state";

// Check if file has pending changes
const file = memFs.get("/project/src/index.js");
if (isFilePending(file)) {
  console.log("File has pending changes");
}

Error Handling

Installation Errors

try {
  const success = await packageManagerInstallTask({
    memFs,
    packageJsonLocation: "/project",
    adapter
  });
  
  if (!success) {
    console.log("Installation failed but did not throw");
  }
} catch (error) {
  console.error("Installation error:", error.message);
  
  // Handle specific error types
  if (error.code === "ENOENT") {
    console.error("package.json not found");
  } else if (error.code === "EACCES") {
    console.error("Permission denied");
  }
}

Commit Errors

try {
  await commitSharedFsTask({
    adapter,
    sharedFs: memFs,
    conflicterOptions: { bail: true }
  });
} catch (error) {
  if (error.message.includes("conflict")) {
    console.error("File conflict detected:", error.filePath);
  } else if (error.code === "EPERM") {
    console.error("Permission error writing file");
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-yeoman-environment

docs

cli.md

environment.md

filesystem.md

generator-discovery.md

generator-management.md

index.md

module-lookup.md

tile.json