@sk8metal/michi-cli 0.8.2 → 0.8.4
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 +34 -0
- package/dist/scripts/confluence-sync.js +2 -2
- package/dist/scripts/confluence-sync.js.map +1 -1
- package/dist/scripts/jira-sync.js +2 -2
- package/dist/scripts/jira-sync.js.map +1 -1
- package/dist/scripts/multi-project-estimate.js +2 -2
- package/dist/scripts/multi-project-estimate.js.map +1 -1
- package/dist/scripts/pr-automation.js +2 -2
- package/dist/scripts/pr-automation.js.map +1 -1
- package/dist/scripts/pre-flight-check.js +2 -2
- package/dist/scripts/pre-flight-check.js.map +1 -1
- package/dist/scripts/resource-dashboard.js +2 -2
- package/dist/scripts/resource-dashboard.js.map +1 -1
- package/dist/scripts/spec-impl-workflow.js +2 -2
- package/dist/scripts/spec-impl-workflow.js.map +1 -1
- package/dist/scripts/template/multi-repo-renderer.d.ts +1 -1
- package/dist/scripts/template/multi-repo-renderer.d.ts.map +1 -1
- package/dist/scripts/template/multi-repo-renderer.js +8 -3
- package/dist/scripts/template/multi-repo-renderer.js.map +1 -1
- package/dist/scripts/test-workflow-stages.js +2 -2
- package/dist/scripts/test-workflow-stages.js.map +1 -1
- package/dist/scripts/utils/config-loader.d.ts.map +1 -1
- package/dist/scripts/utils/config-loader.js +3 -2
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/env-loader.d.ts +11 -0
- package/dist/scripts/utils/env-loader.d.ts.map +1 -0
- package/dist/scripts/utils/env-loader.js +23 -0
- package/dist/scripts/utils/env-loader.js.map +1 -0
- package/dist/scripts/workflow-orchestrator.js +2 -2
- package/dist/scripts/workflow-orchestrator.js.map +1 -1
- package/dist/src/cli.js +3 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/multi-repo-ci-status.js +1 -1
- package/dist/src/commands/multi-repo-ci-status.js.map +1 -1
- package/dist/src/commands/multi-repo-confluence-sync.js +1 -1
- package/dist/src/commands/multi-repo-confluence-sync.js.map +1 -1
- package/dist/src/commands/multi-repo-init.js +1 -1
- package/dist/src/commands/multi-repo-init.js.map +1 -1
- package/dist/src/commands/multi-repo-test.js +1 -1
- package/dist/src/commands/multi-repo-test.js.map +1 -1
- package/package.json +1 -1
- package/scripts/confluence-sync.ts +2 -2
- package/scripts/jira-sync.ts +2 -2
- package/scripts/multi-project-estimate.ts +2 -2
- package/scripts/pr-automation.ts +2 -2
- package/scripts/pre-flight-check.ts +2 -2
- package/scripts/resource-dashboard.ts +2 -2
- package/scripts/spec-impl-workflow.ts +2 -2
- package/scripts/template/__tests__/multi-repo-renderer.test.ts +15 -10
- package/scripts/template/multi-repo-renderer.ts +9 -3
- package/scripts/test-workflow-stages.ts +2 -2
- package/scripts/utils/__tests__/env-loader.test.ts +145 -0
- package/scripts/utils/config-loader.ts +3 -2
- package/scripts/utils/env-loader.ts +25 -0
- package/scripts/workflow-orchestrator.ts +2 -2
package/scripts/pr-automation.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import { existsSync, readFileSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import axios from 'axios';
|
|
9
|
-
import {
|
|
9
|
+
import { loadEnv } from './utils/env-loader.js';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
loadEnv();
|
|
12
12
|
|
|
13
13
|
interface PreFlightResult {
|
|
14
14
|
valid: boolean;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Octokit } from '@octokit/rest';
|
|
7
|
-
import {
|
|
7
|
+
import { loadEnv } from './utils/env-loader.js';
|
|
8
8
|
import { ConfluenceClient, getConfluenceConfig } from './confluence-sync.js';
|
|
9
9
|
import { getConfig } from './utils/config-loader.js';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
loadEnv();
|
|
12
12
|
|
|
13
13
|
interface ProjectResource {
|
|
14
14
|
projectName: string;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - 終了時: PR作成、Epic + 最初の Story を「レビュー待ち」に移動、PRリンクをコメント
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { loadEnv } from './utils/env-loader.js';
|
|
11
11
|
import { JIRAClient } from './jira-sync.js';
|
|
12
12
|
import { getConfig } from './utils/config-loader.js';
|
|
13
13
|
import {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
JiraInfo,
|
|
17
17
|
} from './utils/spec-loader.js';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
loadEnv();
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* spec-impl 統合ワークフローのオプション
|
|
@@ -13,6 +13,13 @@ import {
|
|
|
13
13
|
type MultiRepoTemplateContext,
|
|
14
14
|
} from '../multi-repo-renderer.js';
|
|
15
15
|
import * as fs from 'fs';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { dirname, resolve } from 'path';
|
|
18
|
+
|
|
19
|
+
// Calculate MICHI_PACKAGE_ROOT for tests
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
const MICHI_PACKAGE_ROOT = resolve(__dirname, '..', '..', '..');
|
|
16
23
|
|
|
17
24
|
vi.mock('fs');
|
|
18
25
|
|
|
@@ -63,10 +70,11 @@ describe('loadMultiRepoTemplate', () => {
|
|
|
63
70
|
const mockContent = '# {{PROJECT_NAME}} - Requirements';
|
|
64
71
|
vi.spyOn(fs, 'readFileSync').mockReturnValue(mockContent);
|
|
65
72
|
|
|
66
|
-
const content = loadMultiRepoTemplate('overview/requirements'
|
|
73
|
+
const content = loadMultiRepoTemplate('overview/requirements');
|
|
67
74
|
|
|
75
|
+
const expectedPath = resolve(MICHI_PACKAGE_ROOT, 'templates', 'multi-repo', 'overview', 'requirements.md');
|
|
68
76
|
expect(fs.readFileSync).toHaveBeenCalledWith(
|
|
69
|
-
|
|
77
|
+
expectedPath,
|
|
70
78
|
'utf-8'
|
|
71
79
|
);
|
|
72
80
|
expect(content).toBe(mockContent);
|
|
@@ -77,7 +85,7 @@ describe('loadMultiRepoTemplate', () => {
|
|
|
77
85
|
throw new Error('ENOENT: no such file or directory');
|
|
78
86
|
});
|
|
79
87
|
|
|
80
|
-
expect(() => loadMultiRepoTemplate('invalid/template'
|
|
88
|
+
expect(() => loadMultiRepoTemplate('invalid/template')).toThrow(
|
|
81
89
|
'Multi-Repo template not found: invalid/template.md'
|
|
82
90
|
);
|
|
83
91
|
});
|
|
@@ -151,8 +159,7 @@ describe('loadAndRenderMultiRepoTemplate', () => {
|
|
|
151
159
|
|
|
152
160
|
const rendered = loadAndRenderMultiRepoTemplate(
|
|
153
161
|
'overview/requirements',
|
|
154
|
-
context
|
|
155
|
-
'/test/root'
|
|
162
|
+
context
|
|
156
163
|
);
|
|
157
164
|
|
|
158
165
|
expect(rendered).toBe('# test-project Requirements\n\n**JIRA**: TEST');
|
|
@@ -184,8 +191,7 @@ describe('renderMultiRepoTemplates', () => {
|
|
|
184
191
|
|
|
185
192
|
const rendered = renderMultiRepoTemplates(
|
|
186
193
|
['overview/requirements', 'overview/architecture'],
|
|
187
|
-
context
|
|
188
|
-
'/test/root'
|
|
194
|
+
context
|
|
189
195
|
);
|
|
190
196
|
|
|
191
197
|
expect(rendered['overview/requirements']).toBe('# test-project Requirements');
|
|
@@ -200,7 +206,7 @@ describe('renderMultiRepoTemplates', () => {
|
|
|
200
206
|
CREATED_AT: '2025-12-14T10:00:00Z',
|
|
201
207
|
};
|
|
202
208
|
|
|
203
|
-
const rendered = renderMultiRepoTemplates([], context
|
|
209
|
+
const rendered = renderMultiRepoTemplates([], context);
|
|
204
210
|
|
|
205
211
|
expect(rendered).toEqual({});
|
|
206
212
|
});
|
|
@@ -248,8 +254,7 @@ describe('統合テスト', () => {
|
|
|
248
254
|
|
|
249
255
|
const rendered = loadAndRenderMultiRepoTemplate(
|
|
250
256
|
'overview/requirements',
|
|
251
|
-
context
|
|
252
|
-
'/test/root'
|
|
257
|
+
context
|
|
253
258
|
);
|
|
254
259
|
|
|
255
260
|
expect(rendered).toContain('# my-awesome-project - 要件定義書');
|
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { readFileSync } from 'fs';
|
|
8
|
-
import { resolve, relative, isAbsolute } from 'path';
|
|
8
|
+
import { resolve, relative, isAbsolute, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
9
10
|
import { renderTemplate, type TemplateContext } from './renderer.js';
|
|
10
11
|
|
|
12
|
+
// Resolve Michi package root directory
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
const MICHI_PACKAGE_ROOT = resolve(__dirname, '..', '..');
|
|
16
|
+
|
|
11
17
|
export interface MultiRepoTemplateContext {
|
|
12
18
|
PROJECT_NAME: string;
|
|
13
19
|
JIRA_KEY: string;
|
|
@@ -51,7 +57,7 @@ export const createMultiRepoTemplateContext = (
|
|
|
51
57
|
*/
|
|
52
58
|
export const loadMultiRepoTemplate = (
|
|
53
59
|
templateName: string,
|
|
54
|
-
|
|
60
|
+
_projectRoot: string = process.cwd()
|
|
55
61
|
): string => {
|
|
56
62
|
// Security Layer 1: Validate template name
|
|
57
63
|
// Reject path traversal characters (../, ..\, absolute paths)
|
|
@@ -63,7 +69,7 @@ export const loadMultiRepoTemplate = (
|
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
// Security Layer 2: Resolve absolute paths
|
|
66
|
-
const templateDir = resolve(
|
|
72
|
+
const templateDir = resolve(MICHI_PACKAGE_ROOT, 'templates', 'multi-repo');
|
|
67
73
|
const templatePath = resolve(templateDir, `${templateName}.md`);
|
|
68
74
|
|
|
69
75
|
// Security Layer 3: Verify path containment
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* testとreleaseステージのみを実行
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { loadEnv } from './utils/env-loader.js';
|
|
7
7
|
import { WorkflowOrchestrator, WorkflowConfig } from './workflow-orchestrator.js';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
loadEnv();
|
|
10
10
|
|
|
11
11
|
async function main() {
|
|
12
12
|
const args = process.argv.slice(2);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* env-loader.ts のユニットテスト
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { writeFileSync, existsSync, mkdirSync, rmSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { config } from 'dotenv';
|
|
9
|
+
|
|
10
|
+
describe('env-loader', () => {
|
|
11
|
+
const testDir = join(process.cwd(), 'test-temp-env-loader');
|
|
12
|
+
const testGlobalDir = join(testDir, '.michi');
|
|
13
|
+
const testGlobalEnvPath = join(testGlobalDir, '.env');
|
|
14
|
+
const testLocalEnvPath = join(testDir, '.env');
|
|
15
|
+
|
|
16
|
+
// 元の環境変数を保存
|
|
17
|
+
const originalEnv = { ...process.env };
|
|
18
|
+
const originalHome = process.env.HOME;
|
|
19
|
+
const originalCwd = process.cwd();
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// テスト用ディレクトリ作成
|
|
23
|
+
if (!existsSync(testDir)) {
|
|
24
|
+
mkdirSync(testDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
if (!existsSync(testGlobalDir)) {
|
|
27
|
+
mkdirSync(testGlobalDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// カレントディレクトリをテストディレクトリに変更
|
|
31
|
+
process.chdir(testDir);
|
|
32
|
+
|
|
33
|
+
// 環境変数をクリア
|
|
34
|
+
for (const key in process.env) {
|
|
35
|
+
if (key.startsWith('TEST_')) {
|
|
36
|
+
delete process.env[key];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
// カレントディレクトリを戻す
|
|
43
|
+
process.chdir(originalCwd);
|
|
44
|
+
|
|
45
|
+
// クリーンアップ
|
|
46
|
+
if (existsSync(testDir)) {
|
|
47
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 環境変数を復元
|
|
51
|
+
process.env = { ...originalEnv };
|
|
52
|
+
if (originalHome) {
|
|
53
|
+
process.env.HOME = originalHome;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should load global .env if exists', () => {
|
|
58
|
+
// グローバル .env を作成
|
|
59
|
+
const globalContent = `TEST_GLOBAL_VAR=global_value
|
|
60
|
+
TEST_SHARED_VAR=global_shared`;
|
|
61
|
+
writeFileSync(testGlobalEnvPath, globalContent, 'utf-8');
|
|
62
|
+
|
|
63
|
+
// HOMEを一時的に変更
|
|
64
|
+
process.env.HOME = testDir;
|
|
65
|
+
|
|
66
|
+
// dotenvで読み込みをシミュレート(env-loader.tsと同じロジック)
|
|
67
|
+
if (existsSync(testGlobalEnvPath)) {
|
|
68
|
+
config({ path: testGlobalEnvPath });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
expect(process.env.TEST_GLOBAL_VAR).toBe('global_value');
|
|
72
|
+
expect(process.env.TEST_SHARED_VAR).toBe('global_shared');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should load local .env if exists', () => {
|
|
76
|
+
// ローカル .env を作成
|
|
77
|
+
const localContent = `TEST_LOCAL_VAR=local_value
|
|
78
|
+
TEST_SHARED_VAR=local_shared`;
|
|
79
|
+
writeFileSync(testLocalEnvPath, localContent, 'utf-8');
|
|
80
|
+
|
|
81
|
+
// dotenvでローカル.envを読み込み
|
|
82
|
+
config();
|
|
83
|
+
|
|
84
|
+
expect(process.env.TEST_LOCAL_VAR).toBe('local_value');
|
|
85
|
+
expect(process.env.TEST_SHARED_VAR).toBe('local_shared');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should override global with local', () => {
|
|
89
|
+
// グローバル .env を作成
|
|
90
|
+
const globalContent = `TEST_GLOBAL_VAR=global_value
|
|
91
|
+
TEST_SHARED_VAR=global_shared`;
|
|
92
|
+
writeFileSync(testGlobalEnvPath, globalContent, 'utf-8');
|
|
93
|
+
|
|
94
|
+
// ローカル .env を作成
|
|
95
|
+
const localContent = `TEST_LOCAL_VAR=local_value
|
|
96
|
+
TEST_SHARED_VAR=local_shared`;
|
|
97
|
+
writeFileSync(testLocalEnvPath, localContent, 'utf-8');
|
|
98
|
+
|
|
99
|
+
// HOMEを一時的に変更
|
|
100
|
+
process.env.HOME = testDir;
|
|
101
|
+
|
|
102
|
+
// env-loader.tsと同じロジックで読み込み
|
|
103
|
+
if (existsSync(testGlobalEnvPath)) {
|
|
104
|
+
config({ path: testGlobalEnvPath });
|
|
105
|
+
}
|
|
106
|
+
config({ override: true }); // ローカル .env(グローバルを上書き)
|
|
107
|
+
|
|
108
|
+
// ローカルがグローバルを上書きする
|
|
109
|
+
expect(process.env.TEST_GLOBAL_VAR).toBe('global_value');
|
|
110
|
+
expect(process.env.TEST_LOCAL_VAR).toBe('local_value');
|
|
111
|
+
expect(process.env.TEST_SHARED_VAR).toBe('local_shared'); // ローカルで上書き
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle missing files gracefully', () => {
|
|
115
|
+
// 両方のファイルを作成しない
|
|
116
|
+
process.env.HOME = join(testDir, 'nonexistent');
|
|
117
|
+
|
|
118
|
+
// エラーを投げないことを確認
|
|
119
|
+
expect(() => {
|
|
120
|
+
if (existsSync(join(process.env.HOME!, '.michi', '.env'))) {
|
|
121
|
+
config({ path: join(process.env.HOME!, '.michi', '.env') });
|
|
122
|
+
}
|
|
123
|
+
config();
|
|
124
|
+
}).not.toThrow();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should not fail if global directory does not exist', () => {
|
|
128
|
+
// ローカル .env のみ作成
|
|
129
|
+
const localContent = `TEST_LOCAL_VAR=local_value`;
|
|
130
|
+
writeFileSync(testLocalEnvPath, localContent, 'utf-8');
|
|
131
|
+
|
|
132
|
+
// .michiディレクトリが存在しないHOMEを設定
|
|
133
|
+
process.env.HOME = join(testDir, 'no-michi-dir');
|
|
134
|
+
|
|
135
|
+
expect(() => {
|
|
136
|
+
const globalPath = join(process.env.HOME!, '.michi', '.env');
|
|
137
|
+
if (existsSync(globalPath)) {
|
|
138
|
+
config({ path: globalPath });
|
|
139
|
+
}
|
|
140
|
+
config();
|
|
141
|
+
}).not.toThrow();
|
|
142
|
+
|
|
143
|
+
expect(process.env.TEST_LOCAL_VAR).toBe('local_value');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -7,7 +7,8 @@ import { readFileSync, writeFileSync, existsSync, statSync, renameSync, unlinkSy
|
|
|
7
7
|
import { resolve, relative, isAbsolute, dirname } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
|
-
import {
|
|
10
|
+
import { parse as dotenvParse } from 'dotenv';
|
|
11
|
+
import { loadEnv } from './env-loader.js';
|
|
11
12
|
import {
|
|
12
13
|
AppConfigSchema,
|
|
13
14
|
MultiRepoProjectSchema,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
} from '../config/config-schema.js';
|
|
19
20
|
|
|
20
21
|
// 環境変数読み込み
|
|
21
|
-
|
|
22
|
+
loadEnv();
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* グローバル設定ファイルのパス定数
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 環境変数読み込みユーティリティ
|
|
3
|
+
* グローバル設定(~/.michi/.env)とローカル設定(.env)を読み込む
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { config } from 'dotenv';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 環境変数を読み込む
|
|
13
|
+
* グローバル設定(~/.michi/.env)→ローカル設定(.env)の順で読み込み
|
|
14
|
+
* ローカル設定がグローバル設定を上書きする
|
|
15
|
+
*/
|
|
16
|
+
export function loadEnv(): void {
|
|
17
|
+
// 1. グローバル設定 ~/.michi/.env を読み込む
|
|
18
|
+
const globalEnvPath = join(homedir(), '.michi', '.env');
|
|
19
|
+
if (existsSync(globalEnvPath)) {
|
|
20
|
+
config({ path: globalEnvPath });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. ローカル設定 .env を読み込む(グローバル設定を上書き)
|
|
24
|
+
config({ override: true });
|
|
25
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* AI開発フロー全体を統合実行
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { loadEnv } from './utils/env-loader.js';
|
|
7
7
|
import { loadProjectMeta } from './utils/project-meta.js';
|
|
8
8
|
import { syncToConfluence, getConfluenceConfig } from './confluence-sync.js';
|
|
9
9
|
import { syncTasksToJIRA } from './jira-sync.js';
|
|
@@ -12,7 +12,7 @@ import { executeTests, generateTestReport } from './utils/test-runner.js';
|
|
|
12
12
|
import { createReleaseNotes } from './utils/release-notes-generator.js';
|
|
13
13
|
import { pollForApproval, waitForManualApproval } from './utils/confluence-approval.js';
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
loadEnv();
|
|
16
16
|
|
|
17
17
|
export interface WorkflowConfig {
|
|
18
18
|
feature: string;
|