mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-15 18:13:38 +08:00
This should clean up the code a bit. Note: at first I added the no-useless-cast rule, but after getting frustrated with it (as it has many false positive), I decided to remove the rule but some useless casts were removed so I let those in the PR.
153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {
|
|
Path,
|
|
basename,
|
|
dirname,
|
|
join,
|
|
normalize,
|
|
relative,
|
|
virtualFs,
|
|
} from '@angular-devkit/core';
|
|
import { NodeJsSyncHost } from '@angular-devkit/core/node';
|
|
import { Stats } from 'fs';
|
|
import { EMPTY, Observable, from, of } from 'rxjs';
|
|
import { concatMap, delay, finalize, map, mergeMap, retry, tap } from 'rxjs/operators';
|
|
|
|
|
|
export class TestProjectHost extends NodeJsSyncHost {
|
|
private _currentRoot: Path | null = null;
|
|
private _scopedSyncHost: virtualFs.SyncDelegateHost<Stats> | null = null;
|
|
|
|
constructor(protected _templateRoot: Path) {
|
|
super();
|
|
}
|
|
|
|
root(): Path {
|
|
if (this._currentRoot === null) {
|
|
throw new Error('TestProjectHost must be initialized before being used.');
|
|
}
|
|
|
|
return this._currentRoot;
|
|
}
|
|
|
|
scopedSync(): virtualFs.SyncDelegateHost<Stats> {
|
|
if (this._currentRoot === null || this._scopedSyncHost === null) {
|
|
throw new Error('TestProjectHost must be initialized before being used.');
|
|
}
|
|
|
|
return this._scopedSyncHost;
|
|
}
|
|
|
|
initialize(): Observable<void> {
|
|
const recursiveList = (path: Path): Observable<Path> => this.list(path).pipe(
|
|
// Emit each fragment individually.
|
|
concatMap(fragments => from(fragments)),
|
|
// Join the path with fragment.
|
|
map(fragment => join(path, fragment)),
|
|
// Emit directory content paths instead of the directory path.
|
|
mergeMap(path => this.isDirectory(path).pipe(
|
|
concatMap(isDir => isDir ? recursiveList(path) : of(path)),
|
|
)),
|
|
);
|
|
|
|
// Find a unique folder that we can write to to use as current root.
|
|
return this.findUniqueFolderPath().pipe(
|
|
// Save the path and create a scoped host for it.
|
|
tap(newFolderPath => {
|
|
this._currentRoot = newFolderPath;
|
|
this._scopedSyncHost = new virtualFs.SyncDelegateHost(
|
|
new virtualFs.ScopedHost(this, this.root()));
|
|
}),
|
|
// List all files in root.
|
|
concatMap(() => recursiveList(this._templateRoot)),
|
|
// Copy them over to the current root.
|
|
concatMap(from => {
|
|
const to = join(this.root(), relative(this._templateRoot, from));
|
|
|
|
return this.read(from).pipe(
|
|
concatMap(buffer => this.write(to, buffer)),
|
|
);
|
|
}),
|
|
map(() => { }),
|
|
);
|
|
}
|
|
|
|
restore(): Observable<void> {
|
|
if (this._currentRoot === null) {
|
|
return EMPTY;
|
|
}
|
|
|
|
// Delete the current root and clear the variables.
|
|
// Wait 50ms and retry up to 10 times, to give time for file locks to clear.
|
|
return this.exists(this.root()).pipe(
|
|
delay(50),
|
|
concatMap(exists => exists ? this.delete(this.root()) : EMPTY),
|
|
retry(10),
|
|
finalize(() => {
|
|
this._currentRoot = null;
|
|
this._scopedSyncHost = null;
|
|
}),
|
|
);
|
|
}
|
|
|
|
writeMultipleFiles(files: { [path: string]: string | ArrayBufferLike | Buffer }): void {
|
|
Object.keys(files).forEach(fileName => {
|
|
let content = files[fileName];
|
|
if (typeof content == 'string') {
|
|
content = virtualFs.stringToFileBuffer(content);
|
|
} else if (content instanceof Buffer) {
|
|
content = content.buffer.slice(
|
|
content.byteOffset,
|
|
content.byteOffset + content.byteLength,
|
|
);
|
|
}
|
|
|
|
this.scopedSync().write(
|
|
normalize(fileName),
|
|
content,
|
|
);
|
|
});
|
|
}
|
|
|
|
replaceInFile(path: string, match: RegExp | string, replacement: string) {
|
|
const content = virtualFs.fileBufferToString(this.scopedSync().read(normalize(path)));
|
|
this.scopedSync().write(normalize(path),
|
|
virtualFs.stringToFileBuffer(content.replace(match, replacement)));
|
|
}
|
|
|
|
appendToFile(path: string, str: string) {
|
|
const content = virtualFs.fileBufferToString(this.scopedSync().read(normalize(path)));
|
|
this.scopedSync().write(normalize(path),
|
|
virtualFs.stringToFileBuffer(content.concat(str)));
|
|
}
|
|
|
|
fileMatchExists(dir: string, regex: RegExp) {
|
|
const [fileName] = this.scopedSync().list(normalize(dir)).filter(name => name.match(regex));
|
|
|
|
return fileName || undefined;
|
|
}
|
|
|
|
copyFile(from: string, to: string) {
|
|
const content = this.scopedSync().read(normalize(from));
|
|
this.scopedSync().write(normalize(to), content);
|
|
}
|
|
|
|
private findUniqueFolderPath(): Observable<Path> {
|
|
// 11 character alphanumeric string.
|
|
const randomString = Math.random().toString(36).slice(2);
|
|
const newFolderName = `test-project-host-${basename(this._templateRoot)}-${randomString}`;
|
|
const newFolderPath = join(dirname(this._templateRoot), newFolderName);
|
|
|
|
return this.exists(newFolderPath).pipe(
|
|
concatMap(exists => exists ? this.findUniqueFolderPath() : of(newFolderPath)),
|
|
);
|
|
}
|
|
}
|