Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
PERSONAL_ACCESS_TOKEN="..."
OPENAI_API_KEY="..."
PUBLIC_IOS_UDID_CERTIFICATE_LINK="https://example.com/your-trust-certificate.crt"
IOS_UDID_PROFILE_SIGNING_CERT_PEM="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
IOS_UDID_PROFILE_SIGNING_KEY_PEM="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
IOS_UDID_PROFILE_SIGNING_CHAIN_PEM=""
2 changes: 1 addition & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export default defineConfig({
locales: localeNames,
defaultLocale,
redirectDefaultLocale: true,
exclude: ['pages/**/*.json.ts'],
exclude: ['pages/**/*.json.ts', 'pages/api/**/*.ts'],
}),
sitemap({
i18n: {
Expand Down
6 changes: 6 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 75 additions & 0 deletions docs/ios-udid-finder-certificate-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# iOS UDID Finder certificate setup

This runbook explains how to finish the optional certificate-backed configuration for the `/tools/ios-udid-finder/` flow.

The tool works without extra configuration, but if you want:

- a public "Download trust certificate" button on the page
- a signed `.mobileconfig` response instead of a plain profile payload

then fill in the environment variables described below.

## What the page expects

The implementation uses these variables:

- `PUBLIC_IOS_UDID_CERTIFICATE_LINK`
- `IOS_UDID_PROFILE_SIGNING_CERT_PEM`
- `IOS_UDID_PROFILE_SIGNING_KEY_PEM`
- `IOS_UDID_PROFILE_SIGNING_CHAIN_PEM` (optional)

`PUBLIC_IOS_UDID_CERTIFICATE_LINK` is only used for the public button shown on the page.

The `IOS_UDID_PROFILE_SIGNING_*` values are server-only and are used to sign the downloaded `.mobileconfig`.

## Option 1: use an existing TLS certificate

If you already terminate HTTPS with a certificate you control and can export:

1. Export the certificate in PEM format.
2. Export the matching private key in PEM format.
3. If your provider gives you an intermediate certificate chain, export that in PEM format too.
4. Publish the public certificate file at a stable HTTPS URL.
5. Set:
- `PUBLIC_IOS_UDID_CERTIFICATE_LINK` to the public certificate URL
- `IOS_UDID_PROFILE_SIGNING_CERT_PEM` to the PEM certificate contents
- `IOS_UDID_PROFILE_SIGNING_KEY_PEM` to the PEM private key contents
- `IOS_UDID_PROFILE_SIGNING_CHAIN_PEM` to the concatenated intermediate certificates, if needed

## Option 2: export from Let’s Encrypt or another host

If your host manages certificates for you:

1. Find the PEM certificate file used by your HTTPS endpoint.
2. Find the matching PEM private key.
3. Copy the full chain PEM if one exists.
4. Upload the public certificate to a stable HTTPS location if you want the page button.
5. Set the environment variables with those PEM values.

## Example `.env`

```dotenv
PUBLIC_IOS_UDID_CERTIFICATE_LINK="https://example.com/certs/udid-finder.crt"
IOS_UDID_PROFILE_SIGNING_CERT_PEM="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
IOS_UDID_PROFILE_SIGNING_KEY_PEM="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
IOS_UDID_PROFILE_SIGNING_CHAIN_PEM="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
```

Keep the PEM values on the server only. Do not expose them in public client code and do not commit them to Git.

## How to verify the page

1. Start the site locally with the environment variables set.
2. Open `/tools/ios-udid-finder/`.
3. Confirm the page shows the "Download trust certificate" button if `PUBLIC_IOS_UDID_CERTIFICATE_LINK` is present.
4. Download the profile from `/api/tools/ios-udid-finder/profile`.
5. Confirm the response header is:
- `Content-Type: application/x-apple-aspen-config`
6. Install the profile on a real iPhone or iPad. The device must be able to reach the host serving `/api/tools/ios-udid-finder/profile` and the optional `PUBLIC_IOS_UDID_CERTIFICATE_LINK`, so use a LAN-accessible hostname or IP address, or a tunnel such as ngrok or localhost.run, when testing from a local machine.
7. Confirm the device lands on `/tools/ios-udid-finder/result/` with the UDID and device details rendered.

## Notes

- The callback endpoint extracts the plist payload from the raw iOS response body server-side.
- The result page is intentionally `noindex` because it can contain device identifiers.
- If you want to hard-wire a specific certificate download link, send me that URL and I can drop it into the environment config for deployment.
1 change: 1 addition & 0 deletions messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"flexible_payment_terms": "Flexible Zahlungsbedingungen",
"flutter_power_approximately_1_of_apps_on_google_play_store": "Flutter macht etwa 1% der Apps im Google Play Store aus.",
"footer": "Fußzeile",
"free_mobile_tools": "Kostenlose Mobile-Tools",
"footer_tagline_part1": "Versorgen Sie Ihre Capacitor-Apps mit",
"footer_tagline_part2": "über 90+produktionsbereite Plugins",
"for_the_pay_as_you_go_plan": "für den Pay-as-you-go-Plan",
Expand Down
1 change: 1 addition & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,7 @@
"flexible_payment_terms": "Flexible Payment Terms",
"flutter_power_approximately_1_of_apps_on_google_play_store": "Flutter power approximately $1% of apps on Google Play Store",
"footer": "Footer",
"free_mobile_tools": "Free Mobile Tools",
"footer_tagline_part1": "Power your Capacitor apps with",
"footer_tagline_part2": "over 90+production-ready plugins",
"for_the_pay_as_you_go_plan": "for any plan with credit-based usage",
Expand Down
1 change: 1 addition & 0 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"flexible_payment_terms": "Términos de Pago Flexibles",
"flutter_power_approximately_1_of_apps_on_google_play_store": "Flutter potencia aproximadamente el 1% de las aplicaciones en Google Play Store.",
"footer": "Pie de página",
"free_mobile_tools": "Herramientas móviles gratuitas",
"footer_tagline_part1": "Alimenta tus aplicaciones de Capacitor con",
"footer_tagline_part2": "más de 90+complementos listos para producción",
"for_the_pay_as_you_go_plan": "para el plan de pago por uso",
Expand Down
1 change: 1 addition & 0 deletions messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"flexible_payment_terms": "Conditions de Paiement Flexibles",
"flutter_power_approximately_1_of_apps_on_google_play_store": "Flutter alimente environ $1% des applications sur le Google Play Store",
"footer": "Pied de page",
"free_mobile_tools": "Outils mobiles gratuits",
"footer_tagline_part1": "Alimentez vos applications Capacitor avec",
"footer_tagline_part2": "plus de 90+plugins prêts à la production",
"for_the_pay_as_you_go_plan": "pour le plan de paiement à l'utilisation",
Expand Down
1 change: 1 addition & 0 deletions messages/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"flexible_payment_terms": "Syarat Pembayaran Fleksibel",
"flutter_power_approximately_1_of_apps_on_google_play_store": "Flutter menggerakkan sekitar $1% dari aplikasi di Google Play Store",
"footer": "Footer",
"free_mobile_tools": "Alat Seluler Gratis",
"footer_tagline_part1": "Tenagai aplikasi Capacitor Anda dengan",
"footer_tagline_part2": "lebih dari 90+plugin siap produksi",
"for_the_pay_as_you_go_plan": "untuk rencana Bayar sesuai penggunaan",
Expand Down
1 change: 1 addition & 0 deletions messages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"flexible_payment_terms": "Termini di Pagamento Flessibili",
"flutter_power_approximately_1_of_apps_on_google_play_store": "Flutter alimenta aproximadamente el 1% de las aplicaciones en Google Play Store.",
"footer": "Pié de página",
"free_mobile_tools": "Strumenti mobili gratuiti",
"footer_tagline_part1": "Alimenta le tue app Capacitor con",
"footer_tagline_part2": "oltre 90+plugin pronti per la produzione",
"for_the_pay_as_you_go_plan": "per il piano Pay-as-you-go",
Expand Down
1 change: 1 addition & 0 deletions messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"flexible_payment_terms": "柔軟な支払い条件",
"flutter_power_approximately_1_of_apps_on_google_play_store": "FlutterはGoogle Playストアにあるアプリの約1%を占めています。",
"footer": "フッター",
"free_mobile_tools": "無料モバイルツール",
"footer_tagline_part1": "あなたのCapacitorアプリをパワーアップさせてください",
"footer_tagline_part2": "70以上の本番用プラグインが利用可能です",
"for_the_pay_as_you_go_plan": "従量課金プラン用",
Expand Down
1 change: 1 addition & 0 deletions messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"flexible_payment_terms": "유연한 결제 조건",
"flutter_power_approximately_1_of_apps_on_google_play_store": "Google Play 스토어 앱의 약 $1%를 Flutter가 지원합니다",
"footer": "푸터",
"free_mobile_tools": "무료 모바일 도구",
"footer_tagline_part1": "캐패시터 앱을 구동하세요",
"footer_tagline_part2": "70개 이상의 제품 준비용 플러그인",
"for_the_pay_as_you_go_plan": "종량제 요금제의 경우",
Expand Down
1 change: 1 addition & 0 deletions messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"flexible_payment_terms": "灵活的付款条件",
"flutter_power_approximately_1_of_apps_on_google_play_store": "Flutter大约为Google Play Store上的1%的应用程序提供动力",
"footer": "页脚",
"free_mobile_tools": "免费移动工具",
"footer_tagline_part1": "为您的Capacitor应用程序供电",
"footer_tagline_part2": "超过70+个生产就绪的插件",
"for_the_pay_as_you_go_plan": "按使用付费计划",
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"github-slugger": "^2.0.0",
"glob": "^13.0.6",
"mermaid": "^11.13.0",
"node-forge": "^1.4.0",
"openai": "^6.27.0",
"schema-dts": "^1.1.5",
"sitemap": "^9.0.1",
Expand All @@ -76,6 +77,7 @@
"@types/js-yaml": "^4.0.9",
"@types/marked": "^6.0.0",
"@types/node": "^25.5.0",
"@types/node-forge": "^1.3.14",
"@types/semver": "^7.7.1",
"@types/toastify-js": "^1.12.4",
"astro-font": "^1.1.0",
Expand Down
4 changes: 4 additions & 0 deletions src/components/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ const navigation: Record<string, NavigationItem[]> = {
name: m.semver_tester({}, { locale: Astro.locals.locale }),
href: getRelativeLocaleUrl(Astro.locals.locale, 'semver_tester'),
},
{
name: m.free_mobile_tools({}, { locale: Astro.locals.locale }),
href: getRelativeLocaleUrl(Astro.locals.locale, 'tools'),
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{
name: 'Security Scanner',
href: getRelativeLocaleUrl(Astro.locals.locale, 'security-scanner'),
Expand Down
46 changes: 46 additions & 0 deletions src/components/tools/ToolCatalog.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
import { getRelativeLocaleUrl } from 'astro:i18n'
import { toolCatalog, type ToolSlug } from '@/lib/tools/catalog'

interface Props {
current?: ToolSlug | null
title?: string
intro?: string
}

const {
current = null,
title = 'More mobile delivery tools',
intro = 'Use these generators and device utilities together to keep signing, testing, and distribution moving without leaving the browser.',
} = Astro.props as Props
---

<section class="py-16 sm:py-20">
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div class="mb-10 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div>
<p class="text-sm font-semibold tracking-[0.25em] text-cyan-300 uppercase">Tool stack</p>
<h2 class="mt-3 text-3xl font-bold text-white sm:text-4xl">{title}</h2>
</div>
<p class="max-w-2xl text-sm leading-7 text-slate-300">{intro}</p>
</div>

<div class="grid gap-5 md:grid-cols-3">
{
toolCatalog
.filter((tool) => tool.slug !== current)
.map((tool) => (
<a
href={getRelativeLocaleUrl(Astro.locals.locale, tool.href)}
class="group rounded-3xl border border-white/10 bg-white/5 p-6 transition hover:-translate-y-1 hover:border-cyan-400/50 hover:bg-white/8"
>
<p class="text-xs font-semibold tracking-[0.25em] text-cyan-300 uppercase">{tool.eyebrow}</p>
<h3 class="mt-3 text-2xl font-bold text-white">{tool.name}</h3>
<p class="mt-3 text-sm leading-7 text-slate-300">{tool.summary}</p>
<span class="mt-5 inline-flex items-center text-sm font-semibold text-cyan-200 transition group-hover:text-white">Open tool</span>
</a>
))
}
</div>
</div>
</section>
35 changes: 35 additions & 0 deletions src/components/tools/ToolFaq.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
interface FaqItem {
question: string
answer: string
}

interface Props {
title?: string
intro?: string
items: FaqItem[]
}

const { title = 'Frequently asked questions', intro = '', items } = Astro.props as Props
---

<section id="faq" class="py-16 sm:py-20">
<div class="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<div class="mb-10">
<p class="text-sm font-semibold tracking-[0.25em] text-cyan-300 uppercase">FAQ</p>
<h2 class="mt-3 text-3xl font-bold text-white sm:text-4xl">{title}</h2>
{intro && <p class="mt-4 max-w-2xl text-base text-slate-300">{intro}</p>}
</div>

<div class="space-y-4">
{
items.map((item) => (
<details class="rounded-2xl border border-white/10 bg-slate-900/80 p-6">
<summary class="cursor-pointer list-none text-lg font-semibold text-white marker:hidden">{item.question}</summary>
<p class="mt-4 text-sm leading-7 text-slate-300">{item.answer}</p>
</details>
))
}
</div>
</div>
</section>
Loading