EJS集成json-schema-to-typescript时renameSync报ENOENT错误
问题背景
目标是将JSON Schema转换为TypeScript代码并写入文件。
常规场景下直接使用json-schema-to-typescript即可完成单步转换,但该步骤只是大型代码生成程序的其中一个环节,需要适配现有工作流:先通过EJS渲染文件,经prettier格式化后写入临时目录,待所有文件处理完成后再统一移动到最终目录(prettier是流程固有组成部分,并非问题来源)。
当前执行renameSync时抛出如下错误:
Error: ENOENT: no such file or directory, rename 'tempfile' -> 'file.ts'
以下为可复现问题的最小代码,共包含3个文件:index.ts、file.ejs、interface.ejs。
index.ts
import { writeFileSync, renameSync } from "fs"; import ejs from "ejs"; import { compile } from "json-schema-to-typescript"; import prettier from "prettier"; const schemas = [ { type: "object", properties: { id: { type: "integer", format: "int64", }, }, modelName: "thingOne", }, ]; export function convertJsonSchemaToTypescript(jsonSchema: any) { return compile(jsonSchema, jsonSchema.modelName); } function renderMyFile(fileIn: string, fileOut: string) { ejs.renderFile( fileIn, { schemas, convertJsonSchemaToTypescript, // 实际业务中会传入大量数据及数据操作方法,由模板决定调用逻辑,此处仅做最小复现 }, { async: true }, async (err, renderedFileAsAString) => { if (err) { throw err; } const prettierRenderedString = prettier.format( await renderedFileAsAString, { parser: "typescript" } ); writeFileSync(fileOut, await prettierRenderedString); } ); } renderMyFile("file.ejs", "tempfile"); // 模拟业务逻辑:所有文件写入临时目录,全部生成完成后移动到最终目录 renameSync("tempfile", "file.ts");
file.ejs
<% for ( let i=0; i < schemas.length; i++) { %> <%- await include("interface", { schema: schemas[i] }) %> <% } %>
interface.ejs
<%- await convertJsonSchemaToTypescript(schema) %>
问题根因
报错本质是异步执行时序错误:
- 配置
{async: true}后,ejs.renderFile的渲染流程、后续的prettier格式化、临时文件写入逻辑全部在异步回调中执行,不会阻塞后续代码运行 - 调用
renderMyFile后会立刻同步执行renameSync,此时异步回调还没触发,tempfile尚未被写入磁盘,自然会抛出文件不存在的ENOENT错误。
修复方案
调整执行时序,等所有文件渲染、写入临时目录的逻辑全部完成后,再执行批量移动到最终目录的操作:
- 单文件场景:将渲染逻辑包装为Promise,等待临时文件写入完成后再执行重命名
- 多文件场景:使用
Promise.all等待所有文件的渲染写入任务全部完成,再统一执行移动操作,完全匹配现有工作流设计
核心修改后的代码如下:
import { writeFileSync, renameSync } from "fs"; import ejs from "ejs"; import { compile } from "json-schema-to-typescript"; import prettier from "prettier"; const schemas = [ { type: "object", properties: { id: { type: "integer", format: "int64", }, }, modelName: "thingOne", }, ]; export function convertJsonSchemaToTypescript(jsonSchema: any) { return compile(jsonSchema, jsonSchema.modelName); } function renderMyFile(fileIn: string, fileOut: string) { // 将异步渲染逻辑包装为Promise return new Promise((resolve, reject) => { ejs.renderFile( fileIn, { schemas, convertJsonSchemaToTypescript, }, { async: true }, async (err, renderedFileAsAString) => { if (err) return reject(err); try { const prettierRenderedString = prettier.format( await renderedFileAsAString, { parser: "typescript" } ); writeFileSync(fileOut, prettierRenderedString); resolve(true); } catch (e) { reject(e); } } ); }); } // 入口函数,等待所有异步操作完成后再执行文件移动 async function main() { await renderMyFile("file.ejs", "tempfile"); // 多文件场景替换为:await Promise.all(所有renderMyFile任务) renameSync("tempfile", "file.ts"); } main().catch(err => { console.error("代码生成失败:", err); process.exit(1); });
内容的提问来源于stack exchange,提问作者Justin
相关产品推荐
相关产品推荐

