mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added Stripe, OAuth, and Freestyle mock services to the local emulator * Introduced `emulator run` CLI command to execute applications with emulator credentials automatically injected * Enhanced credential management for local development * **Improvements** * Improved ARM64 QEMU emulation with cross-architecture support * Better error detection and logging during emulator provisioning * Added example middleware configuration with authentication support <!-- end of auto-generated comment: release notes by coderabbit.ai -->
206 lines
9.8 KiB
JavaScript
206 lines
9.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const rootDir = path.resolve(scriptDir, "..", "..");
|
|
|
|
const outputPath = path.join(scriptDir, ".env.development");
|
|
const backendEnvPath = path.join(rootDir, "apps", "backend", ".env.development");
|
|
const dashboardEnvPath = path.join(rootDir, "apps", "dashboard", ".env.development");
|
|
|
|
const args = process.argv.slice(2);
|
|
if (args.length > 1 || (args[0] != null && args[0] !== "--check")) {
|
|
throw new Error("Usage: node docker/local-emulator/generate-env-development.mjs [--check]");
|
|
}
|
|
|
|
const parseEnvFile = (filePath) => {
|
|
const env = new Map();
|
|
|
|
for (const rawLine of fs.readFileSync(filePath, "utf8").split(/\r?\n/)) {
|
|
const trimmedLine = rawLine.trim();
|
|
if (trimmedLine === "" || trimmedLine.startsWith("#")) {
|
|
continue;
|
|
}
|
|
|
|
const separatorIndex = rawLine.indexOf("=");
|
|
if (separatorIndex < 0) {
|
|
throw new Error(`Invalid env line in ${filePath}: ${rawLine}`);
|
|
}
|
|
|
|
const key = rawLine.slice(0, separatorIndex).trim();
|
|
const value = rawLine.slice(separatorIndex + 1);
|
|
env.set(key, value);
|
|
}
|
|
|
|
return env;
|
|
};
|
|
|
|
const backendEnv = parseEnvFile(backendEnvPath);
|
|
const dashboardEnv = parseEnvFile(dashboardEnvPath);
|
|
|
|
const getRequiredEnvValue = (sourceName, envMap, key) => {
|
|
const value = envMap.get(key);
|
|
if (value == null) {
|
|
throw new Error(`Missing ${key} in ${sourceName}; update the generator or source env file.`);
|
|
}
|
|
return value;
|
|
};
|
|
|
|
const fromSource = (sourceName, envMap, key) => ({
|
|
type: "entry",
|
|
key,
|
|
value: getRequiredEnvValue(sourceName, envMap, key),
|
|
});
|
|
|
|
const literal = (key, value) => ({
|
|
type: "entry",
|
|
key,
|
|
value,
|
|
});
|
|
|
|
const comment = (value) => ({
|
|
type: "comment",
|
|
value,
|
|
});
|
|
|
|
const blank = () => ({
|
|
type: "blank",
|
|
});
|
|
|
|
const entries = [
|
|
comment("# Generated by docker/local-emulator/generate-env-development.mjs"),
|
|
comment("# Do not edit manually; update apps/backend/.env.development, apps/dashboard/.env.development, or this generator."),
|
|
blank(),
|
|
comment("# Public emulator/app credentials"),
|
|
literal("NEXT_PUBLIC_STACK_DOCS_BASE_URL", "https://docs.stack-auth.com"),
|
|
literal("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR", "true"),
|
|
fromSource("apps/dashboard/.env.development", dashboardEnv, "NEXT_PUBLIC_STACK_PROJECT_ID"),
|
|
fromSource("apps/dashboard/.env.development", dashboardEnv, "NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY"),
|
|
fromSource("apps/dashboard/.env.development", dashboardEnv, "STACK_SECRET_SERVER_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SERVER_SECRET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_CHANGELOG_URL"),
|
|
blank(),
|
|
comment("# Seed/project defaults"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_ENABLE_DUMMY_PROJECT"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_SIGN_UP_ENABLED"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_OTP_ENABLED"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_ALLOW_LOCALHOST"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_OAUTH_PROVIDERS"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS"),
|
|
// STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY is generated per-VM at boot
|
|
// by docker/local-emulator/qemu/cloud-init/emulator/user-data and injected via
|
|
// /run/stack-auth/local-emulator.env. SECRET_SERVER_KEY and SUPER_SECRET_ADMIN_KEY
|
|
// are intentionally omitted so the seed script leaves them null on the internal
|
|
// project; per-project credentials come from /api/v1/internal/local-emulator/project.
|
|
blank(),
|
|
comment("# Third-party/test integrations"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SVIX_API_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_OPENAI_API_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_OPENROUTER_API_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_STRIPE_SECRET_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_STRIPE_WEBHOOK_SECRET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_RESEND_API_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_RESEND_WEBHOOK_SECRET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_DNSIMPLE_API_TOKEN"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_DNSIMPLE_ACCOUNT_ID"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_DNSIMPLE_API_BASE_URL"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_FREESTYLE_API_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_VERCEL_SANDBOX_TOKEN"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "CRON_SECRET"),
|
|
blank(),
|
|
comment("# Storage, queueing, and analytics"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_S3_REGION"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_S3_ACCESS_KEY_ID"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_S3_SECRET_ACCESS_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_S3_BUCKET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_S3_PRIVATE_BUCKET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_AWS_REGION"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_AWS_ACCESS_KEY_ID"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_AWS_SECRET_ACCESS_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_QSTASH_TOKEN"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_QSTASH_CURRENT_SIGNING_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_QSTASH_NEXT_SIGNING_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_CLICKHOUSE_ADMIN_USER"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_CLICKHOUSE_ADMIN_PASSWORD"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_CLICKHOUSE_EXTERNAL_PASSWORD"),
|
|
blank(),
|
|
comment("# Email and dashboard integration"),
|
|
literal("STACK_EMAIL_PORT", "2500"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_SECURE"),
|
|
literal("STACK_EMAIL_USERNAME", "does-not-matter"),
|
|
literal("STACK_EMAIL_PASSWORD", "does-not-matter"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_SENDER"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_DEFAULT_EMAIL_CAPACITY_PER_HOUR"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_MONITOR_PROJECT_ID"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_MONITOR_PUBLISHABLE_CLIENT_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_MONITOR_RESEND_EMAIL_DOMAIN"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_MONITOR_RESEND_EMAIL_API_KEY"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_MONITOR_SECRET_TOKEN"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_MONITOR_USE_INBUCKET"),
|
|
fromSource("apps/dashboard/.env.development", dashboardEnv, "STACK_FEATUREBASE_JWT_SECRET"),
|
|
blank(),
|
|
comment("# Mock OAuth defaults"),
|
|
literal("STACK_FORWARD_MOCK_OAUTH_SERVER", "false"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_GITHUB_CLIENT_ID"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_GITHUB_CLIENT_SECRET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_GOOGLE_CLIENT_ID"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_GOOGLE_CLIENT_SECRET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_MICROSOFT_CLIENT_ID"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_MICROSOFT_CLIENT_SECRET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SPOTIFY_CLIENT_ID"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_SPOTIFY_CLIENT_SECRET"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_ALLOW_SHARED_OAUTH_ACCESS_TOKENS"),
|
|
blank(),
|
|
comment("# Internal service endpoints (defaults for docker-compose; overridden in QEMU)"),
|
|
literal("STACK_DATABASE_CONNECTION_STRING", "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@127.0.0.1:5432/stackframe"),
|
|
fromSource("apps/backend/.env.development", backendEnv, "STACK_EMAIL_HOST"),
|
|
literal("STACK_SVIX_SERVER_URL", "http://127.0.0.1:8071"),
|
|
literal("STACK_S3_ENDPOINT", "http://127.0.0.1:9090"),
|
|
literal("STACK_QSTASH_URL", "http://127.0.0.1:8080"),
|
|
literal("STACK_CLICKHOUSE_URL", "http://127.0.0.1:8123"),
|
|
literal("STACK_CLICKHOUSE_DATABASE", "default"),
|
|
literal("STACK_EMAIL_MONITOR_INBUCKET_API_URL", "http://127.0.0.1:9001"),
|
|
literal("BACKEND_PORT", "8102"),
|
|
literal("DASHBOARD_PORT", "8101"),
|
|
];
|
|
|
|
const seenKeys = new Set();
|
|
for (const entry of entries) {
|
|
if (entry.type !== "entry") {
|
|
continue;
|
|
}
|
|
|
|
if (seenKeys.has(entry.key)) {
|
|
throw new Error(`Duplicate env key in generator: ${entry.key}`);
|
|
}
|
|
|
|
seenKeys.add(entry.key);
|
|
}
|
|
|
|
const content = `${entries.map((entry) => {
|
|
if (entry.type === "blank") {
|
|
return "";
|
|
}
|
|
|
|
if (entry.type === "comment") {
|
|
return entry.value;
|
|
}
|
|
|
|
return `${entry.key}=${entry.value}`;
|
|
}).join("\n")}\n`;
|
|
|
|
if (args[0] === "--check") {
|
|
const currentContent = fs.readFileSync(outputPath, "utf8");
|
|
if (currentContent !== content) {
|
|
throw new Error(`${path.relative(rootDir, outputPath)} is out of date. Run pnpm run emulator:generate-env.`);
|
|
}
|
|
|
|
console.log(`${path.relative(rootDir, outputPath)} is up to date.`);
|
|
} else {
|
|
fs.writeFileSync(outputPath, content);
|
|
console.log(`Wrote ${path.relative(rootDir, outputPath)}.`);
|
|
}
|