feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
139
frontend/infra/idl/idl2ts-cli/src/actions.ts
Normal file
139
frontend/infra/idl/idl2ts-cli/src/actions.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { genClient } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
import { lookupConfig } from './utils';
|
||||
import { type ApiConfig, type ApiTypeConfig } from './types';
|
||||
import { MockPlugin } from './plugins/mock-plugin';
|
||||
import { LocalConfigPlugin } from './plugins/local-config';
|
||||
import { FormatPlugin } from './plugins/formatter';
|
||||
import { FilterTypesPlugin } from './plugins/filter-types-plugin';
|
||||
import { AliasPlugin } from './plugins/alias';
|
||||
|
||||
interface GenOptions {
|
||||
formatConfig?: string;
|
||||
}
|
||||
|
||||
export const gen = (projectRoot: string, options: GenOptions) => {
|
||||
const configs = lookupConfig(projectRoot);
|
||||
|
||||
function genSingle(config: ApiConfig) {
|
||||
const {
|
||||
entries,
|
||||
plugins = [],
|
||||
commonCodePath,
|
||||
aggregationExport,
|
||||
formatter,
|
||||
} = config;
|
||||
const aliasMap = new Map();
|
||||
const idlRoot = path.resolve(projectRoot, config.idlRoot);
|
||||
const output = path.resolve(projectRoot, config.output);
|
||||
const realEntries = [] as string[];
|
||||
Object.keys(entries).forEach(i => {
|
||||
aliasMap.set(path.resolve(idlRoot, entries[i]), i);
|
||||
realEntries.push(entries[i]);
|
||||
});
|
||||
|
||||
genClient({
|
||||
entries: realEntries,
|
||||
idlRoot: path.resolve(projectRoot, idlRoot),
|
||||
genSchema: false,
|
||||
genClient: true,
|
||||
genMock: false,
|
||||
plugins: [
|
||||
new MockPlugin(),
|
||||
new AliasPlugin(aliasMap),
|
||||
new FormatPlugin({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
options.formatConfig || '.prettierrc',
|
||||
),
|
||||
formatter,
|
||||
}),
|
||||
new LocalConfigPlugin({ outputDir: output, projectRoot, idlRoot }),
|
||||
...plugins,
|
||||
],
|
||||
entryName: aggregationExport || 'index',
|
||||
outputDir: output,
|
||||
commonCodePath,
|
||||
});
|
||||
}
|
||||
configs.forEach(c => {
|
||||
genSingle(c);
|
||||
});
|
||||
};
|
||||
|
||||
export const genTypes = (projectRoot: string, options: GenOptions) => {
|
||||
const configs = lookupConfig<ApiTypeConfig>(projectRoot, 'api.filter.js');
|
||||
|
||||
function genSingle(config: ApiTypeConfig) {
|
||||
const {
|
||||
entries,
|
||||
plugins = [],
|
||||
commonCodePath,
|
||||
aggregationExport,
|
||||
formatter,
|
||||
filters,
|
||||
} = config;
|
||||
const aliasMap = new Map();
|
||||
const idlRoot = path.resolve(projectRoot, config.idlRoot);
|
||||
const output = path.resolve(projectRoot, config.output);
|
||||
const realEntries = [] as string[];
|
||||
Object.keys(entries).forEach(i => {
|
||||
aliasMap.set(path.resolve(idlRoot, entries[i]), i);
|
||||
realEntries.push(entries[i]);
|
||||
});
|
||||
|
||||
genClient({
|
||||
entries: realEntries,
|
||||
idlRoot: path.resolve(projectRoot, idlRoot),
|
||||
genSchema: false,
|
||||
genClient: true,
|
||||
genMock: false,
|
||||
plugins: [
|
||||
new MockPlugin(),
|
||||
new AliasPlugin(aliasMap),
|
||||
new FormatPlugin({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
options.formatConfig || '.prettierrc',
|
||||
),
|
||||
formatter,
|
||||
}),
|
||||
new LocalConfigPlugin({ outputDir: output, projectRoot, idlRoot }),
|
||||
new FilterTypesPlugin(filters, output),
|
||||
...plugins,
|
||||
],
|
||||
entryName: aggregationExport || 'index',
|
||||
outputDir: output,
|
||||
commonCodePath,
|
||||
});
|
||||
}
|
||||
configs.forEach(c => {
|
||||
genSingle(c);
|
||||
});
|
||||
};
|
||||
|
||||
export function defineConfig(c: ApiConfig[]) {
|
||||
return c;
|
||||
}
|
||||
|
||||
export function defineApiTpeConfig(c: ApiTypeConfig[]) {
|
||||
return c;
|
||||
}
|
||||
3
frontend/infra/idl/idl2ts-cli/src/cli.js
Executable file
3
frontend/infra/idl/idl2ts-cli/src/cli.js
Executable file
@@ -0,0 +1,3 @@
|
||||
require('sucrase/register/ts');
|
||||
|
||||
require('./cli.ts');
|
||||
79
frontend/infra/idl/idl2ts-cli/src/cli.ts
Normal file
79
frontend/infra/idl/idl2ts-cli/src/cli.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import ora from 'ora';
|
||||
import { Command } from 'commander';
|
||||
|
||||
import { gen, genTypes } from './actions';
|
||||
|
||||
const main = () => {
|
||||
const program = new Command();
|
||||
program
|
||||
.command('gen')
|
||||
.description('gen api code by thrift or pb')
|
||||
.argument('<projectRoot>', 'project root')
|
||||
.option(
|
||||
'-f --format-config <formatConfig>',
|
||||
'prettier config file',
|
||||
'.prettierrc',
|
||||
)
|
||||
.action(
|
||||
(projectRoot, options: { genMock: boolean; formatConfig: string }) => {
|
||||
const spinner = ora(
|
||||
'Generating api. It may take a few seconds',
|
||||
).start();
|
||||
try {
|
||||
gen(projectRoot, {
|
||||
formatConfig: options.formatConfig,
|
||||
});
|
||||
spinner.succeed('Generate api successfully');
|
||||
} catch (error) {
|
||||
spinner.fail('Generate api fail');
|
||||
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
program
|
||||
.command('filter')
|
||||
.description('filter api types')
|
||||
.argument('<projectRoot>', 'project root')
|
||||
.option(
|
||||
'-f --format-config <formatConfig>',
|
||||
'prettier config file',
|
||||
'.prettierrc',
|
||||
)
|
||||
.action((projectRoot, options: { formatConfig: string }) => {
|
||||
const spinner = ora(
|
||||
'Generating filtered types. It may take a few seconds',
|
||||
).start();
|
||||
try {
|
||||
genTypes(projectRoot, options);
|
||||
spinner.succeed('Generate filtered types successfully');
|
||||
} catch (error) {
|
||||
spinner.fail('Generate filtered types fail');
|
||||
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
};
|
||||
|
||||
main();
|
||||
105
frontend/infra/idl/idl2ts-cli/src/mock-dev.ts
Normal file
105
frontend/infra/idl/idl2ts-cli/src/mock-dev.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { type ApiConfig } from './types';
|
||||
|
||||
let hasShowHint = false;
|
||||
|
||||
function requiredWithoutCache(src, onError?) {
|
||||
let data;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { Module } = require('module');
|
||||
try {
|
||||
// disable 了 require 的缓存,这样可以改变了 mock 数据后,无需重启服务。
|
||||
const originCache = Module._cache;
|
||||
Module._cache = {};
|
||||
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-require-imports
|
||||
data = require(src);
|
||||
Module._cache = originCache;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (onError) {
|
||||
onError(error);
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export function createProxy({
|
||||
root,
|
||||
handleResponseData,
|
||||
}: {
|
||||
root: string;
|
||||
handleResponseData?: (service: string, method: string, data: any) => any;
|
||||
}) {
|
||||
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-require-imports
|
||||
const apiConfig = require(path.resolve(root, 'api.config.js')) as ApiConfig[];
|
||||
// eslint-disable-next-line max-params
|
||||
return async function proxyResWithMock(_, __, req, resp) {
|
||||
if (!req.headers['x-svc-method']) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const config = requiredWithoutCache(
|
||||
path.resolve(root, './api.dev.local.js'),
|
||||
() => {
|
||||
if (!hasShowHint) {
|
||||
console.warn(
|
||||
'can not find mock config, please run "gen-api" command if you want to mock',
|
||||
);
|
||||
hasShowHint = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
if (
|
||||
config &&
|
||||
config.mock.includes(req.headers['x-svc-method'].split('_').join('.'))
|
||||
) {
|
||||
const [svc, method] = req.headers['x-svc-method'].split('_');
|
||||
const target = apiConfig.find(i => i.entries[svc].length > 0);
|
||||
if (!target) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const src = path.resolve(
|
||||
root,
|
||||
target.output,
|
||||
target.entries[svc].replace(/\.(thrift|proto)$/, '.mock.js'),
|
||||
);
|
||||
const data = requiredWithoutCache(src);
|
||||
if (data) {
|
||||
try {
|
||||
if (resp) {
|
||||
resp.statusCode = 200;
|
||||
resp.setHeader('Content-Type', 'application/json');
|
||||
} else {
|
||||
console.warn('resp is not defined');
|
||||
}
|
||||
const res = await data[svc][method].res(req);
|
||||
if (handleResponseData) {
|
||||
return await handleResponseData(svc, method, res);
|
||||
}
|
||||
return res;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
59
frontend/infra/idl/idl2ts-cli/src/optional/forward.ts
Normal file
59
frontend/infra/idl/idl2ts-cli/src/optional/forward.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type IPlugin, type Program, after } from '@coze-arch/idl2ts-plugin';
|
||||
import {
|
||||
type IParseEntryCtx,
|
||||
isServiceDefinition,
|
||||
} from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
interface IOptions {
|
||||
patch: {
|
||||
[service: string]: {
|
||||
prefix?: string;
|
||||
method?: { [name: string]: 'GET' | 'POST' };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export class PatchPlugin implements IPlugin {
|
||||
private options: IOptions;
|
||||
constructor(options: IOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
apply(p: Program) {
|
||||
p.register(after(HOOK.PARSE_ENTRY), (ctx: IParseEntryCtx) => {
|
||||
ctx.ast = ctx.ast.map(i => {
|
||||
i.statements.map(s => {
|
||||
if (isServiceDefinition(s) && this.options.patch[s.name.value]) {
|
||||
const { prefix = '/', method = {} } =
|
||||
this.options.patch[s.name.value];
|
||||
s.functions.forEach(f => {
|
||||
f.extensionConfig = {
|
||||
uri: `${prefix}/${f.name.value}`,
|
||||
method: method[f.name.value] || 'POST',
|
||||
};
|
||||
});
|
||||
}
|
||||
return s;
|
||||
});
|
||||
return i;
|
||||
});
|
||||
return ctx;
|
||||
});
|
||||
}
|
||||
}
|
||||
17
frontend/infra/idl/idl2ts-cli/src/optional/index.ts
Normal file
17
frontend/infra/idl/idl2ts-cli/src/optional/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { PatchPlugin } from './forward';
|
||||
47
frontend/infra/idl/idl2ts-cli/src/plugins/alias.ts
Normal file
47
frontend/infra/idl/idl2ts-cli/src/plugins/alias.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type Program, on } from '@coze-arch/idl2ts-plugin';
|
||||
import {
|
||||
type IParseEntryCtx,
|
||||
isServiceDefinition,
|
||||
} from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
export class AliasPlugin {
|
||||
alias = new Map();
|
||||
|
||||
constructor(alias: Map<string, string>) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
apply(program: Program) {
|
||||
program.register(on(HOOK.PARSE_ENTRY), this.setAlias.bind(this));
|
||||
}
|
||||
|
||||
setAlias(ctx: IParseEntryCtx) {
|
||||
ctx.ast.forEach(i => {
|
||||
if (i.isEntry) {
|
||||
i.statements.forEach(s => {
|
||||
if (isServiceDefinition(s) && this.alias.has(i.idlPath)) {
|
||||
s.name.value = this.alias.get(i.idlPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
72
frontend/infra/idl/idl2ts-cli/src/plugins/comment.ts
Normal file
72
frontend/infra/idl/idl2ts-cli/src/plugins/comment.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type IPlugin, type Program, after } from '@coze-arch/idl2ts-plugin';
|
||||
import { type IParseEntryCtx } from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} content
|
||||
* @param {"CommentBlock" | "CommentLine"} type
|
||||
* @returns {{
|
||||
* value: string;
|
||||
* type: "CommentBlock" | "CommentLine";
|
||||
* }}
|
||||
*/
|
||||
function createComment(content, type = 'CommentBlock') {
|
||||
return {
|
||||
value: content,
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
export class CommentPlugin implements IPlugin {
|
||||
config: { comments: string[] };
|
||||
comments: any[] = [];
|
||||
/**
|
||||
* @param {{comments: string[]}} config
|
||||
*/
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
apply(program: Program) {
|
||||
program.register(after(HOOK.GEN_FILE_AST), this.addComment.bind(this));
|
||||
}
|
||||
|
||||
addComment(ctx: IParseEntryCtx) {
|
||||
const { files } = ctx;
|
||||
for (const [file, res] of files.entries()) {
|
||||
if (
|
||||
res.type === 'babel' &&
|
||||
file.includes('/auto-gen/') &&
|
||||
file.endsWith('.ts')
|
||||
) {
|
||||
res.content.leadingComments = this.getComments();
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
getComments() {
|
||||
if (this.comments) {
|
||||
return this.comments;
|
||||
}
|
||||
this.comments = this.config.comments.map(i => createComment(i));
|
||||
return this.comments;
|
||||
}
|
||||
}
|
||||
230
frontend/infra/idl/idl2ts-cli/src/plugins/filter-types-plugin.ts
Normal file
230
frontend/infra/idl/idl2ts-cli/src/plugins/filter-types-plugin.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { type Program, after } from '@coze-arch/idl2ts-plugin';
|
||||
import {
|
||||
type Comment,
|
||||
type EnumDefinition,
|
||||
type FieldDefinition,
|
||||
type FieldType,
|
||||
type FunctionType,
|
||||
type IParseEntryCtx,
|
||||
type IParseResultItem,
|
||||
type Identifier,
|
||||
type ProcessIdlCtx,
|
||||
type StructDefinition,
|
||||
SyntaxType,
|
||||
type UnifyStatement,
|
||||
createFile,
|
||||
findDefinition,
|
||||
getParseResultFromNamespace,
|
||||
getStatementById,
|
||||
getValuesFromEnum,
|
||||
isBaseType,
|
||||
isEnumDefinition,
|
||||
isIdentifier,
|
||||
isListType,
|
||||
isMapType,
|
||||
isServiceDefinition,
|
||||
isSetType,
|
||||
isStructDefinition,
|
||||
parseIdFiledType,
|
||||
withExportDeclaration,
|
||||
} from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
// eslint-disable-next-line @coze-arch/no-batch-import-or-export
|
||||
import * as t from '@babel/types';
|
||||
|
||||
export class FilterTypesPlugin {
|
||||
methods: Record<string, string[]>;
|
||||
statements: Record<string, UnifyStatement[]> = {};
|
||||
enums: Record<string, EnumDefinition[]> = {};
|
||||
output: string;
|
||||
constructor(methods: Record<string, string[]>, output: string) {
|
||||
this.methods = methods;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
apply(program: Program) {
|
||||
program.register(after(HOOK.PARSE_ENTRY), this.filterTypes.bind(this));
|
||||
program.register(
|
||||
after(HOOK.PROCESS_IDL_AST),
|
||||
this.genEnumsFiles.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
genEnumsFiles(ctx: ProcessIdlCtx) {
|
||||
const file = createFile('');
|
||||
Object.keys(this.enums).forEach(key => {
|
||||
const defs = this.enums[key];
|
||||
const block = t.tsModuleBlock([]);
|
||||
|
||||
defs.forEach(d => {
|
||||
const values = getValuesFromEnum(d);
|
||||
const objExps = d.members.map((m, index) => {
|
||||
const valueProps = t.objectProperty(
|
||||
t.identifier('value'),
|
||||
t.numericLiteral(values[index]),
|
||||
);
|
||||
t.addComment(valueProps, 'trailing', 'm.name.value');
|
||||
return t.objectExpression([
|
||||
valueProps,
|
||||
t.objectProperty(
|
||||
t.identifier('label'),
|
||||
t.stringLiteral(m.name.value),
|
||||
),
|
||||
]);
|
||||
});
|
||||
const constNode = t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
t.identifier(d.name.value),
|
||||
t.arrayExpression(objExps),
|
||||
),
|
||||
]);
|
||||
block.body.push(withExportDeclaration(constNode));
|
||||
});
|
||||
const exportNode = withExportDeclaration(
|
||||
t.tsModuleDeclaration(t.identifier(key), block),
|
||||
);
|
||||
file.program.body.push(exportNode);
|
||||
});
|
||||
ctx.output.set(path.resolve(this.output, 'enums.ts'), {
|
||||
type: 'babel',
|
||||
content: file,
|
||||
});
|
||||
return ctx;
|
||||
}
|
||||
|
||||
filterTypes(ctx: IParseEntryCtx) {
|
||||
ctx.ast.forEach(i => {
|
||||
if (i.isEntry) {
|
||||
i.statements.forEach(s => {
|
||||
if (isServiceDefinition(s) && this.methods[s.name.value]) {
|
||||
for (const f of s.functions) {
|
||||
if (this.methods[s.name.value].includes(f.name.value)) {
|
||||
if (isIdentifier(f.returnType)) {
|
||||
this.lookupTypes(f.returnType, i);
|
||||
}
|
||||
const fieldType = f.fields[0]?.fieldType;
|
||||
if (isIdentifier(fieldType)) {
|
||||
this.lookupTypes(fieldType, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
ctx.ast = ctx.ast
|
||||
.filter(i => this.statements[i.idlPath])
|
||||
.map(i => ({ ...i, statements: this.statements[i.idlPath] }));
|
||||
return ctx;
|
||||
}
|
||||
private lookupTypes(id: Identifier, current: IParseResultItem) {
|
||||
const { namespace, refName } = parseIdFiledType(id);
|
||||
if (namespace) {
|
||||
const next = getParseResultFromNamespace(namespace, current);
|
||||
const nextID = findDefinition(next.statements, refName);
|
||||
if (nextID) {
|
||||
this.lookupTypes(nextID.name, next);
|
||||
}
|
||||
} else {
|
||||
const statement = getStatementById(id, current);
|
||||
if (statement) {
|
||||
if (this.statements[current.idlPath]) {
|
||||
if (!this.statements[current.idlPath].includes(statement)) {
|
||||
this.statements[current.idlPath].push(statement);
|
||||
}
|
||||
} else {
|
||||
this.statements[current.idlPath] = [statement];
|
||||
}
|
||||
if (isStructDefinition(statement)) {
|
||||
this.lookupStructTypes(statement, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lookupStructTypes(
|
||||
statement: StructDefinition,
|
||||
current: IParseResultItem,
|
||||
) {
|
||||
for (const field of statement.fields) {
|
||||
const { fieldType } = field;
|
||||
this.processFiledType(fieldType, current, field);
|
||||
}
|
||||
}
|
||||
|
||||
private processFiledType(
|
||||
fieldType: FieldType | FunctionType,
|
||||
current: IParseResultItem,
|
||||
field: FieldDefinition,
|
||||
) {
|
||||
if (isBaseType(fieldType)) {
|
||||
return;
|
||||
} else if (isListType(fieldType) || isSetType(fieldType)) {
|
||||
const { valueType } = fieldType;
|
||||
return this.processFiledType(valueType, current, field);
|
||||
} else if (isMapType(fieldType)) {
|
||||
const { valueType } = fieldType;
|
||||
return this.processFiledType(valueType, current, field);
|
||||
} else if (isIdentifier(fieldType)) {
|
||||
const statement = getStatementById(fieldType, current);
|
||||
if (isEnumDefinition(statement)) {
|
||||
// 强制转位 number
|
||||
// @ts-expect-error fixme late
|
||||
fieldType.type = SyntaxType.I32Keyword;
|
||||
let namespace = current.unifyNamespace;
|
||||
const parsedFieldType = parseIdFiledType(fieldType);
|
||||
|
||||
if (parsedFieldType.namespace) {
|
||||
const next = getParseResultFromNamespace(
|
||||
parsedFieldType.namespace,
|
||||
current,
|
||||
);
|
||||
namespace = next.unifyNamespace;
|
||||
}
|
||||
const extraComment = {
|
||||
type: SyntaxType.CommentLine,
|
||||
value: `@see${fieldType.value}`,
|
||||
} as Comment;
|
||||
if (field.comments) {
|
||||
field.comments.push(extraComment);
|
||||
} else {
|
||||
field.comments = [extraComment];
|
||||
}
|
||||
if (this.enums[namespace]) {
|
||||
if (
|
||||
this.enums[namespace].some(
|
||||
i => i.name.value === statement.name.value,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.enums[namespace].push(statement);
|
||||
} else {
|
||||
this.enums[namespace] = [statement];
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
return this.lookupTypes(fieldType, current);
|
||||
}
|
||||
}
|
||||
throw new Error(`unknown type:${fieldType.type}`);
|
||||
}
|
||||
}
|
||||
83
frontend/infra/idl/idl2ts-cli/src/plugins/formatter.ts
Normal file
83
frontend/infra/idl/idl2ts-cli/src/plugins/formatter.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import { format } from 'prettier';
|
||||
import { type Program, on } from '@coze-arch/idl2ts-plugin';
|
||||
import { HOOK, type WriteFileCtx } from '@coze-arch/idl2ts-generator';
|
||||
interface IOption {
|
||||
path: string;
|
||||
formatter?: (content: string, filename: string) => string;
|
||||
}
|
||||
function isPromise(p: any) {
|
||||
return (
|
||||
p.then &&
|
||||
typeof p.then === 'function' &&
|
||||
typeof p.catch === 'function' &&
|
||||
typeof p.finally === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
function readConfig(file: string) {
|
||||
let config = {};
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-require-imports
|
||||
config = require(file);
|
||||
if (!config) {
|
||||
const content = fs.readFileSync(file, { encoding: 'utf8' });
|
||||
config = JSON.parse(content);
|
||||
}
|
||||
// eslint-disable-next-line @coze-arch/no-empty-catch
|
||||
} catch (error) {
|
||||
// just
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
export class FormatPlugin {
|
||||
private config: any;
|
||||
private formatter?: (content: string, filename: string) => string;
|
||||
constructor(op: IOption) {
|
||||
this.config = readConfig(op.path);
|
||||
this.formatter = op.formatter;
|
||||
}
|
||||
apply(program: Program) {
|
||||
program.register(on(HOOK.WRITE_FILE), this.format.bind(this));
|
||||
}
|
||||
|
||||
format(ctx: WriteFileCtx) {
|
||||
if (this.formatter) {
|
||||
ctx.content = this.formatter(ctx.content, ctx.filename);
|
||||
return ctx;
|
||||
}
|
||||
if (ctx.filename.endsWith('ts')) {
|
||||
try {
|
||||
const content = format(ctx.content, {
|
||||
...this.config,
|
||||
parser: 'typescript',
|
||||
});
|
||||
if (!isPromise(content)) {
|
||||
// @ts-expect-error fixme late
|
||||
ctx.content = content;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
61
frontend/infra/idl/idl2ts-cli/src/plugins/local-config.ts
Normal file
61
frontend/infra/idl/idl2ts-cli/src/plugins/local-config.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { type IPlugin, type Program, after } from '@coze-arch/idl2ts-plugin';
|
||||
import { type IParseEntryCtx } from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
interface Config {
|
||||
idlRoot: string;
|
||||
outputDir: string;
|
||||
projectRoot: string;
|
||||
}
|
||||
|
||||
export class LocalConfigPlugin implements IPlugin {
|
||||
config: Config;
|
||||
|
||||
/**
|
||||
* @param {} config
|
||||
*/
|
||||
constructor(config: Config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
apply(program: Program) {
|
||||
program.register(after(HOOK.GEN_FILE_AST), this.genLocalConfig.bind(this));
|
||||
}
|
||||
|
||||
genLocalConfig(ctx: IParseEntryCtx) {
|
||||
const mockFile = { mock: [] };
|
||||
const target = path.resolve(this.config.projectRoot, './api.dev.local.js');
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-require-imports
|
||||
const local_config = require(target);
|
||||
mockFile.mock = local_config.mock || [];
|
||||
// eslint-disable-next-line @coze-arch/no-empty-catch, no-empty
|
||||
} catch (error) {}
|
||||
|
||||
const content = `
|
||||
module.exports = {
|
||||
mock:[${mockFile.mock.map(i => `"${i}"`).join(', ')}],
|
||||
}
|
||||
`;
|
||||
ctx.files.set(target, { type: 'text', content });
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
101
frontend/infra/idl/idl2ts-cli/src/plugins/mock-plugin.ts
Normal file
101
frontend/infra/idl/idl2ts-cli/src/plugins/mock-plugin.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { type IPlugin, type Program, before } from '@coze-arch/idl2ts-plugin';
|
||||
import {
|
||||
type IntConstant,
|
||||
isBaseType,
|
||||
SyntaxType,
|
||||
getBaseTypeConverts,
|
||||
} from '@coze-arch/idl2ts-helper';
|
||||
import { type GenMockFieldCtx, HOOK } from '@coze-arch/idl2ts-generator';
|
||||
// eslint-disable-next-line @coze-arch/no-batch-import-or-export
|
||||
import * as t from '@babel/types';
|
||||
|
||||
const NumMapper = {
|
||||
total: 1,
|
||||
code: 0,
|
||||
};
|
||||
|
||||
const StrMapper = {
|
||||
name: faker.person.lastName(),
|
||||
};
|
||||
|
||||
export class MockPlugin implements IPlugin {
|
||||
apply(program: Program) {
|
||||
program.register(before(HOOK.GEN_MOCK_FILED), this.genMockValue.bind(this));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
genMockValue = (ctx: GenMockFieldCtx) => {
|
||||
const { context, fieldType, defaultValue } = ctx;
|
||||
if (isBaseType(fieldType)) {
|
||||
const type = getBaseTypeConverts('number')[fieldType.type];
|
||||
|
||||
if (type === 'string') {
|
||||
let value = faker.word.words();
|
||||
if (defaultValue && defaultValue.type === SyntaxType.StringLiteral) {
|
||||
value = (defaultValue as any).value;
|
||||
}
|
||||
if (context) {
|
||||
const { fieldDefinition } = context;
|
||||
const fieldName = fieldDefinition.name.value;
|
||||
// 各类 ID
|
||||
if (fieldName.toLocaleUpperCase().endsWith('ID')) {
|
||||
value = String(faker.number.int());
|
||||
}
|
||||
// email 处理
|
||||
if (fieldName.includes('Email')) {
|
||||
value = `${faker.person.lastName()}@foo.com`;
|
||||
}
|
||||
// 直接映射值
|
||||
value = StrMapper[fieldName] || value;
|
||||
}
|
||||
ctx.output = t.stringLiteral(value);
|
||||
} else if (type === 'number') {
|
||||
let value = faker.number.int({ min: 0, max: 10000 });
|
||||
if (defaultValue && defaultValue.type === SyntaxType.IntConstant) {
|
||||
value = Number((defaultValue as IntConstant).value.value);
|
||||
}
|
||||
if (context) {
|
||||
const { fieldDefinition } = context;
|
||||
const fieldName = fieldDefinition.name.value;
|
||||
const formatName = fieldName.toLocaleUpperCase();
|
||||
// 各类 ID
|
||||
if (formatName.endsWith('ID')) {
|
||||
value = faker.number.int();
|
||||
}
|
||||
// 时间戳
|
||||
if (formatName.endsWith('TIME') || formatName.includes('TIMESTAMP')) {
|
||||
value = dayjs(faker.date.anytime()).valueOf();
|
||||
}
|
||||
// 类型状态
|
||||
if (formatName.endsWith('STATUS') || formatName.includes('TYPE')) {
|
||||
value = faker.number.int({ min: 0, max: 1 });
|
||||
}
|
||||
|
||||
// 直接映射值
|
||||
const mapVal = NumMapper[fieldName];
|
||||
value = typeof mapVal !== 'undefined' ? mapVal : value;
|
||||
}
|
||||
ctx.output = t.numericLiteral(value);
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
}
|
||||
53
frontend/infra/idl/idl2ts-cli/src/types.ts
Normal file
53
frontend/infra/idl/idl2ts-cli/src/types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type IPlugin } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
export interface ApiConfig {
|
||||
// idl 入口
|
||||
entries: Record<string, string>;
|
||||
// idl 根目录
|
||||
idlRoot: string;
|
||||
// 服务别名
|
||||
// 自定义 api 方法
|
||||
commonCodePath: string;
|
||||
// api 产物目录
|
||||
output: string;
|
||||
// 仓库信息设置
|
||||
repository?: {
|
||||
// 仓库地址
|
||||
url: string;
|
||||
// clone 到本地的位置
|
||||
dest: string;
|
||||
};
|
||||
// 插件
|
||||
plugins?: IPlugin[];
|
||||
// 聚合导出的文件名
|
||||
aggregationExport?: string;
|
||||
// 格式化文件
|
||||
formatter: (name: string, content: string) => string;
|
||||
idlFetchConfig?: {
|
||||
source: string;
|
||||
branch?: string;
|
||||
commit?: string;
|
||||
rootDir?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApiTypeConfig extends ApiConfig {
|
||||
// 需要过滤的方法
|
||||
filters: Record<string, string[]>;
|
||||
}
|
||||
33
frontend/infra/idl/idl2ts-cli/src/utils.ts
Normal file
33
frontend/infra/idl/idl2ts-cli/src/utils.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { type ApiConfig } from './types';
|
||||
|
||||
export function lookupConfig<T = ApiConfig>(
|
||||
projectRoot: string,
|
||||
configName = 'api.config',
|
||||
) {
|
||||
const apiConfigPath = path.resolve(process.cwd(), projectRoot, configName);
|
||||
try {
|
||||
require.resolve(apiConfigPath);
|
||||
} catch (error) {
|
||||
throw Error(`Can not find api config in path ${process.cwd()}`);
|
||||
}
|
||||
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-require-imports
|
||||
return require(apiConfigPath) as T[];
|
||||
}
|
||||
Reference in New Issue
Block a user