develop.mjs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { execSync, fork } from "child_process";
  2. import swc from "@swc/core";
  3. import fs from "node:fs/promises";
  4. import chokidar from "chokidar";
  5. import path from "path";
  6. const inputDir = "./src";
  7. const outputDir = "./dist";
  8. const compileExtensions = [".ts", ".tsx", ".js", ".jsx"];
  9. const ignoreExtensions = [".md"];
  10. const dryRun = false;
  11. const started = Date.now();
  12. const inputDirName = path.basename(inputDir);
  13. const outputDirName = path.basename(outputDir);
  14. // Recursively find all files to compile
  15. const findFiles = async (dir, { allowNonExistent } = {}) => {
  16. try {
  17. let files = await fs.readdir(dir, { withFileTypes: true });
  18. let paths = await Promise.all(
  19. files.map(async (file) => {
  20. const res = path.join(dir, file.name);
  21. return file.isDirectory() ? findFiles(res) : res;
  22. })
  23. );
  24. return paths.flat();
  25. } catch (e) {
  26. if (allowNonExistent && e.code === "ENOENT") {
  27. return [];
  28. }
  29. throw e;
  30. }
  31. };
  32. const files = await findFiles(inputDir);
  33. const outFiles = await findFiles(outputDir, { allowNonExistent: true });
  34. const srcFileSet = new Set(files);
  35. // Delete all unexpected outFiles
  36. await Promise.all(
  37. outFiles.map(async (file) => {
  38. // Calculate expected src path
  39. const relativePath = file.replace(outputDirName, inputDirName);
  40. // try each compile extension
  41. const found =
  42. srcFileSet.has(relativePath) ||
  43. compileExtensions.some((ext) => {
  44. const srcPath = relativePath.replace(path.extname(relativePath), ext);
  45. return srcFileSet.has(srcPath);
  46. });
  47. if (!found) {
  48. if (dryRun) {
  49. console.log(`Deleting ${file}`);
  50. return;
  51. }
  52. await fs.unlink(file);
  53. // Recursively check if directory is empty and delete
  54. const cleanup = async (dir) => {
  55. const files = await fs.readdir(dir);
  56. if (files.length === 0) {
  57. await fs.rmdir(dir).catch(() => {});
  58. await cleanup(path.dirname(dir));
  59. }
  60. };
  61. await cleanup(path.dirname(file));
  62. }
  63. })
  64. );
  65. const getOutputPath = (file, config) => {
  66. const { inputDir, outputDir, targetExtension } = config;
  67. const relativePath = file.replace(inputDirName, outputDirName);
  68. let outputPath = relativePath;
  69. if (targetExtension) {
  70. const currentExtension = path.extname(outputPath);
  71. outputPath = outputPath.replace(currentExtension, targetExtension);
  72. }
  73. return outputPath;
  74. };
  75. const writeToOut = async (file, content, config) => {
  76. const outputPath = getOutputPath(file, config);
  77. if (dryRun) {
  78. console.log(`Writing to ${outputPath}`);
  79. return;
  80. }
  81. await fs.mkdir(outputPath.replace(/\/[^/]+$/, ""), { recursive: true });
  82. await fs.writeFile(outputPath, content);
  83. };
  84. const copyToOut = async (file, config) => {
  85. const outputPath = getOutputPath(file, config);
  86. if (dryRun) {
  87. console.log(`Copying ${file} to ${outputPath}`);
  88. return;
  89. }
  90. await fs.mkdir(outputPath.replace(/\/[^/]+$/, ""), { recursive: true });
  91. await fs.copyFile(file, outputPath);
  92. };
  93. const medusaTransform = async (file) => {
  94. if (file.includes("__tests__")) {
  95. return;
  96. }
  97. if (compileExtensions.some((ext) => file.endsWith(ext))) {
  98. const output = await swc.transformFile(file, {
  99. sourceMaps: "inline",
  100. module: {
  101. type: "commonjs",
  102. },
  103. jsc: {
  104. parser: {
  105. syntax: "typescript",
  106. decorators: true,
  107. },
  108. transform: {
  109. decoratorMetadata: true,
  110. },
  111. },
  112. });
  113. await writeToOut(file, output.code, {
  114. inputDir,
  115. outputDir,
  116. targetExtension: ".js",
  117. });
  118. } else if (!ignoreExtensions.some((ext) => file.endsWith(ext))) {
  119. // Copy non-ts files
  120. await copyToOut(file, { inputDir, outputDir });
  121. }
  122. };
  123. const deleteOut = async (file) => {
  124. const config = { inputDir, outputDir };
  125. if (compileExtensions.some((ext) => file.endsWith(ext))) {
  126. config.targetExtension = ".js";
  127. }
  128. const outputPath = getOutputPath(file, config);
  129. await fs.unlink(outputPath);
  130. };
  131. const deleteDirInOut = async (path) => {
  132. const config = { inputDir, outputDir };
  133. const outputPath = getOutputPath(path, config);
  134. await fs.rmdir(outputPath, { recursive: true });
  135. };
  136. // Compile all files
  137. await Promise.all(files.map(async (file) => medusaTransform(file)));
  138. console.log(`Compiled all files in ${Date.now() - started}ms`);
  139. const inputPath = path.resolve(inputDir);
  140. const startMedusa = () => {
  141. const cliPath = path.resolve("node_modules", ".bin", "medusa");
  142. const child = fork(cliPath, [`start`], {
  143. cwd: process.cwd(),
  144. env: { ...process.env },
  145. });
  146. child.on("error", function (err) {
  147. console.log("Error ", err);
  148. process.exit(1);
  149. });
  150. return child;
  151. };
  152. const killMedusa = (child) => {
  153. if (process.platform === "win32") {
  154. execSync(`taskkill /PID ${child.pid} /F /T`);
  155. }
  156. child.kill("SIGINT");
  157. };
  158. let medusaChild = startMedusa();
  159. const killAndStartMedusa = () => {
  160. if (medusaChild) {
  161. killMedusa(medusaChild);
  162. }
  163. medusaChild = startMedusa();
  164. };
  165. // Debounce function
  166. const debounce = (func, wait) => {
  167. let timeout;
  168. return (...args) => {
  169. const later = () => {
  170. clearTimeout(timeout);
  171. func(...args);
  172. };
  173. clearTimeout(timeout);
  174. timeout = setTimeout(later, wait);
  175. };
  176. };
  177. // Wrap your restart function with debounce, adjust the 1000ms delay as needed
  178. const restartMedusa = debounce(killAndStartMedusa, 100);
  179. // Watch for changes
  180. const watcher = chokidar.watch(inputPath, {
  181. ignoreInitial: true,
  182. });
  183. watcher.on("all", (e, path) => {
  184. console.log(e, path);
  185. restartMedusa();
  186. });
  187. watcher.on("change", async (path) => {
  188. const now = Date.now();
  189. await medusaTransform(path);
  190. console.log(`Compiled ${path} in ${Date.now() - now}ms`);
  191. });
  192. watcher.on("add", async (path) => {
  193. const now = Date.now();
  194. await medusaTransform(path);
  195. });
  196. watcher.on("unlink", async (path) => {
  197. const now = Date.now();
  198. await deleteOut(path);
  199. });
  200. watcher.on("unlinkDir", async (path) => {
  201. const now = Date.now();
  202. await deleteDirInOut(path);
  203. });