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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ Create a `myPlugin.ts` file:

```ts
// 引用模块
// import { start } from 'https://deno.land/x/stc@2.16.2/mod.ts'
import { start } from 'jsr:@lonu/stc@^2.16.2'
// import { start } from 'https://deno.land/x/stc@2.16.3/mod.ts'
import { start } from 'jsr:@lonu/stc@^2.16.3'

// Defining plugins
const myPlugin: IPlugin = {
Expand Down
6 changes: 3 additions & 3 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "@lonu/stc",
"version": "2.16.2",
"version": "2.16.3",
"exports": "./mod.ts",
"tasks": {
"pack": "deno run -A src/pack.ts",
"dev": "deno task pack && deno run -A --watch=src src/main.ts --url='https://petstore3.swagger.io/api/v3/openapi.json' --lang=js",
"dev": "deno task pack && deno run -A --watch=src src/main.ts --url='https://petstore3.swagger.io/api/v3/openapi.json' --lang=ts",
"serve": "deno run -A --watch=src src/service.ts",
"version": "echo '2.16.2' > release/version",
"version": "echo '2.16.3' > release/version",
"build:npm": "deno run -A src/npm/build.ts",
"build:mac": "deno compile -A --target x86_64-apple-darwin --output release/stc src/main.ts",
"build:mac-m": "deno compile -A --target aarch64-apple-darwin --output release/stc-m src/main.ts",
Expand Down
19 changes: 17 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ import { getT } from "./i18n/index.ts";

const LOCK_FILE = ".stc.lock";

const formatErrorMessage = (error: unknown) => {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;

try {
return JSON.stringify(error, null, 2);
} catch {
return String(error);
}
};

/**
* 初始化插件管理器
*/
Expand Down Expand Up @@ -82,7 +93,9 @@ const getData = async (

return data;
} catch (error) {
throw new Error(getT("$t(app.apiJsonFileError)", { error }));
throw new Error(getT("$t(app.apiJsonFileError)", {
error: formatErrorMessage(error),
}));
}
};

Expand All @@ -100,7 +113,9 @@ export const start = async (options: DefaultConfigOptions): Promise<void> => {
context.onLoad?.(data, context.options);

// 处理类型定义。v2 版本中,通过 `definitions` 属性获取。而 v3 版本,则通过 `components.schemas` 属性获取。
const defData = getDefinition(data.definitions || data.components?.schemas);
const defData = getDefinition(
data.definitions || data.components?.schemas || {},
);
// 触发插件 onDefinition 事件
context.onDefinition?.(defData, context.options);

Expand Down
30 changes: 22 additions & 8 deletions src/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,54 @@ import { getT } from "./i18n/index.ts";
* 输出提示信息
* @param str - 文本内容
*/
const info = (str: string) => {
const info = (str: unknown) => {
console.info(
Colors.bgBlue(getT(" $t(console.info) ")) +
` ${Colors.blue(str)}`,
` ${Colors.blue(formatLogMessage(str))}`,
);
};

/**
* 输出成功信息
* @param str - 文本内容
*/
const success = (str: string) => {
const success = (str: unknown) => {
console.info(
Colors.bgGreen(getT(" $t(console.success) ")) + ` ${Colors.green(str)}`,
Colors.bgGreen(getT(" $t(console.success) ")) +
` ${Colors.green(formatLogMessage(str))}`,
);
};

/**
* 输出警告信息
* @param str - 文本内容
*/
const warn = (str: string) => {
const warn = (str: unknown) => {
console.log(
Colors.bgYellow(getT(" $t(console.warn) ")) + ` ${Colors.yellow(str)}`,
Colors.bgYellow(getT(" $t(console.warn) ")) +
` ${Colors.yellow(formatLogMessage(str))}`,
);
};

const formatLogMessage = (value: unknown): string => {
if (typeof value === "string") return value;
if (value instanceof Error) return value.message;

try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
};

/**
* 输出错误信息
* @param str - 文本内容
*/
const error = (str: string) => {
const error = (str: unknown) => {
console.error(
Colors.bgRed(getT(" $t(console.error) ")) + ` ${Colors.red(str)}`,
Colors.bgRed(getT(" $t(console.error) ")) +
` ${Colors.red(formatLogMessage(str))}`,
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const getVirtualProperties = (
IDefinitionVirtualProperty[] | IDefinitionVirtualProperty
>,
): IDefinitionVirtualProperty[] => {
if (!defItem.type.includes("object")) {
if (!defItem.type?.includes("object")) {
Logs.warn(
getT("$t(def.parserTypeError)", {
name: defMapping.name,
Expand Down
2 changes: 1 addition & 1 deletion src/npm/pkg.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lonu/stc",
"version": "2.16.2",
"version": "2.16.3",
"description": "A tool for converting OpenApi/Swagger/Apifox into code.",
"type": "module",
"module": "esm/mod.js",
Expand Down
5 changes: 4 additions & 1 deletion src/plugins/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ const parseParams = (parameters: IPathVirtualParameter, action: string) =>
// 形参
let _formalParam = {
name: current,
originalName: "",
category: current,
type: _defName,
description: "",
Expand Down Expand Up @@ -216,7 +217,9 @@ const parseParams = (parameters: IPathVirtualParameter, action: string) =>
if (!_multiParam) {
_formalParam = {
name: item.name,
originalName: item.originalName,
originalName: item.originalName && item.originalName !== item.name
? item.originalName
: "",
category: current,
type: _type,
description: (item.title || item.description) ?? "",
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/dart/template/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// this file is auto generated.
export default {"actionImport":"import '<%= it.importPath%>shared/api_client_base.dart';\nimport 'shared/dio/index.dart';\n<% if (it.imports.length) { %>\nimport '<%= it.importPath %><%= it.typeFileName %>.dart';\n<% } %>","actionMethod":"<% /* API 方法注释 */ %>\n/// <%~ it.url %>\n<% if (it.summary) { %>\n/// <%~ it.summary %>\n<% } %>\n<% if (it.summary !== it.description) { %>\n<% if (it.summary && it.description) { %>\n\n///\n<% } %>\n<% if (it.description) { %>\n/// <%~ it.description %>\n<% } %>\n<% } %>\n\n<% if (it.deprecated) { %>\n@deprecated\n<% } %>\n<% /* API 方法 */ %>\nFuture<<%~ it.responseType %>> <%= it.methodName %>(<% it.params.forEach((param, index) => { %>\n<%~ param.type %><% if(!param.required) { %>?<% } %> <%= param.name %><% if (index < it.params.length - 1) { %>, <% } %>\n<% }) %>) async {\n var _res = await request<<%~ it.responseType %>>(\n ApiClientConfig(\n url: '<%= it.url %>',\n method: '<%= it.method %>'<% if (it.params.length) { %>,\n params: {\n<% it.params.forEach((param, index) => { %>\n '<%= param.category %>': <% if (param.category === param.name) { %><%= param.name %><% } else { %>{'<%= param.name %>' : <%= param.name %>}<% } %><% if (index < it.params.length - 1) { %>, <% } %>\n\n<% }) %>\n }\n<% } %>\n )<% if (it.responseType.includes('List<')) { %>, (json) => [<%~ it.responseName %>.fromJson(json)]<% } else if (it.responseName) { %>, <%~ it.responseName %>.fromJson<% } %>);\n\n return _res;\n}\n","definitionBody":"<% if (it.propCommit) { %>\n /// <%~ it.propCommit %>\n\n<% } %>\n <%~ it.propType %><% if (!it.prop.required) { %>?<% } %> <%= it.prop.name %><% if (it.nullable) { %> = <%~ it.nullable %><% } %>;","definitionFooter":" <%= it.defName %>({\n<% it.props.forEach((prop, index) => { %>\n <% if (prop.required) { %>required <% } %>this.<%= prop.name %><% if (index < it.props.length - 1) { %>,<% } %>\n\n<% }) %>\n });\n\n factory <%= it.defName %>.fromJson(Map<String, dynamic> json) {\n return <%= it.defName %>(\n<% it.props.forEach((prop, index) => { %>\n <%= prop.name %>: json['<%= prop.name %>']<% if (index < it.props.length - 1) { %>,<% } %>\n\n<% }) %>\n );\n }\n\n Map<String, dynamic> toJson() {\n final Map<String, dynamic> data = <String, dynamic>{};\n\n<% it.props.forEach((prop) => { %>\n data['<%= prop.name %>'] = <%= prop.name %>;\n<% }) %>\n\n return data;\n }\n}\n","definitionHeader":"class <%= it.defName %> {","enum":"<% if (it.isEnum) { %>\nenum <%= it.name %> {\n <%= it.data.map(it.convertValue).join(\",\\n\\t\")%>\n \n}\n<% } else { %>\n<%~ it.data %> <%= it.name %>;\n<% } %>"}
export default {"actionImport":"import '<%= it.importPath%>shared/api_client_base.dart';\nimport 'shared/dio/index.dart';\n<% if (it.imports.length) { %>\nimport '<%= it.importPath %><%= it.typeFileName %>.dart';\n<% } %>","actionMethod":"<% /* API 方法注释 */ %>\n/// <%~ it.url %>\n<% if (it.summary) { %>\n/// <%~ it.summary %>\n<% } %>\n<% if (it.summary !== it.description) { %>\n<% if (it.summary && it.description) { %>\n\n///\n<% } %>\n<% if (it.description) { %>\n/// <%~ it.description %>\n<% } %>\n<% } %>\n\n<% if (it.deprecated) { %>\n@deprecated\n<% } %>\n<% /* API 方法 */ %>\nFuture<<%~ it.responseType %>> <%= it.methodName %>(<% it.params.forEach((param, index) => { %>\n<%~ param.type %><% if(!param.required) { %>?<% } %> <%= param.name %><% if (index < it.params.length - 1) { %>, <% } %>\n<% }) %>) async {\n var _res = await request<<%~ it.responseType %>>(\n ApiClientConfig(\n url: '<%= it.url %>',\n method: '<%= it.method %>'<% if (it.params.length) { %>,\n params: {\n<% it.params.forEach((param, index) => { %>\n '<%= param.category %>': <% if (param.category === param.name) { %><%= param.name %><% } else { %>{'<%= param.originalName || param.name %>' : <%= param.name %>}<% } %><% if (index < it.params.length - 1) { %>, <% } %>\n\n<% }) %>\n }\n<% } %>\n )<% if (it.responseType.includes('List<')) { %>, (json) => [<%~ it.responseName %>.fromJson(json)]<% } else if (it.responseName) { %>, <%~ it.responseName %>.fromJson<% } %>);\n\n return _res;\n}\n","definitionBody":"<% if (it.propCommit) { %>\n /// <%~ it.propCommit %>\n\n<% } %>\n <%~ it.propType %><% if (!it.prop.required) { %>?<% } %> <%= it.prop.name %><% if (it.nullable) { %> = <%~ it.nullable %><% } %>;","definitionFooter":" <%= it.defName %>({\n<% it.props.forEach((prop, index) => { %>\n <% if (prop.required) { %>required <% } %>this.<%= prop.name %><% if (index < it.props.length - 1) { %>,<% } %>\n\n<% }) %>\n });\n\n factory <%= it.defName %>.fromJson(Map<String, dynamic> json) {\n return <%= it.defName %>(\n<% it.props.forEach((prop, index) => { %>\n <%= prop.name %>: json['<%= prop.originalName || prop.name %>']<% if (index < it.props.length - 1) { %>,<% } %>\n\n<% }) %>\n );\n }\n\n Map<String, dynamic> toJson() {\n final Map<String, dynamic> data = <String, dynamic>{};\n\n<% it.props.forEach((prop) => { %>\n data['<%= prop.originalName || prop.name %>'] = <%= prop.name %>;\n<% }) %>\n\n return data;\n }\n}\n","definitionHeader":"class <%= it.defName %> {","enum":"<% if (it.isEnum) { %>\nenum <%= it.name %> {\n <%= it.data.map(it.convertValue).join(\",\\n\\t\")%>\n \n}\n<% } else { %>\n<%~ it.data %> <%= it.name %>;\n<% } %>"}
29 changes: 27 additions & 2 deletions src/plugins/javascript/oxc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@ import { isolatedDeclarationSync, transformSync } from "oxc-transform";

import Logs from "../../console.ts";

const formatOxcError = (error: unknown): string => {
if (typeof error === "string") return error;
if (error instanceof Error) return error.message;

if (typeof error === "object" && error !== null) {
const obj = error as Record<string, unknown>;
const message = typeof obj.message === "string"
? obj.message
: JSON.stringify(obj, null, 2);
const codeframe = typeof obj.codeframe === "string"
? obj.codeframe
: undefined;
return [message, codeframe].filter(Boolean).join("\n\n");
}

try {
return JSON.stringify(error, null, 2);
} catch {
return String(error);
}
};

const formatOxcErrors = (errors: unknown[]): string =>
errors.map((error) => formatOxcError(error)).join("\n\n");

/**
* Generates a declaration file from the given source code.
*
Expand All @@ -18,7 +43,7 @@ export const generateDeclarationFile = (sourceCode: string): string => {
);

if (errors.length > 0) {
Logs.error(errors.join("\n"));
Logs.error(formatOxcErrors(errors));
return "";
}

Expand Down Expand Up @@ -48,7 +73,7 @@ export const oxcTransform = (
);

if (errors.length > 0) {
Logs.error(errors.join("\n"));
Logs.error(formatOxcErrors(errors));
return { code: "", declaration: "" };
}

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/swift/template/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// this file is auto generated.
export default {"actionImport":"import Foundation\n<% if (it.imports.length) { %>\nimport <%= it.typeFileName %>\n<% } %>\nimport APIClient\n","actionMethod":"<% /* API 方法注释 */ %>\n/// <%~ it.url %>\n<% if (it.summary) { %>\n/// <%~ it.summary %>\n<% } %>\n<% if (it.summary !== it.description) { %>\n<% if (it.summary && it.description) { %>\n///\n<% } %>\n<% if (it.description) { %>\n/// <%~ it.description %>\n<% } %>\n<% } %>\n\n<% if (it.deprecated) { %>\n@available(*, deprecated, message: \"This API is deprecated\")\n<% } %>\n<% /* API 方法 */ %>\npublic func <%= it.methodName %>(<% it.params.forEach((param, index) => { %>\n <%= param.name %>: <%= param.type %><% if (!param.required) { %>?<% } %><% if (index < it.params.length - 1) { %>,<% } %>\n<% }) %>) async throws -> <%= it.responseType %> {\n <% /* 处理路径参数 */ %>\n <% const pathParams = it.params.filter(p => p.category === 'path'); %>\n <% if (pathParams.length > 0) { %>\n let pathParams: [String: String] = [\n <% pathParams.forEach((param, index) => { %>\n \"<%= param.name %>\": String(<%= param.name %>)<% if (index < pathParams.length - 1) { %>,<% } %>\n <% }) %>\n ]\n let parsedUrl = \"<%= it.url %>\".parsePathParams(pathParams)\n <% } %>\n \n <% /* 处理请求参数 */ %>\n <% const bodyParams = it.params.filter(p => p.category === 'body' || p.category === 'formData'); %>\n <% const queryParams = it.params.filter(p => p.category === 'query'); %>\n <% if (bodyParams.length > 0 || queryParams.length > 0) { %>\n var params: [String: Any] = [:]\n <% bodyParams.forEach(param => { %>\n <% if (param.required) { %>\n params[\"<%= param.category %>\"] = <%= param.name %>\n <% } else { %>\n if let <%= param.name %> = <%= param.name %> {\n params[\"<%= param.category %>\"] = <%= param.name %>\n }\n <% } %>\n <% }) %>\n <% queryParams.forEach(param => { %>\n <% if (param.required) { %>\n params[\"<%= param.category %>\"] = <%= param.name %>\n <% } else { %>\n if let <%= param.name %> = <%= param.name %> {\n params[\"<%= param.category %>\"] = <%= param.name %>\n }\n <% } %>\n <% }) %>\n <% } %>\n \n let config = APIClientConfig(\n url: <% if (pathParams.length > 0) { %>parsedUrl<% } else { %>\"<%= it.url %>\"<% } %>,\n method: \"<%= it.method %>\"<% if (bodyParams.length > 0 || queryParams.length > 0) { %>,\n params: params<% } %>\n )\n \n return try await APIClient.shared.request(config, type: <%= it.responseType %>.self)\n}\n","definitionBody":"<% if (it.propCommit) { %>\n /// <%~ it.propCommit %>\n\n<% } %>\n public let <%= it.prop.name %>: <%= it.propType %><% if (!it.prop.required) { %>?<% } %>","definitionFooter":"}\n","definitionHeader":"public struct <%= it.defName %>: Codable {","enum":"<% if (it.isEnum) { %>\npublic enum <%= it.name %>: String, Codable {\n <% it.data.forEach(function(item) { %>\n case <%= it.convertValue(item).replace(/[^a-zA-Z0-9_]/g, '_') %> = \"<%= item %>\"\n <% }) %>\n}\n<% } else { %>\npublic typealias <%= it.name %> = <%= it.data %>\n<% } %>\n"}
export default {"actionImport":"import Foundation\n<% if (it.imports.length) { %>\nimport <%= it.typeFileName %>\n<% } %>\nimport APIClient\n","actionMethod":"<% /* API 方法注释 */ %>\n/// <%~ it.url %>\n<% if (it.summary) { %>\n/// <%~ it.summary %>\n<% } %>\n<% if (it.summary !== it.description) { %>\n<% if (it.summary && it.description) { %>\n///\n<% } %>\n<% if (it.description) { %>\n/// <%~ it.description %>\n<% } %>\n<% } %>\n\n<% if (it.deprecated) { %>\n@available(*, deprecated, message: \"This API is deprecated\")\n<% } %>\n<% /* API 方法 */ %>\npublic func <%= it.methodName %>(<% it.params.forEach((param, index) => { %>\n <%= param.name %>: <%= param.type %><% if (!param.required) { %>?<% } %><% if (index < it.params.length - 1) { %>,<% } %>\n<% }) %>) async throws -> <%= it.responseType %> {\n <% /* 处理路径参数 */ %>\n <% const pathParams = it.params.filter(p => p.category === 'path'); %>\n <% if (pathParams.length > 0) { %>\n let pathParams: [String: String] = [\n <% pathParams.forEach((param, index) => { %>\n \"<%= param.originalName || param.name %>\": String(<%= param.name %>)<% if (index < pathParams.length - 1) { %>,<% } %>\n <% }) %>\n ]\n let parsedUrl = \"<%= it.url %>\".parsePathParams(pathParams)\n <% } %>\n \n <% /* 处理请求参数 */ %>\n <% const bodyParams = it.params.filter(p => p.category === 'body' || p.category === 'formData'); %>\n <% const queryParams = it.params.filter(p => p.category === 'query'); %>\n <% if (bodyParams.length > 0 || queryParams.length > 0) { %>\n var params: [String: Any] = [:]\n <% bodyParams.forEach(param => { %>\n <% if (param.required) { %>\n params[\"<%= param.category %>\"] = <%= param.name %>\n <% } else { %>\n if let <%= param.name %> = <%= param.name %> {\n params[\"<%= param.category %>\"] = <%= param.name %>\n }\n <% } %>\n <% }) %>\n <% queryParams.forEach(param => { %>\n <% if (param.required) { %>\n params[\"<%= param.category %>\"] = <%= param.name %>\n <% } else { %>\n if let <%= param.name %> = <%= param.name %> {\n params[\"<%= param.category %>\"] = <%= param.name %>\n }\n <% } %>\n <% }) %>\n <% } %>\n \n let config = APIClientConfig(\n url: <% if (pathParams.length > 0) { %>parsedUrl<% } else { %>\"<%= it.url %>\"<% } %>,\n method: \"<%= it.method %>\"<% if (bodyParams.length > 0 || queryParams.length > 0) { %>,\n params: params<% } %>\n )\n \n return try await APIClient.shared.request(config, type: <%= it.responseType %>.self)\n}\n","definitionBody":"<% if (it.propCommit) { %>\n /// <%~ it.propCommit %>\n\n<% } %>\n public let <%= it.prop.name %>: <%= it.propType %><% if (!it.prop.required) { %>?<% } %>","definitionFooter":"<% if (it.props && it.props.some(prop => prop.originalName && prop.originalName !== prop.name)) { %>\n private enum CodingKeys: String, CodingKey {\n<% it.props.forEach(prop => { %>\n case <%= prop.name %> = \"<%= prop.originalName || prop.name %>\"\n<% }) %>\n }\n<% } %>\n}","definitionHeader":"public struct <%= it.defName %>: Codable {","enum":"<% if (it.isEnum) { %>\npublic enum <%= it.name %>: String, Codable {\n <% it.data.forEach(function(item) { %>\n case <%= it.convertValue(item).replace(/[^a-zA-Z0-9_]/g, '_') %> = \"<%= item %>\"\n <% }) %>\n}\n<% } else { %>\npublic typealias <%= it.name %> = <%= it.data %>\n<% } %>\n"}
Loading