renderTemplate.ts 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import * as fs from "node:fs";
  2. import * as path from "node:path";
  3. import nunjucks, { ConfigureOptions } from "nunjucks";
  4. import { deepMerge } from "./deepMerge";
  5. import { RenderContext } from "./getRenderContext";
  6. import { sortDependencies } from "./sortDependencies";
  7. import {
  8. camelCase,
  9. kebabCase,
  10. pascalCase,
  11. snakeCase,
  12. titleCase,
  13. } from "./strings";
  14. /**
  15. * Renders a template folder/file to the provided destination,
  16. * by recursively copying all files under the source directory,
  17. * with the following exception:
  18. * - `_filename` are renamed to `.filename`.
  19. * - `package.json` files are merged.
  20. * - `.gitignore` files are concatenated.
  21. * - `.njk` files are rendered as nunjucks templates
  22. * and saved without the `.njk` extension.
  23. */
  24. export function renderTemplate(ctx: RenderContext, src: string, dest: string) {
  25. const stats = fs.statSync(src);
  26. // Recursively render directories.
  27. if (stats.isDirectory()) {
  28. // Skip node_module.
  29. if (path.basename(src) === "node_modules") return;
  30. // Render its subdirectories and files recursively.
  31. fs.mkdirSync(dest, { recursive: true });
  32. for (const file of fs.readdirSync(src)) {
  33. renderTemplate(ctx, path.resolve(src, file), path.resolve(dest, file));
  34. }
  35. return;
  36. }
  37. const filename = path.basename(src);
  38. // Merge package.json files.
  39. if (filename === "package.json" && fs.existsSync(dest)) {
  40. const existing = JSON.parse(fs.readFileSync(dest, "utf8"));
  41. const newPackage = JSON.parse(fs.readFileSync(src, "utf8"));
  42. const pkg = sortDependencies(deepMerge(existing, newPackage));
  43. fs.writeFileSync(dest, JSON.stringify(pkg, null, 2) + "\n");
  44. return;
  45. }
  46. // Rename `_file` to `.file`.
  47. if (filename.startsWith("_")) {
  48. dest = path.resolve(path.dirname(dest), filename.replace(/^_/, "."));
  49. }
  50. // Append to existing .gitignore.
  51. if (filename === "_gitignore" && fs.existsSync(dest)) {
  52. const existing = fs.readFileSync(dest, "utf8");
  53. const newGitignore = fs.readFileSync(src, "utf8");
  54. fs.writeFileSync(dest, existing + "\n" + newGitignore);
  55. return;
  56. }
  57. // Render nunjucks templates.
  58. if (filename.endsWith(".njk")) {
  59. dest = dest.replace(/\.njk$/, "");
  60. fs.writeFileSync(dest, resolveNunjunksTemplate(src, ctx));
  61. return;
  62. }
  63. fs.copyFileSync(src, dest);
  64. }
  65. function resolveNunjunksTemplate(
  66. file: string,
  67. context?: object,
  68. options?: ConfigureOptions
  69. ): string {
  70. const directory = path.dirname(file);
  71. const filename = path.basename(file);
  72. const env = nunjucks.configure(directory, {
  73. trimBlocks: true,
  74. autoescape: false,
  75. ...options,
  76. });
  77. env.addFilter("pascalCase", pascalCase);
  78. env.addFilter("camelCase", camelCase);
  79. env.addFilter("snakeCase", snakeCase);
  80. env.addFilter("kebabCase", kebabCase);
  81. env.addFilter("titleCase", titleCase);
  82. return env.render(filename, context);
  83. }