flow-typed
Version:
A repository of high quality flow type definitions
866 lines (799 loc) • 27.1 kB
Flow
// @flow
import colors from 'colors/safe';
import semver from 'semver';
import typeof Yargs from 'yargs';
import {
getCacheRepoDir,
_setCustomCacheDir as setCustomCacheDir,
CACHE_REPO_EXPIRY,
} from '../lib/cacheRepoUtils';
import {signCodeStream, verifySignedCode} from '../lib/codeSign';
import {getEnvDefs, findEnvDef, getEnvDefVersionHash} from '../lib/envDefs';
import {copyFile, mkdirp} from '../lib/fileUtils';
import {findFlowRoot} from '../lib/flowProjectUtils';
import {
toSemverString as flowVersionToSemver,
determineFlowSpecificVersion,
type FlowVersion,
} from '../lib/flowVersion';
import {getFtConfig, type FtConfig} from '../lib/ftConfig';
import {fs, path, child_process} from '../lib/node';
import {
findNpmLibDef,
getCacheNpmLibDefs,
getInstalledNpmLibDefs,
getNpmLibDefVersionHash,
getScopedPackageName,
type NpmLibDef,
} from '../lib/npm/npmLibDefs';
import {
findWorkspacesPackages,
getPackageJsonData,
getPackageJsonDependencies,
loadPnpResolver,
mergePackageJsonDependencies,
} from '../lib/npm/npmProjectUtils';
import {getRangeLowerBound} from '../lib/semver';
import {createStub, pkgHasFlowFiles} from '../lib/stubUtils';
import {listItem, sectionHeader} from '../lib/logger';
export const name = 'install [explicitLibDefs...]';
export const description = 'Installs libdefs into the ./flow-typed directory';
export type Args = {
flowVersion?: mixed, // string
overwrite: mixed, // boolean
skip: mixed, // boolean
skipCache?: mixed, // boolean
skipFlowRestart?: mixed, // boolean
verbose: mixed, // boolean
libdefDir?: mixed, // string
cacheDir?: mixed, // string
packageDir?: mixed, // string
ignoreDeps?: mixed, // Array<string>
rootDir?: mixed, // string,
useCacheUntil?: mixed, // number (milliseconds)
explicitLibDefs: mixed, // Array<string>
...
};
export function setup(yargs: Yargs): Yargs {
return yargs
.usage(`$0 ${name} - ${description}`)
.positional('explicitLibDefs', {
describe: 'Explicitly specify packages to install',
default: [],
})
.options({
flowVersion: {
alias: 'f',
describe:
'The Flow version that fetched libdefs must be compatible with',
type: 'string',
},
verbose: {
describe: 'Print additional, verbose info while installing libdefs',
type: 'boolean',
demandOption: false,
},
skip: {
alias: 's',
describe: 'Do not generate stubs for missing libdefs',
type: 'boolean',
demandOption: false,
},
skipCache: {
describe: 'Do not update cache prior to installing libdefs',
type: 'boolean',
demandOption: false,
},
skipFlowRestart: {
describe: 'Do not restart flow after installing libdefs',
type: 'boolean',
demandOption: false,
},
libdefDir: {
alias: 'l',
describe: 'Use a custom directory to install libdefs',
type: 'string',
demandOption: false,
},
cacheDir: {
alias: 'c',
describe:
'Directory (absolute or relative path, ~ is not supported) to store cache of libdefs',
type: 'string',
demandOption: false,
},
packageDir: {
alias: 'p',
describe:
'The relative path of package.json where flow-bin is installed',
type: 'string',
},
overwrite: {
alias: 'o',
describe: 'Overwrite an existing libdef',
type: 'boolean',
demandOption: false,
},
ignoreDeps: {
alias: 'i',
describe: 'Dependency categories to ignore when installing definitions',
choices: ['dev', 'bundled', 'peer'],
type: 'array',
},
rootDir: {
alias: 'r',
describe: 'Directory of .flowconfig relative to node_modules',
type: 'string',
},
useCacheUntil: {
alias: 'u',
describe: 'Use cache until specified time in milliseconds',
type: 'number',
},
});
}
export async function run(args: Args): Promise<number> {
const cwd =
typeof args.rootDir === 'string'
? path.resolve(args.rootDir)
: process.cwd();
const packageDir =
typeof args.packageDir === 'string' ? path.resolve(args.packageDir) : cwd;
const flowVersion = await determineFlowSpecificVersion(
packageDir,
args.flowVersion,
);
const libdefDir =
typeof args.libdefDir === 'string' ? args.libdefDir : 'flow-typed';
if (args.ignoreDeps !== undefined && !Array.isArray(args.ignoreDeps)) {
throw new Error('ignoreDeps is not array');
}
const ignoreDeps = (args.ignoreDeps || []).map(dep => {
if (typeof dep !== 'string') {
throw new Error('ignoreDeps should be array of strings');
}
return dep;
});
if (
args.explicitLibDefs !== undefined &&
!Array.isArray(args.explicitLibDefs)
) {
throw new Error('explicitLibDefs is not array');
}
const explicitLibDefs = (args.explicitLibDefs || []).map(dep => {
if (typeof dep !== 'string') {
throw new Error('explicitLibDefs should be array of strings');
}
return dep;
});
const ftConfig = getFtConfig(cwd);
if (args.cacheDir) {
const cacheDir = path.resolve(String(args.cacheDir));
console.log('• Setting cache dir', cacheDir);
setCustomCacheDir(cacheDir);
}
const useCacheUntil = Number(args.useCacheUntil) || CACHE_REPO_EXPIRY;
const {status: npmLibDefResult, dependencyEnvs} = await installNpmLibDefs({
cwd,
flowVersion,
explicitLibDefs,
libdefDir: libdefDir,
verbose: Boolean(args.verbose),
overwrite: Boolean(args.overwrite),
skip: Boolean(args.skip),
skipCache: Boolean(args.skipCache),
ignoreDeps: ignoreDeps,
useCacheUntil: Number(args.useCacheUntil) || CACHE_REPO_EXPIRY,
ftConfig,
});
if (npmLibDefResult !== 0) {
return npmLibDefResult;
}
// Must be after `installNpmLibDefs` to ensure cache is updated first
if (ftConfig?.env || dependencyEnvs.length > 0) {
const envLibDefResult = await installEnvLibDefs(
[...new Set([...(ftConfig?.env ?? []), ...dependencyEnvs])],
flowVersion,
cwd,
libdefDir,
useCacheUntil,
Boolean(args.overwrite),
);
if (envLibDefResult !== 0) {
return envLibDefResult;
}
}
// Once complete restart flow to solve flow issues when scanning large diffs
if (!args.skipFlowRestart) {
try {
await child_process.execP('npx flow stop');
await child_process.execP('npx flow');
} catch (e) {
console.log(colors.red('!! Flow restarted with some errors'));
}
}
return 0;
}
async function installEnvLibDefs(
env: Array<string>,
flowVersion: FlowVersion,
flowProjectRoot: string,
libdefDir: string,
useCacheUntil: number,
overwrite: boolean,
): Promise<number> {
if (env) {
console.log(
colors.green(
'• `env` key found in `flow-typed.config.json`, attempting to install env definitions...\n',
),
);
if (!Array.isArray(env)) {
console.log(
colors.yellow(
'Warning: `env` in `flow-typed.config.json` must be of type Array<string> - skipping',
),
);
return 0;
}
// Get a list of all environment defs
const envDefs = await getEnvDefs();
const flowTypedDirPath = path.join(
flowProjectRoot,
libdefDir,
'environments',
);
await mkdirp(flowTypedDirPath);
// Go through each env and try to install a libdef of the same name
// for the given flow version,
// if none is found throw a warning and continue. We shouldn't block the user.
await Promise.all(
env.map(async en => {
if (typeof en === 'string') {
const def = await findEnvDef(en, flowVersion, useCacheUntil, envDefs);
if (def) {
const fileName = `${en}.js`;
const defLocalPath = path.join(flowTypedDirPath, fileName);
const envAlreadyInstalled = await fs.exists(defLocalPath);
if (envAlreadyInstalled) {
const localFile = fs.readFileSync(defLocalPath, 'utf-8');
if (!verifySignedCode(localFile) && !overwrite) {
listItem(
en,
colors.red(
`${en} already exists and appears to have been manually written or changed!`,
),
colors.yellow(
`Consider contributing your changes back to flow-typed repository :)`,
),
colors.yellow(
`${en} already exists and appears to have been manually written or changed!`,
),
colors.yellow(
`Read more at https://github.com/flow-typed/flow-typed/blob/main/CONTRIBUTING.md`,
),
colors.yellow(
`Use --overwrite to overwrite the existing env defs.`,
),
);
return;
}
fs.unlink(defLocalPath);
}
const repoVersion = await getEnvDefVersionHash(
getCacheRepoDir(),
def,
);
const codeSignPreprocessor = signCodeStream(repoVersion);
await copyFile(def.path, defLocalPath, codeSignPreprocessor);
listItem(
en,
colors.green(
`.${path.sep}${path.relative(flowProjectRoot, defLocalPath)}`,
),
);
} else {
listItem(
en,
colors.yellow(
`Was unable to install ${en}. The env might not exist or there is not a version compatible with your version of flow`,
),
);
}
}
}),
);
}
return 0;
}
const FLOW_BUILT_IN_NPM_LIBS = ['react'];
type installNpmLibDefsArgs = {|
cwd: string,
flowVersion: FlowVersion,
explicitLibDefs: Array<string>,
libdefDir: string,
verbose: boolean,
overwrite: boolean,
skip: boolean,
skipCache: boolean,
ignoreDeps: Array<string>,
useCacheUntil: number,
ftConfig?: FtConfig,
|};
async function installNpmLibDefs({
cwd,
flowVersion,
explicitLibDefs,
libdefDir,
verbose,
overwrite,
skip,
skipCache,
ignoreDeps,
useCacheUntil,
ftConfig = {},
}: installNpmLibDefsArgs): Promise<{|
status: number,
dependencyEnvs: Array<string>,
|}> {
const flowProjectRoot = await findFlowRoot(cwd);
if (flowProjectRoot === null) {
console.error(
'Error: Unable to find a flow project in the current dir or any of ' +
"it's parent dirs!\n" +
'Please run this command from within a Flow project.',
);
return {
status: 1,
dependencyEnvs: [],
};
}
const libdefsToSearchFor: Map<string, string> = new Map();
let ignoreDefs: Array<string>;
if (Array.isArray(ftConfig.ignore)) {
ignoreDefs = ftConfig.ignore;
} else {
try {
ignoreDefs = fs
.readFileSync(path.join(cwd, libdefDir, '.ignore'), 'utf-8')
.replace(/"/g, '')
.split('\n');
} catch (err) {
// If the error is unrelated to file not existing we should continue throwing
if (err.code !== 'ENOENT') {
throw err;
}
ignoreDefs = [];
}
}
let workspacesPkgJsonData;
// If a specific pkg/version was specified, only add those packages.
// Otherwise, extract dependencies from the package.json
if (explicitLibDefs.length > 0) {
for (var i = 0; i < explicitLibDefs.length; i++) {
const term = explicitLibDefs[i];
const termMatches = term.match(/(@[^@\/]+\/)?([^@]+)@(.+)/);
if (termMatches == null) {
const pkgJsonData = await getPackageJsonData(cwd);
workspacesPkgJsonData = await findWorkspacesPackages(
cwd,
pkgJsonData,
ftConfig,
);
const pkgJsonDeps = workspacesPkgJsonData.reduce((acc, pckData) => {
return mergePackageJsonDependencies(
acc,
getPackageJsonDependencies(pckData, [], []),
);
}, getPackageJsonDependencies(pkgJsonData, [], []));
const packageVersion = pkgJsonDeps[term];
if (packageVersion) {
libdefsToSearchFor.set(term, packageVersion);
} else {
console.error(
'ERROR: Package not found from package.json.\n' +
'Please specify version for the package in the format of `foo@1.2.3`',
);
return {
status: 1,
dependencyEnvs: [],
};
}
} else {
const [_, npmScope, pkgName, pkgVerStr] = termMatches;
const scopedPkgName = npmScope != null ? npmScope + pkgName : pkgName;
libdefsToSearchFor.set(scopedPkgName, pkgVerStr);
}
}
console.log(`• Searching for ${libdefsToSearchFor.size} libdefs...`);
} else {
const pkgJsonData = await getPackageJsonData(cwd);
workspacesPkgJsonData = await findWorkspacesPackages(
cwd,
pkgJsonData,
ftConfig,
);
const pkgJsonDeps = workspacesPkgJsonData.reduce((acc, pckData) => {
return mergePackageJsonDependencies(
acc,
getPackageJsonDependencies(pckData, ignoreDeps, ignoreDefs),
);
}, getPackageJsonDependencies(pkgJsonData, ignoreDeps, ignoreDefs));
for (const pkgName in pkgJsonDeps) {
libdefsToSearchFor.set(pkgName, pkgJsonDeps[pkgName]);
}
if (libdefsToSearchFor.size === 0) {
console.error(
"No dependencies were found in this project's package.json!",
);
return {
status: 0,
dependencyEnvs: [],
};
}
if (verbose) {
libdefsToSearchFor.forEach((ver, name) => {
console.log(`• Found package.json dependency: ${name}@${ver}`);
});
} else {
console.log(
`• Found ${libdefsToSearchFor.size} dependencies in package.json to install libdefs for. Searching...`,
);
}
}
const libDefsToSearchForEntries = [...libdefsToSearchFor.entries()];
// Search for the requested libdefs
const libDefsToInstall: Map<string, NpmLibDef> = new Map();
const outdatedLibDefsToInstall: [
NpmLibDef,
{name: string, ver: string},
][] = [];
const unavailableLibDefs = [];
const defDepsToInstall: {
[deps: string]: Array<string>,
} = {};
// This updates the cache for all definition types, npm/env/etc
const libDefs = await getCacheNpmLibDefs(useCacheUntil, skipCache);
const getLibDefsToInstall = async (entries: Array<[string, string]>) => {
await Promise.all(
entries.map(async ([name, ver]) => {
delete defDepsToInstall[name];
// To comment in json files a work around is to give a key value pair
// of `"//": "comment"` we should exclude these so the install doesn't crash
// Ref: https://stackoverflow.com/a/14221781/430128
if (name === '//') {
return;
}
if (FLOW_BUILT_IN_NPM_LIBS.indexOf(name) !== -1) {
return;
}
const libDef = await findNpmLibDef(
name,
ver,
flowVersion,
useCacheUntil,
true,
libDefs,
);
if (libDef === null) {
unavailableLibDefs.push({name, ver});
} else {
libDefsToInstall.set(name, libDef);
const libDefLower = getRangeLowerBound(libDef.version);
const depLower = getRangeLowerBound(ver);
if (semver.lt(libDefLower, depLower)) {
outdatedLibDefsToInstall.push([libDef, {name, ver}]);
}
// If this libdef has dependencies let's first add it to a giant list
if (libDef.depVersions) {
Object.keys(libDef.depVersions).forEach(dep => {
if (libDef.depVersions) {
// This may result in overriding other libDef dependencies
// but we don't care because either way a project cannot have the
// same module declared twice.
defDepsToInstall[dep] = libDef.depVersions[dep];
}
});
}
}
}),
);
};
await getLibDefsToInstall(libDefsToSearchForEntries);
const pnpResolver = await loadPnpResolver(await getPackageJsonData(cwd));
const dependencyEnvs: Array<string> = [];
// If a package that's missing a flow-typed libdef has any .flow files,
// we'll skip generating a stub for it.
const untypedMissingLibDefs: Array<[string, string]> = [];
const typedMissingLibDefs: Array<[string, string, string]> = [];
await Promise.all(
unavailableLibDefs.map(async ({name: pkgName, ver: pkgVer}) => {
const {isFlowTyped, pkgPath} = await pkgHasFlowFiles(
cwd,
pkgName,
pnpResolver,
workspacesPkgJsonData,
);
if (isFlowTyped && pkgPath) {
typedMissingLibDefs.push([pkgName, pkgVer, pkgPath]);
try {
// If the flow typed dependency has a flow-typed config
// check if they have env's defined and if so keep them in
// a list so we can later install them along with project
// defined envs.
const pkgFlowTypedConfig: FtConfig = await fs.readJson(
path.join(pkgPath, 'flow-typed.config.json'),
);
if (pkgFlowTypedConfig.env) {
dependencyEnvs.push(...pkgFlowTypedConfig.env);
}
} catch (e) {
// Ignore if we cannot read flow-typed config
}
} else {
untypedMissingLibDefs.push([pkgName, pkgVer]);
}
}),
);
// If there are any typed packages we should try install any missing
// lib defs for that package.
// Scanning through all typed dependencies then checking their immediate deps
// but do not overwrite the lib def that the project itself wants
if (typedMissingLibDefs.length > 0) {
const typedDepsLibDefsToSearchFor: Array<[string, string]> = [];
await Promise.all(
typedMissingLibDefs.map(async typedLibDef => {
const pkgJsonData = await getPackageJsonData(
`${typedLibDef[2]}/package.json`,
);
const pkgJsonDeps = getPackageJsonDependencies(
pkgJsonData,
// Only get their production dependencies
// as the rest aren't installed anyways
['dev', 'peer', ...ignoreDeps],
ignoreDefs,
);
for (const pkgName in pkgJsonDeps) {
if (!libDefsToInstall.has(pkgName)) {
typedDepsLibDefsToSearchFor.push([pkgName, pkgJsonDeps[pkgName]]);
}
}
}),
);
await getLibDefsToInstall(typedDepsLibDefsToSearchFor);
}
// Now that we've captured all package.json libdefs and typed package libdefs
// We can compare libDefsToInstall with defDepsToInstall and override any that
// are already needed but don't match the supported dependency versions.
if (Object.keys(defDepsToInstall).length > 0) {
sectionHeader('Running definition dependency check');
while (Object.keys(defDepsToInstall).length > 0) {
await getLibDefsToInstall(
Object.keys(defDepsToInstall)
.map((dep): [string, string] => {
const libDef = libDefsToInstall.get(dep);
const defVersions = defDepsToInstall[dep];
if (libDef) {
if (!defVersions.includes(libDef.version)) {
// If no supported version warn it'll be overridden and continue
listItem(
colors.yellow(
`One of your definitions has a dependency to ${dep} @ version(s) ${defVersions.join(
', ',
)}`,
),
`You have version ${colors.yellow(libDef.version)} installed`,
`We're overriding to a supported version to fix flow-typed ${colors.red(
'but you may experience other type errors',
)}`,
);
} else {
// If a supported version already installed return dummy tuple
// to get filtered out
delete defDepsToInstall[dep];
return ['', ''];
}
}
// This only hits if no supported version installed,
// we will find the last version in dependency to install
return [dep, defVersions[defVersions.length - 1]];
})
.filter(o => !!o[0]),
);
}
sectionHeader('Check complete');
}
// Scan libdefs that are already installed
const libDefsToUninstall = new Map<string, string>();
const alreadyInstalledLibDefs = await getInstalledNpmLibDefs(
path.join(flowProjectRoot),
libdefDir,
);
[...alreadyInstalledLibDefs.entries()].forEach(([filePath, npmLibDef]) => {
const fullFilePath = path.join(flowProjectRoot, filePath);
switch (npmLibDef.kind) {
case 'LibDef':
// If a libdef is already installed for some dependency, we need to
// uninstall it before installing the new (potentially updated) ver
const libDef = npmLibDef.libDef;
const scopedPkgName = getScopedPackageName(libDef);
if (libDefsToInstall.has(scopedPkgName)) {
libDefsToUninstall.set(scopedPkgName, fullFilePath);
}
break;
case 'Stub':
break;
default:
(npmLibDef: empty);
}
});
if (libDefsToInstall.size > 0) {
sectionHeader(`Installing ${libDefsToInstall.size} libDefs...`);
const flowTypedDirPath = path.join(flowProjectRoot, libdefDir, 'npm');
await mkdirp(flowTypedDirPath);
const results = await Promise.all(
[...libDefsToInstall.values()].map(async (libDef: NpmLibDef) => {
const toUninstall = libDefsToUninstall.get(
getScopedPackageName(libDef),
);
if (toUninstall != null) {
await fs.unlink(toUninstall);
}
return installNpmLibDef(libDef, flowTypedDirPath, overwrite);
}),
);
if (results.some(res => !res)) {
return {
status: 1,
dependencyEnvs: [],
};
}
}
if (
(verbose || unavailableLibDefs.length === 0) &&
outdatedLibDefsToInstall.length > 0
) {
console.log(
'• The following installed libdefs are compatible with your ' +
'dependencies, but may not include all minor and patch changes for ' +
'your specific dependency version:\n',
);
outdatedLibDefsToInstall.forEach(
([libDef, {name: pkgName, ver: pkgVersion}]) => {
console.log(
' • libdef: %s (satisfies %s)',
colors.yellow(`${libDef.name}_${libDef.version}`),
colors.bold(`${pkgName}@${pkgVersion}`),
);
const libDefPlural =
outdatedLibDefsToInstall.length > 1
? ['versioned updates', 'these packages']
: ['a versioned update', 'this package'];
console.log(
`\n` +
` Consider submitting ${libDefPlural[0]} for ${libDefPlural[1]} to \n` +
` https://github.com/flowtype/flow-typed/\n`,
);
},
);
}
if (
unavailableLibDefs.length > 0 &&
unavailableLibDefs.length === explicitLibDefs.length
) {
// If the user specified an explicit library to be installed, don't generate
// a stub if no libdef exists -- just inform them that one doesn't exist
console.log(
colors.red(
`!! No flow@${flowVersionToSemver(flowVersion)}-compatible libdefs ` +
`found in flow-typed for the explicitly requested libdefs. !!`,
) +
'\n' +
'\n' +
'Consider using `%s` to generate an empty libdef that you can fill in.',
colors.bold(`flow-typed create-stub ${explicitLibDefs.join(' ')}`),
);
return {
status: 1,
dependencyEnvs: [],
};
} else {
if (untypedMissingLibDefs.length > 0 && !skip) {
console.log('• Generating stubs for untyped dependencies...');
await Promise.all(
untypedMissingLibDefs.map(async ([pkgName, pkgVerStr]) => {
await createStub(
flowProjectRoot,
pkgName,
pkgVerStr,
overwrite,
pnpResolver,
/* typescript */ false,
libdefDir,
);
}),
);
console.log(
colors.red(
`\n!! No flow@${flowVersionToSemver(
flowVersion,
)}-compatible libdefs ` +
`found in flow-typed for the above untyped dependencies !!`,
),
);
const plural =
unavailableLibDefs.length > 1
? ['libdefs', 'these packages', 'them']
: ['a libdef', 'this package', 'it'];
console.log(
`\n` +
`We've generated ${'`'}any${'`'}-typed stubs for ${plural[1]}, but ` +
`consider submitting \n` +
`${plural[0]} for ${plural[2]} to ` +
`${colors.bold('https://github.com/flowtype/flow-typed/')}\n`,
);
}
}
return {
status: 0,
dependencyEnvs,
};
}
async function installNpmLibDef(
npmLibDef: NpmLibDef,
npmDir: string,
overwrite: boolean,
): Promise<boolean> {
const scopedDir =
npmLibDef.scope === null ? npmDir : path.join(npmDir, npmLibDef.scope);
mkdirp(scopedDir);
const fileName = `${npmLibDef.name}_${npmLibDef.version}.js`;
const filePath = path.join(scopedDir, fileName);
// Find the libDef in the cached repo
try {
const terseFilePath = path.relative(
path.resolve(npmDir, '..', '..'),
filePath,
);
if (!overwrite && (await fs.exists(filePath))) {
listItem(
colors.bold(
colors.red(
`${terseFilePath} already exists and appears to have been manually ` +
`written or changed!`,
),
),
colors.green(
`Consider contributing your changes back to flow-typed repository :)`,
),
`Read more at https://github.com/flow-typed/flow-typed/blob/main/CONTRIBUTING.md`,
'Use --overwrite to overwrite the existing libdef.',
);
return true;
}
const repoVersion = await getNpmLibDefVersionHash(
getCacheRepoDir(),
npmLibDef,
);
const codeSignPreprocessor = signCodeStream(repoVersion);
await copyFile(npmLibDef.path, filePath, codeSignPreprocessor);
listItem(fileName, colors.green(`.${path.sep}${terseFilePath}`));
// Remove any lingering stubs
console.log(npmLibDef.name);
console.log(scopedDir);
const stubName = `${npmLibDef.name}_vx.x.x.js`;
const stubPath = path.join(scopedDir, stubName);
if (overwrite && (await fs.exists(stubPath))) {
await fs.unlink(stubPath);
}
return true;
} catch (e) {
console.error(` !! Failed to install ${npmLibDef.name} at ${filePath}`);
console.error(` ERROR: ${e.message}`);
return false;
}
}
export {
installNpmLibDefs as _installNpmLibDefs,
installNpmLibDef as _installNpmLibDef,
};