Skip to content

Commit

Permalink
feat(cli): internationalization support & build upgrades & responsive…
Browse files Browse the repository at this point in the history
… config #17 #23
  • Loading branch information
seho-dev committed Apr 5, 2023
1 parent cedd3c1 commit 709b4f0
Show file tree
Hide file tree
Showing 31 changed files with 2,349 additions and 1,936 deletions.
4 changes: 4 additions & 0 deletions build/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default defineBuildConfig({
entries: ['./src/index'],
outDir: 'lib',
declaration: true,
failOnWarn: false,
rollup: {
emitCJS: true,
// 从tsconfig.json读取paths
Expand All @@ -14,6 +15,9 @@ export default defineBuildConfig({
cjsBridge: true,
esbuild: {
target: 'node12'
},
dts: {
respectExternal: false
}
}
});
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
"husky": "^7.0.4",
"lint-staged": "^11.2.6",
"prettier": "^2.6.2",
"typescript": "^4.6.3",
"unbuild": "^0.7.4",
"typescript": "4.9.5",
"unbuild": "^1.2.0",
"vitest": "latest"
},
"dependencies": {
"@swordjs/h3": "^0.7.6",
"@vue/reactivity": "^3.2.47",
"i18next": "^22.4.14",
"i18next-fs-backend": "^2.1.1",
"mri": "^1.2.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/.typesafe-i18n.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"adapters": [],
"$schema": "https://unpkg.com/[email protected]/schema/typesafe-i18n.json"
}
14 changes: 8 additions & 6 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
"registry": "https://registry.npmjs.org/"
},
"scripts": {
"dev": "nodemon --watch src/ -C -e ts --debug -x 'npm run build'",
"dev": "nodemon --watch src/ -C -e ts --debug -x 'npm run build' & npm run typesafe-i18n",
"build": "unbuild",
"test:cli:dev": "node ./lib/index.cjs dev",
"test:cli:init": "node ./lib/index.cjs init",
"test:cli:doc": "node ./lib/index.cjs doc"
"test:cli:doc": "node ./lib/index.cjs doc",
"typesafe-i18n": "typesafe-i18n"
},
"bin": {
"sword": "./lib/index.cjs"
Expand All @@ -38,22 +39,23 @@
"@types/mv": "^2.1.2",
"openapi-types": "^11.0.0",
"typescript": "^4.3.5",
"unbuild": "^0.6.9",
"unplugin": "^1.3.1"
"unplugin": "^1.3.1",
"unbuild": "^1.2.0"
},
"dependencies": {
"@swordjs/esbuild-plugin-condition-comment-macro": "^1.0.1",
"@swordjs/esbuild-register": "0.0.2",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
"consola": "^2.15.3",
"download": "^8.0.0",
"enquirer": "^2.3.6",
"esbuild": "^0.14.27",
"esbuild-register": "^3.3.3",
"esbuild": "^0.15.13",
"glob": "^9.3.1",
"mv": "^2.1.1",
"ohmyfetch": "^0.4.15",
"tsbuffer-proto-generator": "^1.7.1",
"typesafe-i18n": "^5.24.3",
"unconfig": "^0.3.1",
"unimport": "^3.0.4"
}
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { writeFileRecursive, copyDir } from '~util/file';
import { esbuildPluginConditionalCompiler, esbuildDefineConditionalCompiler } from './core/conditionalCompiler';
import { esbuildPluginAutoImport } from './core/autoImport';
import { env } from '~types/env';
import { t } from './i18n/i18n-node';
import { generateSchema } from './core/api';
import type { Argv } from 'mri';
import type { CommandConfig } from '../../../typings/config';
import { generateSchema } from './core/api';

type BuildOptions = {
skipPackageJson?: boolean;
Expand Down Expand Up @@ -94,8 +95,8 @@ export default async (args: Argv<CommandConfig>) => {
// 拷贝shim文件夹到server中
copyDir(resolve(process.cwd(), `.sword/shim`), resolve(process.cwd(), `.sword/build/server/.shim`));
build(args, {
success: () => log.success(`[server]📦 打包成功`),
error: () => log.err(`[server]📦 打包出现未知问题`)
success: () => log.success(`[server]📦 ${t.Server_Pack_Success()}`),
error: () => log.err(`[server]📦 ${t.Server_Pack_Failed()}`)
});
} else if (args.platform === 'unicloud') await buildUnicloudApp(args);
} catch (e) {
Expand Down
104 changes: 43 additions & 61 deletions packages/cli/src/core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,127 +5,109 @@ import { traverseSourceDir, writeFileRecursive } from '~util/file';
import { getKey } from '~util/map';
import log from './log';
import { existsSync } from 'fs';
import type { HttpApiReturn, HttpApiInstructType } from '#types/index';
import { t } from './../i18n/i18n-node';
import type { HttpApiReturn, HttpApiInstructType } from '~types/index';
import { APP_SRC_DIR, APP_API_DIR, API_SUITE_FILES, API_SUITE_INDEX_FILE, API_SUITE_PROTO_FILE } from '~util/constants';

export type Result = Record<
string,
{
path?: string;
method?: string[];
protoPath?: string;
proto: Record<string, unknown>;
}
>;

type Options = {
dev?: boolean;
format?: boolean;
keepComment?: boolean;
};

type Map = Record<string, { path: string; method: string[]; protoPath?: string; type: HttpApiInstructType }>;

/**
*
* 生成apimap以及api路径数组(用于打包产物分析)
* @param {string} [apiDir='api']
* @param {string} [dir='src']
* Generate apimap
* @return {*} {Promise<{
* // apiPaths用于cli构建产物,用于esbuild打包分析, 只需要真实的api路径,不需要指示器强行修改的api路径
* apiPaths: string[];
* apiMap: Map;
* }>}
*/
export const getApiMap = async (
apiDir = 'api',
dir = 'src'
): Promise<{
// apiPaths用于cli构建产物,用于esbuild打包分析, 只需要真实的api路径,不需要指示器强行修改的api路径
apiPaths: string[];
export const getApiMap = async (): Promise<{
apiMap: Map;
}> => {
const require = createRequire(import.meta.url);
const apiPaths: Set<string> = new Set();
const apiMap: Map = {};
const files = traverseSourceDir(resolve(dir, apiDir));
const files = traverseSourceDir(resolve(APP_SRC_DIR, APP_API_DIR));
for (const key in files) {
// 解构path和d
const [path, d] = files[key];
const modulePath = resolve(path, d);
delete require.cache[modulePath];
if (['index.ts', 'proto.ts'].includes(d)) {
// apiPath 比如hello/detail 诸如此类
// 如果在windows下, 则路径中的\被转义成\\, 所以需要进行转义
const apiPath = path.substring(path.lastIndexOf(apiDir)).substring(apiDir.length).replace(/\\/g, '/');
// 执行函数,获取instruct指示器
if (d === 'index.ts') {
apiPaths.add(apiPath);
if (API_SUITE_FILES.includes(d)) {
// apiPath such as hello/detail and so on
// If it is under windows, the \ in the path is escaped as \\, so it needs to be escaped
const apiPath = path.substring(path.lastIndexOf(APP_API_DIR)).substring(APP_API_DIR.length).replace(/\\/g, '/');
// Execute the function and get the instruct indicator
if (d === API_SUITE_INDEX_FILE) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const module = require(modulePath) as any;
const { instruct }: HttpApiReturn<any> = module.default ?? module.main;
// 解析instruct, 是一个map
// Parse instruct, is a map
instruct.forEach((value, key) => {
let _key = getKey(`/${apiDir}`, apiPath, key);
// 在windows环境下, /会被转义为\\, 所以需要转义为/
let _key = getKey(`/${APP_API_DIR}`, apiPath, key);
// In windows environment, / is escaped to \\, so it needs to be escaped to /
_key = _key.replace(/\\/g, '/');
apiMap[_key] = {
path: modulePath,
type: value.type,
// set转换成数组
method: [...value.methods]
};
// 如果当前目录下,存在proto.ts文件,则记录
if (existsSync(resolve(path, 'proto.ts'))) {
// If the proto.ts file exists in the current directory, record
if (existsSync(resolve(path, API_SUITE_PROTO_FILE))) {
apiMap[_key] = { ...apiMap[_key], protoPath: resolve(path, 'proto.ts') };
}
});
}
}
}
return {
apiPaths: [...apiPaths],
apiMap
};
};

export type Result = Record<
string,
{
path?: string;
method?: string[];
protoPath?: string;
proto: Record<string, unknown>;
}
>;
type Options = {
dev?: boolean;
format?: boolean;
keepComment?: boolean;
};

/**
*
* 检索资源目录生成api路径数组 & protoMap & proto ast树
* Retrieve resource directory to generate api path array & protoMap & proto ast tree
* @param {(string | null)} outPath
* @param {Options} [options={
* // 是否是dev环境,如果是dev环境,则不生成除了proto的其他内容
* // Whether it is a dev environment or not, if it is a dev environment, no other content other than proto is generated
* dev: false,
* // 是否格式化
* format: false,
* keepComment: false
* }]
* @return {*} {Promise<{
* apiPaths: string[];
* apiResult: Result;
* }>}
*/
export const generateSchema = async (
outPath: string | null,
options: Options = {
// 是否是dev环境,如果是dev环境,则不生成除了proto的其他内容
// Whether it is a dev environment or not, if it is a dev environment, no other content other than proto is generated
dev: false,
// 是否格式化
format: false,
keepComment: false
}
): Promise<{
apiPaths: string[];
apiResult: Result;
}> => {
const { apiMap, apiPaths } = await getApiMap();
const { apiMap } = await getApiMap();
const result: Result = {};
try {
// 迭代apimap
for (const key in apiMap) {
// 生成proto信息
const generator = new TSBufferProtoGenerator({
keepComment: options.keepComment
});
// 如果是dev环境, 则仅仅生成proto
// If it is a dev environment, only the proto is generated
if (apiMap[key].protoPath) {
result[key] = {
proto: await generator.generate(apiMap[key].protoPath as any),
Expand All @@ -140,14 +122,14 @@ export const generateSchema = async (
};
}
}
// 判断outPath是否存在,如果存在,就把api.json输出到指定目录
// Determine if outPath exists, and if so, output api.json to the specified directory
if (outPath) {
// 生成api json文件到指定目录
// Generate API JSON file to specified directory
await writeFileRecursive(outPath, JSON.stringify(result, null, typeof options?.format === 'undefined' ? undefined : 2));
log.success('API.Schema加载成功');
log.success(t.Schema_Loaded_Successfully());
}
} catch (error) {
log.err(`API.Schema加载错误: ${error}`);
log.err(t.Schema_Loaded_Failed(error));
}
return { apiPaths, apiResult: result };
return { apiResult: result };
};
6 changes: 3 additions & 3 deletions packages/cli/src/core/autoImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const autoImportsPresets: Preset[] = [
let importCode: string;
let toExports: (filepath?: string | undefined) => Promise<string>;
let generateTypeDeclarations: (options?: TypeDeclarationOptions | undefined) => Promise<string>;
let esbuildPluginAutoImport: EsbuildPlugin | undefined;
let esbuildPluginAutoImport: EsbuildPlugin;

const getImportCode = async () => {
if (importCode) return importCode;
Expand All @@ -37,8 +37,8 @@ const generateTypeDeclarationsFile = async () => {

export default () => {
const options = {
presets: autoImportsPresets.concat(configData.autoImport?.presets || []),
imports: configData.autoImport?.imports || []
presets: autoImportsPresets.concat(configData.value.autoImport?.presets || []),
imports: configData.value.autoImport?.imports || []
};
const unimport = createUnimport(options);
toExports = unimport.toExports;
Expand Down
24 changes: 13 additions & 11 deletions packages/cli/src/core/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { t } from 'i18next';
import { cwd } from 'process';
import { loadConfig } from 'unconfig';
import { getPackageJson } from '~util/package';
import autoImport from '../core/autoImport';
import { init } from './i18n';
import { loadI18n } from '../i18n/i18n-node';
import { ref } from '@vue/reactivity';
import type { Config } from '../../typings/config';

export let configData: Required<Config>;
export const configData = ref<Required<Config>>({} as any);

const packageData = getPackageJson();

Expand All @@ -24,7 +24,7 @@ const defaultConfig: Config = {
}
}
},
language: 'EN'
language: 'en'
};

export const initConfig = async () => {
Expand All @@ -45,14 +45,16 @@ export const initConfig = async () => {
],
merge: false
});
if (typeof config === 'undefined') return defaultConfig;
configData = mergeConfig(config, defaultConfig) as any;
if (typeof config === 'undefined') {
configData.value = defaultConfig as Required<Config>;
return;
}
configData.value = mergeConfig(config, defaultConfig);
await afterInitConfig();
return configData;
};

// recursively iterate through defaultConfig, and if there is no configuration in config, use the configuration in defaultConfig
const mergeConfig = (config: Config, defaultConfig: Config) => {
const mergeConfig = (config: Config, defaultConfig: Config): Required<Config> => {
let key: keyof typeof config;
for (key in defaultConfig) {
// If there is an object in the configuration, then recursively traverse
Expand All @@ -62,12 +64,12 @@ const mergeConfig = (config: Config, defaultConfig: Config) => {
config[key] = defaultConfig[key] as any;
}
}
return config;
return config as Required<Config>;
};

const afterInitConfig = async () => {
// i18n
await init();
// Load i18n
await loadI18n(configData.value.language);
// The automatically imported configuration items are initialized in autoImport
await autoImport();
};
Expand Down
16 changes: 0 additions & 16 deletions packages/cli/src/core/i18n.ts

This file was deleted.

Loading

0 comments on commit 709b4f0

Please sign in to comment.