quasqui
Version:
The meta-framework suite designed from scratch for frontend-focused modern web development.
156 lines (140 loc) • 3.7 kB
text/typescript
import path from 'path';
import axios from 'axios';
import fs from 'fs-extra';
import tar from 'tar-fs';
import gunzip from 'gunzip-maybe';
import execa from 'execa';
const NPM_REGISTRY_URL = 'http://registry.npmjs.org';
// https://github.com/axios/axios/issues/2654
axios.defaults.adapter = require('axios/lib/adapters/http');
const npmAxios = axios.create({
baseURL: NPM_REGISTRY_URL,
responseType: 'json',
timeout: 10000,
});
export const preparePackage = async (
workDir: string,
name: string,
version: string,
) => {
const tarballURL = await getTarballURL(name, version);
try {
await downloadTarball(tarballURL, workDir);
} catch (e: any) {
// eslint-disable-next-line no-console
console.log('download tarball failed:', tarballURL, e.message);
throw e;
}
const packageDir = path.resolve(workDir, 'package');
const targetDir = path.resolve(workDir, `node_modules/${name}`);
await fs.move(packageDir, targetDir, { overwrite: true });
return workDir;
};
export const preparePackages = async (
workDir: string,
packages: Record<string, string>,
) => {
for (const [k, v] of Object.entries(packages)) {
await preparePackage(workDir, k, v);
}
return workDir;
};
export const getPackageMeta = async (name: string) => {
try {
const res = await npmAxios.get<PackageMetaData>(`/${name}`);
return res.data;
} catch (e) {
// eslint-disable-next-line no-console
console.log('fetch package meta failed', name);
throw e;
}
};
export const getTarballURL = async (
name: string,
version: string,
): Promise<string> => {
const metadata = await getPackageMeta(name);
const versionInfo = metadata.versions[version];
const ret = versionInfo?.dist.tarball;
if (!ret) {
throw new Error(`${name}@${version} does not available`);
}
return ret;
};
export const downloadTarball = async (tarball: string, destDir: string) => {
const tarball$ = (
await npmAxios.get(tarball, {
responseType: 'stream',
timeout: 60000,
})
).data;
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, {
recursive: true,
});
}
await new Promise<void>((resolve, reject) => {
const extract = tar.extract(destDir);
extract.on('finish', () => resolve());
extract.on('error', (e: unknown) => reject(e));
tarball$.pipe(gunzip()).pipe(extract);
});
};
export interface PackageMetaData {
name: string;
description?: string;
'dist-tags': {
latest: string;
next?: string;
[key: string]: string | undefined;
};
versions: {
[version: string]: {
name: string;
description?: string;
keywords: string[];
version: string;
main: string;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
dist: {
shasum: string;
size: number;
/**
* @example /react/-/react-17.0.0-rc.0.tgz
*/
key: string;
/**
* @example https://registry.npmjs.org/react/-/react-17.0.0-rc.0.tgz
*/
tarball: string;
};
};
};
}
export const installPackage = async (
workDir: string,
name: string,
version: string,
) => {
try {
await execa(
'npm',
['install', `${name}@${version}`, `--registry=${NPM_REGISTRY_URL}`],
{
cwd: workDir,
},
);
} catch (e) {
throw new Error(`npm install ${name}@${version} failed`);
}
};
export const installPackages = async (
workDir: string,
packages: Record<string, string>,
) => {
for (const [k, v] of Object.entries(packages)) {
await installPackage(workDir, k, v);
}
};