universal-dev-standards 5.0.0 → 5.1.0-beta.1
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/bin/uds.js +34 -11
- package/bundled/ai/standards/ai-response-navigation.ai.yaml +70 -0
- package/bundled/ai/standards/context-aware-loading.ai.yaml +5 -4
- package/bundled/core/ai-response-navigation.md +289 -0
- package/bundled/locales/zh-CN/README.md +1 -1
- package/bundled/locales/zh-CN/core/ai-response-navigation.md +297 -0
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/core/ai-response-navigation.md +297 -0
- package/bundled/skills/ac-coverage-assistant/SKILL.md +1 -1
- package/bundled/skills/ai-collaboration-standards/SKILL.md +11 -0
- package/bundled/skills/ai-friendly-architecture/SKILL.md +11 -0
- package/bundled/skills/ai-instruction-standards/SKILL.md +11 -0
- package/bundled/skills/api-design-assistant/SKILL.md +1 -1
- package/bundled/skills/atdd-assistant/SKILL.md +1 -1
- package/bundled/skills/audit-assistant/SKILL.md +1 -1
- package/bundled/skills/bdd-assistant/SKILL.md +1 -1
- package/bundled/skills/brainstorm-assistant/SKILL.md +1 -1
- package/bundled/skills/changelog-guide/SKILL.md +1 -1
- package/bundled/skills/checkin-assistant/SKILL.md +1 -1
- package/bundled/skills/ci-cd-assistant/SKILL.md +1 -1
- package/bundled/skills/code-review-assistant/SKILL.md +1 -1
- package/bundled/skills/commands/release.md +27 -7
- package/bundled/skills/commit-standards/SKILL.md +1 -1
- package/bundled/skills/database-assistant/SKILL.md +1 -1
- package/bundled/skills/dev-workflow-guide/SKILL.md +1 -1
- package/bundled/skills/docs-generator/SKILL.md +1 -1
- package/bundled/skills/documentation-guide/SKILL.md +11 -0
- package/bundled/skills/durable-execution-assistant/SKILL.md +1 -1
- package/bundled/skills/error-code-guide/SKILL.md +11 -0
- package/bundled/skills/forward-derivation/SKILL.md +1 -1
- package/bundled/skills/git-workflow-guide/SKILL.md +11 -0
- package/bundled/skills/incident-response-assistant/SKILL.md +1 -1
- package/bundled/skills/logging-guide/SKILL.md +11 -0
- package/bundled/skills/methodology-system/SKILL.md +1 -1
- package/bundled/skills/metrics-dashboard-assistant/SKILL.md +1 -1
- package/bundled/skills/migration-assistant/SKILL.md +1 -1
- package/bundled/skills/pr-automation-assistant/SKILL.md +1 -1
- package/bundled/skills/project-discovery/SKILL.md +1 -1
- package/bundled/skills/project-structure-guide/SKILL.md +11 -0
- package/bundled/skills/refactoring-assistant/SKILL.md +1 -1
- package/bundled/skills/release-standards/SKILL.md +31 -7
- package/bundled/skills/requirement-assistant/SKILL.md +1 -1
- package/bundled/skills/reverse-engineer/SKILL.md +1 -1
- package/bundled/skills/security-assistant/SKILL.md +1 -1
- package/bundled/skills/security-scan-assistant/SKILL.md +1 -1
- package/bundled/skills/spec-driven-dev/SKILL.md +1 -1
- package/bundled/skills/tdd-assistant/SKILL.md +1 -1
- package/bundled/skills/test-coverage-assistant/SKILL.md +1 -1
- package/bundled/skills/testing-guide/SKILL.md +11 -0
- package/package.json +3 -3
- package/src/commands/config.js +35 -2
- package/src/commands/init.js +17 -1
- package/src/commands/release.js +378 -0
- package/src/flows/init-flow.js +7 -2
- package/src/i18n/messages.js +87 -0
- package/src/installers/manifest-installer.js +2 -1
- package/src/prompts/init.js +52 -0
- package/src/utils/build-manifest.js +46 -0
- package/src/utils/deployment-tracker.js +73 -0
- package/src/utils/release-config.js +94 -0
- package/src/utils/update-checker.js +17 -0
- package/src/utils/version-promote.js +73 -0
- package/standards-registry.json +14 -3
package/src/commands/init.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getAgentDisplayName
|
|
22
22
|
} from '../utils/github.js';
|
|
23
23
|
import { displayLanguageToLocale } from '../utils/locale.js';
|
|
24
|
+
import { generateReleaseConfig, RELEASE_MODE_LABELS } from '../utils/release-config.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Init command - initialize standards in current project
|
|
@@ -91,6 +92,16 @@ export async function initCommand(options) {
|
|
|
91
92
|
const standardsResults = await installStandards(config, projectPath);
|
|
92
93
|
config.installedStandards = standardsResults.standards.map(s => basename(s));
|
|
93
94
|
|
|
95
|
+
// 1.5. Generate release-config.yaml if non-default mode selected
|
|
96
|
+
if (config.releaseMode && config.releaseMode !== 'ci-cd') {
|
|
97
|
+
const releaseConfigData = generateReleaseConfig(config.releaseMode);
|
|
98
|
+
const releaseConfigPath = join(projectPath, '.standards', 'release-config.yaml');
|
|
99
|
+
const yaml = (await import('js-yaml')).default;
|
|
100
|
+
mkdirSync(join(projectPath, '.standards'), { recursive: true });
|
|
101
|
+
writeFileSync(releaseConfigPath, yaml.dump(releaseConfigData), 'utf-8');
|
|
102
|
+
console.log(chalk.green(` ✓ release-config.yaml (${config.releaseMode})`));
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
// 2. Install Integrations
|
|
95
106
|
const integrationResults = await installIntegrations(config, projectPath);
|
|
96
107
|
|
|
@@ -324,7 +335,8 @@ function buildNonInteractiveConfig(options, detected, projectPath) {
|
|
|
324
335
|
integrations: [...aiToolsNormalized],
|
|
325
336
|
contentMode: skillsConfig.contentMode || 'minimal',
|
|
326
337
|
methodology: null,
|
|
327
|
-
generateAgentsMd
|
|
338
|
+
generateAgentsMd,
|
|
339
|
+
releaseMode: options.releaseMode || 'ci-cd'
|
|
328
340
|
};
|
|
329
341
|
}
|
|
330
342
|
|
|
@@ -397,6 +409,10 @@ function displaySummary(config, msg, common) {
|
|
|
397
409
|
if (config.standardOptions.workflow) {
|
|
398
410
|
console.log(chalk.gray(` ${msg.gitWorkflow}: ${getValueLabel('gitWorkflow', config.standardOptions.workflow)}`));
|
|
399
411
|
}
|
|
412
|
+
if (config.releaseMode) {
|
|
413
|
+
const releaseModeLabels = RELEASE_MODE_LABELS;
|
|
414
|
+
console.log(chalk.gray(` ${msg.releaseMode || 'Release Mode'}: ${releaseModeLabels[config.releaseMode] || config.releaseMode}`));
|
|
415
|
+
}
|
|
400
416
|
if (config.standardOptions.merge_strategy) {
|
|
401
417
|
console.log(chalk.gray(` ${msg.mergeStrategy}: ${getValueLabel('mergeStrategy', config.standardOptions.merge_strategy)}`));
|
|
402
418
|
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release command — manage release process with mode-aware behavior.
|
|
3
|
+
* SPEC-RELEASE-01: Manual Deployment Release Mode
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
* promote — RC → Stable promotion (manual/hybrid mode)
|
|
7
|
+
* deploy — Record deployment to environment (manual/hybrid mode)
|
|
8
|
+
* manifest — Generate build-manifest.json (manual/hybrid mode)
|
|
9
|
+
* verify — Verify manifest against git state (manual/hybrid mode)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import yaml from 'js-yaml';
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
import { resolveReleaseWorkflow } from '../utils/release-config.js';
|
|
18
|
+
import { formatGitTag, createPromotionRecord, parseRCVersion } from '../utils/version-promote.js';
|
|
19
|
+
import { createManifest, verifyManifest } from '../utils/build-manifest.js';
|
|
20
|
+
import { recordDeployment, updateDeploymentResult, checkDeploymentReadiness } from '../utils/deployment-tracker.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load release config from .standards/release-config.yaml
|
|
24
|
+
*/
|
|
25
|
+
function loadReleaseConfig(projectPath) {
|
|
26
|
+
const configPath = join(projectPath, '.standards', 'release-config.yaml');
|
|
27
|
+
if (!existsSync(configPath)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return yaml.load(readFileSync(configPath, 'utf-8'));
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.log(chalk.yellow(`⚠ release-config.yaml 解析失敗: ${err.message}`));
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load deployments.yaml
|
|
40
|
+
*/
|
|
41
|
+
function loadDeployments(projectPath) {
|
|
42
|
+
const deployPath = join(projectPath, 'deployments.yaml');
|
|
43
|
+
if (!existsSync(deployPath)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const data = yaml.load(readFileSync(deployPath, 'utf-8'));
|
|
48
|
+
return data?.deployments || [];
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Save deployments.yaml
|
|
56
|
+
*/
|
|
57
|
+
function saveDeployments(projectPath, deployments) {
|
|
58
|
+
const deployPath = join(projectPath, 'deployments.yaml');
|
|
59
|
+
writeFileSync(deployPath, yaml.dump({ deployments }), 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get current git user name
|
|
64
|
+
*/
|
|
65
|
+
function getGitUser() {
|
|
66
|
+
try {
|
|
67
|
+
return execSync('git config user.name', { encoding: 'utf-8' }).trim();
|
|
68
|
+
} catch {
|
|
69
|
+
return 'unknown';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get current git commit hash (short)
|
|
75
|
+
*/
|
|
76
|
+
function getGitCommit() {
|
|
77
|
+
try {
|
|
78
|
+
return execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
|
|
79
|
+
} catch {
|
|
80
|
+
return 'unknown';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get current git branch
|
|
86
|
+
*/
|
|
87
|
+
function getGitBranch() {
|
|
88
|
+
try {
|
|
89
|
+
return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
90
|
+
} catch {
|
|
91
|
+
return 'unknown';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get current version from package.json
|
|
97
|
+
*/
|
|
98
|
+
function getPackageVersion(projectPath) {
|
|
99
|
+
const pkgPath = join(projectPath, 'package.json');
|
|
100
|
+
if (!existsSync(pkgPath)) {
|
|
101
|
+
// Try cli/package.json
|
|
102
|
+
const cliPkgPath = join(projectPath, 'cli', 'package.json');
|
|
103
|
+
if (existsSync(cliPkgPath)) {
|
|
104
|
+
return JSON.parse(readFileSync(cliPkgPath, 'utf-8')).version;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Main release command entry point
|
|
113
|
+
*/
|
|
114
|
+
export async function releaseCommand(subcommand, args, options) {
|
|
115
|
+
const projectPath = process.cwd();
|
|
116
|
+
const releaseConfig = loadReleaseConfig(projectPath);
|
|
117
|
+
const workflow = resolveReleaseWorkflow(releaseConfig);
|
|
118
|
+
|
|
119
|
+
switch (subcommand) {
|
|
120
|
+
case 'promote':
|
|
121
|
+
if (!workflow.hasPromote) {
|
|
122
|
+
console.log(chalk.red('promote 子命令僅在 manual 或 hybrid 模式下可用。'));
|
|
123
|
+
console.log(chalk.gray(' 目前模式: ' + workflow.type));
|
|
124
|
+
console.log(chalk.gray(' 使用 `uds config --type release_mode` 切換模式'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await handlePromote(args, projectPath);
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'deploy':
|
|
131
|
+
if (!workflow.hasDeploy) {
|
|
132
|
+
console.log(chalk.red('deploy 子命令僅在 manual 或 hybrid 模式下可用。'));
|
|
133
|
+
console.log(chalk.gray(' 目前模式: ' + workflow.type));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await handleDeploy(args, options, projectPath);
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case 'manifest':
|
|
140
|
+
if (!workflow.hasManifest) {
|
|
141
|
+
console.log(chalk.red('manifest 子命令僅在 manual 或 hybrid 模式下可用。'));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await handleManifest(args, options, projectPath);
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case 'verify':
|
|
148
|
+
if (!workflow.hasManifest) {
|
|
149
|
+
console.log(chalk.red('verify 子命令僅在 manual 或 hybrid 模式下可用。'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
await handleVerify(projectPath);
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
default:
|
|
156
|
+
showReleaseHelp(workflow);
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* /release promote <version>
|
|
163
|
+
* Promote RC to stable version
|
|
164
|
+
*/
|
|
165
|
+
async function handlePromote(targetVersion, projectPath) {
|
|
166
|
+
|
|
167
|
+
if (!targetVersion) {
|
|
168
|
+
console.log(chalk.red('請指定目標 stable 版本號。'));
|
|
169
|
+
console.log(chalk.gray(' 用法: uds release promote 1.2.0'));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Verify this is a valid stable version (no pre-release suffix)
|
|
174
|
+
if (targetVersion.includes('-')) {
|
|
175
|
+
console.log(chalk.red('目標版本不應包含 pre-release 後綴。'));
|
|
176
|
+
console.log(chalk.gray(` 請使用: uds release promote ${targetVersion.split('-')[0]}`));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Find the latest RC for this version
|
|
181
|
+
const currentVersion = getPackageVersion(projectPath);
|
|
182
|
+
const parsed = currentVersion ? parseRCVersion(currentVersion) : null;
|
|
183
|
+
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(chalk.bold('Release Promote: RC → Stable'));
|
|
186
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
187
|
+
|
|
188
|
+
if (parsed) {
|
|
189
|
+
console.log(chalk.gray(` 目前版本: ${currentVersion}`));
|
|
190
|
+
console.log(chalk.gray(` 晉升目標: ${targetVersion}`));
|
|
191
|
+
} else {
|
|
192
|
+
console.log(chalk.gray(` 晉升目標: ${targetVersion}`));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Create promotion record
|
|
196
|
+
const record = createPromotionRecord(currentVersion || 'unknown', targetVersion);
|
|
197
|
+
console.log();
|
|
198
|
+
console.log(chalk.green(` ✓ 晉升紀錄建立: ${record.promoted_from} → ${record.version}`));
|
|
199
|
+
|
|
200
|
+
// Create git tag
|
|
201
|
+
const tag = formatGitTag(targetVersion);
|
|
202
|
+
console.log(chalk.green(` ✓ Git tag: ${tag}`));
|
|
203
|
+
console.log();
|
|
204
|
+
console.log(chalk.cyan('後續步驟:'));
|
|
205
|
+
console.log(chalk.gray(` 1. 更新版本檔案為 ${targetVersion}`));
|
|
206
|
+
console.log(chalk.gray(` 2. git tag ${tag}`));
|
|
207
|
+
console.log(chalk.gray(' 3. 從此 commit 重新打包'));
|
|
208
|
+
console.log(chalk.gray(' 4. 執行 uds release deploy production 記錄部署'));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* /release deploy <environment> [--result <result>]
|
|
213
|
+
*/
|
|
214
|
+
async function handleDeploy(environment, options, projectPath) {
|
|
215
|
+
|
|
216
|
+
if (!environment) {
|
|
217
|
+
console.log(chalk.red('請指定部署環境。'));
|
|
218
|
+
console.log(chalk.gray(' 用法: uds release deploy staging'));
|
|
219
|
+
console.log(chalk.gray(' 用法: uds release deploy staging --result passed'));
|
|
220
|
+
console.log(chalk.gray(' 用法: uds release deploy production'));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const currentVersion = getPackageVersion(projectPath) || 'unknown';
|
|
225
|
+
const deployments = loadDeployments(projectPath);
|
|
226
|
+
|
|
227
|
+
// If --result flag: update existing deployment
|
|
228
|
+
if (options.result) {
|
|
229
|
+
const { deployments: updated, updatedCount } = updateDeploymentResult(deployments, {
|
|
230
|
+
version: currentVersion,
|
|
231
|
+
environment,
|
|
232
|
+
result: options.result,
|
|
233
|
+
});
|
|
234
|
+
if (updatedCount === 0) {
|
|
235
|
+
console.log(chalk.yellow(`⚠ 找不到 ${currentVersion} 在 ${environment} 的部署紀錄`));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
saveDeployments(projectPath, updated);
|
|
239
|
+
console.log(chalk.green(`✓ 已更新 ${currentVersion} 在 ${environment} 的結果: ${options.result}`));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check readiness for production
|
|
244
|
+
if (environment === 'production') {
|
|
245
|
+
const warnings = checkDeploymentReadiness(deployments, {
|
|
246
|
+
version: currentVersion,
|
|
247
|
+
environment: 'production',
|
|
248
|
+
});
|
|
249
|
+
if (warnings.length > 0) {
|
|
250
|
+
console.log();
|
|
251
|
+
console.log(chalk.yellow('⚠ 警告:'));
|
|
252
|
+
for (const w of warnings) {
|
|
253
|
+
console.log(chalk.yellow(` - ${w}`));
|
|
254
|
+
}
|
|
255
|
+
console.log();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Record deployment
|
|
260
|
+
const record = recordDeployment({
|
|
261
|
+
version: currentVersion,
|
|
262
|
+
environment,
|
|
263
|
+
deployer: getGitUser(),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
deployments.push(record);
|
|
267
|
+
saveDeployments(projectPath, deployments);
|
|
268
|
+
|
|
269
|
+
console.log(chalk.green(`✓ 已記錄部署: ${currentVersion} → ${environment}`));
|
|
270
|
+
console.log(chalk.gray(` 部署者: ${record.deployer}`));
|
|
271
|
+
console.log(chalk.gray(` 時間: ${record.date}`));
|
|
272
|
+
|
|
273
|
+
if (environment === 'staging') {
|
|
274
|
+
console.log();
|
|
275
|
+
console.log(chalk.cyan('後續步驟:'));
|
|
276
|
+
console.log(chalk.gray(' 測試完成後執行:'));
|
|
277
|
+
console.log(chalk.gray(' uds release deploy staging --result passed'));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* /release manifest [--checksum <hash>]
|
|
283
|
+
*/
|
|
284
|
+
async function handleManifest(versionArg, options, projectPath) {
|
|
285
|
+
const version = getPackageVersion(projectPath) || versionArg || 'unknown';
|
|
286
|
+
const commit = getGitCommit();
|
|
287
|
+
const branch = getGitBranch();
|
|
288
|
+
const builder = getGitUser();
|
|
289
|
+
|
|
290
|
+
const manifest = createManifest({
|
|
291
|
+
version,
|
|
292
|
+
commit,
|
|
293
|
+
branch,
|
|
294
|
+
builder,
|
|
295
|
+
checksum: options.checksum || null,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const manifestPath = join(projectPath, 'build-manifest.json');
|
|
299
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
300
|
+
|
|
301
|
+
console.log(chalk.green('✓ build-manifest.json 已產生'));
|
|
302
|
+
console.log(chalk.gray(` 版本: ${manifest.version}`));
|
|
303
|
+
console.log(chalk.gray(` Commit: ${manifest.commit}`));
|
|
304
|
+
console.log(chalk.gray(` 分支: ${manifest.branch}`));
|
|
305
|
+
console.log(chalk.gray(` 建置者: ${manifest.builder}`));
|
|
306
|
+
console.log(chalk.gray(` 時間: ${manifest.build_date}`));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* /release verify
|
|
311
|
+
*/
|
|
312
|
+
async function handleVerify(projectPath) {
|
|
313
|
+
const manifestPath = join(projectPath, 'build-manifest.json');
|
|
314
|
+
|
|
315
|
+
if (!existsSync(manifestPath)) {
|
|
316
|
+
console.log(chalk.red('找不到 build-manifest.json'));
|
|
317
|
+
console.log(chalk.gray(' 請先執行 uds release manifest'));
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let manifest;
|
|
322
|
+
try {
|
|
323
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
324
|
+
} catch {
|
|
325
|
+
console.log(chalk.red('build-manifest.json 格式不正確'));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const currentCommit = getGitCommit();
|
|
329
|
+
|
|
330
|
+
const result = verifyManifest(manifest, currentCommit);
|
|
331
|
+
|
|
332
|
+
if (result.valid) {
|
|
333
|
+
console.log(chalk.green('✓ Manifest 驗證通過'));
|
|
334
|
+
console.log(chalk.gray(` 版本: ${manifest.version}`));
|
|
335
|
+
console.log(chalk.gray(` Commit: ${manifest.commit} (一致)`));
|
|
336
|
+
} else {
|
|
337
|
+
console.log(chalk.red('✗ Manifest 驗證失敗'));
|
|
338
|
+
for (const err of result.errors) {
|
|
339
|
+
console.log(chalk.red(` - ${err}`));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Show staging status
|
|
344
|
+
const deployments = loadDeployments(projectPath);
|
|
345
|
+
const stagingPassed = deployments.some(
|
|
346
|
+
(d) => d.environment === 'staging' && d.result === 'passed'
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
if (stagingPassed) {
|
|
350
|
+
console.log(chalk.green(' Staging: 已通過 ✓'));
|
|
351
|
+
} else {
|
|
352
|
+
console.log(chalk.yellow(' Staging: 未通過或無紀錄'));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Show help for release command
|
|
358
|
+
*/
|
|
359
|
+
function showReleaseHelp(workflow) {
|
|
360
|
+
console.log();
|
|
361
|
+
console.log(chalk.bold('uds release — 版本發布管理'));
|
|
362
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
363
|
+
console.log(chalk.gray(` 目前模式: ${workflow.type}`));
|
|
364
|
+
console.log();
|
|
365
|
+
|
|
366
|
+
if (workflow.hasPromote) {
|
|
367
|
+
console.log(chalk.cyan(' 可用子命令:'));
|
|
368
|
+
console.log(chalk.gray(' promote <version> RC → Stable 晉升'));
|
|
369
|
+
console.log(chalk.gray(' deploy <env> 記錄部署紀錄'));
|
|
370
|
+
console.log(chalk.gray(' deploy <env> --result 更新測試結果'));
|
|
371
|
+
console.log(chalk.gray(' manifest 產生 build-manifest.json'));
|
|
372
|
+
console.log(chalk.gray(' verify 驗證 manifest 一致性'));
|
|
373
|
+
} else {
|
|
374
|
+
console.log(chalk.gray(' CI/CD 模式下,請使用 /release start 和 /release finish'));
|
|
375
|
+
console.log(chalk.gray(' 切換到手動模式: uds config --type release_mode'));
|
|
376
|
+
}
|
|
377
|
+
console.log();
|
|
378
|
+
}
|
package/src/flows/init-flow.js
CHANGED
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
promptMethodology,
|
|
13
13
|
promptCommandsInstallation,
|
|
14
14
|
handleAgentsMdSharing,
|
|
15
|
-
promptAgentsMd
|
|
15
|
+
promptAgentsMd,
|
|
16
|
+
promptReleaseMode
|
|
16
17
|
} from '../prompts/init.js';
|
|
17
18
|
import {
|
|
18
19
|
promptIntegrationConfig
|
|
@@ -181,6 +182,9 @@ export async function runInitFlow(options, detected, projectPath) {
|
|
|
181
182
|
// === STEP 7: Standard Options ===
|
|
182
183
|
standardOptions = await promptStandardOptions(3, displayLanguage, { experimental: options.experimental });
|
|
183
184
|
|
|
185
|
+
// === STEP 7.5: Release Mode ===
|
|
186
|
+
const releaseMode = await promptReleaseMode();
|
|
187
|
+
|
|
184
188
|
// === STEP 8: Language Extensions ===
|
|
185
189
|
if (!languages) {
|
|
186
190
|
languages = await promptLanguage(detected.languages) || [];
|
|
@@ -243,6 +247,7 @@ export async function runInitFlow(options, detected, projectPath) {
|
|
|
243
247
|
aiTools,
|
|
244
248
|
integrations,
|
|
245
249
|
contentMode,
|
|
246
|
-
generateAgentsMd
|
|
250
|
+
generateAgentsMd,
|
|
251
|
+
releaseMode
|
|
247
252
|
};
|
|
248
253
|
}
|
package/src/i18n/messages.js
CHANGED
|
@@ -269,6 +269,32 @@ export const messages = {
|
|
|
269
269
|
}
|
|
270
270
|
},
|
|
271
271
|
|
|
272
|
+
// Release Mode
|
|
273
|
+
releaseMode: {
|
|
274
|
+
title: 'Release Mode:',
|
|
275
|
+
description: 'Choose release mode, affects how versions are published',
|
|
276
|
+
question: 'Select release mode:',
|
|
277
|
+
choices: {
|
|
278
|
+
'ci-cd': 'CI/CD automatic publishing (GitHub Actions, GitLab CI, etc.)',
|
|
279
|
+
manual: 'Manual packaging and deployment (RC workflow)',
|
|
280
|
+
hybrid: 'CI builds + manual deployment'
|
|
281
|
+
},
|
|
282
|
+
details: {
|
|
283
|
+
'ci-cd': [
|
|
284
|
+
' → Automatic: push tag → CI builds → publishes',
|
|
285
|
+
' → Best for: open source, npm/PyPI packages, SaaS'
|
|
286
|
+
],
|
|
287
|
+
manual: [
|
|
288
|
+
' → Package → deploy to staging → test → promote to production',
|
|
289
|
+
' → Best for: on-premise, air-gapped environments, manual QA'
|
|
290
|
+
],
|
|
291
|
+
hybrid: [
|
|
292
|
+
' → CI builds artifact, but deployment is manual',
|
|
293
|
+
' → Best for: regulated industries, multi-stage approval'
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
|
|
272
298
|
// Merge Strategy
|
|
273
299
|
mergeStrategy: {
|
|
274
300
|
title: 'Merge Strategy:',
|
|
@@ -953,6 +979,7 @@ export const messages = {
|
|
|
953
979
|
integrationConfigLabel: 'Integration Config',
|
|
954
980
|
ruleCategoriesLabel: 'Rule Categories',
|
|
955
981
|
gitWorkflow: 'Git Workflow',
|
|
982
|
+
releaseMode: 'Release Mode',
|
|
956
983
|
mergeStrategy: 'Merge Strategy',
|
|
957
984
|
commitLanguage: 'Commit Language',
|
|
958
985
|
testLevels: 'Test Levels',
|
|
@@ -1137,10 +1164,12 @@ export const messages = {
|
|
|
1137
1164
|
optionLevel: 'Adoption Level - Change Level 1/2/3',
|
|
1138
1165
|
optionContentMode: 'Content Mode - Change full/index/minimal',
|
|
1139
1166
|
optionMethodology: 'Methodology - Change development methodology',
|
|
1167
|
+
optionReleaseMode: 'Release Mode - CI/CD, Manual, or Hybrid',
|
|
1140
1168
|
optionAll: 'All Options',
|
|
1141
1169
|
experimental: '[Experimental]',
|
|
1142
1170
|
// Labels
|
|
1143
1171
|
gitWorkflow: 'Git Workflow',
|
|
1172
|
+
releaseMode: 'Release Mode',
|
|
1144
1173
|
mergeStrategy: 'Merge Strategy',
|
|
1145
1174
|
commitLanguage: 'Commit Language',
|
|
1146
1175
|
testLevels: 'Test Levels',
|
|
@@ -1499,6 +1528,32 @@ export const messages = {
|
|
|
1499
1528
|
}
|
|
1500
1529
|
},
|
|
1501
1530
|
|
|
1531
|
+
// Release Mode
|
|
1532
|
+
releaseMode: {
|
|
1533
|
+
title: '發布模式:',
|
|
1534
|
+
description: '選擇發布模式,影響版本如何發布',
|
|
1535
|
+
question: '選擇發布模式:',
|
|
1536
|
+
choices: {
|
|
1537
|
+
'ci-cd': 'CI/CD 自動發布(GitHub Actions、GitLab CI 等)',
|
|
1538
|
+
manual: '手動打包部署(RC 工作流程)',
|
|
1539
|
+
hybrid: 'CI 建置 + 手動部署'
|
|
1540
|
+
},
|
|
1541
|
+
details: {
|
|
1542
|
+
'ci-cd': [
|
|
1543
|
+
' → 自動化:推送 tag → CI 建置 → 發布',
|
|
1544
|
+
' → 適合:開源專案、npm/PyPI 套件、SaaS'
|
|
1545
|
+
],
|
|
1546
|
+
manual: [
|
|
1547
|
+
' → 打包 → 部署到測試機 → 測試 → 晉升到正式機',
|
|
1548
|
+
' → 適合:地端部署、隔離環境、手動 QA'
|
|
1549
|
+
],
|
|
1550
|
+
hybrid: [
|
|
1551
|
+
' → CI 建置產物,但部署是手動的',
|
|
1552
|
+
' → 適合:受監管產業、多階段審核'
|
|
1553
|
+
]
|
|
1554
|
+
}
|
|
1555
|
+
},
|
|
1556
|
+
|
|
1502
1557
|
// Merge Strategy
|
|
1503
1558
|
mergeStrategy: {
|
|
1504
1559
|
title: '合併策略:',
|
|
@@ -2183,6 +2238,7 @@ export const messages = {
|
|
|
2183
2238
|
integrationConfigLabel: '整合設定',
|
|
2184
2239
|
ruleCategoriesLabel: '規則類別',
|
|
2185
2240
|
gitWorkflow: 'Git 工作流程',
|
|
2241
|
+
releaseMode: '發布模式',
|
|
2186
2242
|
mergeStrategy: '合併策略',
|
|
2187
2243
|
commitLanguage: '提交訊息語言',
|
|
2188
2244
|
testLevels: '測試層級',
|
|
@@ -2367,10 +2423,12 @@ export const messages = {
|
|
|
2367
2423
|
optionLevel: '採用等級 - 變更等級 1/2/3',
|
|
2368
2424
|
optionContentMode: '內容模式 - 變更 full/index/minimal',
|
|
2369
2425
|
optionMethodology: '方法論 - 變更開發方法論',
|
|
2426
|
+
optionReleaseMode: '發布模式 - CI/CD、手動或混合',
|
|
2370
2427
|
optionAll: '全部選項',
|
|
2371
2428
|
experimental: '[實驗性]',
|
|
2372
2429
|
// Labels
|
|
2373
2430
|
gitWorkflow: 'Git 工作流程',
|
|
2431
|
+
releaseMode: '發布模式',
|
|
2374
2432
|
mergeStrategy: '合併策略',
|
|
2375
2433
|
commitLanguage: '提交訊息語言',
|
|
2376
2434
|
testLevels: '測試層級',
|
|
@@ -2729,6 +2787,32 @@ export const messages = {
|
|
|
2729
2787
|
}
|
|
2730
2788
|
},
|
|
2731
2789
|
|
|
2790
|
+
// Release Mode
|
|
2791
|
+
releaseMode: {
|
|
2792
|
+
title: '发布模式:',
|
|
2793
|
+
description: '选择发布模式,影响版本如何发布',
|
|
2794
|
+
question: '选择发布模式:',
|
|
2795
|
+
choices: {
|
|
2796
|
+
'ci-cd': 'CI/CD 自动发布(GitHub Actions、GitLab CI 等)',
|
|
2797
|
+
manual: '手动打包部署(RC 工作流程)',
|
|
2798
|
+
hybrid: 'CI 构建 + 手动部署'
|
|
2799
|
+
},
|
|
2800
|
+
details: {
|
|
2801
|
+
'ci-cd': [
|
|
2802
|
+
' → 自动化:推送 tag → CI 构建 → 发布',
|
|
2803
|
+
' → 适合:开源项目、npm/PyPI 包、SaaS'
|
|
2804
|
+
],
|
|
2805
|
+
manual: [
|
|
2806
|
+
' → 打包 → 部署到测试机 → 测试 → 晋升到正式机',
|
|
2807
|
+
' → 适合:本地部署、隔离环境、手动 QA'
|
|
2808
|
+
],
|
|
2809
|
+
hybrid: [
|
|
2810
|
+
' → CI 构建产物,但部署是手动的',
|
|
2811
|
+
' → 适合:受监管行业、多阶段审批'
|
|
2812
|
+
]
|
|
2813
|
+
}
|
|
2814
|
+
},
|
|
2815
|
+
|
|
2732
2816
|
// Merge Strategy
|
|
2733
2817
|
mergeStrategy: {
|
|
2734
2818
|
title: '合并策略:',
|
|
@@ -3159,6 +3243,7 @@ export const messages = {
|
|
|
3159
3243
|
integrationConfigLabel: '集成配置',
|
|
3160
3244
|
ruleCategoriesLabel: '规则类别',
|
|
3161
3245
|
gitWorkflow: 'Git 工作流',
|
|
3246
|
+
releaseMode: '发布模式',
|
|
3162
3247
|
mergeStrategy: '合并策略',
|
|
3163
3248
|
commitLanguage: '提交消息语言',
|
|
3164
3249
|
testLevels: '测试级别',
|
|
@@ -3526,10 +3611,12 @@ export const messages = {
|
|
|
3526
3611
|
optionLevel: '采用级别 - 更改级别 1/2/3',
|
|
3527
3612
|
optionContentMode: '内容模式 - 更改 full/index/minimal',
|
|
3528
3613
|
optionMethodology: '方法论 - 更改开发方法论',
|
|
3614
|
+
optionReleaseMode: '发布模式 - CI/CD、手动或混合',
|
|
3529
3615
|
optionAll: '所有选项',
|
|
3530
3616
|
experimental: '[实验性]',
|
|
3531
3617
|
// Labels
|
|
3532
3618
|
gitWorkflow: 'Git 工作流',
|
|
3619
|
+
releaseMode: '发布模式',
|
|
3533
3620
|
mergeStrategy: '合并策略',
|
|
3534
3621
|
commitLanguage: 'Commit 消息语言',
|
|
3535
3622
|
testLevels: '测试级别',
|
|
@@ -35,7 +35,8 @@ export function writeFinalManifest(config, results, projectPath) {
|
|
|
35
35
|
workflow: config.standardOptions?.workflow || null,
|
|
36
36
|
merge_strategy: config.standardOptions?.merge_strategy || null,
|
|
37
37
|
commit_language: config.standardOptions?.commit_language || null,
|
|
38
|
-
test_levels: config.standardOptions?.test_levels || []
|
|
38
|
+
test_levels: config.standardOptions?.test_levels || [],
|
|
39
|
+
release_mode: config.releaseMode || 'ci-cd'
|
|
39
40
|
},
|
|
40
41
|
generateAgentsMd: config.generateAgentsMd || false,
|
|
41
42
|
aiTools: config.aiTools || [],
|
package/src/prompts/init.js
CHANGED
|
@@ -643,6 +643,58 @@ export async function promptGitWorkflow() {
|
|
|
643
643
|
return workflow;
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
/**
|
|
647
|
+
* Prompt for release mode
|
|
648
|
+
*
|
|
649
|
+
* Release modes determine how versions are published:
|
|
650
|
+
* - ci-cd: Automatic publishing via CI/CD pipeline
|
|
651
|
+
* - manual: Manual packaging and deployment with RC workflow
|
|
652
|
+
* - hybrid: CI builds artifact, manual deployment
|
|
653
|
+
*
|
|
654
|
+
* @returns {Promise<string>} Selected release mode ID
|
|
655
|
+
*/
|
|
656
|
+
export async function promptReleaseMode() {
|
|
657
|
+
const msg = t().releaseMode;
|
|
658
|
+
|
|
659
|
+
console.log();
|
|
660
|
+
console.log(chalk.cyan(msg.title));
|
|
661
|
+
console.log(chalk.gray(` ${msg.description}`));
|
|
662
|
+
console.log();
|
|
663
|
+
|
|
664
|
+
const { mode } = await inquirer.prompt([
|
|
665
|
+
{
|
|
666
|
+
type: 'list',
|
|
667
|
+
name: 'mode',
|
|
668
|
+
message: msg.question,
|
|
669
|
+
suffix: ' ',
|
|
670
|
+
choices: [
|
|
671
|
+
{
|
|
672
|
+
name: `${chalk.green('CI/CD')} ${chalk.gray(`(${t().recommended})`)} - ${msg.choices['ci-cd']}`,
|
|
673
|
+
value: 'ci-cd'
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: `${chalk.blue('Manual')} - ${msg.choices.manual}`,
|
|
677
|
+
value: 'manual'
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: `${chalk.yellow('Hybrid')} - ${msg.choices.hybrid}`,
|
|
681
|
+
value: 'hybrid'
|
|
682
|
+
}
|
|
683
|
+
],
|
|
684
|
+
default: 'ci-cd'
|
|
685
|
+
}
|
|
686
|
+
]);
|
|
687
|
+
|
|
688
|
+
// Show mode details
|
|
689
|
+
console.log();
|
|
690
|
+
for (const line of msg.details[mode]) {
|
|
691
|
+
console.log(chalk.gray(line));
|
|
692
|
+
}
|
|
693
|
+
console.log();
|
|
694
|
+
|
|
695
|
+
return mode;
|
|
696
|
+
}
|
|
697
|
+
|
|
646
698
|
/**
|
|
647
699
|
* Prompt for merge strategy
|
|
648
700
|
*
|