Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 <hi@typix.art>",
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 <hi@typix.art>",
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
12 changes: 12 additions & 0 deletions src/server/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,25 @@ 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: {
crossSubDomainCookies: {
enabled: true,
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"