@sk8metal/michi-cli 0.0.1 → 0.0.3
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 +30 -0
- package/README.md +60 -24
- package/dist/scripts/__tests__/validate-phase.test.d.ts +5 -0
- package/dist/scripts/__tests__/validate-phase.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/validate-phase.test.js +162 -0
- package/dist/scripts/__tests__/validate-phase.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/config-validator.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/config-validator.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js +247 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/feature-name-validator.test.js +106 -0
- package/dist/scripts/utils/__tests__/feature-name-validator.test.js.map +1 -0
- package/dist/scripts/utils/config-loader.js +1 -1
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.js +2 -1
- package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
- package/dist/src/__tests__/cli.test.d.ts +5 -0
- package/dist/src/__tests__/cli.test.d.ts.map +1 -0
- package/dist/src/__tests__/cli.test.js +58 -0
- package/dist/src/__tests__/cli.test.js.map +1 -0
- package/dist/src/cli.js +0 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +29 -0
- package/dist/vitest.config.js.map +1 -0
- package/docs/setup.md +1 -1
- package/package.json +8 -4
- package/scripts/__tests__/README.md +101 -0
- package/scripts/__tests__/validate-phase.test.ts +185 -0
- package/scripts/config/config-schema.ts +130 -0
- package/scripts/config/default-config.json +57 -0
- package/scripts/config-interactive.ts +494 -0
- package/scripts/confluence-sync.ts +503 -0
- package/scripts/create-project.ts +293 -0
- package/scripts/jira-sync.ts +644 -0
- package/scripts/list-projects.ts +85 -0
- package/scripts/markdown-to-confluence.ts +161 -0
- package/scripts/multi-project-estimate.ts +255 -0
- package/scripts/phase-runner.ts +303 -0
- package/scripts/pr-automation.ts +67 -0
- package/scripts/pre-flight-check.ts +285 -0
- package/scripts/resource-dashboard.ts +124 -0
- package/scripts/setup-env.sh +52 -0
- package/scripts/setup-existing-project.ts +381 -0
- package/scripts/setup-existing.sh +145 -0
- package/scripts/utils/__tests__/config-validator.test.ts +302 -0
- package/scripts/utils/__tests__/feature-name-validator.test.ts +129 -0
- package/scripts/utils/config-loader.ts +326 -0
- package/scripts/utils/config-validator.ts +347 -0
- package/scripts/utils/confluence-hierarchy.ts +855 -0
- package/scripts/utils/feature-name-validator.ts +135 -0
- package/scripts/utils/project-meta.ts +69 -0
- package/scripts/validate-phase.ts +279 -0
- package/scripts/workflow-orchestrator.ts +178 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* 新規プロジェクト自動セットアップスクリプト
|
|
4
|
+
*
|
|
5
|
+
* 使い方:
|
|
6
|
+
* npm run create-project -- \
|
|
7
|
+
* --name "customer-a-service-1" \
|
|
8
|
+
* --project-name "A社 サービス1" \
|
|
9
|
+
* --jira-key "PRJA"
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import { writeFileSync, mkdirSync, cpSync, existsSync } from 'fs';
|
|
14
|
+
import { resolve, join, dirname } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { config as loadDotenv } from 'dotenv';
|
|
17
|
+
|
|
18
|
+
// ESモジュール対応: __dirnameの代替
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
21
|
+
|
|
22
|
+
loadDotenv();
|
|
23
|
+
|
|
24
|
+
interface ProjectConfig {
|
|
25
|
+
name: string; // リポジトリ名: customer-a-service-1
|
|
26
|
+
projectName: string; // 表示名: A社 サービス1
|
|
27
|
+
jiraKey: string; // JIRAキー: PRJA
|
|
28
|
+
org?: string; // GitHub組織名(デフォルト: .envから)
|
|
29
|
+
labels?: string[]; // Confluenceラベル(デフォルト: 自動生成)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseArgs(): ProjectConfig {
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const config: Partial<ProjectConfig> = {};
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
37
|
+
const key = args[i].replace(/^--/, '');
|
|
38
|
+
const value = args[i + 1];
|
|
39
|
+
|
|
40
|
+
switch (key) {
|
|
41
|
+
case 'name':
|
|
42
|
+
config.name = value;
|
|
43
|
+
break;
|
|
44
|
+
case 'project-name':
|
|
45
|
+
config.projectName = value;
|
|
46
|
+
break;
|
|
47
|
+
case 'jira-key':
|
|
48
|
+
config.jiraKey = value;
|
|
49
|
+
break;
|
|
50
|
+
case 'org':
|
|
51
|
+
config.org = value;
|
|
52
|
+
break;
|
|
53
|
+
case 'labels':
|
|
54
|
+
// カンマ区切りでラベル配列に変換
|
|
55
|
+
config.labels = value.split(',').map(l => l.trim());
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 必須フィールドチェック
|
|
61
|
+
if (!config.name || !config.projectName || !config.jiraKey) {
|
|
62
|
+
console.error('Missing required parameters');
|
|
63
|
+
console.error('Usage: npm run create-project -- --name <name> --project-name <display> --jira-key <key>');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return config as ProjectConfig;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function createProject(config: ProjectConfig): Promise<void> {
|
|
71
|
+
const org = config.org || process.env.GITHUB_ORG;
|
|
72
|
+
|
|
73
|
+
if (!org) {
|
|
74
|
+
console.error('❌ GitHub organization not specified');
|
|
75
|
+
console.error(' Set GITHUB_ORG in .env or use --org parameter');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const repoName = config.name;
|
|
80
|
+
const repoUrl = `https://github.com/${org}/${repoName}`;
|
|
81
|
+
const projectDir = resolve(`../${repoName}`);
|
|
82
|
+
|
|
83
|
+
console.log(`🚀 Creating new project: ${config.projectName}`);
|
|
84
|
+
console.log(` Repository: ${org}/${repoName}`);
|
|
85
|
+
console.log(` JIRA: ${config.jiraKey}`);
|
|
86
|
+
console.log('');
|
|
87
|
+
|
|
88
|
+
// Step 1: GitHubリポジトリ作成
|
|
89
|
+
console.log('📦 Step 1: Creating GitHub repository...');
|
|
90
|
+
try {
|
|
91
|
+
execSync(
|
|
92
|
+
`gh repo create ${org}/${repoName} --private --description "${config.projectName}"`,
|
|
93
|
+
{ stdio: 'inherit' }
|
|
94
|
+
);
|
|
95
|
+
console.log(' ✅ Repository created');
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.log(' ⚠️ Repository may already exist');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Step 2: リポジトリクローン
|
|
101
|
+
console.log('\n📥 Step 2: Cloning repository...');
|
|
102
|
+
try {
|
|
103
|
+
execSync(`jj git clone ${repoUrl} ${projectDir}`, { stdio: 'inherit' });
|
|
104
|
+
console.log(' ✅ Repository cloned');
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.log(' ⚠️ Clone failed, checking if directory exists...');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Step 3: ディレクトリ存在確認と移動
|
|
110
|
+
if (!existsSync(projectDir)) {
|
|
111
|
+
console.error(' ❌ Project directory does not exist. Clone may have failed.');
|
|
112
|
+
console.error(` Expected directory: ${projectDir}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
process.chdir(projectDir);
|
|
117
|
+
console.log(` 📂 Working directory: ${projectDir}`);
|
|
118
|
+
|
|
119
|
+
// Step 4: cc-sdd導入
|
|
120
|
+
console.log('\n⚙️ Step 4: Installing cc-sdd...');
|
|
121
|
+
execSync('npx cc-sdd@latest --cursor --lang ja --yes', { stdio: 'inherit' });
|
|
122
|
+
console.log(' ✅ cc-sdd installed');
|
|
123
|
+
|
|
124
|
+
// Step 5: .kiro/project.json 作成
|
|
125
|
+
console.log('\n📝 Step 5: Creating project metadata...');
|
|
126
|
+
mkdirSync('.kiro', { recursive: true });
|
|
127
|
+
|
|
128
|
+
// ラベル生成(安全なフォールバック付き)
|
|
129
|
+
const labels = config.labels || (() => {
|
|
130
|
+
// プロジェクトIDからプロジェクトラベル生成
|
|
131
|
+
const projectLabel = repoName.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
132
|
+
const labelSet = new Set([`project:${projectLabel}`]);
|
|
133
|
+
|
|
134
|
+
// ハイフンが存在する場合のみサービスラベルを生成
|
|
135
|
+
if (repoName.includes('-')) {
|
|
136
|
+
const parts = repoName.split('-');
|
|
137
|
+
const servicePart = parts[parts.length - 1];
|
|
138
|
+
const serviceLabel = servicePart.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
139
|
+
|
|
140
|
+
// サービスラベルがプロジェクトラベルと異なる場合のみ追加
|
|
141
|
+
if (serviceLabel !== projectLabel) {
|
|
142
|
+
labelSet.add(`service:${serviceLabel}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return Array.from(labelSet);
|
|
147
|
+
})();
|
|
148
|
+
|
|
149
|
+
const projectJson = {
|
|
150
|
+
projectId: repoName,
|
|
151
|
+
projectName: config.projectName,
|
|
152
|
+
jiraProjectKey: config.jiraKey,
|
|
153
|
+
confluenceLabels: labels,
|
|
154
|
+
status: 'active',
|
|
155
|
+
team: [],
|
|
156
|
+
stakeholders: ['@企画', '@部長'],
|
|
157
|
+
repository: repoUrl,
|
|
158
|
+
description: `${config.projectName}の開発`
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
writeFileSync('.kiro/project.json', JSON.stringify(projectJson, null, 2));
|
|
162
|
+
console.log(' ✅ project.json created');
|
|
163
|
+
|
|
164
|
+
// Step 6: Michiから共通ファイルをコピー
|
|
165
|
+
console.log('\n📋 Step 6: Copying common files from Michi...');
|
|
166
|
+
const michiPath = resolve(__dirname, '..');
|
|
167
|
+
|
|
168
|
+
// コピー先ディレクトリを事前に作成
|
|
169
|
+
mkdirSync('.cursor/rules', { recursive: true });
|
|
170
|
+
mkdirSync('.cursor/commands/kiro', { recursive: true });
|
|
171
|
+
mkdirSync('.kiro/steering', { recursive: true });
|
|
172
|
+
mkdirSync('.kiro/settings/templates', { recursive: true });
|
|
173
|
+
mkdirSync('scripts/utils', { recursive: true });
|
|
174
|
+
|
|
175
|
+
// ルールファイル
|
|
176
|
+
const rulesToCopy = [
|
|
177
|
+
'multi-project.mdc',
|
|
178
|
+
'github-ssot.mdc',
|
|
179
|
+
'atlassian-mcp.mdc'
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
for (const rule of rulesToCopy) {
|
|
183
|
+
const src = join(michiPath, '.cursor/rules', rule);
|
|
184
|
+
const dest = join(projectDir, '.cursor/rules', rule);
|
|
185
|
+
if (existsSync(src)) {
|
|
186
|
+
cpSync(src, dest);
|
|
187
|
+
console.log(` ✅ Copied: .cursor/rules/${rule}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// カスタムコマンド
|
|
192
|
+
const commandsToCopy = [
|
|
193
|
+
'confluence-sync.md',
|
|
194
|
+
'project-switch.md'
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
for (const cmd of commandsToCopy) {
|
|
198
|
+
const src = join(michiPath, '.cursor/commands/kiro', cmd);
|
|
199
|
+
const dest = join(projectDir, '.cursor/commands/kiro', cmd);
|
|
200
|
+
if (existsSync(src)) {
|
|
201
|
+
cpSync(src, dest);
|
|
202
|
+
console.log(` ✅ Copied: .cursor/commands/kiro/${cmd}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Steering
|
|
207
|
+
const steeringDir = join(michiPath, '.kiro/steering');
|
|
208
|
+
if (existsSync(steeringDir)) {
|
|
209
|
+
mkdirSync('.kiro/steering', { recursive: true });
|
|
210
|
+
cpSync(steeringDir, '.kiro/steering', { recursive: true });
|
|
211
|
+
console.log(' ✅ Copied: .kiro/steering/');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Scripts(必要なスクリプトのみコピー)
|
|
215
|
+
const scriptsDir = join(michiPath, 'scripts');
|
|
216
|
+
if (existsSync(scriptsDir)) {
|
|
217
|
+
const scriptsToCopy = [
|
|
218
|
+
'confluence-sync.ts',
|
|
219
|
+
'jira-sync.ts',
|
|
220
|
+
'pr-automation.ts',
|
|
221
|
+
'markdown-to-confluence.ts',
|
|
222
|
+
'workflow-orchestrator.ts',
|
|
223
|
+
'list-projects.ts',
|
|
224
|
+
'resource-dashboard.ts',
|
|
225
|
+
'multi-project-estimate.ts',
|
|
226
|
+
'utils/project-meta.ts'
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
for (const script of scriptsToCopy) {
|
|
230
|
+
const src = join(scriptsDir, script);
|
|
231
|
+
const dest = join(projectDir, 'scripts', script);
|
|
232
|
+
if (existsSync(src)) {
|
|
233
|
+
// ディレクトリが必要な場合は作成
|
|
234
|
+
const destDir = dirname(dest);
|
|
235
|
+
mkdirSync(destDir, { recursive: true });
|
|
236
|
+
cpSync(src, dest);
|
|
237
|
+
console.log(` ✅ Copied: scripts/${script}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// package.json, tsconfig.json
|
|
243
|
+
['package.json', 'tsconfig.json'].forEach(file => {
|
|
244
|
+
const src = join(michiPath, file);
|
|
245
|
+
if (existsSync(src)) {
|
|
246
|
+
cpSync(src, file);
|
|
247
|
+
console.log(` ✅ Copied: ${file}`);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Step 7: .env テンプレート作成
|
|
252
|
+
console.log('\n🔐 Step 7: Creating .env template...');
|
|
253
|
+
execSync('npm run setup:env', { stdio: 'inherit' });
|
|
254
|
+
console.log(' ✅ .env created');
|
|
255
|
+
|
|
256
|
+
// Step 8: npm install
|
|
257
|
+
console.log('\n📦 Step 8: Installing dependencies...');
|
|
258
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
259
|
+
console.log(' ✅ Dependencies installed');
|
|
260
|
+
|
|
261
|
+
// Step 9: 初期コミット
|
|
262
|
+
console.log('\n💾 Step 9: Creating initial commit...');
|
|
263
|
+
execSync(`jj commit -m "chore: プロジェクト初期化
|
|
264
|
+
|
|
265
|
+
- cc-sdd導入
|
|
266
|
+
- プロジェクトメタデータ設定(${config.jiraKey})
|
|
267
|
+
- 自動化スクリプト追加
|
|
268
|
+
- Confluence/JIRA連携設定"`, { stdio: 'inherit' });
|
|
269
|
+
|
|
270
|
+
execSync('jj bookmark create main -r "@-"', { stdio: 'inherit' });
|
|
271
|
+
console.log(' ✅ Initial commit created');
|
|
272
|
+
|
|
273
|
+
// 完了メッセージ
|
|
274
|
+
console.log('\n');
|
|
275
|
+
console.log('🎉 プロジェクトセットアップ完了!');
|
|
276
|
+
console.log('');
|
|
277
|
+
console.log('次のステップ:');
|
|
278
|
+
console.log(` 1. cd ${projectDir}`);
|
|
279
|
+
console.log(' 2. .env ファイルを編集して認証情報を設定');
|
|
280
|
+
console.log(' 3. jj git push --bookmark main --allow-new');
|
|
281
|
+
console.log(' 4. Cursor で開く: cursor .');
|
|
282
|
+
console.log(' 5. /kiro:spec-init <機能説明> で開発開始');
|
|
283
|
+
console.log('');
|
|
284
|
+
console.log('詳細: docs/new-project-setup.md');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 実行
|
|
288
|
+
const config = parseArgs();
|
|
289
|
+
createProject(config).catch(error => {
|
|
290
|
+
console.error('❌ Error:', error.message);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
});
|
|
293
|
+
|