diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c34030e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +node_modules +.git +.gitignore +README.md +LICENSE +.vscode +.idea +.env +.editorconfig +coverage* +dist +*.log +blog.sqlite +Dockerfile* +docker-compose* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 87e5610..08585d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies +blog.sqlite /node_modules /.pnp .pnp.js @@ -25,6 +26,7 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env.local .env.development.local .env.test.local @@ -39,4 +41,4 @@ yarn-error.log* **/*.tgz **/*.log package-lock.json -**/*.bun \ No newline at end of file +**/*.bun diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..42f1112 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# Build stage +FROM oven/bun:1 AS builder +WORKDIR /usr/src/app + +# Install dependencies +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +# Copy source code and content +COPY . . + +# Copy database initialization script +COPY init-db.ts ./ + +# Initialize database using init-db.ts script +# This script handles draft-aware post verification and stability detection +ENV NODE_ENV=build +RUN bun run init-db.ts + +# Final production stage +FROM oven/bun:1 +WORKDIR /usr/src/app + +# Copy dependencies +COPY --from=builder /usr/src/app/node_modules ./node_modules +COPY --from=builder /usr/src/app/package.json ./ + +# Copy source code (for runtime transpilation) +COPY --from=builder /usr/src/app/src ./src +COPY --from=builder /usr/src/app/content ./content +COPY --from=builder /usr/src/app/bun_plugins ./bun_plugins +COPY --from=builder /usr/src/app/index.tsx . +COPY --from=builder /usr/src/app/tsconfig.json . + +# Copy the initialized database for efficient layer caching +COPY --from=builder /usr/src/app/blog.sqlite ./ + +# Fix ownership for bun user (needed for runtime operations) +RUN chown -R bun:bun /usr/src/app + +# Switch to bun user +USER bun + +# Expose the default port +EXPOSE 3000/tcp + +# Production environment (onStartup-post-importer won't run since DB exists) +ENV NODE_ENV=production + +# Run the application directly (Bun handles TSX transpilation at runtime) +ENTRYPOINT [ "bun", "run", "index.tsx" ] diff --git a/bun_plugins/onStartup-post-importer.ts b/bun_plugins/onStartup-post-importer.ts index 9d407b4..ffe9fca 100644 --- a/bun_plugins/onStartup-post-importer.ts +++ b/bun_plugins/onStartup-post-importer.ts @@ -14,6 +14,11 @@ import { dbConnection } from "../src/db"; const {data, content } = matter(await Bun.file(`./content/${file}`).text()); const route = `/${file.replace(/\.md$/, "")}`; + // Omit draft blog posts from database initialization in production + if (process.env.NODE_ENV === 'build' && data.draft) { + continue; + } + // Remove the title from content if it matches the frontmatter title to avoid duplicate H1s let processedContent = content; if (data.title) { diff --git a/init-db.ts b/init-db.ts new file mode 100644 index 0000000..9316ba9 --- /dev/null +++ b/init-db.ts @@ -0,0 +1,85 @@ +const { Database } = require("bun:sqlite"); + +async function waitForDatabase() { + const serverProcess = Bun.spawn(["bun", "run", "index.tsx"], { + env: { ...process.env, NODE_ENV: "development" }, + stdout: "pipe", + stderr: "pipe" + }); + + // Count expected markdown files - this gives us total potential posts + const files = []; + for await (const file of new Bun.Glob("**/*.md").scan("./content")) { + files.push(file); + } + const totalMarkdownFiles = files.length; + console.log(`Found ${totalMarkdownFiles} markdown files to process`); + + const maxWait = 30000; // 30 seconds total timeout + const checkInterval = 100; // Check every 100ms + let waited = 0; + let stableCount = 0; + let previousCount = -1; + + while (waited < maxWait) { + if (await Bun.file("blog.sqlite").exists()) { + try { + const db = new Database("blog.sqlite", { readonly: true }); + + // Count only published posts (routes ending without .md) + // This matches the logic used in the actual application queries + const result = db.query("SELECT COUNT(*) as count FROM posts WHERE path NOT LIKE '%.md'").get(); + const publishedPosts = result.count; + db.close(); + + if (publishedPosts === previousCount) { + stableCount++; + } else { + stableCount = 0; + previousCount = publishedPosts; + console.log(`Importing progress: ${publishedPosts}/${totalMarkdownFiles} published posts`); + } + + // If the count has been stable for 10 consecutive checks (1 second), consider import complete + // We check against available posts, not total markdown files, since some may be drafts + if (stableCount >= 10 && publishedPosts >= 1) { + console.log(`Import complete: ${publishedPosts} published posts ready`); + break; + } + } catch (e) { + // Database might still be writing or locked + } + } + + await new Promise(resolve => setTimeout(resolve, checkInterval)); + waited += checkInterval; + } + + // Check if we succeeded + if (!(await Bun.file("blog.sqlite").exists())) { + console.error("Database file was not created"); + await serverProcess.kill(); + process.exit(1); + } + + // Final verification - check published posts count + const db = new Database("blog.sqlite", { readonly: true }); + const result = db.query("SELECT COUNT(*) as count FROM posts WHERE path NOT LIKE '%.md'").get(); + const finalPublishedPosts = result.count; + + // Also get total posts for informational purposes + const totalResult = db.query("SELECT COUNT(*) as count FROM posts").get(); + const totalPosts = totalResult.count; + db.close(); + + if (finalPublishedPosts < 1) { + console.error(`No published posts found! Total: ${totalPosts}, Published: ${finalPublishedPosts}`); + await serverProcess.kill(); + process.exit(1); + } + + await serverProcess.kill(); + console.log(`Database initialization complete: ${totalPosts} total posts (${finalPublishedPosts} published)`); +} + +waitForDatabase(); \ No newline at end of file diff --git a/package.json b/package.json index 796fddc..a0c2966 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,11 @@ "name": "portfolio", "version": "1.0.50", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "dev": "bun run --watch ./index.tsx" + "dev": "bun run --watch ./index.tsx", + "build": "bun run docker:build", + "docker:build": "docker build --no-cache -t blog:latest .", + "docker:run": "docker run -d -p 3000:3000 --name blog blog:latest", + "docker:stop": "docker stop blog && docker rm blog" }, "dependencies": { "gray-matter": "^4.0.3", @@ -15,4 +18,4 @@ "@types/react-dom": "^19.2.3" }, "module": "src/index.js" -} \ No newline at end of file +}