Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: API History #594

Merged
merged 66 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
14afa19
feat(api-history): basic webpack and remark plugin
piotrpdev Jun 1, 2024
9a5d259
feat(api-history): basic table
piotrpdev Jun 3, 2024
f1e9b09
feat(api-history): minor table styling
piotrpdev Jun 4, 2024
253d9c9
refactor(api-history): move regexp to variable
piotrpdev Jun 8, 2024
ae1e413
feat(api-history): better regexp
piotrpdev Jun 9, 2024
f687070
feat(api-history): move plugin to own file
piotrpdev Jun 18, 2024
2ab0e80
feat(api-history): remove `removed` property
piotrpdev Jun 18, 2024
1309387
feat(api-history): fetch version using pr
piotrpdev Jun 20, 2024
efdd84c
feat(api-history): types for data fetch code
piotrpdev Jun 20, 2024
92cc468
refactor(api-history): more readable row sorting
piotrpdev Jun 20, 2024
41c13b2
feat(api-history): changes markdown support
piotrpdev Jun 20, 2024
225a50c
feat(api-history): strip comment tags in pre-build instead of plugin
piotrpdev Jun 28, 2024
0382bc2
feat(api-history): `pr-release-versions-plugin`
piotrpdev Jun 28, 2024
2f74afe
feat(api-history): use react component instead of handmade AST
piotrpdev Jun 28, 2024
edb5a84
fix(api-history): revert accidental dependency
piotrpdev Jun 28, 2024
8397e97
refactor(api-history): get pr releases in transformer, remove plugin
piotrpdev Jun 28, 2024
0674b0e
docs(api-history): todo in transformer
piotrpdev Jul 2, 2024
e5345d6
docs(api-history): add temp example api history and schema for pr review
piotrpdev Jul 2, 2024
96d8046
fix(api-history): update `yarn.lock` for now
piotrpdev Jul 2, 2024
d0be11b
Merge remote-tracking branch 'origin/main' into feat/api-history
piotrpdev Jul 4, 2024
fd76258
feat(api-history): use example pr versions by default for now
piotrpdev Jul 4, 2024
d9e15cb
refactor(api-history): move `ApiHistoryTable` styles to module
piotrpdev Jul 5, 2024
c927a72
style(api-history): better var name, fix style lint
piotrpdev Jul 5, 2024
a5d30f1
feat(api-history): map pre-releases to released stable versions
piotrpdev Jul 5, 2024
6d2032c
feat(api-history): better styles, remove pr number
piotrpdev Jul 9, 2024
503b6e7
feat(api-history): link table change to breaking-changes
piotrpdev Jul 11, 2024
c65c39d
fix(api-history): provide key for table elements
piotrpdev Jul 11, 2024
6a72bea
feat(api-history): table semver ranges
piotrpdev Jul 11, 2024
394cc14
feat(api-history): better error handling
piotrpdev Jul 11, 2024
d563d0a
feat(api-history): type predicates in transformer
piotrpdev Jul 12, 2024
6454c48
feat(api-history): update to accepted schema
piotrpdev Jul 12, 2024
2503006
Merge remote-tracking branch 'origin/main' into feat/api-history
piotrpdev Jul 12, 2024
e2c16c5
docs(api-history): remove old comments
piotrpdev Jul 12, 2024
247b7d9
feat(api-history): only pre-process `/docs/api/*.md` containing `<!--`
piotrpdev Jul 15, 2024
dbe887c
Update scripts/tasks/preprocess-api-history.ts
piotrpdev Jul 15, 2024
acc8d21
fix(api-history): match api history block consistently
piotrpdev Jul 15, 2024
cb6ca04
feat(api-history): pre-process more error checking
piotrpdev Jul 15, 2024
0b1b6a7
Update docs/latest/api-history.schema.json
piotrpdev Jul 15, 2024
99b36d1
fix(api-history): duplicate dependencies
piotrpdev Jul 15, 2024
5a552aa
docs(api-history): remove unnecessary comment
piotrpdev Jul 15, 2024
51ab304
refactor(api-history): move pre-process deps to dev deps
piotrpdev Jul 15, 2024
9481b1c
feat(api-history): `isHtml(node)` type guard
piotrpdev Jul 15, 2024
a111aea
fix(api-history): missing `toLowerCase()`
piotrpdev Jul 16, 2024
228653f
Update scripts/tasks/preprocess-api-history.ts
piotrpdev Jul 16, 2024
6849610
Update scripts/tasks/preprocess-api-history.ts
piotrpdev Jul 16, 2024
8a08d34
Update src/components/ApiHistoryTable.tsx
piotrpdev Jul 16, 2024
6e74b23
revert(api-history): `isHtml(node)` type guard
piotrpdev Jul 16, 2024
04029d1
feat(api-history): tell user to run `lint-roller`
piotrpdev Jul 16, 2024
92ae687
fix(api-history): stricter change type param
piotrpdev Jul 16, 2024
f5295f5
Apply suggestions from code review
piotrpdev Jul 26, 2024
0db471e
style(api-history): fix lint
piotrpdev Jul 26, 2024
b7ef54e
Merge branch 'main' into feat/api-history
piotrpdev Jul 26, 2024
644106a
fix(api-history): remove unnecessary null check
piotrpdev Jul 26, 2024
f72365b
style(api-history): fix lint
piotrpdev Jul 26, 2024
0186844
docs(api-history): stricter schema
piotrpdev Jul 29, 2024
ca2f9de
docs(api-history): `ipc-renderer`
piotrpdev Jul 29, 2024
b310ecf
docs(api-history): `browser-view`
piotrpdev Jul 29, 2024
6c709fd
Merge branch 'main' into feat/api-history
piotrpdev Aug 4, 2024
cdd1eb3
ci: use GH token with correct permissions
dsanders11 Aug 5, 2024
a047f6a
fix(api-history): change semver sign if backport is `x.0.0`
piotrpdev Aug 5, 2024
e5b2578
fix(api-history): preprocess warn on missing schema
piotrpdev Aug 6, 2024
60f5457
Update src/components/ApiHistoryTable.tsx
piotrpdev Aug 11, 2024
e61e7d6
refactor(api-history): better semver logic
piotrpdev Aug 11, 2024
b1325ae
Merge branch 'main' into feat/api-history
piotrpdev Aug 11, 2024
3176216
refactor(api-history): remove test `docs/`
piotrpdev Aug 12, 2024
a9d5975
refactor(api-history): remove test PR data
piotrpdev Aug 12, 2024
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
51 changes: 51 additions & 0 deletions docs/latest/api-history.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"title": "JSON schema for API history blocks in Electron documentation",
"$schema": "http://json-schema.org/draft-07/schema#",
"$comment": "If you change this schema, remember to edit the TypeScript interfaces in the linting script.",
"definitions": {
"changeSchema": {
"type": "object",
"properties": {
"pr-url": {
"type": "string",
"pattern": "^https://github.com/electron/electron/pull/\\d+$"
},
"breaking-changes-header": {
"type": "string",
"minLength": 3
},
"description": {
"type": "string",
"minLength": 3,
"maxLength": 72
}
},
"required": [
"pr-url"
],
"additionalProperties": false
}
},
"type": "object",
"properties": {
"added": {
"type": "array",
"items": {
"$ref": "#/definitions/changeSchema"
}
},
"deprecated": {
"type": "array",
"items": {
"$ref": "#/definitions/changeSchema"
}
},
"changes": {
"type": "array",
"items": {
"$ref": "#/definitions/changeSchema"
}
}
},
"additionalProperties": false
}
9 changes: 9 additions & 0 deletions docs/latest/api/base-window.md
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,15 @@ win.setSheetOffset(toolbarRect.height)

#### `win.flashFrame(flag)`

```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/35658
changes:
- pr-url: https://github.com/electron/electron/pull/41391
description: Made `window.flashFrame(bool)` flash continuously on macOS.
breaking-changes-header: behavior-changed-windowflashframebool-will-flash-dock-icon-continuously-on-macos
```

* `flag` boolean

Starts or stops flashing the window to attract user's attention.
Expand Down
9 changes: 9 additions & 0 deletions docs/latest/api/web-contents-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ Process: [Main](latest/glossary.md#main-process)

### `new WebContentsView([options])`

```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/35658
changes:
- pr-url: https://github.com/electron/electron/pull/42086
description: Extended `WebContentsView` to accept pre-existing `webContents` object.
breaking-changes-header: behavior-changed-browserviewsetautoresize-behavior-on-macos
```

* `options` Object (optional)
* `webPreferences` [WebPreferences](latest/api/structures/web-preferences.md) (optional) - Settings of web page's features.
* `webContents` [WebContents](latest/api/web-contents.md) (optional) - If present, the given WebContents will be adopted by the WebContentsView. A WebContents may only be presented in one WebContentsView at a time.
Expand Down
2 changes: 2 additions & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import apiOptionsClass from './src/transformers/api-options-class';
import apiStructurePreviews from './src/transformers/api-structure-previews';
import jsCodeBlocks from './src/transformers/js-code-blocks';
import fiddleEmbedder from './src/transformers/fiddle-embedder';
import apiHistory from './src/transformers/api-history';

const config: Config = {
title: 'Electron',
Expand Down Expand Up @@ -219,6 +220,7 @@ const config: Config = {
apiStructurePreviews,
jsCodeBlocks,
fiddleEmbedder,
apiHistory,
[npm2yarn, { sync: true, converters: ['yarn'] }],
],
},
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,22 @@
"@docusaurus/remark-plugin-npm2yarn": "3.4.0",
"@docusaurus/theme-mermaid": "3.4.0",
"@mdx-js/react": "^3.0.0",
"adm-zip": "^0.5.14",
"ajv": "^8.16.0",
"clsx": "^1.1.1",
"docusaurus-plugin-sass": "^0.2.1",
"dotenv": "^16.0.3",
"execa": "^5.0.0",
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
"hast-util-from-html": "^2.0.1",
"mdast-util-from-markdown": "^2.0.1",
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
"prism-react-renderer": "^2.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",
"react-useportal": "^1.0.14",
"sass": "^1.37.5",
"semver": "^7.5.4"
"semver": "^7.5.4",
"yaml": "^2.4.3"
},
"devDependencies": {
"@crowdin/cli": "^3.9.3",
Expand All @@ -57,6 +64,7 @@
"@electron/docs-parser": "^1.0.1",
"@electron/lint-roller": "^2.2.0",
"@tsconfig/docusaurus": "^2.0.3",
"@types/adm-zip": "^0.5.5",
"@types/fs-extra": "^9.0.13",
"@types/mdast": "^4.0.4",
"@types/node": "^18.11.10",
Expand Down
4 changes: 4 additions & 0 deletions scripts/pre-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { addFrontmatterToAllDocs } from './tasks/add-frontmatter';
import { createSidebar } from './tasks/create-sidebar';
import { fixContent } from './tasks/md-fixers';
import { copyNewContent } from './tasks/copy-new-content';
import { preprocessApiHistory } from './tasks/preprocess-api-history';

const DOCS_FOLDER = path.join(__dirname, '..', 'docs', 'latest');

Expand Down Expand Up @@ -65,6 +66,9 @@ const start = async (source: string): Promise<void> => {
logger.info('Copying new content');
await copyNewContent(DOCS_FOLDER);

logger.info('Finding, validating, and uncommenting API history blocks');
await preprocessApiHistory(DOCS_FOLDER);

logger.info('Fixing markdown');
await fixContent('docs', 'latest');

Expand Down
199 changes: 199 additions & 0 deletions scripts/tasks/preprocess-api-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import globby from 'globby';
import path from 'path';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { visit } from 'unist-util-visit';
import type { Html, Root } from 'mdast';
import type { Node, Literal } from 'unist';
import Ajv, { JSONSchemaType, ValidateFunction } from 'ajv';
import { readFile, writeFile } from 'fs/promises';
import { fromHtml } from 'hast-util-from-html';
import logger from '@docusaurus/logger';
import { parse as parseYaml } from 'yaml';
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved

const apiHistoryRegex = /<!--[\s\S]*?(```[\s\S]*?```)[\s\S]*?-->/;

interface ChangeSchema {
'pr-url': string;
'breaking-changes-header'?: string;
description?: string;
}

interface ApiHistory {
added?: ChangeSchema[];
deprecated?: ChangeSchema[];
changes?: ChangeSchema[];
}

interface LiteralString extends Literal {
value: string;
}

// Copied from here: <https://github.com/electron/website/blob/feat/api-history/scripts/tasks/add-frontmatter.ts#L16-L23>
const getMarkdownFiles = async (startPath: string) => {
const filesPaths = await globby(path.posix.join(startPath, '**/*.md'));

const files: Map<string, string> = new Map();
for (const filePath of filesPaths) {
const content = await readFile(filePath, 'utf-8');
files.set(filePath, content);
}

return files;
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
};

// Copied from here but slightly different: <https://github.com/electron/lint-roller/blob/bac245478ba69017c2a82e8ba1b2884a37647494/bin/lint-markdown-api-history.ts#L42-L67>
function findPossibleApiHistoryBlocks(tree: Root) {
const codeBlocks: Html[] = [];

visit(
tree,
(node: Node) =>
node.type === 'html' &&
(node as LiteralString).value.toLowerCase().includes('```') &&
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
(node as LiteralString).value.toLowerCase().includes('yaml') &&
(node as LiteralString).value.toLowerCase().includes('history'),
(node) => {
codeBlocks.push(node as Html);
}
);

return codeBlocks;
}

function findValidApiHistoryBlocks(
possibleHistoryBlocks: Html[],
filePath: string,
validateAgainstSchema: ValidateFunction<ApiHistory>
) {
const validHistoryBlocks: Html[] = [];

// All of the validation logic copied from here: <https://github.com/electron/lint-roller/blob/bac245478ba69017c2a82e8ba1b2884a37647494/bin/lint-markdown-api-history.ts#L117-L176>
for (const possibleHistoryBlock of possibleHistoryBlocks) {
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
const {
children: [htmlComment],
} = fromHtml(possibleHistoryBlock.value);

if (htmlComment.type !== 'comment') {
logger.warn(
`Possible API History block is not in a HTML comment (${logger.green(
filePath
)})`
);
continue;
}

const {
children: [codeBlock],
} = fromMarkdown(htmlComment.value);

if (
codeBlock.type !== 'code' ||
codeBlock.lang?.toLowerCase() !== 'yaml' ||
codeBlock.meta?.trim() !== 'history'
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
) {
// TODO: Better error message
logger.warn(
`Error parsing possible history block in ${logger.green(filePath)}`
);
continue;
}

let unsafeHistory = null;

try {
unsafeHistory = parseYaml(codeBlock.value);
} catch (error) {
logger.warn(
`Error parsing YAML in possible history block (${logger.green(
filePath
)})`
);
continue;
}

const isValid = validateAgainstSchema(unsafeHistory);

if (!isValid) {
logger.warn(
`Error validating YAML in possible history block (${logger.green(
filePath
)})`
);
continue;
}

validHistoryBlocks.push(possibleHistoryBlock);
}

return validHistoryBlocks;
}

export const preprocessApiHistory = async (startPath: string) => {
// ? Allow for a custom schema to be passed in.
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
const schema = path.resolve(startPath, 'api-history.schema.json');

let validateAgainstSchema: ValidateFunction<ApiHistory> | null = null;

try {
const ajv = new Ajv();
const ApiHistorySchemaFile = await readFile(schema, { encoding: 'utf-8' });
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
const ApiHistorySchema = JSON.parse(
ApiHistorySchemaFile
) as JSONSchemaType<ApiHistory>;
validateAgainstSchema = ajv.compile(ApiHistorySchema);
} catch (error) {
logger.error(`Error reading API history schema:\n${error}`);
return;
}

const files = await getMarkdownFiles(startPath);

for (const [filePath, content] of files) {
const tree = fromMarkdown(content);

const possibleHistoryBlocks = findPossibleApiHistoryBlocks(tree as Root);
const validHistoryBlocks = findValidApiHistoryBlocks(
possibleHistoryBlocks,
filePath,
validateAgainstSchema
);

if (validHistoryBlocks.length === 0) continue;

let newContent = content;
let fileLengthDifference = 0;

// Strip HTML comment tags from history blocks
for (const validHistoryBlock of validHistoryBlocks) {
const apiHistoryRegexMatches =
validHistoryBlock.value.match(apiHistoryRegex);

if (apiHistoryRegexMatches.length !== 2) {
logger.warn(
`Error extracting the API history block inside HTML comment in ${logger.green(
filePath
)}`
);
continue;
}

const [, historyBlockWithoutTags] = apiHistoryRegexMatches;

const start = newContent.substring(
0,
validHistoryBlock.position!.start.offset + fileLengthDifference
);
const end = newContent.substring(
validHistoryBlock.position!.end.offset + fileLengthDifference
);

// Stripping the HTML comment tags of a history block will offset the position of the next history block
fileLengthDifference +=
historyBlockWithoutTags.length - validHistoryBlock.value.length;

newContent = start + historyBlockWithoutTags + end;
}

await writeFile(filePath, newContent, { encoding: 'utf-8' });
piotrpdev marked this conversation as resolved.
Show resolved Hide resolved
}
};
Loading