ensure-repo.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. /*-----------------------------------------------------------------------------
  2. | Copyright (c) Jupyter Development Team.
  3. | Distributed under the terms of the Modified BSD License.
  4. |----------------------------------------------------------------------------*/
  5. /**
  6. * Ensure the integrity of the packages in the repo.
  7. *
  8. * Ensure the core package version dependencies match everywhere.
  9. * Ensure imported packages match dependencies.
  10. * Ensure a consistent version of all packages.
  11. * Manage the metapackage meta package.
  12. */
  13. import * as path from 'path';
  14. import * as utils from './utils';
  15. import { ensurePackage, IEnsurePackageOptions } from './ensure-package';
  16. type Dict<T> = { [key: string]: T };
  17. // Data to ignore.
  18. let MISSING: Dict<string[]> = {
  19. '@jupyterlab/buildutils': ['path'],
  20. '@jupyterlab/testutils': ['fs'],
  21. '@jupyterlab/vega4-extension': ['vega-embed'],
  22. '@jupyterlab/vega5-extension': ['vega-embed']
  23. };
  24. let UNUSED: Dict<string[]> = {
  25. '@jupyterlab/apputils': ['@types/react'],
  26. '@jupyterlab/application': ['font-awesome'],
  27. '@jupyterlab/apputils-extension': ['es6-promise'],
  28. '@jupyterlab/services': ['node-fetch', 'ws'],
  29. '@jupyterlab/testutils': ['node-fetch', 'identity-obj-proxy'],
  30. '@jupyterlab/test-csvviewer': ['csv-spectrum'],
  31. '@jupyterlab/vega4-extension': ['vega', 'vega-lite'],
  32. '@jupyterlab/vega5-extension': ['vega', 'vega-lite'],
  33. '@jupyterlab/ui-components': ['@blueprintjs/icons']
  34. };
  35. // Packages that are allowed to have differing versions
  36. let DIFFERENT_VERSIONS: Array<string> = ['vega-lite', 'vega', 'vega-embed'];
  37. let SKIP_CSS: Dict<string[]> = {
  38. '@jupyterlab/application': ['@jupyterlab/rendermime'],
  39. '@jupyterlab/application-extension': ['@jupyterlab/apputils'],
  40. '@jupyterlab/completer': ['@jupyterlab/codeeditor'],
  41. '@jupyterlab/docmanager': ['@jupyterlab/statusbar'], // Statusbar styles should not be used by status reporters
  42. '@jupyterlab/docregistry': [
  43. '@jupyterlab/codeeditor', // Only used for model
  44. '@jupyterlab/codemirror', // Only used for Mode.findByFileName
  45. '@jupyterlab/rendermime' // Only used for model
  46. ],
  47. '@jupyterlab/documentsearch': [
  48. '@jupyterlab/cells',
  49. '@jupyterlab/codeeditor',
  50. '@jupyterlab/codemirror',
  51. '@jupyterlab/fileeditor',
  52. '@jupyterlab/notebook'
  53. ],
  54. '@jupyterlab/filebrowser': ['@jupyterlab/statusbar'],
  55. '@jupyterlab/fileeditor': ['@jupyterlab/statusbar'],
  56. '@jupyterlab/help-extension': ['@jupyterlab/application'],
  57. '@jupyterlab/shortcuts-extension': ['@jupyterlab/application'],
  58. '@jupyterlab/tabmanager-extension': ['@jupyterlab/application'],
  59. '@jupyterlab/theme-dark-extension': [
  60. '@jupyterlab/application',
  61. '@jupyterlab/apputils'
  62. ],
  63. '@jupyterlab/theme-light-extension': [
  64. '@jupyterlab/application',
  65. '@jupyterlab/apputils'
  66. ],
  67. '@jupyterlab/ui-extension': ['@blueprintjs/icons']
  68. };
  69. let pkgData: Dict<any> = {};
  70. let pkgPaths: Dict<string> = {};
  71. let pkgNames: Dict<string> = {};
  72. let depCache: Dict<string> = {};
  73. let locals: Dict<string> = {};
  74. /**
  75. * Ensure the metapackage package.
  76. *
  77. * @returns An array of messages for changes.
  78. */
  79. function ensureMetaPackage(): string[] {
  80. let basePath = path.resolve('.');
  81. let mpPath = path.join(basePath, 'packages', 'metapackage');
  82. let mpJson = path.join(mpPath, 'package.json');
  83. let mpData = utils.readJSONFile(mpJson);
  84. let messages: string[] = [];
  85. let seen: Dict<boolean> = {};
  86. utils.getCorePaths().forEach(pkgPath => {
  87. if (path.resolve(pkgPath) === path.resolve(mpPath)) {
  88. return;
  89. }
  90. let name = pkgNames[pkgPath];
  91. if (!name) {
  92. return;
  93. }
  94. seen[name] = true;
  95. let data = pkgData[name];
  96. let valid = true;
  97. // Ensure it is a dependency.
  98. if (!mpData.dependencies[name]) {
  99. valid = false;
  100. mpData.dependencies[name] = '^' + data.version;
  101. }
  102. if (!valid) {
  103. messages.push(`Updated: ${name}`);
  104. }
  105. });
  106. // Make sure there are no extra deps.
  107. Object.keys(mpData.dependencies).forEach(name => {
  108. if (!(name in seen)) {
  109. messages.push(`Removing dependency: ${name}`);
  110. delete mpData.dependencies[name];
  111. }
  112. });
  113. // Write the files.
  114. if (messages.length > 0) {
  115. utils.writePackageData(mpJson, mpData);
  116. }
  117. // Update the global data.
  118. pkgData[mpData.name] = mpData;
  119. return messages;
  120. }
  121. /**
  122. * Ensure the jupyterlab application package.
  123. */
  124. function ensureJupyterlab(): string[] {
  125. let basePath = path.resolve('.');
  126. let corePath = path.join(basePath, 'dev_mode', 'package.json');
  127. let corePackage = utils.readJSONFile(corePath);
  128. corePackage.jupyterlab.extensions = {};
  129. corePackage.jupyterlab.mimeExtensions = {};
  130. corePackage.jupyterlab.linkedPackages = {};
  131. corePackage.dependencies = {};
  132. let singletonPackages = corePackage.jupyterlab.singletonPackages;
  133. let vendorPackages = corePackage.jupyterlab.vendor;
  134. utils.getCorePaths().forEach(pkgPath => {
  135. let dataPath = path.join(pkgPath, 'package.json');
  136. let data: any;
  137. try {
  138. data = utils.readJSONFile(dataPath);
  139. } catch (e) {
  140. return;
  141. }
  142. // Determine whether to include the package.
  143. if (!data.jupyterlab) {
  144. return;
  145. }
  146. // Skip if explicitly marked as not a core dep.
  147. if (
  148. 'coreDependency' in data.jupyterlab &&
  149. !data.jupyterlab.coreDependency
  150. ) {
  151. return;
  152. }
  153. // Skip if it is not marked as an extension or a core dep.
  154. if (
  155. !data.jupyterlab.coreDependency &&
  156. !data.jupyterlab.extension &&
  157. !data.jupyterlab.mimeExtension
  158. ) {
  159. return;
  160. }
  161. // Make sure it is included as a dependency.
  162. corePackage.dependencies[data.name] = '^' + String(data.version);
  163. // Add its dependencies to the core dependencies if they are in the
  164. // singleton packages or vendor packages.
  165. let deps = data.dependencies || {};
  166. for (let dep in deps) {
  167. if (singletonPackages.indexOf(dep) !== -1) {
  168. corePackage.dependencies[dep] = deps[dep];
  169. }
  170. if (vendorPackages.indexOf(dep) !== -1) {
  171. corePackage.dependencies[dep] = deps[dep];
  172. }
  173. }
  174. let jlab = data.jupyterlab;
  175. if (!jlab) {
  176. return;
  177. }
  178. // Handle extensions.
  179. ['extension', 'mimeExtension'].forEach(item => {
  180. let ext = jlab[item];
  181. if (ext === true) {
  182. ext = '';
  183. }
  184. if (typeof ext !== 'string') {
  185. return;
  186. }
  187. corePackage.jupyterlab[item + 's'][data.name] = ext;
  188. });
  189. });
  190. utils.getLernaPaths().forEach(pkgPath => {
  191. let dataPath = path.join(pkgPath, 'package.json');
  192. let data: any;
  193. try {
  194. data = utils.readJSONFile(dataPath);
  195. } catch (e) {
  196. return;
  197. }
  198. // watch all src, build, and test files in the Jupyterlab project
  199. let relativePath = utils.ensureUnixPathSep(
  200. path.join('..', path.relative(basePath, pkgPath))
  201. );
  202. corePackage.jupyterlab.linkedPackages[data.name] = relativePath;
  203. });
  204. // Write the package.json back to disk.
  205. if (utils.writePackageData(corePath, corePackage)) {
  206. return ['Updated dev mode'];
  207. }
  208. return [];
  209. }
  210. /**
  211. * Ensure the repo integrity.
  212. */
  213. export async function ensureIntegrity(): Promise<boolean> {
  214. let messages: Dict<string[]> = {};
  215. // Pick up all the package versions.
  216. let paths = utils.getLernaPaths();
  217. // These two are not part of the workspaces but should be kept
  218. // in sync.
  219. paths.push('./jupyterlab/tests/mock_packages/extension');
  220. paths.push('./jupyterlab/tests/mock_packages/mimeextension');
  221. const cssImports: Dict<Array<string>> = {};
  222. // Get the package graph.
  223. const graph = utils.getPackageGraph();
  224. // Gather all of our package data and other metadata.
  225. paths.forEach(pkgPath => {
  226. // Read in the package.json.
  227. let data: any;
  228. try {
  229. data = utils.readJSONFile(path.join(pkgPath, 'package.json'));
  230. } catch (e) {
  231. console.error(e);
  232. return;
  233. }
  234. pkgData[data.name] = data;
  235. pkgPaths[data.name] = pkgPath;
  236. pkgNames[pkgPath] = data.name;
  237. locals[data.name] = pkgPath;
  238. });
  239. // Build up an ordered list of CSS imports for each local package.
  240. Object.keys(locals).forEach(name => {
  241. const data = pkgData[name];
  242. const deps: Dict<string> = data.dependencies || {};
  243. const skip = SKIP_CSS[name] || [];
  244. const cssData: Dict<Array<string>> = {};
  245. if (data.jupyterlab && data.jupyterlab.extraStyles) {
  246. Object.keys(data.jupyterlab.extraStyles).forEach(depName => {
  247. cssData[depName] = data.jupyterlab.extraStyles[depName];
  248. });
  249. }
  250. Object.keys(deps).forEach(depName => {
  251. // Bail for skipped imports and known extra styles.
  252. if (skip.indexOf(depName) !== -1 || depName in cssData) {
  253. return;
  254. }
  255. const depData = graph.getNodeData(depName);
  256. if (depData.style) {
  257. cssData[depName] = [depData.style];
  258. }
  259. });
  260. // Get our CSS imports in dependency order.
  261. cssImports[name] = [];
  262. graph.dependenciesOf(name).forEach(depName => {
  263. if (depName in cssData) {
  264. cssData[depName].forEach(cssPath => {
  265. cssImports[name].push(`${depName}/${cssPath}`);
  266. });
  267. }
  268. });
  269. });
  270. // Update the metapackage.
  271. let pkgMessages = ensureMetaPackage();
  272. if (pkgMessages.length > 0) {
  273. let pkgName = '@jupyterlab/metapackage';
  274. if (!messages[pkgName]) {
  275. messages[pkgName] = [];
  276. }
  277. messages[pkgName] = messages[pkgName].concat(pkgMessages);
  278. }
  279. // Validate each package.
  280. for (let name in locals) {
  281. let unused = UNUSED[name] || [];
  282. // Allow jest-junit to be unused in the test suite.
  283. if (name.indexOf('@jupyterlab/test-') === 0) {
  284. unused.push('jest-junit');
  285. }
  286. let options: IEnsurePackageOptions = {
  287. pkgPath: pkgPaths[name],
  288. data: pkgData[name],
  289. depCache,
  290. missing: MISSING[name],
  291. unused,
  292. locals,
  293. cssImports: cssImports[name],
  294. differentVersions: DIFFERENT_VERSIONS
  295. };
  296. if (name === '@jupyterlab/metapackage') {
  297. options.noUnused = false;
  298. }
  299. let pkgMessages = await ensurePackage(options);
  300. if (pkgMessages.length > 0) {
  301. messages[name] = pkgMessages;
  302. }
  303. }
  304. // Handle the top level package.
  305. let corePath = path.resolve('.', 'package.json');
  306. let coreData: any = utils.readJSONFile(corePath);
  307. if (utils.writePackageData(corePath, coreData)) {
  308. messages['top'] = ['Update package.json'];
  309. }
  310. // Handle the JupyterLab application top package.
  311. pkgMessages = ensureJupyterlab();
  312. if (pkgMessages.length > 0) {
  313. let pkgName = '@jupyterlab/application-top';
  314. if (!messages[pkgName]) {
  315. messages[pkgName] = [];
  316. }
  317. messages[pkgName] = messages[pkgName].concat(pkgMessages);
  318. }
  319. // Handle any messages.
  320. if (Object.keys(messages).length > 0) {
  321. console.log(JSON.stringify(messages, null, 2));
  322. if ('--force' in process.argv) {
  323. console.log(
  324. '\n\nPlease run `jlpm run integrity` locally and commit the changes'
  325. );
  326. process.exit(1);
  327. }
  328. utils.run('jlpm install');
  329. console.log('\n\nMade integrity changes!');
  330. console.log('Please commit the changes by running:');
  331. console.log('git commit -a -m "Package integrity updates"');
  332. return false;
  333. }
  334. console.log('Repo integrity verified!');
  335. return true;
  336. }
  337. if (require.main === module) {
  338. void ensureIntegrity();
  339. }