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: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Build package (needed by the example site workspace link)
run: pnpm run build
- name: Type-check the showcase site as a consumer would
run: pnpm --filter example-gitlab run typecheck
- name: Run tests (unit + e2e docusaurus build)
run: pnpm test

Expand Down
17 changes: 15 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ static HTML. The browser never holds a token or calls the GitLab API.
exercises as a second build variant. Keep `configureWebpack` — do NOT add
`configureBundler` until a v4 release publishes its signature; a tripwire test
in `src/plugin/index.test.ts` enforces this.
- **The default export must stay assignable to Docusaurus's `PluginModule`.**
README and `examples/gitlab` register the plugin in its *function* form
(`plugins: [[gitlabPlugin, opts]]`), which Docusaurus type-checks against
`PluginConfig` — so both parameters must stay `unknown` (a narrower
`options: PluginOptions` fails contravariantly, which is why Docusaurus's own
plugins cannot be passed this way) and the returned object must satisfy
`Plugin`. `src/plugin/types.test.ts` is the compile-time guard;
`examples/gitlab`'s `typecheck` script is the consumer-side one.
`@docusaurus/types` is a **devDependency only** — never import it from a file
that reaches `dist/`, because pnpm consumers cannot resolve it from their own
`node_modules` (`examples/gitlab` has to declare it explicitly).
- Prefer the latest versions of libraries.
- ESM-first. Intra-package imports use explicit `.js` extensions
(e.g. `import { Fallback } from "./Fallback.js"`) — required by the
Expand All @@ -54,8 +65,10 @@ static HTML. The browser never holds a token or calls the GitLab API.
`mise run setup | lint | lint:fix | typecheck | test | build`, plus
`mise run release` (runs the full gate locally and shows the pending
release-please PR — never publishes) and example-site tasks that rebuild
`dist/` first: `gitlab:build`/`gitlab:start` (showcase site, live gitlab.com
data) and `site:build`/`site:start` (`examples/site` is the e2e fixture —
`dist/` first: `gitlab:build`/`gitlab:start`/`gitlab:typecheck` (showcase site,
live gitlab.com data; `gitlab:typecheck` checks the site against the built
`dist/` the way a consumer would, and also runs in CI)
and `site:build`/`site:start` (`examples/site` is the e2e fixture —
its stub projects 404 against real gitlab.com, so `site:build` only works
with `GITLAB_HOST` pointing at a stub; `site:start` renders Fallbacks in dev).

Expand Down
5 changes: 4 additions & 1 deletion examples/gitlab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"build": "docusaurus build",
"start": "docusaurus start",
"serve": "docusaurus serve",
"clear": "docusaurus clear"
"clear": "docusaurus clear",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@docusaurus/core": "^3.5.0",
Expand All @@ -16,7 +17,9 @@
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@docusaurus/plugin-content-docs": "^3.10.2",
"@docusaurus/tsconfig": "^3.5.0",
"@docusaurus/types": "^3.10.2",
"typescript": "^5.4.0"
}
}
18 changes: 18 additions & 0 deletions examples/gitlab/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// This site registers the plugin in its FUNCTION form
// (`plugins: [[gitlabPlugin, gitlabOptions]]`), which is the form Docusaurus
// actually type-checks against `PluginConfig`. Without a tsconfig here the
// `const config: Config` annotation in docusaurus.config.ts was never checked,
// so a plugin that did not satisfy `PluginModule` still looked fine.
//
// Requires a built `dist/` (the workspace link resolves to it):
// pnpm --filter @ebuildy/docusaurus-plugin-gitlab run build
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"strict": true
},
// Scoped to the config: `src/theme/*` swizzles import `@theme-original/*`,
// a webpack alias that only resolves against a generated `.docusaurus/` dir,
// so pulling it in here would demand a full site build just to type-check.
"include": ["docusaurus.config.ts", "sidebars.ts"]
}
5 changes: 5 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ description = "Build the full GitLab showcase site (examples/gitlab)"
depends = ["build"]
run = "pnpm --filter example-gitlab run build"

[tasks."gitlab:typecheck"]
description = "Type-check the GitLab showcase site against the built dist/, as a consumer would (examples/gitlab)"
depends = ["build"]
run = "pnpm --filter example-gitlab run typecheck"

[tasks."gitlab:start"]
description = "Run the GitLab showcase site dev server (examples/gitlab)"
depends = ["build"]
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ebuildy/docusaurus-plugin-gitlab",
"version": "0.5.0",
"version": "0.5.1",
"description": "MDX extensions to embed GitLab resources in Docusaurus 3 and 4 docs",
"license": "MIT",
"publishConfig": {
Expand Down Expand Up @@ -85,6 +85,7 @@
},
"devDependencies": {
"@docusaurus/logger": "^3.10.2",
"@docusaurus/types": "^3.10.2",
"@eslint/js": "^9",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.4.0",
Expand Down
95 changes: 95 additions & 0 deletions pnpm-lock.yaml

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

20 changes: 16 additions & 4 deletions src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,18 @@ function buildIncludeLoaderRule(args: {
};
}

export default async function gitlabPlugin(context: unknown, options: PluginOptions) {
// Both parameters stay `unknown` so this stays assignable to Docusaurus's
// `PluginModule` — `(context: LoadContext, options: unknown)`. Parameters are
// checked contravariantly, so a narrower `options: PluginOptions` breaks the
// documented `plugins: [[gitlabPlugin, opts]]` form (it is why Docusaurus's own
// plugins cannot be registered that way). Guarded by ./types.test.ts.
// `?? {}` is load-bearing: without it `resolveOptions(undefined)` sails past
// Joi's object schema and dies on `opts.host` with a raw TypeError instead of
// the branded `invalid options — "host" is required`.
export default async function gitlabPlugin(context: unknown, options: unknown) {
const pluginOptions = (options ?? {}) as PluginOptions;
const mode = process.env.NODE_ENV === "production" ? "production" : "development";
const resolved = resolveOptions(options, mode);
const resolved = resolveOptions(pluginOptions, mode);
const loadContext = context as PluginContextLike | undefined;
const providedSiteDir = loadContext?.siteDir;
const siteDir = providedSiteDir ?? process.cwd();
Expand All @@ -115,7 +124,7 @@ export default async function gitlabPlugin(context: unknown, options: PluginOpti
// driven separately by the serializable `resolved.fixAutolinks` boolean, so it
// never depends on this registry.)
const processorsId = `gitlab-out-${processorSeq++}`;
registerOutProcessors(processorsId, options.outProcessors ?? []);
registerOutProcessors(processorsId, pluginOptions.outProcessors ?? []);

const ctx = buildContext(resolved);

Expand Down Expand Up @@ -166,7 +175,10 @@ export default async function gitlabPlugin(context: unknown, options: PluginOpti
// instead of concatenating — `append` makes it plain-concat so other
// plugins' rule objects pass through unchanged rather than being
// merged with ours.
mergeStrategy: { "module.rules": "append" },
// `as const` is load-bearing: without it the literal widens to `string`,
// which is not a webpack-merge `CustomizeRuleString`, and the whole
// return value stops being a valid `ConfigureWebpackResult`.
mergeStrategy: { "module.rules": "append" as const },
};
},
};
Expand Down
22 changes: 22 additions & 0 deletions src/plugin/types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { PluginConfig, PluginModule } from "@docusaurus/types";
import gitlabPlugin from "./index.js";

// Compile-time conformance guard for the documented registration form,
//
// plugins: [[gitlabPlugin, gitlabOptions]]
//
// which Docusaurus type-checks against `PluginConfig` (see README.md and
// examples/gitlab). `PluginModule` pins the RETURN type too, so a widened
// `mergeStrategy` — the `as const` in ./index.js — fails here as well.
//
// `@docusaurus/types` is a devDependency and `tsconfig.build.json` excludes
// `*.test.ts`, so this import is checked by `pnpm run typecheck` without ever
// reaching `dist/`. That matters: a `.d.ts` importing it would not resolve for
// pnpm consumers, who do not get the package hoisted — examples/gitlab has to
// declare it explicitly.
//
// No runtime assertions: `passWithNoTests` (vitest.config.ts) makes that a pass,
// and ./index.test.ts already covers the runtime behaviour.

gitlabPlugin satisfies PluginModule;
[[gitlabPlugin, { host: "https://gitlab.com" }]] satisfies PluginConfig[];
Loading