packages/jsii-diff/lib/util.ts (98 lines of code) (raw):
import * as childProcess from 'child_process';
import * as fs from 'fs-extra';
import * as log4js from 'log4js';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
const LOG = log4js.getLogger('jsii-diff');
const exec = util.promisify(childProcess.exec);
export async function inTempDir<T>(block: () => T | Promise<T>): Promise<T> {
const origDir = process.cwd();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'jsii'));
process.chdir(tmpDir);
try {
return await block();
} finally {
process.chdir(origDir);
await fs.remove(tmpDir);
}
}
export type DownloadFailure = 'no_such_package';
export type NpmDownloadResult<T> =
| { success: true; result: T }
| { success: false; reason: DownloadFailure };
export function showDownloadFailure(f: DownloadFailure) {
switch (f) {
case 'no_such_package':
return 'NPM package does not exist';
default:
return undefined;
}
}
export async function downloadNpmPackage<T>(
pkg: string,
block: (dir: string) => Promise<T>,
): Promise<NpmDownloadResult<T>> {
return inTempDir(async () => {
LOG.info(`Fetching NPM package ${pkg}`);
try {
// Need to install package and dependencies in order for jsii-reflect
// to not bork when it can find the dependencies.
await exec(`npm install --silent --prefix . ${pkg}`);
} catch (e: any) {
// If this fails, might be because the package doesn't exist
if (!isSubprocesFailedError(e)) {
throw e;
}
if (await npmPackageExists(pkg)) {
throw new Error(`NPM fetch failed: ${e}. Please try again.`);
}
LOG.warn(`NPM package ${pkg} does not exist.`);
return {
success: false,
reason: 'no_such_package',
} as NpmDownloadResult<T>;
}
const pkgDir = trimVersionString(pkg);
return {
success: true,
result: await block(path.join(process.cwd(), 'node_modules', pkgDir)),
} as NpmDownloadResult<T>;
});
}
function isSubprocesFailedError(e: any) {
return e.code !== undefined && e.cmd !== undefined;
}
async function npmPackageExists(pkg: string): Promise<boolean> {
try {
LOG.info(`Checking existence of ${pkg}`);
await exec(`npm show --silent ${pkg}`);
return true;
} catch (e) {
if (!isSubprocesFailedError(e)) {
throw e;
}
return false;
}
}
/**
* Trim an optional version string from an NPM package name
*/
function trimVersionString(pkg: string) {
// The arbitrary char before the @ prevents matching a @ at the start of the
// string.
return pkg.replace(/(.)@.*$/, '$1');
}
export function flatMap<T, U>(xs: T[], fn: (x: T) => U[]): U[] {
const ret = new Array<U>();
for (const x of xs) {
ret.push(...fn(x));
}
return ret;
}
/**
* Don't recurse infinitely by guarding a block with `do()`.
*/
export class RecursionBreaker<A> {
private readonly elements = new Set<A>();
public do(key: A, block: () => void) {
if (this.elements.has(key)) {
return;
}
this.elements.add(key);
try {
block();
} finally {
this.elements.delete(key);
}
}
}