@k2works/claude-code-booster 1.9.0 → 1.10.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/LICENSE +21 -21
- package/README.md +42 -42
- package/bin/claude-code-booster +79 -79
- package/lib/assets/.claude/README.md +162 -162
- package/lib/assets/.claude/SKILLS_TEMPLATE.md +100 -100
- package/lib/assets/.claude/scripts/generate-inception-deck.mjs +911 -1024
- package/lib/assets/.claude/settings.json +11 -11
- package/lib/assets/.claude/skills/ai-agent-guidelines/SKILL.md +119 -119
- package/lib/assets/.claude/skills/analyzing-architecture/SKILL.md +87 -87
- package/lib/assets/.claude/skills/analyzing-business/SKILL.md +117 -117
- package/lib/assets/.claude/skills/analyzing-data-model/SKILL.md +80 -80
- package/lib/assets/.claude/skills/analyzing-domain-model/SKILL.md +88 -88
- package/lib/assets/.claude/skills/analyzing-inception-deck/SKILL.md +137 -137
- package/lib/assets/.claude/skills/analyzing-non-functional/SKILL.md +91 -91
- package/lib/assets/.claude/skills/analyzing-operation/SKILL.md +91 -91
- package/lib/assets/.claude/skills/analyzing-requirements/SKILL.md +87 -87
- package/lib/assets/.claude/skills/analyzing-tech-stack/SKILL.md +102 -102
- package/lib/assets/.claude/skills/analyzing-test-strategy/SKILL.md +87 -87
- package/lib/assets/.claude/skills/analyzing-ui-design/SKILL.md +86 -86
- package/lib/assets/.claude/skills/analyzing-usecases/SKILL.md +87 -87
- package/lib/assets/.claude/skills/creating-adr/SKILL.md +115 -115
- package/lib/assets/.claude/skills/developing-backend/SKILL.md +106 -106
- package/lib/assets/.claude/skills/developing-frontend/SKILL.md +96 -96
- package/lib/assets/.claude/skills/developing-release/SKILL.md +154 -154
- package/lib/assets/.claude/skills/generating-slides/SKILL.md +136 -106
- package/lib/assets/.claude/skills/git-commit/SKILL.md +106 -106
- package/lib/assets/.claude/skills/killing-processes/SKILL.md +98 -98
- package/lib/assets/.claude/skills/managing-docs/SKILL.md +200 -200
- package/lib/assets/.claude/skills/managing-operations/DEPLOY.md +77 -77
- package/lib/assets/.claude/skills/managing-operations/SETUP_CSHARP.md +80 -80
- package/lib/assets/.claude/skills/managing-operations/SETUP_FRONTEND.md +84 -84
- package/lib/assets/.claude/skills/managing-operations/SETUP_JAVA.md +75 -75
- package/lib/assets/.claude/skills/managing-operations/SKILL.md +156 -156
- package/lib/assets/.claude/skills/orchestrating-analysis/SKILL.md +134 -134
- package/lib/assets/.claude/skills/orchestrating-development/SKILL.md +243 -243
- package/lib/assets/.claude/skills/orchestrating-project/SKILL.md +193 -193
- package/lib/assets/.claude/skills/planning-releases/SKILL.md +222 -222
- package/lib/assets/.claude/skills/syncing-github-project/SKILL.md +181 -69
- package/lib/assets/.claude/skills/tracking-progress/SKILL.md +164 -164
- package/lib/assets/.devcontainer/devcontainer.json +34 -34
- package/lib/assets/.env.example +17 -17
- package/lib/assets/.gitattributes +4 -4
- package/lib/assets/.github/workflows/docker-publish.yml +77 -77
- package/lib/assets/.github/workflows/mkdocs.yml +39 -39
- package/lib/assets/AGENTS.md +94 -94
- package/lib/assets/CLAUDE.md +162 -162
- package/lib/assets/README.md +269 -269
- package/lib/assets/docker-compose.yml +33 -33
- package/lib/assets/docs/assets/css/extra.css +29 -29
- package/lib/assets/docs/assets/js/extra.js +44 -44
- package/lib/assets/docs/index.md +14 -14
- package/lib/assets/docs/reference/CodexCLIMCP/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/351/226/213/347/231/272/343/203/225/343/203/255/343/203/274.md +532 -532
- package/lib/assets/docs/reference/CodexCLIMCP/343/202/265/343/203/274/343/203/220/343/203/274/350/250/255/345/256/232/346/211/213/351/240/206.md +341 -341
- package/lib/assets/docs/reference/Java/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/347/222/260/345/242/203/346/247/213/347/257/211/343/202/254/343/202/244/343/203/211.md +578 -578
- package/lib/assets/docs/reference/TypeScript/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/347/222/260/345/242/203/346/247/213/347/257/211/343/202/254/343/202/244/343/203/211.md +465 -465
- package/lib/assets/docs/reference/UI/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +448 -448
- package/lib/assets/docs/reference//343/202/210/343/201/204/343/202/275/343/203/225/343/203/210/343/202/246/343/202/247/343/202/242/343/201/250/343/201/257.md +242 -242
- package/lib/assets/docs/reference//343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +2216 -2216
- package/lib/assets/docs/reference//343/202/244/343/203/263/343/203/225/343/203/251/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +1878 -1878
- package/lib/assets/docs/reference//343/202/250/343/202/257/343/202/271/343/203/210/343/203/252/343/203/274/343/203/240/343/203/227/343/203/255/343/202/260/343/203/251/343/203/237/343/203/263/343/202/260.md +554 -554
- package/lib/assets/docs/reference//343/202/263/343/203/274/343/203/207/343/202/243/343/203/263/343/202/260/343/201/250/343/203/206/343/202/271/343/203/210/343/202/254/343/202/244/343/203/211.md +705 -705
- package/lib/assets/docs/reference//343/203/206/343/202/271/343/203/210/346/210/246/347/225/245/343/202/254/343/202/244/343/203/211.md +1313 -1313
- package/lib/assets/docs/reference//343/203/207/343/203/274/343/202/277/343/203/242/343/203/207/343/203/253/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +311 -311
- package/lib/assets/docs/reference//343/203/211/343/203/241/343/202/244/343/203/263/343/203/242/343/203/207/343/203/253/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +599 -599
- package/lib/assets/docs/reference//343/203/223/343/202/270/343/203/215/343/202/271/343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243/345/210/206/346/236/220/343/202/254/343/202/244/343/203/211.md +528 -528
- package/lib/assets/docs/reference//343/203/246/343/203/274/343/202/271/343/202/261/343/203/274/343/202/271/344/275/234/346/210/220/343/202/254/343/202/244/343/203/211.md +682 -682
- package/lib/assets/docs/reference//343/203/252/343/203/252/343/203/274/343/202/271/343/202/254/343/202/244/343/203/211.md +442 -442
- package/lib/assets/docs/reference//343/203/252/343/203/252/343/203/274/343/202/271/343/203/273/343/202/244/343/203/206/343/203/254/343/203/274/343/202/267/343/203/247/343/203/263/350/250/210/347/224/273/343/202/254/343/202/244/343/203/211.md +558 -558
- package/lib/assets/docs/reference//347/222/260/345/242/203/345/244/211/346/225/260/347/256/241/347/220/206/343/202/254/343/202/244/343/203/211.md +663 -663
- package/lib/assets/docs/reference//350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +1248 -1248
- package/lib/assets/docs/reference//351/201/213/347/224/250/350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +392 -392
- package/lib/assets/docs/reference//351/226/213/347/231/272/343/202/254/343/202/244/343/203/211.md +235 -235
- package/lib/assets/docs/reference//351/235/236/346/251/237/350/203/275/350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +1236 -1236
- package/lib/assets/docs/template/ADR.md +30 -30
- package/lib/assets/docs/template/README.md +50 -50
- package/lib/assets/docs/template//343/201/276/343/201/232/343/201/223/343/202/214/343/202/222/350/252/255/343/202/202/343/201/206/343/203/252/343/202/271/343/203/210.md +12 -12
- package/lib/assets/docs/template//343/202/244/343/203/206/343/203/254/343/203/274/343/202/267/343/203/247/343/203/263/345/256/214/344/272/206/345/240/261/345/221/212/346/233/270.md +58 -58
- package/lib/assets/docs/template//343/202/244/343/203/263/343/202/273/343/203/227/343/202/267/343/203/247/343/203/263/343/203/207/343/203/203/343/202/255.md +13 -13
- package/lib/assets/docs/template//343/203/223/343/202/270/343/203/215/343/202/271/343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243.md +379 -379
- package/lib/assets/docs/template//345/256/214/345/205/250/345/275/242/345/274/217/343/201/256/343/203/246/343/203/274/343/202/271/343/202/261/343/203/274/343/202/271.md +68 -68
- package/lib/assets/docs/template//350/246/201/344/273/266/345/256/232/347/276/251.md +669 -669
- package/lib/assets/docs/template//350/250/255/350/250/210.md +163 -163
- package/lib/assets/gulpfile.js +23 -23
- package/lib/assets/mkdocs.yml +65 -65
- package/lib/assets/ops/docker/mkdoc/Dockerfile +19 -19
- package/lib/assets/ops/scripts/journal.js +180 -180
- package/lib/assets/ops/scripts/mkdocs.js +82 -82
- package/lib/assets/ops/scripts/release.js +431 -431
- package/lib/assets/ops/scripts/ssh.js +190 -190
- package/lib/assets/ops/scripts/vault.js +299 -299
- package/lib/assets/package-lock.json +1653 -1653
- package/lib/assets/package.json +40 -40
- package/lib/gulpfile.js +37 -37
- package/package.json +41 -41
|
@@ -1,299 +1,299 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
import crypto from 'crypto';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import readline from 'readline';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Vault - .env ファイルの暗号化・復号化ユーティリティ
|
|
10
|
-
*
|
|
11
|
-
* 暗号化アルゴリズム: AES-256-GCM
|
|
12
|
-
* 鍵導出: PBKDF2 (SHA-512, 100,000 iterations)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const ALGORITHM = 'aes-256-gcm';
|
|
16
|
-
const PBKDF2_ITERATIONS = 100000;
|
|
17
|
-
const SALT_LENGTH = 32;
|
|
18
|
-
const IV_LENGTH = 16;
|
|
19
|
-
const AUTH_TAG_LENGTH = 16;
|
|
20
|
-
const KEY_LENGTH = 32;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* パスワードから暗号化キーを導出
|
|
24
|
-
* @param {string} password - パスワード
|
|
25
|
-
* @param {Buffer} salt - ソルト
|
|
26
|
-
* @returns {Buffer} - 導出されたキー
|
|
27
|
-
*/
|
|
28
|
-
function deriveKey(password, salt) {
|
|
29
|
-
return crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* データを暗号化
|
|
34
|
-
* @param {string} plaintext - 平文
|
|
35
|
-
* @param {string} password - パスワード
|
|
36
|
-
* @returns {Buffer} - 暗号化されたデータ (salt + iv + authTag + ciphertext)
|
|
37
|
-
*/
|
|
38
|
-
function encrypt(plaintext, password) {
|
|
39
|
-
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
40
|
-
const key = deriveKey(password, salt);
|
|
41
|
-
const iv = crypto.randomBytes(IV_LENGTH);
|
|
42
|
-
|
|
43
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
44
|
-
const encrypted = Buffer.concat([
|
|
45
|
-
cipher.update(plaintext, 'utf8'),
|
|
46
|
-
cipher.final()
|
|
47
|
-
]);
|
|
48
|
-
const authTag = cipher.getAuthTag();
|
|
49
|
-
|
|
50
|
-
// フォーマット: salt (32) + iv (16) + authTag (16) + ciphertext
|
|
51
|
-
return Buffer.concat([salt, iv, authTag, encrypted]);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* データを復号化
|
|
56
|
-
* @param {Buffer} encryptedData - 暗号化されたデータ
|
|
57
|
-
* @param {string} password - パスワード
|
|
58
|
-
* @returns {string} - 復号化された平文
|
|
59
|
-
*/
|
|
60
|
-
function decrypt(encryptedData, password) {
|
|
61
|
-
const salt = encryptedData.subarray(0, SALT_LENGTH);
|
|
62
|
-
const iv = encryptedData.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
63
|
-
const authTag = encryptedData.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
64
|
-
const ciphertext = encryptedData.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
65
|
-
|
|
66
|
-
const key = deriveKey(password, salt);
|
|
67
|
-
|
|
68
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
69
|
-
decipher.setAuthTag(authTag);
|
|
70
|
-
|
|
71
|
-
const decrypted = Buffer.concat([
|
|
72
|
-
decipher.update(ciphertext),
|
|
73
|
-
decipher.final()
|
|
74
|
-
]);
|
|
75
|
-
|
|
76
|
-
return decrypted.toString('utf8');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* パスワードをプロンプトで取得(シンプル版)
|
|
81
|
-
* @param {string} prompt - プロンプトメッセージ
|
|
82
|
-
* @returns {Promise<string>} - 入力されたパスワード
|
|
83
|
-
*/
|
|
84
|
-
function promptPassword(prompt) {
|
|
85
|
-
return new Promise((resolve) => {
|
|
86
|
-
const rl = readline.createInterface({
|
|
87
|
-
input: process.stdin,
|
|
88
|
-
output: process.stdout
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
rl.question(prompt, (answer) => {
|
|
92
|
-
rl.close();
|
|
93
|
-
resolve(answer);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 環境変数からパスワードを取得、なければプロンプト
|
|
100
|
-
* @returns {Promise<string>} - パスワード
|
|
101
|
-
*/
|
|
102
|
-
async function getPassword() {
|
|
103
|
-
const envPassword = process.env.VAULT_PASSWORD;
|
|
104
|
-
if (envPassword) {
|
|
105
|
-
return envPassword;
|
|
106
|
-
}
|
|
107
|
-
return promptPassword('Vault password: ');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 環境変数からパスワードを取得、なければプロンプト(確認付き)
|
|
112
|
-
* @returns {Promise<string>} - パスワード
|
|
113
|
-
*/
|
|
114
|
-
async function getPasswordWithConfirm() {
|
|
115
|
-
const envPassword = process.env.VAULT_PASSWORD;
|
|
116
|
-
if (envPassword) {
|
|
117
|
-
return envPassword;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const password = await promptPassword('New vault password: ');
|
|
121
|
-
const confirm = await promptPassword('Confirm vault password: ');
|
|
122
|
-
|
|
123
|
-
if (password !== confirm) {
|
|
124
|
-
throw new Error('Passwords do not match');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return password;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 確認プロンプト
|
|
132
|
-
* @param {string} message - メッセージ
|
|
133
|
-
* @returns {Promise<boolean>} - true: yes, false: no
|
|
134
|
-
*/
|
|
135
|
-
function confirm(message) {
|
|
136
|
-
return new Promise((resolve) => {
|
|
137
|
-
const rl = readline.createInterface({
|
|
138
|
-
input: process.stdin,
|
|
139
|
-
output: process.stdout
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
rl.question(message, (answer) => {
|
|
143
|
-
rl.close();
|
|
144
|
-
resolve(answer.toLowerCase() === 'y');
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Function to register the vault tasks
|
|
150
|
-
export default function (gulp) {
|
|
151
|
-
const ENV_FILE = '.env';
|
|
152
|
-
const ENCRYPTED_FILE = '.env.vault';
|
|
153
|
-
|
|
154
|
-
// Encrypt .env file
|
|
155
|
-
gulp.task('vault:encrypt', async () => {
|
|
156
|
-
const envPath = path.join(process.cwd(), ENV_FILE);
|
|
157
|
-
const encryptedPath = path.join(process.cwd(), ENCRYPTED_FILE);
|
|
158
|
-
|
|
159
|
-
// Check if .env exists
|
|
160
|
-
if (!fs.existsSync(envPath)) {
|
|
161
|
-
throw new Error(`${ENV_FILE} not found`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Read .env content
|
|
165
|
-
const content = fs.readFileSync(envPath, 'utf8');
|
|
166
|
-
|
|
167
|
-
// Get password
|
|
168
|
-
const password = await getPasswordWithConfirm();
|
|
169
|
-
|
|
170
|
-
if (!password || password.length < 8) {
|
|
171
|
-
throw new Error('Password must be at least 8 characters');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Encrypt
|
|
175
|
-
const encrypted = encrypt(content, password);
|
|
176
|
-
|
|
177
|
-
// Write encrypted file
|
|
178
|
-
fs.writeFileSync(encryptedPath, encrypted);
|
|
179
|
-
|
|
180
|
-
console.log(`\nEncrypted ${ENV_FILE} -> ${ENCRYPTED_FILE}`);
|
|
181
|
-
console.log(`You can now safely commit ${ENCRYPTED_FILE} to version control.`);
|
|
182
|
-
console.log(`\nRemember to keep your password safe!`);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Decrypt .env.vault file
|
|
186
|
-
gulp.task('vault:decrypt', async () => {
|
|
187
|
-
const envPath = path.join(process.cwd(), ENV_FILE);
|
|
188
|
-
const encryptedPath = path.join(process.cwd(), ENCRYPTED_FILE);
|
|
189
|
-
|
|
190
|
-
// Check if .env.vault exists
|
|
191
|
-
if (!fs.existsSync(encryptedPath)) {
|
|
192
|
-
throw new Error(`${ENCRYPTED_FILE} not found`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Check if .env already exists
|
|
196
|
-
if (fs.existsSync(envPath)) {
|
|
197
|
-
const shouldOverwrite = await confirm(`${ENV_FILE} already exists. Overwrite? (y/N): `);
|
|
198
|
-
if (!shouldOverwrite) {
|
|
199
|
-
console.log('Aborted.');
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Read encrypted content
|
|
205
|
-
const encryptedData = fs.readFileSync(encryptedPath);
|
|
206
|
-
|
|
207
|
-
// Get password
|
|
208
|
-
const password = await getPassword();
|
|
209
|
-
|
|
210
|
-
// Decrypt
|
|
211
|
-
try {
|
|
212
|
-
const decrypted = decrypt(encryptedData, password);
|
|
213
|
-
|
|
214
|
-
// Write .env file
|
|
215
|
-
fs.writeFileSync(envPath, decrypted);
|
|
216
|
-
|
|
217
|
-
console.log(`\nDecrypted ${ENCRYPTED_FILE} -> ${ENV_FILE}`);
|
|
218
|
-
} catch (error) {
|
|
219
|
-
if (error.message.includes('Unsupported state') || error.code === 'ERR_OSSL_BAD_DECRYPT') {
|
|
220
|
-
throw new Error('Invalid password');
|
|
221
|
-
}
|
|
222
|
-
throw error;
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// View encrypted file content (without saving)
|
|
227
|
-
gulp.task('vault:view', async () => {
|
|
228
|
-
const encryptedPath = path.join(process.cwd(), ENCRYPTED_FILE);
|
|
229
|
-
|
|
230
|
-
// Check if .env.vault exists
|
|
231
|
-
if (!fs.existsSync(encryptedPath)) {
|
|
232
|
-
throw new Error(`${ENCRYPTED_FILE} not found`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Read encrypted content
|
|
236
|
-
const encryptedData = fs.readFileSync(encryptedPath);
|
|
237
|
-
|
|
238
|
-
// Get password
|
|
239
|
-
const password = await getPassword();
|
|
240
|
-
|
|
241
|
-
// Decrypt
|
|
242
|
-
try {
|
|
243
|
-
const decrypted = decrypt(encryptedData, password);
|
|
244
|
-
|
|
245
|
-
console.log(`\n--- ${ENCRYPTED_FILE} contents ---\n`);
|
|
246
|
-
console.log(decrypted);
|
|
247
|
-
console.log(`\n--- end ---\n`);
|
|
248
|
-
} catch (error) {
|
|
249
|
-
if (error.message.includes('Unsupported state') || error.code === 'ERR_OSSL_BAD_DECRYPT') {
|
|
250
|
-
throw new Error('Invalid password');
|
|
251
|
-
}
|
|
252
|
-
throw error;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Re-encrypt with new password
|
|
257
|
-
gulp.task('vault:rekey', async () => {
|
|
258
|
-
const encryptedPath = path.join(process.cwd(), ENCRYPTED_FILE);
|
|
259
|
-
|
|
260
|
-
// Check if .env.vault exists
|
|
261
|
-
if (!fs.existsSync(encryptedPath)) {
|
|
262
|
-
throw new Error(`${ENCRYPTED_FILE} not found`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Read encrypted content
|
|
266
|
-
const encryptedData = fs.readFileSync(encryptedPath);
|
|
267
|
-
|
|
268
|
-
// Get current password
|
|
269
|
-
console.log('Enter current password:');
|
|
270
|
-
const currentPassword = await promptPassword('Current vault password: ');
|
|
271
|
-
|
|
272
|
-
// Decrypt with current password
|
|
273
|
-
let decrypted;
|
|
274
|
-
try {
|
|
275
|
-
decrypted = decrypt(encryptedData, currentPassword);
|
|
276
|
-
} catch (error) {
|
|
277
|
-
if (error.message.includes('Unsupported state') || error.code === 'ERR_OSSL_BAD_DECRYPT') {
|
|
278
|
-
throw new Error('Invalid password');
|
|
279
|
-
}
|
|
280
|
-
throw error;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Get new password
|
|
284
|
-
console.log('\nEnter new password:');
|
|
285
|
-
const newPassword = await getPasswordWithConfirm();
|
|
286
|
-
|
|
287
|
-
if (!newPassword || newPassword.length < 8) {
|
|
288
|
-
throw new Error('Password must be at least 8 characters');
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Re-encrypt with new password
|
|
292
|
-
const reEncrypted = encrypt(decrypted, newPassword);
|
|
293
|
-
|
|
294
|
-
// Write encrypted file
|
|
295
|
-
fs.writeFileSync(encryptedPath, reEncrypted);
|
|
296
|
-
|
|
297
|
-
console.log(`\nRe-encrypted ${ENCRYPTED_FILE} with new password.`);
|
|
298
|
-
});
|
|
299
|
-
}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Vault - .env ファイルの暗号化・復号化ユーティリティ
|
|
10
|
+
*
|
|
11
|
+
* 暗号化アルゴリズム: AES-256-GCM
|
|
12
|
+
* 鍵導出: PBKDF2 (SHA-512, 100,000 iterations)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
16
|
+
const PBKDF2_ITERATIONS = 100000;
|
|
17
|
+
const SALT_LENGTH = 32;
|
|
18
|
+
const IV_LENGTH = 16;
|
|
19
|
+
const AUTH_TAG_LENGTH = 16;
|
|
20
|
+
const KEY_LENGTH = 32;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* パスワードから暗号化キーを導出
|
|
24
|
+
* @param {string} password - パスワード
|
|
25
|
+
* @param {Buffer} salt - ソルト
|
|
26
|
+
* @returns {Buffer} - 導出されたキー
|
|
27
|
+
*/
|
|
28
|
+
function deriveKey(password, salt) {
|
|
29
|
+
return crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* データを暗号化
|
|
34
|
+
* @param {string} plaintext - 平文
|
|
35
|
+
* @param {string} password - パスワード
|
|
36
|
+
* @returns {Buffer} - 暗号化されたデータ (salt + iv + authTag + ciphertext)
|
|
37
|
+
*/
|
|
38
|
+
function encrypt(plaintext, password) {
|
|
39
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
40
|
+
const key = deriveKey(password, salt);
|
|
41
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
42
|
+
|
|
43
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
44
|
+
const encrypted = Buffer.concat([
|
|
45
|
+
cipher.update(plaintext, 'utf8'),
|
|
46
|
+
cipher.final()
|
|
47
|
+
]);
|
|
48
|
+
const authTag = cipher.getAuthTag();
|
|
49
|
+
|
|
50
|
+
// フォーマット: salt (32) + iv (16) + authTag (16) + ciphertext
|
|
51
|
+
return Buffer.concat([salt, iv, authTag, encrypted]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* データを復号化
|
|
56
|
+
* @param {Buffer} encryptedData - 暗号化されたデータ
|
|
57
|
+
* @param {string} password - パスワード
|
|
58
|
+
* @returns {string} - 復号化された平文
|
|
59
|
+
*/
|
|
60
|
+
function decrypt(encryptedData, password) {
|
|
61
|
+
const salt = encryptedData.subarray(0, SALT_LENGTH);
|
|
62
|
+
const iv = encryptedData.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
63
|
+
const authTag = encryptedData.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
64
|
+
const ciphertext = encryptedData.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
65
|
+
|
|
66
|
+
const key = deriveKey(password, salt);
|
|
67
|
+
|
|
68
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
69
|
+
decipher.setAuthTag(authTag);
|
|
70
|
+
|
|
71
|
+
const decrypted = Buffer.concat([
|
|
72
|
+
decipher.update(ciphertext),
|
|
73
|
+
decipher.final()
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
return decrypted.toString('utf8');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* パスワードをプロンプトで取得(シンプル版)
|
|
81
|
+
* @param {string} prompt - プロンプトメッセージ
|
|
82
|
+
* @returns {Promise<string>} - 入力されたパスワード
|
|
83
|
+
*/
|
|
84
|
+
function promptPassword(prompt) {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const rl = readline.createInterface({
|
|
87
|
+
input: process.stdin,
|
|
88
|
+
output: process.stdout
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
rl.question(prompt, (answer) => {
|
|
92
|
+
rl.close();
|
|
93
|
+
resolve(answer);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 環境変数からパスワードを取得、なければプロンプト
|
|
100
|
+
* @returns {Promise<string>} - パスワード
|
|
101
|
+
*/
|
|
102
|
+
async function getPassword() {
|
|
103
|
+
const envPassword = process.env.VAULT_PASSWORD;
|
|
104
|
+
if (envPassword) {
|
|
105
|
+
return envPassword;
|
|
106
|
+
}
|
|
107
|
+
return promptPassword('Vault password: ');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 環境変数からパスワードを取得、なければプロンプト(確認付き)
|
|
112
|
+
* @returns {Promise<string>} - パスワード
|
|
113
|
+
*/
|
|
114
|
+
async function getPasswordWithConfirm() {
|
|
115
|
+
const envPassword = process.env.VAULT_PASSWORD;
|
|
116
|
+
if (envPassword) {
|
|
117
|
+
return envPassword;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const password = await promptPassword('New vault password: ');
|
|
121
|
+
const confirm = await promptPassword('Confirm vault password: ');
|
|
122
|
+
|
|
123
|
+
if (password !== confirm) {
|
|
124
|
+
throw new Error('Passwords do not match');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return password;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 確認プロンプト
|
|
132
|
+
* @param {string} message - メッセージ
|
|
133
|
+
* @returns {Promise<boolean>} - true: yes, false: no
|
|
134
|
+
*/
|
|
135
|
+
function confirm(message) {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
const rl = readline.createInterface({
|
|
138
|
+
input: process.stdin,
|
|
139
|
+
output: process.stdout
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
rl.question(message, (answer) => {
|
|
143
|
+
rl.close();
|
|
144
|
+
resolve(answer.toLowerCase() === 'y');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Function to register the vault tasks
|
|
150
|
+
export default function (gulp) {
|
|
151
|
+
const ENV_FILE = '.env';
|
|
152
|
+
const ENCRYPTED_FILE = '.env.vault';
|
|
153
|
+
|
|
154
|
+
// Encrypt .env file
|
|
155
|
+
gulp.task('vault:encrypt', async () => {
|
|
156
|
+
const envPath = path.join(process.cwd(), ENV_FILE);
|
|
157
|
+
const encryptedPath = path.join(process.cwd(), ENCRYPTED_FILE);
|
|
158
|
+
|
|
159
|
+
// Check if .env exists
|
|
160
|
+
if (!fs.existsSync(envPath)) {
|
|
161
|
+
throw new Error(`${ENV_FILE} not found`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Read .env content
|
|
165
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
166
|
+
|
|
167
|
+
// Get password
|
|
168
|
+
const password = await getPasswordWithConfirm();
|
|
169
|
+
|
|
170
|
+
if (!password || password.length < 8) {
|
|
171
|
+
throw new Error('Password must be at least 8 characters');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Encrypt
|
|
175
|
+
const encrypted = encrypt(content, password);
|
|
176
|
+
|
|
177
|
+
// Write encrypted file
|
|
178
|
+
fs.writeFileSync(encryptedPath, encrypted);
|
|
179
|
+
|
|
180
|
+
console.log(`\nEncrypted ${ENV_FILE} -> ${ENCRYPTED_FILE}`);
|
|
181
|
+
console.log(`You can now safely commit ${ENCRYPTED_FILE} to version control.`);
|
|
182
|
+
console.log(`\nRemember to keep your password safe!`);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Decrypt .env.vault file
|
|
186
|
+
gulp.task('vault:decrypt', async () => {
|
|
187
|
+
const envPath = path.join(process.cwd(), ENV_FILE);
|
|
188
|
+
const encryptedPath = path.join(process.cwd(), ENCRYPTED_FILE);
|
|
189
|
+
|
|
190
|
+
// Check if .env.vault exists
|
|
191
|
+
if (!fs.existsSync(encryptedPath)) {
|
|
192
|
+
throw new Error(`${ENCRYPTED_FILE} not found`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check if .env already exists
|
|
196
|
+
if (fs.existsSync(envPath)) {
|
|
197
|
+
const shouldOverwrite = await confirm(`${ENV_FILE} already exists. Overwrite? (y/N): `);
|
|
198
|
+
if (!shouldOverwrite) {
|
|
199
|
+
console.log('Aborted.');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Read encrypted content
|
|
205
|
+
const encryptedData = fs.readFileSync(encryptedPath);
|
|
206
|
+
|
|
207
|
+
// Get password
|
|
208
|
+
const password = await getPassword();
|
|
209
|
+
|
|
210
|
+
// Decrypt
|
|
211
|
+
try {
|
|
212
|
+
const decrypted = decrypt(encryptedData, password);
|
|
213
|
+
|
|
214
|
+
// Write .env file
|
|
215
|
+
fs.writeFileSync(envPath, decrypted);
|
|
216
|
+
|
|
217
|
+
console.log(`\nDecrypted ${ENCRYPTED_FILE} -> ${ENV_FILE}`);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error.message.includes('Unsupported state') || error.code === 'ERR_OSSL_BAD_DECRYPT') {
|
|
220
|
+
throw new Error('Invalid password');
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// View encrypted file content (without saving)
|
|
227
|
+
gulp.task('vault:view', async () => {
|
|
228
|
+
const encryptedPath = path.join(process.cwd(), ENCRYPTED_FILE);
|
|
229
|
+
|
|
230
|
+
// Check if .env.vault exists
|
|
231
|
+
if (!fs.existsSync(encryptedPath)) {
|
|
232
|
+
throw new Error(`${ENCRYPTED_FILE} not found`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Read encrypted content
|
|
236
|
+
const encryptedData = fs.readFileSync(encryptedPath);
|
|
237
|
+
|
|
238
|
+
// Get password
|
|
239
|
+
const password = await getPassword();
|
|
240
|
+
|
|
241
|
+
// Decrypt
|
|
242
|
+
try {
|
|
243
|
+
const decrypted = decrypt(encryptedData, password);
|
|
244
|
+
|
|
245
|
+
console.log(`\n--- ${ENCRYPTED_FILE} contents ---\n`);
|
|
246
|
+
console.log(decrypted);
|
|
247
|
+
console.log(`\n--- end ---\n`);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error.message.includes('Unsupported state') || error.code === 'ERR_OSSL_BAD_DECRYPT') {
|
|
250
|
+
throw new Error('Invalid password');
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Re-encrypt with new password
|
|
257
|
+
gulp.task('vault:rekey', async () => {
|
|
258
|
+
const encryptedPath = path.join(process.cwd(), ENCRYPTED_FILE);
|
|
259
|
+
|
|
260
|
+
// Check if .env.vault exists
|
|
261
|
+
if (!fs.existsSync(encryptedPath)) {
|
|
262
|
+
throw new Error(`${ENCRYPTED_FILE} not found`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Read encrypted content
|
|
266
|
+
const encryptedData = fs.readFileSync(encryptedPath);
|
|
267
|
+
|
|
268
|
+
// Get current password
|
|
269
|
+
console.log('Enter current password:');
|
|
270
|
+
const currentPassword = await promptPassword('Current vault password: ');
|
|
271
|
+
|
|
272
|
+
// Decrypt with current password
|
|
273
|
+
let decrypted;
|
|
274
|
+
try {
|
|
275
|
+
decrypted = decrypt(encryptedData, currentPassword);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (error.message.includes('Unsupported state') || error.code === 'ERR_OSSL_BAD_DECRYPT') {
|
|
278
|
+
throw new Error('Invalid password');
|
|
279
|
+
}
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Get new password
|
|
284
|
+
console.log('\nEnter new password:');
|
|
285
|
+
const newPassword = await getPasswordWithConfirm();
|
|
286
|
+
|
|
287
|
+
if (!newPassword || newPassword.length < 8) {
|
|
288
|
+
throw new Error('Password must be at least 8 characters');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Re-encrypt with new password
|
|
292
|
+
const reEncrypted = encrypt(decrypted, newPassword);
|
|
293
|
+
|
|
294
|
+
// Write encrypted file
|
|
295
|
+
fs.writeFileSync(encryptedPath, reEncrypted);
|
|
296
|
+
|
|
297
|
+
console.log(`\nRe-encrypted ${ENCRYPTED_FILE} with new password.`);
|
|
298
|
+
});
|
|
299
|
+
}
|