flow-typed
Version:
A repository of high quality flow type definitions
268 lines (246 loc) • 8.54 kB
Flow
// @flow
import path from 'path';
import typeof Yargs from 'yargs';
import {table} from 'table';
import {findFlowRoot} from '../lib/flowProjectUtils';
import {
findNpmLibDef,
getCacheNpmLibDefs,
getInstalledNpmLibDefs,
getNpmLibDefVersionHash,
} from '../lib/npm/npmLibDefs';
import {fs} from '../lib/node';
import {determineFlowSpecificVersion} from '../lib/flowVersion';
import {signCodeStream} from '../lib/codeSign';
import {CACHE_REPO_EXPIRY, getCacheRepoDir} from '../lib/cacheRepoUtils';
import {getFtConfig} from '../lib/ftConfig';
import {findEnvDef, getEnvDefVersionHash, getEnvDefs} from '../lib/envDefs';
const pullSignature = (v: string) => v.split('\n').slice(0, 2);
export const name = 'outdated';
export const description =
'Update the flow-typed cache and print any outdated libdefs in current project';
export function setup(yargs: Yargs): Yargs {
return yargs
.usage(`$0 ${name}`)
.options({
flowVersion: {
alias: 'f',
describe:
'The Flow version that outdated libdefs must be compatible with',
type: 'string',
},
useCacheUntil: {
alias: 'u',
describe: 'Use cache until specified time in milliseconds',
type: 'number',
},
libdefDir: {
alias: 'l',
describe: 'Scan currently installed libdefs from a custom directory',
type: 'string',
demandOption: false,
},
rootDir: {
alias: 'r',
describe: 'Directory of .flowconfig relative to node_modules',
type: 'string',
},
packageDir: {
alias: 'p',
describe:
'The relative path of package.json where flow-bin is installed',
type: 'string',
},
})
.example('$0 outdated', '')
.help('h')
.alias('h', 'help');
}
type Args = {
flowVersion?: mixed, // string
useCacheUntil?: mixed, // number (milliseconds)
libdefDir?: mixed, // string
rootDir?: mixed, // string
packageDir?: mixed, // string
...
};
/**
* 1. Update and pull the cache
* 2. Compare current installed with what's in the cache
* 3. Create a list to print out
*/
export async function run(args: Args): Promise<number> {
const cwd =
typeof args.rootDir === 'string'
? path.resolve(args.rootDir)
: process.cwd();
const flowProjectRoot = await findFlowRoot(cwd);
const libdefDir =
typeof args.libdefDir === 'string' ? args.libdefDir : 'flow-typed';
const packageDir =
typeof args.packageDir === 'string' ? path.resolve(args.packageDir) : cwd;
const flowVersion = await determineFlowSpecificVersion(
packageDir,
args.flowVersion,
);
const useCacheUntil = Number(args.useCacheUntil) || CACHE_REPO_EXPIRY;
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 1;
}
const cachedLibDefs = await getCacheNpmLibDefs(useCacheUntil, true);
const installedLibDefs = await getInstalledNpmLibDefs(
flowProjectRoot,
libdefDir,
);
let outdatedList: Array<{
name: string,
message: string,
}> = [];
await Promise.all(
cachedLibDefs.map(async cachedDef => {
await Promise.all(
[...installedLibDefs.values()].map(async installedDef => {
// For each cached def we'll check if it's installed as a stub
// if so then we should mark as outdated
if (
installedDef.kind === 'Stub' &&
installedDef.name.startsWith(`${cachedDef.name}_`)
) {
const stubName = installedDef.name.substring(
0,
installedDef.name.indexOf('_'),
);
outdatedList.push({
name: stubName,
message: `A new libdef has been published to the registry replacing your stub install it with \`flow-typed install ${stubName}\``,
});
}
if (
installedDef.kind === 'LibDef' &&
installedDef.libDef.name === cachedDef.name &&
installedDef.libDef.scope === cachedDef.scope
) {
const definitionFullName = installedDef.libDef.scope
? `${installedDef.libDef.scope}/${installedDef.libDef.name}`
: installedDef.libDef.name;
// If we've found a match we need to know if definition has changed
// We can just find a compatible matching library and then
// figure out if the flow signatures has changed
const npmLibDef = await findNpmLibDef(
definitionFullName,
installedDef.libDef.version,
flowVersion,
args.useCacheUntil ? Number(args.useCacheUntil) : undefined,
undefined,
cachedLibDefs,
);
if (npmLibDef) {
const file = await fs.readFile(
path.join(cwd, installedDef.libDef.path),
'utf-8',
);
const installedSignatureArray = pullSignature(file);
const repoVersion = await getNpmLibDefVersionHash(
getCacheRepoDir(),
npmLibDef,
);
const codeSignPreprocessor = signCodeStream(repoVersion);
const content = fs.readFileSync(npmLibDef.path, 'utf-8');
const cacheSignatureArray = pullSignature(
codeSignPreprocessor(content),
);
if (
installedSignatureArray[0] !== cacheSignatureArray[0] ||
installedSignatureArray[1] !== cacheSignatureArray[1]
) {
outdatedList.push({
name: definitionFullName,
message:
'This libdef does not match what we found in the registry, update it with `flow-typed update`',
});
}
}
}
}),
);
}),
);
const ftConfig = getFtConfig(cwd);
const {env} = ftConfig ?? {};
if (Array.isArray(env)) {
const envDefs = await getEnvDefs();
await Promise.all(
env.map(async en => {
if (typeof en !== 'string') return;
const def = await findEnvDef(en, flowVersion, useCacheUntil, envDefs);
if (def) {
const localDefPath = path.join(
flowProjectRoot,
libdefDir,
'environments',
`${en}.js`,
);
if (!(await fs.exists(localDefPath))) {
outdatedList.push({
name: en,
message:
'This env def has not yet been installed try running `flow-typed install`',
});
return;
} else {
const installedDef = fs.readFileSync(localDefPath, 'utf-8');
const installedSignatureArray = pullSignature(installedDef);
const repoVersion = await getEnvDefVersionHash(
getCacheRepoDir(),
def,
);
const codeSignPreprocessor = signCodeStream(repoVersion);
const content = fs.readFileSync(def.path, 'utf-8');
const cacheSignatureArray = pullSignature(
codeSignPreprocessor(content),
);
if (
installedSignatureArray[0] !== cacheSignatureArray[0] ||
installedSignatureArray[1] !== cacheSignatureArray[1]
) {
outdatedList.push({
name: en,
message:
'This env definition does not match what we found in the registry, update it with `flow-typed update`',
});
}
}
} else {
outdatedList.push({
name: en,
message:
'This env definition does not exist in the registry or there is no compatible definition for your version of flow',
});
}
}),
);
}
if (outdatedList.length > 0) {
// Cleanup duplicated dependencies which come from nested libraries that ship flow
outdatedList = outdatedList.reduce((acc, cur) => {
if (acc.find(o => o.name === cur.name)) {
return acc;
}
return [...acc, cur];
}, []);
console.log(
table([
['Name', 'Details'],
...outdatedList.map(o => [o.name, o.message]),
]),
);
} else {
console.log('All your lib defs are up to date!');
}
return 0;
}