feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View 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;
}
}

View 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;
}
}

View 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}`);
}
}

View 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;
}
}

View 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;
}
}

View 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;
};
}