create-einja-app 0.2.2 → 0.2.6
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/dist/cli.js +7 -4
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/.gitattributes +14 -0
- package/templates/default/README.md +0 -117
- package/templates/default/package.json +1 -3
- package/templates/default/scripts/env-rotate-secrets.ts +336 -0
- package/templates/default/scripts/env.ts +7 -83
- package/templates/default/scripts/lib/env-common.ts +108 -0
- package/templates/default/scripts/template-update.ts +41 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 環境変数管理の共通処理
|
|
3
|
+
*
|
|
4
|
+
* scripts/env.ts と scripts/env-rotate-secrets.ts で共有される
|
|
5
|
+
* 共通のユーティリティ関数と型定義を提供します。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
|
|
13
|
+
/** 環境変数ファイルのパス */
|
|
14
|
+
export const ENV_KEYS_PATH = path.join(cwd, ".env.keys");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 環境設定の定義
|
|
18
|
+
*/
|
|
19
|
+
export interface EnvironmentConfig {
|
|
20
|
+
name: string;
|
|
21
|
+
file: string;
|
|
22
|
+
privateKeyEnv: string;
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* サポートされる環境の定義
|
|
28
|
+
*/
|
|
29
|
+
export const ENVIRONMENTS: EnvironmentConfig[] = [
|
|
30
|
+
{
|
|
31
|
+
name: "local",
|
|
32
|
+
file: ".env.local",
|
|
33
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_LOCAL",
|
|
34
|
+
description: "ローカル開発環境",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "development",
|
|
38
|
+
file: ".env.development",
|
|
39
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_DEVELOPMENT",
|
|
40
|
+
description: "開発環境",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "staging",
|
|
44
|
+
file: ".env.staging",
|
|
45
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_STAGING",
|
|
46
|
+
description: "ステージング環境",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "production",
|
|
50
|
+
file: ".env.production",
|
|
51
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_PRODUCTION",
|
|
52
|
+
description: "本番環境",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "ci",
|
|
56
|
+
file: ".env.ci",
|
|
57
|
+
privateKeyEnv: "DOTENV_PRIVATE_KEY_CI",
|
|
58
|
+
description: "CI環境",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 環境変数ファイルを読み込んでパース
|
|
64
|
+
*
|
|
65
|
+
* @param filePath - パースする環境変数ファイルのパス
|
|
66
|
+
* @returns 環境変数のキーバリューペア
|
|
67
|
+
*/
|
|
68
|
+
export function parseEnvFile(filePath: string): Record<string, string> {
|
|
69
|
+
if (!fs.existsSync(filePath)) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
73
|
+
const result: Record<string, string> = {};
|
|
74
|
+
|
|
75
|
+
for (const line of content.split("\n")) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
78
|
+
|
|
79
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
80
|
+
if (match) {
|
|
81
|
+
const key = match[1].trim();
|
|
82
|
+
let value = match[2].trim();
|
|
83
|
+
// クォートを除去
|
|
84
|
+
if (
|
|
85
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
86
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
87
|
+
) {
|
|
88
|
+
value = value.slice(1, -1);
|
|
89
|
+
}
|
|
90
|
+
result[key] = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* .env.keysから指定された環境の秘密鍵を取得
|
|
98
|
+
*
|
|
99
|
+
* @param privateKeyEnv - 秘密鍵の環境変数名(例: DOTENV_PRIVATE_KEY_LOCAL)
|
|
100
|
+
* @returns 秘密鍵の値。見つからない場合はnull
|
|
101
|
+
*/
|
|
102
|
+
export function getPrivateKey(privateKeyEnv: string): string | null {
|
|
103
|
+
if (!fs.existsSync(ENV_KEYS_PATH)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const keys = parseEnvFile(ENV_KEYS_PATH);
|
|
107
|
+
return keys[privateKeyEnv] || null;
|
|
108
|
+
}
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
import {
|
|
13
13
|
copyFileSync,
|
|
14
14
|
existsSync,
|
|
15
|
+
lstatSync,
|
|
15
16
|
mkdirSync,
|
|
16
17
|
readFileSync,
|
|
17
18
|
rmSync,
|
|
19
|
+
statSync,
|
|
18
20
|
writeFileSync,
|
|
19
21
|
} from "node:fs";
|
|
20
22
|
import { dirname, join, relative } from "node:path";
|
|
@@ -165,12 +167,34 @@ function transformImports(content: string): string {
|
|
|
165
167
|
return content.replace(/@repo\//g, "{{packageName}}/");
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
/**
|
|
171
|
+
* @einja:template-exclude マーカーを除去し、除外後の空行・水平線をクリーンアップ
|
|
172
|
+
*/
|
|
173
|
+
function removeExcludeMarkers(content: string): string {
|
|
174
|
+
const excludePattern =
|
|
175
|
+
/<!-- @einja:template-exclude:start -->[\s\S]*?<!-- @einja:template-exclude:end -->/g;
|
|
176
|
+
let result = content.replace(excludePattern, "");
|
|
177
|
+
|
|
178
|
+
// 連続する水平線を1つに
|
|
179
|
+
result = result.replace(/(\n---\s*){2,}/g, "\n---\n");
|
|
180
|
+
|
|
181
|
+
// 3行以上の空行を2行に圧縮
|
|
182
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
183
|
+
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
168
187
|
/**
|
|
169
188
|
* ファイル内容を変換
|
|
170
189
|
*/
|
|
171
190
|
function transformFileContent(filePath: string, content: string): string {
|
|
172
191
|
const fileName = filePath.split("/").pop() || "";
|
|
173
192
|
|
|
193
|
+
// ルートREADME.mdの変換(@einja:template-exclude マーカー除去)
|
|
194
|
+
if (filePath === "README.md") {
|
|
195
|
+
return removeExcludeMarkers(content);
|
|
196
|
+
}
|
|
197
|
+
|
|
174
198
|
// package.json の変換
|
|
175
199
|
if (fileName === "package.json") {
|
|
176
200
|
return transformPackageJson(content, filePath);
|
|
@@ -201,7 +225,7 @@ async function main(): Promise<void> {
|
|
|
201
225
|
"packages",
|
|
202
226
|
"create-einja-app",
|
|
203
227
|
"templates",
|
|
204
|
-
"
|
|
228
|
+
"default"
|
|
205
229
|
);
|
|
206
230
|
|
|
207
231
|
console.log("\n🔄 テンプレート更新を開始...\n");
|
|
@@ -251,6 +275,22 @@ async function main(): Promise<void> {
|
|
|
251
275
|
const sourcePath = join(rootDir, file);
|
|
252
276
|
const destPath = join(templateDir, file);
|
|
253
277
|
|
|
278
|
+
// ファイルの存在確認
|
|
279
|
+
if (!existsSync(sourcePath)) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// シンボリックリンクの場合はスキップ
|
|
284
|
+
const stats = lstatSync(sourcePath);
|
|
285
|
+
if (stats.isSymbolicLink()) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ディレクトリの場合はスキップ(念のため)
|
|
290
|
+
if (stats.isDirectory()) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
254
294
|
// ディレクトリを作成
|
|
255
295
|
const destDir = dirname(destPath);
|
|
256
296
|
if (!existsSync(destDir)) {
|