ensure-integrity.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /**
  2. * Ensure the integrity of the packages in the repo.
  3. *
  4. * Ensure the core package version dependencies match everywhere.
  5. * Ensure imports match dependencies for TypeScript packages.
  6. * Manage the all-packages meta package.
  7. */
  8. var path = require('path');
  9. var glob = require('glob');
  10. var ts = require("typescript");
  11. var fs = require('fs-extra');
  12. var getDependency = require('./get-dependency');
  13. var utils = require('./utils');
  14. // Data to ignore.
  15. var MISSING = {
  16. "@jupyterlab/buildutils": ["path"]
  17. }
  18. var UNUSED = {
  19. "@jupyterlab/apputils-extension": ["es6-promise"],
  20. "@jupyterlab/theme-dark-extension": ["font-awesome"],
  21. "@jupyterlab/theme-light-extension": ["font-awesome"],
  22. "@jupyterlab/vega2-extension": ["d3","vega","vega-lite"]
  23. }
  24. var pkgData = {};
  25. var pkgPaths = {};
  26. var pkgNames = {};
  27. var seenDeps = {};
  28. /**
  29. * Ensure the integrity of a package.
  30. */
  31. function ensurePackage(pkgName) {
  32. var dname = pkgPaths[pkgName];
  33. var data = pkgData[pkgName];
  34. var deps = data.dependencies;
  35. var devDeps = data.devDependencies;
  36. var messages = [];
  37. // Verify dependencies are consistent.
  38. Object.keys(deps).forEach(function(name) {
  39. if (!(name in seenDeps)) {
  40. seenDeps[name] = getDependency(name);
  41. }
  42. deps[name] = seenDeps[name];
  43. });
  44. // Verify devDependencies are consistent.
  45. Object.keys(devDeps).forEach(function(name) {
  46. if (!(name in seenDeps)) {
  47. seenDeps[name] = getDependency(name);
  48. }
  49. devDeps[name] = seenDeps[name];
  50. });
  51. // For TypeScript files, verify imports match dependencies.
  52. filenames = glob.sync(path.join(dname, 'src/*.ts*'));
  53. filenames = filenames.concat(glob.sync(path.join(dname, 'src/**/*.ts*')));
  54. if (filenames.length == 0) {
  55. if (utils.ensurePackageData(data, path.join(dname, 'package.json'))) {
  56. messages.push('Package data changed');
  57. }
  58. return messages;
  59. }
  60. var imports = [];
  61. // Extract all of the imports from the TypeScript files.
  62. filenames.forEach(fileName => {
  63. var sourceFile = ts.createSourceFile(fileName,
  64. fs.readFileSync(fileName).toString(), ts.ScriptTarget.ES6,
  65. /*setParentNodes */ true);
  66. imports = imports.concat(getImports(sourceFile));
  67. });
  68. var names = Array.from(new Set(imports)).sort();
  69. names = names.map(function(name) {
  70. var parts = name.split('/');
  71. if (name.indexOf('@') === 0) {
  72. return parts[0] + '/' + parts[1];
  73. }
  74. return parts[0];
  75. })
  76. // Look for imports with no dependencies.
  77. names.forEach(function(name) {
  78. if (MISSING[pkgName] && MISSING[pkgName].indexOf(name) !== -1) {
  79. return;
  80. }
  81. if (name == '.' || name == '..') {
  82. return;
  83. }
  84. if (!deps[name]) {
  85. messages.push('Missing dependency: ' + name);
  86. if (!(name in seenDeps)) {
  87. seenDeps[name] = getDependency(name);
  88. }
  89. deps[name] = seenDeps[name];
  90. }
  91. });
  92. // Look for unused packages
  93. Object.keys(deps).forEach(function(name) {
  94. if (UNUSED[pkgName] && UNUSED[pkgName].indexOf(name) !== -1) {
  95. return;
  96. }
  97. if (names.indexOf(name) === -1) {
  98. messages.push('Unused dependency: ' + name);
  99. delete data.dependencies[name]
  100. }
  101. });
  102. if (utils.ensurePackageData(data, path.join(dname, 'package.json'))) {
  103. messages.push('Package data changed');
  104. }
  105. return messages;
  106. }
  107. /**
  108. * Extract the module imports from a TypeScript source file.
  109. */
  110. function getImports(sourceFile) {
  111. var imports = [];
  112. handleNode(sourceFile);
  113. function handleNode(node) {
  114. switch (node.kind) {
  115. case ts.SyntaxKind.ImportDeclaration:
  116. imports.push(node.moduleSpecifier.text);
  117. break;
  118. case ts.SyntaxKind.ImportEqualsDeclaration:
  119. imports.push(node.moduleReference.expression.text);
  120. break;
  121. }
  122. ts.forEachChild(node, handleNode);
  123. }
  124. return imports;
  125. }
  126. /**
  127. * Ensure the all-packages package.
  128. */
  129. function ensureAllPackages() {
  130. var basePath = path.resolve('.');
  131. var allPackagesPath = path.join(basePath, 'packages', 'all-packages');
  132. var allPackageJson = path.join(allPackagesPath, 'package.json');
  133. var allPackageData = require(allPackageJson);
  134. var tsconfigPath = path.join(allPackagesPath, 'tsconfig.json');
  135. var tsconfig = require(tsconfigPath);
  136. var indexPath = path.join(allPackagesPath, 'src', 'index.ts');
  137. var index = fs.readFileSync(indexPath, 'utf8');
  138. var lines = index.split('\n').slice(0, 3);
  139. var messages = [];
  140. utils.getCorePaths().forEach(function (pkgPath) {
  141. if (pkgPath === allPackagesPath) {
  142. return;
  143. }
  144. var name = pkgNames[pkgPath];
  145. var data = pkgData[name];
  146. var valid = true;
  147. // Ensure it is a dependency.
  148. if (!allPackageData.dependencies[name]) {
  149. valid = false;
  150. allPackageData.dependencies[name] = '^' + data.version;
  151. }
  152. // Ensure it is in index.ts
  153. if (index.indexOf(name) === -1) {
  154. valid = false;
  155. }
  156. lines.push('import "' + name + '";\n');
  157. if (!valid) {
  158. messages.push('Updated: ' + name);
  159. }
  160. });
  161. // Write the files.
  162. if (utils.ensurePackageData(allPackageData, allPackageJson)) {
  163. messages.push('Package data changed');
  164. }
  165. var newIndex = lines.join('\n');
  166. if (newIndex != index) {
  167. messages.push('Index changed');
  168. fs.writeFileSync(indexPath, lines.join('\n'));
  169. }
  170. return messages;
  171. }
  172. /**
  173. * Ensure the repo integrity.
  174. */
  175. function ensureIntegrity() {
  176. var messages = {};
  177. // Pick up all the package versions.
  178. utils.getLernaPaths().forEach(function(pkgPath) {
  179. // Read in the package.json.
  180. try {
  181. var package = require(path.join(pkgPath, 'package.json'));
  182. } catch (e) {
  183. return;
  184. }
  185. pkgData[package.name] = package;
  186. pkgPaths[package.name] = pkgPath;
  187. pkgNames[pkgPath] = package.name;
  188. });
  189. // Validate each package.
  190. for (let name in pkgData) {
  191. var pkgMessages = ensurePackage(name);
  192. if (pkgMessages.length > 0) {
  193. messages[name] = pkgMessages;
  194. }
  195. };
  196. // Handle the all-packages metapackage.
  197. var pkgMessages = ensureAllPackages();
  198. if (pkgMessages.length > 0) {
  199. var allName ='@jupyterlab/all-packages';
  200. if (!messages[allName]) {
  201. messages[allName] = [];
  202. }
  203. messages[allName] = messages[allName].concat(pkgMessages);
  204. }
  205. // Handle any messages.
  206. if (Object.keys(messages).length > 0) {
  207. console.log(JSON.stringify(messages, null, 2));
  208. if (process.env.TRAVIS_BRANCH) {
  209. console.log('\n\nPlease run `npm run integrity` locally and commit the changes');
  210. } else {
  211. console.log('\n\nPlease commit the changes by running:');
  212. console.log('git commit -a -m "Package integrity updates"')
  213. }
  214. process.exit(1);
  215. } else {
  216. console.log('Repo integrity verified!');
  217. }
  218. }
  219. ensureIntegrity();