diff --git a/apps/server/package.json b/apps/server/package.json index eaea2ba10..455c4bc15 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -7,6 +7,7 @@ } }, "scripts": { + "prebuild": "bun run scripts/validate-env.js", "build": "bun build src/index.ts --outdir ./dist --target bun", "dev": "bun --max-old-space-size=512 run --hot src/index.ts", "lint": "biome check", diff --git a/apps/server/scripts/validate-env.js b/apps/server/scripts/validate-env.js new file mode 100644 index 000000000..8e4d236e4 --- /dev/null +++ b/apps/server/scripts/validate-env.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +// Load environment variables from .env file +import { config } from 'dotenv'; +config(); + +// Build-time environment validation + +console.log('šŸ” Validating environment variables...'); + +const env = { + PORT: process.env.PORT, + SUPABASE_URL: process.env.SUPABASE_URL, + SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY, + ELECTRIC_PROXY_URL: process.env.ELECTRIC_PROXY_URL, + DATABASE_URL: process.env.DATABASE_URL, + NODE_ENV: process.env.NODE_ENV || 'development', +}; + +let hasErrors = false; + +for (const [envKey, value] of Object.entries(env)) { + if (!value) { + console.error(`āŒ Missing required environment variable: ${envKey}`); + hasErrors = true; + } else { + console.log(`āœ… ${envKey} is set`); + } +} + +if (hasErrors) { + console.error(''); + console.error('āŒ Build cannot continue with missing environment variables.'); + console.error('Please check your .env file and ensure all required variables are set.'); + process.exit(1); +} + +console.log('āœ… All required environment variables are present'); diff --git a/package.json b/package.json index eac3f8153..61ffcda4f 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "check": "biome check ${1:-.}", "check:fix": "biome check --write ${1:-.}", "ci:check": "pnpm run check && pnpm run typecheck", + "new:package": "bun run scripts/new-package.ts", "db:check": "pnpm --filter @buster/database run db:check", "db:generate": "pnpm --filter @buster/database run db:generate", "db:introspect": "pnpm --filter @buster/database run db:introspect", diff --git a/scripts/new-package.ts b/scripts/new-package.ts new file mode 100755 index 000000000..4ccda5818 --- /dev/null +++ b/scripts/new-package.ts @@ -0,0 +1,233 @@ +#!/usr/bin/env bun + +import { mkdir, writeFile } from "fs/promises"; +import { join } from "path"; +import { createInterface } from "readline"; + +interface PackageConfig { + name: string; + directory: string; +} + +function createReadlineInterface() { + return createInterface({ + input: process.stdin, + output: process.stdout, + }); +} + +function askQuestion(rl: any, question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer: string) => { + resolve(answer); + }); + }); +} + +async function main() { + const rl = createReadlineInterface(); + + console.log("šŸš€ Creating a new package in the packages/ directory\n"); + + // Get package name from user + let packageName = ""; + while (!packageName) { + const answer = await askQuestion(rl, "What should the package be called? "); + const trimmed = answer.trim(); + + if (!trimmed) { + console.log("āŒ Package name is required"); + continue; + } + + if (!/^[a-z0-9-]+$/.test(trimmed)) { + console.log("āŒ Package name should only contain lowercase letters, numbers, and hyphens"); + continue; + } + + packageName = trimmed; + } + + const config: PackageConfig = { + name: packageName, + directory: join(process.cwd(), "packages", packageName), + }; + + // Check if directory already exists + try { + await mkdir(config.directory, { recursive: false }); + } catch (error) { + console.error(`āŒ Directory packages/${config.name} already exists!`); + rl.close(); + process.exit(1); + } + + console.log(`\nšŸ“ Creating package: @buster/${config.name}`); + console.log(`šŸ“ Location: packages/${config.name}\n`); + + // Confirm before proceeding + const shouldProceed = await askQuestion(rl, "Continue with package creation? (y/N) "); + rl.close(); + + if (shouldProceed.toLowerCase() !== 'y' && shouldProceed.toLowerCase() !== 'yes') { + console.log("āŒ Package creation cancelled"); + process.exit(0); + } + + await createPackageFiles(config); + + console.log("\nāœ… Package created successfully!"); + console.log(`\nšŸ“‹ Next steps:`); + console.log(` 1. cd packages/${config.name}`); + console.log(` 2. Update the env.d.ts file with your environment variables`); + console.log(` 3. Add your source code in the src/ directory`); + console.log(` 4. Run 'npm run build' to build the package`); +} + +async function createPackageFiles(config: PackageConfig) { + const { name, directory } = config; + + // Create src directory + await mkdir(join(directory, "src"), { recursive: true }); + await mkdir(join(directory, "scripts"), { recursive: true }); + await mkdir(join(directory, "tests"), { recursive: true }); + + // Create package.json + const packageJson = { + name: `@buster/${name}`, + version: "1.0.0", + type: "module", + main: "dist/index.js", + types: "dist/index.d.ts", + exports: { + ".": { + types: "./dist/index.d.ts", + default: "./dist/index.js", + }, + "./*": { + types: "./dist/*.d.ts", + default: "./dist/*.js", + }, + }, + scripts: { + prebuild: "node scripts/validate-env.js", + build: "tsc", + typecheck: "tsc --noEmit", + dev: "tsc --watch", + lint: "biome check", + test: "vitest run", + "test:watch": "vitest watch", + "test:coverage": "vitest run --coverage", + }, + dependencies: { + "@buster/typescript-config": "workspace:*", + "@buster/vitest-config": "workspace:*", + }, + devDependencies: { + typescript: "catalog:", + vitest: "catalog:", + }, + }; + + await writeFile( + join(directory, "package.json"), + JSON.stringify(packageJson, null, 2) + "\n" + ); + + // Create env.d.ts + const envDts = `declare global { + namespace NodeJS { + interface ProcessEnv { + NODE_ENV?: 'development' | 'production' | 'test'; + // Add your environment variables here + } + } +} + +export {}; +`; + + await writeFile(join(directory, "env.d.ts"), envDts); + + // Create tsconfig.json + const tsconfig = { + extends: "@buster/typescript-config/base.json", + compilerOptions: { + tsBuildInfoFile: "dist/.cache/tsbuildinfo.json", + outDir: "dist", + rootDir: "src", + }, + include: ["src/**/*", "env.d.ts"], + exclude: ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"], + }; + + await writeFile( + join(directory, "tsconfig.json"), + JSON.stringify(tsconfig, null, 2) + "\n" + ); + + // Create biome.json + const biomeJson = { + $schema: "https://biomejs.dev/schemas/1.9.4/schema.json", + extends: ["../../biome.json"], + files: { + include: ["src/**/*", "scripts/**/*"], + }, + }; + + await writeFile( + join(directory, "biome.json"), + JSON.stringify(biomeJson, null, 2) + "\n" + ); + + // Create basic index.ts file + const indexTs = `export * from './lib/index.js'; +`; + + await writeFile(join(directory, "src", "index.ts"), indexTs); + + // Create lib directory and basic lib file + await mkdir(join(directory, "src", "lib"), { recursive: true }); + const libIndex = `// Export your library functions here +export function example() { + return 'Hello from @buster/${name}!'; +} +`; + + await writeFile(join(directory, "src", "lib", "index.ts"), libIndex); + + // Create a basic validate-env.js script + const validateEnv = `// Validate environment variables here +console.log('Environment validation passed'); +`; + + await writeFile(join(directory, "scripts", "validate-env.js"), validateEnv); + + // Create a basic test file + const testFile = `import { describe, it, expect } from 'vitest'; +import { example } from '../src/lib/index.js'; + +describe('${name}', () => { + it('should work', () => { + expect(example()).toBe('Hello from @buster/${name}!'); + }); +}); +`; + + await writeFile(join(directory, "tests", "index.test.ts"), testFile); + + console.log("šŸ“„ Created package.json"); + console.log("šŸ“„ Created env.d.ts"); + console.log("šŸ“„ Created tsconfig.json"); + console.log("šŸ“„ Created biome.json"); + console.log("šŸ“„ Created src/index.ts"); + console.log("šŸ“„ Created src/lib/index.ts"); + console.log("šŸ“„ Created scripts/validate-env.js"); + console.log("šŸ“„ Created tests/index.test.ts"); +} + +// Run the CLI +main().catch((error) => { + console.error("āŒ Error creating package:", error); + process.exit(1); +});