diff --git a/.github/workflows/build_dart.yaml b/.github/workflows/build_dart.yaml new file mode 100644 index 00000000..8c699d6f --- /dev/null +++ b/.github/workflows/build_dart.yaml @@ -0,0 +1,94 @@ +name: build_dart + +on: + pull_request: + push: + branches: + - typedoc-cli + - main + +jobs: + build_linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + + - name: Install dependencies + working-directory: packages/dart_cli + run: dart pub get + + - name: Compiling + working-directory: packages/dart_cli + run: dart compile exe bin/docs_page.dart -o docs_page + + - name: Store binary + uses: actions/upload-artifact@v3 + with: + name: linux-binary + path: packages/dart_cli/docs_page + + build_macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + + - name: Install dependencies + working-directory: packages/dart_cli + run: dart pub get + + - name: Compiling + working-directory: packages/dart_cli + run: dart compile exe bin/docs_page.dart -o docs_page + + - name: Store binary + uses: actions/upload-artifact@v3 + with: + name: macos-binary + path: packages/dart_cli/docs_page + + build_windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + + - name: Install dependencies + working-directory: packages/dart_cli + run: dart pub get + + - name: Compiling + working-directory: packages/dart_cli + run: dart compile exe bin/docs_page.dart -o docs_page.exe + + - name: Store binary + uses: actions/upload-artifact@v3 + with: + name: windows-binary + path: packages/dart_cli/docs_page.exe + + combine_outputs: + runs-on: ubuntu-latest + needs: [build_linux, build_macos, build_windows] + steps: + - name: Download linux artifact + uses: actions/download-artifact@v3 + with: + name: linux-binary + path: ~/binaries/linux + - name: Download macos artifact + uses: actions/download-artifact@v3 + with: + name: macos-binary + path: ~/binaries/darwin + - name: Download windows artifact + uses: actions/download-artifact@v3 + with: + name: windows-binary + path: ~/binaries/win32 + - name: Store binaries + uses: actions/upload-artifact@v3 + with: + name: binaries + path: ~/binaries diff --git a/.prettierignore b/.prettierignore index a927018b..66731ef0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,6 @@ dist repositories.json domains.json spelling.json -_docs.page \ No newline at end of file +_docs.page +packages/dart_cli/example +packages/dart_cli/docs \ No newline at end of file diff --git a/api/src/controllers/raw.ts b/api/src/controllers/raw.ts index 4aa83fa6..c6bc1318 100644 --- a/api/src/controllers/raw.ts +++ b/api/src/controllers/raw.ts @@ -14,7 +14,6 @@ export const bundleRaw = async ( const path = (req?.query.path as string) || 'index'; const headerDepth = req?.query?.headerDepth ? parseInt(req?.query?.headerDepth as string) : 3; const { md: markdown, config: sourceConfig } = req.body; - const bundleInstance = new Bundle({ owner: 'n/a', repository: 'n/a', diff --git a/api/src/utils/bundle.ts b/api/src/utils/bundle.ts index 2d0e1559..7bc92aa3 100644 --- a/api/src/utils/bundle.ts +++ b/api/src/utils/bundle.ts @@ -33,6 +33,12 @@ type BundleConstructorParams = { config?: OutputConfig; }; +export type References = { + name: string; + path: string; + kind: string; +}[]; + export class Bundle { code: string; markdown: string; @@ -48,6 +54,7 @@ export class Bundle { headerDepth: number; built: boolean; contentFetched: boolean; + referenceConfig: References | null; constructor({ owner, @@ -77,6 +84,7 @@ export class Bundle { this.sourceChecked = false; this.built = false; this.contentFetched = false; + this.referenceConfig = null; } // check for branch/PR ref @@ -101,9 +109,11 @@ export class Bundle { path: this.path, }); } catch (e) { + throw new BundleError(404, "Couldn't fetch github contents", 'REPO_NOT_FOUND'); } if (!githubContents.repositoryFound) { + throw new BundleError(404, "Couldn't find github contents", 'REPO_NOT_FOUND'); } this.baseBranch = githubContents.baseBranch; @@ -114,6 +124,7 @@ export class Bundle { } this.repositoryFound = githubContents.repositoryFound; + this.referenceConfig = githubContents.referenceConfig; this.formatConfigLocales(githubContents.config); await this.matchSymLinks(); @@ -162,6 +173,7 @@ export class Bundle { repositoryFound: this.repositoryFound, source: this.source, ref: this.ref, + referenceConfig: this.referenceConfig, }; } diff --git a/api/src/utils/github.ts b/api/src/utils/github.ts index 7a4fad2d..efc1d69d 100644 --- a/api/src/utils/github.ts +++ b/api/src/utils/github.ts @@ -1,6 +1,7 @@ import A2A from 'a2a'; import { graphql } from '@octokit/graphql'; import dotenv from 'dotenv'; +import { References } from './bundle'; dotenv.config(); const getGitHubToken = (() => { @@ -55,6 +56,9 @@ type PageContentsQuery = { mdxIndex?: { text: string; }; + referenceConfig?: { + text: string; + }; }; }; @@ -69,6 +73,7 @@ export type Contents = { md: string | null; path: string; repositoryFound: boolean; + referenceConfig: References | null; }; export async function getGitHubContents(metadata: MetaData, noDir?: boolean): Promise { @@ -81,7 +86,7 @@ export async function getGitHubContents(metadata: MetaData, noDir?: boolean): Pr const [error, response] = await A2A( getGithubGQLClient()({ query: ` - query RepositoryConfig($owner: String!, $repository: String!, $configJson: String!, $configYaml: String!, $configToml: String!, $mdx: String!, $mdxIndex: String!) { + query RepositoryConfig($owner: String!, $repository: String!, $configJson: String!, $configYaml: String!, $configToml: String!, $mdx: String!, $mdxIndex: String!, $referenceConfig: String!) { repository(owner: $owner, name: $repository) { baseBranch: defaultBranchRef { name @@ -112,6 +117,11 @@ export async function getGitHubContents(metadata: MetaData, noDir?: boolean): Pr text } } + referenceConfig: object(expression: $referenceConfig) { + ... on Blob { + text + } + } } } `, @@ -122,17 +132,30 @@ export async function getGitHubContents(metadata: MetaData, noDir?: boolean): Pr configToml: `${ref}:docs.toml`, mdx: `${ref}:${absolutePath}.mdx`, mdxIndex: `${ref}:${indexPath}.mdx`, + referenceConfig: `${ref}:docs.refs.json` }), ); // if an error is thrown then the repo is not found, if the repo is private then response = { repository: null } if (error || response?.repository === null) { + console.error(error.response.errors); + //@ts-ignore return { repositoryFound: false, }; } + let referenceConfig = null; + try { + referenceConfig = response?.repository.referenceConfig?.text + ? JSON.parse(response?.repository.referenceConfig?.text) + : null; + } catch (e) { + console.error('Could not parse reference config'); + console.error(e); + } + return { repositoryFound: true, isFork: response?.repository?.isFork ?? false, @@ -144,6 +167,7 @@ export async function getGitHubContents(metadata: MetaData, noDir?: boolean): Pr }, md: response?.repository.mdxIndex?.text || response?.repository.mdx?.text || null, path: response?.repository.mdxIndex?.text ? indexPath : absolutePath, + referenceConfig, }; } diff --git a/docs/typedoc.mdx b/docs/typedoc.mdx new file mode 100644 index 00000000..a264c3c4 --- /dev/null +++ b/docs/typedoc.mdx @@ -0,0 +1,28 @@ +--- +title: Typedoc Support +description: Learn how to generate docs.page references using typedoc comments +--- + +# Typedoc Support + +If you've added typedoc-compliant comments to your code, Docs.page is able to generate reference pages from your typedoc.json. + +The steps are as follows: + +1. In your root directory, generate your `typedoc.json` from your code, using the flag `--json`, and specifying your output path as `docs/typedoc.json`. You will have to install typedoc first, and the command should look similar to: + +``` +npm run typedoc --json docs/typedoc.json src/index.ts +``` + +or if you're using yarn, + +``` +yarn typedoc --json docs/typedoc.json src/index.ts +``` + +2. Add a `references` field to your `docs.json` or `docs.yaml` configuration file. This value specifies the folder in which your references will be generated. If you're unsure, set it as `Reference` or `API`. +3. Add a `typedocEntryDir` field to your `docs.json` or `docs.yaml` configuration file. This should be set as the path of the directory where your typedoc entry file is. +4. Run + `docs_page typedoc` + from your root directory. diff --git a/packages/dart_cli/.gitignore b/packages/dart_cli/.gitignore index 50d967d8..9860ff75 100644 --- a/packages/dart_cli/.gitignore +++ b/packages/dart_cli/.gitignore @@ -7,4 +7,5 @@ build/ # Created by running the command in this directory, don't commit docs.yaml -docs \ No newline at end of file +docs +docs_page \ No newline at end of file diff --git a/packages/dart_cli/CHANGELOG.md b/packages/dart_cli/CHANGELOG.md index effe43c8..23e1156e 100644 --- a/packages/dart_cli/CHANGELOG.md +++ b/packages/dart_cli/CHANGELOG.md @@ -1,3 +1,11 @@ -## 1.0.0 +## 0.0.1 -- Initial version. +- prerelease version. + +## 0.0.2 + +- added `help` command and made `init` command more user-friendly + +## 0.0.3 + +- fix check for `docs.refs.json` on `typedoc` command diff --git a/packages/dart_cli/LICENSE b/packages/dart_cli/LICENSE new file mode 100644 index 00000000..887467cc --- /dev/null +++ b/packages/dart_cli/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Invertase Limited + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/dart_cli/README.md b/packages/dart_cli/README.md index 3816eca3..ee3b8b59 100644 --- a/packages/dart_cli/README.md +++ b/packages/dart_cli/README.md @@ -1,2 +1,7 @@ -A sample command-line application with an entrypoint in `bin/`, library code -in `lib/`, and example unit test in `test/`. +A sample command-line application for use with [Docs.page](https://docs.page). Entrypoint in `bin/`, library code +in `lib/`. + +Currently in prelease. + +Supports two commands. `docs_page init` initializes a directory as a `Docs.page` project, and `docs_page typedoc` generates documentation compatible with `Docs.page` from +a typedoc.json file in a `docs` subdirectory. See [use.docs.page](https://use.docs.page) for more information about `Docs.page`. diff --git a/packages/dart_cli/bin/docs_page.dart b/packages/dart_cli/bin/docs_page.dart index 14fe0a5d..7477e279 100644 --- a/packages/dart_cli/bin/docs_page.dart +++ b/packages/dart_cli/bin/docs_page.dart @@ -1,56 +1,28 @@ import 'package:args/args.dart'; -import 'dart:io'; -import 'package:path/path.dart' as path; -import 'package:ansi_styles/extension.dart'; +import 'package:docs_page/src/init.dart'; +import 'package:docs_page/src/typedoc.dart'; +import 'package:docs_page/src/help.dart'; void main(List arguments) async { final parser = ArgParser(); parser.addCommand('init'); + parser.addCommand('typedoc'); + parser.addCommand('help'); final argResults = parser.parse(arguments); - if (argResults.command?.name == 'init') { - await createFiles(); - } else { - throw UnsupportedError('Only the init command is currently supported.'); - } -} - -createFiles() async { - final indexContent = '# Welcome to your new documentation!'; - final configContent = ''' ---- -name: '' -logo: '' -logoDark: '' -favicon: '' -socialPreview: '' -twitter: '' -noindex: false -theme: "#00bcd4" -sidebar: [] -headerDepth: 3 -variables: {} -googleTagManager: '' -zoomImages: false -experimentalCodehike: false -experimentalMath: false -'''; - - final current = Directory.current; - - final configPath = path.joinAll([current.path, 'docs.yaml']); - - File configFile = await File(configPath).create(recursive: true); - await configFile.writeAsString(configContent); - - final indexPath = path.joinAll([current.path, 'docs', 'index.mdx']); - - File indexFile = await File(indexPath).create(recursive: true); - - await indexFile.writeAsString(indexContent); - - print('Docs.page created files: \n $configPath \n $indexPath'.blueBright); - print( - 'To learn more about config file options and how customize your Docs.page project, visit ' + - "https://use.docs.page".underline.blueBright); + switch (argResults.command?.name) { + case 'init': + await createFiles(); + break; + case 'typedoc': + Node ast = await getTypedocJson(); + await generate(ast: ast); + break; + case 'help': + displayHelp(); + break; + default: + throw UnsupportedError( + 'Only help, init, typedoc commands are currently supported.'); + } } diff --git a/packages/dart_cli/example/main.dart b/packages/dart_cli/example/main.dart new file mode 100644 index 00000000..e50c35c2 --- /dev/null +++ b/packages/dart_cli/example/main.dart @@ -0,0 +1,7 @@ +import 'package:docs_page/src/typedoc.dart'; + +void main() async { + final rootAst = await getTypedocJson(); + + await generate(ast: rootAst); +} diff --git a/packages/dart_cli/lib/src/docs_page_config.dart b/packages/dart_cli/lib/src/docs_page_config.dart new file mode 100644 index 00000000..c4a4618d --- /dev/null +++ b/packages/dart_cli/lib/src/docs_page_config.dart @@ -0,0 +1,100 @@ +import 'dart:convert'; +import 'package:json_annotation/json_annotation.dart'; +import 'dart:io'; +import 'package:yaml/yaml.dart'; +import 'package:toml/toml.dart'; +import 'package:path/path.dart' as path; + +part 'docs_page_config.g.dart'; + +@JsonSerializable() +class DocsPageConfig { + final String? name; + final String? theme; + final String? twitter; + final Object? sidebar; + final DocSearch? docsearch; + final String? googleTagManager; + final bool? experimentalCodeHike; + final bool? experimentalMath; + final String? references; + final String? typedocEntryDir; + final Map? locales; + + DocsPageConfig( + {this.name, + this.theme, + this.docsearch, + this.twitter, + this.sidebar, + this.googleTagManager, + this.experimentalCodeHike, + this.experimentalMath, + this.references, + this.typedocEntryDir, + this.locales}); + + factory DocsPageConfig.fromJson(Map json) => + _$DocsPageConfigFromJson(json); + + factory DocsPageConfig.fromDirectory({Directory? dir}) { + final directory = dir ?? Directory.current; + + String configJsonPath = path.joinAll([directory.path, 'docs.json']); + String configYamlPath = path.joinAll([directory.path, 'docs.yaml']); + String configTomlPath = path.joinAll([directory.path, 'docs.toml']); + + File configJson = File(configJsonPath); + File configYaml = File(configYamlPath); + File configToml = File(configTomlPath); + + if (configJson.existsSync()) { + return _$DocsPageConfigFromJson( + jsonDecode(configJson.readAsStringSync())); + } else if (configYaml.existsSync()) { + return _$DocsPageConfigFromJson( + Map.from(loadYaml(configYaml.readAsStringSync()))); + } else if (configToml.existsSync()) { + return _$DocsPageConfigFromJson( + TomlDocument.parse(configToml.readAsStringSync()).toMap()); + } else { + throw Exception("can't find config"); + } + } + + void overwriteInDirectory({Directory? dir, DocsPageConfig? config}) { + final directory = dir ?? Directory.current; + + String configJsonPath = path.joinAll([directory.path, 'docs.json']); + String configYamlPath = path.joinAll([directory.path, 'docs.yaml']); + + File configJson = File(configJsonPath); + File configYaml = File(configYamlPath); + final config = this; + + if (configJson.existsSync()) { + configJson.writeAsStringSync(json.encode(config)); + return; + } else if (configYaml.existsSync()) { + configJson.writeAsStringSync(json.encode(config)); + return; + } else { + throw UnsupportedError("Can only overwrite yaml and json formats"); + } + } + + Map toJson() => _$DocsPageConfigToJson(this); +} + +@JsonSerializable() +class DocSearch { + final String apiKey; + final String indexName; + + DocSearch({required this.apiKey, required this.indexName}); + + factory DocSearch.fromJson(Map json) => + _$DocSearchFromJson(json); + + Map toJson() => _$DocSearchToJson(this); +} diff --git a/packages/dart_cli/lib/src/docs_page_config.g.dart b/packages/dart_cli/lib/src/docs_page_config.g.dart new file mode 100644 index 00000000..1013845e --- /dev/null +++ b/packages/dart_cli/lib/src/docs_page_config.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'docs_page_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DocsPageConfig _$DocsPageConfigFromJson(Map json) => + DocsPageConfig( + name: json['name'] as String?, + theme: json['theme'] as String?, + docsearch: json['docsearch'] == null + ? null + : DocSearch.fromJson(json['docsearch'] as Map), + twitter: json['twitter'] as String?, + sidebar: json['sidebar'], + googleTagManager: json['googleTagManager'] as String?, + experimentalCodeHike: json['experimentalCodeHike'] as bool?, + experimentalMath: json['experimentalMath'] as bool?, + references: json['references'] as String?, + typedocEntryDir: json['typedocEntryDir'] as String?, + locales: (json['locales'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + ); + +Map _$DocsPageConfigToJson(DocsPageConfig instance) => + { + 'name': instance.name, + 'theme': instance.theme, + 'twitter': instance.twitter, + 'sidebar': instance.sidebar, + 'docsearch': instance.docsearch, + 'googleTagManager': instance.googleTagManager, + 'experimentalCodeHike': instance.experimentalCodeHike, + 'experimentalMath': instance.experimentalMath, + 'references': instance.references, + 'typedocEntryDir': instance.typedocEntryDir, + 'locales': instance.locales, + }; + +DocSearch _$DocSearchFromJson(Map json) => DocSearch( + apiKey: json['apiKey'] as String, + indexName: json['indexName'] as String, + ); + +Map _$DocSearchToJson(DocSearch instance) => { + 'apiKey': instance.apiKey, + 'indexName': instance.indexName, + }; diff --git a/packages/dart_cli/lib/src/help.dart b/packages/dart_cli/lib/src/help.dart new file mode 100644 index 00000000..9bffa8d2 --- /dev/null +++ b/packages/dart_cli/lib/src/help.dart @@ -0,0 +1,15 @@ +displayHelp() { + print(''' +docs_page CLI tool + +These are the supported commands: + + init Initializes the current directory as a Docs.page project. + + typedoc Generates reference documentation from your docs/typedoc.json. + + Make sure to complete configuration (see https://use.docs.page/typedoc) to see these in your documentaion. + + Encounter a problem? Raise an issue in our GitHub repository - https://github.com/invertase/docs.page. +'''); +} diff --git a/packages/dart_cli/lib/src/init.dart b/packages/dart_cli/lib/src/init.dart new file mode 100644 index 00000000..a68de9cd --- /dev/null +++ b/packages/dart_cli/lib/src/init.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:path/path.dart' as path; +import 'package:ansi_styles/extension.dart'; +import 'package:yaml/yaml.dart' as yaml; + +createFiles() async { + final indexContent = '# Welcome to your new documentation!'; + final configContent = ''' +--- +name: '' +logo: '' +logoDark: '' +favicon: '' +socialPreview: '' +twitter: '' +noindex: false +theme: "#00bcd4" +sidebar: [['Home', '/']] +headerDepth: 3 +variables: {} +googleTagManager: '' +zoomImages: false +experimentalCodehike: false +experimentalMath: false +'''; + + final current = Directory.current; + final configJson = File(path.joinAll([current.path, 'docs.json'])); + final configYaml = File(path.joinAll([current.path, 'docs.yaml'])); + final configToml = File(path.joinAll([current.path, 'docs.toml'])); + + if (configJson.existsSync() || + configYaml.existsSync() || + configToml.existsSync()) { + print( + "Docs.page tried to create a docs.page configuration file, but a docs.json/docs.yaml/docs.toml file already exists." + .yellow); + } else if (Platform.environment['DOCS_PAGE_CONFIG_FORMAT'] == 'json') { + await configJson.create(recursive: true); + + final yamlMap = Map.from(yaml.loadYaml(configContent)); + + final jsonString = JsonEncoder.withIndent(' ').convert(yamlMap); + + await configJson.writeAsString(jsonString); + + print('Docs.page created file: \n ${configJson.path}'.blueBright); + } else { + await configYaml.create(recursive: true); + await configYaml.writeAsString(configContent); + print('Docs.page created file: \n ${configYaml.path}'.blueBright); + } + + final indexPath = path.joinAll([current.path, 'docs', 'index.mdx']); + + if (File(indexPath).existsSync()) { + print( + "Docs.page tried to create an docs/index.mdx file, but one already exists." + .yellow); + } else { + File indexFile = await File(indexPath).create(recursive: true); + await indexFile.writeAsString(indexContent); + print('Docs.page created file: \n $indexFile'.blueBright); + } + + print( + 'To learn more about config file options and how customize your Docs.page project, visit ' + + "https://use.docs.page".underline.blueBright); +} diff --git a/packages/dart_cli/lib/src/typedoc.dart b/packages/dart_cli/lib/src/typedoc.dart new file mode 100644 index 00000000..052fb841 --- /dev/null +++ b/packages/dart_cli/lib/src/typedoc.dart @@ -0,0 +1,293 @@ +import 'package:docs_page/src/docs_page_config.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:path/path.dart' as path; +import 'dart:io'; +import 'dart:convert'; +import 'package:ansi_styles/extension.dart'; + +part 'typedoc.g.dart'; + +@JsonSerializable() +class Node { + /// The generated code assumes these values exist in JSON. + final int? id; + final String name; + final int? kind; + final String? kindString; + final Map? flags; + final List? children; + final List? groups; + final List? sources; + final Comment? comment; + + Node( + {this.id, + required this.name, + this.kind, + this.flags, + this.children, + this.groups, + this.kindString, + this.sources, + this.comment}); + + /// Connect the generated [_$NodeFromJson] function to the `fromJson` + /// factory. + factory Node.fromJson(Map json) => _$NodeFromJson(json); + + /// Connect the generated [_$PersonToJson] function to the `toJson` method. + Map toJson() => _$NodeToJson(this); +} + +@JsonSerializable() +class Group { + final String title; + final int kind; + final List children; + + Group({required this.title, required this.kind, required this.children}); + + factory Group.fromJson(Map json) => _$GroupFromJson(json); + + /// Connect the generated [_GroupToJson] function to the `toJson` method. + Map toJson() => _$GroupToJson(this); +} + +@JsonSerializable() +class Source { + final String fileName; + final int line; + final int character; + + Source({required this.fileName, required this.line, required this.character}); + + factory Source.fromJson(Map json) => _$SourceFromJson(json); + + /// Connect the generated [_$SourceToJson] function to the `toJson` method. + Map toJson() => _$SourceToJson(this); +} + +@JsonSerializable() +class Comment { + final String? shortText; + final String? text; + final List? tags; + + Comment({this.shortText, this.text, this.tags}); + + factory Comment.fromJson(Map json) => + _$CommentFromJson(json); + + /// Connect the generated [_$CommentToJson] function to the `toJson` method. + Map toJson() => _$CommentToJson(this); +} + +@JsonSerializable() +class Tag { + final String tag; + final String text; + + Tag({required this.tag, required this.text}); + + factory Tag.fromJson(Map json) => _$TagFromJson(json); + + /// Connect the generated [_$TagToJson] function to the `toJson` method. + Map toJson() => _$TagToJson(this); +} + +Future getTypedocJson() async { + final current = Directory.current; + + final jsonPath = path.joinAll([current.path, 'docs', 'typedoc.json']); + + File typedocFile = File(jsonPath); + + if (typedocFile.existsSync()) { + String typedocString = await typedocFile.readAsString(); + Node parsed = Node.fromJson(jsonDecode(typedocString)); + return parsed; + } + throw Exception('Missing docs/typedoc.json, please generate this first.'); +} + +Future generate( + {required Node ast, String? docPath, List? refs}) async { + DocsPageConfig config = DocsPageConfig.fromDirectory(); + + String? referenceRoot = config.references; + if (referenceRoot != null) { + String currentPath = docPath ?? + path.joinAll([Directory.current.path, 'docs', referenceRoot]); + if (config.locales != null) { + currentPath = docPath ?? + path.joinAll([Directory.current.path, 'docs', 'gb', referenceRoot]); + } + final children = ast.children; + + File refsFile = + File(path.joinAll([Directory.current.path, 'docs.refs.json'])); + if (refsFile.existsSync()) { + refsFile.delete(recursive: true); + } + + if (children != null && children.isNotEmpty) { + print('Docs.page created files:'.blueBright); + + await addRef( + name: 'Overview', + filePath: path.relative(path.joinAll([currentPath, 'index.mdx']), + from: path.joinAll([Directory.current.path, 'docs']))); + + for (final child in children) { + final childPath = path.joinAll([currentPath, '${child.name}.mdx']); + + final refPath = path.relative(path.joinAll([currentPath, child.name]), + from: path.joinAll([Directory.current.path, 'docs'])); + + await addRef(node: child, filePath: refPath); + + await createDoc(node: child, filePath: childPath); + print(childPath.blue); + } + } + + await createIndexFile(filePath: path.joinAll([currentPath, 'index.mdx'])); + } else { + throw Exception( + 'Missing field "references" in your docs.json or docs.yaml file. This field should be a string which determines the subdirectory in which your API references are generated'); + } +} + +Future addRef( + {Node? node, String? name, required String filePath}) async { + Map ref; + if (node != null) { + ref = { + "name": node.name, + "path": Uri.encodeFull(filePath), + "kind": node.kindString + }; + } else if (name != null) { + ref = {"name": name, "path": Uri.encodeFull(filePath), "kind": "Overview"}; + } else { + throw Exception('need to specify node or name'); + } + File refsFile = + File(path.joinAll([Directory.current.path, 'docs.refs.json'])); + + String refsString = "[]"; + + if (await refsFile.exists() && refsFile.readAsStringSync() != '') { + refsString = await refsFile.readAsString(); + } + + List refs = jsonDecode(refsString); + + refs.add(ref); + + refsFile.writeAsString(json.encode(refs)); +} + +Future createDoc( + {required Node node, + required String filePath, + bool frontmatterEnabled = true, + int depth = 1}) async { + File file = await File(filePath).create(recursive: true); + + if (frontmatterEnabled) { + String frontmatter = ''' +--- +title: ${node.name} +description: ${node.comment?.shortText} +reference: true +referenceKind: ${node.kindString ?? ''} +--- +'''; + + await file.writeAsString(frontmatter); + } + + String headerPrefix = ''; + + for (int i = 0; i < depth; i++) { + headerPrefix += '#'; + } + await appendAllToFile(file, [ + '$headerPrefix ${node.name}', + node.comment?.shortText, + node.comment?.text, + ]); + + List? sources = node.sources; + + if (sources != null) { + await appendToFile(file, '**Source**'); + for (final source in sources) { + DocsPageConfig config = DocsPageConfig.fromDirectory(); + await appendToFile(file, getGithubLink(config: config, source: source)); + } + } + + List? children = node.children; + + if (children != null) { + for (final child in children) { + await createDoc( + node: child, + filePath: filePath, + frontmatterEnabled: false, + depth: depth + 1); + } + } +} + +Future appendToFile(File file, String? content) async { + if (content != null) { + await file.writeAsString(content + '\n \n ', mode: FileMode.append); + } +} + +Future appendAllToFile(File file, List content) async { + for (final text in content) { + await appendToFile(file, text); + } +} + +Future createIndexFile({required String filePath}) async { + if (!File(filePath).existsSync()) { + File file = await File(filePath).create(recursive: true); + + String frontmatter = ''' +--- +title: Overview +description: Overview for references +reference: true +referenceKind: null +--- +'''; + + await file.writeAsString(frontmatter); + await file.writeAsString('\n \n', mode: FileMode.append); + + await file.writeAsString('# References \n \n', mode: FileMode.append); + + await file.writeAsString('## Overview Page for API references \n \n', + mode: FileMode.append); + await file.writeAsString('Edit this page!', mode: FileMode.append); + } +} + +String getGithubLink({required DocsPageConfig config, required Source source}) { + final typedocEntryDir = config.typedocEntryDir; + + final fileName = source.fileName; + final line = source.line; + + if (typedocEntryDir != null) { + final filePath = path.joinAll([typedocEntryDir, fileName]); + return ''; + } else { + throw Exception('Missing typedocEntryDir option in docs.json'); + } +} diff --git a/packages/dart_cli/lib/src/typedoc.g.dart b/packages/dart_cli/lib/src/typedoc.g.dart new file mode 100644 index 00000000..39ffd573 --- /dev/null +++ b/packages/dart_cli/lib/src/typedoc.g.dart @@ -0,0 +1,90 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'typedoc.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Node _$NodeFromJson(Map json) => Node( + id: json['id'] as int?, + name: json['name'] as String, + kind: json['kind'] as int?, + flags: (json['flags'] as Map?)?.map( + (k, e) => MapEntry(k, e as bool), + ), + children: (json['children'] as List?) + ?.map((e) => Node.fromJson(e as Map)) + .toList(), + groups: (json['groups'] as List?) + ?.map((e) => Group.fromJson(e as Map)) + .toList(), + kindString: json['kindString'] as String?, + sources: (json['sources'] as List?) + ?.map((e) => Source.fromJson(e as Map)) + .toList(), + comment: json['comment'] == null + ? null + : Comment.fromJson(json['comment'] as Map), + ); + +Map _$NodeToJson(Node instance) => { + 'id': instance.id, + 'name': instance.name, + 'kind': instance.kind, + 'kindString': instance.kindString, + 'flags': instance.flags, + 'children': instance.children, + 'groups': instance.groups, + 'sources': instance.sources, + 'comment': instance.comment, + }; + +Group _$GroupFromJson(Map json) => Group( + title: json['title'] as String, + kind: json['kind'] as int, + children: + (json['children'] as List).map((e) => e as int).toList(), + ); + +Map _$GroupToJson(Group instance) => { + 'title': instance.title, + 'kind': instance.kind, + 'children': instance.children, + }; + +Source _$SourceFromJson(Map json) => Source( + fileName: json['fileName'] as String, + line: json['line'] as int, + character: json['character'] as int, + ); + +Map _$SourceToJson(Source instance) => { + 'fileName': instance.fileName, + 'line': instance.line, + 'character': instance.character, + }; + +Comment _$CommentFromJson(Map json) => Comment( + shortText: json['shortText'] as String?, + text: json['text'] as String?, + tags: (json['tags'] as List?) + ?.map((e) => Tag.fromJson(e as Map)) + .toList(), + ); + +Map _$CommentToJson(Comment instance) => { + 'shortText': instance.shortText, + 'text': instance.text, + 'tags': instance.tags, + }; + +Tag _$TagFromJson(Map json) => Tag( + tag: json['tag'] as String, + text: json['text'] as String, + ); + +Map _$TagToJson(Tag instance) => { + 'tag': instance.tag, + 'text': instance.text, + }; diff --git a/packages/dart_cli/pubspec.lock b/packages/dart_cli/pubspec.lock index 4e38d520..34c6dbc3 100644 --- a/packages/dart_cli/pubspec.lock +++ b/packages/dart_cli/pubspec.lock @@ -43,6 +43,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.8" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.3" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.4" charcode: dependency: transitive description: @@ -50,6 +106,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" collection: dependency: transitive description: @@ -78,6 +148,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" file: dependency: transitive description: @@ -85,6 +162,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.2" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" frontend_server_client: dependency: transitive description: @@ -99,6 +183,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" http_multi_server: dependency: transitive description: @@ -127,6 +218,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" + json_serializable: + dependency: "direct main" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.5" lints: dependency: "direct dev" description: @@ -183,6 +288,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.1" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" pool: dependency: transitive description: @@ -197,6 +309,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1+1" shelf: dependency: transitive description: @@ -225,6 +351,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" source_map_stack_trace: dependency: transitive description: @@ -260,6 +400,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" string_scanner: dependency: transitive description: @@ -295,6 +442,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.11" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + toml: + dependency: "direct main" + description: + name: toml + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0" typed_data: dependency: transitive description: @@ -331,7 +492,7 @@ packages: source: hosted version: "1.0.0" yaml: - dependency: transitive + dependency: "direct main" description: name: yaml url: "https://pub.dartlang.org" diff --git a/packages/dart_cli/pubspec.yaml b/packages/dart_cli/pubspec.yaml index 98477d9c..e56e9f42 100644 --- a/packages/dart_cli/pubspec.yaml +++ b/packages/dart_cli/pubspec.yaml @@ -1,6 +1,6 @@ name: docs_page -description: A sample command-line application. -version: 0.0.1 +description: Docs.page CLI package +version: 0.0.3 environment: sdk: '>=2.16.1 <3.0.0' @@ -8,11 +8,18 @@ environment: dependencies: ansi_styles: ^0.3.2+1 args: ^2.3.0 + json_serializable: ^6.1.5 + json_annotation: ^4.4.0 path: ^1.8.1 + toml: '^0.12.0' + yaml: ^3.1.0 dev_dependencies: lints: ^1.0.0 test: ^1.16.0 + build_runner: executables: docs_page: + +repository: https://github.com/invertase/docs.page diff --git a/packages/npx_cli/binaries/darwin/docs_page b/packages/npx_cli/binaries/darwin/docs_page new file mode 100755 index 00000000..c846219b Binary files /dev/null and b/packages/npx_cli/binaries/darwin/docs_page differ diff --git a/packages/npx_cli/binaries/linux/docs_page b/packages/npx_cli/binaries/linux/docs_page new file mode 100755 index 00000000..8ad0c36e Binary files /dev/null and b/packages/npx_cli/binaries/linux/docs_page differ diff --git a/packages/npx_cli/binaries/win32/docs_page.exe b/packages/npx_cli/binaries/win32/docs_page.exe new file mode 100755 index 00000000..ea7c5e5e Binary files /dev/null and b/packages/npx_cli/binaries/win32/docs_page.exe differ diff --git a/packages/npx_cli/index.js b/packages/npx_cli/index.js new file mode 100755 index 00000000..b6182b16 --- /dev/null +++ b/packages/npx_cli/index.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node +var path = require('path'); +var fs = require('fs'); +var childProcess = require('child_process'); + +var binary = path.join( + __dirname, + 'binaries', + process.platform, + `docs_page${process.platform === 'win32' ? '.exe' : ''}`, +); + +if (!fs.existsSync(binary)) { + throw new Error('Docs.page does not support this platform'); +} + +var docsPage = childProcess.spawn(binary, process.argv.slice(2), { + stdio: ['inherit', 'inherit', 'inherit'], + env: { DOCS_PAGE_CONFIG_FORMAT: 'json', ...process.env }, +}); + +docsPage.on('exit', event => process.exit(event.code)); + +process.on('SIGTERM', () => docsPage.kill('SIGTERM')); + +process.on('SIGINT', () => docsPage.kill('SIGINT')); diff --git a/packages/npx_cli/package.json b/packages/npx_cli/package.json index 076d8f8f..743f8bbc 100644 --- a/packages/npx_cli/package.json +++ b/packages/npx_cli/package.json @@ -1,20 +1,19 @@ { - "name": "@docs.page/init", - "version": "1.0.5", + "name": "@docs.page/cli", + "version": "0.1.5", "description": "npx cli for docs.page initialization", - "main": "index.js", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", - "dependencies": { - "typescript": "^4.5.4" - }, "scripts": { - "build": "tsc" + "prepublish": "chmod u+x binaries/darwin/docs_page && chmod u+x binaries/linux/docs_page && chmod u+x binaries/win32/docs_page.exe" + }, + "bin": { + "docs_page": "./index.js" }, - "bin": "./dist/index.js", "files": [ "dist", "LICENSE", - "README.md" + "README.md", + "binaries" ] } diff --git a/packages/npx_cli/src/index.ts b/packages/npx_cli/src/index.ts deleted file mode 100644 index e42b65ea..00000000 --- a/packages/npx_cli/src/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); - -const configString = `{ - "name": "docs.page", - "theme": "#36B9B9", - "sidebar": [ - ["Home", "/"], - ["Another Page", "/another-page"] - ] - }`; - -try { - fs.mkdir(`./docs`, { recursive: true }, (err: Error) => { - if (err) throw err; - fs.writeFile(`./docs/index.mdx`, '# Example Docs!', (err: Error) => { - if (err) throw err; - }); - fs.writeFile(`./docs/another-page.mdx`, '# Another Page', (err: Error) => { - if (err) throw err; - }); - }); - fs.writeFile(`./docs.json`, configString, (err: Error) => { - if (err) throw err; - }); -} catch (err) { - console.log('Error', err); -} diff --git a/packages/npx_cli/tsconfig.json b/packages/npx_cli/tsconfig.json deleted file mode 100644 index 80c65ee8..00000000 --- a/packages/npx_cli/tsconfig.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "ESNEXT" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, - "module": "EsNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist" /* Redirect output structure to the directory. */, - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ - /* Module Resolution Options */ - "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - /* Advanced Options */ - "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } -} diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts index 85f9a693..3341674e 100644 --- a/packages/server/src/types.ts +++ b/packages/server/src/types.ts @@ -36,6 +36,10 @@ export interface ConfigWithoutLocales { experimentalCodehike: boolean; // Whether Math is enabled experimentalMath: boolean; + // alias for references (root of path for references) + reference?: string; + //entry directory for typedoc + typedocEntryDir?: string; } export interface ConfigWithLocales { @@ -77,6 +81,10 @@ export interface ConfigWithLocales { experimentalCodehike: boolean; // Whether Math is enabled experimentalMath: boolean; + // alias for references (root of path for references) + reference?: string; + // entry dir for typedoc + typedocEntryDir?: string; } export type InputConfig = ConfigWithoutLocales | ConfigWithLocales; @@ -125,6 +133,10 @@ export interface OutputConfig { experimentalCodehike: boolean; // Whether Math is enabled experimentalMath: boolean; + // alias for references (root of path for references) + reference?: string; + // entry directory for typedoc + typedocEntryDir?: string; } export const defaultConfig: OutputConfig = { @@ -189,6 +201,13 @@ export type BundleSuccess = { repository: string; ref: string; }; + referenceConfig: References; }; +export type References = { + name: string; + path: string; + kind: string; +}[]; + export type BundleResponseData = BundleSuccess | BundleError; diff --git a/website/app/components/DocsRefSwitch.tsx b/website/app/components/DocsRefSwitch.tsx new file mode 100644 index 00000000..e6369d1d --- /dev/null +++ b/website/app/components/DocsRefSwitch.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { DocsLink } from './DocsLink'; +import cx from 'classnames'; +import { AcademicCapIcon, ArchiveIcon } from '@heroicons/react/solid'; + +export function DocsRefsSwitch({ + locales, + currentLocale, + isReference, + referencePath, +}: { + locales: Record | undefined; + currentLocale: string; + isReference: boolean; + referencePath: string; +}) { + return ( +
    +
  • +
    +
    + +
    + + cx('my-2 block', { + 'hover:text-gray-800 dark:hover:text-gray-100': isReference, + 'text-docs-theme border-docs-theme font-medium': !isReference, + }) + } + > + {'Docs'} + +
    +
  • +
  • +
    +
    + +
    + + cx('my-2 block', { + 'hover:text-gray-800 dark:hover:text-gray-100': !isReference, + 'text-docs-theme border-docs-theme font-medium': isReference, + }) + } + > + {referencePath} + +
    +
  • +
+ ); +} diff --git a/website/app/components/Sidebar.tsx b/website/app/components/Sidebar.tsx index 58970347..b76d1656 100644 --- a/website/app/components/Sidebar.tsx +++ b/website/app/components/Sidebar.tsx @@ -3,12 +3,31 @@ import { useLocation } from 'react-router-dom'; import { useDocumentationContext } from '~/context'; import { DarkModeToggle } from './DarkModeToggle'; import { DocsLink } from './DocsLink'; +import { DocsRefsSwitch } from './DocsRefSwitch'; import { LocaleSelect } from './LocaleSelect'; export function Sidebar() { - const { sidebar, locales } = useDocumentationContext().config; + const documentationContext = useDocumentationContext(); + const { sidebar, locales, references } = documentationContext.config; + const { referenceConfig, frontmatter } = documentationContext; + + const referencePath = references ?? 'API'; + const location = useLocation(); const currentLocale = location.pathname.split('/')[3]; + + const formattedRefs: Record = {}; + + for (const ref of referenceConfig || []) { + if (!formattedRefs[ref.kind]) { + formattedRefs[ref.kind] = [ref]; + } else { + formattedRefs[ref.kind].push(ref); + } + } + + const formattedRefsArray = Object.entries(formattedRefs); + return ( ); diff --git a/website/app/components/mdx/GithubLink.tsx b/website/app/components/mdx/GithubLink.tsx new file mode 100644 index 00000000..175082f6 --- /dev/null +++ b/website/app/components/mdx/GithubLink.tsx @@ -0,0 +1,8 @@ +import { useDocumentationContext } from '~/context'; +import { DocsLink } from '../DocsLink'; + +export function GithubLink(props: { title: string; src: string }) { + const { owner, repo, ref } = useDocumentationContext(); + const href = `https://github.com/${owner}/${repo}/blob/${ref || 'main'}/${props.src}`; + return {props.title}; +} diff --git a/website/app/components/mdx/Tabs.tsx b/website/app/components/mdx/Tabs.tsx index 868ca4e5..31fa1a92 100644 --- a/website/app/components/mdx/Tabs.tsx +++ b/website/app/components/mdx/Tabs.tsx @@ -141,6 +141,7 @@ export function Tabs(props: TabsProps): JSX.Element { } // MDX wraps components in a `p` tag: https://github.com/mdx-js/mdx/issues/1451 + const tabs = extractTabItems(props.children); const [selected, setSelected] = useState(() => { @@ -184,7 +185,6 @@ export function Tabs(props: TabsProps): JSX.Element { `} {tabs.map(child => { const tab = props.values.find(v => v.value === child.props.value); - // Ensure the TabItem actually matches the values provided if (!tab) { return null; diff --git a/website/app/components/mdx/index.tsx b/website/app/components/mdx/index.tsx index 8deeaf59..8d992c87 100644 --- a/website/app/components/mdx/index.tsx +++ b/website/app/components/mdx/index.tsx @@ -7,6 +7,7 @@ import { Image } from './Image'; import { Table } from './Table'; import { Pre } from './Pre'; import { Tabs, TabItem } from './Tabs'; +import { GithubLink } from './GithubLink'; import { Vimeo } from './Vimeo'; import { Tweet } from './Tweet'; @@ -70,6 +71,7 @@ export default { Image, Tabs, TabItem, + GithubLink, Tweet, Vimeo, }; diff --git a/website/app/context.tsx b/website/app/context.tsx index 089a74d9..bee6be06 100644 --- a/website/app/context.tsx +++ b/website/app/context.tsx @@ -37,11 +37,15 @@ export function useDocumentationContext() { export function useBaseUrl(): string { const { domain } = useCustomDomain(); + const { owner, repo } = React.useContext(DocumentationContext); const { ref } = React.useContext(DocumentationContext); let url = '/'; + if (!domain) { + url += `${owner}/${repo}`; + } if (ref && !domain) { url += `${ref === 'HEAD' ? '' : `~${encodeURIComponent(ref)}`}`; } diff --git a/website/app/loaders/documentation.server.ts b/website/app/loaders/documentation.server.ts index e9cb954a..6220a9ab 100644 --- a/website/app/loaders/documentation.server.ts +++ b/website/app/loaders/documentation.server.ts @@ -46,6 +46,14 @@ export type DocumentationLoader = { frontmatter: BundleSuccess['frontmatter']; // base branch baseBranch: string; + // + referenceConfig?: + | { + name: string; + kind: string; + path: string; + }[] + | null; }; // Utility to guard against a bundler error. @@ -118,6 +126,7 @@ export const docsLoader: LoaderFunction = async ({ params }) => { config: mergeConfig(response.config), frontmatter: response.frontmatter, baseBranch: response.baseBranch ?? 'main', + referenceConfig: response.referenceConfig, }, { headers: { diff --git a/website/app/utils/config.ts b/website/app/utils/config.ts index 6a2376de..9eb08abc 100644 --- a/website/app/utils/config.ts +++ b/website/app/utils/config.ts @@ -98,6 +98,10 @@ export interface ProjectConfig { experimentalCodehike: boolean; // Whether Math is enabled experimentalMath: boolean; + // The name of the reference subdirectoy (of docs) and reference route. + references: string; + // The entry directory of the typedoc command + typedocEntryDir: string; } export const defaultConfig: ProjectConfig = { @@ -117,6 +121,8 @@ export const defaultConfig: ProjectConfig = { zoomImages: false, experimentalCodehike: false, experimentalMath: false, + references: 'API', + typedocEntryDir: 'src', }; // Merges any user config with default values. @@ -151,6 +157,8 @@ export function mergeConfig(json: Record): ProjectConfig { defaultConfig.experimentalCodehike, ), experimentalMath: getBoolean(json, 'experimentalMath', defaultConfig.experimentalMath), + references: getString(json, 'references', defaultConfig.references), + typedocEntryDir: getString(json, 'typedocEntryDir', defaultConfig.typedocEntryDir), }; } diff --git a/yarn.lock b/yarn.lock index 279c8556..259df96e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -733,24 +733,24 @@ "@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/resolve-uri@^3.0.3": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" - integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + version "3.0.6" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.6.tgz#4ac237f4dabc8dd93330386907b97591801f7352" + integrity sha512-R7xHtBSNm+9SyvpJkdQl+qrM3Hm2fea3Ef197M3mUug+v+yR+Rhfbs7PBtcBUVnIWJ4JcAdjvij+c8hXS9p5aw== "@jridgewell/set-array@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" - integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.0.tgz#1179863356ac8fbea64a5a4bcde93a4871012c01" + integrity sha512-SfJxIxNVYLTsKwzB3MoOQ1yxf4w/E6MdkvTgrgAt1bfxjSrLUoHMKrDOykwN14q65waezZIdqDneUIPh4/sKxg== "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.13" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" - integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + version "1.4.11" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" + integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== "@jridgewell/trace-mapping@^0.3.9": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" - integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -2172,7 +2172,7 @@ arrify@^1.0.1: assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= ast-types@0.14.2: version "0.14.2"