Summary
Expose a clean, typed programmatic API for using CopyTree as a library in Node.js applications, enabling developers to integrate file discovery, transformation, and formatting into their build tools and scripts.
Problem Statement
CopyTree is currently CLI-only with no programmatic API:
No entry point:
// ❌ This doesn't work
import { scan, format } from 'copytree';
import copytree from 'copytree'; // No default export
Current workarounds:
// Ugly: Spawn CLI process
import { exec } from 'child_process';
exec('copytree --format json -o output.json', ...);
// Brittle: Directly import internal modules
import copyCommand from 'copytree/src/commands/copy.js'; // Undocumented
Blocked use cases:
- Custom build tools (Vite plugins, Webpack loaders)
- GitHub Actions (programmatic file collection)
- Documentation generators (introspecting codebases)
- Testing frameworks (fixture generation)
- CI/CD pipelines (programmatic output processing)
User Story
As a tool developer
I want a stable programmatic API for CopyTree
So that I can integrate file discovery and transformation into my Node.js applications
Current State
Package structure:
- Entry point: None (
package.json only defines bin)
- Exports: Only
copyCommand() from src/commands/copy.js
- Internal modules: Not documented for public use
- TypeScript: No
.d.ts type definitions
Key internal modules (undocumented):
Expected Behavior
Public API Surface
// Main functions
import { scan, format, copy } from 'copytree';
// Core classes (for advanced usage)
import { Pipeline, ProfileLoader, TransformerRegistry } from 'copytree';
// Utilities
import { parseProfile, validateConfig } from 'copytree/utils';
// Types (if TypeScript support added)
import type { ScanOptions, FormatOptions, FileResult } from 'copytree';
Primary API: scan()
Discover and filter files without formatting:
import { scan } from 'copytree';
// Simple scan
const files = await scan('/path/to/project');
// Returns: AsyncIterable<FileResult>
// With options
const files = await scan('/path/to/project', {
profile: 'default', // Profile name or object
filter: ['**/*.js'], // Additional patterns
exclude: ['node_modules'], // Exclusions
respectGitignore: true, // Use .gitignore
modified: true, // Git modified files only
maxDepth: 3, // Directory depth limit
transform: true, // Apply transformers
transformers: ['pdf', 'ai-summary'] // Specific transformers
});
// Iterate results
for await (const file of files) {
console.log(file.path, file.size, file.content);
}
// Collect all (be careful with memory)
const allFiles = [];
for await (const file of files) {
allFiles.push(file);
}
Secondary API: format()
Format a list of files into output:
import { format } from 'copytree';
const files = [...]; // From scan() or custom source
// Format to string
const xml = await format(files, { format: 'xml' });
const json = await format(files, { format: 'json' });
const markdown = await format(files, { format: 'markdown' });
const ndjson = await format(files, { format: 'ndjson' });
// Format with options
const output = await format(files, {
format: 'xml',
onlyTree: false,
addLineNumbers: true,
basePath: '/project',
instructions: 'Please review this code'
});
Convenience API: copy()
Complete end-to-end operation (equivalent to CLI):
import { copy } from 'copytree';
// Simple copy (returns formatted string)
const result = await copy('/path/to/project');
// With options (matches CLI flags)
const result = await copy('/path/to/project', {
profile: 'default',
format: 'json',
output: './output.json', // Write to file
display: true, // Also log to console
clipboard: false, // Don't copy to clipboard
modified: true, // Git modified only
charLimit: 50000, // Character budget
// ... all CLI options supported
});
// Result structure
{
output: '...', // Formatted string
files: [...], // File list
stats: {
totalFiles: 123,
duration: 523,
totalSize: 456789,
secretsGuard: {...} // If enabled
}
}
Advanced API: Classes
For complex use cases:
import { Pipeline, ProfileLoader, TransformerRegistry } from 'copytree';
// Custom pipeline
const pipeline = new Pipeline({ continueOnError: true });
pipeline.through([
new FileDiscoveryStage({ basePath: '/project' }),
new ProfileFilterStage({ profile }),
new TransformStage({ transformers: registry }),
new OutputFormattingStage({ format: 'json' })
]);
const result = await pipeline.process({ basePath: '/project' });
// Load custom profile
const loader = new ProfileLoader();
const profile = await loader.load('my-profile');
// Create transformer registry
const registry = await TransformerRegistry.createDefault();
const transformers = registry.getAll();
Affected Components
- New file:
src/index.js - Main API exports
- New file:
types/index.d.ts - TypeScript definitions
package.json - Add main, exports, types fields
src/commands/copy.js - Refactor for programmatic use
- New file:
docs/api/programmatic-usage.md - API documentation
Implementation Approach
1. Create src/index.js
// Main API
export { scan } from './api/scan.js';
export { format } from './api/format.js';
export { copy } from './api/copy.js';
// Core classes (advanced)
export { default as Pipeline } from './pipeline/Pipeline.js';
export { default as ProfileLoader } from './profiles/ProfileLoader.js';
export { default as TransformerRegistry } from './transforms/TransformerRegistry.js';
// Utilities
export { parseProfile, validateProfile } from './profiles/ProfileLoader.js';
export { config, validateConfig } from './config/ConfigManager.js';
// Errors
export * from './utils/errors.js';
// Default export (convenience)
export { copy as default } from './api/copy.js';
2. Create src/api/scan.js
export async function* scan(basePath, options = {}) {
const pipeline = new Pipeline();
pipeline.through([
new FileDiscoveryStage({ basePath, ...options }),
new ProfileFilterStage({ ...options }),
...(options.transform ? [new TransformStage({ ...options })] : [])
]);
const result = await pipeline.process({ basePath, options });
for (const file of result.files) {
yield file;
}
}
3. Update package.json
{
"main": "./src/index.js",
"types": "./types/index.d.ts",
"exports": {
".": {
"import": "./src/index.js",
"types": "./types/index.d.ts"
},
"./utils": {
"import": "./src/api/utils.js",
"types": "./types/utils.d.ts"
}
},
"files": [
"src/",
"bin/",
"types/",
"profiles/",
"config/"
]
}
4. Create TypeScript Definitions
// types/index.d.ts
export interface FileResult {
path: string;
absolutePath: string;
size: number;
modified: Date;
content?: string;
isBinary: boolean;
encoding?: string;
}
export interface ScanOptions {
profile?: string | Profile;
filter?: string[];
exclude?: string[];
respectGitignore?: boolean;
modified?: boolean;
changed?: string;
maxDepth?: number;
transform?: boolean;
transformers?: string[];
}
export interface FormatOptions {
format?: 'xml' | 'json' | 'markdown' | 'tree' | 'ndjson' | 'sarif';
onlyTree?: boolean;
addLineNumbers?: boolean;
basePath?: string;
instructions?: string;
}
export interface CopyResult {
output: string;
files: FileResult[];
stats: {
totalFiles: number;
duration: number;
totalSize: number;
secretsGuard?: object;
};
}
export function scan(basePath: string, options?: ScanOptions): AsyncIterable<FileResult>;
export function format(files: FileResult[], options?: FormatOptions): Promise<string>;
export function copy(basePath: string, options?: ScanOptions & FormatOptions): Promise<CopyResult>;
// Classes
export class Pipeline { /* ... */ }
export class ProfileLoader { /* ... */ }
export class TransformerRegistry { /* ... */ }
Tasks
Acceptance Criteria
Additional Context
Semver commitment:
- Major (1.0.0): Breaking changes to public API
- Minor (0.x.0): New features, backward compatible
- Patch (0.0.x): Bug fixes, no API changes
Competitor examples:
fast-glob: import fg from 'fast-glob'; const files = await fg('**/*.js');
globby: import { globby } from 'globby'; const paths = await globby('**/*.js');
eslint: import { ESLint } from 'eslint'; const eslint = new ESLint();
Usage examples:
Vite plugin:
import { scan, format } from 'copytree';
export function copytreePlugin() {
return {
name: 'copytree',
async buildStart() {
const files = await scan('./src', { profile: 'typescript' });
const xml = await format(files, { format: 'xml' });
this.emitFile({ type: 'asset', fileName: 'structure.xml', source: xml });
}
};
}
GitHub Action:
import { copy } from 'copytree';
import * as core from '@actions/core';
const result = await copy(process.env.GITHUB_WORKSPACE, {
format: 'sarif',
output: 'results.sarif'
});
core.setOutput('file-count', result.stats.totalFiles);
Related issues:
Summary
Expose a clean, typed programmatic API for using CopyTree as a library in Node.js applications, enabling developers to integrate file discovery, transformation, and formatting into their build tools and scripts.
Problem Statement
CopyTree is currently CLI-only with no programmatic API:
No entry point:
Current workarounds:
Blocked use cases:
User Story
As a tool developer
I want a stable programmatic API for CopyTree
So that I can integrate file discovery and transformation into my Node.js applications
Current State
Package structure:
package.jsononly definesbin)copyCommand()fromsrc/commands/copy.js.d.tstype definitionsKey internal modules (undocumented):
src/commands/copy.js- Main copy logicsrc/pipeline/Pipeline.js- Pipeline orchestrationsrc/profiles/ProfileLoader.js- Profile loadingsrc/transforms/TransformerRegistry.js- Transformer systemsrc/utils/ignoreWalker.js- File discoveryExpected Behavior
Public API Surface
Primary API:
scan()Discover and filter files without formatting:
Secondary API:
format()Format a list of files into output:
Convenience API:
copy()Complete end-to-end operation (equivalent to CLI):
Advanced API: Classes
For complex use cases:
Affected Components
src/index.js- Main API exportstypes/index.d.ts- TypeScript definitionspackage.json- Addmain,exports,typesfieldssrc/commands/copy.js- Refactor for programmatic usedocs/api/programmatic-usage.md- API documentationImplementation Approach
1. Create
src/index.js2. Create
src/api/scan.js3. Update
package.json{ "main": "./src/index.js", "types": "./types/index.d.ts", "exports": { ".": { "import": "./src/index.js", "types": "./types/index.d.ts" }, "./utils": { "import": "./src/api/utils.js", "types": "./types/utils.d.ts" } }, "files": [ "src/", "bin/", "types/", "profiles/", "config/" ] }4. Create TypeScript Definitions
Tasks
src/index.jswith public API exportssrc/api/scan.jsimplementing async iterationsrc/api/format.jsimplementing formattingsrc/api/copy.js(wrapper aroundcopyCommand)src/commands/copy.jsto support both CLI and programmatic usepackage.jsonwithmain,exports,typesfieldstypes/index.d.tswith TypeScript definitionsscan(),format(),copy()docs/api/programmatic-usage.mdwith examplesexamples/directory:examples/scan-files.js- Basic scanningexamples/custom-pipeline.js- Advanced pipelineexamples/build-tool-integration.js- Vite/Webpack exampleAcceptance Criteria
import { scan } from 'copytree'works without errorsscan()returns AsyncIterable that can be used withfor awaitformat()accepts file list and returns formatted stringcopy()supports all CLI options programmaticallyAdditional Context
Semver commitment:
Competitor examples:
fast-glob:import fg from 'fast-glob'; const files = await fg('**/*.js');globby:import { globby } from 'globby'; const paths = await globby('**/*.js');eslint:import { ESLint } from 'eslint'; const eslint = new ESLint();Usage examples:
Vite plugin:
GitHub Action:
Related issues: