ensure-integrity.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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 childProcess = require('child_process');
  9. var path = require('path');
  10. var glob = require('glob');
  11. var sortPackageJson = require('sort-package-json');
  12. var ts = require("typescript");
  13. var fs = require('fs');
  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 basePath = path.resolve('.');
  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 problems = [];
  36. // Verify local dependencies are correct.
  37. Object.keys(deps).forEach(function(name) {
  38. if (pkgData[name]) {
  39. var desired = '^' + pkgData[name].version;
  40. if (deps[name] !== desired) {
  41. problems.push('Invalid core version: ' + name);
  42. }
  43. data.dependencies[name] = '^' + pkgData[name].version;
  44. }
  45. });
  46. if (pkgName == '@jupyterlab/all-packages') {
  47. problems = problems.concat(ensureAllPackages());
  48. }
  49. // For TypeScript files, verify imports match dependencies.
  50. filenames = glob.sync(path.join(dname, 'src/*.ts*'));
  51. filenames = filenames.concat(glob.sync(path.join(dname, 'src/**/*.ts*')));
  52. if (filenames.length == 0) {
  53. if (ensurePackageData(data, path.join(dname, 'package.json'))) {
  54. problems.push('Package data changed');
  55. }
  56. return problems;
  57. }
  58. var imports = [];
  59. // Extract all of the imports from the TypeScript files.
  60. filenames.forEach(fileName => {
  61. var sourceFile = ts.createSourceFile(fileName,
  62. fs.readFileSync(fileName).toString(), ts.ScriptTarget.ES6,
  63. /*setParentNodes */ true);
  64. imports = imports.concat(getImports(sourceFile));
  65. });
  66. var names = Array.from(new Set(imports)).sort();
  67. names = names.map(function(name) {
  68. var parts = name.split('/');
  69. if (name.indexOf('@') === 0) {
  70. return parts[0] + '/' + parts[1];
  71. }
  72. return parts[0];
  73. })
  74. // Look for imports with no dependencies.
  75. names.forEach(function(name) {
  76. if (MISSING[pkgName] && MISSING[pkgName].indexOf(name) !== -1) {
  77. return;
  78. }
  79. if (name == '.' || name == '..') {
  80. return;
  81. }
  82. if (!deps[name]) {
  83. problems.push('Missing dependency: ' + name);
  84. }
  85. });
  86. // Look for unused packages
  87. Object.keys(deps).forEach(function(name) {
  88. if (UNUSED[pkgName] && UNUSED[pkgName].indexOf(name) !== -1) {
  89. return;
  90. }
  91. if (names.indexOf(name) === -1) {
  92. problems.push('Unused dependency: ' + name);
  93. delete data.dependencies[name]
  94. }
  95. });
  96. if (ensurePackageData(data, path.join(dname, 'package.json'))) {
  97. problems.push('Package data changed');
  98. }
  99. return problems;
  100. }
  101. /**
  102. * Ensure the all-packages package.
  103. */
  104. function ensureAllPackages() {
  105. var localPackages = glob.sync(path.join(basePath, 'packages', '*'));
  106. var allPackagesPath = path.join(basePath, 'packages', 'all-packages');
  107. var allPackageJson = path.join(allPackagesPath, 'package.json');
  108. var allPackageData = require(allPackageJson);
  109. var tsconfigPath = path.join(
  110. basePath, 'packages', 'all-packages', 'tsconfig.json'
  111. );
  112. var tsconfig = require(tsconfigPath);
  113. var indexPath = path.join(basePath, 'packages', 'all-packages', 'src', 'index.ts');
  114. var index = fs.readFileSync(indexPath, 'utf8');
  115. var lines = index.split('\n').slice(0, 3);
  116. var problems = [];
  117. localPackages.forEach(function (pkgPath) {
  118. if (pkgPath === allPackagesPath) {
  119. return;
  120. }
  121. var name = pkgNames[pkgPath];
  122. var data = pkgData[name];
  123. var valid = true;
  124. // Ensure it is a dependency.
  125. if (!allPackageData.dependencies[name]) {
  126. valid = false;
  127. allPackageData.dependencies[name] = '^' + data.version;
  128. }
  129. // Ensure it is in index.ts
  130. if (index.indexOf(name) === -1) {
  131. valid = false;
  132. }
  133. lines.push('import "' + name + '";\n');
  134. if (!valid) {
  135. problems.push('Updated: ' + name);
  136. }
  137. });
  138. // Write the files.
  139. if (ensurePackageData(allPackageData, allPackageJson)) {
  140. problems.push('Package data changed');
  141. }
  142. var newIndex = lines.join('\n');
  143. if (newIndex != index) {
  144. problems.push('Index changed');
  145. fs.writeFileSync(indexPath, lines.join('\n'));
  146. }
  147. return problems;
  148. }
  149. /**
  150. * Extract the module imports from a TypeScript source file.
  151. */
  152. function getImports(sourceFile) {
  153. var imports = [];
  154. handleNode(sourceFile);
  155. function handleNode(node) {
  156. switch (node.kind) {
  157. case ts.SyntaxKind.ImportDeclaration:
  158. imports.push(node.moduleSpecifier.text);
  159. break;
  160. case ts.SyntaxKind.ImportEqualsDeclaration:
  161. imports.push(node.moduleReference.expression.text);
  162. break;
  163. }
  164. ts.forEachChild(node, handleNode);
  165. }
  166. return imports;
  167. }
  168. /**
  169. * Write package data using sort-package-json.
  170. */
  171. function ensurePackageData(data, pkgJsonPath) {
  172. var text = JSON.stringify(sortPackageJson(data), null, 2) + '\n';
  173. var orig = fs.readFileSync(pkgJsonPath).toString();
  174. if (text !== orig) {
  175. fs.writeFileSync(pkgJsonPath, text);
  176. return true;
  177. }
  178. return false;
  179. }
  180. /**
  181. * Ensure the repo integrity.
  182. */
  183. function ensureIntegrity() {
  184. var errors = {};
  185. // Look in all of the packages.
  186. var lernaConfig = require(path.join(basePath, 'lerna.json'));
  187. var paths = [];
  188. for (let spec of lernaConfig.packages) {
  189. paths = paths.concat(glob.sync(path.join(basePath, spec)));
  190. }
  191. // Pick up all the package versions.
  192. paths.forEach(function(pkgPath) {
  193. pkgPath = path.resolve(pkgPath);
  194. // Read in the package.json.
  195. try {
  196. var package = require(path.join(pkgPath, 'package.json'));
  197. } catch (e) {
  198. return;
  199. }
  200. pkgData[package.name] = package;
  201. pkgPaths[package.name] = pkgPath;
  202. pkgNames[pkgPath] = package.name;
  203. });
  204. // Validate each package.
  205. for (let name in pkgData) {
  206. var problems = ensurePackage(name);
  207. if (problems.length > 0) {
  208. errors[name] = problems;
  209. }
  210. };
  211. // Handle any errors.
  212. if (Object.keys(errors).length > 0) {
  213. console.log('Repo integrity report:')
  214. console.log(JSON.stringify(errors, null, 2));
  215. process.exit(1);
  216. } else {
  217. console.log('Repo integrity verified!');
  218. }
  219. }
  220. ensureIntegrity();