node-jest/scripts/buildTs.js

198 lines
5.6 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const util = require('util');
const chalk = require('chalk');
const execa = require('execa');
const globby = require('globby');
const stripJsonComments = require('strip-json-comments');
const throat = require('throat');
const {getPackages} = require('./buildUtils');
const readFilePromise = util.promisify(fs.readFile);
(async () => {
const packages = getPackages();
const packagesWithTs = packages.filter(p =>
fs.existsSync(path.resolve(p.packageDir, 'tsconfig.json')),
);
const {stdout: allWorkspacesString} = await execa('cat', [
'debian/yarn-workspaces.json'
]);
const workspacesWithTs = new Map(
JSON.parse(`[${allWorkspacesString.split('\n').join(',')}]`)
.filter(({location}) =>
packagesWithTs.some(({packageDir}) => packageDir.endsWith(location)),
)
.map(({location, name}) => [name, location]),
);
packagesWithTs.forEach(({packageDir, pkg}) => {
assert.ok(pkg.types, `Package ${pkg.name} is missing \`types\` field`);
assert.strictEqual(
pkg.types,
pkg.main.replace(/\.js$/, '.d.ts'),
`\`main\` and \`types\` field of ${pkg.name} does not match`,
);
const jestDependenciesOfPackage = Object.keys(pkg.dependencies || {})
.concat(Object.keys(pkg.devDependencies || {}))
.filter(dep => workspacesWithTs.has(dep))
.filter(dep => {
// nothing should depend on these
if (dep === 'jest-circus' || dep === 'jest-jasmine2') {
return false;
}
// these are just `require.resolve`-ed
if (pkg.name === 'jest-config') {
if (dep === '@jest/test-sequencer' || dep === 'babel-jest') {
return false;
}
}
return true;
})
.map(dep =>
path.relative(
packageDir,
`${packageDir}/../../${workspacesWithTs.get(dep)}`,
),
)
.sort();
if (jestDependenciesOfPackage.length > 0) {
const tsConfig = JSON.parse(
stripJsonComments(
fs.readFileSync(`${packageDir}/tsconfig.json`, 'utf8'),
),
);
const references = tsConfig.references.map(({path}) => path);
assert.deepStrictEqual(
references,
jestDependenciesOfPackage,
`Expected declared references to match dependencies in packages ${
pkg.name
}. Got:\n\n${references.join(
'\n',
)}\nExpected:\n\n${jestDependenciesOfPackage.join('\n')}`,
);
}
});
const args = [
'-b',
...packagesWithTs.map(({packageDir}) => packageDir),
...process.argv.slice(2),
];
console.log(chalk.inverse(' Building TypeScript definition files '));
try {
await execa('tsc', args, {stdio: 'inherit'});
console.log(
chalk.inverse.green(' Successfully built TypeScript definition files '),
);
} catch (e) {
console.error(
chalk.inverse.red(' Unable to build TypeScript definition files '),
);
}
console.log(chalk.inverse(' Validating TypeScript definition files '));
// we want to limit the number of processes we spawn
const cpus = Math.max(1, os.cpus().length - 1);
try {
await Promise.all(
packagesWithTs.map(
throat(cpus, async ({packageDir, pkg}) => {
const buildDir = path.resolve(packageDir, 'build/**/*.d.ts');
const globbed = await globby([buildDir]);
const files = await Promise.all(
globbed.map(file =>
Promise.all([file, readFilePromise(file, 'utf8')]),
),
);
const filesWithTypeReferences = files
.filter(([, content]) => content.includes('/// <reference types'))
.filter(hit => hit.length > 0);
const filesWithReferences = filesWithTypeReferences
.map(([name, content]) => [
name,
content
.split('\n')
.filter(line => line !== '/// <reference types="node" />')
.filter(line => line.includes('/// <reference types'))
.join('\n'),
])
.filter(([, content]) => content.length > 0)
.filter(hit => hit.length > 0)
.map(([file, references]) =>
chalk.red(
`${chalk.bold(
file,
)} has the following non-node type references:\n\n${references}\n`,
),
)
.join('\n\n');
if (filesWithReferences) {
throw new Error(filesWithReferences);
}
const filesWithNodeReference = filesWithTypeReferences.map(
([filename]) => filename,
);
if (filesWithNodeReference.length > 0) {
assert.ok(
pkg.dependencies,
`Package \`${pkg.name}\` is missing \`dependencies\``,
);
assert.strictEqual(
pkg.dependencies['@types/node'],
'*',
`Package \`${pkg.name}\` is missing a dependency on \`@types/node\``,
);
}
}),
),
);
} catch (e) {
console.error(
chalk.inverse.red(' Unable to validate TypeScript definition files '),
);
throw e;
}
console.log(
chalk.inverse.green(' Successfully validated TypeScript definition files '),
);
})().catch(error => {
console.error('Got error', error.stack);
process.exitCode = 1;
});