import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import yaml from 'yaml'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const generatedDir = path.resolve(__dirname, '../src/kickflow-api/generated')
const schemaPath = path.resolve(__dirname, '../schema.yaml')
const nameMapping = new Map([
['カテゴリ', 'category'],
['コメント', 'comment'],
['チーム', 'team'],
['チケット', 'ticket'],
['ファイル', 'file'],
['フォルダ', 'folder'],
['ユーザー', 'user'],
['ワークフロー', 'workflow'],
['監査ログ', 'audit-log'],
['管理者ロール', 'role'],
['経路', 'route'],
['組織図', 'organization-chart'],
['代理承認', 'proxy-approver'],
['代理申請', 'proxy-applicant'],
['汎用マスタ', 'general-master'],
['役職', 'grade'],
])
function renameItems(dirPath) {
try {
const items = fs.readdirSync(dirPath)
items.forEach((item) => {
const oldPath = path.join(dirPath, item)
// Check if the path exists before stating
if (!fs.existsSync(oldPath)) {
console.warn(`Path does not exist, skipping: ${oldPath}`)
return
}
const stats = fs.statSync(oldPath)
let lookupName
let fileSuffix = ''
if (stats.isDirectory()) {
lookupName = item
} else {
const firstDotIndex = item.indexOf('.')
if (firstDotIndex === -1) {
// ファイル名にドットがない場合 (通常はありえないが念のため)
lookupName = item
fileSuffix = ''
} else {
lookupName = item.substring(0, firstDotIndex)
fileSuffix = item.substring(firstDotIndex) // .zod.ts などを含む
}
}
if (nameMapping.has(lookupName)) {
const newBaseName = nameMapping.get(lookupName)
const newName = stats.isDirectory()
? newBaseName
: `${newBaseName}${fileSuffix}`
const newPath = path.join(dirPath, newName)
// Avoid renaming to itself or if new path already exists unexpectedly
if (oldPath !== newPath) {
if (fs.existsSync(newPath)) {
console.warn(
`Target path already exists, skipping rename: ${newPath}`,
)
// If it's a directory that already exists, still try to process its contents
if (stats.isDirectory()) {
renameItems(newPath) // Process existing directory
}
} else {
console.log(`Renaming ${oldPath} to ${newPath}`)
fs.renameSync(oldPath, newPath)
// If it was a directory that got renamed, process its contents in the new path
if (stats.isDirectory()) {
renameItems(newPath)
}
}
} else if (stats.isDirectory()) {
// If oldPath equals newPath, and it's a directory, process its contents
renameItems(oldPath)
}
} else if (stats.isDirectory()) {
// If it's a directory but not in mapping, still process its contents
renameItems(oldPath)
}
})
} catch (error) {
console.error(`Error processing directory ${dirPath}:`, error)
}
}
function extractPathParamInfo(param) {
const info = {
name: param.name,
type: param.schema?.type || 'string',
required: true,
}
if (param.description) info.description = param.description
if (param.schema?.format) info.format = param.schema.format
if (param.schema?.pattern) info.pattern = param.schema.pattern
return info
}
function generateApiDefinitions() {
console.log('Generating api-definitions.ts...')
const schemaContent = fs.readFileSync(schemaPath, 'utf-8')
const spec = yaml.parse(schemaContent)
const apis = []
const methods = ['get', 'post', 'put', 'patch', 'delete']
for (const [, pathItem] of Object.entries(spec.paths)) {
const sharedPathParams =
pathItem.parameters?.filter((p) => p.in === 'path') || []
for (const method of methods) {
const operation = pathItem[method]
if (!operation?.operationId) continue
const operationPathParams =
operation.parameters?.filter((p) => p.in === 'path') || []
const allPathParams = [...sharedPathParams, ...operationPathParams]
const seenNames = new Set()
const pathParams = []
for (const param of allPathParams) {
if (!seenNames.has(param.name)) {
seenNames.add(param.name)
pathParams.push(extractPathParamInfo(param))
}
}
apis.push({
operationId: operation.operationId,
summary: operation.summary || '',
pathParams,
})
}
}
const code = `/**
* Generated by orval-hook.js
* Do not edit manually.
*/
export interface PathParamInfo {
name: string
type: string
required: boolean
description?: string
format?: string
pattern?: string
}
export interface ApiDefinition {
operationId: string
summary: string
pathParams: PathParamInfo[]
}
export const apiDefinitions: ApiDefinition[] = ${JSON.stringify(apis, null, 2)}
export function findApiByOperationId(operationId: string): ApiDefinition | undefined {
return apiDefinitions.find((api) => api.operationId === operationId)
}
`
const outputPath = path.join(generatedDir, 'api-definitions.ts')
fs.writeFileSync(outputPath, code, 'utf-8')
console.log(`Generated ${outputPath} with ${apis.length} APIs`)
}
function generateZodSchemasIndex() {
console.log('Generating zod-schemas.ts...')
const categories = [...nameMapping.values()]
const importStatements = []
const exportEntries = []
for (const category of categories) {
const zodFilePath = path.join(generatedDir, category, `${category}.zod.ts`)
if (fs.existsSync(zodFilePath)) {
const varName = category.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
importStatements.push(
`import * as ${varName} from './${category}/${category}.zod.js'`,
)
exportEntries.push(` ...${varName},`)
}
}
const code = `/**
* Generated by orval-hook.js
* Do not edit manually.
*/
${importStatements.join('\n')}
export const allZodSchemas: Record<string, unknown> = {
${exportEntries.join('\n')}
}
`
const outputPath = path.join(generatedDir, 'zod-schemas.ts')
fs.writeFileSync(outputPath, code, 'utf-8')
console.log(`Generated ${outputPath}`)
}
console.log(`Starting rename process in ${generatedDir}...`)
renameItems(generatedDir)
console.log('Rename process completed.')
generateApiDefinitions()
generateZodSchemasIndex()