Skip to content

Commit 4f3de21

Browse files
authored
ci: add markdown, website, and image lint checks (#1987)
Adds Markdown, website, and image lint checks to CI, mirroring the setup in `apify-docs` and `apify-core`. The docs and website now go through the same kind of automated checks as the Python code. The website tooling moves from eslint + prettier (which weren't wired into CI) to [oxlint](https://oxc.rs/) and [oxfmt](https://oxc.rs/), and [markdownlint](https://github.com/DavidAnson/markdownlint) now covers the Markdown files. Three jobs run in the existing Checks workflow: - **Markdown lint** — `markdownlint` over `README.md`, `CONTRIBUTING.md`, and the `docs/` folder. - **Website lint and format** — `oxlint` plus an `oxfmt --check` formatting gate for the Docusaurus site. - **Image lint** — fails when a PR adds unoptimized raster images, which should be converted with `pnpm opt:images` first. Notes: - The website JS keeps single quotes, set via `.editorconfig` (`quote_type`) and `.oxfmtrc.json` (`singleQuote`). - The markdownlint config matches `apify-docs`: line length is disabled and inline HTML is allowed. - Minor doc and code fixes were applied where needed to pass the new checks. Closes #1692
1 parent ef3e91d commit 4f3de21

65 files changed

Lines changed: 1369 additions & 2846 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ charset = utf-8
77
trim_trailing_whitespace = true
88
insert_final_newline = true
99
end_of_line = lf
10+
quote_type = single
1011

1112
[Makefile]
1213
indent_style = tab

.github/workflows/_checks.yaml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ on:
1515
permissions:
1616
contents: read
1717

18+
env:
19+
NODE_VERSION: 24
20+
1821
jobs:
1922
actions_lint_check:
2023
name: Actions lint check
@@ -46,6 +49,86 @@ jobs:
4649
with:
4750
python_versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
4851

52+
markdown_lint_check:
53+
name: Markdown lint check
54+
runs-on: ubuntu-latest
55+
steps:
56+
- name: Checkout repository
57+
uses: actions/checkout@v7
58+
59+
- name: Set up Node
60+
uses: actions/setup-node@v6
61+
with:
62+
node-version: ${{ env.NODE_VERSION }}
63+
64+
- name: Install pnpm and website dependencies
65+
uses: apify/actions/pnpm-install@v1.2.0
66+
with:
67+
working-directory: website
68+
69+
- name: Lint Markdown
70+
run: pnpm lint:md
71+
working-directory: website
72+
73+
website_lint_check:
74+
name: Website lint check
75+
runs-on: ubuntu-latest
76+
steps:
77+
- name: Checkout repository
78+
uses: actions/checkout@v7
79+
80+
- name: Set up Node
81+
uses: actions/setup-node@v6
82+
with:
83+
node-version: ${{ env.NODE_VERSION }}
84+
85+
- name: Install pnpm and website dependencies
86+
uses: apify/actions/pnpm-install@v1.2.0
87+
with:
88+
working-directory: website
89+
90+
- name: Lint website code
91+
run: pnpm lint:code
92+
working-directory: website
93+
94+
- name: Check website formatting
95+
run: pnpm format:check
96+
working-directory: website
97+
98+
image_lint_check:
99+
name: Image lint check
100+
runs-on: ubuntu-latest
101+
steps:
102+
- name: Checkout repository
103+
uses: actions/checkout@v7
104+
with:
105+
fetch-depth: 0
106+
107+
# Doc images must be committed as optimized `.webp`. This fails when a PR adds raster
108+
# images in another format so they get converted via `pnpm opt:images` first.
109+
- name: Get changed unoptimized images
110+
id: changed-files
111+
uses: tj-actions/changed-files@v47
112+
with:
113+
files: |
114+
docs/**/*.{png,jpg,jpeg,gif,bmp,tif,tiff,avif}
115+
website/static/**/*.{png,jpg,jpeg,gif,bmp,tif,tiff,avif}
116+
separator: "\n"
117+
118+
- name: Fail on unoptimized images
119+
if: steps.changed-files.outputs.any_changed == 'true'
120+
env:
121+
UNOPTIMIZED_IMAGE_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
122+
run: |
123+
echo "Unoptimized images detected! Convert each one to WebP, e.g.:"
124+
echo ""
125+
while IFS= read -r file_path; do
126+
echo " (cd website && pnpm opt:images \"../$file_path\")"
127+
done <<< "$UNOPTIMIZED_IMAGE_FILES"
128+
echo ""
129+
echo "Then reference the resulting .webp files in your Markdown."
130+
exit 1
131+
49132
unit_tests:
50133
name: Unit tests
51134
if: inputs.run_tests

.github/workflows/manual_release_docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ permissions:
2323
contents: read
2424

2525
env:
26-
NODE_VERSION: 22
26+
NODE_VERSION: 24
2727
PYTHON_VERSION: 3.14
2828

2929
jobs:

.github/workflows/manual_version_docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ permissions:
2727
contents: read
2828

2929
env:
30-
NODE_VERSION: "22"
30+
NODE_VERSION: "24"
3131
PYTHON_VERSION: "3.14"
3232

3333
jobs:

.github/workflows/on_schedule_tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ permissions:
1616
contents: read
1717

1818
env:
19-
NODE_VERSION: 22
19+
NODE_VERSION: 24
2020
PYTHON_VERSION: 3.14
2121
TESTS_CONCURRENCY: 1
2222

.markdownlint.yaml

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
1+
# markdownlint config for the docs and top-level Markdown files.
2+
# Run via `pnpm lint:md` / `pnpm lint:md:fix` from the `website/` directory.
13
default: true
2-
line-length:
3-
line_length: 120
4-
MD007:
5-
indent: 4
6-
MD004:
4+
5+
# Prose is written one sentence per line, so line length is not enforced.
6+
line-length: false
7+
8+
ul-style:
79
style: dash
10+
11+
# Nested unordered lists use 4-space indentation.
12+
ul-indent:
13+
indent: 4
14+
15+
# Docs are MDX and embed JSX components.
816
no-inline-html: false
17+
18+
# MDX pages set their title via front matter, so multiple/duplicate H1s are fine.
19+
single-title: false
20+
no-duplicate-heading:
21+
siblings_only: true
22+
23+
# Anchor links into other pages can't be validated locally.
24+
link-fragments: false
25+
26+
no-bare-urls: false
27+
no-trailing-punctuation:
28+
punctuation: ".,;:。,;:"
29+
no-multiple-blanks:
30+
maximum: 2

.rules.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,18 @@ Note: `uv run poe unit-tests` first runs tests marked `@pytest.mark.run_alone` i
5252
- **Type checker**: ty (Astral's type checker), target Python 3.10
5353
- **Async mode**: pytest-asyncio in `auto` mode (no need for `@pytest.mark.asyncio`)
5454
- **Commits**: [Conventional Commits](https://www.conventionalcommits.org/) format. Choose the type based on *what* changed, not just *why*:
55-
- `feat:` / `fix:` / `perf:` / `refactor:` / `style:`**source code only**; these trigger a release and appear in the changelog
56-
- `test:` — test additions or changes (no release triggered)
57-
- `docs:` — documentation changes; also triggers a doc release on master
58-
- `ci:` — CI/workflow changes
59-
- `chore:` — dependency bumps, tooling, and other housekeeping
60-
- `build:` — build system changes
55+
- `feat:` / `fix:` / `perf:` / `refactor:` / `style:`**source code only**; these trigger a release and appear in the changelog
56+
- `test:` — test additions or changes (no release triggered)
57+
- `docs:` — documentation changes; also triggers a doc release on master
58+
- `ci:` — CI/workflow changes
59+
- `chore:` — dependency bumps, tooling, and other housekeeping
60+
- `build:` — build system changes
6161

6262
## Architecture
6363

6464
### Crawler Hierarchy
6565

66-
```
66+
```text
6767
BasicCrawler[TCrawlingContext, TStatisticsState]
6868
├── AbstractHttpCrawler → HttpCrawler, BeautifulSoupCrawler, ParselCrawler
6969
├── PlaywrightCrawler
@@ -78,7 +78,7 @@ BasicCrawler[TCrawlingContext, TStatisticsState]
7878

7979
Contexts are progressively enhanced through `ContextPipeline` middleware:
8080

81-
```
81+
```text
8282
BasicCrawlingContext → HttpCrawlingContext → ParsedHttpCrawlingContext → BeautifulSoupCrawlingContext
8383
```
8484

@@ -87,6 +87,7 @@ Each middleware is an async generator that wraps the next handler, enabling setu
8787
### Storage Layer
8888

8989
Three-tier design:
90+
9091
- **High-level**: `Dataset`, `KeyValueStore`, `RequestQueue` in `src/crawlee/storages/`
9192
- **Storage clients** (`src/crawlee/storage_clients/`): `FileSystemStorageClient` (default), `MemoryStorageClient`, `SqlStorageClient`, `RedisStorageClient`
9293
- **Instance caching**: `StorageInstanceManager` is a global singleton that caches storage instances by ID/name
@@ -98,6 +99,7 @@ Three-tier design:
9899
### HTTP Clients
99100

100101
Pluggable via `HttpClient` interface in `src/crawlee/http_clients/`:
102+
101103
- `ImpitHttpClient` (default), `HttpxHttpClient`, `CurlImpersonateHttpClient`
102104
- Each provides `crawl()` (for crawler pipeline) and `send_request()` (for in-handler use)
103105

CONTRIBUTING.md

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ To run the documentation locally, ensure you have `Node.js` 20+ installed, then
115115
uv run poe run-docs
116116
```
117117

118+
### Linting the docs and website
119+
120+
Markdown content (this guide, `README.md`, and the `docs/` folder) is checked with
121+
[markdownlint](https://github.com/DavidAnson/markdownlint). The Docusaurus website code is linted
122+
with [oxlint](https://oxc.rs/) and formatted with [oxfmt](https://oxc.rs/). All of them run in CI.
123+
To run them locally (requires Node.js 22.12 or newer and pnpm), from the `website/` directory:
124+
125+
```sh
126+
pnpm lint # lint Markdown and website code
127+
pnpm lint:fix # auto-fix both
128+
pnpm format # format the website code
129+
```
130+
131+
Doc images are committed as optimized `.webp`. To convert a new image, run
132+
`pnpm opt:images <path-to-image>` from the `website/` directory.
133+
118134
## Commits
119135

120136
We use [Conventional Commits](https://www.conventionalcommits.org/) format for commit messages. This convention is used to automatically determine version bumps during the release process.
@@ -146,25 +162,22 @@ Publishing new versions to [PyPI](https://pypi.org/project/crawlee) is automated
146162

147163
1. **Do not do this unless absolutely necessary.** In all conceivable scenarios, you should use the `release` workflow instead.
148164
2. **Make sure you know what you're doing.**
165+
3. Update the version number by modifying the `version` field under `project` in `pyproject.toml`:
149166

150-
3. Update the version number:
151-
152-
- Modify the `version` field under `project` in `pyproject.toml`.
153-
154-
```toml
155-
[project]
156-
name = "crawlee"
157-
version = "x.z.y"
158-
```
167+
```toml
168+
[project]
169+
name = "crawlee"
170+
version = "x.z.y"
171+
```
159172

160173
4. Build the package:
161174

162-
```sh
163-
uv run poe build
164-
```
175+
```sh
176+
uv run poe build
177+
```
165178

166179
5. Upload to PyPI:
167180

168-
```sh
169-
uv publish --token YOUR_API_TOKEN
170-
```
181+
```sh
182+
uv publish --token YOUR_API_TOKEN
183+
```

docs/deployment/apify_platform.mdx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import CrawlerAsActorExample from '!!raw-loader!./code_examples/apify/crawler_as
1313
import ProxyExample from '!!raw-loader!./code_examples/apify/proxy_example.py';
1414
import ProxyAdvancedExample from '!!raw-loader!./code_examples/apify/proxy_advanced_example.py';
1515

16-
Apify is a [platform](https://apify.com) built to serve large-scale and high-performance web scraping and automation needs. It provides easy access to [compute instances (Actors)](#what-is-an-actor), convenient request and result storages, [proxies](../guides/proxy-management), scheduling, webhooks and [more](https://docs.apify.com/), accessible through a [web interface](https://console.apify.com) or an [API](https://docs.apify.com/api).
16+
Apify is a [platform](https://apify.com) built to serve large-scale and high-performance web scraping and automation needs. It provides easy access to [compute instances (Actors)](#what-is-an-actor), convenient request and result storages, [proxies](../guides/proxy-management), scheduling, webhooks, and [more in the Apify documentation](https://docs.apify.com/), accessible through a [web interface](https://console.apify.com) or an [API](https://docs.apify.com/api).
1717

1818
While we think that the Apify platform is super cool, and it's definitely worth signing up for a [free account](https://console.apify.com/sign-up), **Crawlee is and will always be open source**, runnable locally or on any cloud infrastructure.
1919

@@ -25,7 +25,7 @@ We do not test Crawlee in other cloud environments such as Lambda or on specific
2525

2626
## Requirements
2727

28-
To run your Crawlee code on Apify platform, you need an Apify account. If you don't have one yet, you can sign up [here](https://console.apify.com/sign-up).
28+
To run your Crawlee code on Apify platform, you need an Apify account. If you don't have one yet, you can [sign up](https://console.apify.com/sign-up).
2929

3030
Additionally, you must have the [Apify CLI](https://docs.apify.com/cli/) installed on your computer. For installation instructions, refer to the [Installation guide](https://docs.apify.com/cli/docs/installation).
3131

@@ -72,7 +72,7 @@ When you deploy your script to the Apify platform, it becomes an [Actor](https:/
7272

7373
Actors can be shared in the [Apify Store](https://apify.com/store) so that other people can use them. But don't worry, if you share your Actor in the store and somebody uses it, it runs under their account, not yours.
7474

75-
**Related links**
75+
### Related links
7676

7777
- [Store of existing Actors](https://apify.com/store)
7878
- [Documentation](https://docs.apify.com/actors)
@@ -135,7 +135,8 @@ There are several things worth mentioning here.
135135

136136
### Helper functions for default Key-Value Store and Dataset
137137

138-
To simplify access to the _default_ storages, instead of using the helper functions of respective storage classes, you could use:
138+
To simplify access to the *default* storages, instead of using the helper functions of respective storage classes, you could use:
139+
139140
- [`Actor.set_value()`](https://docs.apify.com/sdk/python/reference/class/Actor#set_value), [`Actor.get_value()`](https://docs.apify.com/sdk/python/reference/class/Actor#get_value), [`Actor.get_input()`](https://docs.apify.com/sdk/python/reference/class/Actor#get_input) for [`Key-Value Store`](https://docs.apify.com/sdk/python/reference/class/KeyValueStore)
140141
- [`Actor.push_data()`](https://docs.apify.com/sdk/python/reference/class/Actor#push_data) for [`Dataset`](https://docs.apify.com/sdk/python/reference/class/Dataset)
141142

@@ -150,6 +151,7 @@ If you don't plan to force usage of the platform storages when running the Actor
150151
:::
151152

152153
{/*
154+
153155
### Getting public url of an item in the platform storage
154156
155157
If you need to share a link to some file stored in a [Key-Value](https://docs.apify.com/sdk/python/reference/class/KeyValueStore) Store on Apify platform, you can use [`get_public_url()`](https://docs.apify.com/sdk/python/reference/class/KeyValueStore#get_public_url) method. It accepts only one parameter: `key` - the key of the item you want to share.
@@ -164,7 +166,7 @@ If you need to share a link to some file stored in a [Key-Value](https://docs.ap
164166

165167
When the <ApiLink to="class/Dataset">`Dataset`</ApiLink> is stored on the [Apify platform](https://apify.com/actors), you can export its data to the following formats: HTML, JSON, CSV, Excel, XML and RSS. The datasets are displayed on the Actor run details page and in the [Storage](https://console.apify.com/storage) section in the Apify Console. The actual data is exported using the [Get dataset items](https://apify.com/docs/api/v2#/reference/datasets/item-collection/get-items) Apify API endpoint. This way you can easily share the crawling results.
166168

167-
**Related links**
169+
### Related links
168170

169171
- [Apify platform storage documentation](https://docs.apify.com/storage)
170172
- [View storage in Apify Console](https://console.apify.com/storage)
@@ -245,9 +247,10 @@ in the [proxy dashboard](https://console.apify.com/proxy).
245247
The [`ProxyConfiguration`](https://docs.apify.com/sdk/python/reference/class/ProxyConfiguration) class covers both Apify Proxy and custom proxy URLs so that you can easily switch between proxy providers. However, some features of the class are available only to Apify Proxy users, mainly because Apify Proxy is what one would call a super-proxy. It's not a single proxy server, but an API endpoint that allows connection through millions of different IP addresses. So the class essentially has two modes: Apify Proxy or Own (third party) proxy.
246248

247249
The difference is easy to remember.
250+
248251
- If you're using your own proxies - you should create a <ApiLink to="class/ProxyConfiguration">`ProxyConfiguration`</ApiLink> instance directly.
249252
- If you are planning to use Apify Proxy - you should create an instance using the [`Actor.create_proxy_configuration()`](https://docs.apify.com/sdk/python/reference/class/Actor#create_proxy_configuration) function. The `new_url_function` parameter enables the use of your custom proxy URLs, whereas all the other options are there to configure Apify Proxy.
250253

251-
**Related links**
254+
### Related links
252255

253256
- [Apify Proxy docs](https://docs.apify.com/proxy)

docs/deployment/aws_lambda.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ Create a repository `lambda/aws-playwright` in [Amazon Elastic Container Registr
160160
Navigate to the created repository and click the "View push commands" button. This will open a window with console commands for uploading the Docker image to your repository. Execute them.
161161

162162
Example:
163+
163164
```bash
164165
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin {user-specific-data}
165166
docker build --platform linux/amd64 --provenance=false -t lambda/aws-playwright .

0 commit comments

Comments
 (0)