Skip to content

Commit 987fa29

Browse files
committed
test: guard the public feature graph
1 parent 105cdfd commit 987fa29

9 files changed

Lines changed: 262 additions & 120 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ jobs:
3636
- name: Rebuild ReScript code
3737
run: npm run build
3838

39+
- name: Check public feature builds
40+
run: npm run check:features
41+
3942
- name: Run tests
4043
run: npm test
4144

‎astro.config.mjs‎

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,10 @@ const rescriptTM = JSON.parse(
77
readFileSync("./docs/assets/rescript.tmLanguage.json", "utf-8"),
88
);
99

10-
const apiSidebarItems = apiModules.map(({ moduleName, link, items }) => {
11-
const nestedItems = Object.values(items).map(({ moduleName, link }) => ({
12-
label: moduleName,
13-
link,
14-
}));
15-
16-
return {
17-
label: moduleName,
18-
collapsed: true,
19-
items: [
20-
{
21-
label: `Overview`,
22-
link,
23-
},
24-
...nestedItems,
25-
],
26-
};
27-
});
10+
const apiSidebarItems = apiModules.map(({ moduleName, link }) => ({
11+
label: moduleName,
12+
link,
13+
}));
2814

2915
export default defineConfig({
3016
srcDir: "docs",

‎docs/components/apidoc.astro‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
import { apiModules, getDoc } from "../utils";
2+
import { getDoc } from "../utils";
33
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
44
import Value from "./value.astro";
55
import Type from "./type.astro";

‎docs/content/docs/api-surface.mdx‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,40 @@ Generated implementation modules such as `DomTypes`, `FetchTypes`, `EventTypes`,
4141
module's `t` type when it has one, or use a dedicated public type module. Otherwise, let
4242
the value type be inferred from constructors and accessors.
4343

44+
## Consumer feature bundles
45+
46+
Feature names select related bindings and their transitive dependencies. They do not add
47+
another namespace segment. For example, selecting `WebAPI.Fetch` enables flat modules such
48+
as `WebAPI.Fetch`, `WebAPI.Request`, `WebAPI.Response`, and `WebAPI.Headers`.
49+
50+
```json
51+
{
52+
"dependencies": [
53+
{
54+
"name": "@rescript/webapi",
55+
"features": ["WebAPI.Fetch", "WebAPI.HTML"]
56+
}
57+
]
58+
}
59+
```
60+
61+
The supported feature bundles are:
62+
63+
```text
64+
WebAPI.DOM WebAPI.Event WebAPI.DOMPlatform
65+
WebAPI.DOMNodes WebAPI.File WebAPI.HTML
66+
WebAPI.Window WebAPI.CSSOM WebAPI.CSSFontLoading
67+
WebAPI.Geometry WebAPI.SVG WebAPI.Animation
68+
WebAPI.Device WebAPI.Navigator WebAPI.Canvas
69+
WebAPI.URL WebAPI.Fetch WebAPI.UIEvents
70+
WebAPI.Observers WebAPI.Media WebAPI.WebAudio
71+
WebAPI.Storage WebAPI.Messaging WebAPI.Workers
72+
WebAPI.Crypto WebAPI.Performance WebAPI.ViewTransitions
73+
```
74+
75+
The package owns each bundle as one internal source folder. Those internal folder feature
76+
names are an implementation detail; consumers should use only the qualified names above.
77+
4478
## Fetch
4579

4680
Use `WebAPI.Fetch.fetch` for string URLs and `WebAPI.Fetch.fetchWithRequest`

‎docs/llm.js‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as path from "node:path";
22
import { exec } from "node:child_process";
33
import { promisify } from "node:util";
44
import fs from "node:fs/promises";
5-
import { featureSpecs } from "../scripts/unmonorepo/feature-spec.mjs";
65

76
const execAsync = promisify(exec);
87

@@ -95,12 +94,11 @@ Module: ${moduleName}${typeString}${functionString}
9594
`;
9695
}
9796

98-
const specByDir = new Map(featureSpecs.map((spec) => [spec.dirName, spec]));
9997
const rootDir = path.join(import.meta.dirname, "..");
10098
const rootConfig = JSON.parse(await fs.readFile(path.join(rootDir, "rescript.json"), "utf-8"));
10199
const publicModulesBySourceDir = new Map(
102100
rootConfig.sources
103-
.filter((source) => typeof source === "object")
101+
.filter((source) => source !== null && typeof source === "object")
104102
.filter((source) => source.dir?.startsWith("src/") && Array.isArray(source.public))
105103
.map((source) => [source.dir, new Set(source.public)]),
106104
);
@@ -130,16 +128,7 @@ function isPublicFile(filePath) {
130128
}
131129

132130
function moduleNameForFile(relativePath) {
133-
const [, dirName, fileName] = relativePath.split(path.sep);
134-
const spec = specByDir.get(dirName);
135-
136-
if (!spec) {
137-
throw new Error(`Unsupported source directory for documentation: ${relativePath}`);
138-
}
139-
140-
const leafName = path.basename(fileName, ".res");
141-
142-
return `WebAPI.${leafName}`;
131+
return `WebAPI.${path.basename(relativePath, ".res")}`;
143132
}
144133

145134
const pattern = "../src/*/**/*.res";

‎docs/pages/apidocs/[API]/[Module].astro‎

Lines changed: 0 additions & 22 deletions
This file was deleted.

‎docs/utils.js‎

Lines changed: 16 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import { exec } from "node:child_process";
33
import { promisify } from "node:util";
44
import { readdirSync, existsSync, readFileSync } from "fs";
55
import { micromark } from "micromark";
6-
import { featureSpecs } from "../scripts/unmonorepo/feature-spec.mjs";
76

87
const execAsync = promisify(exec);
98
const rootDir = process.cwd();
109
const rootConfig = JSON.parse(readFileSync(path.join(rootDir, "rescript.json"), "utf8"));
11-
const publicModulesBySourceDir = new Map(
12-
rootConfig.sources
13-
.filter((source) => typeof source === "object")
14-
.filter((source) => source.dir?.startsWith("src/") && Array.isArray(source.public))
15-
.map((source) => [source.dir, new Set(source.public)]),
10+
const publicSourceEntries = rootConfig.sources.filter(
11+
(source) =>
12+
source !== null &&
13+
typeof source === "object" &&
14+
source.dir?.startsWith("src/") &&
15+
Array.isArray(source.public),
1616
);
1717

1818
function toKebabCase(input) {
@@ -32,64 +32,16 @@ export function createTypeModuleLink(parentModuleLink, typeName) {
3232
return `${parentModuleLink}/${toKebabCase(typeName)}`;
3333
}
3434

35-
function mapTypeModules(parentModuleLink, file, spec) {
36-
const folder = path.dirname(file);
37-
38-
if (!existsSync(folder)) {
39-
return [];
40-
}
41-
42-
const publicModules = publicModulesBySourceDir.get(spec.sourceDir) ?? new Set();
43-
const typesFileName = `${spec.internalPrefix}Types.res`;
44-
const files = readdirSync(folder);
45-
return files
46-
.filter((f) => f.endsWith(".res") && f !== typesFileName)
47-
.filter((file) => publicModules.has(file.replace("$", "").replace(".res", "")))
48-
.map((file) => {
49-
const filePath = path.join(folder, file);
50-
51-
const leafName = file.replace("$", "").replace(".res", "");
52-
const moduleName = leafName;
53-
const apiRouteParameter = toKebabCase(moduleName);
54-
const link = createTypeModuleLink(parentModuleLink, moduleName);
55-
const typeName = moduleName[0].toLocaleLowerCase() + moduleName.slice(1);
56-
57-
return [
58-
typeName,
59-
{
60-
filePath,
61-
moduleName,
62-
link,
63-
apiRouteParameter,
64-
},
65-
];
66-
});
67-
}
68-
69-
function mapRescriptFile(srcDir, file, spec) {
70-
const filePath = path.join(srcDir, file);
71-
const moduleName = spec.publicModule;
72-
const link = createAPIModuleLink(moduleName);
73-
const items = Object.fromEntries(mapTypeModules(link, filePath, spec));
74-
75-
return {
76-
filePath,
77-
moduleName,
78-
link,
79-
apiRouteParameter: toKebabCase(moduleName),
80-
items,
81-
};
82-
}
83-
84-
const srcRoot = path.resolve(process.cwd(), "src");
85-
export const apiModules = featureSpecs
86-
.map((spec) => ({
87-
spec,
88-
srcDir: path.join(srcRoot, spec.dirName),
89-
typesFileName: `${spec.internalPrefix}Types.res`,
90-
}))
91-
.filter(({ srcDir, typesFileName }) => existsSync(path.join(srcDir, typesFileName)))
92-
.map(({ spec, srcDir, typesFileName }) => mapRescriptFile(srcDir, typesFileName, spec))
35+
export const apiModules = publicSourceEntries
36+
.flatMap((source) =>
37+
source.public.map((moduleName) => ({
38+
filePath: path.join(rootDir, source.dir, `${moduleName}.res`),
39+
moduleName,
40+
link: createAPIModuleLink(moduleName),
41+
apiRouteParameter: toKebabCase(moduleName),
42+
})),
43+
)
44+
.filter(({ filePath }) => existsSync(filePath))
9345
.sort((a, b) => a.moduleName.localeCompare(b.moduleName));
9446

9547
async function getRescriptDoc(absoluteFilePath) {

‎package.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@
3838
"scripts": {
3939
"test": "node tests/index.js",
4040
"build": "rescript",
41-
"format": "rescript format && oxfmt ./tests/index.js ./package.json ./docs && prettier --write ./docs/pages",
42-
"format:check": "rescript format --check && oxfmt ./tests/index.js ./package.json ./docs --check && prettier --check ./docs/pages",
41+
"check:features": "node scripts/check-features.mjs",
42+
"format": "rescript format && oxfmt ./tests/index.js ./scripts/check-features.mjs ./package.json ./docs && prettier --write ./docs/pages",
43+
"format:check": "rescript format --check && oxfmt ./tests/index.js ./scripts/check-features.mjs ./package.json ./docs --check && prettier --check ./docs/pages",
4344
"docs": "astro dev",
4445
"prebuild:docs": "node docs/llm.js",
4546
"build:docs": "astro build"

0 commit comments

Comments
 (0)