@teambit/isolator
Version:
140 lines (124 loc) • 4.37 kB
text/typescript
import type { NodeFS } from '@teambit/any-fs';
import type { Exec } from '@teambit/capsule';
import { Capsule as CapsuleTemplate, Console, State } from '@teambit/capsule';
import type { Component } from '@teambit/component';
import filenamify from 'filenamify';
import { realpathSync } from 'fs';
import { globSync } from 'glob';
import path from 'path';
import { v4 } from 'uuid';
import type { BitExecOption } from './container';
import FsContainer from './container';
import ContainerExec from './container-exec';
export default class Capsule extends CapsuleTemplate<Exec, NodeFS> {
private _wrkDir: string;
constructor(
/**
* container implementation the capsule is being executed within.
*/
protected container: FsContainer,
/**
* the capsule's file system.
*/
readonly fs: NodeFS,
/**
* console for controlling process streams as stdout, stdin and stderr.
*/
readonly console: Console = new Console(),
/**
* capsule's state.
*/
readonly state: State,
readonly component: Component
) {
super(container, fs, console, state);
this._wrkDir = container.wrkDir;
}
/**
* @deprecated please use `this.path`
*/
get wrkDir(): string {
return this.path;
}
get path(): string {
return realpathSync(this._wrkDir);
}
start(): Promise<any> {
return this.container.start();
}
async execNode(executable: string, args: any, exec: ContainerExec) {
return this.typedExec(
{
command: ['node', executable, ...(args.args || [])],
cwd: '',
},
exec
);
}
async typedExec(opts: BitExecOption, exec = new ContainerExec()) {
return this.container.exec(opts, exec);
}
outputFile(file: string, data: any, options?: any): Promise<any> {
return this.container.outputFile(file, data, options);
}
removePath(dir: string): Promise<any> {
return this.container.removePath(dir);
}
symlink(src: string, dest: string): Promise<any> {
return this.container.symlink(src, dest);
}
// TODO: refactor this crap and simplify capsule API
async execute(cmd: string, options?: Record<string, any> | null | undefined) {
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
const execResults = await this.exec({ command: cmd.split(' '), options });
let stdout = '';
let stderr = '';
return new Promise((resolve, reject) => {
execResults.stdout.on('data', (data: string) => {
stdout += data;
});
execResults.stdout.on('error', (error: string) => {
return reject(error);
});
execResults.on('close', () => {
return resolve({ stdout, stderr });
});
execResults.stderr.on('error', (error: string) => {
return reject(error);
});
execResults.stderr.on('data', (data: string) => {
stderr += data;
});
});
}
/**
* @todo: fix.
* it skips the capsule fs because for some reason `capsule.fs.promises.readdir` doesn't work
* the same as `capsule.fs.readdir` and it doesn't have the capsule dir as pwd.
*
* returns the paths inside the capsule
*/
getAllFilesPaths(dir = '.', options: { ignore?: string[] } = {}) {
const files = globSync('**', { cwd: path.join(this.path, dir), nodir: true, ...options });
return files.map((file) => path.join(dir, file));
}
static getCapsuleDirName(component: Component, config: { alwaysNew?: boolean; name?: string } = {}) {
return config.name || filenamify(component.id.toString(), { replacement: '_' });
}
static getCapsuleRootDir(component: Component, baseDir: string, config: { alwaysNew?: boolean; name?: string } = {}) {
return path.join(baseDir, Capsule.getCapsuleDirName(component, config));
}
static async createFromComponent(
component: Component,
baseDir: string,
config: { alwaysNew?: boolean } = {}
): Promise<Capsule> {
// TODO: make this a static method and combine with ComponentCapsule
const capsuleDirName = Capsule.getCapsuleDirName(component, config);
const wrkDir = path.join(baseDir, config.alwaysNew ? `${capsuleDirName}_${v4()}` : capsuleDirName);
const container = new FsContainer(wrkDir);
const capsule = new Capsule(container, container.fs, new Console(), new State(), component);
await capsule.start();
return capsule;
}
}