From bdca2397bfcd9c5c7bd43cca47bcd8097414edb4 Mon Sep 17 00:00:00 2001 From: Mike Hartington Date: Sat, 21 Feb 2026 13:32:02 -0500 Subject: [PATCH 1/2] fix(): further validate 404 reports --- apps/docs/vercel.json | 430 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 415 insertions(+), 15 deletions(-) diff --git a/apps/docs/vercel.json b/apps/docs/vercel.json index 005aa3b5f8..4a7660de0d 100644 --- a/apps/docs/vercel.json +++ b/apps/docs/vercel.json @@ -5,7 +5,11 @@ "destination": "/docs", "permanent": true }, - { "source": "/docs/tags/apm", "destination": "/docs", "permanent": true }, + { + "source": "/docs/tags/apm", + "destination": "/docs", + "permanent": true + }, { "source": "/docs/tags/application-performance-monitoring", "destination": "/docs", @@ -16,7 +20,11 @@ "destination": "/docs", "permanent": true }, - { "source": "/docs/tags/ci-cd", "destination": "/docs", "permanent": true }, + { + "source": "/docs/tags/ci-cd", + "destination": "/docs", + "permanent": true + }, { "source": "/docs/tags/containerization", "destination": "/docs", @@ -67,7 +75,11 @@ "destination": "/docs", "permanent": true }, - { "source": "/docs/tags/git", "destination": "/docs", "permanent": true }, + { + "source": "/docs/tags/git", + "destination": "/docs", + "permanent": true + }, { "source": "/docs/tags/guides", "destination": "/docs", @@ -108,13 +120,21 @@ "destination": "/docs", "permanent": true }, - { "source": "/docs/tags/nuxt", "destination": "/docs", "permanent": true }, + { + "source": "/docs/tags/nuxt", + "destination": "/docs", + "permanent": true + }, { "source": "/docs/tags/optimization", "destination": "/docs", "permanent": true }, - { "source": "/docs/tags/orm", "destination": "/docs", "permanent": true }, + { + "source": "/docs/tags/orm", + "destination": "/docs", + "permanent": true + }, { "source": "/docs/tags/pnpm-workspace", "destination": "/docs", @@ -180,7 +200,11 @@ "destination": "/docs", "permanent": true }, - { "source": "/docs/tags/spans", "destination": "/docs", "permanent": true }, + { + "source": "/docs/tags/spans", + "destination": "/docs", + "permanent": true + }, { "source": "/docs/tags/sq-lite", "destination": "/docs", @@ -216,9 +240,21 @@ "destination": "/docs", "permanent": true }, - { "source": "/docs/search", "destination": "/docs", "permanent": true }, - { "source": "/docs/tags", "destination": "/docs", "permanent": true }, - { "source": "/docs/about", "destination": "/docs", "permanent": true }, + { + "source": "/docs/search", + "destination": "/docs", + "permanent": true + }, + { + "source": "/docs/tags", + "destination": "/docs", + "permanent": true + }, + { + "source": "/docs/about", + "destination": "/docs", + "permanent": true + }, { "source": "/docs/about/docs-components", "destination": "/docs", @@ -1509,7 +1545,11 @@ "destination": "/docs/postgres/database/caching", "permanent": true }, - { "source": "/docs/showcase", "destination": "/docs", "permanent": true }, + { + "source": "/docs/showcase", + "destination": "/docs", + "permanent": true + }, { "source": "/docs/getting-started/quickstart-prismaPostgres", "destination": "/docs/prisma-orm/quickstart/prisma-postgres", @@ -4975,11 +5015,6 @@ "destination": "/docs/prisma-orm/quickstart/sqlite", "permanent": true }, - { - "source": "/docs/img/guides/prisma-bun-cover-image.png", - "destination": "/docs/guides/runtimes/bun", - "permanent": true - }, { "source": "/docs/reference/tools-and-interfaces/prisma-client/crud", "destination": "/docs/orm/prisma-client/queries/crud", @@ -5209,6 +5244,371 @@ "source": "/docs/ai/prompt", "destination": "/docs/ai/prompts/prisma-7", "permanent": true + }, + { + "source": "/docs/orm/more/development-environment/environment-variables/managing-env-files-and-setting-variables", + "destination": "/docs/orm/more/dev-environment/environment-variables#using-multiple-env-files", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/setup-and-configuration/databases-connections/prisma-config-ts", + "destination": "/docs/orm/reference/prisma-config-reference", + "permanent": true + }, + { + "source": "/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-vercel", + "destination": "/docs/orm/more/troubleshooting/nextjs", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/typed-sql", + "destination": "/docs/orm/prisma-client/using-raw-sql/typedsql", + "permanent": true + }, + { + "source": "/docs/guides/upgrade-prisma-orm", + "destination": "/docs/guides/upgrade-prisma-orm/v7", + "permanent": true + }, + { + "source": "/docs/reference/api-reference/connection-management", + "destination": "/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management", + "permanent": true + }, + { + "source": "/docs/data-platform/accelerate/getting-started", + "destination": "/docs/accelerate/getting-started", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/using-prisma-migrate-typescript-mysql", + "destination": "/docs/prisma-orm/quickstart/mysql", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/install-prisma-client-typescript-postgresql", + "destination": "/docs/prisma-orm/quickstart/postgresql", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/adapter/sqlite", + "destination": "/docs/orm/core-concepts/supported-databases/sqlite", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases/mysql", + "destination": "/docs/prisma-orm/add-to-existing-project/mysql", + "permanent": true + }, + { + "source": "/docs/orm/more/help-and-troubleshooting/help-articles/prisma-client-esm", + "destination": "/docs/orm/more/troubleshooting/bundler-issues", + "permanent": true + }, + { + "source": "/docs/prisma-server/authentication-and-security-kke4", + "destination": "/docs/orm", + "permanent": true + }, + { + "source": "/docs/orm/troubleshooting/help-articles/nextjs-prisma-client-dev-practices", + "destination": "/docs/orm/more/troubleshooting/nextjs", + "permanent": true + }, + { + "source": "/docs/orm/guides/performance-and-optimization/connection-management", + "destination": "/docs/orm/prisma-client/setup-and-configuration/databases-connections/connection-management", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/start-from-scratch/python", + "destination": "/docs/prisma-orm/quickstart/prisma-postgres", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/add-to-existing-project/nextjs", + "destination": "/docs/guides/frameworks/nextjs", + "permanent": true + }, + { + "source": "/docs/orm/reference/driver-adapters", + "destination": "/docs/orm/core-concepts/supported-databases/database-drivers", + "permanent": true + }, + { + "source": "/docs/orm/prisma-schema/data-model/ids", + "destination": "/docs/orm/prisma-schema/data-model/models", + "permanent": true + }, + { + "source": "/docs/concepts/components/prisma-client/initialization", + "destination": "/docs/orm/prisma-client/setup-and-configuration/introduction", + "permanent": true + }, + { + "source": "/docs/pulse/what-is-pulse", + "destination": "/docs/postgres", + "permanent": true + }, + { + "source": "/docs/concepts/more/comparisons/prisma-and-typeorm", + "destination": "/docs/guides/switch-to-prisma-orm/from-sql-orms", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/queries/bulk-operations", + "destination": "/docs/orm/prisma-client/queries/crud", + "permanent": true + }, + { + "source": "/docs/pulse/getting-started", + "destination": "/docs/postgres", + "permanent": true + }, + { + "source": "/docs/data-platform/data-proxy/prisma-cli-with-data-proxy", + "destination": "/docs/accelerate/getting-started", + "permanent": true + }, + { + "source": "/docs/orm/prisma-schema/overview/generators%23field-reference-1", + "destination": "/docs/orm/prisma-schema/overview/generators", + "permanent": true + }, + { + "source": "/docs/reference/database-reference/prisma-postgres", + "destination": "/docs/postgres", + "permanent": true + }, + { + "source": "/docs/orm/prisma-schema/prisma-config-reference", + "destination": "/docs/orm/reference/prisma-config-reference", + "permanent": true + }, + { + "source": "/docs/orm/more/help-and-troubleshooting/help-articles/prisma-with-express", + "destination": "/docs/guides", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases-node-mysql", + "destination": "/docs/prisma-orm/add-to-existing-project/mysql", + "permanent": true + }, + { + "source": "/docs/orm/more/help-and-troubleshooting/help-articles/database-errors", + "destination": "/docs/orm/reference/error-reference", + "permanent": true + }, + { + "source": "/docs/5.4.1/reference/api-reference/prisma-client-reference", + "destination": "/docs/orm/reference/prisma-client-reference", + "permanent": true + }, + { + "source": "/docs/concepts/performance-and-optimization", + "destination": "/docs/orm/prisma-client/queries/advanced/query-optimization-performance", + "permanent": true + }, + { + "source": "/docs/guides/database/developing-with-prisma-migrate/team-development", + "destination": "/docs/orm/prisma-migrate/workflows/development-and-production", + "permanent": true + }, + { + "source": "/docs/understand-prisma/prisma-in-your-stack/graphql", + "destination": "/docs/orm", + "permanent": true + }, + { + "source": "/docs/guides/other/troubleshooting-orm/help-articles/nextjs-prisma-client-monorepo", + "destination": "/docs/orm/more/troubleshooting/nextjs", + "permanent": true + }, + { + "source": "/docs/orm/prisma-migrate/workflows/setting-up-prisma-migrate/creating-baseline", + "destination": "/docs/orm/prisma-migrate/workflows/baselining", + "permanent": true + }, + { + "source": "/docs/orm/more/prisma-config", + "destination": "/docs/orm/reference/prisma-config-reference", + "permanent": true + }, + { + "source": "/docs/orm/prisma-7", + "destination": "/docs/guides/upgrade-prisma-orm/v7", + "permanent": true + }, + { + "source": "/docs/orm/overview/what-is-prisma", + "destination": "/docs/orm", + "permanent": true + }, + { + "source": "/docs/orm/more/help-and-troubleshooting/help-articles/prisma-client-constructor-validation-error", + "destination": "/docs/orm/prisma-client/setup-and-configuration/error-formatting", + "permanent": true + }, + { + "source": "/docs/orm/more/deployment/deployment-guides/deploying-to-digitalocean", + "destination": "/docs/orm/prisma-client/deployment/deploy-prisma", + "permanent": true + }, + { + "source": "/docs/7/orm/prisma-client/setup-and-configuration/databases-connections", + "destination": "/docs/orm/prisma-client/setup-and-configuration/databases-connections", + "permanent": true + }, + { + "source": "/docs/orm/more/development-environment/prisma-config-ts", + "destination": "/docs/orm/reference/prisma-config-reference", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases/evolve-your-schema-node-mysql", + "destination": "/docs/prisma-orm/add-to-existing-project/mysql", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-node-mysql", + "destination": "/docs/prisma-orm/quickstart/mysql", + "permanent": true + }, + { + "source": "/docs/reference/api-reference/prisma-schema-reference/indexes", + "destination": "/docs/orm/prisma-schema/data-model/indexes", + "permanent": true + }, + { + "source": "/docs/getting-started/prisma-orm/add-to-existing-project", + "destination": "/docs/prisma-orm/add-to-existing-project/prisma-postgres", + "permanent": true + }, + { + "source": "/docs/www.google.com", + "destination": "/docs", + "permanent": true + }, + { + "source": "/docs/reference/tools-and-interfaces/prisma-client/advanced-usage-of-generated-types", + "destination": "/docs/orm/prisma-client/type-safety/operating-against-partial-structures-of-model-types", + "permanent": true + }, + { + "source": "/docs/orm/prisma-schema/data-model/data-types/json", + "destination": "/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields", + "permanent": true + }, + { + "source": "/docs/orm/prisma-schema/data-model/composite-ids", + "destination": "/docs/orm/prisma-client/special-fields-and-types/working-with-composite-ids-and-constraints", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/deployment/edge-runtime", + "destination": "/docs/orm/prisma-client/deployment/edge/overview", + "permanent": true + }, + { + "source": "/docs/orm/more/development-environment/electron", + "destination": "/docs/orm/more/troubleshooting/bundler-issues", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/setup-and-configuration/databases-connections/postgresql", + "destination": "/docs/orm/core-concepts/supported-databases/postgresql", + "permanent": true + }, + { + "source": "/docs/guides/bun-workspaces", + "destination": "/docs/guides/deployment/bun-workspaces", + "permanent": true + }, + { + "source": "/docs/orm/more/help-and-troubleshooting/help-articles/prisma-client-cannot-find-module", + "destination": "/docs/orm/more/troubleshooting/bundler-issues", + "permanent": true + }, + { + "source": "/docs/guides/general-guides/database-workflows/mapping-tables-to-models/prisma-schema", + "destination": "/docs/orm/prisma-schema/data-model/database-mapping", + "permanent": true + }, + { + "source": "/docs/orm/reference/prisma-schema-reference/generator", + "destination": "/docs/orm/prisma-schema/overview/generators", + "permanent": true + }, + { + "source": "/docs/orm/prisma-schema/relations", + "destination": "/docs/orm/prisma-schema/data-model/relations", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/setup-and-configuration/databases-connections/driver-adapters", + "destination": "/docs/orm/core-concepts/supported-databases/database-drivers", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript", + "destination": "/docs/prisma-orm/quickstart/prisma-postgres", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-postgresql", + "destination": "/docs/prisma-orm/quickstart/postgresql", + "permanent": true + }, + { + "source": "/docs/orm/prisma-migrate/workflows/resolving-migrations", + "destination": "/docs/orm/prisma-migrate/workflows/troubleshooting", + "permanent": true + }, + { + "source": "/docs/getting-started/setup-prisma/add-to-existing-project/relational-databases/baseline-your-database-typescript-postgresql", + "destination": "/docs/orm/prisma-migrate/workflows/baselining", + "permanent": true + }, + { + "source": "/docs/guides/deployment/deployment-guides/deploy-database-google-cloud-platform", + "destination": "/docs/orm/prisma-client/deployment/deploy-prisma", + "permanent": true + }, + { + "source": "/docs/guides/other/nestjs", + "destination": "/docs/guides/frameworks/nestjs", + "permanent": true + }, + { + "source": "/docs/v5", + "destination": "/docs/guides/upgrade-prisma-orm/v5", + "permanent": true + }, + { + "source": "/docs/more", + "destination": "/docs/orm/more/releases", + "permanent": true + }, + { + "source": "/docs/orm/prisma-migrate/workflows/resolving-migration-issues-in-production", + "destination": "/docs/orm/prisma-migrate/workflows/troubleshooting", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/deployment/deploy-supabase", + "destination": "/docs/guides/integrations/supabase-accelerate", + "permanent": true + }, + { + "source": "/docs/orm/accelerate/getting-started/connection-pooler/client-extensions", + "destination": "/docs/accelerate/connection-pooling", + "permanent": true + }, + { + "source": "/docs/orm/prisma-client/queries/relation-aggregates", + "destination": "/docs/orm/prisma-client/queries/aggregation-grouping-summarizing", + "permanent": true } ] } From e2c29e1af1b86f4d2229588ce95bce96c6967f42 Mon Sep 17 00:00:00 2001 From: Mike Hartington Date: Mon, 23 Feb 2026 08:41:04 -0500 Subject: [PATCH 2/2] fix(): links and linting --- .../add-to-existing-project/mongodb.mdx | 2 +- .../(index)/prisma-orm/quickstart/mongodb.mdx | 2 +- .../docs.v6/orm/more/ai-tools/chatgpt.mdx | 2 +- .../orm/more/dev-environment/editor-setup.mdx | 10 - .../databases-connections/index.mdx | 2 +- .../add-to-existing-project/mongodb.mdx | 2 +- .../(index)/prisma-orm/quickstart/mongodb.mdx | 2 +- apps/docs/content/docs/ai/tools/chatgpt.mdx | 2 +- .../orm/more/dev-environment/editor-setup.mdx | 10 - apps/docs/package.json | 1 + apps/docs/scripts/lint-external-links.ts | 301 ++++++++++++++++++ package.json | 1 + turbo.json | 3 +- 13 files changed, 312 insertions(+), 28 deletions(-) create mode 100644 apps/docs/scripts/lint-external-links.ts diff --git a/apps/docs/content/docs.v6/(index)/prisma-orm/add-to-existing-project/mongodb.mdx b/apps/docs/content/docs.v6/(index)/prisma-orm/add-to-existing-project/mongodb.mdx index 4311c030e3..e6f9b156b6 100644 --- a/apps/docs/content/docs.v6/(index)/prisma-orm/add-to-existing-project/mongodb.mdx +++ b/apps/docs/content/docs.v6/(index)/prisma-orm/add-to-existing-project/mongodb.mdx @@ -175,7 +175,7 @@ If you see the `Error in connector: SCRAM failure: Authentication failed.` error #### `Raw query failed. Error code 8000 (AtlasError): empty database name not allowed.` -If you see the `Raw query failed. Code: unknown. Message: Kind: Command failed: Error code 8000 (AtlasError): empty database name not allowed.` error message, be sure to append the database name to the database URL. You can find more info in this [GitHub issue](https://github.com/prisma/docs/issues/5562). +If you see the `Raw query failed. Code: unknown. Message: Kind: Command failed: Error code 8000 (AtlasError): empty database name not allowed.` error message, be sure to append the database name to the database URL. You can find more info in this [GitHub issue](https://github.com/prisma/web/issues/5562). ## 4. Introspect your database diff --git a/apps/docs/content/docs.v6/(index)/prisma-orm/quickstart/mongodb.mdx b/apps/docs/content/docs.v6/(index)/prisma-orm/quickstart/mongodb.mdx index 86c8d0997d..6ce4c00fda 100644 --- a/apps/docs/content/docs.v6/(index)/prisma-orm/quickstart/mongodb.mdx +++ b/apps/docs/content/docs.v6/(index)/prisma-orm/quickstart/mongodb.mdx @@ -302,7 +302,7 @@ If you see the `Error in connector: SCRAM failure: Authentication failed.` error ### `Raw query failed. Error code 8000 (AtlasError): empty database name not allowed.` -If you see the `Raw query failed. Code: unknown. Message: Kind: Command failed: Error code 8000 (AtlasError): empty database name not allowed.` error message, be sure to append the database name to the database URL. You can find more info in this [GitHub issue](https://github.com/prisma/docs/issues/5562). +If you see the `Raw query failed. Code: unknown. Message: Kind: Command failed: Error code 8000 (AtlasError): empty database name not allowed.` error message, be sure to append the database name to the database URL. You can find more info in this [GitHub issue](https://github.com/prisma/web/issues/5562). ## More info diff --git a/apps/docs/content/docs.v6/orm/more/ai-tools/chatgpt.mdx b/apps/docs/content/docs.v6/orm/more/ai-tools/chatgpt.mdx index 5c8b98f655..23d3682806 100644 --- a/apps/docs/content/docs.v6/orm/more/ai-tools/chatgpt.mdx +++ b/apps/docs/content/docs.v6/orm/more/ai-tools/chatgpt.mdx @@ -14,7 +14,7 @@ This guide explains how to add the remote Prisma MCP server to ChatGPT, allowing This feature is still in development by OpenAI and might look or work a little differently over time. Some users may not have access to it yet. -If you notice that something has changed or doesn't match this guide, please [open an issue](https://github.com/prisma/docs/issues) or submit a pull request to update our docs. +If you notice that something has changed or doesn't match this guide, please [open an issue](https://github.com/prisma/web/issues) or submit a pull request to update our docs. ::: Here is an end to end demo of setting up the remote Prisma MCP server and using it in ChatGPT: diff --git a/apps/docs/content/docs.v6/orm/more/dev-environment/editor-setup.mdx b/apps/docs/content/docs.v6/orm/more/dev-environment/editor-setup.mdx index 7316a59196..d6e38e1955 100644 --- a/apps/docs/content/docs.v6/orm/more/dev-environment/editor-setup.mdx +++ b/apps/docs/content/docs.v6/orm/more/dev-environment/editor-setup.mdx @@ -97,13 +97,3 @@ To install, run: ```npm npm install -g @microsoft/inshellisense ``` - -#### Fig - -`inshellisense` is built on top of [Fig](https://fig.io/) which you can also use directly. It works in bash, zsh, and fish. - -To install, run: - -```bash -brew install fig -``` diff --git a/apps/docs/content/docs.v6/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx b/apps/docs/content/docs.v6/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx index f3307c7cc2..b9816628e3 100644 --- a/apps/docs/content/docs.v6/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx +++ b/apps/docs/content/docs.v6/orm/prisma-client/setup-and-configuration/databases-connections/index.mdx @@ -176,7 +176,7 @@ mysql://USER:PASSWORD@HOST:PORT/DATABASE?connection_limit=1 :::tip -If you are using AWS Lambda and _not_ configuring a `connection_limit`, refer to the following GitHub issue for information about the expected default pool size: https://github.com/prisma/docs/issues/667 +If you are using AWS Lambda and _not_ configuring a `connection_limit`, refer to the following GitHub issue for information about the expected default pool size: https://github.com/prisma/web/issues/667 ::: diff --git a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx index f8c9e3b15f..2a5fc660c0 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx @@ -167,7 +167,7 @@ For MongoDB Atlas, you can manually append the database name to the connection U ### Troubleshooting connection issues - **Authentication failed** — If you see a `SCRAM failure: Authentication failed` error, [add `?authSource=admin`](https://github.com/prisma/prisma/discussions/9994#discussioncomment-1562283) to the end of your connection string. -- **Empty database name** — If you see an `Error code 8000 (AtlasError): empty database name not allowed` error, append the database name to your connection URL. See this [GitHub issue](https://github.com/prisma/docs/issues/5562) for details. +- **Empty database name** — If you see an `Error code 8000 (AtlasError): empty database name not allowed` error, append the database name to your connection URL. See this [GitHub issue](https://github.com/prisma/web/issues/5562) for details. ## 4. Introspect your database diff --git a/apps/docs/content/docs/(index)/prisma-orm/quickstart/mongodb.mdx b/apps/docs/content/docs/(index)/prisma-orm/quickstart/mongodb.mdx index 0b4552cce5..447d9263ad 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/quickstart/mongodb.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/quickstart/mongodb.mdx @@ -323,7 +323,7 @@ You've successfully set up Prisma ORM. Here's what you can explore next: ## Troubleshooting - **Authentication failed** — If you see a `SCRAM failure: Authentication failed` error, [add `?authSource=admin`](https://github.com/prisma/prisma/discussions/9994#discussioncomment-1562283) to the end of your connection string. -- **Empty database name** — If you see an `Error code 8000 (AtlasError): empty database name not allowed` error, append the database name to your connection URL. See this [GitHub issue](https://github.com/prisma/docs/issues/5562) for details. +- **Empty database name** — If you see an `Error code 8000 (AtlasError): empty database name not allowed` error, append the database name to your connection URL. See this [GitHub issue](https://github.com/prisma/web/issues/5562) for details. ## More info diff --git a/apps/docs/content/docs/ai/tools/chatgpt.mdx b/apps/docs/content/docs/ai/tools/chatgpt.mdx index b85a88ee82..a72b6dc05f 100644 --- a/apps/docs/content/docs/ai/tools/chatgpt.mdx +++ b/apps/docs/content/docs/ai/tools/chatgpt.mdx @@ -14,7 +14,7 @@ This guide explains how to add the remote Prisma MCP server to ChatGPT, allowing This feature is still in development by OpenAI and might look or work a little differently over time. Some users may not have access to it yet. -If you notice that something has changed or doesn't match this guide, please [open an issue](https://github.com/prisma/docs/issues) or submit a pull request to update our docs. +If you notice that something has changed or doesn't match this guide, please [open an issue](https://github.com/prisma/web/issues) or submit a pull request to update our docs. ::: Here is an end to end demo of setting up the remote Prisma MCP server and using it in ChatGPT: diff --git a/apps/docs/content/docs/orm/more/dev-environment/editor-setup.mdx b/apps/docs/content/docs/orm/more/dev-environment/editor-setup.mdx index 8213902def..031d2e595e 100644 --- a/apps/docs/content/docs/orm/more/dev-environment/editor-setup.mdx +++ b/apps/docs/content/docs/orm/more/dev-environment/editor-setup.mdx @@ -97,13 +97,3 @@ To install, run: ```npm npm install -g @microsoft/inshellisense ``` - -#### Fig - -`inshellisense` is built on top of [Fig](https://fig.io/) which you can also use directly. It works in bash, zsh, and fish. - -To install, run: - -```bash -brew install fig -``` diff --git a/apps/docs/package.json b/apps/docs/package.json index b37adea988..9fb809b15c 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -12,6 +12,7 @@ "types:check": "fumadocs-mdx && next typegen && tsc --noEmit", "postinstall": "fumadocs-mdx", "lint:links": "tsx ./scripts/lint-links.ts", + "lint:external-links": "tsx ./scripts/lint-external-links.ts", "lint:images": "tsx ./scripts/lint-images.ts", "lint:code": "tsx ./scripts/lint-code-blocks.ts", "lint:spellcheck": "cspell \"content/docs/**/*.mdx\" \"content/docs/**/*.json\"" diff --git a/apps/docs/scripts/lint-external-links.ts b/apps/docs/scripts/lint-external-links.ts new file mode 100644 index 0000000000..cafd50510e --- /dev/null +++ b/apps/docs/scripts/lint-external-links.ts @@ -0,0 +1,301 @@ +#!/usr/bin/env npx tsx + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const CONTENT_DIR = path.join(__dirname, "../content"); +const TIMEOUT_MS = 10_000; +const MAX_CONCURRENCY = 15; +const ACCEPTED_STATUSES = new Set([403, 429]); + +const IMAGE_EXTENSIONS = new Set([ + ".png", + ".jpg", + ".jpeg", + ".gif", + ".webp", + ".svg", + ".avif", + ".bmp", + ".ico", +]); + +const CODE_BLOCK_REGEX = /```[\s\S]*?```/g; +const MARKDOWN_IMAGE_REGEX = /!\[[^\]]*]\(([^)\n]+)\)/g; +const MARKDOWN_LINK_REGEX = /(?\s]+)>/g; +const HTML_ANCHOR_REGEX = /]*\bhref=(["'])(.*?)\1/gi; +const BROWSER_HEADERS: HeadersInit = { + "user-agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "accept-language": "en-US,en;q=0.9", +}; + +type LinkOccurrence = { + file: string; + line: number; +}; + +type FailedResult = { + url: string; + reason: string; + occurrences: LinkOccurrence[]; +}; + +function findMarkdownFiles(dir: string, fileList: string[] = []): string[] { + const files = fs.readdirSync(dir); + + for (const file of files) { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + findMarkdownFiles(filePath, fileList); + continue; + } + + if (file.endsWith(".md") || file.endsWith(".mdx")) { + fileList.push(filePath); + } + } + + return fileList; +} + +function stripCodeBlocks(content: string): string { + return content.replace(CODE_BLOCK_REGEX, (match) => + "\n".repeat((match.match(/\n/g) ?? []).length), + ); +} + +function lineNumberAt(content: string, index: number): number { + return content.slice(0, index).split("\n").length; +} + +function extractMarkdownUrl(raw: string): string { + const trimmed = raw.trim(); + if (!trimmed) return ""; + + if (trimmed.startsWith("<") && trimmed.includes(">")) { + return trimmed.slice(1, trimmed.indexOf(">")).trim(); + } + + const [url] = trimmed.split(/\s+/); + return url ?? ""; +} + +function stripQueryAndHash(url: string): string { + return url.split(/[?#]/, 1)[0] ?? ""; +} + +function isImageUrl(url: string): boolean { + const noQuery = stripQueryAndHash(url); + try { + const parsed = new URL(noQuery); + return IMAGE_EXTENSIONS.has(path.extname(parsed.pathname).toLowerCase()); + } catch { + return IMAGE_EXTENSIONS.has(path.extname(noQuery).toLowerCase()); + } +} + +function toExternalHttpUrl(raw: string): string | null { + const trimmed = raw.trim(); + if (!trimmed) return null; + + try { + const parsed = new URL(trimmed); + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null; + if (parsed.hostname === "localhost") return null; + return parsed.toString(); + } catch { + return null; + } +} + +function collectFileLinks(filePath: string): Array<{ url: string; line: number }> { + const rawContent = fs.readFileSync(filePath, "utf8"); + const content = stripCodeBlocks(rawContent); + const links: Array<{ url: string; line: number }> = []; + + MARKDOWN_IMAGE_REGEX.lastIndex = 0; + MARKDOWN_LINK_REGEX.lastIndex = 0; + AUTOLINK_REGEX.lastIndex = 0; + HTML_ANCHOR_REGEX.lastIndex = 0; + + let match: RegExpExecArray | null; + + while ((match = MARKDOWN_LINK_REGEX.exec(content)) !== null) { + const extracted = extractMarkdownUrl(match[1] ?? ""); + if (!extracted) continue; + const external = toExternalHttpUrl(extracted); + if (!external || isImageUrl(external)) continue; + + links.push({ + url: external, + line: lineNumberAt(content, match.index), + }); + } + + while ((match = AUTOLINK_REGEX.exec(content)) !== null) { + const external = toExternalHttpUrl(match[1] ?? ""); + if (!external || isImageUrl(external)) continue; + + links.push({ + url: external, + line: lineNumberAt(content, match.index), + }); + } + + while ((match = HTML_ANCHOR_REGEX.exec(content)) !== null) { + const external = toExternalHttpUrl(match[2] ?? ""); + if (!external || isImageUrl(external)) continue; + + links.push({ + url: external, + line: lineNumberAt(content, match.index), + }); + } + + return links; +} + +async function fetchWithTimeout( + url: string, + init: RequestInit = {}, +): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS); + + try { + return await fetch(url, { + ...init, + redirect: "follow", + signal: controller.signal, + }); + } finally { + clearTimeout(timeout); + } +} + +async function checkUrl(url: string): Promise<{ ok: boolean; reason?: string }> { + try { + // Some providers (e.g. VS Marketplace, Bluesky) return 404 for HEAD + // while the same URL works with GET in a browser. + let response = await fetchWithTimeout(url, { + method: "HEAD", + headers: BROWSER_HEADERS, + }); + + if (response.status >= 400 || response.status === 429) { + response = await fetchWithTimeout(url, { method: "GET", headers: BROWSER_HEADERS }); + } + + if (response.status === 404) { + return { ok: false, reason: "HTTP 404" }; + } + + if (ACCEPTED_STATUSES.has(response.status)) { + return { ok: true }; + } + + if (response.status < 200 || response.status >= 400) { + return { ok: false, reason: `HTTP ${response.status}` }; + } + + return { ok: true }; + } catch (error) { + if (error instanceof Error && error.name === "AbortError") { + return { ok: false, reason: "timeout" }; + } + + const message = error instanceof Error ? error.message : "unknown error"; + return { ok: false, reason: message }; + } +} + +async function runWithConcurrency( + items: T[], + limit: number, + worker: (item: T) => Promise, +): Promise { + let index = 0; + + const workers = Array.from({ length: Math.min(limit, items.length) }, async () => { + while (index < items.length) { + const item = items[index]; + index += 1; + await worker(item); + } + }); + + await Promise.all(workers); +} + +async function main(): Promise { + if (!fs.existsSync(CONTENT_DIR)) { + console.error(`Content directory not found: ${CONTENT_DIR}`); + process.exit(1); + } + + const files = findMarkdownFiles(CONTENT_DIR); + const occurrencesByUrl = new Map(); + let extractedLinks = 0; + + for (const filePath of files) { + const relativeFile = path.relative(process.cwd(), filePath); + const links = collectFileLinks(filePath); + extractedLinks += links.length; + + for (const link of links) { + const occurrences = occurrencesByUrl.get(link.url) ?? []; + occurrences.push({ file: relativeFile, line: link.line }); + occurrencesByUrl.set(link.url, occurrences); + } + } + + const uniqueUrls = [...occurrencesByUrl.keys()]; + const failed: FailedResult[] = []; + + await runWithConcurrency(uniqueUrls, MAX_CONCURRENCY, async (url) => { + const result = await checkUrl(url); + if (result.ok) return; + + failed.push({ + url, + reason: result.reason ?? "invalid status code", + occurrences: occurrencesByUrl.get(url) ?? [], + }); + }); + + failed.sort((a, b) => a.url.localeCompare(b.url)); + + console.log(`Scanned files: ${files.length}`); + console.log(`Extracted external links: ${extractedLinks}`); + console.log(`Unique links checked: ${uniqueUrls.length}`); + console.log(`Failed links: ${failed.length}`); + + if (failed.length === 0) { + console.log("✅ No failed external links found."); + process.exit(0); + } + + console.error("\n❌ Failed external links:\n"); + for (const item of failed) { + console.error(`${item.url}`); + console.error(` reason: ${item.reason}`); + for (const occurrence of item.occurrences) { + console.error(` at: ${occurrence.file}:${occurrence.line}`); + } + console.error(""); + } + + process.exit(1); +} + +void main(); diff --git a/package.json b/package.json index aee54879e3..daab08195c 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "check": "turbo run check", "types:check": "turbo run types:check", "lint:links": "turbo run lint:links --filter=docs", + "lint:external-links": "turbo run lint:external-links --filter=docs", "lint:code": "turbo run lint:code --filter=docs", "lint:spellcheck": "turbo run lint:spellcheck --filter=docs" }, diff --git a/turbo.json b/turbo.json index ba0b9618c6..d67f90b84c 100644 --- a/turbo.json +++ b/turbo.json @@ -29,7 +29,8 @@ "dependsOn": ["^types:check"] }, "lint:links": {}, + "lint:external-links": {}, "lint:code": {}, "lint:spellcheck": {} } -} +} \ No newline at end of file