更新是否生成generated/index.js
This commit is contained in:
parent
57a2492408
commit
1fa9232c57
28
README.md
28
README.md
|
|
@ -76,6 +76,10 @@ export default {
|
||||||
// 相对路径会基于当前配置文件所在目录解析。
|
// 相对路径会基于当前配置文件所在目录解析。
|
||||||
outputDir: 'src/api/aixue/generated',
|
outputDir: 'src/api/aixue/generated',
|
||||||
|
|
||||||
|
// 是否生成 outputDir/index.js。
|
||||||
|
// 关闭后只保留 shared.js 和各模块文件;sync / gen 也会默认不再同步。
|
||||||
|
generateIndexFile: true,
|
||||||
|
|
||||||
// 由 `sync` / `gen` 维护的外部 API 入口文件。
|
// 由 `sync` / `gen` 维护的外部 API 入口文件。
|
||||||
externalIndexFile: 'src/api/aixue/index.js',
|
externalIndexFile: 'src/api/aixue/index.js',
|
||||||
|
|
||||||
|
|
@ -92,6 +96,7 @@ export default {
|
||||||
|
|
||||||
sync: {
|
sync: {
|
||||||
// 如果你只想生成文件、不想改 externalIndexFile,可以设为 false。
|
// 如果你只想生成文件、不想改 externalIndexFile,可以设为 false。
|
||||||
|
// 当 generateIndexFile=false 时,这个开关默认也会变成 false。
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
||||||
// 是否在受管区块中附带 generated/index.js 的注释快照。
|
// 是否在受管区块中附带 generated/index.js 的注释快照。
|
||||||
|
|
@ -118,8 +123,8 @@ npx yx-generate-api gen
|
||||||
|
|
||||||
1. 拉取 `swaggerUrl`
|
1. 拉取 `swaggerUrl`
|
||||||
2. 在 `outputDir` 下生成 API 文件
|
2. 在 `outputDir` 下生成 API 文件
|
||||||
3. 生成 `outputDir/index.js`
|
3. 如果 `generateIndexFile=true`,生成 `outputDir/index.js`
|
||||||
4. 把导出同步到 `externalIndexFile`
|
4. 如果 `sync.enabled=true`,把导出同步到 `externalIndexFile`
|
||||||
|
|
||||||
## 一个完整的日常流程
|
## 一个完整的日常流程
|
||||||
|
|
||||||
|
|
@ -156,6 +161,8 @@ npx yx-generate-api sync
|
||||||
生成目录。默认是 `src/api/generated`。
|
生成目录。默认是 `src/api/generated`。
|
||||||
- `externalIndexFile`
|
- `externalIndexFile`
|
||||||
业务侧统一导出文件,例如 `src/api/index.js`。如果不填,默认不会执行同步。
|
业务侧统一导出文件,例如 `src/api/index.js`。如果不填,默认不会执行同步。
|
||||||
|
- `generateIndexFile`
|
||||||
|
是否生成 `outputDir/index.js`。默认 `true`。设为 `false` 后,工具只生成 `shared.js` 和各模块文件;`sync.enabled` 的默认值也会随之变成 `false`。
|
||||||
- `requestImport`
|
- `requestImport`
|
||||||
生成模块里 `import request from '...'` 的路径。它应该相对于每个生成出来的模块文件。
|
生成模块里 `import request from '...'` 的路径。它应该相对于每个生成出来的模块文件。
|
||||||
- `paramStyle`
|
- `paramStyle`
|
||||||
|
|
@ -168,7 +175,7 @@ npx yx-generate-api sync
|
||||||
### sync 配置
|
### sync 配置
|
||||||
|
|
||||||
- `sync.enabled`
|
- `sync.enabled`
|
||||||
是否启用同步。默认值等于 `Boolean(externalIndexFile)`。
|
是否启用同步。默认值等于 `Boolean(externalIndexFile && generateIndexFile)`。
|
||||||
- `sync.blockStart`
|
- `sync.blockStart`
|
||||||
受管区块开始标记。
|
受管区块开始标记。
|
||||||
- `sync.blockEnd`
|
- `sync.blockEnd`
|
||||||
|
|
@ -348,6 +355,21 @@ import { curriculumApi } from '@/api/aixue/generated'
|
||||||
import { getCurriculumListApi } from '@/api/aixue/generated'
|
import { getCurriculumListApi } from '@/api/aixue/generated'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
如果你不需要这个聚合入口,也可以这样关闭:
|
||||||
|
|
||||||
|
```js
|
||||||
|
generateIndexFile: false
|
||||||
|
```
|
||||||
|
|
||||||
|
关闭后会有这些变化:
|
||||||
|
|
||||||
|
- 不再生成 `generated/index.js`
|
||||||
|
- 旧的自动生成 `generated/index.js` 会在下次生成时被移除
|
||||||
|
- 不再对“跨模块扁平导出重名”做 `generated/index.js` 级别的冲突校验
|
||||||
|
- `sync.enabled` 默认会变成 `false`
|
||||||
|
|
||||||
|
如果你又显式把 `sync.enabled` 设成 `true`,命令会直接报错,因为 `sync` 依赖 `generated/index.js`
|
||||||
|
|
||||||
需要注意:
|
需要注意:
|
||||||
|
|
||||||
- 如果不同模块里恰好生成了同名函数,例如都生成了 `getListApi`
|
- 如果不同模块里恰好生成了同名函数,例如都生成了 `getListApi`
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export const runGenCommand = async (args) => {
|
||||||
projectRoot: context.projectConfig.rootDir,
|
projectRoot: context.projectConfig.rootDir,
|
||||||
outputDir: context.runtimeConfig.outputDir,
|
outputDir: context.runtimeConfig.outputDir,
|
||||||
externalIndexFile: context.projectConfig.externalIndexFile,
|
externalIndexFile: context.projectConfig.externalIndexFile,
|
||||||
|
generateIndexFile: context.runtimeConfig.generateIndexFile,
|
||||||
syncOptions: context.projectConfig.sync,
|
syncOptions: context.projectConfig.sync,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ export const resolveGenerateCommandContext = async (args) => {
|
||||||
outputDir: getFlagValue(parsedArgs.flags, 'outDir')
|
outputDir: getFlagValue(parsedArgs.flags, 'outDir')
|
||||||
? resolveCwdPath(process.cwd(), getFlagValue(parsedArgs.flags, 'outDir'))
|
? resolveCwdPath(process.cwd(), getFlagValue(parsedArgs.flags, 'outDir'))
|
||||||
: projectConfig.outputDir,
|
: projectConfig.outputDir,
|
||||||
|
generateIndexFile: projectConfig.generateIndexFile,
|
||||||
requestImport: getFlagValue(parsedArgs.flags, 'requestImport') || projectConfig.requestImport,
|
requestImport: getFlagValue(parsedArgs.flags, 'requestImport') || projectConfig.requestImport,
|
||||||
paramStyle: getFlagValue(parsedArgs.flags, 'paramStyle')
|
paramStyle: getFlagValue(parsedArgs.flags, 'paramStyle')
|
||||||
? normalizeParamStyle(getFlagValue(parsedArgs.flags, 'paramStyle'))
|
? normalizeParamStyle(getFlagValue(parsedArgs.flags, 'paramStyle'))
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export const runSyncCommand = async (args) => {
|
||||||
projectRoot: projectConfig.rootDir,
|
projectRoot: projectConfig.rootDir,
|
||||||
outputDir: projectConfig.outputDir,
|
outputDir: projectConfig.outputDir,
|
||||||
externalIndexFile: projectConfig.externalIndexFile,
|
externalIndexFile: projectConfig.externalIndexFile,
|
||||||
|
generateIndexFile: projectConfig.generateIndexFile,
|
||||||
syncOptions: projectConfig.sync,
|
syncOptions: projectConfig.sync,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,13 @@ export const loadProjectConfig = async ({ configPath, cwd = process.cwd() } = {}
|
||||||
outputDir: rawConfig.outputDir
|
outputDir: rawConfig.outputDir
|
||||||
? resolveProjectPath(rootDir, rawConfig.outputDir)
|
? resolveProjectPath(rootDir, rawConfig.outputDir)
|
||||||
: resolveProjectPath(rootDir, 'src/api/generated'),
|
: resolveProjectPath(rootDir, 'src/api/generated'),
|
||||||
|
generateIndexFile: rawConfig.generateIndexFile ?? true,
|
||||||
externalIndexFile,
|
externalIndexFile,
|
||||||
requestImport: normalizeString(rawConfig.requestImport) || '../request',
|
requestImport: normalizeString(rawConfig.requestImport) || '../request',
|
||||||
paramStyle: normalizeParamStyle(rawConfig.paramStyle || PARAM_STYLE.OBJECT),
|
paramStyle: normalizeParamStyle(rawConfig.paramStyle || PARAM_STYLE.OBJECT),
|
||||||
cleanOutput: rawConfig.cleanOutput ?? true,
|
cleanOutput: rawConfig.cleanOutput ?? true,
|
||||||
sync: {
|
sync: {
|
||||||
enabled: syncConfig.enabled ?? Boolean(externalIndexFile),
|
enabled: syncConfig.enabled ?? Boolean(externalIndexFile && (rawConfig.generateIndexFile ?? true)),
|
||||||
blockStart: normalizeString(syncConfig.blockStart) || DEFAULT_SYNC_OPTIONS.blockStart,
|
blockStart: normalizeString(syncConfig.blockStart) || DEFAULT_SYNC_OPTIONS.blockStart,
|
||||||
blockEnd: normalizeString(syncConfig.blockEnd) || DEFAULT_SYNC_OPTIONS.blockEnd,
|
blockEnd: normalizeString(syncConfig.blockEnd) || DEFAULT_SYNC_OPTIONS.blockEnd,
|
||||||
includeGeneratedIndexSnapshot:
|
includeGeneratedIndexSnapshot:
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ export const generateApiFiles = async ({
|
||||||
swaggerUrl,
|
swaggerUrl,
|
||||||
swaggerTimeoutMs,
|
swaggerTimeoutMs,
|
||||||
outputDir,
|
outputDir,
|
||||||
|
generateIndexFile: shouldGenerateIndexFile = true,
|
||||||
requestImport,
|
requestImport,
|
||||||
paramStyle,
|
paramStyle,
|
||||||
modules,
|
modules,
|
||||||
|
|
@ -72,73 +73,80 @@ export const generateApiFiles = async ({
|
||||||
const selectedModules = resolveSelectedModules(moduleMap, modules)
|
const selectedModules = resolveSelectedModules(moduleMap, modules)
|
||||||
const selectedModuleFileNames = new Set(selectedModules.map((moduleInfo) => `${moduleInfo.fileName}.js`))
|
const selectedModuleFileNames = new Set(selectedModules.map((moduleInfo) => `${moduleInfo.fileName}.js`))
|
||||||
const shouldRemoveStaleGeneratedFiles = cleanOutput && modules.length === 0
|
const shouldRemoveStaleGeneratedFiles = cleanOutput && modules.length === 0
|
||||||
const retainedModuleExports = await readRetainedModuleExports({
|
const retainedModuleExports = shouldGenerateIndexFile
|
||||||
outputDir,
|
? await readRetainedModuleExports({
|
||||||
skipFileNames: selectedModuleFileNames,
|
outputDir,
|
||||||
skipRemovableAutoGeneratedFiles: shouldRemoveStaleGeneratedFiles,
|
skipFileNames: selectedModuleFileNames,
|
||||||
})
|
skipRemovableAutoGeneratedFiles: shouldRemoveStaleGeneratedFiles,
|
||||||
const reservedNamespaceFunctionNames = new Set(
|
})
|
||||||
[...selectedModules.map((moduleInfo) => moduleInfo.fileName), ...retainedModuleExports.map((item) => item.fileName)].map(
|
: []
|
||||||
(fileName) => toCamelCase(fileName),
|
const reservedNamespaceFunctionNames = shouldGenerateIndexFile
|
||||||
),
|
? new Set(
|
||||||
)
|
[
|
||||||
|
...selectedModules.map((moduleInfo) => moduleInfo.fileName),
|
||||||
|
...retainedModuleExports.map((item) => item.fileName),
|
||||||
|
].map((fileName) => toCamelCase(fileName)),
|
||||||
|
)
|
||||||
|
: new Set()
|
||||||
|
|
||||||
for (const moduleInfo of selectedModules) {
|
for (const moduleInfo of selectedModules) {
|
||||||
assignFunctionNames(moduleInfo.operations, reservedNamespaceFunctionNames)
|
assignFunctionNames(moduleInfo.operations, reservedNamespaceFunctionNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
const namespaceConflicts = findNamespaceExportConflicts([
|
if (shouldGenerateIndexFile) {
|
||||||
...buildPlannedModuleExports(selectedModules),
|
const namespaceConflicts = findNamespaceExportConflicts([
|
||||||
...retainedModuleExports,
|
...buildPlannedModuleExports(selectedModules),
|
||||||
])
|
...retainedModuleExports,
|
||||||
|
])
|
||||||
|
|
||||||
if (namespaceConflicts.length) {
|
if (namespaceConflicts.length) {
|
||||||
throw createDuplicateModuleExportsError(namespaceConflicts, {
|
throw createDuplicateModuleExportsError(namespaceConflicts, {
|
||||||
reason: 'namespace-conflict',
|
reason: 'namespace-conflict',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateConflicts = findDuplicateModuleExports([
|
const duplicateConflicts = findDuplicateModuleExports([
|
||||||
...buildPlannedModuleExports(selectedModules),
|
...buildPlannedModuleExports(selectedModules),
|
||||||
...retainedModuleExports,
|
...retainedModuleExports,
|
||||||
])
|
])
|
||||||
|
|
||||||
if (duplicateConflicts.length) {
|
if (duplicateConflicts.length) {
|
||||||
const resolution = await resolveDuplicateExports?.({
|
const resolution = await resolveDuplicateExports?.({
|
||||||
conflicts: duplicateConflicts,
|
conflicts: duplicateConflicts,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (resolution?.action === 'rename') {
|
if (resolution?.action === 'rename') {
|
||||||
const renameEntries = applyDuplicateExportRenameStrategy(selectedModules, duplicateConflicts)
|
const renameEntries = applyDuplicateExportRenameStrategy(selectedModules, duplicateConflicts)
|
||||||
const remainingNamespaceConflicts = findNamespaceExportConflicts([
|
const remainingNamespaceConflicts = findNamespaceExportConflicts([
|
||||||
...buildPlannedModuleExports(selectedModules),
|
...buildPlannedModuleExports(selectedModules),
|
||||||
...retainedModuleExports,
|
...retainedModuleExports,
|
||||||
])
|
])
|
||||||
const remainingConflicts = findDuplicateModuleExports([
|
const remainingConflicts = findDuplicateModuleExports([
|
||||||
...buildPlannedModuleExports(selectedModules),
|
...buildPlannedModuleExports(selectedModules),
|
||||||
...retainedModuleExports,
|
...retainedModuleExports,
|
||||||
])
|
])
|
||||||
|
|
||||||
if (remainingNamespaceConflicts.length || remainingConflicts.length) {
|
if (remainingNamespaceConflicts.length || remainingConflicts.length) {
|
||||||
throw createDuplicateModuleExportsError(remainingConflicts, {
|
throw createDuplicateModuleExportsError(remainingConflicts, {
|
||||||
reason: 'rename-failed',
|
reason: 'rename-failed',
|
||||||
supplementalConflicts: remainingNamespaceConflicts,
|
supplementalConflicts: remainingNamespaceConflicts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renameEntries.length) {
|
||||||
|
console.log('resolved duplicate exports by appending module names:')
|
||||||
|
|
||||||
|
for (const renameEntry of renameEntries) {
|
||||||
|
console.log(
|
||||||
|
` ${renameEntry.from} -> ${renameEntry.to} (${renameEntry.moduleName}${renameEntry.path ? ` | ${renameEntry.path}` : ''})`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw createDuplicateModuleExportsError(duplicateConflicts, {
|
||||||
|
reason: resolution?.action === 'abort' ? 'user-aborted' : 'unresolved',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renameEntries.length) {
|
|
||||||
console.log('resolved duplicate exports by appending module names:')
|
|
||||||
|
|
||||||
for (const renameEntry of renameEntries) {
|
|
||||||
console.log(
|
|
||||||
` ${renameEntry.from} -> ${renameEntry.to} (${renameEntry.moduleName}${renameEntry.path ? ` | ${renameEntry.path}` : ''})`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw createDuplicateModuleExportsError(duplicateConflicts, {
|
|
||||||
reason: resolution?.action === 'abort' ? 'user-aborted' : 'unresolved',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,14 +185,23 @@ export const generateApiFiles = async ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexFilePath = path.join(outputDir, DEFAULT_INDEX_FILE_NAME)
|
const indexFilePath = path.join(outputDir, DEFAULT_INDEX_FILE_NAME)
|
||||||
await writeGeneratedFile(
|
|
||||||
indexFilePath,
|
if (shouldGenerateIndexFile) {
|
||||||
await generateIndexFile({
|
await writeGeneratedFile(
|
||||||
outDir: outputDir,
|
indexFilePath,
|
||||||
swaggerUrl,
|
await generateIndexFile({
|
||||||
}),
|
outDir: outputDir,
|
||||||
)
|
swaggerUrl,
|
||||||
console.log(`generated: ${path.relative(projectRoot, indexFilePath)}`)
|
}),
|
||||||
|
)
|
||||||
|
console.log(`generated: ${path.relative(projectRoot, indexFilePath)}`)
|
||||||
|
} else {
|
||||||
|
await removeAutoGeneratedIndexFile({
|
||||||
|
indexFilePath,
|
||||||
|
projectRoot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`done: ${selectedModules.length} module(s)`)
|
console.log(`done: ${selectedModules.length} module(s)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +290,25 @@ const cleanStaleGeneratedFiles = async ({ outputDir, nextModuleFileNames, projec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeAutoGeneratedIndexFile = async ({ indexFilePath, projectRoot }) => {
|
||||||
|
try {
|
||||||
|
const content = await fs.readFile(indexFilePath, 'utf8')
|
||||||
|
|
||||||
|
if (!content.startsWith(AUTO_GENERATED_BANNER)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.rm(indexFilePath)
|
||||||
|
console.log(`removed: ${path.relative(projectRoot, indexFilePath)}`)
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.code === 'ENOENT') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const readRetainedModuleExports = async ({
|
const readRetainedModuleExports = async ({
|
||||||
outputDir,
|
outputDir,
|
||||||
skipFileNames = new Set(),
|
skipFileNames = new Set(),
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,19 @@ export const syncExternalIndex = async ({
|
||||||
projectRoot,
|
projectRoot,
|
||||||
outputDir,
|
outputDir,
|
||||||
externalIndexFile,
|
externalIndexFile,
|
||||||
|
generateIndexFile = true,
|
||||||
syncOptions,
|
syncOptions,
|
||||||
}) => {
|
}) => {
|
||||||
if (!externalIndexFile) {
|
if (!externalIndexFile) {
|
||||||
throw new Error('externalIndexFile is required for sync')
|
throw new Error('externalIndexFile is required for sync')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!generateIndexFile) {
|
||||||
|
throw new Error(
|
||||||
|
'sync requires generated/index.js, but generateIndexFile=false. Set generateIndexFile=true or sync.enabled=false.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const generatedIndexPath = path.join(outputDir, 'index.js')
|
const generatedIndexPath = path.join(outputDir, 'index.js')
|
||||||
const [generatedContent, targetContent] = await Promise.all([
|
const [generatedContent, targetContent] = await Promise.all([
|
||||||
fs.readFile(generatedIndexPath, 'utf8'),
|
fs.readFile(generatedIndexPath, 'utf8'),
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ export default {
|
||||||
// 相对路径会基于当前配置文件所在目录解析。
|
// 相对路径会基于当前配置文件所在目录解析。
|
||||||
outputDir: 'src/api/generated',
|
outputDir: 'src/api/generated',
|
||||||
|
|
||||||
|
// 是否生成 outputDir/index.js。
|
||||||
|
// 关闭后只保留 shared.js 和各模块文件;sync / gen 也会默认不再同步。
|
||||||
|
generateIndexFile: true,
|
||||||
|
|
||||||
// 由 `sync` / `gen` 维护的外部 API 入口文件。
|
// 由 `sync` / `gen` 维护的外部 API 入口文件。
|
||||||
externalIndexFile: 'src/api/index.js',
|
externalIndexFile: 'src/api/index.js',
|
||||||
|
|
||||||
|
|
@ -26,6 +30,7 @@ export default {
|
||||||
|
|
||||||
sync: {
|
sync: {
|
||||||
// 如果你只想生成文件、不想改 externalIndexFile,可以设为 false。
|
// 如果你只想生成文件、不想改 externalIndexFile,可以设为 false。
|
||||||
|
// 当 generateIndexFile=false 时,这个开关默认也会变成 false。
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
||||||
// 是否在受管区块中附带 generated/index.js 的注释快照。
|
// 是否在受管区块中附带 generated/index.js 的注释快照。
|
||||||
|
|
|
||||||
|
|
@ -148,3 +148,97 @@ test('cli gen reports duplicate export conflicts with module and url details in
|
||||||
await fs.rm(tempDir, { recursive: true, force: true })
|
await fs.rm(tempDir, { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('cli gen skips sync by default when generateIndexFile is false', async () => {
|
||||||
|
const tempDir = await createTempDir()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const swaggerPath = path.join(tempDir, 'swagger.json')
|
||||||
|
const configPath = path.join(tempDir, 'yx-generate-api.config.mjs')
|
||||||
|
const outputDir = path.join(tempDir, 'generated')
|
||||||
|
const externalIndexFile = path.join(tempDir, 'api-index.js')
|
||||||
|
const cliPath = path.resolve('bin/yx-generate-api.js')
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
swaggerPath,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
openapi: '3.0.0',
|
||||||
|
paths: {
|
||||||
|
'/api/v1/Alpha/GetThing': {
|
||||||
|
get: {
|
||||||
|
tags: ['Alpha'],
|
||||||
|
responses: {
|
||||||
|
200: { description: 'OK' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
configPath,
|
||||||
|
`export default {
|
||||||
|
swaggerUrl: './swagger.json',
|
||||||
|
swaggerTimeoutMs: 1000,
|
||||||
|
outputDir: './generated',
|
||||||
|
generateIndexFile: false,
|
||||||
|
externalIndexFile: './api-index.js',
|
||||||
|
requestImport: '../request',
|
||||||
|
cleanOutput: true,
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
|
||||||
|
await execFile(process.execPath, [cliPath, 'gen', '--config', configPath], {
|
||||||
|
cwd: tempDir,
|
||||||
|
})
|
||||||
|
|
||||||
|
await assert.doesNotReject(fs.access(path.join(outputDir, 'alpha.js')))
|
||||||
|
await assert.rejects(fs.access(path.join(outputDir, 'index.js')))
|
||||||
|
await assert.rejects(fs.access(externalIndexFile))
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cli sync fails clearly when sync.enabled=true but generateIndexFile is false', async () => {
|
||||||
|
const tempDir = await createTempDir()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const configPath = path.join(tempDir, 'yx-generate-api.config.mjs')
|
||||||
|
const cliPath = path.resolve('bin/yx-generate-api.js')
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
configPath,
|
||||||
|
`export default {
|
||||||
|
outputDir: './generated',
|
||||||
|
generateIndexFile: false,
|
||||||
|
externalIndexFile: './api-index.js',
|
||||||
|
sync: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
execFile(process.execPath, [cliPath, 'sync', '--config', configPath], {
|
||||||
|
cwd: tempDir,
|
||||||
|
}),
|
||||||
|
(error) => {
|
||||||
|
assert.match(error.stderr, /generateIndexFile=false/)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,110 @@ test('generated index includes namespace exports and flattened re-exports', asyn
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('generation can skip generated index.js when generateIndexFile is false', async () => {
|
||||||
|
const tempDir = await createTempDir()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const swaggerPath = path.join(tempDir, 'swagger.json')
|
||||||
|
const outputDir = path.join(tempDir, 'generated')
|
||||||
|
|
||||||
|
await fs.mkdir(outputDir, { recursive: true })
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(outputDir, 'index.js'),
|
||||||
|
`// Auto-generated. Do not edit manually.
|
||||||
|
// Swagger: old
|
||||||
|
export * from './old'
|
||||||
|
`,
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
|
||||||
|
await writeJson(swaggerPath, {
|
||||||
|
openapi: '3.0.0',
|
||||||
|
paths: {
|
||||||
|
'/api/v1/Alpha/GetThing': {
|
||||||
|
get: {
|
||||||
|
tags: ['Alpha'],
|
||||||
|
responses: {
|
||||||
|
200: { description: 'OK' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await generateApiFiles({
|
||||||
|
projectRoot: tempDir,
|
||||||
|
swaggerUrl: swaggerPath,
|
||||||
|
swaggerTimeoutMs: 1000,
|
||||||
|
outputDir,
|
||||||
|
generateIndexFile: false,
|
||||||
|
requestImport: '../request',
|
||||||
|
paramStyle: 'object',
|
||||||
|
modules: [],
|
||||||
|
cleanOutput: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await assert.doesNotReject(fs.access(path.join(outputDir, 'alpha.js')))
|
||||||
|
await assert.rejects(fs.access(path.join(outputDir, 'index.js')))
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generation allows duplicate module export names when generateIndexFile is false', async () => {
|
||||||
|
const tempDir = await createTempDir()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const swaggerPath = path.join(tempDir, 'swagger.json')
|
||||||
|
const outputDir = path.join(tempDir, 'generated')
|
||||||
|
|
||||||
|
await writeJson(swaggerPath, {
|
||||||
|
openapi: '3.0.0',
|
||||||
|
paths: {
|
||||||
|
'/api/v1/Alpha/GetList': {
|
||||||
|
get: {
|
||||||
|
tags: ['Alpha'],
|
||||||
|
responses: {
|
||||||
|
200: { description: 'OK' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/api/v1/Beta/GetList': {
|
||||||
|
get: {
|
||||||
|
tags: ['Beta'],
|
||||||
|
responses: {
|
||||||
|
200: { description: 'OK' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await assert.doesNotReject(
|
||||||
|
generateApiFiles({
|
||||||
|
projectRoot: tempDir,
|
||||||
|
swaggerUrl: swaggerPath,
|
||||||
|
swaggerTimeoutMs: 1000,
|
||||||
|
outputDir,
|
||||||
|
generateIndexFile: false,
|
||||||
|
requestImport: '../request',
|
||||||
|
paramStyle: 'object',
|
||||||
|
modules: [],
|
||||||
|
cleanOutput: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const alphaContent = await readFile(path.join(outputDir, 'alpha.js'))
|
||||||
|
const betaContent = await readFile(path.join(outputDir, 'beta.js'))
|
||||||
|
|
||||||
|
assert.match(alphaContent, /const getListApi =/)
|
||||||
|
assert.match(betaContent, /const getListApi =/)
|
||||||
|
await assert.rejects(fs.access(path.join(outputDir, 'index.js')))
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test('generation can rename duplicate exports by appending module names', async () => {
|
test('generation can rename duplicate exports by appending module names', async () => {
|
||||||
const tempDir = await createTempDir()
|
const tempDir = await createTempDir()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue