@epoint-testtech/stage-create 0.0.3-alpha.1 → 0.0.4-alpha.0
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/dist/index.js +3 -58
- package/dist/stage-context.d.ts +8 -16
- package/dist/stage-context.js +35 -140
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
import { program } from 'commander';
|
|
6
6
|
import prompts from 'prompts';
|
|
7
7
|
import { copyTemplateDir } from './render-template.js';
|
|
8
|
-
import { writeGlueProjectContext
|
|
8
|
+
import { writeGlueProjectContext } from './stage-context.js';
|
|
9
9
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
10
10
|
const TEMPLATES_DIR = path.resolve(path.dirname(currentFilePath), '..', 'templates');
|
|
11
11
|
const DEFAULT_STAGE_CORE_DEPENDENCY = '^0.0.3-alpha.1';
|
|
@@ -28,14 +28,14 @@ program
|
|
|
28
28
|
name: 'mode',
|
|
29
29
|
message: '选择模式',
|
|
30
30
|
choices: [
|
|
31
|
-
{ title: '默认 — 基础 playwright.config.ts 配置', value: 'default' },
|
|
32
31
|
{ title: '胶水模式 — 完整骨架(CrudPage + LoginPage + MenuPage 示例)', value: 'glue' },
|
|
32
|
+
{ title: '默认 — 基础 playwright.config.ts 配置', value: 'default' },
|
|
33
33
|
],
|
|
34
34
|
initial: 0,
|
|
35
35
|
},
|
|
36
36
|
]);
|
|
37
37
|
const rawName = projectName ?? answers.projectName;
|
|
38
|
-
const mode = options?.mode ?? answers.mode ?? '
|
|
38
|
+
const mode = options?.mode ?? answers.mode ?? 'glue';
|
|
39
39
|
if (!rawName) {
|
|
40
40
|
console.error('项目名称不能为空');
|
|
41
41
|
process.exit(1);
|
|
@@ -50,17 +50,6 @@ program
|
|
|
50
50
|
console.error(`目录已存在: ${outputDir}`);
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
|
-
const glueContextAnswers = mode === 'glue'
|
|
54
|
-
? await readGlueContextAnswers()
|
|
55
|
-
: {};
|
|
56
|
-
// glue 模式必填项校验必须早于 mkdirSync,避免缺失输入时留下空项目骨架。
|
|
57
|
-
if (mode === 'glue') {
|
|
58
|
-
validateRequiredContextInput({
|
|
59
|
-
loginSystemUrl: glueContextAnswers.loginSystemUrl ?? '',
|
|
60
|
-
loginUsername: glueContextAnswers.loginUsername ?? '',
|
|
61
|
-
loginPassword: glueContextAnswers.loginPassword ?? '',
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
53
|
console.log('生成中...');
|
|
65
54
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
66
55
|
copyTemplateDir(templateDir, outputDir, {
|
|
@@ -72,10 +61,6 @@ program
|
|
|
72
61
|
writeGlueProjectContext({
|
|
73
62
|
projectName: name,
|
|
74
63
|
projectDir: outputDir,
|
|
75
|
-
loginSystemUrl: glueContextAnswers.loginSystemUrl ?? '',
|
|
76
|
-
loginUsername: glueContextAnswers.loginUsername ?? '',
|
|
77
|
-
loginPassword: glueContextAnswers.loginPassword ?? '',
|
|
78
|
-
knowledgeRoot: glueContextAnswers.knowledgeRoot,
|
|
79
64
|
});
|
|
80
65
|
}
|
|
81
66
|
console.log(`\n完成!执行以下命令开始:`);
|
|
@@ -87,43 +72,3 @@ program
|
|
|
87
72
|
console.log(` pnpm test`);
|
|
88
73
|
});
|
|
89
74
|
program.parse();
|
|
90
|
-
/**
|
|
91
|
-
* 读取 glue 模式项目上下文输入。
|
|
92
|
-
*
|
|
93
|
-
* @returns 登录配置和可选知识库路径。
|
|
94
|
-
*/
|
|
95
|
-
async function readGlueContextAnswers() {
|
|
96
|
-
if (!process.stdin.isTTY) {
|
|
97
|
-
const [loginSystemUrl, loginUsername, loginPassword, knowledgeRoot] = fs
|
|
98
|
-
.readFileSync(0, 'utf8')
|
|
99
|
-
.split(/\r?\n/);
|
|
100
|
-
return {
|
|
101
|
-
loginSystemUrl,
|
|
102
|
-
loginUsername,
|
|
103
|
-
loginPassword,
|
|
104
|
-
knowledgeRoot,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
return prompts([
|
|
108
|
-
{
|
|
109
|
-
type: 'text',
|
|
110
|
-
name: 'loginSystemUrl',
|
|
111
|
-
message: '登录页面地址',
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
type: 'text',
|
|
115
|
-
name: 'loginUsername',
|
|
116
|
-
message: '登录用户名',
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
type: 'password',
|
|
120
|
-
name: 'loginPassword',
|
|
121
|
-
message: '登录密码',
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
type: 'text',
|
|
125
|
-
name: 'knowledgeRoot',
|
|
126
|
-
message: '知识库路径(可选)',
|
|
127
|
-
},
|
|
128
|
-
]);
|
|
129
|
-
}
|
package/dist/stage-context.d.ts
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
export type WriteGlueProjectContextInput = {
|
|
2
2
|
projectName: string;
|
|
3
3
|
projectDir: string;
|
|
4
|
-
|
|
5
|
-
loginUsername: string;
|
|
6
|
-
loginPassword: string;
|
|
7
|
-
knowledgeRoot?: string;
|
|
4
|
+
stageCreateVersion?: string;
|
|
8
5
|
homeDir?: string;
|
|
9
6
|
};
|
|
10
7
|
/**
|
|
11
|
-
*
|
|
8
|
+
* 写入用户级项目索引(REQ-ENV-01 修订)。
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* 中性化:不写 .env、不写 stage-context.md;项目元信息合并到 projects.index.json5。
|
|
11
|
+
* index 条目含 stageCreateVersion,不含 stageContextPath/envPath/knowledgeRoot/codeListPaths。
|
|
12
|
+
* 顶层加 systems: [](首次创建时)。
|
|
13
|
+
*
|
|
14
|
+
* @param input - 项目名、项目目录、可选 stageCreateVersion 与 homeDir。
|
|
15
|
+
* @returns 写入的 projects.index.json5 路径。
|
|
15
16
|
*/
|
|
16
17
|
export declare function writeGlueProjectContext(input: WriteGlueProjectContextInput): {
|
|
17
|
-
envPath: string;
|
|
18
|
-
stageContextPath: string;
|
|
19
18
|
projectIndexPath: string;
|
|
20
19
|
};
|
|
21
|
-
/**
|
|
22
|
-
* 校验 glue 模式运行所需的必填输入。
|
|
23
|
-
*
|
|
24
|
-
* @param input - glue 项目上下文输入。
|
|
25
|
-
* @throws 当登录地址、用户名或密码为空时抛出错误。
|
|
26
|
-
*/
|
|
27
|
-
export declare function validateRequiredContextInput(input: Pick<WriteGlueProjectContextInput, 'loginSystemUrl' | 'loginUsername' | 'loginPassword'>): void;
|
package/dist/stage-context.js
CHANGED
|
@@ -1,175 +1,70 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
4
5
|
import JSON5 from 'json5';
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* 写入用户级项目索引(REQ-ENV-01 修订)。
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* 中性化:不写 .env、不写 stage-context.md;项目元信息合并到 projects.index.json5。
|
|
10
|
+
* index 条目含 stageCreateVersion,不含 stageContextPath/envPath/knowledgeRoot/codeListPaths。
|
|
11
|
+
* 顶层加 systems: [](首次创建时)。
|
|
12
|
+
*
|
|
13
|
+
* @param input - 项目名、项目目录、可选 stageCreateVersion 与 homeDir。
|
|
14
|
+
* @returns 写入的 projects.index.json5 路径。
|
|
10
15
|
*/
|
|
11
16
|
export function writeGlueProjectContext(input) {
|
|
12
17
|
const projectDir = path.resolve(input.projectDir);
|
|
13
|
-
const envPath = path.join(projectDir, '.env');
|
|
14
|
-
const stageContextPath = path.join(projectDir, 'stage-context.md');
|
|
15
18
|
const projectIndexPath = path.join(input.homeDir ?? os.homedir(), '.ep-stage', 'projects.index.json5');
|
|
16
|
-
const
|
|
17
|
-
const codeListPaths = getExistingCodeListPaths(knowledgeRoot);
|
|
18
|
-
validateRequiredContextInput(input);
|
|
19
|
-
fs.writeFileSync(envPath, renderEnv(input), 'utf8');
|
|
20
|
-
fs.writeFileSync(stageContextPath, renderStageContext({
|
|
21
|
-
projectName: input.projectName,
|
|
22
|
-
knowledgeRoot,
|
|
23
|
-
codeListPaths,
|
|
24
|
-
}), 'utf8');
|
|
19
|
+
const stageCreateVersion = input.stageCreateVersion ?? readSelfVersion();
|
|
25
20
|
const index = readProjectIndex(projectIndexPath);
|
|
26
21
|
const nextEntry = {
|
|
27
22
|
projectName: input.projectName,
|
|
28
23
|
projectDir,
|
|
29
24
|
mode: 'glue',
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
stageContextPath,
|
|
25
|
+
createdAt: new Date().toISOString(),
|
|
26
|
+
stageCreateVersion,
|
|
27
|
+
requirements: [],
|
|
34
28
|
};
|
|
35
29
|
const dedupedProjects = index.projects.filter((project) => path.resolve(project.projectDir) !== projectDir);
|
|
36
30
|
fs.mkdirSync(path.dirname(projectIndexPath), { recursive: true });
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* @returns .env 文件内容。
|
|
45
|
-
*/
|
|
46
|
-
function renderEnv(input) {
|
|
47
|
-
return [
|
|
48
|
-
'# Stage 环境变量配置',
|
|
49
|
-
'',
|
|
50
|
-
'LOGIN_SYSTEM_URL=' + renderEnvValue(input.loginSystemUrl),
|
|
51
|
-
'LOGIN_USERNAME=' + renderEnvValue(input.loginUsername),
|
|
52
|
-
'LOGIN_PASSWORD=' + renderEnvValue(input.loginPassword),
|
|
53
|
-
'',
|
|
54
|
-
].join('\n');
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* 渲染项目级 stage-context.md。
|
|
58
|
-
*
|
|
59
|
-
* @param input - 项目名和可选知识库路径。
|
|
60
|
-
* @returns stage-context.md 文件内容。
|
|
61
|
-
*/
|
|
62
|
-
function renderStageContext(input) {
|
|
63
|
-
const frontmatter = [
|
|
64
|
-
'---',
|
|
65
|
-
`projectName: ${renderYamlString(input.projectName)}`,
|
|
66
|
-
`projectDir: ${renderYamlString('.')}`,
|
|
67
|
-
`mode: ${renderYamlString('glue')}`,
|
|
68
|
-
`envPath: ${renderYamlString('.env')}`,
|
|
69
|
-
...(input.knowledgeRoot ? [`knowledgeRoot: ${renderYamlString(input.knowledgeRoot)}`] : []),
|
|
70
|
-
...renderYamlStringArray('codeListPaths', input.codeListPaths),
|
|
71
|
-
'---',
|
|
72
|
-
];
|
|
73
|
-
return [
|
|
74
|
-
...frontmatter,
|
|
75
|
-
'',
|
|
76
|
-
`# ${input.projectName}`,
|
|
77
|
-
'',
|
|
78
|
-
'此文件由 stage-create glue 模式生成,用于 ep-stage skills/CLI 解析项目上下文。',
|
|
79
|
-
'',
|
|
80
|
-
].join('\n');
|
|
31
|
+
const next = {
|
|
32
|
+
version: 1,
|
|
33
|
+
projects: [...dedupedProjects, nextEntry],
|
|
34
|
+
systems: index.systems ?? [],
|
|
35
|
+
};
|
|
36
|
+
fs.writeFileSync(projectIndexPath, `${JSON5.stringify(next, null, 2)}\n`, 'utf8');
|
|
37
|
+
return { projectIndexPath };
|
|
81
38
|
}
|
|
82
39
|
/**
|
|
83
40
|
* 读取已有用户级项目索引,允许 JSON5 注释和尾逗号。
|
|
84
41
|
*
|
|
85
42
|
* @param projectIndexPath - 用户级项目索引路径。
|
|
86
|
-
* @returns
|
|
43
|
+
* @returns 解析后的项目索引;缺失时返回空 projects/systems。
|
|
87
44
|
*/
|
|
88
45
|
function readProjectIndex(projectIndexPath) {
|
|
89
46
|
if (!fs.existsSync(projectIndexPath)) {
|
|
90
|
-
return { projects: [] };
|
|
47
|
+
return { projects: [], systems: [] };
|
|
91
48
|
}
|
|
92
49
|
const parsed = JSON5.parse(fs.readFileSync(projectIndexPath, 'utf8'));
|
|
93
|
-
return {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
* @param input - glue 项目上下文输入。
|
|
99
|
-
* @throws 当登录地址、用户名或密码为空时抛出错误。
|
|
100
|
-
*/
|
|
101
|
-
export function validateRequiredContextInput(input) {
|
|
102
|
-
if (!input.loginSystemUrl.trim()) {
|
|
103
|
-
throw new Error('登录页面地址不能为空');
|
|
104
|
-
}
|
|
105
|
-
if (!input.loginUsername.trim()) {
|
|
106
|
-
throw new Error('登录用户名不能为空');
|
|
107
|
-
}
|
|
108
|
-
if (!input.loginPassword.trim()) {
|
|
109
|
-
throw new Error('登录密码不能为空');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* 渲染 .env 值,必要时使用双引号并转义特殊字符。
|
|
114
|
-
*
|
|
115
|
-
* @param value - 原始环境变量值。
|
|
116
|
-
* @returns 可写入 .env 的安全值。
|
|
117
|
-
*/
|
|
118
|
-
function renderEnvValue(value) {
|
|
119
|
-
if (!/[\s#"\\\r\n]/.test(value)) {
|
|
120
|
-
return value;
|
|
121
|
-
}
|
|
122
|
-
return `"${value
|
|
123
|
-
.replace(/\\/g, '\\\\')
|
|
124
|
-
.replace(/"/g, '\\"')
|
|
125
|
-
.replace(/\n/g, '\\n')
|
|
126
|
-
.replace(/\r/g, '\\r')}"`;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* 渲染 YAML 字符串标量。
|
|
130
|
-
*
|
|
131
|
-
* @param value - 原始字符串。
|
|
132
|
-
* @returns 可放入 YAML frontmatter 的双引号标量。
|
|
133
|
-
*/
|
|
134
|
-
function renderYamlString(value) {
|
|
135
|
-
return JSON.stringify(value);
|
|
50
|
+
return {
|
|
51
|
+
projects: parsed.projects ?? [],
|
|
52
|
+
systems: parsed.systems ?? [],
|
|
53
|
+
};
|
|
136
54
|
}
|
|
137
55
|
/**
|
|
138
|
-
*
|
|
56
|
+
* 读取自身 package.json 版本号,作为 stageCreateVersion 默认值。
|
|
139
57
|
*
|
|
140
|
-
* @
|
|
141
|
-
* @param values - 字符串数组。
|
|
142
|
-
* @returns YAML 行数组。
|
|
58
|
+
* @returns 当前 stage-create 的版本号;无法解析时返回 'unknown'。
|
|
143
59
|
*/
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
60
|
+
function readSelfVersion() {
|
|
61
|
+
try {
|
|
62
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
63
|
+
const packageJsonPath = path.resolve(path.dirname(currentFilePath), '..', 'package.json');
|
|
64
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
65
|
+
return pkg.version ?? 'unknown';
|
|
147
66
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
...values.map((value) => ` - ${renderYamlString(value)}`),
|
|
151
|
-
];
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* 只在 knowledgeRoot/_docs/code_list.md 实际存在时登记 code list。
|
|
155
|
-
*
|
|
156
|
-
* @param knowledgeRoot - 可选知识库根目录。
|
|
157
|
-
* @returns 已存在的 code_list.md 路径列表。
|
|
158
|
-
*/
|
|
159
|
-
function getExistingCodeListPaths(knowledgeRoot) {
|
|
160
|
-
if (!knowledgeRoot) {
|
|
161
|
-
return [];
|
|
67
|
+
catch {
|
|
68
|
+
return 'unknown';
|
|
162
69
|
}
|
|
163
|
-
const codeListPath = path.join(knowledgeRoot, '_docs', 'code_list.md');
|
|
164
|
-
return fs.existsSync(codeListPath) ? [codeListPath] : [];
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* 归一化可选路径,空字符串视为未提供。
|
|
168
|
-
*
|
|
169
|
-
* @param value - 用户输入的可选路径。
|
|
170
|
-
* @returns 绝对路径或 undefined。
|
|
171
|
-
*/
|
|
172
|
-
function normalizeOptionalPath(value) {
|
|
173
|
-
const trimmed = value?.trim();
|
|
174
|
-
return trimmed ? path.resolve(trimmed) : undefined;
|
|
175
70
|
}
|