sync-exports.mjs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. #!/usr/bin/env node
  2. import { EMPTY_OBJECT, arrayFromAsync, parallelForEach, pojo } from '@sequelize/utils';
  3. import { listDirectories, listFilesRecursive, readFileIfExists } from '@sequelize/utils/node';
  4. import isEqual from 'lodash/isEqual.js';
  5. import fs from 'node:fs/promises';
  6. import path from 'node:path';
  7. /**
  8. * does not modify the contents of the file but exits with code 1 if outdated, 0 if not
  9. */
  10. const checkOutdated = process.argv.includes('--check-outdated');
  11. /**
  12. * The package contains multiple individual exports that each need their own index file
  13. */
  14. const multipleEntryPoints = process.argv.includes('--multi-entry-points');
  15. const requestedSrcDir = process.argv[2];
  16. if (!requestedSrcDir) {
  17. console.error('Please provide the path to the src folder to synchronize');
  18. }
  19. const srcDir = path.normalize(path.join(process.cwd(), requestedSrcDir));
  20. console.info(
  21. `${checkOutdated ? 'Testing synchronization of' : 'Synchronizing'} exports of folder ${srcDir}`,
  22. );
  23. const folders = multipleEntryPoints
  24. ? (await listDirectories(srcDir)).map(folder => path.join(srcDir, folder))
  25. : [srcDir];
  26. const outdatedPaths = [];
  27. await parallelForEach(folders, async folder => {
  28. const files = await arrayFromAsync(listFilesRecursive(folder));
  29. const commonExports = [];
  30. /**
  31. * You can provide a browser-specific or node-specific implementation by adding ".browser" or ".node" to their filename
  32. */
  33. const browserExportOverrides = pojo();
  34. const nodeExportOverrides = pojo();
  35. files
  36. .map(file => {
  37. return path.relative(folder, file).replace(/\.ts$/, '.js');
  38. })
  39. .filter(pathname => {
  40. return (
  41. !/(^|\\)index\./.test(pathname) &&
  42. !pathname.startsWith('.DS_Store') &&
  43. !pathname.endsWith('.spec.js') &&
  44. !pathname.endsWith('.test.js') &&
  45. !pathname.includes('__tests__/') &&
  46. !pathname.includes('_internal/') &&
  47. !pathname.includes('.internal') &&
  48. !pathname.endsWith('.d.js')
  49. );
  50. })
  51. // eslint-disable-next-line unicorn/no-array-for-each -- clearer like this, perf doesn't matter
  52. .forEach(pathname => {
  53. if (pathname.includes('.node.')) {
  54. nodeExportOverrides[pathname.replace('.node.', '.')] = pathname;
  55. } else if (pathname.includes('.browser.')) {
  56. browserExportOverrides[pathname.replace('.browser.', '.')] = pathname;
  57. } else {
  58. commonExports.push(pathname);
  59. }
  60. });
  61. const baseExports = getExportsWithOverrides(commonExports, EMPTY_OBJECT);
  62. const browserExports = getExportsWithOverrides(commonExports, browserExportOverrides);
  63. const nodeExports = getExportsWithOverrides(commonExports, nodeExportOverrides);
  64. const promises = [];
  65. promises.push(outputExports(baseExports, path.join(folder, 'index.ts')));
  66. if (!isEqual(browserExports, baseExports)) {
  67. promises.push(outputExports(browserExports, path.join(folder, 'index.browser.ts')));
  68. }
  69. if (!isEqual(nodeExports, baseExports)) {
  70. promises.push(outputExports(nodeExports, path.join(folder, 'index.node.ts')));
  71. }
  72. await Promise.all(promises);
  73. });
  74. async function outputExports(exports, indexPath) {
  75. const imports = exports
  76. .map(pathname => {
  77. return `export * from './${pathname}';\n`;
  78. })
  79. .sort()
  80. .join('');
  81. const fileContents = `/** Generated File, do not modify directly. Run "yarn sync-exports" in the folder of the package instead */\n\n${imports}`;
  82. const file = await readFileIfExists(indexPath, 'utf-8');
  83. if (file === null || file !== fileContents) {
  84. outdatedPaths.push(indexPath);
  85. }
  86. if (!checkOutdated) {
  87. await fs.writeFile(indexPath, fileContents, 'utf-8');
  88. }
  89. }
  90. function getExportsWithOverrides(commonExports, platformExportOverrides) {
  91. const platformExportKeys = Object.keys(platformExportOverrides);
  92. if (platformExportKeys.length === 0) {
  93. return commonExports;
  94. }
  95. const platformExports = [];
  96. /** Add exports that were not replaced by another */
  97. for (const commonExport of commonExports) {
  98. if (platformExportOverrides[commonExport]) {
  99. continue;
  100. }
  101. platformExports.push(commonExport);
  102. }
  103. platformExports.push(...Object.values(platformExportOverrides));
  104. return platformExports;
  105. }
  106. if (outdatedPaths.length === 0) {
  107. console.info('All index files up-to-date');
  108. } else {
  109. const fileListStr = outdatedPaths.map(pathname => `- ${pathname}\n`).join('');
  110. if (checkOutdated) {
  111. console.info(`Outdated files:\n${fileListStr}`);
  112. process.exit(1);
  113. } else {
  114. console.info(`Updated files:\n${fileListStr}`);
  115. }
  116. }