← back to gregpriday__copytree

Function bodies 337 total

All specs Real LLM only Function bodies
validateReport function · javascript · L67-L75 (9 LOC)
scripts/profile-compare.js
function validateReport(report, label) {
  const required = ['timestamp', 'version', 'duration'];
  const missing = required.filter((k) => report[k] == null);
  if (missing.length > 0) {
    console.error(`${label} is missing required fields: ${missing.join(', ')}`);
    return false;
  }
  return true;
}
copy function · javascript · L102-L276 (175 LOC)
src/api/copy.js
export async function copy(basePath, options = {}) {
  const startTime = Date.now();

  // Guard against null options
  options = options ?? {};

  // Validate basePath
  if (!basePath || typeof basePath !== 'string') {
    throw new ValidationError('basePath must be a non-empty string', 'copy', basePath);
  }

  // Create isolated config instance for this operation if not provided
  // This enables concurrent copy operations with different configurations
  const configInstance = options.config || (await ConfigManager.create());

  // Build progress wrapper: scan gets 0-80%, format gets 80-100%
  const { onProgress, progressThrottleMs } = options;
  let scanProgress = null;
  let lastEmittedPercent = -1;

  /**
   * Emit progress with a monotonic guard so percent never decreases.
   * Swallows exceptions so a buggy callback never breaks the operation.
   */
  const emitProgress = onProgress
    ? (percent, message) => {
        const clamped = Math.max(percent, lastEmittedPercent);
   
copyStream function · javascript · L65-L109 (45 LOC)
src/api/copyStream.js
export async function* copyStream(basePath, options = {}) {
  // Guard against null options
  options = options ?? {};

  // Validate basePath
  if (!basePath || typeof basePath !== 'string') {
    throw new ValidationError('basePath must be a non-empty string', 'copyStream', basePath);
  }

  // Create isolated config instance for this operation if not provided
  const configInstance = options.config || (await ConfigManager.create());

  // Normalize format option
  const rawFormat = (options.format || 'xml').toString().toLowerCase();
  const formatType = rawFormat === 'md' ? 'markdown' : rawFormat;

  // Validate format type
  const validFormats = ['xml', 'json', 'markdown', 'tree', 'ndjson', 'sarif'];
  if (!validFormats.includes(formatType)) {
    throw new ValidationError(
      `Invalid format: ${formatType}. Valid formats: ${validFormats.join(', ')}`,
      'copyStream',
      formatType,
    );
  }

  // Create scan async generator with config for isolation
  const filesGenerat
format function · javascript · L45-L196 (152 LOC)
src/api/format.js
export async function format(files, options = {}) {
  // Guard against null options
  options = options ?? {};

  // Validate files parameter
  if (!files) {
    throw new ValidationError('files parameter is required', 'format', files);
  }

  // Normalize format option
  const rawFormat = (options.format || 'xml').toString().toLowerCase();
  const formatType = rawFormat === 'md' ? 'markdown' : rawFormat;

  // Validate format type
  const validFormats = ['xml', 'json', 'markdown', 'tree', 'ndjson', 'sarif'];
  if (!validFormats.includes(formatType)) {
    throw new ValidationError(
      `Invalid format: ${formatType}. Valid formats: ${validFormats.join(', ')}`,
      'format',
      formatType,
    );
  }

  // Collect files from various input types
  let fileArray;
  if (Array.isArray(files)) {
    fileArray = files;
  } else if (Symbol.asyncIterator && files[Symbol.asyncIterator]) {
    // Async iterable
    fileArray = [];
    for await (const file of files) {
      fileArray.push
formatAsJSON function · javascript · L202-L242 (41 LOC)
src/api/format.js
function formatAsJSON(input, options) {
  const output = {
    directory: input.basePath,
    metadata: {
      generated: new Date().toISOString(),
      fileCount: input.files.length,
      totalSize: calculateTotalSize(input.files),
      directoryStructure: generateDirectoryStructure(input.files),
      ...(input.instructions && {
        instructions: input.instructions,
      }),
    },
    files: input.files.map((file) => {
      const fileObj = {
        path: file.path,
        size: file.size,
        modified: file.modified,
        isBinary: file.isBinary,
        encoding: file.encoding,
      };

      // Add git status if available
      if (file.gitStatus) {
        fileObj.gitStatus = file.gitStatus;
      }

      // Add content unless --only-tree is set
      if (!options.onlyTree && file.content !== undefined) {
        fileObj.content =
          options.addLineNumbers && !file.isBinary
            ? addLineNumbersToContent(file.content)
            : file.content;
formatAsTree function · javascript · L248-L261 (14 LOC)
src/api/format.js
function formatAsTree(input, options) {
  const lines = [];

  lines.push(input.basePath);
  lines.push('');

  // Build tree structure
  const tree = buildTreeStructure(input.files);

  // Render tree
  renderTree(tree, lines, '', true, options);

  return lines.join('\n');
}
generateDirectoryStructure function · javascript · L275-L286 (12 LOC)
src/api/format.js
function generateDirectoryStructure(files) {
  const dirs = new Set();

  for (const file of files) {
    const parts = file.path.split('/');
    for (let i = 1; i < parts.length; i++) {
      dirs.add(parts.slice(0, i).join('/'));
    }
  }

  return Array.from(dirs).sort();
}
Repobility · open methodology · https://repobility.com/research/
addLineNumbersToContent function · javascript · L292-L304 (13 LOC)
src/api/format.js
function addLineNumbersToContent(content) {
  if (!content) return content;

  const lines = content.split('\n');
  const format = config().get('copytree.lineNumberFormat', '%4d: ');

  return lines
    .map((line, index) => {
      const lineNum = (index + 1).toString().padStart(4, ' ');
      return format.replace('%4d', lineNum) + line;
    })
    .join('\n');
}
buildTreeStructure function · javascript · L310-L333 (24 LOC)
src/api/format.js
function buildTreeStructure(files) {
  const tree = {};

  for (const file of files) {
    const parts = file.path.split('/');
    let current = tree;

    for (let i = 0; i < parts.length; i++) {
      const part = parts[i];
      if (i === parts.length - 1) {
        // Leaf node (file)
        current[part] = { _file: file };
      } else {
        // Directory node
        if (!current[part]) {
          current[part] = {};
        }
        current = current[part];
      }
    }
  }

  return tree;
}
renderTree function · javascript · L339-L371 (33 LOC)
src/api/format.js
function renderTree(node, lines, prefix = '', isLast = true, options = {}) {
  const entries = Object.entries(node).sort(([a], [b]) => {
    // Directories first, then files
    const aIsFile = node[a]?._file;
    const bIsFile = node[b]?._file;
    if (aIsFile && !bIsFile) return 1;
    if (!aIsFile && bIsFile) return -1;
    return a.localeCompare(b);
  });

  entries.forEach(([name, value], index) => {
    const isLastEntry = index === entries.length - 1;
    const connector = isLastEntry ? '└── ' : '├── ';
    const extension = isLastEntry ? '    ' : '│   ';

    if (value._file) {
      // File
      const file = value._file;
      let line = prefix + connector + name;

      if (options.showSize) {
        const sizeStr = formatBytes(file.size);
        line += ` (${sizeStr})`;
      }

      lines.push(line);
    } else {
      // Directory
      lines.push(prefix + connector + name + '/');
      renderTree(value, lines, prefix + extension, isLastEntry, options);
    }
  });
}
formatBytes function · javascript · L377-L383 (7 LOC)
src/api/format.js
function formatBytes(bytes) {
  if (bytes === 0) return '0 B';
  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
formatStream function · javascript · L59-L119 (61 LOC)
src/api/formatStream.js
export async function* formatStream(files, options = {}) {
  // Guard against null options
  options = options ?? {};

  // Validate files parameter
  if (!files) {
    throw new ValidationError('files parameter is required', 'formatStream', files);
  }

  // Normalize format option
  const rawFormat = (options.format || 'xml').toString().toLowerCase();
  const formatType = rawFormat === 'md' ? 'markdown' : rawFormat;

  // Validate format type
  const validFormats = ['xml', 'json', 'markdown', 'tree', 'ndjson', 'sarif'];
  if (!validFormats.includes(formatType)) {
    throw new ValidationError(
      `Invalid format: ${formatType}. Valid formats: ${validFormats.join(', ')}`,
      'formatStream',
      formatType,
    );
  }

  // Get config instance
  const cfg = options.config || config();

  // Create helper functions context
  const helpers = createHelpers(cfg, options);

  // Route to appropriate streaming formatter
  switch (formatType) {
    case 'xml':
      yield* streamXML(f
createHelpers function · javascript · L125-L220 (96 LOC)
src/api/formatStream.js
function createHelpers(cfg, options) {
  return {
    config: cfg,
    options: options,

    calculateTotalSize(files) {
      return files.reduce((sum, file) => sum + (file?.size || 0), 0);
    },

    addLineNumbersToContent(content) {
      if (!content) return content;
      const lines = content.split('\n');
      const format = cfg.get('copytree.lineNumberFormat', '%4d: ');
      return lines
        .map((line, index) => {
          const lineNum = (index + 1).toString().padStart(4, ' ');
          return format.replace('%4d', lineNum) + line;
        })
        .join('\n');
    },

    formatBytes(bytes) {
      if (bytes === 0) return '0 B';
      const k = 1024;
      const sizes = ['B', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
    },

    buildTreeStructure(files) {
      const tree = {};
      for (const file of files) {
        if (!file) continue;
        
collectFiles function · javascript · L226-L238 (13 LOC)
src/api/formatStream.js
async function collectFiles(files) {
  if (Array.isArray(files)) {
    return files.filter((f) => f && typeof f === 'object' && f.path);
  }

  const collected = [];
  for await (const file of files) {
    if (file && typeof file === 'object' && file.path) {
      collected.push(file);
    }
  }
  return collected;
}
streamXML function · javascript · L244-L342 (99 LOC)
src/api/formatStream.js
async function* streamXML(files, options, helpers) {
  const basePath = options.basePath || '.';
  const addLineNumbers = options.addLineNumbers || options.withLineNumbers || false;
  const onlyTree = options.onlyTree || false;

  // For XML, we need to collect files first to get metadata
  const fileArray = await collectFiles(files);
  const totalSize = helpers.calculateTotalSize(fileArray);
  const fileCount = fileArray.length;
  const generated = new Date().toISOString();

  // Header
  yield '<?xml version="1.0" encoding="UTF-8"?>\n';
  yield `<ct:directory xmlns:ct="urn:copytree" path="${basePath}">\n`;

  // Metadata
  yield '  <ct:metadata>\n';
  yield `    <ct:generated>${generated}</ct:generated>\n`;
  yield `    <ct:fileCount>${fileCount}</ct:fileCount>\n`;
  yield `    <ct:totalSize>${totalSize}</ct:totalSize>\n`;

  // Instructions if present
  if (options.instructions) {
    const instr = helpers.escapeCdata(options.instructions);
    yield `    <ct:instructions><![CDATA[$
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
streamJSON function · javascript · L348-L417 (70 LOC)
src/api/formatStream.js
async function* streamJSON(files, options, helpers) {
  const basePath = options.basePath || '.';
  const addLineNumbers = options.addLineNumbers || options.withLineNumbers || false;
  const onlyTree = options.onlyTree || false;
  const prettyPrint = options.prettyPrint ?? true;

  // Collect files for metadata
  const fileArray = await collectFiles(files);
  const totalSize = helpers.calculateTotalSize(fileArray);
  const generated = new Date().toISOString();

  // Header
  yield '{\n';
  yield `  "directory": ${JSON.stringify(basePath)},\n`;
  yield '  "metadata": {\n';
  yield `    "generated": "${generated}",\n`;
  yield `    "fileCount": ${fileArray.length},\n`;
  yield `    "totalSize": ${totalSize},\n`;
  yield `    "profile": "default",\n`;
  yield `    "directoryStructure": ${JSON.stringify(helpers.generateDirectoryStructure(fileArray))}`;

  if (options.instructions) {
    yield ',\n';
    yield `    "instructions": ${JSON.stringify(options.instructions)}`;
  }

  yield '\n  
streamMarkdown function · javascript · L423-L580 (158 LOC)
src/api/formatStream.js
async function* streamMarkdown(files, options, helpers) {
  const basePath = options.basePath || '.';
  const addLineNumbers = options.addLineNumbers || options.withLineNumbers || false;
  const onlyTree = options.onlyTree || false;
  const includeGitStatus = options.withGitStatus || false;

  // Collect files for tree structure
  const fileArray = await collectFiles(files);
  const totalSize = helpers.calculateTotalSize(fileArray);
  const generated = new Date().toISOString();
  const charLimitApplied = fileArray.some((f) => f?.truncated);

  // YAML front matter
  yield '---\n';
  yield 'format: copytree-md@1\n';
  yield 'tool: copytree\n';
  yield `generated: ${escapeYamlScalar(generated)}\n`;
  yield `base_path: ${escapeYamlScalar(basePath)}\n`;
  yield `profile: ${escapeYamlScalar('default')}\n`;
  yield `file_count: ${fileArray.length}\n`;
  yield `total_size_bytes: ${totalSize}\n`;
  yield `char_limit_applied: ${charLimitApplied ? 'true' : 'false'}\n`;
  yield `only_tree: ${only
streamNDJSON function · javascript · L586-L667 (82 LOC)
src/api/formatStream.js
async function* streamNDJSON(files, options, helpers) {
  const basePath = options.basePath || '.';
  const addLineNumbers = options.addLineNumbers || options.withLineNumbers || false;
  const onlyTree = options.onlyTree || false;

  // For NDJSON we need to collect to get accurate counts
  const fileArray = await collectFiles(files);
  const totalSize = helpers.calculateTotalSize(fileArray);
  const generated = new Date().toISOString();

  // Metadata record
  const metadata = {
    type: 'metadata',
    directory: basePath,
    generated: generated,
    fileCount: fileArray.length,
    totalSize: totalSize,
    profile: 'default',
  };

  if (options.instructions) {
    metadata.instructions = {
      name: 'default',
      content: options.instructions,
    };
  }

  yield JSON.stringify(metadata) + '\n';

  // File records
  for (const file of fileArray) {
    if (!file) continue;

    const record = {
      type: 'file',
      path: file.path,
      size: file.size,
      modified
streamTree function · javascript · L673-L686 (14 LOC)
src/api/formatStream.js
async function* streamTree(files, options, helpers) {
  const basePath = options.basePath || '.';

  // Tree requires all files to build structure
  const fileArray = await collectFiles(files);

  yield basePath + '\n\n';

  const tree = helpers.buildTreeStructure(fileArray);
  const treeLines = [];
  helpers.renderTree(tree, treeLines, '', true);

  yield treeLines.join('\n') + '\n';
}
streamSARIF function · javascript · L692-L783 (92 LOC)
src/api/formatStream.js
async function* streamSARIF(files, options, helpers) {
  const basePath = options.basePath || '.';

  // SARIF requires all files
  const fileArray = await collectFiles(files);
  const totalSize = helpers.calculateTotalSize(fileArray);
  const generated = new Date().toISOString();
  const prettyPrint = options.prettyPrint ?? true;

  const results = fileArray.map((file) => {
    const totalLines =
      typeof file.content === 'string' && !file.isBinary ? file.content.split('\n').length : 0;

    const result = {
      ruleId: 'file-discovered',
      level: 'note',
      message: { text: `File discovered: ${file.path}` },
      locations: [
        {
          physicalLocation: {
            artifactLocation: {
              uri: file.path,
              uriBaseId: '%SRCROOT%',
            },
          },
        },
      ],
      properties: {
        size: file.size || 0,
        modified: file.modified || null,
        isBinary: !!file.isBinary,
      },
    };

    if (totalLines 
parseExtensions function · javascript · L248-L260 (13 LOC)
src/commands/copy.js
function parseExtensions(extStr) {
  const exts = extStr
    .split(',')
    .map((e) => e.trim())
    .filter(Boolean)
    .map((e) => (e.startsWith('.') ? e.toLowerCase() : `.${e.toLowerCase()}`));
  if (exts.length === 0) {
    throw new CommandError(
      `Invalid --ext value '${extStr}'. Provide at least one extension, e.g., .js,.ts`,
    );
  }
  return exts;
}
parseSizeOption function · javascript · L269-L277 (9 LOC)
src/commands/copy.js
function parseSizeOption(sizeStr, flagName) {
  try {
    return parseSize(sizeStr);
  } catch {
    throw new CommandError(
      `Invalid --${flagName} value '${sizeStr}'. Use a format like 1KB, 500B, 10MB, 1GB.`,
    );
  }
}
buildProfileFromCliOptions function · javascript · L283-L397 (115 LOC)
src/commands/copy.js
async function buildProfileFromCliOptions(options) {
  const copytreeConfig = config().get('copytree', {});

  // Try to load folder profile if requested or if -r/--as-reference is used
  // Note: options.folderProfile is the renamed --folder-profile/-p flag;
  //       options.profile is now reserved for performance profiling (cpu/heap/all).
  let folderProfile = null;
  if (options.folderProfile || options.asReference) {
    const loader = new FolderProfileLoader({ cwd: process.cwd() });
    try {
      if (options.folderProfile) {
        // Load named profile: -p <name> / --folder-profile <name>
        folderProfile = await loader.loadNamed(options.folderProfile);
        logger.debug(`Loaded folder profile: ${options.folderProfile}`);
      } else {
        // Auto-discover profile for -r/--as-reference
        folderProfile = await loader.discover();
        if (folderProfile) {
          logger.debug(`Auto-discovered folder profile: ${folderProfile.name}`);
        }
      }
  
Powered by Repobility — scan your code at https://repobility.com
setupPipelineStages function · javascript · L402-L569 (168 LOC)
src/commands/copy.js
async function setupPipelineStages(basePath, profile, options) {
  const stages = [];

  // Merge force-include patterns from all sources (CLI, profile, .copytreeinclude)
  const mergedAlways = [
    ...(Array.isArray(options.always) ? options.always : options.always ? [options.always] : []),
    ...(Array.isArray(profile.always) ? profile.always : []),
    // .copytreeinclude is loaded by the stage itself
  ];

  // 1. File Discovery Stage
  const { default: FileDiscoveryStage } = await import('../pipeline/stages/FileDiscoveryStage.js');
  stages.push(
    new FileDiscoveryStage({
      basePath,
      patterns: profile.include || ['**/*'],
      respectGitignore: profile.options?.respectGitignore ?? true,
      includeHidden: profile.options?.includeHidden ?? false,
      followSymlinks: profile.options?.followSymlinks ?? false,
      maxFileSize: profile.options?.maxFileSize,
      maxTotalSize: profile.options?.maxTotalSize,
      maxFileCount: profile.options?.maxFileCount,
      
prepareOutput function · javascript · L574-L606 (33 LOC)
src/commands/copy.js
async function prepareOutput(result, options) {
  // If streaming was used, output has already been handled
  if (result.streamed) {
    const fileCount = result.files.filter((f) => f !== null).length;
    const totalSize = result.files
      .filter((f) => f !== null)
      .reduce((sum, file) => sum + (file.size || 0), 0);

    return {
      type: 'streamed',
      fileCount,
      totalSize,
      outputPath: options.output,
    };
  }

  const output = result.output;

  if (!output) {
    throw new CommandError('No output generated', 'copy');
  }

  // Calculate output size
  const outputSize = Buffer.byteLength(output, 'utf8');
  const fileCount = result.files.filter((f) => f !== null).length;

  return {
    type: 'normal',
    output,
    outputSize,
    fileCount,
  };
}
displayOutput function · javascript · L611-L711 (101 LOC)
src/commands/copy.js
async function displayOutput(outputResult, options, basePath) {
  const { type, output, outputSize, fileCount, totalSize, outputPath } = outputResult;

  if (type === 'streamed') {
    if (outputPath) {
      logger.success(
        `Streamed ${fileCount} files [${logger.formatBytes(totalSize)}] to ${path.resolve(outputPath)}`,
      );
    } else {
      logger.success(`Streamed ${fileCount} files [${logger.formatBytes(totalSize)}]`);
    }
    return;
  }

  // Handle --as-reference option
  if (options.asReference) {
    const f = (options.format || 'xml').toString().toLowerCase();
    const format = f === 'md' ? 'markdown' : f;
    const extension =
      format === 'json'
        ? 'json'
        : format === 'markdown'
          ? 'md'
          : format === 'tree'
            ? 'txt'
            : format === 'ndjson'
              ? 'ndjson'
              : format === 'sarif'
                ? 'sarif'
                : 'xml';
    const os = await import('os');
    const dirName 
showSummary function · javascript · L716-L744 (29 LOC)
src/commands/copy.js
function showSummary(result, startTime) {
  const duration = Date.now() - startTime;
  const stats = result.stats || {};

  console.log('\n📊 Summary:');
  console.log(`  Files processed: ${result.files.length}`);

  // Calculate total size from files
  const totalSize = result.files.reduce((sum, file) => sum + (file.size || 0), 0);
  console.log(`  Total size: ${logger.formatBytes(totalSize)}`);
  console.log(`  Output size: ${logger.formatBytes(result.outputSize || 0)}`);
  console.log(`  Duration: ${logger.formatDuration(duration)}`);

  if (stats.excludedFiles > 0) {
    console.log(`  Excluded files: ${stats.excludedFiles}`);
  }

  // Show secrets guard stats if present
  if (stats.secretsGuard) {
    const sg = stats.secretsGuard;
    console.log(
      `  🔒 Secrets Guard: ${sg.filesExcluded || 0} files excluded, ${sg.secretsRedacted || 0} redactions, ${sg.secretsFound || 0} findings`,
    );
  }

  if (result.errors && result.errors.length > 0) {
    console.log(`  Errors: ${res
ConfigManager.constructor method · javascript · L13-L45 (33 LOC)
src/config/ConfigManager.js
  constructor(options = {}) {
    this.config = {};
    this.configPath = path.join(moduleDir, '../../config');
    this.userConfigPath = path.join(os.homedir(), '.copytree');

    // Check if validation should be disabled via options or environment
    this.validationEnabled =
      !options.noValidate &&
      process.env.COPYTREE_NO_VALIDATE !== 'true' &&
      process.env.NODE_ENV !== 'test';

    // Initialize AJV validator
    this.ajv = new Ajv({
      allErrors: true,
      removeAdditional: false,
      strict: false,
      coerceTypes: true,
    });
    addFormats(this.ajv);

    this.schema = null;
    this.validate = null;
    this.schemaVersion = '1.0.0';

    // Track configuration sources for provenance
    this.configSources = {};
    this.defaultConfig = {};
    this.userConfig = {};
    this.envOverrides = {};

    // Flag to track if configuration has been loaded
    this._initialized = false;
  }
ConfigManager.loadConfiguration method · javascript · L58-L78 (21 LOC)
src/config/ConfigManager.js
  async loadConfiguration() {
    // Prevent double initialization
    if (this._initialized) {
      return;
    }

    // If already initializing, wait for completion
    if (this._initializing) {
      return this._initPromise;
    }

    // Mark as initializing and create promise
    this._initializing = true;
    this._initPromise = this._doLoadConfiguration();

    try {
      await this._initPromise;
    } finally {
      this._initializing = false;
    }
  }
ConfigManager._doLoadConfiguration method · javascript · L80-L96 (17 LOC)
src/config/ConfigManager.js
  async _doLoadConfiguration() {
    // 1. Load schema for validation
    await this.loadSchema();

    // 2. Load default configuration files
    await this.loadDefaults();

    // 3. Load user configuration overrides
    await this.loadUserConfig();

    // 4. Validate final configuration if enabled
    if (this.validationEnabled) {
      this.validateConfig();
    }

    this._initialized = true;
  }
ConfigManager.loadDefaults method · javascript · L98-L114 (17 LOC)
src/config/ConfigManager.js
  async loadDefaults() {
    const configFiles = fs.readdirSync(this.configPath).filter((file) => file.endsWith('.js'));

    for (const file of configFiles) {
      const configName = path.basename(file, '.js');
      try {
        const filePath = path.join(this.configPath, file);
        const moduleUrl = pathToFileURL(filePath).href;
        const configModule = await import(moduleUrl);
        const configData = configModule.default || configModule;
        this.config[configName] = configData;
        this.defaultConfig[configName] = _.cloneDeep(configData);
      } catch (error) {
        console.error(`Failed to load config ${configName}:`, error.message);
      }
    }
  }
Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
ConfigManager.loadUserConfig method · javascript · L116-L149 (34 LOC)
src/config/ConfigManager.js
  async loadUserConfig() {
    if (!fs.existsSync(this.userConfigPath)) {
      return;
    }

    const userConfigFiles = fs
      .readdirSync(this.userConfigPath)
      .filter((file) => file.endsWith('.js') || file.endsWith('.json'));

    for (const file of userConfigFiles) {
      const configName = path.basename(file).replace(/\.(js|json)$/, '');
      const filePath = path.join(this.userConfigPath, file);

      try {
        let userConfigData;
        if (file.endsWith('.json')) {
          userConfigData = fs.readJsonSync(filePath);
        } else {
          // Use dynamic import for ES modules
          const moduleUrl = pathToFileURL(filePath).href + `?t=${Date.now()}`; // Add timestamp to bypass cache
          const configModule = await import(moduleUrl);
          userConfigData = configModule.default || configModule;
        }

        // Store user config for provenance tracking
        this.userConfig[configName] = _.cloneDeep(userConfigData);

        // Deep merge
ConfigManager.loadSchema method · javascript · L204-L226 (23 LOC)
src/config/ConfigManager.js
  async loadSchema() {
    try {
      const schemaPath = path.join(this.configPath, 'schema.json');

      if (await fs.pathExists(schemaPath)) {
        this.schema = await fs.readJson(schemaPath);

        // Create a unique schema ID to avoid conflicts
        const uniqueSchema = {
          ...this.schema,
          $id: `${this.schema.$id || 'copytree-config'}-${Date.now()}`,
        };

        this.validate = this.ajv.compile(uniqueSchema);
      } else {
        console.warn('Configuration schema not found. Validation disabled.');
        this.validationEnabled = false;
      }
    } catch (error) {
      console.warn(`Failed to load configuration schema: ${error.message}`);
      this.validationEnabled = false;
    }
  }
ConfigManager.validateConfig method · javascript · L231-L258 (28 LOC)
src/config/ConfigManager.js
  validateConfig() {
    if (!this.validationEnabled || !this.validate) {
      return; // Validation disabled or schema not loaded, skip validation
    }

    const isValid = this.validate(this.config);

    if (!isValid) {
      const errors = this.validate.errors
        .map((err) => {
          const path = err.instancePath || '(root)';
          const message = err.message;
          const value = err.data !== undefined ? ` (got: ${JSON.stringify(err.data)})` : '';
          return `${path}: ${message}${value}`;
        })
        .join('; ');

      throw new ConfigurationError(
        `Configuration validation failed: ${errors}`,
        'SCHEMA_VALIDATION_ERROR',
        {
          validationErrors: this.validate.errors,
          schemaVersion: this.schemaVersion,
          config: this.config,
        },
      );
    }
  }
ConfigManager.migrateConfig method · javascript · L283-L288 (6 LOC)
src/config/ConfigManager.js
  migrateConfig(config, fromVersion, toVersion) {
    // Schema migration logic can be added here
    // For now, just return the config unchanged
    console.log(`Config migration from ${fromVersion} to ${toVersion} not implemented yet`);
    return config;
  }
ConfigManager.getSchemaInfo method · javascript · L294-L302 (9 LOC)
src/config/ConfigManager.js
  getSchemaInfo() {
    return {
      version: this.schemaVersion,
      loaded: this.schema !== null,
      validationEnabled: this.validationEnabled,
      schemaId: this.schema?.$id || null,
      title: this.schema?.title || null,
    };
  }
ConfigManager.effective method · javascript · L319-L340 (22 LOC)
src/config/ConfigManager.js
  effective(options = {}) {
    const { redact = true, section = null } = options;
    const result = {};

    // Get the config to walk through
    const configToWalk = section ? this.config[section] || {} : this.config;
    const prefix = section || '';

    this._walkConfig(configToWalk, prefix, (path, value) => {
      const source = this._getConfigSource(path, value);
      const shouldRedact = redact && this._shouldRedact(path);

      result[path] = {
        value: shouldRedact ? '***' : value,
        source,
        type: typeof value,
        redacted: shouldRedact,
      };
    });

    return result;
  }
ConfigManager._walkConfig method · javascript · L346-L358 (13 LOC)
src/config/ConfigManager.js
  _walkConfig(obj, prefix, callback) {
    for (const [key, value] of Object.entries(obj)) {
      const path = prefix ? `${prefix}.${key}` : key;

      if (value && typeof value === 'object' && !Array.isArray(value)) {
        // Handle nested objects
        this._walkConfig(value, path, callback);
      } else {
        // Call callback for leaf values
        callback(path, value);
      }
    }
  }
ConfigManager._getConfigSource method · javascript · L364-L372 (9 LOC)
src/config/ConfigManager.js
  _getConfigSource(path, value) {
    // Check if value exists in user config
    if (this._isFromUserConfig(path, value)) {
      return 'user-config';
    }

    // Default to default config
    return 'default';
  }
Repobility · open methodology · https://repobility.com/research/
ConfigManager._isFromUserConfig method · javascript · L378-L389 (12 LOC)
src/config/ConfigManager.js
  _isFromUserConfig(path, value) {
    const pathParts = path.split('.');
    const configSection = pathParts[0];

    if (!this.userConfig[configSection]) {
      return false;
    }

    // Get the value from user config at this path
    const userValue = _.get(this.userConfig, path);
    return userValue !== undefined && _.isEqual(userValue, value);
  }
config function · javascript · L428-L450 (23 LOC)
src/config/ConfigManager.js
export function config(options = {}) {
  // Deprecation warning removed to prevent CLI noise until migration is complete
  /*
  // Emit deprecation warning (only once per process)
  if (!config._deprecationWarned && process.env.NODE_ENV !== 'test') {
    config._deprecationWarned = true;
    console.warn(
      '[CopyTree] config() singleton is deprecated. Use ConfigManager.create() for concurrent operations.',
    );
  }
  */

  if (!instance) {
    instance = new ConfigManager(options);
    // Initialize asynchronously in background (for backward compatibility)
    // Consumers should await config().loadConfiguration() if they need to ensure initialization
    initPromise = instance.loadConfiguration().catch(console.error);
  } else if (options.noValidate !== undefined) {
    // Allow runtime disabling of validation
    instance.setValidationEnabled(!options.noValidate);
  }
  return instance;
}
configAsync function · javascript · L462-L486 (25 LOC)
src/config/ConfigManager.js
export async function configAsync(options = {}) {
  // Deprecation warning removed to prevent CLI noise until migration is complete
  /*
  // Emit deprecation warning (only once per process)
  if (!configAsync._deprecationWarned && process.env.NODE_ENV !== 'test') {
    configAsync._deprecationWarned = true;
    console.warn(
      '[CopyTree] configAsync() singleton is deprecated. Use ConfigManager.create() for concurrent operations.',
    );
  }
  */

  if (!instance) {
    instance = await ConfigManager.create(options);
  } else {
    // Await any in-flight initialization
    if (initPromise) {
      await initPromise;
    }
    if (options.noValidate !== undefined) {
      instance.setValidationEnabled(!options.noValidate);
    }
  }
  return instance;
}
FolderProfileLoader.discover method · javascript · L25-L36 (12 LOC)
src/config/FolderProfileLoader.js
  async discover() {
    const extensions = ['.yml', '.yaml', '.json', ''];

    for (const ext of extensions) {
      const filePath = path.join(this.cwd, `.copytree${ext}`);
      if (await fs.pathExists(filePath)) {
        return await this.load(filePath);
      }
    }

    return null;
  }
FolderProfileLoader.loadNamed method · javascript · L45-L59 (15 LOC)
src/config/FolderProfileLoader.js
  async loadNamed(name) {
    const extensions = ['.yml', '.yaml', '.json', ''];

    for (const ext of extensions) {
      const filePath = path.join(this.cwd, `.copytree-${name}${ext}`);
      if (await fs.pathExists(filePath)) {
        return await this.load(filePath);
      }
    }

    throw new ConfigurationError(`Profile not found: ${name}`, 'FolderProfileLoader', {
      name,
      searchPath: this.cwd,
    });
  }
FolderProfileLoader.load method · javascript · L67-L94 (28 LOC)
src/config/FolderProfileLoader.js
  async load(filePath) {
    try {
      const content = await fs.readFile(filePath, 'utf8');
      const ext = path.extname(filePath);

      let data;
      if (ext === '.json') {
        data = JSON.parse(content);
      } else if (ext === '.yml' || ext === '.yaml') {
        data = yaml.load(content);
        // YAML parsing empty file returns null/undefined, treat as empty object
        if (!data) {
          data = {};
        }
      } else {
        // Try to parse as INI-style format
        data = this.parseINI(content);
      }

      return this.validate(data, filePath);
    } catch (error) {
      throw new ConfigurationError(
        `Failed to load profile from ${filePath}: ${error.message}`,
        'FolderProfileLoader',
        { filePath, originalError: error.message },
      );
    }
  }
FolderProfileLoader.validate method · javascript · L103-L155 (53 LOC)
src/config/FolderProfileLoader.js
  validate(data, filePath) {
    if (!data || typeof data !== 'object' || Array.isArray(data)) {
      throw new ConfigurationError(
        `Invalid profile data: must be an object`,
        'FolderProfileLoader',
        { filePath, data },
      );
    }

    // Normalize include patterns
    let include = [];
    if (data.include) {
      if (Array.isArray(data.include)) {
        include = data.include.filter((p) => typeof p === 'string' && p.trim().length > 0);
      } else if (typeof data.include === 'string' && data.include.trim().length > 0) {
        include = [data.include];
      }
    }

    // Normalize exclude patterns
    let exclude = [];
    if (data.exclude) {
      if (Array.isArray(data.exclude)) {
        exclude = data.exclude.filter((p) => typeof p === 'string' && p.trim().length > 0);
      } else if (typeof data.exclude === 'string' && data.exclude.trim().length > 0) {
        exclude = [data.exclude];
      }
    }

    // Normalize always patterns
    let al
FolderProfileLoader.parseINI method · javascript · L163-L206 (44 LOC)
src/config/FolderProfileLoader.js
  parseINI(content) {
    const profile = { include: [], exclude: [], always: [], options: {} };
    let currentSection = null;

    for (const line of content.split('\n')) {
      const trimmed = line.trim();

      // Skip empty lines and comments
      if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith(';')) {
        continue;
      }

      // Section headers
      if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
        currentSection = trimmed.slice(1, -1).toLowerCase();
        continue;
      }

      // Section content
      if (currentSection === 'include') {
        profile.include.push(trimmed);
      } else if (currentSection === 'exclude') {
        profile.exclude.push(trimmed);
      } else if (currentSection === 'always') {
        profile.always.push(trimmed);
      } else if (currentSection === 'options' && trimmed.includes('=')) {
        const [key, value] = trimmed.split('=').map((s) => s.trim());
        if (key) {
          // Try to parse boo
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
FolderProfileLoader.listProfiles method · javascript · L213-L230 (18 LOC)
src/config/FolderProfileLoader.js
  async listProfiles() {
    const profiles = [];
    const files = await fs.readdir(this.cwd);

    const pattern = /^\.copytree-([^.]+)(\.(yml|yaml|json))?$/;

    for (const file of files) {
      const match = file.match(pattern);
      if (match) {
        const profileName = match[1];
        if (!profiles.includes(profileName)) {
          profiles.push(profileName);
        }
      }
    }

    return profiles.sort();
  }
FolderProfileLoader.exists method · javascript · L237-L258 (22 LOC)
src/config/FolderProfileLoader.js
  async exists(name = null) {
    if (name) {
      const extensions = ['.yml', '.yaml', '.json', ''];
      for (const ext of extensions) {
        const filePath = path.join(this.cwd, `.copytree-${name}${ext}`);
        if (await fs.pathExists(filePath)) {
          return true;
        }
      }
      return false;
    } else {
      // Check for auto-discoverable profile
      const extensions = ['.yml', '.yaml', '.json', ''];
      for (const ext of extensions) {
        const filePath = path.join(this.cwd, `.copytree${ext}`);
        if (await fs.pathExists(filePath)) {
          return true;
        }
      }
      return false;
    }
  }
MarkdownFormatter.format method · javascript · L19-L206 (188 LOC)
src/pipeline/formatters/MarkdownFormatter.js
  async format(input) {
    const lines = [];
    const files = (input.files || []).filter((f) => f !== null);
    const fileCount = files.length;
    const totalSize = this.stage.calculateTotalSize(files);
    const profileName = input.profile?.name || 'default';
    const includeGitStatus = !!input.options?.withGitStatus;
    const includeLineNumbers = !!(this.addLineNumbers || input.options?.withLineNumbers);
    const onlyTree = !!(this.onlyTree || input.options?.onlyTree);
    const charLimitApplied = !!(
      input.options?.charLimit ||
      input.stats?.truncatedFiles > 0 ||
      files.some((f) => f?.truncated)
    );

    // YAML front matter
    lines.push('---');
    lines.push('format: copytree-md@1');
    lines.push('tool: copytree');
    lines.push(`generated: ${escapeYamlScalar(new Date().toISOString())}`);
    lines.push(`base_path: ${escapeYamlScalar(input.basePath)}`);
    lines.push(`profile: ${escapeYamlScalar(profileName)}`);
    lines.push(`file_count: ${fileCou
page 1 / 7next ›