tools-cc 1.0.7 → 1.0.8
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/CHANGELOG.md +13 -0
- package/CHANGELOG_en.md +13 -0
- package/dist/commands/template.d.ts +18 -0
- package/dist/commands/template.js +154 -0
- package/dist/core/template.d.ts +17 -0
- package/dist/core/template.js +74 -0
- package/dist/index.js +32 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/utils/path.d.ts +2 -0
- package/dist/utils/path.js +6 -1
- package/package.json +3 -3
- package/src/commands/template.ts +160 -0
- package/src/core/template.ts +91 -0
- package/src/index.ts +48 -11
- package/src/types/config.ts +112 -101
- package/src/utils/path.ts +23 -18
- package/tests/commands/export.test.ts +120 -0
- package/tests/commands/use.test.ts +326 -0
- package/tests/core/config.test.ts +37 -0
- package/tests/core/manifest.test.ts +37 -0
- package/tests/core/project.test.ts +317 -0
- package/tests/core/source.test.ts +75 -0
- package/tests/core/symlink.test.ts +39 -0
- package/tests/core/template.test.ts +103 -0
- package/tests/types/config.test.ts +232 -0
- package/tests/utils/parsePath.test.ts +235 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
本项目的所有重要变更都将记录在此文件中。
|
|
4
4
|
|
|
5
|
+
## [1.0.8] - 2026-03-04
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- 新增模板管理功能,支持保存/复用项目配置
|
|
9
|
+
- `tools-cc template save [-n <name>]` 保存当前项目配置为模板
|
|
10
|
+
- `tools-cc template list` 列出所有已保存的模板
|
|
11
|
+
- `tools-cc template rm <name>` 删除模板
|
|
12
|
+
- `tools-cc template use [name]` 应用模板到当前项目(无参数时交互选择)
|
|
13
|
+
- 模板存储在 `~/.tools-cc/templates/` 目录
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- 修复测试文件未纳入版本控制的问题
|
|
17
|
+
|
|
5
18
|
## [1.0.7] - 2026-03-02
|
|
6
19
|
|
|
7
20
|
### Fixed
|
package/CHANGELOG_en.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8] - 2026-03-04
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added template management feature for saving/reusing project configs
|
|
9
|
+
- `tools-cc template save [-n <name>]` save current project config as template
|
|
10
|
+
- `tools-cc template list` list all saved templates
|
|
11
|
+
- `tools-cc template rm <name>` remove a template
|
|
12
|
+
- `tools-cc template use [name]` apply template to current project (interactive selection if no name)
|
|
13
|
+
- Templates stored in `~/.tools-cc/templates/` directory
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Fixed test files not being tracked in version control
|
|
17
|
+
|
|
5
18
|
## [1.0.7] - 2026-03-02
|
|
6
19
|
|
|
7
20
|
### Fixed
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 处理 template save 命令
|
|
3
|
+
*/
|
|
4
|
+
export declare function handleTemplateSave(options: {
|
|
5
|
+
name?: string;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* 处理 template list 命令
|
|
9
|
+
*/
|
|
10
|
+
export declare function handleTemplateList(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* 处理 template rm 命令
|
|
13
|
+
*/
|
|
14
|
+
export declare function handleTemplateRemove(name: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* 处理 template use 命令
|
|
17
|
+
*/
|
|
18
|
+
export declare function handleTemplateUse(name?: string): Promise<void>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.handleTemplateSave = handleTemplateSave;
|
|
7
|
+
exports.handleTemplateList = handleTemplateList;
|
|
8
|
+
exports.handleTemplateRemove = handleTemplateRemove;
|
|
9
|
+
exports.handleTemplateUse = handleTemplateUse;
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const template_1 = require("../core/template");
|
|
14
|
+
const config_1 = require("../core/config");
|
|
15
|
+
const project_1 = require("../core/project");
|
|
16
|
+
const source_1 = require("../core/source");
|
|
17
|
+
const path_2 = require("../utils/path");
|
|
18
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
19
|
+
/**
|
|
20
|
+
* 处理 template save 命令
|
|
21
|
+
*/
|
|
22
|
+
async function handleTemplateSave(options) {
|
|
23
|
+
const projectDir = process.cwd();
|
|
24
|
+
const configFile = (0, path_2.getProjectConfigPath)(projectDir);
|
|
25
|
+
// 检查项目配置是否存在
|
|
26
|
+
if (!(await fs_extra_1.default.pathExists(configFile))) {
|
|
27
|
+
console.log(chalk_1.default.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
// 读取项目配置
|
|
32
|
+
const config = await (0, config_1.loadProjectConfig)(projectDir);
|
|
33
|
+
if (!config) {
|
|
34
|
+
console.log(chalk_1.default.yellow('No project configuration found.'));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// 确定模板名称
|
|
38
|
+
let templateName = options.name;
|
|
39
|
+
if (!templateName) {
|
|
40
|
+
templateName = path_1.default.basename(projectDir);
|
|
41
|
+
}
|
|
42
|
+
// 检查是否已存在
|
|
43
|
+
const existing = await (0, template_1.getTemplate)(templateName, path_2.TEMPLATES_DIR);
|
|
44
|
+
if (existing) {
|
|
45
|
+
const answers = await inquirer_1.default.prompt([
|
|
46
|
+
{
|
|
47
|
+
type: 'confirm',
|
|
48
|
+
name: 'overwrite',
|
|
49
|
+
message: `Template "${templateName}" already exists. Overwrite?`,
|
|
50
|
+
default: false
|
|
51
|
+
}
|
|
52
|
+
]);
|
|
53
|
+
if (!answers.overwrite) {
|
|
54
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 保存模板
|
|
59
|
+
const template = await (0, template_1.saveTemplate)(templateName, projectDir, config, path_2.TEMPLATES_DIR);
|
|
60
|
+
console.log(chalk_1.default.green(`✓ Template saved: ${template.name}`));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.log(chalk_1.default.red(`✗ Failed to save template: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 处理 template list 命令
|
|
68
|
+
*/
|
|
69
|
+
async function handleTemplateList() {
|
|
70
|
+
const templates = await (0, template_1.listTemplates)(path_2.TEMPLATES_DIR);
|
|
71
|
+
if (templates.length === 0) {
|
|
72
|
+
console.log(chalk_1.default.gray('No templates saved.'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(chalk_1.default.bold('Saved templates:'));
|
|
76
|
+
for (const template of templates) {
|
|
77
|
+
const date = new Date(template.savedAt).toLocaleDateString();
|
|
78
|
+
console.log(` ${chalk_1.default.cyan(template.name.padEnd(20))} (saved: ${date})`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 处理 template rm 命令
|
|
83
|
+
*/
|
|
84
|
+
async function handleTemplateRemove(name) {
|
|
85
|
+
try {
|
|
86
|
+
await (0, template_1.removeTemplate)(name, path_2.TEMPLATES_DIR);
|
|
87
|
+
console.log(chalk_1.default.green(`✓ Template removed: ${name}`));
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.log(chalk_1.default.red(`✗ Failed to remove template: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 处理 template use 命令
|
|
95
|
+
*/
|
|
96
|
+
async function handleTemplateUse(name) {
|
|
97
|
+
const projectDir = process.cwd();
|
|
98
|
+
const configFile = (0, path_2.getProjectConfigPath)(projectDir);
|
|
99
|
+
// 检查项目是否已初始化
|
|
100
|
+
if (!(await fs_extra_1.default.pathExists(configFile))) {
|
|
101
|
+
console.log(chalk_1.default.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// 如果没有指定名称,显示选择列表
|
|
105
|
+
if (!name) {
|
|
106
|
+
const templates = await (0, template_1.listTemplates)(path_2.TEMPLATES_DIR);
|
|
107
|
+
if (templates.length === 0) {
|
|
108
|
+
console.log(chalk_1.default.gray('No templates saved.'));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const answers = await inquirer_1.default.prompt([
|
|
112
|
+
{
|
|
113
|
+
type: 'list',
|
|
114
|
+
name: 'selectedTemplate',
|
|
115
|
+
message: 'Select a template to use:',
|
|
116
|
+
choices: templates.map(t => ({
|
|
117
|
+
name: `${t.name} (from: ${path_1.default.basename(t.sourceProject)})`,
|
|
118
|
+
value: t.name
|
|
119
|
+
}))
|
|
120
|
+
}
|
|
121
|
+
]);
|
|
122
|
+
name = answers.selectedTemplate;
|
|
123
|
+
}
|
|
124
|
+
// 获取模板
|
|
125
|
+
const template = await (0, template_1.getTemplate)(name, path_2.TEMPLATES_DIR);
|
|
126
|
+
if (!template) {
|
|
127
|
+
console.log(chalk_1.default.red(`✗ Template not found: ${name}`));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// 定义源路径解析函数
|
|
131
|
+
const resolveSourcePath = async (sourceName) => {
|
|
132
|
+
return await (0, source_1.getSourcePath)(sourceName, path_2.GLOBAL_CONFIG_DIR);
|
|
133
|
+
};
|
|
134
|
+
// 创建临时配置文件
|
|
135
|
+
const tempConfigPath = path_1.default.join(projectDir, '.toolscc-template-temp.json');
|
|
136
|
+
const exportConfig = {
|
|
137
|
+
version: '1.0',
|
|
138
|
+
type: 'project',
|
|
139
|
+
config: template.config,
|
|
140
|
+
exportedAt: new Date().toISOString()
|
|
141
|
+
};
|
|
142
|
+
// 导入配置
|
|
143
|
+
try {
|
|
144
|
+
await fs_extra_1.default.writeJson(tempConfigPath, exportConfig, { spaces: 2 });
|
|
145
|
+
await (0, project_1.importProjectConfig)(tempConfigPath, projectDir, resolveSourcePath);
|
|
146
|
+
console.log(chalk_1.default.green(`✓ Applied template: ${name}`));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.log(chalk_1.default.red(`✗ Failed to apply template: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
await fs_extra_1.default.remove(tempConfigPath);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TemplateConfig, ProjectConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* 保存项目配置为模板
|
|
4
|
+
*/
|
|
5
|
+
export declare function saveTemplate(name: string, sourceProject: string, config: ProjectConfig, templatesDir: string): Promise<TemplateConfig>;
|
|
6
|
+
/**
|
|
7
|
+
* 列出所有模板
|
|
8
|
+
*/
|
|
9
|
+
export declare function listTemplates(templatesDir: string): Promise<TemplateConfig[]>;
|
|
10
|
+
/**
|
|
11
|
+
* 获取指定模板
|
|
12
|
+
*/
|
|
13
|
+
export declare function getTemplate(name: string, templatesDir: string): Promise<TemplateConfig | null>;
|
|
14
|
+
/**
|
|
15
|
+
* 删除模板
|
|
16
|
+
*/
|
|
17
|
+
export declare function removeTemplate(name: string, templatesDir: string): Promise<void>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.saveTemplate = saveTemplate;
|
|
7
|
+
exports.listTemplates = listTemplates;
|
|
8
|
+
exports.getTemplate = getTemplate;
|
|
9
|
+
exports.removeTemplate = removeTemplate;
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
/**
|
|
13
|
+
* 保存项目配置为模板
|
|
14
|
+
*/
|
|
15
|
+
async function saveTemplate(name, sourceProject, config, templatesDir) {
|
|
16
|
+
if (!name || !name.trim()) {
|
|
17
|
+
throw new Error('Template name is required');
|
|
18
|
+
}
|
|
19
|
+
await fs_extra_1.default.ensureDir(templatesDir);
|
|
20
|
+
const template = {
|
|
21
|
+
version: '1.0',
|
|
22
|
+
name,
|
|
23
|
+
sourceProject,
|
|
24
|
+
savedAt: new Date().toISOString(),
|
|
25
|
+
config
|
|
26
|
+
};
|
|
27
|
+
const templatePath = path_1.default.join(templatesDir, `${name}.json`);
|
|
28
|
+
await fs_extra_1.default.writeJson(templatePath, template, { spaces: 2 });
|
|
29
|
+
return template;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 列出所有模板
|
|
33
|
+
*/
|
|
34
|
+
async function listTemplates(templatesDir) {
|
|
35
|
+
if (!(await fs_extra_1.default.pathExists(templatesDir))) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const files = await fs_extra_1.default.readdir(templatesDir);
|
|
39
|
+
const templates = [];
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
if (file.endsWith('.json')) {
|
|
42
|
+
try {
|
|
43
|
+
const template = await fs_extra_1.default.readJson(path_1.default.join(templatesDir, file));
|
|
44
|
+
if (template.version && template.name && template.config) {
|
|
45
|
+
templates.push(template);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Skip invalid files
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return templates.sort((a, b) => b.savedAt.localeCompare(a.savedAt));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 获取指定模板
|
|
57
|
+
*/
|
|
58
|
+
async function getTemplate(name, templatesDir) {
|
|
59
|
+
const templatePath = path_1.default.join(templatesDir, `${name}.json`);
|
|
60
|
+
if (!(await fs_extra_1.default.pathExists(templatePath))) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return await fs_extra_1.default.readJson(templatePath);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 删除模板
|
|
67
|
+
*/
|
|
68
|
+
async function removeTemplate(name, templatesDir) {
|
|
69
|
+
const templatePath = path_1.default.join(templatesDir, `${name}.json`);
|
|
70
|
+
if (!(await fs_extra_1.default.pathExists(templatePath))) {
|
|
71
|
+
throw new Error(`Template not found: ${name}`);
|
|
72
|
+
}
|
|
73
|
+
await fs_extra_1.default.remove(templatePath);
|
|
74
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const config_1 = require("./commands/config");
|
|
|
6
6
|
const source_1 = require("./commands/source");
|
|
7
7
|
const use_1 = require("./commands/use");
|
|
8
8
|
const export_1 = require("./commands/export");
|
|
9
|
+
const template_1 = require("./commands/template");
|
|
9
10
|
const help_1 = require("./commands/help");
|
|
10
11
|
const program = new commander_1.Command();
|
|
11
12
|
program
|
|
@@ -76,6 +77,37 @@ configCmd
|
|
|
76
77
|
.action(async () => {
|
|
77
78
|
await (0, config_1.handleConfigList)();
|
|
78
79
|
});
|
|
80
|
+
// Template commands
|
|
81
|
+
const templateCmd = program
|
|
82
|
+
.command('template')
|
|
83
|
+
.description('Template management')
|
|
84
|
+
.alias('tpl');
|
|
85
|
+
templateCmd
|
|
86
|
+
.command('save')
|
|
87
|
+
.description('Save current project config as template')
|
|
88
|
+
.option('-n, --name <name>', 'Template name (default: project directory name)')
|
|
89
|
+
.action(async (options) => {
|
|
90
|
+
await (0, template_1.handleTemplateSave)(options);
|
|
91
|
+
});
|
|
92
|
+
templateCmd
|
|
93
|
+
.command('list')
|
|
94
|
+
.alias('ls')
|
|
95
|
+
.description('List all saved templates')
|
|
96
|
+
.action(async () => {
|
|
97
|
+
await (0, template_1.handleTemplateList)();
|
|
98
|
+
});
|
|
99
|
+
templateCmd
|
|
100
|
+
.command('rm <name>')
|
|
101
|
+
.description('Remove a template')
|
|
102
|
+
.action(async (name) => {
|
|
103
|
+
await (0, template_1.handleTemplateRemove)(name);
|
|
104
|
+
});
|
|
105
|
+
templateCmd
|
|
106
|
+
.command('use [name]')
|
|
107
|
+
.description('Apply a template to current project')
|
|
108
|
+
.action(async (name) => {
|
|
109
|
+
await (0, template_1.handleTemplateUse)(name);
|
|
110
|
+
});
|
|
79
111
|
// Project commands
|
|
80
112
|
program
|
|
81
113
|
.command('use [sources...]')
|
package/dist/types/config.d.ts
CHANGED
|
@@ -66,3 +66,13 @@ export interface GlobalExportConfig {
|
|
|
66
66
|
config: GlobalConfig;
|
|
67
67
|
exportedAt: string;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* 模板配置格式
|
|
71
|
+
*/
|
|
72
|
+
export interface TemplateConfig {
|
|
73
|
+
version: string;
|
|
74
|
+
name: string;
|
|
75
|
+
sourceProject: string;
|
|
76
|
+
savedAt: string;
|
|
77
|
+
config: ProjectConfig;
|
|
78
|
+
}
|
package/dist/utils/path.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export declare const GLOBAL_CONFIG_DIR: string;
|
|
2
2
|
export declare const GLOBAL_CONFIG_FILE: string;
|
|
3
|
+
export declare const TEMPLATES_DIR: string;
|
|
3
4
|
export declare const DEFAULT_CONFIG: {
|
|
4
5
|
sourcesDir: string;
|
|
5
6
|
sources: {};
|
|
6
7
|
};
|
|
8
|
+
export declare function getTemplatePath(templateName: string): string;
|
|
7
9
|
export declare function getToolsccDir(projectDir: string): string;
|
|
8
10
|
export declare function getProjectConfigPath(projectDir: string): string;
|
package/dist/utils/path.js
CHANGED
|
@@ -3,17 +3,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DEFAULT_CONFIG = exports.GLOBAL_CONFIG_FILE = exports.GLOBAL_CONFIG_DIR = void 0;
|
|
6
|
+
exports.DEFAULT_CONFIG = exports.TEMPLATES_DIR = exports.GLOBAL_CONFIG_FILE = exports.GLOBAL_CONFIG_DIR = void 0;
|
|
7
|
+
exports.getTemplatePath = getTemplatePath;
|
|
7
8
|
exports.getToolsccDir = getToolsccDir;
|
|
8
9
|
exports.getProjectConfigPath = getProjectConfigPath;
|
|
9
10
|
const os_1 = __importDefault(require("os"));
|
|
10
11
|
const path_1 = __importDefault(require("path"));
|
|
11
12
|
exports.GLOBAL_CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.tools-cc');
|
|
12
13
|
exports.GLOBAL_CONFIG_FILE = path_1.default.join(exports.GLOBAL_CONFIG_DIR, 'config.json');
|
|
14
|
+
exports.TEMPLATES_DIR = path_1.default.join(exports.GLOBAL_CONFIG_DIR, 'templates');
|
|
13
15
|
exports.DEFAULT_CONFIG = {
|
|
14
16
|
sourcesDir: path_1.default.join(exports.GLOBAL_CONFIG_DIR, 'sources'),
|
|
15
17
|
sources: {}
|
|
16
18
|
};
|
|
19
|
+
function getTemplatePath(templateName) {
|
|
20
|
+
return path_1.default.join(exports.TEMPLATES_DIR, `${templateName}.json`);
|
|
21
|
+
}
|
|
17
22
|
function getToolsccDir(projectDir) {
|
|
18
23
|
return path_1.default.join(projectDir, '.toolscc');
|
|
19
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tools-cc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "tools-cc [options] <command> [args]",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"release": "npm run tag:create && npm run publish:npm && npm run release:gh"
|
|
19
19
|
},
|
|
20
20
|
"bin": {
|
|
21
|
-
"tools-cc": "
|
|
21
|
+
"tools-cc": "dist/index.js"
|
|
22
22
|
},
|
|
23
23
|
"keywords": [],
|
|
24
24
|
"author": "",
|
|
25
25
|
"license": "ISC",
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
|
-
"url": "https://github.com/q759410559/tools-cc.git"
|
|
28
|
+
"url": "git+https://github.com/q759410559/tools-cc.git"
|
|
29
29
|
},
|
|
30
30
|
"bugs": {
|
|
31
31
|
"url": "https://github.com/q759410559/tools-cc/issues"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { saveTemplate, listTemplates, removeTemplate, getTemplate } from '../core/template';
|
|
5
|
+
import { loadProjectConfig } from '../core/config';
|
|
6
|
+
import { importProjectConfig } from '../core/project';
|
|
7
|
+
import { getSourcePath } from '../core/source';
|
|
8
|
+
import { TEMPLATES_DIR, getProjectConfigPath, GLOBAL_CONFIG_DIR } from '../utils/path';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 处理 template save 命令
|
|
13
|
+
*/
|
|
14
|
+
export async function handleTemplateSave(options: { name?: string }): Promise<void> {
|
|
15
|
+
const projectDir = process.cwd();
|
|
16
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
17
|
+
|
|
18
|
+
// 检查项目配置是否存在
|
|
19
|
+
if (!(await fs.pathExists(configFile))) {
|
|
20
|
+
console.log(chalk.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// 读取项目配置
|
|
26
|
+
const config = await loadProjectConfig(projectDir);
|
|
27
|
+
if (!config) {
|
|
28
|
+
console.log(chalk.yellow('No project configuration found.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 确定模板名称
|
|
33
|
+
let templateName = options.name;
|
|
34
|
+
if (!templateName) {
|
|
35
|
+
templateName = path.basename(projectDir);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 检查是否已存在
|
|
39
|
+
const existing = await getTemplate(templateName, TEMPLATES_DIR);
|
|
40
|
+
if (existing) {
|
|
41
|
+
const answers = await inquirer.prompt([
|
|
42
|
+
{
|
|
43
|
+
type: 'confirm',
|
|
44
|
+
name: 'overwrite',
|
|
45
|
+
message: `Template "${templateName}" already exists. Overwrite?`,
|
|
46
|
+
default: false
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
49
|
+
if (!answers.overwrite) {
|
|
50
|
+
console.log(chalk.gray('Cancelled.'));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 保存模板
|
|
56
|
+
const template = await saveTemplate(templateName, projectDir, config, TEMPLATES_DIR);
|
|
57
|
+
console.log(chalk.green(`✓ Template saved: ${template.name}`));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.log(chalk.red(`✗ Failed to save template: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 处理 template list 命令
|
|
65
|
+
*/
|
|
66
|
+
export async function handleTemplateList(): Promise<void> {
|
|
67
|
+
const templates = await listTemplates(TEMPLATES_DIR);
|
|
68
|
+
|
|
69
|
+
if (templates.length === 0) {
|
|
70
|
+
console.log(chalk.gray('No templates saved.'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(chalk.bold('Saved templates:'));
|
|
75
|
+
for (const template of templates) {
|
|
76
|
+
const date = new Date(template.savedAt).toLocaleDateString();
|
|
77
|
+
console.log(` ${chalk.cyan(template.name.padEnd(20))} (saved: ${date})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 处理 template rm 命令
|
|
83
|
+
*/
|
|
84
|
+
export async function handleTemplateRemove(name: string): Promise<void> {
|
|
85
|
+
try {
|
|
86
|
+
await removeTemplate(name, TEMPLATES_DIR);
|
|
87
|
+
console.log(chalk.green(`✓ Template removed: ${name}`));
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.log(chalk.red(`✗ Failed to remove template: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 处理 template use 命令
|
|
95
|
+
*/
|
|
96
|
+
export async function handleTemplateUse(name?: string): Promise<void> {
|
|
97
|
+
const projectDir = process.cwd();
|
|
98
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
99
|
+
|
|
100
|
+
// 检查项目是否已初始化
|
|
101
|
+
if (!(await fs.pathExists(configFile))) {
|
|
102
|
+
console.log(chalk.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 如果没有指定名称,显示选择列表
|
|
107
|
+
if (!name) {
|
|
108
|
+
const templates = await listTemplates(TEMPLATES_DIR);
|
|
109
|
+
|
|
110
|
+
if (templates.length === 0) {
|
|
111
|
+
console.log(chalk.gray('No templates saved.'));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const answers = await inquirer.prompt([
|
|
116
|
+
{
|
|
117
|
+
type: 'list',
|
|
118
|
+
name: 'selectedTemplate',
|
|
119
|
+
message: 'Select a template to use:',
|
|
120
|
+
choices: templates.map(t => ({
|
|
121
|
+
name: `${t.name} (from: ${path.basename(t.sourceProject)})`,
|
|
122
|
+
value: t.name
|
|
123
|
+
}))
|
|
124
|
+
}
|
|
125
|
+
]);
|
|
126
|
+
name = answers.selectedTemplate as string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 获取模板
|
|
130
|
+
const template = await getTemplate(name as string, TEMPLATES_DIR);
|
|
131
|
+
if (!template) {
|
|
132
|
+
console.log(chalk.red(`✗ Template not found: ${name}`));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 定义源路径解析函数
|
|
137
|
+
const resolveSourcePath = async (sourceName: string): Promise<string> => {
|
|
138
|
+
return await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// 创建临时配置文件
|
|
142
|
+
const tempConfigPath = path.join(projectDir, '.toolscc-template-temp.json');
|
|
143
|
+
const exportConfig = {
|
|
144
|
+
version: '1.0',
|
|
145
|
+
type: 'project' as const,
|
|
146
|
+
config: template.config,
|
|
147
|
+
exportedAt: new Date().toISOString()
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// 导入配置
|
|
151
|
+
try {
|
|
152
|
+
await fs.writeJson(tempConfigPath, exportConfig, { spaces: 2 });
|
|
153
|
+
await importProjectConfig(tempConfigPath, projectDir, resolveSourcePath);
|
|
154
|
+
console.log(chalk.green(`✓ Applied template: ${name}`));
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.log(chalk.red(`✗ Failed to apply template: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
157
|
+
} finally {
|
|
158
|
+
await fs.remove(tempConfigPath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { TemplateConfig, ProjectConfig } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 保存项目配置为模板
|
|
7
|
+
*/
|
|
8
|
+
export async function saveTemplate(
|
|
9
|
+
name: string,
|
|
10
|
+
sourceProject: string,
|
|
11
|
+
config: ProjectConfig,
|
|
12
|
+
templatesDir: string
|
|
13
|
+
): Promise<TemplateConfig> {
|
|
14
|
+
if (!name || !name.trim()) {
|
|
15
|
+
throw new Error('Template name is required');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await fs.ensureDir(templatesDir);
|
|
19
|
+
|
|
20
|
+
const template: TemplateConfig = {
|
|
21
|
+
version: '1.0',
|
|
22
|
+
name,
|
|
23
|
+
sourceProject,
|
|
24
|
+
savedAt: new Date().toISOString(),
|
|
25
|
+
config
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const templatePath = path.join(templatesDir, `${name}.json`);
|
|
29
|
+
await fs.writeJson(templatePath, template, { spaces: 2 });
|
|
30
|
+
|
|
31
|
+
return template;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 列出所有模板
|
|
36
|
+
*/
|
|
37
|
+
export async function listTemplates(templatesDir: string): Promise<TemplateConfig[]> {
|
|
38
|
+
if (!(await fs.pathExists(templatesDir))) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const files = await fs.readdir(templatesDir);
|
|
43
|
+
const templates: TemplateConfig[] = [];
|
|
44
|
+
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
if (file.endsWith('.json')) {
|
|
47
|
+
try {
|
|
48
|
+
const template = await fs.readJson(path.join(templatesDir, file));
|
|
49
|
+
if (template.version && template.name && template.config) {
|
|
50
|
+
templates.push(template);
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// Skip invalid files
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return templates.sort((a, b) => b.savedAt.localeCompare(a.savedAt));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 获取指定模板
|
|
63
|
+
*/
|
|
64
|
+
export async function getTemplate(
|
|
65
|
+
name: string,
|
|
66
|
+
templatesDir: string
|
|
67
|
+
): Promise<TemplateConfig | null> {
|
|
68
|
+
const templatePath = path.join(templatesDir, `${name}.json`);
|
|
69
|
+
|
|
70
|
+
if (!(await fs.pathExists(templatePath))) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return await fs.readJson(templatePath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 删除模板
|
|
79
|
+
*/
|
|
80
|
+
export async function removeTemplate(
|
|
81
|
+
name: string,
|
|
82
|
+
templatesDir: string
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
const templatePath = path.join(templatesDir, `${name}.json`);
|
|
85
|
+
|
|
86
|
+
if (!(await fs.pathExists(templatePath))) {
|
|
87
|
+
throw new Error(`Template not found: ${name}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await fs.remove(templatePath);
|
|
91
|
+
}
|