@sk8metal/michi-cli 0.4.0 → 0.5.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/CHANGELOG.md +40 -0
- package/dist/scripts/__tests__/spec-impl-workflow.test.js +4 -2
- package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +1 -1
- package/dist/scripts/config/config-schema.d.ts +52 -0
- package/dist/scripts/config/config-schema.d.ts.map +1 -1
- package/dist/scripts/config/config-schema.js +25 -0
- package/dist/scripts/config/config-schema.js.map +1 -1
- package/dist/scripts/pr-automation.d.ts.map +1 -1
- package/dist/scripts/pr-automation.js +11 -3
- package/dist/scripts/pr-automation.js.map +1 -1
- package/dist/scripts/spec-impl-workflow.d.ts.map +1 -1
- package/dist/scripts/spec-impl-workflow.js +22 -6
- package/dist/scripts/spec-impl-workflow.js.map +1 -1
- package/dist/scripts/utils/__tests__/config-loader.test.js +114 -1
- package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/config-validator.test.js +2 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/env-config.test.js +0 -2
- package/dist/scripts/utils/__tests__/env-config.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/project-meta.test.d.ts +6 -0
- package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/project-meta.test.js +154 -0
- package/dist/scripts/utils/__tests__/project-meta.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/security-validator.test.d.ts +6 -0
- package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/security-validator.test.js +219 -0
- package/dist/scripts/utils/__tests__/security-validator.test.js.map +1 -0
- package/dist/scripts/utils/config-loader.d.ts +10 -4
- package/dist/scripts/utils/config-loader.d.ts.map +1 -1
- package/dist/scripts/utils/config-loader.js +214 -47
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/env-config.d.ts +1 -1
- package/dist/scripts/utils/env-config.d.ts.map +1 -1
- package/dist/scripts/utils/env-config.js +2 -14
- package/dist/scripts/utils/env-config.js.map +1 -1
- package/dist/scripts/utils/project-meta.d.ts +9 -0
- package/dist/scripts/utils/project-meta.d.ts.map +1 -1
- package/dist/scripts/utils/project-meta.js +22 -0
- package/dist/scripts/utils/project-meta.js.map +1 -1
- package/dist/scripts/utils/security-validator.d.ts +55 -0
- package/dist/scripts/utils/security-validator.d.ts.map +1 -0
- package/dist/scripts/utils/security-validator.js +232 -0
- package/dist/scripts/utils/security-validator.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +41 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/__tests__/init.test.d.ts +5 -0
- package/dist/src/commands/__tests__/init.test.d.ts.map +1 -0
- package/dist/src/commands/__tests__/init.test.js +255 -0
- package/dist/src/commands/__tests__/init.test.js.map +1 -0
- package/dist/src/commands/__tests__/migrate.test.d.ts +5 -0
- package/dist/src/commands/__tests__/migrate.test.d.ts.map +1 -0
- package/dist/src/commands/__tests__/migrate.test.js +216 -0
- package/dist/src/commands/__tests__/migrate.test.js.map +1 -0
- package/dist/src/commands/config-validate.d.ts +9 -0
- package/dist/src/commands/config-validate.d.ts.map +1 -0
- package/dist/src/commands/config-validate.js +90 -0
- package/dist/src/commands/config-validate.js.map +1 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +29 -6
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/migrate.d.ts +25 -0
- package/dist/src/commands/migrate.d.ts.map +1 -0
- package/dist/src/commands/migrate.js +341 -0
- package/dist/src/commands/migrate.js.map +1 -0
- package/dist/src/commands/setup-existing.d.ts.map +1 -1
- package/dist/src/commands/setup-existing.js +0 -1
- package/dist/src/commands/setup-existing.js.map +1 -1
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +32 -8
- package/dist/vitest.config.js.map +1 -1
- package/docs/michi-development/design/config-unification.md +4789 -0
- package/docs/user-guide/getting-started/github-token-setup.md +2 -1
- package/docs/user-guide/getting-started/new-repository-setup.md +1 -1
- package/docs/user-guide/getting-started/quick-start.md +1 -1
- package/docs/user-guide/getting-started/setup.md +4 -11
- package/docs/user-guide/hands-on/claude-agent-setup.md +2 -2
- package/docs/user-guide/hands-on/claude-setup.md +2 -2
- package/docs/user-guide/hands-on/cursor-setup.md +2 -2
- package/docs/user-guide/hands-on/workflow-walkthrough.md +4 -1
- package/env.example +1 -1
- package/package.json +2 -2
- package/scripts/__tests__/spec-impl-workflow.test.ts +5 -2
- package/scripts/config/config-schema.ts +40 -0
- package/scripts/pr-automation.ts +15 -5
- package/scripts/spec-impl-workflow.ts +22 -6
- package/scripts/utils/__tests__/config-loader.test.ts +149 -0
- package/scripts/utils/__tests__/config-validator.test.ts +2 -0
- package/scripts/utils/__tests__/env-config.test.ts +0 -2
- package/scripts/utils/__tests__/project-meta.test.ts +192 -0
- package/scripts/utils/__tests__/security-validator.test.ts +272 -0
- package/scripts/utils/config-loader.ts +232 -53
- package/scripts/utils/env-config.ts +2 -14
- package/scripts/utils/project-meta.ts +27 -0
- package/scripts/utils/security-validator.ts +286 -0
- package/templates/claude/commands/kiro/kiro-spec-impl.md +1 -1
- package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +1 -1
- package/templates/cursor/commands/kiro/kiro-spec-impl.md +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* security-validator.ts
|
|
3
|
+
* セキュリティ検証ユーティリティ
|
|
4
|
+
*
|
|
5
|
+
* 環境変数やAPIトークンなど機密情報のバリデーションとセキュリティチェックを行います。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { statSync } from 'fs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* バリデーション結果
|
|
12
|
+
*/
|
|
13
|
+
export interface ValidationResult {
|
|
14
|
+
isValid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
warnings: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* APIトークン形式の検証
|
|
21
|
+
*/
|
|
22
|
+
export function validateAtlassianToken(token: string): ValidationResult {
|
|
23
|
+
const errors: string[] = [];
|
|
24
|
+
const warnings: string[] = [];
|
|
25
|
+
|
|
26
|
+
if (!token || token.trim() === '') {
|
|
27
|
+
errors.push('Atlassian API token is empty');
|
|
28
|
+
return { isValid: false, errors, warnings };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (token.includes(' ')) {
|
|
32
|
+
errors.push('Atlassian API token contains spaces');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (token === 'your-token-here' || token === 'test-token' || token === 'token123') {
|
|
36
|
+
errors.push('Atlassian API token is a placeholder value');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (token.length < 20) {
|
|
40
|
+
warnings.push('Atlassian API token seems too short (expected >20 characters)');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
isValid: errors.length === 0,
|
|
45
|
+
errors,
|
|
46
|
+
warnings,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* GitHub Personal Access Token の検証
|
|
52
|
+
*/
|
|
53
|
+
export function validateGitHubToken(token: string): ValidationResult {
|
|
54
|
+
const errors: string[] = [];
|
|
55
|
+
const warnings: string[] = [];
|
|
56
|
+
|
|
57
|
+
if (!token || token.trim() === '') {
|
|
58
|
+
errors.push('GitHub token is empty');
|
|
59
|
+
} else if (token.includes(' ')) {
|
|
60
|
+
errors.push('GitHub token contains spaces');
|
|
61
|
+
} else if (!token.startsWith('ghp_') && !token.startsWith('github_pat_')) {
|
|
62
|
+
warnings.push('GitHub token does not start with expected prefix (ghp_ or github_pat_)');
|
|
63
|
+
} else if (token === 'ghp_xxx' || token === 'your-github-token' || token === 'github-token') {
|
|
64
|
+
errors.push('GitHub token is a placeholder value');
|
|
65
|
+
} else if (token.length < 30) {
|
|
66
|
+
warnings.push('GitHub token seems too short');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
isValid: errors.length === 0,
|
|
71
|
+
errors,
|
|
72
|
+
warnings,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* メールアドレス形式の検証
|
|
78
|
+
*/
|
|
79
|
+
export function validateEmail(email: string): ValidationResult {
|
|
80
|
+
const errors: string[] = [];
|
|
81
|
+
const warnings: string[] = [];
|
|
82
|
+
|
|
83
|
+
if (!email || email.trim() === '') {
|
|
84
|
+
errors.push('Email is empty');
|
|
85
|
+
} else {
|
|
86
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
87
|
+
if (!emailRegex.test(email)) {
|
|
88
|
+
errors.push('Email format is invalid');
|
|
89
|
+
} else if (
|
|
90
|
+
email === 'user@example.com' ||
|
|
91
|
+
email.includes('your-email') ||
|
|
92
|
+
email === 'test@example.com'
|
|
93
|
+
) {
|
|
94
|
+
errors.push('Email is a placeholder value');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
isValid: errors.length === 0,
|
|
100
|
+
errors,
|
|
101
|
+
warnings,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* URL形式の検証
|
|
107
|
+
*/
|
|
108
|
+
export function validateUrl(url: string, expectedDomain?: string): ValidationResult {
|
|
109
|
+
const errors: string[] = [];
|
|
110
|
+
const warnings: string[] = [];
|
|
111
|
+
|
|
112
|
+
if (!url || url.trim() === '') {
|
|
113
|
+
errors.push('URL is empty');
|
|
114
|
+
} else {
|
|
115
|
+
try {
|
|
116
|
+
const parsedUrl = new URL(url);
|
|
117
|
+
|
|
118
|
+
if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
|
|
119
|
+
errors.push(`Invalid URL protocol: ${parsedUrl.protocol} (expected https: or http:)`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (parsedUrl.protocol === 'http:') {
|
|
123
|
+
warnings.push('URL uses insecure http:// protocol (consider using https://)');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (expectedDomain && !parsedUrl.hostname.includes(expectedDomain)) {
|
|
127
|
+
errors.push(`URL hostname ${parsedUrl.hostname} does not contain expected domain: ${expectedDomain}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (url === 'https://example.com' || url === 'https://your-domain.atlassian.net') {
|
|
131
|
+
errors.push('URL is a placeholder value');
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
errors.push(`URL parsing failed: ${error instanceof Error ? error.message : error}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
isValid: errors.length === 0,
|
|
140
|
+
errors,
|
|
141
|
+
warnings,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Atlassian URL の検証
|
|
147
|
+
*/
|
|
148
|
+
export function validateAtlassianUrl(url: string): ValidationResult {
|
|
149
|
+
const result = validateUrl(url, 'atlassian.net');
|
|
150
|
+
|
|
151
|
+
if (result.isValid) {
|
|
152
|
+
try {
|
|
153
|
+
const parsedUrl = new URL(url);
|
|
154
|
+
if (!parsedUrl.hostname.endsWith('.atlassian.net')) {
|
|
155
|
+
result.errors.push('Atlassian URL must end with .atlassian.net');
|
|
156
|
+
result.isValid = false;
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// Already handled in validateUrl
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* GitHub リポジトリ URL の検証
|
|
168
|
+
*/
|
|
169
|
+
export function validateGitHubRepositoryUrl(url: string): ValidationResult {
|
|
170
|
+
const errors: string[] = [];
|
|
171
|
+
const warnings: string[] = [];
|
|
172
|
+
|
|
173
|
+
if (!url || url.trim() === '') {
|
|
174
|
+
errors.push('Repository URL is empty');
|
|
175
|
+
} else {
|
|
176
|
+
// GitHub URL 形式のパターン
|
|
177
|
+
const httpsPattern = /^https:\/\/github\.com\/[\w-]+\/[\w-]+(\.git)?$/;
|
|
178
|
+
const sshPattern = /^git@github\.com:[\w-]+\/[\w-]+(\.git)?$/;
|
|
179
|
+
|
|
180
|
+
if (!httpsPattern.test(url) && !sshPattern.test(url)) {
|
|
181
|
+
errors.push(
|
|
182
|
+
'Repository URL must be in format: https://github.com/owner/repo.git or git@github.com:owner/repo.git',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (url.includes('your-org') || url.includes('your-repo')) {
|
|
187
|
+
errors.push('Repository URL contains placeholder values');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
isValid: errors.length === 0,
|
|
193
|
+
errors,
|
|
194
|
+
warnings,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 環境変数の包括的検証
|
|
200
|
+
*/
|
|
201
|
+
export interface EnvValidationConfig {
|
|
202
|
+
atlassianUrl?: string;
|
|
203
|
+
atlassianEmail?: string;
|
|
204
|
+
atlassianApiToken?: string;
|
|
205
|
+
githubOrg?: string;
|
|
206
|
+
githubToken?: string;
|
|
207
|
+
repositoryUrl?: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function validateEnvironmentConfig(config: EnvValidationConfig): ValidationResult {
|
|
211
|
+
const allErrors: string[] = [];
|
|
212
|
+
const allWarnings: string[] = [];
|
|
213
|
+
|
|
214
|
+
if (config.atlassianUrl !== undefined) {
|
|
215
|
+
const result = validateAtlassianUrl(config.atlassianUrl);
|
|
216
|
+
allErrors.push(...result.errors.map((e) => `ATLASSIAN_URL: ${e}`));
|
|
217
|
+
allWarnings.push(...result.warnings.map((w) => `ATLASSIAN_URL: ${w}`));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (config.atlassianEmail !== undefined) {
|
|
221
|
+
const result = validateEmail(config.atlassianEmail);
|
|
222
|
+
allErrors.push(...result.errors.map((e) => `ATLASSIAN_EMAIL: ${e}`));
|
|
223
|
+
allWarnings.push(...result.warnings.map((w) => `ATLASSIAN_EMAIL: ${w}`));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (config.atlassianApiToken !== undefined) {
|
|
227
|
+
const result = validateAtlassianToken(config.atlassianApiToken);
|
|
228
|
+
allErrors.push(...result.errors.map((e) => `ATLASSIAN_API_TOKEN: ${e}`));
|
|
229
|
+
allWarnings.push(...result.warnings.map((w) => `ATLASSIAN_API_TOKEN: ${w}`));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (config.githubOrg !== undefined) {
|
|
233
|
+
if (!config.githubOrg || config.githubOrg.trim() === '') {
|
|
234
|
+
allErrors.push('GITHUB_ORG: GitHub organization is empty');
|
|
235
|
+
} else if (config.githubOrg.includes('your-org')) {
|
|
236
|
+
allErrors.push('GITHUB_ORG: GitHub organization is a placeholder value');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (config.githubToken !== undefined) {
|
|
241
|
+
const result = validateGitHubToken(config.githubToken);
|
|
242
|
+
allErrors.push(...result.errors.map((e) => `GITHUB_TOKEN: ${e}`));
|
|
243
|
+
allWarnings.push(...result.warnings.map((w) => `GITHUB_TOKEN: ${w}`));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (config.repositoryUrl !== undefined) {
|
|
247
|
+
const result = validateGitHubRepositoryUrl(config.repositoryUrl);
|
|
248
|
+
allErrors.push(...result.errors.map((e) => `repository: ${e}`));
|
|
249
|
+
allWarnings.push(...result.warnings.map((w) => `repository: ${w}`));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
isValid: allErrors.length === 0,
|
|
254
|
+
errors: allErrors,
|
|
255
|
+
warnings: allWarnings,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* ファイルパーミッションの検証(Unix系のみ)
|
|
261
|
+
*/
|
|
262
|
+
export function validateFilePermissions(filePath: string, expectedMode: number = 0o600): ValidationResult {
|
|
263
|
+
const errors: string[] = [];
|
|
264
|
+
const warnings: string[] = [];
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const stats = statSync(filePath);
|
|
268
|
+
const actualMode = stats.mode & 0o777;
|
|
269
|
+
|
|
270
|
+
if (actualMode !== expectedMode) {
|
|
271
|
+
const actualOctal = actualMode.toString(8);
|
|
272
|
+
const expectedOctal = expectedMode.toString(8);
|
|
273
|
+
warnings.push(
|
|
274
|
+
`File permissions are ${actualOctal} but should be ${expectedOctal} for security. Run: chmod ${expectedOctal} ${filePath}`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
errors.push(`Failed to check file permissions: ${error instanceof Error ? error.message : error}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
isValid: errors.length === 0,
|
|
283
|
+
errors,
|
|
284
|
+
warnings,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
@@ -29,7 +29,7 @@ This command implements the specifications defined in `tasks.md` following TDD (
|
|
|
29
29
|
- `ATLASSIAN_EMAIL`: JIRA user email
|
|
30
30
|
- `ATLASSIAN_API_TOKEN`: JIRA API token
|
|
31
31
|
- `GITHUB_TOKEN`: GitHub API token
|
|
32
|
-
-
|
|
32
|
+
- ~~`GITHUB_REPO`: GitHub repository (owner/repo format)~~ **Deprecated (v0.5.0+)**: Repository info is now automatically loaded from `.kiro/project.json`
|
|
33
33
|
|
|
34
34
|
## Execution Flow
|
|
35
35
|
|
|
@@ -25,7 +25,7 @@ This command implements the specifications defined in `tasks.md` following TDD (
|
|
|
25
25
|
- `ATLASSIAN_EMAIL`: JIRA user email
|
|
26
26
|
- `ATLASSIAN_API_TOKEN`: JIRA API token
|
|
27
27
|
- `GITHUB_TOKEN`: GitHub API token
|
|
28
|
-
-
|
|
28
|
+
- ~~`GITHUB_REPO`: GitHub repository (owner/repo format)~~ **Deprecated (v0.5.0+)**: Repository info is now automatically loaded from `.kiro/project.json`
|
|
29
29
|
|
|
30
30
|
## Execution Flow
|
|
31
31
|
|
|
@@ -25,7 +25,7 @@ This command implements the specifications defined in `tasks.md` following TDD (
|
|
|
25
25
|
- `ATLASSIAN_EMAIL`: JIRA user email
|
|
26
26
|
- `ATLASSIAN_API_TOKEN`: JIRA API token
|
|
27
27
|
- `GITHUB_TOKEN`: GitHub API token
|
|
28
|
-
-
|
|
28
|
+
- ~~`GITHUB_REPO`: GitHub repository (owner/repo format)~~ **Deprecated (v0.5.0+)**: Repository info is now automatically loaded from `.kiro/project.json`
|
|
29
29
|
|
|
30
30
|
## Execution Flow
|
|
31
31
|
|