You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 15:31:57