renderTemplates.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import * as fs from 'node:fs';
  2. import * as path from 'node:path';
  3. import nunjucks, { ConfigureOptions } from 'nunjucks';
  4. import { RenderContext } from './getRenderContext';
  5. import { deepMerge, sortDependencies } from './objectHelpers';
  6. import {
  7. camelCase,
  8. kebabCase,
  9. pascalCase,
  10. snakeCase,
  11. titleCase,
  12. } from './stringHelpers';
  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. // Merge package.json files.
  38. if (filename === 'package.json' && fs.existsSync(dest)) {
  39. const existing = JSON.parse(fs.readFileSync(dest, 'utf8'));
  40. const newPackage = JSON.parse(fs.readFileSync(src, 'utf8'));
  41. const pkg = sortDependencies(deepMerge(existing, newPackage));
  42. fs.writeFileSync(dest, JSON.stringify(pkg, null, 2) + '\n');
  43. return;
  44. }
  45. // Append to existing .gitignore.
  46. if (filename === '.gitignore' && fs.existsSync(dest)) {
  47. const existing = fs.readFileSync(dest, 'utf8');
  48. const newGitignore = fs.readFileSync(src, 'utf8');
  49. fs.writeFileSync(dest, existing + '\n' + newGitignore);
  50. return;
  51. }
  52. // Render nunjucks templates.
  53. if (filename.endsWith('.njk')) {
  54. dest = dest.replace(/\.njk$/, '');
  55. fs.writeFileSync(dest, resolveNunjunksTemplate(src, ctx));
  56. fs.chmodSync(dest, stats.mode);
  57. return;
  58. }
  59. fs.copyFileSync(src, dest);
  60. }
  61. function resolveNunjunksTemplate(
  62. file: string,
  63. context?: object,
  64. options?: ConfigureOptions
  65. ): string {
  66. const directory = path.dirname(file);
  67. const filename = path.basename(file);
  68. const env = nunjucks.configure(directory, {
  69. trimBlocks: true,
  70. autoescape: false,
  71. ...options,
  72. });
  73. env.addFilter('pascalCase', pascalCase);
  74. env.addFilter('camelCase', camelCase);
  75. env.addFilter('snakeCase', snakeCase);
  76. env.addFilter('kebabCase', kebabCase);
  77. env.addFilter('titleCase', titleCase);
  78. return env.render(filename, context);
  79. }