develop.mjs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. let dirNameRegex = new RegExp("\\" + path.sep + "([^\\" + path.sep + "]+)$");
  91. await fs.mkdir(outputPath.replace(dirNameRegex, ""), { recursive: true });
  92. await fs.copyFile(file, outputPath);
  93. };
  94. const medusaTransform = async (file) => {
  95. if (file.includes("__tests__")) {
  96. return;
  97. }
  98. if (compileExtensions.some((ext) => file.endsWith(ext))) {
  99. const output = await swc.transformFile(file, {
  100. sourceMaps: "inline",
  101. module: {
  102. type: "commonjs",
  103. },
  104. jsc: {
  105. parser: {
  106. syntax: "typescript",
  107. decorators: true,
  108. },
  109. transform: {
  110. decoratorMetadata: true,
  111. },
  112. },
  113. });
  114. await writeToOut(file, output.code, {
  115. inputDir,
  116. outputDir,
  117. targetExtension: ".js",
  118. });
  119. } else if (!ignoreExtensions.some((ext) => file.endsWith(ext))) {
  120. // Copy non-ts files
  121. await copyToOut(file, { inputDir, outputDir });
  122. }
  123. };
  124. const deleteOut = async (file) => {
  125. const config = { inputDir, outputDir };
  126. if (compileExtensions.some((ext) => file.endsWith(ext))) {
  127. config.targetExtension = ".js";
  128. }
  129. const outputPath = getOutputPath(file, config);
  130. await fs.unlink(outputPath);
  131. };
  132. const deleteDirInOut = async (path) => {
  133. const config = { inputDir, outputDir };
  134. const outputPath = getOutputPath(path, config);
  135. await fs.rmdir(outputPath, { recursive: true });
  136. };
  137. // Compile all files
  138. await Promise.all(files.map(async (file) => medusaTransform(file)));
  139. console.log(`Compiled all files in ${Date.now() - started}ms`);
  140. const inputPath = path.resolve(inputDir);
  141. const startMedusa = () => {
  142. const cliPath = path.resolve("node_modules", ".bin", "medusa");
  143. const child = fork(cliPath, [`start`], {
  144. cwd: process.cwd(),
  145. env: { ...process.env },
  146. });
  147. child.on("error", function (err) {
  148. console.log("Error ", err);
  149. process.exit(1);
  150. });
  151. return child;
  152. };
  153. const killMedusa = (child) => {
  154. if (process.platform === "win32") {
  155. execSync(`taskkill /PID ${child.pid} /F /T`);
  156. }
  157. child.kill("SIGINT");
  158. };
  159. let medusaChild = startMedusa();
  160. const killAndStartMedusa = () => {
  161. if (medusaChild) {
  162. killMedusa(medusaChild);
  163. }
  164. medusaChild = startMedusa();
  165. };
  166. // Debounce function
  167. const debounce = (func, wait) => {
  168. let timeout;
  169. return (...args) => {
  170. const later = () => {
  171. clearTimeout(timeout);
  172. func(...args);
  173. };
  174. clearTimeout(timeout);
  175. timeout = setTimeout(later, wait);
  176. };
  177. };
  178. // Wrap your restart function with debounce, adjust the 1000ms delay as needed
  179. const restartMedusa = debounce(killAndStartMedusa, 100);
  180. // Watch for changes
  181. const watcher = chokidar.watch(inputPath, {
  182. ignoreInitial: true,
  183. });
  184. watcher.on("all", (e, path) => {
  185. console.log(e, path);
  186. restartMedusa();
  187. });
  188. watcher.on("change", async (path) => {
  189. const now = Date.now();
  190. await medusaTransform(path);
  191. console.log(`Compiled ${path} in ${Date.now() - now}ms`);
  192. });
  193. watcher.on("add", async (path) => {
  194. const now = Date.now();
  195. await medusaTransform(path);
  196. });
  197. watcher.on("unlink", async (path) => {
  198. const now = Date.now();
  199. await deleteOut(path);
  200. });
  201. watcher.on("unlinkDir", async (path) => {
  202. const now = Date.now();
  203. await deleteDirInOut(path);
  204. });