ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
1import * as esbuild from 'esbuild';
2import * as fs from 'fs';
3import * as path from 'path';
4import { fileURLToPath } from 'url';
5import postcss from 'postcss';
6import tailwindcss from 'tailwindcss';
7import autoprefixer from 'autoprefixer';
8
9const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
11const watch = process.argv.includes('--watch');
12const isProd = process.argv.includes('--prod') || process.env.NODE_ENV === 'production';
13const mode = isProd ? 'production' : 'development';
14
15// Environment-specific configuration
16const ATLAST_API_URL = mode === 'production'
17 ? 'https://atlast.byarielm.fyi'
18 : 'http://127.0.0.1:8888';
19
20console.log(`🌍 Building for ${mode} mode`);
21console.log(`🔗 API URL: ${ATLAST_API_URL}`);
22
23// Clean dist directory
24const distBaseDir = path.join(__dirname, 'dist');
25if (fs.existsSync(distBaseDir)) {
26 fs.rmSync(distBaseDir, { recursive: true });
27}
28fs.mkdirSync(distBaseDir, { recursive: true });
29
30// Build configuration base
31const buildConfigBase = {
32 bundle: true,
33 minify: !watch,
34 sourcemap: watch ? 'inline' : false,
35 target: 'es2020',
36 format: 'esm',
37 define: {
38 '__ATLAST_API_URL__': JSON.stringify(ATLAST_API_URL),
39 '__BUILD_MODE__': JSON.stringify(mode),
40 },
41 // Include webextension-polyfill in the bundle
42 external: [],
43};
44
45// Build scripts for a specific browser
46function getScripts(browser) {
47 const distDir = path.join(distBaseDir, browser);
48 return [
49 {
50 ...buildConfigBase,
51 entryPoints: ['src/content/index.ts'],
52 outfile: path.join(distDir, 'content', 'index.js'),
53 },
54 {
55 ...buildConfigBase,
56 entryPoints: ['src/background/service-worker.ts'],
57 outfile: path.join(distDir, 'background', 'service-worker.js'),
58 },
59 {
60 ...buildConfigBase,
61 entryPoints: ['src/popup/popup.ts'],
62 outfile: path.join(distDir, 'popup', 'popup.js'),
63 },
64 ];
65}
66
67// Build function
68async function build() {
69 try {
70 console.log('🔨 Building extension for Chrome and Firefox...');
71
72 const browsers = ['chrome', 'firefox'];
73
74 for (const browser of browsers) {
75 console.log(`\n📦 Building ${browser} version...`);
76 const scripts = getScripts(browser);
77
78 // Build all scripts
79 for (const config of scripts) {
80 if (watch) {
81 const ctx = await esbuild.context(config);
82 await ctx.watch();
83 console.log(`👀 Watching ${browser}/${path.basename(config.entryPoints[0])}...`);
84 } else {
85 await esbuild.build(config);
86 console.log(`✅ Built ${browser}/${path.basename(config.entryPoints[0])}`);
87 }
88 }
89
90 // Copy static files
91 copyStaticFiles(browser);
92
93 // Process CSS with Tailwind
94 await processCSS(browser);
95 }
96
97 if (!watch) {
98 console.log('\n✨ Build complete for both browsers!');
99 }
100 } catch (error) {
101 console.error('❌ Build failed:', error);
102 process.exit(1);
103 }
104}
105
106// Process CSS with PostCSS (Tailwind + Autoprefixer)
107async function processCSS(browser) {
108 const cssPath = path.join(__dirname, 'src/popup/popup.css');
109 const distDir = path.join(distBaseDir, browser);
110 const outputPath = path.join(distDir, 'popup/popup.css');
111
112 const css = fs.readFileSync(cssPath, 'utf8');
113
114 // Import cssnano dynamically for production minification
115 const plugins = [tailwindcss, autoprefixer];
116 if (isProd) {
117 const cssnano = (await import('cssnano')).default;
118 plugins.push(cssnano);
119 }
120
121 const result = await postcss(plugins).process(css, {
122 from: cssPath,
123 to: outputPath,
124 });
125
126 // Create directory if it doesn't exist
127 const destDir = path.dirname(outputPath);
128 if (!fs.existsSync(destDir)) {
129 fs.mkdirSync(destDir, { recursive: true });
130 }
131
132 fs.writeFileSync(outputPath, result.css);
133 console.log('🎨 Processed CSS with Tailwind');
134}
135
136// Copy static files
137function copyStaticFiles(browser) {
138 const distDir = path.join(distBaseDir, browser);
139
140 const filesToCopy = [
141 { from: `manifest.${browser}.json`, to: 'manifest.json', fallback: 'manifest.json' },
142 { from: 'src/popup/popup.html', to: 'popup/popup.html' },
143 ];
144
145 for (const file of filesToCopy) {
146 // Try to use browser-specific file first, fall back to default
147 let srcPath = path.join(__dirname, file.from);
148 if (file.fallback && !fs.existsSync(srcPath)) {
149 srcPath = path.join(__dirname, file.fallback);
150 }
151 const destPath = path.join(distDir, file.to);
152
153 // Create directory if it doesn't exist
154 const destDir = path.dirname(destPath);
155 if (!fs.existsSync(destDir)) {
156 fs.mkdirSync(destDir, { recursive: true });
157 }
158
159 fs.copyFileSync(srcPath, destPath);
160 }
161
162 // Create placeholder icons (TODO: replace with actual icons)
163 const assetsDir = path.join(distDir, 'assets');
164 if (!fs.existsSync(assetsDir)) {
165 fs.mkdirSync(assetsDir, { recursive: true });
166 }
167
168 // Create simple text files as placeholder icons
169 const sizes = [16, 48, 128];
170 for (const size of sizes) {
171 const iconPath = path.join(assetsDir, `icon-${size}.png`);
172 if (!fs.existsSync(iconPath)) {
173 // TODO: Generate actual PNG icons
174 fs.writeFileSync(iconPath, `Placeholder ${size}x${size} icon`);
175 }
176 }
177
178 console.log('📋 Copied static files');
179}
180
181// Run build
182build();