Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6801d3b
Create Astro remote loader for D1
just-be-dev Feb 21, 2026
8315682
Build @just-be/micro CLI package
just-be-dev Feb 21, 2026
fd24b09
Add micro RSS feed at /micro/rss.xml
just-be-dev Feb 21, 2026
0b88b97
Create micro blog page with infinite scroll
just-be-dev Feb 21, 2026
6a8e2cb
Add URL manifest support for micro posts with 'm' prefix codes
just-be-dev Feb 21, 2026
4c23e13
feat(micro): integrate D1 database for micro posts persistence
just-be-dev Feb 21, 2026
43fe1d0
feat(micro): replace infinite scroll with cursor-based pagination
just-be-dev Feb 22, 2026
5f55520
fix(micro): address code review issues
just-be-dev Feb 23, 2026
4246494
fix(micro): add migrations_dir and mise task for micro CLI
just-be-dev Feb 23, 2026
55e181d
refactor(micro): move D1 schema, migrations, and drizzle config into …
just-be-dev Feb 23, 2026
9a3409a
refactor(micro): replace custom D1 HTTP adapter with wrangler platfor…
just-be-dev Feb 23, 2026
eab20d0
fix(micro): fix build failure when D1 is unavailable in CI
just-be-dev Feb 23, 2026
ab0ae61
chore(micro): add deploy:micro task for remote D1 migrations
just-be-dev Feb 23, 2026
894982f
refactor(micro): replace D1/Drizzle with R2 JSONL and HTTP API
just-be-dev Feb 23, 2026
bc6f316
fix(micro): update gen-url-manifest to read posts from R2 instead of D1
just-be-dev Feb 23, 2026
6b0f883
fix(micro): make page live from R2, fix anchor bug, remove stale code
just-be-dev Feb 23, 2026
f1d8530
feat(micro): replace MICRO_SITE_URL with --branch CLI flag
just-be-dev Feb 23, 2026
790f0bf
fix(types): add @cloudflare/workers-types reference to env.d.ts
just-be-dev Mar 18, 2026
08ecd72
chore(micro): remove micro CLI package
just-be-dev Mar 18, 2026
af89319
feat(micro): restore micro CLI with raw stdin/stdout approach
just-be-dev Mar 18, 2026
0353c87
Create Astro remote loader for D1
just-be-dev Feb 21, 2026
4f8c877
Build @just-be/micro CLI package
just-be-dev Feb 21, 2026
7d40396
Add micro RSS feed at /micro/rss.xml
just-be-dev Feb 21, 2026
3faaaa6
Create micro blog page with infinite scroll
just-be-dev Feb 21, 2026
2dd62f8
Add URL manifest support for micro posts with 'm' prefix codes
just-be-dev Feb 21, 2026
e721f01
feat(micro): integrate D1 database for micro posts persistence
just-be-dev Feb 21, 2026
caf6e12
feat(micro): replace infinite scroll with cursor-based pagination
just-be-dev Feb 22, 2026
924aab6
fix(micro): address code review issues
just-be-dev Feb 23, 2026
ae5f3af
fix(micro): add migrations_dir and mise task for micro CLI
just-be-dev Feb 23, 2026
2770e6d
refactor(micro): move D1 schema, migrations, and drizzle config into …
just-be-dev Feb 23, 2026
b22e6df
chore(micro): add deploy:micro task for remote D1 migrations
just-be-dev Feb 23, 2026
48bb178
refactor(micro): replace D1/Drizzle with R2 JSONL and HTTP API
just-be-dev Feb 23, 2026
fca933e
fix(micro): update gen-url-manifest to read posts from R2 instead of D1
just-be-dev Feb 23, 2026
9f22632
fix(micro): make page live from R2, fix anchor bug, remove stale code
just-be-dev Feb 23, 2026
3377088
feat(micro): replace MICRO_SITE_URL with --branch CLI flag
just-be-dev Feb 23, 2026
a4ff261
chore(micro): remove micro CLI package
just-be-dev Mar 18, 2026
077f84f
fix(types): add @cloudflare/workers-types reference to env.d.ts
just-be-dev Mar 18, 2026
b38fe90
feat(micro): migrate storage from R2 JSONL to D1
just-be-dev Mar 18, 2026
8ce6790
Merge origin/add-micro-blog-section, keeping D1 storage
just-be-dev Mar 18, 2026
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
458 changes: 215 additions & 243 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@ minimumReleaseAgeExcludes = [
"@astrojs/rss",
"@astrojs/sitemap",
"@astrojs/underscore-redirects",
"workerd",
"@cloudflare/workerd-darwin-arm64",
"@cloudflare/workerd-darwin-64",
"@cloudflare/workerd-linux-arm64",
"@cloudflare/workerd-linux-64",
"@cloudflare/workerd-windows-64",
]
7 changes: 7 additions & 0 deletions migrations/0001_create_micro_posts.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS micro_posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S.000Z', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S.000Z', 'now')),
syndicated_to TEXT NOT NULL DEFAULT '[]'
);
6 changes: 6 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ description = "Deploy wildcard subdomain service"
run = "uv run --with openai-whisper python scripts/transcribe.py"
description = "Generate word-level transcript from audio/video"

[tasks.micro]
run = "bun packages/micro/index.ts"
description = "Micro blog CLI (list posts, create, or delete)"
raw = true

[tasks."changelog:entry"]
run = "bun scripts/changelog-entry.ts"
description = "Generate changelog entry for a PR"
Expand All @@ -92,3 +97,4 @@ description = "Assemble changelog fragments into CHANGELOG.md"
[tasks."release-notes"]
run = "bun scripts/release-notes.ts"
description = "Preview release notes for a package version"

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,27 @@
"@astrojs/mdx": "5.0.3",
"@astrojs/rss": "4.0.18",
"@astrojs/sitemap": "3.7.2",
"@atproto/api": "^0.15.3",
"@iconify-json/pixel": "^1.2.1",
"@types/p5": "^1.7.6",
"@unocss/reset": "^66.6.0",
"@webtui/css": "^0.1.5",
"astro": "6.1.4",
"beautiful-mermaid": "^1.0.0",
"js-yaml": "^4.1.1",
"marked": "^17.0.1",
"mdast-util-mdx-jsx": "^3.2.0",
"p5": "^1.11.3",
"remark-mdc": "^3.9.0",
"sharp": "^0.34.3",
"twitter-api-v2": "^1.17.3",
"typescript": "^6.0.2",
"unist-util-visit": "^5.0.0",
"zod": "^4.1.13"
},
"devDependencies": {
"@types/bun": "^1.3.6",
"@types/js-yaml": "^4.0.9",
"@unocss/astro": "66.6.0",
"@unocss/preset-icons": "66.6.0",
"oxfmt": "^0.43.0",
Expand Down
61 changes: 61 additions & 0 deletions packages/micro/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bun

import { deletePost, list } from "./src/browse.ts";
import { post } from "./src/post.ts";

const PRODUCTION_URL = "https://just-be.dev";
const WORKER_NAME = "just-be-dev";
const WORKERS_DEV_SUBDOMAIN = "just-be";

function siteUrlFromBranch(branch: string | undefined): string {
if (!branch || branch === "main") return PRODUCTION_URL;
const sanitized = branch
.toLowerCase()
.replace(/[^a-z0-9-]+/g, "-")
.replace(/^-+|-+$/g, "");
return `https://${sanitized}-${WORKER_NAME}.${WORKERS_DEV_SUBDOMAIN}.workers.dev`;
}

const rawArgs = process.argv.slice(2);
let branch: string | undefined;
const args: string[] = [];

for (let i = 0; i < rawArgs.length; i++) {
if ((rawArgs[i] === "--branch" || rawArgs[i] === "-b") && i + 1 < rawArgs.length) {
branch = rawArgs[++i];
} else {
args.push(rawArgs[i]);
}
}

const command = args[0];
const siteUrl = siteUrlFromBranch(branch);

async function main() {
if (!command || command === "list") {
await list(siteUrl);
} else if (command === "post") {
await post(args[1], siteUrl);
} else if (command === "delete") {
const id = Number(args[1]);
if (!args[1] || !Number.isFinite(id)) {
console.error("Usage: micro delete <id>");
process.exit(1);
}
await deletePost(id, siteUrl);
} else {
console.error(`Unknown command: ${command}`);
console.log("Usage:");
console.log(" micro [list] [--branch <name>] - List all posts");
console.log(
' micro post [--branch <name>] ["text"] - Create a post (reads stdin if no text)',
);
console.log(" micro delete [--branch <name>] <id> - Delete a post");
process.exit(1);
}
}

main().catch((error) => {
console.error("Error:", error.message);
process.exit(1);
});
33 changes: 33 additions & 0 deletions packages/micro/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@just-be/micro",
"version": "0.1.0",
"description": "CLI tool for creating and managing micro blog posts",
"keywords": [
"cli",
"microblog"
],
"license": "MIT",
"author": "Justin Bennett",
"repository": {
"type": "git",
"url": "git+https://github.com/just-be-dev/just-be.dev.git",
"directory": "packages/micro"
},
"bin": {
"micro": "index.ts"
},
"files": [
"index.ts",
"src"
],
"type": "module",
"exports": {
".": "./index.ts"
},
"publishConfig": {
"access": "public"
},
"engines": {
"bun": ">=1.0.0"
}
}
56 changes: 56 additions & 0 deletions packages/micro/src/browse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
interface Post {
id: number;
content: string;
createdAt: string;
syndicatedTo: Array<{ platform: string; id: string; url: string }>;
}

function getSecret(): string {
const secret = process.env.MICRO_SECRET;
if (!secret) {
console.error("Error: MICRO_SECRET environment variable not set");
process.exit(1);
}
return secret;
}

export async function list(siteUrl: string) {
const secret = getSecret();

const res = await fetch(`${siteUrl}/micro`, {
headers: {
Authorization: `Bearer ${secret}`,
Accept: "application/json",
},
});

if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);

const posts = (await res.json()) as Post[];

if (posts.length === 0) {
console.log("no posts yet");
return;
}

for (const post of posts) {
const date = new Date(post.createdAt).toLocaleDateString();
console.log(`[${post.id}] ${date} ${post.content}`);
if (post.syndicatedTo.length > 0) {
console.log(` syndicated: ${post.syndicatedTo.map((s) => s.platform).join(", ")}`);
}
}
}

export async function deletePost(id: number, siteUrl: string) {
const secret = getSecret();

const res = await fetch(`${siteUrl}/micro/${id}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${secret}` },
});

if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);

console.log(`deleted #${id}`);
}
62 changes: 62 additions & 0 deletions packages/micro/src/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const MAX_LENGTH = 280;

function getSecret(): string {
const secret = process.env.MICRO_SECRET;
if (!secret) {
console.error("Error: MICRO_SECRET environment variable not set");
process.exit(1);
}
return secret;
}

export async function post(content: string | undefined, siteUrl: string) {
let postContent = content;

if (!postContent) {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) {
chunks.push(chunk as Buffer);
}
postContent = Buffer.concat(chunks).toString("utf8").trim();
}

if (!postContent) {
console.error("Error: no content provided (pass as argument or pipe via stdin)");
process.exit(1);
}

if (postContent.length > MAX_LENGTH) {
console.error(`Error: too long (${postContent.length}/${MAX_LENGTH} characters)`);
process.exit(1);
}

const secret = getSecret();

const res = await fetch(`${siteUrl}/micro`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${secret}`,
},
body: JSON.stringify({ content: postContent }),
});

if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}

const { post: newPost, syndication } = (await res.json()) as {
post: { id: number; content: string; createdAt: string };
syndication: Array<{ platform: string; success: boolean; url?: string; error?: string }>;
};

console.log(`posted #${newPost.id}`);

for (const s of syndication) {
if (s.success) {
console.log(` ${s.platform}: ${s.url}`);
} else {
console.error(` ${s.platform}: ${s.error}`);
}
}
}
2 changes: 2 additions & 0 deletions scripts/gen-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ function inferKind(dir: string): Kind {
return "P";
case "talks":
return "T";
case "micro":
return "M";
default:
throw new Error(`Unknown content directory: ${dir}`);
}
Expand Down
Loading
Loading