Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
AUTH_SOCIAL_GOOGLE_CLIENT_SECRET: ${{ secrets.AUTH_SOCIAL_GOOGLE_CLIENT_SECRET }}
AUTH_SOCIAL_GITHUB_CLIENT_ID: ${{ secrets.AUTH_SOCIAL_GITHUB_CLIENT_ID }}
AUTH_SOCIAL_GITHUB_CLIENT_SECRET: ${{ secrets.AUTH_SOCIAL_GITHUB_CLIENT_SECRET }}
COOKIE_DOMAIN: ${{ secrets.COOKIE_DOMAIN }}
run: |
# build application
sed -i "s/DATABASE_ID/${{ secrets.CLOUDFLARE_DATABASE_ID }}/g" wrangler.toml
Expand Down
291 changes: 154 additions & 137 deletions .github/workflows/scripts/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,55 +9,58 @@ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
const __dirname = process.cwd();

const config = {
preview: {
docs: false,
override: {
name: "typix-preview",
vars: {
MODE: "client",
PROVIDER_CLOUDFLARE_BUILTIN: "true",
},
},
},
production: {
docs: true,
override: {
vars: {
MODE: "mixed",
GOOGLE_ANALYTICS_ID: process.env.GOOGLE_ANALYTICS_ID,
AUTH_EMAIL_VERIFICATION_ENABLED: "true",
AUTH_EMAIL_RESEND_API_KEY: process.env.AUTH_EMAIL_RESEND_API_KEY,
AUTH_EMAIL_RESEND_FROM: "Typix <[email protected]>",
AUTH_SOCIAL_GOOGLE_ENABLED: "true",
AUTH_SOCIAL_GOOGLE_CLIENT_ID: process.env.AUTH_SOCIAL_GOOGLE_CLIENT_ID,
AUTH_SOCIAL_GOOGLE_CLIENT_SECRET: process.env.AUTH_SOCIAL_GOOGLE_CLIENT_SECRET,
AUTH_SOCIAL_GITHUB_ENABLED: "true",
AUTH_SOCIAL_GITHUB_CLIENT_ID: process.env.AUTH_SOCIAL_GITHUB_CLIENT_ID,
AUTH_SOCIAL_GITHUB_CLIENT_SECRET: process.env.AUTH_SOCIAL_GITHUB_CLIENT_SECRET,
PROVIDER_CLOUDFLARE_BUILTIN: "true",
},
},
},
preview: {
docs: false,
override: {
name: "typix-preview",
vars: {
MODE: "client",
PROVIDER_CLOUDFLARE_BUILTIN: "true",
},
},
},
production: {
docs: true,
override: {
vars: {
MODE: "mixed",
GOOGLE_ANALYTICS_ID: process.env.GOOGLE_ANALYTICS_ID,
AUTH_EMAIL_VERIFICATION_ENABLED: "true",
AUTH_EMAIL_RESEND_API_KEY: process.env.AUTH_EMAIL_RESEND_API_KEY,
AUTH_EMAIL_RESEND_FROM: "Typix <[email protected]>",
AUTH_SOCIAL_GOOGLE_ENABLED: "true",
AUTH_SOCIAL_GOOGLE_CLIENT_ID: process.env.AUTH_SOCIAL_GOOGLE_CLIENT_ID,
AUTH_SOCIAL_GOOGLE_CLIENT_SECRET:
process.env.AUTH_SOCIAL_GOOGLE_CLIENT_SECRET,
AUTH_SOCIAL_GITHUB_ENABLED: "true",
AUTH_SOCIAL_GITHUB_CLIENT_ID: process.env.AUTH_SOCIAL_GITHUB_CLIENT_ID,
AUTH_SOCIAL_GITHUB_CLIENT_SECRET:
process.env.AUTH_SOCIAL_GITHUB_CLIENT_SECRET,
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
PROVIDER_CLOUDFLARE_BUILTIN: "true",
},
},
},
};

/**
* Read wrangler.toml file
* @returns {any} Parsed TOML configuration object
*/
function readWranglerConfig() {
const wranglerPath = path.join(__dirname, "wrangler.toml");
const content = fs.readFileSync(wranglerPath, "utf8");
return parseToml(content);
const wranglerPath = path.join(__dirname, "wrangler.toml");
const content = fs.readFileSync(wranglerPath, "utf8");
return parseToml(content);
}

/**
* Write wrangler.toml file
* @param {any} config - Configuration object
*/
function writeWranglerConfig(config) {
const wranglerPath = path.join(__dirname, "wrangler.toml");
const content = stringifyToml(config);
fs.writeFileSync(wranglerPath, content, "utf8");
const wranglerPath = path.join(__dirname, "wrangler.toml");
const content = stringifyToml(config);
fs.writeFileSync(wranglerPath, content, "utf8");
}

/**
Expand All @@ -67,17 +70,21 @@ function writeWranglerConfig(config) {
* @returns {any} Merged object
*/
function deepMerge(target, source) {
const result = { ...target };

for (const key in source) {
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}

return result;
const result = { ...target };

for (const key in source) {
if (
source[key] &&
typeof source[key] === "object" &&
!Array.isArray(source[key])
) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}

return result;
}

/**
Expand All @@ -86,114 +93,124 @@ function deepMerge(target, source) {
* @param {any} baseConfig - Base configuration copy
*/
function updateWranglerConfig(override, baseConfig) {
const updatedConfig = deepMerge(baseConfig, override);
writeWranglerConfig(updatedConfig);
const updatedConfig = deepMerge(baseConfig, override);
writeWranglerConfig(updatedConfig);
}

/**
* Build and deploy
*/
function buildAndDeploy(config) {
console.log("📦 Building application...");
execSync("npm run build", { stdio: "inherit" });
console.log("✅ Application built successfully");

if (config.docs) {
console.log("📚 Building documentation...");
execSync("cd ./docs && npm install && npm run build && cp -r ./out/ ../dist/home && cd ..", { stdio: "inherit" });
console.log("✅ Documentation built successfully");
}

console.log("🚀 Deploying to Cloudflare...");
if (config.override.vars.MODE === "client") {
execSync("npm run deploy:no-migrate", { stdio: "inherit" });
} else {
execSync("npm run deploy", { stdio: "inherit" });
}
console.log("✅ Deployed successfully");
console.log("📦 Building application...");
execSync("npm run build", { stdio: "inherit" });
console.log("✅ Application built successfully");

if (config.docs) {
console.log("📚 Building documentation...");
execSync(
"cd ./docs && npm install && npm run build && cp -r ./out/ ../dist/home && cd ..",
{ stdio: "inherit" }
);
console.log("✅ Documentation built successfully");
}

console.log("🚀 Deploying to Cloudflare...");
if (config.override.vars.MODE === "client") {
execSync("npm run deploy:no-migrate", { stdio: "inherit" });
} else {
execSync("npm run deploy", { stdio: "inherit" });
}
console.log("✅ Deployed successfully");
}

/**
* Deploy multiple environments
* @param {string[]} environments - Environment list
*/
function deployEnvironments(environments) {
console.log(`🔄 Starting deployment for environments: ${environments.join(", ")}`);

// Read original configuration as a copy
const baseConfig = readWranglerConfig();
console.log("📋 Base configuration loaded");

for (const environment of environments) {
console.log(`\n--- Deploying ${environment} environment ---`);
const envConfig = config[environment];
try {
updateWranglerConfig(envConfig.override, baseConfig);
buildAndDeploy(envConfig);
} catch (error) {
console.error(
`❌ Failed to deploy ${environment} environment:`,
error instanceof Error ? error.message : String(error),
);
process.exit(1);
}
}

console.log(`\n🎉 All environments (${environments.join(", ")}) deployed successfully!`);
console.log(
`🔄 Starting deployment for environments: ${environments.join(", ")}`
);

// Read original configuration as a copy
const baseConfig = readWranglerConfig();
console.log("📋 Base configuration loaded");

for (const environment of environments) {
console.log(`\n--- Deploying ${environment} environment ---`);
const envConfig = config[environment];
try {
updateWranglerConfig(envConfig.override, baseConfig);
buildAndDeploy(envConfig);
} catch (error) {
console.error(
`❌ Failed to deploy ${environment} environment:`,
error instanceof Error ? error.message : String(error)
);
process.exit(1);
}
}

console.log(
`\n🎉 All environments (${environments.join(", ")}) deployed successfully!`
);
}

// Command line interface
function main() {
try {
const { values } = parseArgs({
options: {
preview: {
type: "boolean",
short: "p",
default: false,
},
production: {
type: "boolean",
short: "P",
default: false,
},
help: {
type: "boolean",
short: "h",
default: false,
},
},
allowPositionals: false,
});

const environments = [];

if (values.preview) {
environments.push("preview");
}

if (values.production) {
environments.push("production");
}

if (environments.length === 0) {
environments.push("production");
}

for (const env of environments) {
if (!(env in config)) {
console.error(`❌ Unknown environment: ${env}`);
console.log("Available environments: production, preview");
process.exit(1);
}
}

deployEnvironments(environments);
} catch (error) {
console.error("❌ Error parsing arguments:", error instanceof Error ? error.message : String(error));
console.log("Use --help for usage information");
process.exit(1);
}
try {
const { values } = parseArgs({
options: {
preview: {
type: "boolean",
short: "p",
default: false,
},
production: {
type: "boolean",
short: "P",
default: false,
},
help: {
type: "boolean",
short: "h",
default: false,
},
},
allowPositionals: false,
});

const environments = [];

if (values.preview) {
environments.push("preview");
}

if (values.production) {
environments.push("production");
}

if (environments.length === 0) {
environments.push("production");
}

for (const env of environments) {
if (!(env in config)) {
console.error(`❌ Unknown environment: ${env}`);
console.log("Available environments: production, preview");
process.exit(1);
}
}

deployEnvironments(environments);
} catch (error) {
console.error(
"❌ Error parsing arguments:",
error instanceof Error ? error.message : String(error)
);
console.log("Use --help for usage information");
process.exit(1);
}
}

main();
1 change: 1 addition & 0 deletions src/server/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const factory = createFactory<Env>({
clientSecret: env(c).AUTH_SOCIAL_GITHUB_CLIENT_SECRET || "",
},
},
cookieDomain: env(c).COOKIE_DOMAIN ? String(env(c).COOKIE_DOMAIN) : undefined,
};

c.set("db", db);
Expand Down
11 changes: 11 additions & 0 deletions src/server/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,24 @@ export interface AuthConfig {
clientSecret: string;
};
};
// Cookie domain for cross-subdomain sharing (e.g., .xxx.com)
cookieDomain?: string;
}

export const createAuth = (db: any, config?: AuthConfig) =>
betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite",
}),
...(config?.cookieDomain
? {
advanced: {
defaultCookieAttributes: {
domain: config.cookieDomain,
},
},
}
: {}),
emailAndPassword: {
enabled: true,
requireEmailVerification: config?.email?.verification === true,
Expand Down
3 changes: 3 additions & 0 deletions wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ MODE = "client"
# AUTH_SOCIAL_GITHUB_CLIENT_ID = ""
# AUTH_SOCIAL_GITHUB_CLIENT_SECRET = ""

# Cookie domain for cross-subdomain sharing (e.g., .xxx.com)
# COOKIE_DOMAIN = ""

# Enable Cloudflare built-in AI binding
# When set to true, uses Cloudflare Workers AI for image generation without requiring additional API keys
PROVIDER_CLOUDFLARE_BUILTIN = "true"