mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
## Problem Currently troubleshooting guides merged in a private repository trigger an action to open a PR against our Docs project. From now on, we want the `kb` to host the troubleshooting guides and take care of all the Github discussion process while improving some of its parts. ## Solution With this PR now: - All files and scripts related to handling the actions and processes for handling troubleshooting entries and GitHub Discussion creation on Docs are deleted. - A new workflow to sync troubleshooting guides with Supabase's `troubleshooting_entries` table was created with some improvements. - Now troubleshooting entries are fetched at pre-build in `kb` project directory. - When troubleshooting guides are created a new script detects this, creates the GitHub discussion for it, extracts the URL, and pushes the row to a db table at `push`. - When troubleshooting guides are updated, a checksum holding its title and body is compared, and the GitHub discussion content gets updated. _Note: all troubleshooting content hasn't been migrated for **kb**, so content not ready is filtered and not placed in the directory. This allow us to progressively migrate all pages without breaking the build._ <!-- ## Preview links If relevant, include links to changed pages for easy review access. Copy the preview base URL from the Vercel bot comment on this PR. Use the following table as an example template. | Site | Live | Preview | Search for | | -------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------- | | WWW | [/blog/your-post](https://supabase.com/blog/your-post) | [/blog/your-post](https://zone-www-dot-com-git-branch-name-supabase.vercel.app/blog/your-post) | unique phrase from the change | | Docs | [/docs/guides/your-page](https://supabase.com/docs/guides/your-page) | [/docs/guides/your-page](https://docs-git-branch-name-supabase.vercel.app/docs/guides/your-page) | unique phrase from the change | | Studio | [/dashboard](https://supabase.com/dashboard) | [/dashboard](https://studio-git-branch-name-supabase.vercel.app/dashboard) | unique phrase from the change | | Design system | [/design-system](https://supabase.com/design-system) | [/design-system](https://design-system-git-branch-name-supabase.vercel.app/design-system) | unique phrase from the change | | UI library | [/library](https://supabase.com/library) | [/library](https://ui-library-git-branch-name-supabase.vercel.app/library) | unique phrase from the change | | Knowledge base | [/kb/guides/your-page](https://supabase.com/kb/guides/your-page) | [/kb/guides/your-page](https://kb-git-branch-name-supabase.vercel.app/kb/guides/your-page) | unique phrase from the change | --> <!-- ## Additional context Optionally add any other context or screenshots. --> ## Review instructions For the database workflow, sadly, we will have to wait and monitor, but the fetching mechanism can be tested. 1. Go to the preview link for `kb` 2. Go to Topics > Troubleshooting 3. A new guide should be visible "Partial migrations and schema inconsistencies from embedded transactions", click on its link 4. A page showing the guide's content should render <img width="1022" height="810" alt="Screenshot 2026-10-07 at 13 19 17" src="https://github.com/user-attachments/assets/cbd25a60-8721-4329-907f-f92de894c217" /> ## Checklist Check all before review: - [x] I have read [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) - [x] If I wrote a new docs topic or edited an existing topic, I used the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs [style guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
198 lines
5.3 KiB
JavaScript
198 lines
5.3 KiB
JavaScript
// @ts-check
|
|
|
|
/**
|
|
* This file is for utils needed in both the Next.js app build and other
|
|
* scripts that read troubleshooting entries (e.g. search embeddings
|
|
* generation). Any dependencies that are needed in both places are defined
|
|
* here, and then typed in Troubleshooting.utils.ts as required.
|
|
*/
|
|
|
|
import matter from 'gray-matter'
|
|
import { fromMarkdown } from 'mdast-util-from-markdown'
|
|
import { gfmFromMarkdown, gfmToMarkdown } from 'mdast-util-gfm'
|
|
import { mdxFromMarkdown } from 'mdast-util-mdx'
|
|
import { toMarkdown } from 'mdast-util-to-markdown'
|
|
import { gfm } from 'micromark-extension-gfm'
|
|
import { mdxjs } from 'micromark-extension-mdxjs'
|
|
import { readdir, readFile, stat } from 'node:fs/promises'
|
|
import { join, sep } from 'node:path'
|
|
import { parse } from 'smol-toml'
|
|
import { visit } from 'unist-util-visit'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import { z } from 'zod'
|
|
|
|
export const TROUBLESHOOTING_DIRECTORY = join(process.cwd(), 'content/troubleshooting')
|
|
|
|
/**
|
|
* @typedef {Object} TroubleshootingEntry
|
|
* @property {string} filePath
|
|
* @property {string} content
|
|
* @property {string} contentWithoutJsx
|
|
* @property {TroubleshootingMetadata} data
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} TroubleshootingMetadata
|
|
* @property {string} title
|
|
* @property {string[]} topics
|
|
* @property {string[]} [keywords]
|
|
* @property {Object} [api]
|
|
* @property {string[]} [api.sdk]
|
|
* @property {string[]} [api.management_api]
|
|
* @property {string[]} [api.cli]
|
|
* @property {AssociatedError[]} [errors]
|
|
* @property {string} database_id
|
|
* @property {string} [github_url]
|
|
* @property {Date} [date_created]
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} AssociatedError
|
|
* @property {number} [http_status_code]
|
|
* @property {string} [code]
|
|
* @property {string} [message]
|
|
*/
|
|
|
|
export const TroubleshootingSchema = z
|
|
.object({
|
|
title: z.string(),
|
|
topics: z.array(
|
|
z.enum([
|
|
'ai',
|
|
'ai-tools',
|
|
'api',
|
|
'auth',
|
|
'branching',
|
|
'cli',
|
|
'database',
|
|
'functions',
|
|
'platform',
|
|
'realtime',
|
|
'self-hosting',
|
|
'storage',
|
|
'studio',
|
|
'supavisor',
|
|
'terraform',
|
|
])
|
|
),
|
|
keywords: z.array(z.string()).optional(),
|
|
api: z
|
|
.object({
|
|
sdk: z.array(z.string()).optional(),
|
|
management_api: z.array(z.string()).optional(),
|
|
cli: z.array(z.string()).optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
errors: z
|
|
.array(
|
|
z
|
|
.object({
|
|
http_status_code: z.number().optional(),
|
|
code: z.string().optional(),
|
|
message: z.string().optional(),
|
|
})
|
|
.strict()
|
|
)
|
|
.optional(),
|
|
database_id: z.string().default(`pseudo-${uuidv4()}`),
|
|
github_url: z.string().url().optional(),
|
|
date_created: z.date({ coerce: true }).optional(),
|
|
})
|
|
.strict()
|
|
|
|
/**
|
|
* @param {unknown} troubleshootingMetadata
|
|
*/
|
|
function validateTroubleshootingMetadata(troubleshootingMetadata) {
|
|
return TroubleshootingSchema.safeParse(troubleshootingMetadata)
|
|
}
|
|
|
|
export async function getAllTroubleshootingEntriesInternal() {
|
|
const troubleshootingDirectoryContents = await readdir(TROUBLESHOOTING_DIRECTORY, {
|
|
recursive: true,
|
|
})
|
|
const troubleshootingFiles = troubleshootingDirectoryContents.map(async (entry) => {
|
|
const isHidden = entry.startsWith('_')
|
|
if (isHidden) return null
|
|
|
|
const filePath = join(TROUBLESHOOTING_DIRECTORY, entry)
|
|
|
|
const isFile = (await stat(filePath)).isFile()
|
|
if (!isFile) return null
|
|
|
|
const fileContents = await readFile(filePath, 'utf-8')
|
|
const { content, data: frontmatter } = matter(fileContents, {
|
|
language: 'toml',
|
|
engines: { toml: parse },
|
|
})
|
|
|
|
const parseResult = validateTroubleshootingMetadata(frontmatter)
|
|
if ('error' in parseResult) {
|
|
throw Error(
|
|
`Error validating troubleshooting metadata for ${filePath}`,
|
|
{ cause: parseResult.error }
|
|
)
|
|
}
|
|
|
|
const mdxTree = fromMarkdown(content, {
|
|
extensions: [gfm(), mdxjs()],
|
|
mdastExtensions: [gfmFromMarkdown(), mdxFromMarkdown()],
|
|
})
|
|
visit(mdxTree, (node) => {
|
|
if ('children' in node) {
|
|
node.children = node.children.filter(
|
|
(child) =>
|
|
![
|
|
'mdxJsxFlowExpression',
|
|
'mdxJsxTextExpression',
|
|
'mdxFlowExpression',
|
|
'mdxTextExpression',
|
|
'mdxJsxFlowElement',
|
|
'mdxJsxTextElement',
|
|
'mdxJsxExpressionAttribute',
|
|
'mdxJsxAttribute',
|
|
'mdxJsxAttributeValueExpression',
|
|
'mdxjsEsm',
|
|
].includes(child.type)
|
|
)
|
|
}
|
|
|
|
if (node.type === 'link' || node.type === 'image') {
|
|
canonicalizeUrl(node)
|
|
}
|
|
})
|
|
|
|
const contentWithoutJsx = toMarkdown(mdxTree, {
|
|
extensions: [gfmToMarkdown()],
|
|
})
|
|
|
|
return {
|
|
filePath,
|
|
content,
|
|
contentWithoutJsx,
|
|
data: parseResult.data,
|
|
}
|
|
})
|
|
|
|
return (await Promise.all(troubleshootingFiles)).filter((x) => x != null)
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('mdast').Image | import('mdast').Link} node
|
|
*/
|
|
function canonicalizeUrl(node) {
|
|
if (node.url.startsWith('/')) {
|
|
node.url === 'https://supabase.com' + node.url
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {TroubleshootingEntry} entry
|
|
*/
|
|
export function getArticleSlug(entry) {
|
|
const parts = entry.filePath.split(sep)
|
|
return parts[parts.length - 1].replace(/\.mdx$/, '')
|
|
}
|