@atomist/automation-client
Version:
Atomist API for software low-level client
165 lines (129 loc) • 5.09 kB
text/typescript
import { Options } from "fast-glob";
import * as micromatch from "micromatch";
import { AbstractScriptedFlushable } from "../../internal/common/AbstractScriptedFlushable";
import { RepoRef } from "../../operations/common/RepoId";
import { logger } from "../../util/logger";
import {
File,
FileNonBlocking,
} from "../File";
import {
DefaultExcludes,
DefaultFiles,
} from "../fileGlobs";
import {
FileStream,
Project,
} from "../Project";
import { toPromise } from "../util/projectUtils";
/**
* Support for implementations of Project interface
*/
export abstract class AbstractProject extends AbstractScriptedFlushable<Project> implements Project {
/**
* Cached paths
*/
private cachedFiles: Promise<File[]> | undefined;
public readonly cache: Record<string, object> = {};
get name(): string {
return !!this.id ? this.id.repo : undefined;
}
protected constructor(public id: RepoRef,
public shouldCache: boolean = false) {
super();
}
/**
* Return the file, or reject with error
* @param {string} path
* @return {Promise<File>}
*/
public abstract findFile(path: string): Promise<File>;
public abstract getFile(path: string): Promise<File | undefined>;
public async hasFile(path: string): Promise<boolean> {
return !!(await this.getFile(path));
}
public abstract hasDirectory(path: string): Promise<boolean>;
public abstract findFileSync(path: string): File;
public streamFiles(...globPatterns: string[]): FileStream {
const globsToUse = globPatterns.length > 0 ? globPatterns.concat(DefaultExcludes) : DefaultFiles;
return this.streamFilesRaw(globsToUse, { dot: true });
}
public abstract streamFilesRaw(globPatterns: string[], opts: Options): FileStream;
/**
* Get files matching these patterns
* @param {string[]} globPatterns
* @return {Promise<File[]>}
*/
public async getFiles(globPatterns: string | string[] = []): Promise<File[]> {
const globPatternsToUse = globPatterns ?
(typeof globPatterns === "string" ? [globPatterns] : globPatterns) :
[];
// Deliberately checking truthiness of promise
if (!this.cachedFiles || !this.shouldCache) {
const globsToUse = [...DefaultFiles];
this.cachedFiles = this.getFilesInternal(globsToUse);
}
return globMatchesWithin(await this.cachedFiles, globPatternsToUse, { dot: true });
}
/**
* Subclasses can override this to optimize if they wish
* @return {Promise<File[]>}
*/
protected getFilesInternal(globPatterns: string[]): Promise<File[]> {
return toPromise(this.streamFiles());
}
public async totalFileCount(): Promise<number> {
const files = await this.getFiles();
return files.length;
}
public trackFile(f: FileNonBlocking): this {
logger.debug(`Project is tracking '${f.path}'`);
return this.recordAction(p => {
return f.flush().then(_ => p);
});
}
public moveFile(oldPath: string, newPath: string): Promise<this> {
return this.findFile(oldPath)
.then(f =>
f.setPath(newPath).then(() => this),
)
// Not an error if no such file
.catch(err => this);
}
public abstract makeExecutable(path: string): Promise<this>;
public recordAddFile(path: string, content: string): this {
return this.recordAction(p => p.addFile(path, content));
}
public recordDeleteFile(path: string): this {
return this.recordAction(p => p.deleteFile(path));
}
public abstract addFileSync(path: string, content: string): void;
public abstract deleteDirectorySync(path: string): void;
public abstract deleteDirectory(path: string): Promise<this>;
// TODO set permissions
public add(f: File): Promise<this> {
return f.getContent()
.then(content => this.addFile(f.path, content));
}
public abstract addFile(path: string, content: string): Promise<this>;
public abstract addDirectory(path: string): Promise<this>;
public abstract deleteFile(path: string): Promise<this>;
public abstract deleteFileSync(path: string): void;
public abstract makeExecutableSync(path: string): void;
public abstract directoryExistsSync(path: string): boolean;
public abstract fileExistsSync(path: string): boolean;
protected invalidateCache(): void {
this.cachedFiles = undefined;
}
}
/**
* Return the files that match these glob patterns, including negative globs
*/
export function globMatchesWithin(files: File[], globPatterns?: string[], opts?: micromatch.Options): File[] {
if (!globPatterns || globPatterns.length === 0) {
return files || [];
}
const paths = (files || []).map(f => f.path);
const matchingPaths = micromatch(paths, globPatterns, opts);
return files.filter(f => matchingPaths.includes(f.path));
}