Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
name: review
name: code-review

on:
push:
pull_request:
schedule:
# 每天凌晨 3 点
# 每天凌晨 3 点(UTC)
- cron: '0 3 * * *'

jobs:
review:
code-review:
runs-on: ubuntu-latest
steps:
- name: 修改为中国时区
run: |
- run: |
sudo rm /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
- uses: actions/checkout@v3
Expand All @@ -23,3 +22,7 @@ jobs:
- run: npm ci
- run: npm run lint
- run: npm run test
- uses: codacy/codacy-coverage-reporter-action@v1
with:
api-token: ${{ secrets.CODACY_API_TOKEN }}
coverage-reports: coverage/lcov.info
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: release
name: release-please

on:
push:
Expand All @@ -10,13 +10,9 @@ permissions:
pull-requests: write

jobs:
release:
release-please:
runs-on: ubuntu-latest
steps:
- name: 修改为中国时区
run: |
sudo rm /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
- uses: google-github-actions/release-please-action@v3
id: release
with:
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

---

[![review][review-badge]][review-link] ![version][version-badge] ![license][license-badge]
[![code-review][code-review-badge]][code-review-link] [![code-quality][code-quality-badge]][code-quality-link] ![version][version-badge] ![license][license-badge]

OpenAPI Specification ➡️ TypeScript

Expand Down Expand Up @@ -141,7 +141,9 @@ generate({

**At least one of `url` and `spec` exists**

[review-badge]: https://github.com/cloudcome/oas-gen-ts/actions/workflows/review.yml/badge.svg
[review-link]: https://github.com/cloudcome/oas-gen-ts/actions/workflows/review.yml
[code-review-badge]: https://github.com/cloudcome/oas-gen-ts/actions/workflows/code-review.yml/badge.svg
[code-review-link]: https://github.com/cloudcome/oas-gen-ts/actions/workflows/code-review.yml
[code-quality-badge]: https://app.codacy.com/project/badge/Grade/e788387e5e27472ba3b5003bf19aeea7
[code-quality-link]: https://www.codacy.com/gh/cloudcome/oas-gen-ts/dashboard?utm_source=github.com&utm_medium=referral&utm_content=cloudcome/oas-gen-ts&utm_campaign=Badge_Grade
[version-badge]: https://img.shields.io/npm/v/oas-gen-ts
[license-badge]: https://img.shields.io/github/license/cloudcome/oas-gen-ts
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"cosmiconfig": "^8.1.0",
"lodash-es": "^4.17.21",
"swagger-typescript-api": "^12.0.3"
},
Expand Down
79 changes: 79 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import chalk from 'chalk';
import path from 'path';
import * as process from 'process';
import { defineConfig } from './configure';
import { generate } from './generator';
import { UserConfig } from './types';
import { exitError, isFile, normalizeError, tryCatch } from './utils';

interface StartConfig {
// 指定配置文件绝对路径
configFile?: string;
}

export const startConfigFiles = ['oas.config.cjs', 'oas.config.js', 'oas.json'];

export async function resolveConfigFile(cwd: string, configFile?: string) {
if (configFile) {
if (!(await isFile(path.join(configFile)))) {
throw new Error(`指定配置文件 "${configFile}" 不存在`);
}

return configFile;
}

for (const file of startConfigFiles.values()) {
if (await isFile(path.join(cwd, file))) {
return file;
}
}
}

export async function start(startConfig?: StartConfig) {
const configFile = await resolveConfigFile(process.cwd(), startConfig?.configFile);

if (!configFile) {
return exitError(`配置文件未找到,配置文件可以为 ${startConfigFiles.join('、')} 之一`);
}

// eslint-disable-next-line @typescript-eslint/no-var-requires
const [err, config] = await tryCatch<UserConfig>(require(configFile));

if (err) {
return exitError(err.message);
}

if (!config) {
return exitError('配置文件内容可能为空');
}

const strictConfig = defineConfig(config);

try {
await generate(strictConfig, (generated, info) => {
const { oasItem } = generated;
const { index, length, done, start, end } = info;
const width = Math.min(String(length).length, 2);
const stepText = String(index + 1).padStart(width, '0');

if (done) {
const past = end - start;
console.log(
chalk.cyanBright(`[${stepText}/${length}]`),
'generated ',
chalk.yellowBright(oasItem.name),
chalk.gray(`${past}ms`)
);
} else {
console.log(
chalk.cyanBright(`[${stepText}/${length}]`),
'generating',
chalk.yellowBright(oasItem.name),
chalk.gray('...')
);
}
});
} catch (err) {
exitError(normalizeError(err).message);
}
}
1 change: 1 addition & 0 deletions src/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const defaults: StrictConfig = {
axiosImport: axiosImportDefault,
unwrapResponseData: false,
list: [],
onGenerated: () => 0,
};

export function defineConfig(config: UserConfig) {
Expand Down
79 changes: 35 additions & 44 deletions src/generator.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,66 @@
import chalk from 'chalk';
import { cosmiconfig } from 'cosmiconfig';
import fs from 'fs/promises';
import path from 'path';
import { generateApi } from 'swagger-typescript-api';
import { defineConfig } from './configure';
import { axiosImportDefault, helpersImport, templatesDir } from './const';
import { StrictConfig, Oas, UserConfig } from './types';
import { exitError, normalizeError, tryCatch } from './utils';
import { Generated, GeneratedCallback, OasItem, OasItemAsSpec, OasItemAsUrl, StrictConfig } from './types';

export async function generateItem(oas: Oas, config: StrictConfig) {
const { name, url, spec, axiosImport: axiosImportScope } = oas;
export async function generateItem(oasItem: OasItem, config: StrictConfig): Promise<Generated> {
const { name, axiosImport: axiosImportScope } = oasItem;
const { cwd, dest, axiosImport: axiosImportGlobal, unwrapResponseData } = config;
const axiosImport = axiosImportScope || axiosImportGlobal || axiosImportDefault;
const { files } = await generateApi({
name,
url,
spec,
url: (oasItem as OasItemAsUrl).url,
spec: (oasItem as OasItemAsSpec).spec,
output: false,
httpClientType: 'axios',
templates: templatesDir,
silent: true,
unwrapResponseData,
});

const generated: Generated = {
files: [],
oasItem,
config,
};

for (const { content, name: filename } of files) {
const contentFinal = [axiosImport, helpersImport, content].join('\n');
const file = path.join(cwd, dest, filename);
const dir = path.dirname(file);

await fs.mkdir(dir, { recursive: true });
await fs.writeFile(file, contentFinal);
}
}

export async function generate(config: StrictConfig) {
const { list } = config;
let step = 0;
const length = list.length;
const width = String(length).length;

for (const oas of list) {
step++;
const stepText = String(step).padStart(width, '0');
console.log(chalk.cyanBright(`[${stepText}/${length}]`), 'generating', chalk.yellow(oas.name));
await generateItem(oas, config);
generated.files.push(file);
}
}

export async function start() {
const explorer = cosmiconfig('oas', {
searchPlaces: ['oas.config.cjs', 'oas.config.js', 'oas.json'],
});
const [err1, result] = await tryCatch(explorer.search());
return generated;
}

if (err1) {
return exitError('配置文件查找失败');
}
export async function generate(config: StrictConfig, callback?: GeneratedCallback): Promise<Generated[]> {
const { list, onGenerated } = config;
let index = 0;
const length = list.length;
const generatedList: Generated[] = [];

if (!result) {
return exitError('配置文件未找到');
for (const oasItem of list) {
const start = Date.now();
callback?.(
{
files: [],
oasItem,
config,
},
{ index, length, done: false, start, end: start }
);
const generated = await generateItem(oasItem, config);
generatedList.push(generated);
onGenerated(generated);
callback?.(generated, { index, length, done: true, start, end: Date.now() });
index++;
}

const config = result.filepath.endsWith('js')
? // js 文件使用 defineConfig,返回的是 StrictConfig
(result.config as StrictConfig)
: // json 文件是纯文本,返回的 UserConfig
defineConfig(result.config as UserConfig);

try {
await generate(config);
} catch (err) {
exitError(normalizeError(err).message);
}
return generatedList;
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './generator';
export * from './configure';
export * from './types';
export * from './commands';
35 changes: 29 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface OasBase {
interface OasItemBase {
name: string;

/**
Expand All @@ -10,15 +10,17 @@ interface OasBase {
axiosImport?: string;
}

interface OasAsUrl extends OasBase {
export interface OasItemAsUrl extends OasItemBase {
url: string;
}

interface OasAsSpec extends OasBase {
spec: import('swagger-schema-official').Spec;
export type Oas = import('swagger-schema-official').Spec;

export interface OasItemAsSpec extends OasItemBase {
spec: Oas;
}

export interface Oas extends OasAsUrl, OasAsSpec {}
export type OasItem = OasItemAsUrl | OasItemAsSpec;

export interface UserConfig {
/**
Expand Down Expand Up @@ -47,10 +49,16 @@ export interface UserConfig {
*/
unwrapResponseData?: boolean;

/**
* 单个 oas 生成后回调
* @param {Generated} generated
*/
onGenerated?: (generated: Generated) => any;

/**
* oas 列表
*/
list: Oas[];
list: OasItem[];
}

export type StrictConfig = Required<UserConfig>;
Expand All @@ -62,3 +70,18 @@ export enum ContentKind {
TEXT = 'TEXT',
OTHER = 'OTHER',
}

export interface Generated {
files: string[];
oasItem: OasItem;
config: StrictConfig;
}

export type GenerateInfo = {
index: number;
length: number;
done: boolean;
start: number;
end: number;
};
export type GeneratedCallback = (generated: Generated, info: GenerateInfo) => any;
21 changes: 20 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import chalk from 'chalk';
import fs from 'fs/promises';
import path from 'path';
import * as process from 'process';

export async function isFile(p: string) {
try {
const state = await fs.stat(p);
return state.isFile();
} catch (err) {
return false;
}
}

export async function cleanDir(p: string) {
await fs.rm(p, { recursive: true, force: true });
}

export function exitError(message: string) {
console.log(chalk.redBright(message));
process.exit(1);

/* istanbul ignore if */
if (!process.env.VITEST) {
process.exit(1);
}
}

export function normalizeError(err: unknown) {
Expand Down
Loading