diff --git a/.github/assets/og-base64.png b/.github/assets/og-base64.png new file mode 100644 index 0000000..780cdf8 Binary files /dev/null and b/.github/assets/og-base64.png differ diff --git a/.github/assets/og-home.png b/.github/assets/og-home.png new file mode 100644 index 0000000..b14bfd3 Binary files /dev/null and b/.github/assets/og-home.png differ diff --git a/.github/assets/og-invoice-generator.png b/.github/assets/og-invoice-generator.png new file mode 100644 index 0000000..0135cc1 Binary files /dev/null and b/.github/assets/og-invoice-generator.png differ diff --git a/.github/assets/og-json-formatter.png b/.github/assets/og-json-formatter.png new file mode 100644 index 0000000..d09f68b Binary files /dev/null and b/.github/assets/og-json-formatter.png differ diff --git a/.github/assets/og-jwt-decoder.png b/.github/assets/og-jwt-decoder.png new file mode 100644 index 0000000..555ea4e Binary files /dev/null and b/.github/assets/og-jwt-decoder.png differ diff --git a/README.md b/README.md index 2d8f212..bebcc38 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,67 @@ # toolbelt -[![License: MIT](https://img.shields.io/badge/License-MIT-00E5C7.svg)](./LICENSE) +[![Live](https://img.shields.io/badge/live-tools.codercops.com-00E5C7?labelColor=0A0B0F&style=flat)](https://tools.codercops.com) +[![License: MIT](https://img.shields.io/badge/license-MIT-00E5C7.svg?labelColor=0A0B0F)](./LICENSE) [![CI](https://github.com/codercops/toolbelt/actions/workflows/ci.yml/badge.svg)](https://github.com/codercops/toolbelt/actions/workflows/ci.yml) -[![Made by CODERCOPS](https://img.shields.io/badge/by-CODERCOPS-9E7BFF.svg)](https://www.codercops.com) +[![GitHub stars](https://img.shields.io/github/stars/codercops/toolbelt?labelColor=0A0B0F&color=9E7BFF&logo=github)](https://github.com/codercops/toolbelt/stargazers) +[![Last commit](https://img.shields.io/github/last-commit/codercops/toolbelt?labelColor=0A0B0F&color=8A93A8)](https://github.com/codercops/toolbelt/commits) +[![Made by CODERCOPS](https://img.shields.io/badge/by-CODERCOPS-9E7BFF.svg?labelColor=0A0B0F)](https://www.codercops.com) A developer's toolbelt: fast, privacy-first browser tools. Everything runs client-side, so there is no login, no upload, and no data leaves your tab. Live at **[tools.codercops.com](https://tools.codercops.com)**. +

+ + CODERCOPS Tools, sharp and focused developer tools that run entirely in your browser + +

+ +

+ Open the tools +  ·  + Star the repo +  ·  + Contribute +

+ +## Contents + +- [Tools](#tools) +- [Why it exists](#why-it-exists) +- [Stack](#stack) +- [Run locally](#run-locally) +- [Deploy your own](#deploy-your-own) +- [Add a tool](#add-a-tool) +- [Project layout](#project-layout) +- [Branching and releases](#branching-and-releases) +- [Contributing](#contributing) +- [Security](#security) +- [License](#license) + ## Tools -- **JSON formatter and validator** — format, minify, validate, collapsible tree view, and JSON to CSV / TypeScript / YAML / XML / query string. Large numbers are preserved losslessly. -- **JWT decoder and inspector** — decode header, payload, and claims; verify HS/RS/PS/ES signatures via WebCrypto; security audit; HS256/384/512 encoder. -- **Base64 encoder and decoder** — text, images, and files; URL-safe variant; data URIs; magic-byte detection; Base32/58/85 and hashes. -- **Invoice generator** — live preview, vector PDF export with Unicode currency support, a reusable sender profile, all saved locally. + + + + + + + + + +
+ JSON Formatter and Validator + + JWT Decoder and Inspector +
+ Base64 Encoder and Decoder + + Invoice Generator +
+ +- **[JSON formatter and validator](https://tools.codercops.com/json-formatter).** Format, minify, validate, collapsible tree view, and JSON to CSV / TypeScript / YAML / XML / query string. Large numbers are preserved losslessly. +- **[JWT decoder and inspector](https://tools.codercops.com/jwt-decoder).** Decode header, payload, and claims; verify HS/RS/PS/ES signatures via WebCrypto; security audit; HS256/384/512 encoder. +- **[Base64 encoder and decoder](https://tools.codercops.com/base64).** Text, images, and files; URL-safe variant; data URIs; magic-byte detection; Base32/58/85 and hashes. +- **[Invoice generator](https://tools.codercops.com/invoice-generator).** Live preview, vector PDF export with Unicode currency support, a reusable sender profile, all saved locally. ## Why it exists @@ -24,6 +74,7 @@ CODERCOPS builds production backends and full-stack apps. These are the small ut - WebCrypto for JWT signing and verification - jsPDF (dynamically imported) for invoice PDFs - `lossless-json` so JSON number precision is never dropped +- Per-page Open Graph images and schema.org structured data, generated from the tool registry - Installable PWA via a web manifest, with a nonce-based Content-Security-Policy ## Run locally @@ -45,14 +96,14 @@ npm run test # vitest unit tests (lib/) ## Deploy your own -The app deploys to any Node host. On Vercel, import the repo and it builds with zero config. There are no required environment variables. If you fork it, update the domain in `lib/tools.ts` (`SITE_URL`) and `app/robots.ts` so the sitemap and canonical URLs point at your host. +The app deploys to any Node host. On Vercel, import the repo and it builds with zero config. There are no required environment variables. If you fork it, update the domain in `lib/tools.ts` (`SITE_URL`) and `app/robots.ts` so the sitemap and canonical URLs point at your host, and update `GITHUB_REPO` in `lib/tools.ts` so the star-count link points at your fork. ## Add a tool -The tool set is a single typed registry in [`lib/tools.ts`](lib/tools.ts). The home grid, per-tool metadata, JSON-LD, hero, FAQ, CTA, sitemap, web manifest, and command palette all derive from it. To add a tool: +The tool set is a single typed registry in [`lib/tools.ts`](lib/tools.ts). The home grid, per-tool metadata, JSON-LD (SoftwareApplication, breadcrumb, FAQ), social share images, hero, FAQ, CTA, sitemap, web manifest, and command palette all derive from it. To add a tool: 1. Add an entry to the `TOOLS` array in `lib/tools.ts`. -2. Create `app/(tools)//page.tsx` (metadata plus your client component) and its `Client.tsx`. +2. Create `app/(tools)//page.tsx` (metadata plus your client component), its `Client.tsx`, and an `opengraph-image.tsx` (a few lines that pass the registry entry to the shared renderer). 3. Put pure logic in `lib/` and cover it with a test in `lib/__tests__/`. ## Project layout @@ -61,9 +112,11 @@ The tool set is a single typed registry in [`lib/tools.ts`](lib/tools.ts). The h app/ shell (layout, error/not-found boundaries, metadata routes) app/(tools)/ one route per tool, sharing a layout and ToolPageLayout components/shared/ header, theme, toasts, command palette, reusable UI -lib/ pure per-tool logic (parsing, crypto, PDF, conversions) — unit-tested +lib/ pure per-tool logic (parsing, crypto, PDF, conversions), unit-tested lib/tools.ts the tool registry (single source of truth) -public/fonts/ subset Noto Sans used by the invoice PDF (OFL, see OFL.txt) +lib/jsonLd.ts schema.org builders derived from the registry +lib/og.tsx the shared Open Graph card renderer +public/fonts/ subset Noto Sans used by the invoice PDF and OG cards (OFL, see OFL.txt) middleware.ts per-request CSP nonce ``` @@ -77,7 +130,7 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full flow. ## Contributing -Contributions are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and the [Code of Conduct](./CODE_OF_CONDUCT.md). In short: keep logic in `lib/`, add a test, and make sure `npm run lint && npm run test && npm run build` passes. +Contributions are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and the [Code of Conduct](./CODE_OF_CONDUCT.md). In short: keep logic in `lib/`, add a test, and make sure `npm run lint && npm run test && npm run build` passes. If a tool proves useful, a star helps other developers find it. ## Security @@ -86,3 +139,5 @@ Found a vulnerability? Please report it privately per [SECURITY.md](./SECURITY.m ## License MIT, see [LICENSE](./LICENSE). The bundled Noto Sans font subsets are under the SIL Open Font License 1.1 ([public/fonts/OFL.txt](public/fonts/OFL.txt)). + +The tool social cards in [`.github/assets/`](.github/assets) are generated from [`lib/og.tsx`](lib/og.tsx); regenerate them if a tool's name or copy changes. diff --git a/app/(tools)/base64/opengraph-image.tsx b/app/(tools)/base64/opengraph-image.tsx new file mode 100644 index 0000000..7eb1de4 --- /dev/null +++ b/app/(tools)/base64/opengraph-image.tsx @@ -0,0 +1,12 @@ +import { getTool } from "@/lib/tools"; +import { renderToolOgImage, OG_SIZE, OG_CONTENT_TYPE } from "@/lib/og"; + +const tool = getTool("base64")!; + +export const alt = tool.ogTitle; +export const size = OG_SIZE; +export const contentType = OG_CONTENT_TYPE; + +export default function Image() { + return renderToolOgImage(tool); +} diff --git a/app/(tools)/invoice-generator/opengraph-image.tsx b/app/(tools)/invoice-generator/opengraph-image.tsx new file mode 100644 index 0000000..9e1b53e --- /dev/null +++ b/app/(tools)/invoice-generator/opengraph-image.tsx @@ -0,0 +1,12 @@ +import { getTool } from "@/lib/tools"; +import { renderToolOgImage, OG_SIZE, OG_CONTENT_TYPE } from "@/lib/og"; + +const tool = getTool("invoice-generator")!; + +export const alt = tool.ogTitle; +export const size = OG_SIZE; +export const contentType = OG_CONTENT_TYPE; + +export default function Image() { + return renderToolOgImage(tool); +} diff --git a/app/(tools)/json-formatter/opengraph-image.tsx b/app/(tools)/json-formatter/opengraph-image.tsx new file mode 100644 index 0000000..fecc74e --- /dev/null +++ b/app/(tools)/json-formatter/opengraph-image.tsx @@ -0,0 +1,12 @@ +import { getTool } from "@/lib/tools"; +import { renderToolOgImage, OG_SIZE, OG_CONTENT_TYPE } from "@/lib/og"; + +const tool = getTool("json-formatter")!; + +export const alt = tool.ogTitle; +export const size = OG_SIZE; +export const contentType = OG_CONTENT_TYPE; + +export default function Image() { + return renderToolOgImage(tool); +} diff --git a/app/(tools)/jwt-decoder/opengraph-image.tsx b/app/(tools)/jwt-decoder/opengraph-image.tsx new file mode 100644 index 0000000..790bede --- /dev/null +++ b/app/(tools)/jwt-decoder/opengraph-image.tsx @@ -0,0 +1,12 @@ +import { getTool } from "@/lib/tools"; +import { renderToolOgImage, OG_SIZE, OG_CONTENT_TYPE } from "@/lib/og"; + +const tool = getTool("jwt-decoder")!; + +export const alt = tool.ogTitle; +export const size = OG_SIZE; +export const contentType = OG_CONTENT_TYPE; + +export default function Image() { + return renderToolOgImage(tool); +} diff --git a/app/layout.tsx b/app/layout.tsx index 16a6a06..185d7f5 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -51,9 +51,14 @@ export const metadata: Metadata = { type: "website", siteName: "CODERCOPS Tools", url: "https://tools.codercops.com", + locale: "en_US", + title: "CODERCOPS Tools — Developer Utilities", + description: + "Free, fast, privacy-first developer tools: JSON formatter, JWT decoder, Base64 encoder, and more. Runs entirely in your browser.", }, twitter: { card: "summary_large_image", + site: "@codercops", creator: "@codercops", }, }; diff --git a/app/opengraph-image.tsx b/app/opengraph-image.tsx index bf49007..f57cb25 100644 --- a/app/opengraph-image.tsx +++ b/app/opengraph-image.tsx @@ -1,61 +1,9 @@ -import { ImageResponse } from "next/og"; +import { renderHomeOgImage, OG_SIZE, OG_CONTENT_TYPE } from "@/lib/og"; export const alt = "CODERCOPS Tools — fast, privacy-first developer utilities"; -export const size = { width: 1200, height: 630 }; -export const contentType = "image/png"; +export const size = OG_SIZE; +export const contentType = OG_CONTENT_TYPE; export default function OpengraphImage() { - return new ImageResponse( - ( -
-
-
- {"{}"} -
-
CODERCOPS · TOOLS
-
- -
-
- Sharp, focused tools for shipping software -
-
- JSON formatter · JWT decoder · Base64 · Invoice generator. Runs entirely in your browser. -
-
- -
- {["#5AB5FF", "#9E7BFF", "#FFB547", "#A6F36B"].map((c) => ( -
- ))} -
-
- ), - { ...size } - ); + return renderHomeOgImage(); } diff --git a/app/page.tsx b/app/page.tsx index 88c2e8e..d8ab0a4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,11 +1,24 @@ +import type { Metadata } from "next"; import { ToolCard } from "@/components/shared/ToolCard"; import { CtaBanner } from "@/components/shared/CtaBanner"; import { Footer } from "@/components/shared/Footer"; import { TOOLS } from "@/lib/tools"; +import { homeJsonLd } from "@/lib/jsonLd"; + +// Only the canonical is set here; title, description, and OpenGraph are inherited +// from the root layout (Next merges metadata shallowly, so re-declaring openGraph +// would drop the root's siteName/locale/type from the homepage). +export const metadata: Metadata = { + alternates: { canonical: "/" }, +}; export default function HomePage() { return ( <> +