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
2 changes: 1 addition & 1 deletion .github/workflows/action_deploy_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
uses: actions/upload-pages-artifact@v4
with:
name: generated_docs
path: './generated_docs'
path: './docs/site'

# Deployment job
deploy:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/action_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- fastlane/Appfile
- Gemfile
- Gemfile.lock
- .github/workflows/**
- .github/workflows/action_pull_request.yml
common-kotlin: &common-kotlin
- *common-fastlane
- build-logic/**
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ generated_docs/*
.kotlin
docs/__pycache__/
docs/site
docs/docs/overrides/homepage-content.html
**/.gradle/**
**/build/**
**/.idea/**
Expand Down
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,29 @@ It's a good idea to sanity check your work by using the library through the demo
Open the root of this repo in Android Studio and run the `samples.demo-kmm.AndroidApp` or `samples.demo-android` targets. The KMM iOS app can also be run through XCode as normal.
Note: Currently there's no way to test the Swift package locally without it first being deployed.

## Internal API (`@InternalMockzillaApi`)

Some types must be `public` because they are shared across library modules (e.g. DTOs used by both `mockzilla` and `mockzilla-management`), but they are **not intended for use by external consumers**.

These are annotated with `@InternalMockzillaApi` (defined in `mockzilla-common`). The annotation is a `@RequiresOptIn` at the `ERROR` level, so consumers who accidentally reference an internal type will get a compile-time error.

**When to apply it:** Any public declaration (class, interface, function, property) that lives in a `*.internal.*` package.

**When NOT to apply it:**
- Swift/Objective-C interop entry points (e.g. `AsyncUtils.kt`, `NestedClassBridgeGeneration.kt` on iOS) — Swift has no way to satisfy `@OptIn` requirements, so annotating these would break the Swift bridge.
- `@JsExport` declarations in `jsinterface/JsInterface.kt` — these are intentionally public JS API even though they happen to live in an `internal` package.
- Declarations that already have the Kotlin `internal` visibility modifier — the compiler already prevents access, so no annotation is needed (`private` and `internal` Kotlin modifiers should be preferred if they're possible).

**How library modules opt in:** All modules in this repo are inside the internal-API boundary. Rather than adding `@file:OptIn` to every file, each module's `build.gradle.kts` opts in at the module level:

```kotlin
compilerOptions {
freeCompilerArgs.add("-opt-in=com.apadmi.mockzilla.lib.InternalMockzillaApi")
}
```

**For `expect`/`actual` declarations:** The annotation must be present on both the `expect` declaration and **every** `actual` declaration across all platforms. Missing one platform will cause a compile error.

## Spotless

We use Spotless to reformat and organise all of our library code. It runs automatically on compilation so please ensure you've compiled your code before submitting a pull request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.apadmi.mockzilla
import org.gradle.api.Project

object MobileUiConfig {
const val coreVersionForManagementUi = "3.0.0-alpha2"
const val coreVersionForManagementUi = "to be updated during release"
}

fun Project.isMobileUiDeployBuild() = properties["is_building_for_deployment"].toString().toBoolean()
2 changes: 2 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv
homepage/dist-ssr
1 change: 0 additions & 1 deletion docs/docs/browser_stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Browserstack seems to proxy local traffic by default. In your client app you'll
```kotlin
OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY)
.protocols(listOf(Protocol.HTTP_1_1)).build()
```

### Ktor Example:
Expand Down
22 changes: 13 additions & 9 deletions docs/docs/documentation.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
This documentation is primarily built using [MkDocs](https://www.mkdocs.org/)
with the [Material theme](https://squidfunk.github.io/mkdocs-material/).
This documentation is built using [Zensical](https://zensical.org/), a modern static site
generator by the team behind Material for MkDocs.

**Their documentation is brilliant so please check their docs if this is not sufficient.**
**Their documentation is great so check it if this is not sufficient.**


## Working on the HomePage

The homepage is a separate React site which is included in the MkDocs site.
The homepage is a separate React site which is included in the Zensical site.

In your IDE of choice open `docs/homepage` and treat it as regular standalone react site.
In your IDE of choice open `docs/homepage` and treat it as a regular standalone React site.
Install dependencies with `npm install` and run it with `npm run dev`.

Note: Run `npm run build` to get your updates to the homepage reflected in the mkdocs site locally.
Note: Run `npm run build:fragment` (or `./serve.sh`) to get your updates to the homepage reflected in the docs site locally.

## Working on the rest of the documentation

Expand All @@ -31,7 +31,11 @@ Tested on python `v{{get_python_version()}}`

```bash
# Install all dependencies
python3 -m venv .venv
source .venv/bin/activate

pip install -r requirements.txt
cd homepage && npm install
```

Run the following to start the server.
Expand All @@ -40,13 +44,13 @@ This supports hot reloading so updating the docs should
automatically reload the docs in your browser.

```bash
mkdocs serve
./serve.sh
```

## Macros

The docs also uses the [mkdocs-macros](https://mkdocs-macros-plugin.readthedocs.io/en/latest/) plugin.
This lets us call out to python code (and a load of other features) from within markdown.
The docs use Zensical's built-in macro support, which is compatible with the `mkdocs-macros-plugin`
API. This lets us call out to Python code from within Markdown.

See the `main.py` file which includes some useful macros.

Expand Down
4 changes: 4 additions & 0 deletions docs/docs/endpoints.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: Learn how to configure Mockzilla endpoints — define custom handlers for your mock HTTP server.
---

# Configuring Endpoints

## Simple Example
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
title: Home
title: Mockzilla
template: home.html
description: A compile safe solution for running and configuring a local HTTP server for your mobile apps. Supports Android, iOS, Kotlin Multiplatform and Flutter.
---
19 changes: 1 addition & 18 deletions docs/docs/overrides/home.html
Original file line number Diff line number Diff line change
@@ -1,18 +1 @@
{% extends "main.html" %} {% block header %} {{ super() }} {% endblock %} {% block styles %} {{ super() }}
<style>
.md-main__inner {
display: none;
}
.md-main {
display: none;
}

iframe {
flex: 1;
margin: 0;
border: none;
}
</style>
{% endblock %} {% block footer %} {% endblock %} {% block tabs %} {% block content %}
<iframe src="homepage/index.html"></iframe>
{% endblock %} {% endblock %}
{% include "homepage-content.html" %}
44 changes: 1 addition & 43 deletions docs/docs/overrides/main.html
Original file line number Diff line number Diff line change
@@ -1,44 +1,2 @@
{% extends "base.html" %} {% block scripts %} {{ super() }}
<script>
// Send theme updates to homepage iframe
function notifyIframeTheme() {
const iframe = document.querySelector('iframe[src*="homepage"]');
if (iframe && iframe.contentWindow) {
const scheme = document.body.getAttribute('data-md-color-scheme');
iframe.contentWindow.postMessage(
{
type: 'theme-change',
theme: scheme,
},
'*'
);
}
}
{% extends "base.html" %}

// Listen for theme toggle clicks
document.addEventListener('DOMContentLoaded', function () {
setTimeout(notifyIframeTheme, 100);

// Listen for palette changes
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.attributeName === 'data-md-color-scheme') {
notifyIframeTheme();
}
});
});

observer.observe(document.body, {
attributes: true,
attributeFilter: ['data-md-color-scheme'],
});

// Handle requests from iframe
window.addEventListener('message', function (event) {
if (event.data?.type === 'get-theme') {
notifyIframeTheme();
}
});
});
</script>
{% endblock %}
4 changes: 4 additions & 0 deletions docs/docs/quick-start.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: Get started with Mockzilla in minutes. Add the dependency, configure your endpoints, and start a local mock HTTP server for your mobile app.
---

# Quick Start
!!! important
Mockzilla does not support HTTPS, all traffic is cleartext HTTP.
Expand Down
112 changes: 112 additions & 0 deletions docs/homepage/generate-fragment.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { build } from 'vite'
import { readFileSync, writeFileSync, readdirSync } from 'fs'
import { resolve, dirname } from 'path'
import { fileURLToPath, pathToFileURL } from 'url'

const __dirname = dirname(fileURLToPath(import.meta.url))
const outputPath = resolve(__dirname, '../docs/overrides/homepage-content.html')

// Read version from gradle; VITE_VERSION_NAME env var takes precedence (set by CI via Fastlane).
const gradleText = readFileSync(resolve(__dirname, '../../mockzilla/build.gradle.kts'), 'utf-8')
const versionMatch = gradleText.match(/"(.*?)" \/\/ x-release-please-version/)
process.env.VITE_VERSION_NAME ||= versionMatch ? versionMatch[1] : 'Dev'

// Collapses whitespace and strips single-line comments — sufficient for small inline scripts.
const minify = src => src.replace(/\/\/[^\n]*/g, '').replace(/\s+/g, ' ').trim()

// Reads Material's /.__palette key before first paint to prevent FOUC.
// Homepage and docs share this single key so their dark-mode states are always in sync.
const themeInitScript = minify(`
try {
var p = localStorage.getItem('/.__palette');
var dark = p
? JSON.parse(p).index === 1
: window.matchMedia('(prefers-color-scheme:dark)').matches;
if (dark) document.documentElement.classList.add('dark');
} catch (e) {}
`)

// Wires up the toggle button and writes Material's /.__palette key on click.
const toggleScript = minify(`
(function () {
var b = document.getElementById('theme-toggle');
if (!b) return;
b.addEventListener('click', function () {
var dark = document.documentElement.classList.toggle('dark');
try {
localStorage.setItem('/.__palette', JSON.stringify({ index: dark ? 1 : 0 }));
} catch (e) {}
});
})()
`)

// Step 1: Browser build — uses vite.config.mjs (SWC + Tailwind) to compile CSS.
console.log('Building assets...')
await build({ logLevel: 'warn' })

// Step 2: SSR build — uses @vitejs/plugin-react (standard Babel, not SWC) because the SWC
// plugin doesn't reliably produce Node.js-runnable output. react-syntax-highlighter is bundled
// inline (noExternal) to avoid CJS/ESM interop errors when it's externalized.
console.log('Building SSR bundle...')
const { default: reactPlugin } = await import('@vitejs/plugin-react')
await build({
configFile: false,
plugins: [reactPlugin()],
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
alias: { '@': resolve(__dirname, './src') },
},
logLevel: 'warn',
ssr: { noExternal: ['react-syntax-highlighter'] },
build: {
ssr: 'src/ssr-entry.tsx',
outDir: 'dist-ssr',
rollupOptions: { output: { format: 'esm' } },
},
})

// Step 3: Run the SSR bundle to get pre-rendered HTML.
// The cache-bust query param (?t=...) forces Node to re-import on repeated runs.
console.log('Rendering HTML...')
const ssrEntryPath = resolve(__dirname, 'dist-ssr/ssr-entry.js')
const { render } = await import(`${pathToFileURL(ssrEntryPath).href}?t=${Date.now()}`)
const bodyHtml = render()

// Step 4: Inline the compiled CSS.
const assetsDir = resolve(__dirname, 'build/homepage-assets')
const css = readdirSync(assetsDir)
.filter(f => f.endsWith('.css'))
.map(f => readFileSync(resolve(assetsDir, f), 'utf-8'))
.join('\n')

// Step 5: Write the complete standalone HTML document.
const title = 'Mockzilla — Build API mocks with ease'
const desc = 'A compile-safe solution for running and configuring a local HTTP server for your mobile apps. Supports Android, iOS, Kotlin Multiplatform and Flutter.'
const ogUrl = 'https://mockzilla.apadmi.dev/'

const fullDocument = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<link rel="canonical" href="${ogUrl}">
<meta name="description" content="${desc}">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Mockzilla">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${desc}">
<meta property="og:url" content="${ogUrl}">
<script>${themeInitScript}</script>
<style>
${css}
</style>
</head>
<body>
${bodyHtml}
<script>${toggleScript}</script>
</body>
</html>`

writeFileSync(outputPath, fullDocument)
console.log(`✓ Standalone HTML document written to ${outputPath}`)
Loading
Loading