renderTemplates.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 { RenderContext } from './renderContext';
  5. import { deepMerge, sortDependencies } from './objects';
  6. import {
  7. camelCase,
  8. kebabCase,
  9. pascalCase,
  10. snakeCase,
  11. titleCase,
  12. } from './strings';
  13. /**
  14. * Renders a template folder/file to the provided destination,
  15. * by recursively copying all files under the source directory,
  16. * with the following exception:
  17. * - `_.filename` are renamed to `.filename`.
  18. * - `package.json` files are merged.
  19. * - `.gitignore` files are concatenated.
  20. * - `.njk` files are rendered as nunjucks templates
  21. * and saved without the `.njk` extension.
  22. */
  23. export function renderTemplate(ctx: RenderContext, src: string, dest: string) {
  24. const stats = fs.statSync(src);
  25. // Recursively render directories.
  26. if (stats.isDirectory()) {
  27. // Skip node_module.
  28. if (path.basename(src) === 'node_modules') return;
  29. // Render its subdirectories and files recursively.
  30. fs.mkdirSync(dest, { recursive: true });
  31. for (const file of fs.readdirSync(src)) {
  32. renderTemplate(ctx, path.resolve(src, file), path.resolve(dest, file));
  33. }
  34. return;
  35. }
  36. const filename = path.basename(src);
  37. // Rename `_.file` to `.file` in the destination.
  38. if (filename.startsWith('_.')) {
  39. dest = path.resolve(path.dirname(dest), filename.replace(/^_./, '.'));
  40. }
  41. // Concatenate .gitignore files.
  42. if (filename === '_.gitignore' && fs.existsSync(dest)) {
  43. const existing = fs.readFileSync(dest, 'utf8');
  44. const newGitignore = fs.readFileSync(src, 'utf8');
  45. fs.writeFileSync(dest, existing + '\n' + newGitignore);
  46. return;
  47. }
  48. // Merge package.json files.
  49. if (filename === 'package.json' && fs.existsSync(dest)) {
  50. const existing = JSON.parse(fs.readFileSync(dest, 'utf8'));
  51. const newPackage = JSON.parse(fs.readFileSync(src, 'utf8'));
  52. const pkg = sortDependencies(deepMerge(existing, newPackage));
  53. fs.writeFileSync(dest, JSON.stringify(pkg, null, 2) + '\n');
  54. return;
  55. }
  56. // Render nunjucks templates.
  57. if (filename.endsWith('.njk')) {
  58. dest = dest.replace(/\.njk$/, '');
  59. fs.writeFileSync(dest, resolveNunjunksTemplate(src, ctx));
  60. fs.chmodSync(dest, stats.mode);
  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. }