@graphcommerce/docs 3.1.4 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/{content/framework → framework}/deployment.md +0 -0
  3. package/{content/framework → framework}/environment-variables.md +0 -0
  4. package/{content/framework → framework}/favicon.md +0 -0
  5. package/{content/framework → framework}/graphcms.md +0 -0
  6. package/{content/framework → framework}/icons.md +0 -0
  7. package/{content/framework → framework}/readme.md +0 -0
  8. package/{content/framework → framework}/seo.md +0 -0
  9. package/{content/framework → framework}/static-file-serving.md +0 -0
  10. package/{content/framework → framework}/static-generation.md +0 -0
  11. package/{content/framework → framework}/theming.md +0 -0
  12. package/{content/framework → framework}/translations.md +0 -0
  13. package/{content/framework → framework}/troubleshooting.md +0 -0
  14. package/{content/framework → framework}/typography.md +0 -0
  15. package/{content/getting-started → getting-started}/create.md +0 -0
  16. package/{content/getting-started → getting-started}/graphcms-component.md +0 -0
  17. package/{content/getting-started → getting-started}/header.md +0 -0
  18. package/{content/getting-started → getting-started}/pages.md +0 -0
  19. package/{content/getting-started → getting-started}/readme.md +0 -0
  20. package/{content/getting-started → getting-started}/start-building.md +0 -0
  21. package/{content/getting-started → getting-started}/vscode.md +0 -0
  22. package/package.json +6 -51
  23. package/{content/readme.md → readme.md} +0 -0
  24. package/{content/roadmap.md → roadmap.md} +0 -0
  25. package/.babelrc +0 -4
  26. package/components/Layout/LayoutFull.tsx +0 -85
  27. package/components/Layout/Logo.tsx +0 -19
  28. package/components/Layout/graphcommerce.svg +0 -34
  29. package/components/Search.tsx +0 -37
  30. package/components/SearchForm.tsx +0 -110
  31. package/components/SidebarMenu/index.tsx +0 -101
  32. package/components/prism.css +0 -274
  33. package/components/rehype-prism-plus.css +0 -49
  34. package/components/theme.ts +0 -410
  35. package/lib/DocumentIndexer.ts +0 -59
  36. package/lib/files.ts +0 -168
  37. package/lib/instantSearch.ts +0 -26
  38. package/lib/typesense/IndexerHandler.ts +0 -47
  39. package/lib/typesense/Leaves.ts +0 -37
  40. package/lib/typesense/SearchIndexer.ts +0 -64
  41. package/lib/typesense/batchInterable.ts +0 -13
  42. package/lib/typesense/createInstantSearchProps.ts +0 -36
  43. package/lib/typesense/typesenseClientConf.ts +0 -23
  44. package/lib/typesense/typesenseIndexerHandler.ts +0 -23
  45. package/next-env.d.ts +0 -5
  46. package/next.config.js +0 -21
  47. package/pages/[[...url]].tsx +0 -391
  48. package/pages/_app.tsx +0 -26
  49. package/pages/_document.tsx +0 -22
  50. package/pages/api/reindex.ts +0 -4
  51. package/pages/menu/[[...url]].tsx +0 -69
  52. package/public/apple-touch-icon.png +0 -0
  53. package/public/favicon.ico +0 -0
  54. package/public/favicon.svg +0 -12
  55. package/public/link.svg +0 -4
  56. package/public/manifest/favicon-192.png +0 -0
  57. package/public/manifest/favicon-512.png +0 -0
  58. package/public/manifest.webmanifest +0 -20
@@ -1,59 +0,0 @@
1
- /* eslint-disable class-methods-use-this */
2
- import path from 'path'
3
- import { remark } from 'remark'
4
- import remarkGfm from 'remark-gfm'
5
- import strip from 'strip-markdown'
6
- import { toVFile as vfile } from 'to-vfile'
7
- import { matter } from 'vfile-matter'
8
- import { FileNode, findByUrl, getDirectoryPaths, getDirectoryTree } from './files'
9
- import { indexName } from './instantSearch'
10
- import { BaseDocument, SearchIndexer } from './typesense/SearchIndexer'
11
-
12
- export type DocumentationDocument = BaseDocument & {
13
- url: string
14
- content: string
15
- name: string
16
- }
17
-
18
- export class DocumentIndexer extends SearchIndexer<DocumentationDocument> {
19
- #root: string
20
-
21
- constructor(root: string) {
22
- super({
23
- name: indexName,
24
- fields: {
25
- id: { type: 'string' },
26
- name: { type: 'string' },
27
- url: { type: 'string' },
28
- content: { type: 'string' },
29
- },
30
- })
31
- this.#root = root
32
- }
33
-
34
- // load a single document
35
- async #loadDocument(node?: FileNode | false): Promise<DocumentationDocument> {
36
- if (!node) throw Error('Node not found')
37
-
38
- const file = path.join(process.cwd(), this.#root, node.path)
39
- const res = matter(await vfile.read(file), { strip: true })
40
- const content = (await remark().use(strip).process(res)).value.toString()
41
-
42
- if (!content) throw Error('Can not load file')
43
-
44
- const { name, url } = node
45
- return { id: node.path, content, url, name: node.matter.menu ?? name }
46
- }
47
-
48
- async *all() {
49
- const paths = await getDirectoryPaths(this.#root)
50
- const menuData = await getDirectoryTree(this.#root)
51
- if (!menuData) return false
52
-
53
- for (const p of paths) {
54
- yield this.#loadDocument(findByUrl(p.split('/'), menuData))
55
- }
56
-
57
- return true
58
- }
59
- }
package/lib/files.ts DELETED
@@ -1,168 +0,0 @@
1
- import fs from 'fs'
2
- import path from 'path'
3
- import { toVFile as vfile } from 'to-vfile'
4
- import { matter } from 'vfile-matter'
5
-
6
- export type MatterFields = {
7
- menu?: string
8
- order?: string
9
- metaTitle?: string
10
- metaDescription?: string
11
- }
12
-
13
- type BaseFields = {
14
- type: 'folder' | 'file'
15
- path: string
16
- name: string
17
- childNodes?: FileOrFolderNode[]
18
- matter?: MatterFields
19
- }
20
-
21
- export type FolderNode = BaseFields & { type?: 'folder' }
22
- export type FileNode = BaseFields & { type?: 'file'; url: string; matter: MatterFields }
23
-
24
- export type FileOrFolderNode = FolderNode | FileNode
25
-
26
- function toUrl(p: string) {
27
- let url = p.replace('.mdx', '')
28
- url = url.replace('.md', '')
29
- url = url.endsWith('/') ? url.slice(0, -1) : url
30
- return url
31
- }
32
-
33
- async function dirTree(dir: string, root: string): Promise<FileOrFolderNode> {
34
- const stats = await fs.promises.lstat(dir)
35
-
36
- let name = path.basename(dir)
37
- name = name.replace('.mdx', '')
38
- name = name.replace('.md', '')
39
- name = name.replace(/-/g, ' ').replace(/^./, (x) => x.toUpperCase())
40
-
41
- const filePath = path.relative(root, dir)
42
-
43
- const info: Partial<FileOrFolderNode> = { path: filePath, name }
44
-
45
- if (stats.isDirectory()) {
46
- info.type = 'folder'
47
- info.childNodes = await Promise.all(
48
- (await fs.promises.readdir(dir)).map((child) => dirTree(`${dir}/${child}`, root)),
49
- )
50
- } else {
51
- info.type = 'file'
52
- }
53
-
54
- if (info.type === 'file') {
55
- info.url = toUrl(path.relative(root, dir))
56
- info.matter = matter(await vfile.read(dir)).data.matter as Record<string, string>
57
- }
58
-
59
- return info as FileOrFolderNode
60
- }
61
-
62
- // Replace the path of the parent with the path of the child having index.mdx as name and remove from children.
63
- // Do this recursively for each child.
64
- // Remove type from tree
65
- function hoistIndex(tree: FileOrFolderNode): FileOrFolderNode {
66
- let newTree: FileOrFolderNode = tree
67
-
68
- if (newTree.type === 'folder') {
69
- const index = newTree.childNodes?.find(
70
- (child) =>
71
- child.path.endsWith('readme.mdx') ||
72
- (child.path.endsWith('readme.md') && child.type === 'file'),
73
- ) as FileNode | undefined
74
-
75
- if (index) {
76
- newTree = { ...index, name: tree.name, url: index.url.slice(0, -7) }
77
- newTree.childNodes = tree.childNodes?.filter((child) => child !== index)
78
-
79
- const order = index.matter?.order?.split(',').map((x) => x.trim())
80
- if (order) {
81
- newTree.childNodes = newTree.childNodes?.sort((a, b) => {
82
- const aPath = toUrl(a.path.split('/').pop() as string)
83
- const bPath = toUrl(b.path.split('/').pop() as string)
84
- return order.indexOf(aPath) === -1 ? 1 : order.indexOf(aPath) - order.indexOf(bPath)
85
- })
86
- }
87
-
88
- newTree.childNodes = newTree.childNodes?.map((child) => hoistIndex(child))
89
- }
90
- }
91
- return newTree
92
- }
93
-
94
- export async function getDirectoryTree(dir: string): Promise<false | FileNode> {
95
- const absDir = path.join(process.cwd(), dir)
96
-
97
- const tree = await dirTree(absDir, absDir)
98
-
99
- return hoistIndex(tree) as FileNode
100
- }
101
-
102
- export async function getDirectoryPaths(dir: string) {
103
- const menuData = await getDirectoryTree(dir)
104
-
105
- const paths: string[] = []
106
- const addPathsFromTree = (tree: FileOrFolderNode) => {
107
- if (tree.type === 'file') paths.push(tree.url)
108
-
109
- if (tree.childNodes?.length) {
110
- tree.childNodes.forEach((child) => addPathsFromTree(child))
111
- }
112
- }
113
- if (menuData) addPathsFromTree(menuData)
114
-
115
- return paths
116
- }
117
-
118
- /**
119
- * Recursively traverse the tree and return the aggregated path of of each node.
120
- *
121
- * Each URL segment should reference a path (except for the readme.md/readme.mdx file)
122
- */
123
- export function urlToPath(url: string[], node: FileOrFolderNode): string | false {
124
- if (node.childNodes?.length) {
125
- const child = node.childNodes?.reduce<string | false>((prev, curr) => {
126
- if (prev) return prev
127
- const childPath = urlToPath(url, curr)
128
- return childPath ?? prev
129
- }, false)
130
-
131
- if (child) return child
132
- }
133
-
134
- if (node.type === 'file' && node.url === url.join('/')) return node.path
135
-
136
- return false
137
- }
138
-
139
- /** Get the contens of the requested file. */
140
- export function getFileContents(dir: string, filePath: string) {
141
- const absDir = path.join(process.cwd(), dir, filePath)
142
- try {
143
- return fs.promises.readFile(absDir, 'utf8')
144
- } catch (e) {
145
- return false
146
- }
147
- }
148
-
149
- /**
150
- * Recursively traverse the tree and return the aggregated path of of each node.
151
- *
152
- * Each URL segment should reference a path (except for the readme.md/readme.mdx file)
153
- */
154
- export function findByUrl(url: string[], node: FileOrFolderNode): FileNode | false {
155
- if (node.childNodes?.length) {
156
- const child = node.childNodes?.reduce<FileNode | false>((prev, curr) => {
157
- if (prev) return prev
158
- const childPath = findByUrl(url, curr)
159
- return childPath ?? prev
160
- }, false)
161
-
162
- if (child) return child
163
- }
164
-
165
- if (node.type === 'file' && node.url === url.join('/')) return node
166
-
167
- return false
168
- }
@@ -1,26 +0,0 @@
1
- import type { HitsRenderState } from 'instantsearch.js/es/connectors/hits/connectHits'
2
- import { useHits as useHitsBase } from 'react-instantsearch-hooks'
3
- import { SetReturnType } from 'type-fest'
4
- import type { DocumentationDocument } from './DocumentIndexer'
5
- import { createInstantSearchProps } from './typesense/createInstantSearchProps'
6
-
7
- export const indexName = 'documents'
8
-
9
- export const instantSearchProps = createInstantSearchProps<DocumentationDocument>(indexName, {
10
- query_by: {
11
- name: 2,
12
- content: 1,
13
- },
14
- numTypos: '1',
15
- typoTokensThreshold: 1,
16
- highlight_fields: 'content',
17
- highlight_full_fields: 'name',
18
- include_fields: 'name,url',
19
- // highlight_affix_num_tokens: ,
20
- })
21
-
22
- const useHits: SetReturnType<typeof useHitsBase, HitsRenderState<DocumentationDocument>> = (
23
- ...params
24
- ) => useHitsBase(...params) as HitsRenderState<DocumentationDocument>
25
-
26
- export { useHits }
@@ -1,47 +0,0 @@
1
- import { Client } from 'typesense/lib/Typesense'
2
- import { ConfigurationOptions } from 'typesense/lib/Typesense/Configuration'
3
- import { ObjectNotFound } from 'typesense/lib/Typesense/Errors'
4
- import { BaseDocument, SearchIndexer } from './SearchIndexer'
5
- import { batchIterable } from './batchInterable'
6
-
7
- export class IndexerHandler {
8
- client: Client
9
-
10
- constructor(
11
- clientOptions: ConfigurationOptions,
12
- public indexers: SearchIndexer<BaseDocument>[],
13
- private batchSize: number = 1000,
14
- ) {
15
- this.client = new Client(clientOptions)
16
- }
17
-
18
- async all() {
19
- let count = 0
20
- for await (const indexer of this.indexers) {
21
- const resCount = await this.#reindex(indexer)
22
- count += resCount
23
- }
24
- return count
25
- }
26
-
27
- /** @throws {TypesenseError} */
28
- async #reindex(indexer: SearchIndexer<BaseDocument>) {
29
- const { name } = indexer.schema
30
-
31
- try {
32
- await this.client.collections(name).retrieve()
33
- await this.client.collections(name).delete()
34
- } catch (e) {
35
- if (!(e instanceof ObjectNotFound)) throw e
36
- }
37
- await this.client.collections().create(indexer.schema)
38
-
39
- let count = 0
40
- for await (const documents of batchIterable(indexer.all(), this.batchSize)) {
41
- const result = await this.client.collections(name).documents().import(documents)
42
- count += result.length
43
- }
44
-
45
- return count
46
- }
47
- }
@@ -1,37 +0,0 @@
1
- export type Leaves<T, D extends number = 10> = [D] extends [never]
2
- ? never
3
- : T extends object
4
- ? { [K in keyof T]-?: Join<K, Leaves<T[K], Prev[D]>> }[keyof T]
5
- : ''
6
-
7
- type Prev = [
8
- never,
9
- 0,
10
- 1,
11
- 2,
12
- 3,
13
- 4,
14
- 5,
15
- 6,
16
- 7,
17
- 8,
18
- 9,
19
- 10,
20
- 11,
21
- 12,
22
- 13,
23
- 14,
24
- 15,
25
- 16,
26
- 17,
27
- 18,
28
- 19,
29
- 20,
30
- ...0[]
31
- ]
32
-
33
- type Join<K, P> = K extends string | number
34
- ? P extends string | number
35
- ? `${K}${'' extends P ? '' : '.'}${P}`
36
- : never
37
- : never
@@ -1,64 +0,0 @@
1
- import { ConditionalExcept, Get } from 'type-fest'
2
- import { CollectionFieldSchema } from 'typesense/lib/Typesense/Collection'
3
- import { CollectionCreateSchema } from 'typesense/lib/Typesense/Collections'
4
- import { Leaves } from './Leaves'
5
-
6
- type FieldValues = Record<string, any>
7
-
8
- type SchemaType<
9
- TFieldValues extends FieldValues,
10
- TFieldPath extends Leaves<TFieldValues>,
11
- T = Get<TFieldValues, TFieldPath>,
12
- > = T extends object
13
- ? never
14
- : T extends string
15
- ? 'string'
16
- : T extends number
17
- ? 'number'
18
- : T extends boolean
19
- ? 'boolean'
20
- : T
21
-
22
- type OptionalType<
23
- TFieldValues extends FieldValues,
24
- TFieldPath extends Leaves<TFieldValues>,
25
- T = Get<TFieldValues, TFieldPath>,
26
- > = T extends undefined ? true : never
27
-
28
- export type BaseDocument = {
29
- id: string
30
- }
31
-
32
- export type CreateSchema<Schema extends BaseDocument> = Omit<CollectionCreateSchema, 'fields'> & {
33
- fields: {
34
- [K in Leaves<Schema>]: ConditionalExcept<
35
- {
36
- type: SchemaType<Schema, K>
37
- optional: OptionalType<Schema, K>
38
- } & Omit<CollectionFieldSchema, 'type' | 'optional' | 'name'>,
39
- never
40
- >
41
- }
42
- }
43
-
44
- export abstract class SearchIndexer<T extends BaseDocument> {
45
- name: string
46
-
47
- schema: CollectionCreateSchema
48
-
49
- constructor(schema: CreateSchema<T>) {
50
- this.name = schema.name
51
-
52
- const fields = Object.entries(schema.fields).map(
53
- ([name, field]) =>
54
- ({
55
- name,
56
- ...(field as Omit<CollectionFieldSchema, 'name'>),
57
- } as CollectionFieldSchema),
58
- )
59
-
60
- this.schema = { ...schema, fields }
61
- }
62
-
63
- abstract all(): AsyncGenerator<T>
64
- }
@@ -1,13 +0,0 @@
1
- export async function* batchIterable<T>(iterable: AsyncIterableIterator<T>, batchSize: number) {
2
- let items: T[] = []
3
- for await (const item of iterable) {
4
- items.push(item)
5
- if (items.length >= batchSize) {
6
- yield items
7
- items = []
8
- }
9
- }
10
- if (items.length !== 0) {
11
- yield items
12
- }
13
- }
@@ -1,36 +0,0 @@
1
- import { InstantSearchProps } from 'react-instantsearch-hooks'
2
- import TypesenseInstantSearchAdapter from 'typesense-instantsearch-adapter'
3
- import { Leaves } from './Leaves'
4
- import { BaseDocument } from './SearchIndexer'
5
- import { typesenseClientConf } from './typesenseClientConf'
6
-
7
- type BaseParams = Omit<
8
- NonNullable<
9
- ConstructorParameters<typeof TypesenseInstantSearchAdapter>[0]['additionalSearchParameters']
10
- >,
11
- 'query_by' | 'query_by_weight'
12
- >
13
-
14
- type InstantSearchClientParams<Document extends BaseDocument> = BaseParams & {
15
- query_by: {
16
- [key in Leaves<Document>]?: number
17
- }
18
- }
19
-
20
- export function createInstantSearchProps<Document extends BaseDocument>(
21
- indexName: string,
22
- params: InstantSearchClientParams<Document>,
23
- ): InstantSearchProps {
24
- const { query_by, ...rest } = params
25
-
26
- const { searchClient } = new TypesenseInstantSearchAdapter({
27
- server: typesenseClientConf(),
28
- additionalSearchParameters: {
29
- query_by: Object.keys(query_by).join(','),
30
- query_by_weights: Object.values(query_by).join(','),
31
- ...rest,
32
- },
33
- })
34
-
35
- return { searchClient, indexName }
36
- }
@@ -1,23 +0,0 @@
1
- import { ConfigurationOptions } from 'typesense/lib/Typesense/Configuration'
2
-
3
- export function typesenseClientConf() {
4
- // Be sure to use an API key that only allows searches, in production
5
- const apiKey = process.env.NEXT_PUBLIC_TYPESENSE_SEARCH_ONLY_API_KEY
6
- const host = process.env.NEXT_PUBLIC_TYPESENSE_HOST
7
- const port = Number(process.env.NEXT_PUBLIC_TYPESENSE_PORT)
8
- const protocol = process.env.NEXT_PUBLIC_TYPESENSE_PROTOCOL
9
-
10
- if (!apiKey) throw Error('Please provide NEXT_PUBLIC_TYPESENSE_SEARCH_ONLY_API_KEY')
11
- if (!host) throw Error('Please provide NEXT_PUBLIC_TYPESENSE_HOST')
12
- if (!port) throw Error('Please provide NEXT_PUBLIC_TYPESENSE_PORT')
13
- if (!protocol) throw Error('Please provide NEXT_PUBLIC_TYPESENSE_PROTOCOL')
14
-
15
- const conf: ConfigurationOptions = {
16
- apiKey,
17
- nodes: [{ host, port, protocol }],
18
- numRetries: 8,
19
- connectionTimeoutSeconds: 1,
20
- }
21
-
22
- return conf
23
- }
@@ -1,23 +0,0 @@
1
- import type { NextApiRequest, NextApiResponse } from 'next'
2
- import { TypesenseError } from 'typesense/lib/Typesense/Errors'
3
- import { IndexerHandler } from './IndexerHandler'
4
- import { BaseDocument, SearchIndexer } from './SearchIndexer'
5
- import { typesenseClientConf } from './typesenseClientConf'
6
-
7
- export function typesenseIndexerHandler(indexers: SearchIndexer<BaseDocument>[]) {
8
- return async (req: NextApiRequest, res: NextApiResponse) => {
9
- const handler = new IndexerHandler(typesenseClientConf(), indexers)
10
- try {
11
- const count = await handler.all()
12
- res.status(200).json({ status: 'success', count })
13
- } catch (e) {
14
- if (e instanceof TypesenseError) {
15
- res.json(e)
16
- }
17
- console.log(e)
18
- res.status(500)
19
- }
20
-
21
- res.end()
22
- }
23
- }
package/next-env.d.ts DELETED
@@ -1,5 +0,0 @@
1
- /// <reference types="next" />
2
- /// <reference types="next/image-types/global" />
3
-
4
- // NOTE: This file should not be edited
5
- // see https://nextjs.org/docs/basic-features/typescript for more information.
package/next.config.js DELETED
@@ -1,21 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-var-requires */
2
- const withYarn1Workspaces = require('@graphcommerce/next-config').withYarn1Scopes()
3
-
4
- /** @type {import('next').NextConfig} */
5
- const nextConfig = {
6
- // https://nextjs.org/docs/api-reference/next.config.js/configuring-onDemandEntries
7
- onDemandEntries: {
8
- maxInactiveAge: 10 * 60 * 1000, // 10 minutes
9
- },
10
- eslint: {
11
- ignoreDuringBuilds: true,
12
- },
13
- typescript: {
14
- ignoreBuildErrors: true,
15
- },
16
- experimental: {
17
- scrollRestoration: true,
18
- },
19
- }
20
-
21
- module.exports = withYarn1Workspaces(nextConfig)