@farris/cli 2.2.4 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/build-components.js +50 -50
- package/lib/commands/build-components.js.map +1 -1
- package/lib/commands/build-css.js +38 -38
- package/lib/commands/build-css.js.map +1 -1
- package/lib/commands/build-lib.js +25 -25
- package/lib/commands/build.js +14 -14
- package/lib/commands/build.js.map +1 -1
- package/lib/commands/create-app-options.js +74 -74
- package/lib/commands/create-app-options.js.map +1 -1
- package/lib/commands/create-app.js +126 -126
- package/lib/commands/create-app.js.map +1 -1
- package/lib/commands/create-options.js +29 -29
- package/lib/commands/create-options.js.map +1 -1
- package/lib/commands/create.js +224 -202
- package/lib/commands/create.js.map +1 -1
- package/lib/commands/dev-serve.js +18 -18
- package/lib/commands/extract-i18n.js +74 -74
- package/lib/commands/extract-i18n.js.map +1 -1
- package/lib/commands/generate/api.js +124 -124
- package/lib/commands/generate/api.js.map +1 -1
- package/lib/commands/generate/common.js +100 -100
- package/lib/commands/generate/common.js.map +1 -1
- package/lib/commands/generate/component.js +156 -156
- package/lib/commands/generate/component.js.map +1 -1
- package/lib/commands/generate/view.js +168 -168
- package/lib/commands/generate/view.js.map +1 -1
- package/lib/commands/preview-serve.js +16 -16
- package/lib/commands/workspace/add-app.js +180 -180
- package/lib/commands/workspace/add-app.js.map +1 -1
- package/lib/commands/workspace/create.js +379 -379
- package/lib/commands/workspace/create.js.map +1 -1
- package/lib/commands/workspace/inspect.js +30 -30
- package/lib/commands/workspace/inspect.js.map +1 -1
- package/lib/commands/workspace/verify.js +38 -38
- package/lib/commands/workspace/verify.js.map +1 -1
- package/lib/common/apply-catalog-deps.js +15 -15
- package/lib/common/apply-catalog-deps.js.map +1 -1
- package/lib/common/component-file-name.js +3 -3
- package/lib/common/constant.js +64 -64
- package/lib/common/constant.js.map +1 -1
- package/lib/common/create-app-target.js +46 -46
- package/lib/common/create-app-target.js.map +1 -1
- package/lib/common/generate-app.js +85 -85
- package/lib/common/generate-app.js.map +1 -1
- package/lib/common/get-dependencies.js +9 -9
- package/lib/common/get-farris-config.js +25 -25
- package/lib/common/get-farris-config.js.map +1 -1
- package/lib/common/get-version.js +6 -6
- package/lib/common/get-vite-config.js +106 -106
- package/lib/common/get-vite-config.js.map +1 -1
- package/lib/common/lib-package-exports.js +24 -24
- package/lib/common/lib-package-exports.js.map +1 -1
- package/lib/common/list-lib-components.js +23 -23
- package/lib/common/list-lib-components.js.map +1 -1
- package/lib/common/output.js +94 -94
- package/lib/common/output.js.map +1 -1
- package/lib/common/project-package-name.js +4 -4
- package/lib/common/safety.js +171 -171
- package/lib/common/safety.js.map +1 -1
- package/lib/common/template-manifest.js +16 -16
- package/lib/common/template-manifest.js.map +1 -1
- package/lib/common/template-renderer.js +114 -114
- package/lib/common/template-renderer.js.map +1 -1
- package/lib/common/transaction.js +83 -83
- package/lib/common/transaction.js.map +1 -1
- package/lib/common/verifier.js +150 -150
- package/lib/common/verifier.js.map +1 -1
- package/lib/config/vite-app.js +13 -13
- package/lib/config/vite-common.js +20 -20
- package/lib/config/vite-common.js.map +1 -1
- package/lib/config/vite-component.js +32 -32
- package/lib/config/vite-component.js.map +1 -1
- package/lib/config/vite-lib.js +26 -26
- package/lib/index.js +205 -205
- package/lib/index.js.map +1 -1
- package/lib/plugins/create-component-style.js +44 -44
- package/lib/plugins/create-package-json.js +48 -48
- package/lib/plugins/create-package-json.js.map +1 -1
- package/lib/plugins/dts.js +8 -8
- package/lib/plugins/html-system.js +9 -9
- package/lib/plugins/replace.js +17 -17
- package/lib/plugins/systemjs-bundle.js +15 -15
- package/lib/plugins/systemjs-bundle.js.map +1 -1
- package/package.json +1 -1
- package/templates/create/.npmrc.tpl +1 -0
- package/templates/lib/package.json +1 -1
- package/templates/mobile/package.json +1 -1
- package/templates/mobile/project.json +1 -1
- package/templates/web/package.json +1 -1
- package/templates/web/project.json +1 -1
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'fs';
|
|
2
|
-
import { join, basename, dirname, resolve } from 'path';
|
|
3
|
-
import { sync } from 'glob';
|
|
4
|
-
import { pathToFileURL } from 'url';
|
|
5
|
-
/**
|
|
6
|
-
* 抽取国际化资源到JSON文件
|
|
7
|
-
*/
|
|
8
|
-
async function loadConfig(configPath) {
|
|
9
|
-
const defaultConfigPath = resolve(process.cwd(), 'farris.i18n.mjs');
|
|
10
|
-
const finalPath = configPath || defaultConfigPath;
|
|
11
|
-
if (!existsSync(finalPath)) {
|
|
12
|
-
throw new Error(`配置文件不存在: ${finalPath}`);
|
|
13
|
-
}
|
|
14
|
-
const fileUrl = pathToFileURL(finalPath).href;
|
|
15
|
-
const config = (await import(fileUrl)).default;
|
|
16
|
-
return config;
|
|
17
|
-
}
|
|
18
|
-
async function extractByType(type, config, options) {
|
|
19
|
-
console.log(`正在提取 ${type} 资源...`);
|
|
20
|
-
const outputDir = options.outDir || config.outDir;
|
|
21
|
-
const localeMap = {};
|
|
22
|
-
// Process each include pattern
|
|
23
|
-
for (const pattern of config.include) {
|
|
24
|
-
const localeFiles = sync(pattern, {
|
|
25
|
-
cwd: process.cwd(),
|
|
26
|
-
absolute: true,
|
|
27
|
-
ignore: config.exclude
|
|
28
|
-
});
|
|
29
|
-
// Process each locale file
|
|
30
|
-
localeFiles.forEach(file => {
|
|
31
|
-
const componentName = basename(dirname(dirname(dirname(dirname(file)))));
|
|
32
|
-
const localeName = basename(file, '.json');
|
|
33
|
-
const content = readFileSync(file, 'utf8');
|
|
34
|
-
if (!content) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const resources = JSON.parse(content);
|
|
38
|
-
if (!localeMap[localeName]) {
|
|
39
|
-
localeMap[localeName] = {};
|
|
40
|
-
}
|
|
41
|
-
// Add component translations under component namespace
|
|
42
|
-
Object.assign(localeMap[localeName], resources);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
// Ensure output directory exists
|
|
46
|
-
if (!existsSync(outputDir)) {
|
|
47
|
-
mkdirSync(outputDir, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
// Write each locale to a separate file
|
|
50
|
-
Object.entries(localeMap).forEach(([locale, translations]) => {
|
|
51
|
-
const outputFile = join(outputDir, `${locale}.json`);
|
|
52
|
-
const content = config.minify
|
|
53
|
-
? JSON.stringify(translations)
|
|
54
|
-
: JSON.stringify(translations, null, 2);
|
|
55
|
-
writeFileSync(outputFile, content);
|
|
56
|
-
});
|
|
57
|
-
console.log(`${type} 资源提取完成!`);
|
|
58
|
-
}
|
|
59
|
-
export async function extractI18n(options = {}) {
|
|
60
|
-
console.log('正在抽取国际化资源...');
|
|
61
|
-
try {
|
|
62
|
-
const config = await loadConfig(options.configFile);
|
|
63
|
-
// 否则提取所有配置的类型
|
|
64
|
-
for (const [type, typeConfig] of Object.entries(config.extract)) {
|
|
65
|
-
if (typeConfig) {
|
|
66
|
-
await extractByType(type, typeConfig, options);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
console.error(error);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, basename, dirname, resolve } from 'path';
|
|
3
|
+
import { sync } from 'glob';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
/**
|
|
6
|
+
* 抽取国际化资源到JSON文件
|
|
7
|
+
*/
|
|
8
|
+
async function loadConfig(configPath) {
|
|
9
|
+
const defaultConfigPath = resolve(process.cwd(), 'farris.i18n.mjs');
|
|
10
|
+
const finalPath = configPath || defaultConfigPath;
|
|
11
|
+
if (!existsSync(finalPath)) {
|
|
12
|
+
throw new Error(`配置文件不存在: ${finalPath}`);
|
|
13
|
+
}
|
|
14
|
+
const fileUrl = pathToFileURL(finalPath).href;
|
|
15
|
+
const config = (await import(fileUrl)).default;
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
18
|
+
async function extractByType(type, config, options) {
|
|
19
|
+
console.log(`正在提取 ${type} 资源...`);
|
|
20
|
+
const outputDir = options.outDir || config.outDir;
|
|
21
|
+
const localeMap = {};
|
|
22
|
+
// Process each include pattern
|
|
23
|
+
for (const pattern of config.include) {
|
|
24
|
+
const localeFiles = sync(pattern, {
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
absolute: true,
|
|
27
|
+
ignore: config.exclude
|
|
28
|
+
});
|
|
29
|
+
// Process each locale file
|
|
30
|
+
localeFiles.forEach(file => {
|
|
31
|
+
const componentName = basename(dirname(dirname(dirname(dirname(file)))));
|
|
32
|
+
const localeName = basename(file, '.json');
|
|
33
|
+
const content = readFileSync(file, 'utf8');
|
|
34
|
+
if (!content) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const resources = JSON.parse(content);
|
|
38
|
+
if (!localeMap[localeName]) {
|
|
39
|
+
localeMap[localeName] = {};
|
|
40
|
+
}
|
|
41
|
+
// Add component translations under component namespace
|
|
42
|
+
Object.assign(localeMap[localeName], resources);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Ensure output directory exists
|
|
46
|
+
if (!existsSync(outputDir)) {
|
|
47
|
+
mkdirSync(outputDir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
// Write each locale to a separate file
|
|
50
|
+
Object.entries(localeMap).forEach(([locale, translations]) => {
|
|
51
|
+
const outputFile = join(outputDir, `${locale}.json`);
|
|
52
|
+
const content = config.minify
|
|
53
|
+
? JSON.stringify(translations)
|
|
54
|
+
: JSON.stringify(translations, null, 2);
|
|
55
|
+
writeFileSync(outputFile, content);
|
|
56
|
+
});
|
|
57
|
+
console.log(`${type} 资源提取完成!`);
|
|
58
|
+
}
|
|
59
|
+
export async function extractI18n(options = {}) {
|
|
60
|
+
console.log('正在抽取国际化资源...');
|
|
61
|
+
try {
|
|
62
|
+
const config = await loadConfig(options.configFile);
|
|
63
|
+
// 否则提取所有配置的类型
|
|
64
|
+
for (const [type, typeConfig] of Object.entries(config.extract)) {
|
|
65
|
+
if (typeConfig) {
|
|
66
|
+
await extractByType(type, typeConfig, options);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
75
|
//# sourceMappingURL=extract-i18n.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-i18n.js","sourceRoot":"","sources":["../../src/commands/extract-i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAsBpC;;GAEG;AACH,KAAK,UAAU,UAAU,CAAC,UAAmB;IACzC,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,iBAAiB,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,UAAU,IAAI,iBAAiB,CAAC;IAElD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE
|
|
1
|
+
{"version":3,"file":"extract-i18n.js","sourceRoot":"","sources":["../../src/commands/extract-i18n.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { findAppContext, normalizeGeneratorName, toPascalCase, toCamelCase } from './common.js';
|
|
4
|
-
import { Transaction } from '../../common/transaction.js';
|
|
5
|
-
import { sendJson, logInfo, fail, setJsonMode, createSpinner } from '../../common/output.js';
|
|
6
|
-
import { ExitCode } from '../../common/constant.js';
|
|
7
|
-
export async function generateApi(rawName, options = {}) {
|
|
8
|
-
const isJson = !!options.json;
|
|
9
|
-
setJsonMode(isJson);
|
|
10
|
-
if (!rawName) {
|
|
11
|
-
fail({
|
|
12
|
-
command: 'generate.api',
|
|
13
|
-
error: '必须指定 API 模块名称: farris-cli generate api <name>',
|
|
14
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
const { endpoint } = options;
|
|
18
|
-
if (!endpoint || typeof endpoint !== 'string' || !endpoint.trim()) {
|
|
19
|
-
fail({
|
|
20
|
-
command: 'generate.api',
|
|
21
|
-
error: '必须指定真实 API endpoint (--endpoint <path>),如: --endpoint /api/v1/orders',
|
|
22
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
const normalizedName = normalizeGeneratorName(rawName);
|
|
26
|
-
if (!normalizedName.valid) {
|
|
27
|
-
fail({
|
|
28
|
-
command: 'generate.api',
|
|
29
|
-
error: `非法的 API 名称: ${normalizedName.error}`,
|
|
30
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
const name = normalizedName.name;
|
|
34
|
-
const pascalName = toPascalCase(name);
|
|
35
|
-
const camelName = toCamelCase(name);
|
|
36
|
-
const method = (options.method || 'get').toLowerCase();
|
|
37
|
-
const appContext = findAppContext(options.cwd || process.cwd());
|
|
38
|
-
const apiDir = join(appContext.srcDir, 'api');
|
|
39
|
-
const targetApiFile = join(apiDir, `${name}.api.ts`);
|
|
40
|
-
const indexPath = join(apiDir, 'index.ts');
|
|
41
|
-
const httpClientPath = join(apiDir, 'http-client.ts');
|
|
42
|
-
if (existsSync(targetApiFile)) {
|
|
43
|
-
fail({
|
|
44
|
-
command: 'generate.api',
|
|
45
|
-
error: `API 文件已存在: ${targetApiFile}`,
|
|
46
|
-
exitCode: ExitCode.TARGET_CONFLICT
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
const hasHttpClient = existsSync(httpClientPath);
|
|
50
|
-
const clientPath = options.client || './http-client';
|
|
51
|
-
if (!hasHttpClient && !options.client && !options.initClient) {
|
|
52
|
-
fail({
|
|
53
|
-
command: 'generate.api',
|
|
54
|
-
error: '未发现现有 HTTP client (src/api/http-client.ts)。请使用 --init-client 初始化标准客户端,或通过 --client <path> 指定已有客户端路径。',
|
|
55
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
const transaction = new Transaction();
|
|
59
|
-
const spinner = createSpinner(`正在生成 API: ${name}...`);
|
|
60
|
-
spinner.start();
|
|
61
|
-
try {
|
|
62
|
-
if (!existsSync(apiDir)) {
|
|
63
|
-
mkdirSync(apiDir, { recursive: true });
|
|
64
|
-
transaction.recordCreatedDir(apiDir);
|
|
65
|
-
}
|
|
66
|
-
const filesCreated = [];
|
|
67
|
-
const filesModified = [];
|
|
68
|
-
// 1. 若需要且不存在,初始化标准 http-client.ts
|
|
69
|
-
if (!hasHttpClient && options.initClient) {
|
|
70
|
-
transaction.prepareWrite(httpClientPath);
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findAppContext, normalizeGeneratorName, toPascalCase, toCamelCase } from './common.js';
|
|
4
|
+
import { Transaction } from '../../common/transaction.js';
|
|
5
|
+
import { sendJson, logInfo, fail, setJsonMode, createSpinner } from '../../common/output.js';
|
|
6
|
+
import { ExitCode } from '../../common/constant.js';
|
|
7
|
+
export async function generateApi(rawName, options = {}) {
|
|
8
|
+
const isJson = !!options.json;
|
|
9
|
+
setJsonMode(isJson);
|
|
10
|
+
if (!rawName) {
|
|
11
|
+
fail({
|
|
12
|
+
command: 'generate.api',
|
|
13
|
+
error: '必须指定 API 模块名称: farris-cli generate api <name>',
|
|
14
|
+
exitCode: ExitCode.GENERAL_ERROR
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const { endpoint } = options;
|
|
18
|
+
if (!endpoint || typeof endpoint !== 'string' || !endpoint.trim()) {
|
|
19
|
+
fail({
|
|
20
|
+
command: 'generate.api',
|
|
21
|
+
error: '必须指定真实 API endpoint (--endpoint <path>),如: --endpoint /api/v1/orders',
|
|
22
|
+
exitCode: ExitCode.GENERAL_ERROR
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const normalizedName = normalizeGeneratorName(rawName);
|
|
26
|
+
if (!normalizedName.valid) {
|
|
27
|
+
fail({
|
|
28
|
+
command: 'generate.api',
|
|
29
|
+
error: `非法的 API 名称: ${normalizedName.error}`,
|
|
30
|
+
exitCode: ExitCode.GENERAL_ERROR
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const name = normalizedName.name;
|
|
34
|
+
const pascalName = toPascalCase(name);
|
|
35
|
+
const camelName = toCamelCase(name);
|
|
36
|
+
const method = (options.method || 'get').toLowerCase();
|
|
37
|
+
const appContext = findAppContext(options.cwd || process.cwd());
|
|
38
|
+
const apiDir = join(appContext.srcDir, 'api');
|
|
39
|
+
const targetApiFile = join(apiDir, `${name}.api.ts`);
|
|
40
|
+
const indexPath = join(apiDir, 'index.ts');
|
|
41
|
+
const httpClientPath = join(apiDir, 'http-client.ts');
|
|
42
|
+
if (existsSync(targetApiFile)) {
|
|
43
|
+
fail({
|
|
44
|
+
command: 'generate.api',
|
|
45
|
+
error: `API 文件已存在: ${targetApiFile}`,
|
|
46
|
+
exitCode: ExitCode.TARGET_CONFLICT
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const hasHttpClient = existsSync(httpClientPath);
|
|
50
|
+
const clientPath = options.client || './http-client';
|
|
51
|
+
if (!hasHttpClient && !options.client && !options.initClient) {
|
|
52
|
+
fail({
|
|
53
|
+
command: 'generate.api',
|
|
54
|
+
error: '未发现现有 HTTP client (src/api/http-client.ts)。请使用 --init-client 初始化标准客户端,或通过 --client <path> 指定已有客户端路径。',
|
|
55
|
+
exitCode: ExitCode.GENERAL_ERROR
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const transaction = new Transaction();
|
|
59
|
+
const spinner = createSpinner(`正在生成 API: ${name}...`);
|
|
60
|
+
spinner.start();
|
|
61
|
+
try {
|
|
62
|
+
if (!existsSync(apiDir)) {
|
|
63
|
+
mkdirSync(apiDir, { recursive: true });
|
|
64
|
+
transaction.recordCreatedDir(apiDir);
|
|
65
|
+
}
|
|
66
|
+
const filesCreated = [];
|
|
67
|
+
const filesModified = [];
|
|
68
|
+
// 1. 若需要且不存在,初始化标准 http-client.ts
|
|
69
|
+
if (!hasHttpClient && options.initClient) {
|
|
70
|
+
transaction.prepareWrite(httpClientPath);
|
|
71
71
|
const clientContent = `/**
|
|
72
72
|
* Farris 标准 HTTP 请求客户端封装
|
|
73
73
|
*/
|
|
@@ -106,12 +106,12 @@ export async function request<T = any>(url: string, options: RequestOptions = {}
|
|
|
106
106
|
|
|
107
107
|
return response.json();
|
|
108
108
|
}
|
|
109
|
-
`;
|
|
110
|
-
writeFileSync(httpClientPath, clientContent, 'utf-8');
|
|
111
|
-
filesCreated.push(httpClientPath);
|
|
112
|
-
}
|
|
113
|
-
// 2. 生成 API 文件: src/api/<name>.api.ts
|
|
114
|
-
transaction.prepareWrite(targetApiFile);
|
|
109
|
+
`;
|
|
110
|
+
writeFileSync(httpClientPath, clientContent, 'utf-8');
|
|
111
|
+
filesCreated.push(httpClientPath);
|
|
112
|
+
}
|
|
113
|
+
// 2. 生成 API 文件: src/api/<name>.api.ts
|
|
114
|
+
transaction.prepareWrite(targetApiFile);
|
|
115
115
|
const apiFunctionContent = `import { request } from ${JSON.stringify(clientPath)};
|
|
116
116
|
|
|
117
117
|
export interface ${pascalName}Item {
|
|
@@ -137,52 +137,52 @@ export async function get${pascalName}List(params?: ${pascalName}Params): Promis
|
|
|
137
137
|
params
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
|
-
`;
|
|
141
|
-
writeFileSync(targetApiFile, apiFunctionContent, 'utf-8');
|
|
142
|
-
filesCreated.push(targetApiFile);
|
|
143
|
-
// 3. 维护 src/api/index.ts
|
|
144
|
-
transaction.prepareWrite(indexPath);
|
|
145
|
-
let indexContent = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : '';
|
|
146
|
-
const exportStatement = `export * from './${name}.api';\n`;
|
|
147
|
-
if (!indexContent.includes(`./${name}.api`)) {
|
|
148
|
-
indexContent += exportStatement;
|
|
149
|
-
writeFileSync(indexPath, indexContent, 'utf-8');
|
|
150
|
-
if (existsSync(indexPath)) {
|
|
151
|
-
filesModified.push(indexPath);
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
filesCreated.push(indexPath);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
transaction.commit();
|
|
158
|
-
spinner.succeed(`API ${name} 生成成功!`);
|
|
159
|
-
if (isJson) {
|
|
160
|
-
sendJson({
|
|
161
|
-
command: 'generate.api',
|
|
162
|
-
ok: true,
|
|
163
|
-
apiName: name,
|
|
164
|
-
endpoint,
|
|
165
|
-
method,
|
|
166
|
-
targetApiFile,
|
|
167
|
-
filesCreated,
|
|
168
|
-
filesModified
|
|
169
|
-
});
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
logInfo(`\n✨ 已成功生成 API: src/api/${name}.api.ts`);
|
|
173
|
-
filesCreated.forEach((f) => logInfo(` + ${f}`));
|
|
174
|
-
if (filesModified.length > 0) {
|
|
175
|
-
filesModified.forEach((f) => logInfo(` ~ ${f} (更新导出)`));
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
catch (err) {
|
|
179
|
-
spinner.fail('生成 API 失败,正在回滚...');
|
|
180
|
-
transaction.rollback();
|
|
181
|
-
fail({
|
|
182
|
-
command: 'generate.api',
|
|
183
|
-
error: `生成 API 失败: ${err.message}`,
|
|
184
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
140
|
+
`;
|
|
141
|
+
writeFileSync(targetApiFile, apiFunctionContent, 'utf-8');
|
|
142
|
+
filesCreated.push(targetApiFile);
|
|
143
|
+
// 3. 维护 src/api/index.ts
|
|
144
|
+
transaction.prepareWrite(indexPath);
|
|
145
|
+
let indexContent = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : '';
|
|
146
|
+
const exportStatement = `export * from './${name}.api';\n`;
|
|
147
|
+
if (!indexContent.includes(`./${name}.api`)) {
|
|
148
|
+
indexContent += exportStatement;
|
|
149
|
+
writeFileSync(indexPath, indexContent, 'utf-8');
|
|
150
|
+
if (existsSync(indexPath)) {
|
|
151
|
+
filesModified.push(indexPath);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
filesCreated.push(indexPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
transaction.commit();
|
|
158
|
+
spinner.succeed(`API ${name} 生成成功!`);
|
|
159
|
+
if (isJson) {
|
|
160
|
+
sendJson({
|
|
161
|
+
command: 'generate.api',
|
|
162
|
+
ok: true,
|
|
163
|
+
apiName: name,
|
|
164
|
+
endpoint,
|
|
165
|
+
method,
|
|
166
|
+
targetApiFile,
|
|
167
|
+
filesCreated,
|
|
168
|
+
filesModified
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
logInfo(`\n✨ 已成功生成 API: src/api/${name}.api.ts`);
|
|
173
|
+
filesCreated.forEach((f) => logInfo(` + ${f}`));
|
|
174
|
+
if (filesModified.length > 0) {
|
|
175
|
+
filesModified.forEach((f) => logInfo(` ~ ${f} (更新导出)`));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
spinner.fail('生成 API 失败,正在回滚...');
|
|
180
|
+
transaction.rollback();
|
|
181
|
+
fail({
|
|
182
|
+
command: 'generate.api',
|
|
183
|
+
error: `生成 API 失败: ${err.message}`,
|
|
184
|
+
exitCode: ExitCode.GENERAL_ERROR
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
188
|
//# sourceMappingURL=api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/commands/generate/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAW,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EACL,QAAQ,EACR,OAAO,EAEP,IAAI,EACJ,WAAW,EACX,aAAa,EACd,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAWpD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAe,EAAE,UAA8B,EAAE;IACjF,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAC9B,WAAW,CAAC,MAAM,CAAC,CAAC;IAEpB,IAAI,CAAC,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/commands/generate/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
import { existsSync, readdirSync, statSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve, join, dirname } from 'node:path';
|
|
3
|
-
import { fail } from '../../common/output.js';
|
|
4
|
-
import { ExitCode } from '../../common/constant.js';
|
|
5
|
-
export function toKebabCase(str) {
|
|
6
|
-
return str
|
|
7
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
8
|
-
.replace(/[\s_]+/g, '-')
|
|
9
|
-
.toLowerCase()
|
|
10
|
-
.replace(/^-+|-+$/g, '');
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* 生成器名称必须可安全地映射为文件名和 TypeScript 标识符。
|
|
14
|
-
* 先校验原始输入,避免 toKebabCase 保留路径分隔符后造成目录穿越。
|
|
15
|
-
*/
|
|
16
|
-
export function normalizeGeneratorName(value) {
|
|
17
|
-
const raw = typeof value === 'string' ? value.trim() : '';
|
|
18
|
-
if (!raw) {
|
|
19
|
-
return { valid: false, error: '名称不能为空' };
|
|
20
|
-
}
|
|
21
|
-
if (!/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/.test(raw)) {
|
|
22
|
-
return {
|
|
23
|
-
valid: false,
|
|
24
|
-
error: '名称仅支持字母、数字和短横线,且不能以短横线开头或结尾'
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
return { valid: true, name: toKebabCase(raw) };
|
|
28
|
-
}
|
|
29
|
-
export function toPascalCase(str) {
|
|
30
|
-
const kebab = toKebabCase(str);
|
|
31
|
-
return kebab
|
|
32
|
-
.split('-')
|
|
33
|
-
.filter(Boolean)
|
|
34
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
35
|
-
.join('');
|
|
36
|
-
}
|
|
37
|
-
export function toCamelCase(str) {
|
|
38
|
-
const pascal = toPascalCase(str);
|
|
39
|
-
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
40
|
-
}
|
|
41
|
-
export function toReadableTitle(str) {
|
|
42
|
-
const kebab = toKebabCase(str);
|
|
43
|
-
return kebab
|
|
44
|
-
.split('-')
|
|
45
|
-
.filter(Boolean)
|
|
46
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
47
|
-
.join(' ');
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* 向上查找当前所在应用目录
|
|
51
|
-
*/
|
|
52
|
-
export function findAppContext(startDir = process.cwd()) {
|
|
53
|
-
let current = resolve(startDir);
|
|
54
|
-
while (true) {
|
|
55
|
-
const pkgPath = join(current, 'package.json');
|
|
56
|
-
const srcPath = join(current, 'src');
|
|
57
|
-
if (existsSync(pkgPath) && existsSync(srcPath) && statSync(srcPath).isDirectory()) {
|
|
58
|
-
try {
|
|
59
|
-
const pkgContent = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
60
|
-
// 排除根 workspace 的 package.json (根一般有 pnpm-workspace.yaml 且无 src)
|
|
61
|
-
return {
|
|
62
|
-
appRoot: current,
|
|
63
|
-
srcDir: srcPath,
|
|
64
|
-
pkgJsonPath: pkgPath,
|
|
65
|
-
pkgJson: pkgContent
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// 继续查找
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const parent = dirname(current);
|
|
73
|
-
if (parent === current) {
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
current = parent;
|
|
77
|
-
}
|
|
78
|
-
// 检查是否在 workspace 根目录执行
|
|
79
|
-
const wsYaml = join(startDir, 'pnpm-workspace.yaml');
|
|
80
|
-
if (existsSync(wsYaml)) {
|
|
81
|
-
const packagesDir = join(startDir, 'packages');
|
|
82
|
-
if (existsSync(packagesDir)) {
|
|
83
|
-
const apps = readdirSync(packagesDir).filter((d) => statSync(join(packagesDir, d)).isDirectory());
|
|
84
|
-
if (apps.length === 1) {
|
|
85
|
-
const singleAppDir = join(packagesDir, apps[0]);
|
|
86
|
-
return findAppContext(singleAppDir);
|
|
87
|
-
}
|
|
88
|
-
fail({
|
|
89
|
-
command: 'generate',
|
|
90
|
-
error: `当前处于 Workspace 根目录,存在多个应用 (${apps.join(', ')})。请先进入具体应用目录 (如 cd packages/${apps[0]}) 后再执行 generate 命令。`,
|
|
91
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
fail({
|
|
96
|
-
command: 'generate',
|
|
97
|
-
error: '未找到合法的前端应用工程目录 (必须包含 package.json 与 src/ 目录)',
|
|
98
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
99
|
-
});
|
|
100
|
-
}
|
|
1
|
+
import { existsSync, readdirSync, statSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve, join, dirname } from 'node:path';
|
|
3
|
+
import { fail } from '../../common/output.js';
|
|
4
|
+
import { ExitCode } from '../../common/constant.js';
|
|
5
|
+
export function toKebabCase(str) {
|
|
6
|
+
return str
|
|
7
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
8
|
+
.replace(/[\s_]+/g, '-')
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/^-+|-+$/g, '');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 生成器名称必须可安全地映射为文件名和 TypeScript 标识符。
|
|
14
|
+
* 先校验原始输入,避免 toKebabCase 保留路径分隔符后造成目录穿越。
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeGeneratorName(value) {
|
|
17
|
+
const raw = typeof value === 'string' ? value.trim() : '';
|
|
18
|
+
if (!raw) {
|
|
19
|
+
return { valid: false, error: '名称不能为空' };
|
|
20
|
+
}
|
|
21
|
+
if (!/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/.test(raw)) {
|
|
22
|
+
return {
|
|
23
|
+
valid: false,
|
|
24
|
+
error: '名称仅支持字母、数字和短横线,且不能以短横线开头或结尾'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return { valid: true, name: toKebabCase(raw) };
|
|
28
|
+
}
|
|
29
|
+
export function toPascalCase(str) {
|
|
30
|
+
const kebab = toKebabCase(str);
|
|
31
|
+
return kebab
|
|
32
|
+
.split('-')
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
35
|
+
.join('');
|
|
36
|
+
}
|
|
37
|
+
export function toCamelCase(str) {
|
|
38
|
+
const pascal = toPascalCase(str);
|
|
39
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
40
|
+
}
|
|
41
|
+
export function toReadableTitle(str) {
|
|
42
|
+
const kebab = toKebabCase(str);
|
|
43
|
+
return kebab
|
|
44
|
+
.split('-')
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
47
|
+
.join(' ');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 向上查找当前所在应用目录
|
|
51
|
+
*/
|
|
52
|
+
export function findAppContext(startDir = process.cwd()) {
|
|
53
|
+
let current = resolve(startDir);
|
|
54
|
+
while (true) {
|
|
55
|
+
const pkgPath = join(current, 'package.json');
|
|
56
|
+
const srcPath = join(current, 'src');
|
|
57
|
+
if (existsSync(pkgPath) && existsSync(srcPath) && statSync(srcPath).isDirectory()) {
|
|
58
|
+
try {
|
|
59
|
+
const pkgContent = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
60
|
+
// 排除根 workspace 的 package.json (根一般有 pnpm-workspace.yaml 且无 src)
|
|
61
|
+
return {
|
|
62
|
+
appRoot: current,
|
|
63
|
+
srcDir: srcPath,
|
|
64
|
+
pkgJsonPath: pkgPath,
|
|
65
|
+
pkgJson: pkgContent
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// 继续查找
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const parent = dirname(current);
|
|
73
|
+
if (parent === current) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
current = parent;
|
|
77
|
+
}
|
|
78
|
+
// 检查是否在 workspace 根目录执行
|
|
79
|
+
const wsYaml = join(startDir, 'pnpm-workspace.yaml');
|
|
80
|
+
if (existsSync(wsYaml)) {
|
|
81
|
+
const packagesDir = join(startDir, 'packages');
|
|
82
|
+
if (existsSync(packagesDir)) {
|
|
83
|
+
const apps = readdirSync(packagesDir).filter((d) => statSync(join(packagesDir, d)).isDirectory());
|
|
84
|
+
if (apps.length === 1) {
|
|
85
|
+
const singleAppDir = join(packagesDir, apps[0]);
|
|
86
|
+
return findAppContext(singleAppDir);
|
|
87
|
+
}
|
|
88
|
+
fail({
|
|
89
|
+
command: 'generate',
|
|
90
|
+
error: `当前处于 Workspace 根目录,存在多个应用 (${apps.join(', ')})。请先进入具体应用目录 (如 cd packages/${apps[0]}) 后再执行 generate 命令。`,
|
|
91
|
+
exitCode: ExitCode.GENERAL_ERROR
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
fail({
|
|
96
|
+
command: 'generate',
|
|
97
|
+
error: '未找到合法的前端应用工程目录 (必须包含 package.json 与 src/ 目录)',
|
|
98
|
+
exitCode: ExitCode.GENERAL_ERROR
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
101
|
//# sourceMappingURL=common.js.map
|