aws-samples / aws-samples/serverless-full-stack-webapp-starter-kit

環境変数のZodバリデーション + sync-env.sh による自動同期

Ouverte
#112 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub
enhancement stale
Langage dominant
TypeScript
Étoiles
229
Forks
45
Merge moyen
1 min
PR mergées (30 j)
4

Description

## 背景

現在の環境変数管理に以下の問題がある:

1. **typo・未設定の検出が遅い**: `process.env.XXX!` の直接参照が6ファイルに散在しており、typoや未設定を実行時まで検出できない
2. **CDK Outputsの手動コピーが必要**: `cdk deploy` 後にCLI出力から値を手動で `.env.local` にコピーする必要がある([README](https://github.com/aws-samples/serverless-full-stack-webapp-starter-kit#local-development)参照)
3. **`.env.local.example` が不完全**: 現在のexampleファイルにはプレースホルダー値(`dummy`, `""`)が入っているが、どのCDK Outputに対応するかのコメントがない

## 提案

### 1. `src/lib/env.ts` — Zodバリデーション

環境変数をZodスキーマで一元管理し、トップレベルでバリデーションする。

```typescript
// src/lib/env.ts
import { z } from "zod";

const envSchema = z.object({
// Cognito認証
COGNITO_DOMAIN: z.string().min(1),
USER_POOL_ID: z.string().min(1),
USER_POOL_CLIENT_ID: z.string().min(1),
// カスタムドメインありの場合は直接設定、なしの場合はSSMパラメータ経由で動的取得
AMPLIFY_APP_ORIGIN: z.string().min(1).optional(),
// AppSync Events
NEXT_PUBLIC_EVENT_HTTP_ENDPOINT: z.string().url(),
NEXT_PUBLIC_AWS_REGION: z.string().min(1),
// 非同期ジョブ
ASYNC_JOB_HANDLER_ARN: z.string().min(1),
});

export const env = envSchema.parse(process.env);
```

各ファイルでは `process.env` の代わりに `env.USER_POOL_ID` のように参照する。

設計上の注意点:

- **トップレベルparse**: モジュール読み込み時にバリデーションが走る。Next.jsが`.env.local`を読み込んだ後に実行されるため問題ない
- **`AMPLIFY_APP_ORIGIN`は`.optional()`**: カスタムドメインなしの場合、Lambda実行環境ではSSMパラメータから動的取得するため、デプロイ時点では未設定
- **Lambda注入変数は含めない**: `DATABASE_URL`, `EVENT_HTTP_ENDPOINT`, `AWS_REGION`等はCDKがLambda環境変数として注入するため、ローカル開発用の`env.ts`のスコープ外。`DATABASE_URL`はPrismaが`prisma/.env`から読み込む

### 2. ESLint `no-restricted-syntax` で `process.env` 直接参照を禁止

```javascript
// eslint.config.mjs に追加
{
rules: {
"no-restricted-syntax": [
"error",
{
selector: "MemberExpression[object.object.name='process'][object.property.name='env']",
message: "process.env の直接参照は禁止です。src/lib/env.ts の env を使用してください。",
},
],
},
},
// 除外ファイル
{
files: ["src/lib/env.ts", "src/lib/amplifyServerUtils.ts", "next.config.ts"],
rules: { "no-restricted-syntax": "off" },
},
{
files: ["tests/**/*.ts", "**/*.test.ts"],
rules: { "no-restricted-syntax": "off" },
},
```

`amplifyServerUtils.ts`はAmplify SDKの初期化で`AMPLIFY_APP_ORIGIN_SOURCE_PARAMETER`からSSM経由の動的取得を行う特殊なファイルのため除外。テストファイルも除外する。

### 3. `scripts/sync-env.mjs` — CDK Outputsから `.env.local` を自動生成

bashスクリプトではなくNode.jsスクリプトとする。理由:

- Node.js >= v20 はデプロイの前提条件に既にある(追加依存なし)
- Windows環境でも動作する(WSL/Git Bash不要)
- `jq`への依存が不要(`JSON.parse`で済む)

```javascript
#!/usr/bin/env node
// scripts/sync-env.mjs
import { execSync } from "child_process";
import { writeFileSync } from "fs";

const stackName = "ServerlessWebappStarterKitStack";
const outputs = JSON.parse(
execSync(
`aws cloudformation describe-stacks --stack-name ${stackName} --query "Stacks[0].Outputs" --output json`,
).toString(),
);

const get = (prefix) =>
outputs.find((o) => o.OutputKey.startsWith(prefix))?.OutputValue ?? "";

const region = execSync("aws configure get region").toString().trim();

writeFileSync(
"webapp/.env.local",
`# DO NOT EDIT — generated by scripts/sync-env.mjs
COGNITO_DOMAIN=${get("AuthUserPoolDomainName")}
USER_POOL_ID=${get("AuthUserPoolId")}
USER_POOL_CLIENT_ID=${get("AuthUserPoolClientId")}
AMPLIFY_APP_ORIGIN=http://localhost:3010
NEXT_PUBLIC_EVENT_HTTP_ENDPOINT=${get("EventBusHttpEndpoint")}
NEXT_PUBLIC_AWS_REGION=${region}
ASYNC_JOB_HANDLER_ARN=${get("AsyncJobHandlerArn")}
`,
);
```

CfnOutputのキーにはCDKが付与するハッシュサフィックス(例: `AuthUserPoolIdC0605E59`)があるため、`startsWith`で前方一致検索する。

### 4. `.env.local.example` の拡充

```
# CDK Outputsから取得(scripts/sync-env.mjs で自動生成可能)
COGNITO_DOMAIN= # CfnOutput: AuthUserPoolDomainName
USER_POOL_ID= # CfnOutput: AuthUserPoolId
USER_POOL_CLIENT_ID= # CfnOutput: AuthUserPoolClientId
NEXT_PUBLIC_EVENT_HTTP_ENDPOINT= # CfnOutput: EventBusHttpEndpoint
NEXT_PUBLIC_AWS_REGION=us-west-2
ASYNC_JOB_HANDLER_ARN= # CfnOutput: AsyncJobHandlerArn

# ローカル開発用(固定値)
AMPLIFY_APP_ORIGIN=http://localhost:3010
```

## 現在の `process.env` 直接参照箇所

| ファイル | 環境変数 |
|---------|---------|
| `src/lib/amplifyServerUtils.ts` | `AMPLIFY_APP_ORIGIN`, `USER_POOL_ID`, `USER_POOL_CLIENT_ID`, `COGNITO_DOMAIN`, `AMPLIFY_APP_ORIGIN_SOURCE_PARAMETER` |
| `src/hooks/use-event-bus.ts` | `NEXT_PUBLIC_EVENT_HTTP_ENDPOINT`, `NEXT_PUBLIC_AWS_REGION` |
| `src/lib/events.ts` | `EVENT_HTTP_ENDPOINT`, `AWS_REGION` |
| `src/lib/jobs.ts` | `ASYNC_JOB_HANDLER_ARN` |
| `src/lib/prisma.ts` | `DATABASE_URL`, `NODE_ENV` |
| `src/jobs/async-job/translate.ts` | `AWS_REGION` |

注: `amplifyServerUtils.ts`はESLint除外対象のため、`process.env`参照を維持する。それ以外のファイルは`env.ts`経由に移行する。`EVENT_HTTP_ENDPOINT`, `AWS_REGION`, `DATABASE_URL`, `NODE_ENV`はLambda実行環境でCDKが注入する変数であり、ローカル開発時には使用しないため`env.ts`のスキーマには含めない。

## 検証方法

- [ ] `scripts/sync-env.mjs` でCDK Outputsから `webapp/.env.local` が自動生成される
- [ ] 環境変数の不足・型不正がアプリ起動時にZodエラーとして検出される
- [ ] `process.env` の直接参照がESLintエラーになる(除外ファイル以外)

## 備考

- pnpm workspaces化(#98)後に実装する前提。モノレポ構成ではスクリプトの配置場所や`.env.local`のパスが変わる可能性がある

Guide de contribution

Ouvrir le guide de contribution

Piste de recherche

Examinez le prérequis pnpm workspaces dans #98 ainsi que les références process.env indiquées avant de modifier les chemins ou le périmètre. Commencez par src/lib/env.ts, eslint.config.mjs, scripts/sync-env.mjs et .env.local.example ; exécutez le script de synchronisation et ESLint, puis vérifiez que les variables manquantes ou invalides provoquent un échec au démarrage et que le fichier généré contient les sorties CDK attendues.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
aws, nextjs, node.js, typescript
Domaine
cloud, devops, tooling
Type d'issue
Fonctionnalité
Difficulté
4/5
Temps estimé
3-5 jours
Activité
Calme
Clarté
Plutôt claire
Accessibilité débutants
48/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.