From 2f616f7d5e688dbce6a199e4f9f397051e05f0bf Mon Sep 17 00:00:00 2001 From: Andrew Jiang Date: Wed, 18 Sep 2024 04:53:54 +1000 Subject: [PATCH] feat: vercel scripts (#1492) --- .eslintrc.js | 7 +- .github/actions/ete-docs-bundle/action.yml | 8 +- .github/actions/vercel/action.yml | 111 ------- .github/workflows/deploy-docs-bundle-dev.yml | 45 ++- .../workflows/deploy-docs-bundle-preview.yml | 96 +++--- .github/workflows/deploy-docs-bundle-prod.yml | 68 ++-- .../workflows/deploy-fern-dashboard-dev.yml | 21 +- .../deploy-fern-dashboard-preview.yml | 47 ++- .../workflows/deploy-fern-dashboard-prod.yml | 22 +- .github/workflows/deploy-fontawesome-cdn.yml | 44 ++- .github/workflows/playwright.yml | 11 +- .github/workflows/revalidate-all.yml | 10 +- clis/vercel-scripts/.gitignore | 1 + clis/vercel-scripts/package.json | 31 ++ clis/vercel-scripts/src/cli.ts | 191 ++++++++++++ clis/vercel-scripts/src/cwd.ts | 16 + clis/vercel-scripts/src/utils/deployer.ts | 153 +++++++++ clis/vercel-scripts/src/utils/exec.ts | 56 ++++ clis/vercel-scripts/src/utils/revalidator.ts | 88 ++++++ clis/vercel-scripts/tsconfig.json | 11 + fern/apis/fern-docs/definition/api.yml | 1 + .../fern-docs/definition/revalidation.yml | 69 ++++ .../generators.yml | 8 +- .../revalidation/definition/__package__.yml | 63 ---- fern/apis/revalidation/definition/api.yml | 1 - fern/apis/vercel/generators.yml | 4 +- fern/docs.yml | 6 +- package.json | 1 + packages/ui/docs-bundle/next.config.mjs | 6 +- packages/ui/docs-bundle/package.json | 1 + packages/ui/docs-bundle/projects.json | 17 - packages/ui/docs-bundle/src/middleware.ts | 1 - .../src/pages/api/fern-docs/changelog.ts | 5 +- .../src/pages/api/fern-docs/resolve-api.ts | 6 +- .../pages/api/fern-docs/revalidate-all/v3.ts | 102 ++---- .../pages/api/fern-docs/revalidate-all/v4.ts | 85 +++++ .../pages/api/fern-docs/revalidate-path.ts | 39 +-- .../src/pages/api/fern-docs/search.ts | 1 - .../src/pages/api/fern-docs/sitemap.xml.ts | 5 +- .../ui/docs-bundle/src/utils/revalidator.ts | 38 +++ packages/ui/docs-bundle/turbo.json | 1 + packages/ui/docs-bundle/vercel.json | 3 + packages/ui/fern-dashboard/turbo.json | 10 + packages/ui/fern-dashboard/vercel.json | 6 + packages/ui/fontawesome-cdn/turbo.json | 10 + packages/ui/fontawesome-cdn/vercel.json | 6 + playwright/test-runner.ts | 3 +- pnpm-lock.yaml | 295 ++++++++++++------ scripts/fetch-domains.js | 83 ----- scripts/revalidate-all.js | 71 ----- servers/fdr/package.json | 2 +- .../fdr/src/__test__/local/revalidate.test.ts | 4 +- servers/fdr/src/__test__/mock.ts | 6 +- .../docs/v2/getDocsWriteV2Service.ts | 17 +- .../revalidator/RevalidatorService.ts | 34 +- .../fdr/src/services/slack/SlackService.ts | 4 +- turbo.json | 2 +- 57 files changed, 1292 insertions(+), 761 deletions(-) delete mode 100644 .github/actions/vercel/action.yml create mode 100644 clis/vercel-scripts/.gitignore create mode 100644 clis/vercel-scripts/package.json create mode 100644 clis/vercel-scripts/src/cli.ts create mode 100644 clis/vercel-scripts/src/cwd.ts create mode 100644 clis/vercel-scripts/src/utils/deployer.ts create mode 100644 clis/vercel-scripts/src/utils/exec.ts create mode 100644 clis/vercel-scripts/src/utils/revalidator.ts create mode 100644 clis/vercel-scripts/tsconfig.json create mode 100644 fern/apis/fern-docs/definition/api.yml create mode 100644 fern/apis/fern-docs/definition/revalidation.yml rename fern/apis/{revalidation => fern-docs}/generators.yml (55%) delete mode 100644 fern/apis/revalidation/definition/__package__.yml delete mode 100644 fern/apis/revalidation/definition/api.yml delete mode 100644 packages/ui/docs-bundle/projects.json create mode 100644 packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-all/v4.ts create mode 100644 packages/ui/docs-bundle/src/utils/revalidator.ts create mode 100644 packages/ui/fern-dashboard/turbo.json create mode 100644 packages/ui/fontawesome-cdn/turbo.json delete mode 100644 scripts/fetch-domains.js delete mode 100644 scripts/revalidate-all.js diff --git a/.eslintrc.js b/.eslintrc.js index 53068e8bdc..1b1a40cb11 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -97,12 +97,7 @@ module.exports = { indent: "off", "object-shorthand": ["error"], "deprecation/deprecation": "error", - "import/no-internal-modules": [ - "error", - { - forbid: ["@fern-ui/*/**"], - }, - ], + // "import/no-internal-modules": ["error"], eqeqeq: [ "error", "always", diff --git a/.github/actions/ete-docs-bundle/action.yml b/.github/actions/ete-docs-bundle/action.yml index 5aba5a77f1..b5c61dc44d 100644 --- a/.github/actions/ete-docs-bundle/action.yml +++ b/.github/actions/ete-docs-bundle/action.yml @@ -17,11 +17,9 @@ runs: shell: bash env: DEPLOYMENT_URL: ${{ inputs.deployment_url }} - run: pnpm exec playwright test playwright/smoke --workers 6 + run: PLAYWRIGHT_JSON_OUTPUT_NAME=results.json pnpm exec playwright test playwright/smoke --workers 6 --reporter json - - uses: actions/upload-artifact@v4 + - uses: daun/playwright-report-summary@v3 if: always() with: - name: playwright-report - path: playwright-report/ - retention-days: 30 + report-file: results.json diff --git a/.github/actions/vercel/action.yml b/.github/actions/vercel/action.yml deleted file mode 100644 index 5166b2b1a5..0000000000 --- a/.github/actions/vercel/action.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: Build & Deploy to Vercel -description: Build and deploy a Vercel project using CI/CD - -inputs: - ref: - description: The Git ref to deploy - required: false - package: - description: The workspace package to deploy, which is used for turbo-ignore - required: true - environment: - description: production | preview - required: false - default: "preview" - force: - description: Force deployment even if there are no changes (skips turbo-ignore) - required: false - default: "false" - skip_deploy: - description: Skip deployment - required: false - default: "false" - promote: - description: Promote the production deployment - required: false - default: "false" - -outputs: - deployment_url: - description: The URL of the deployment - value: ${{ steps.deploy-preview.outputs.deployment_url || steps.deploy-production.outputs.deployment_url }} - -runs: - using: "composite" - - steps: - # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - - uses: actions/checkout@v4 - with: - fetch-depth: 2 # used for turbo-ignore - ref: ${{ inputs.ref || github.ref }} - - - name: Install - uses: ./.github/actions/install - with: - run_install: false # install is handled by vercel build - - # turbo-ignore is used to skip the build step if there are no changes since the last commit - - name: Ignore Build Step - id: ignore-build - shell: bash - if: inputs.force == 'false' - continue-on-error: true - run: | - set +e - pnpx turbo-ignore ${{ inputs.package }} --fallback=HEAD^1 - echo "exit_code=$?" >> $GITHUB_OUTPUT - - - name: Install Vercel CLI - if: steps.ignore-build.outputs.exit_code == '1' || inputs.force == 'true' - shell: bash - run: pnpm install --global vercel@latest - - - name: Pull Vercel Environment - if: steps.ignore-build.outputs.exit_code == '1' || inputs.force == 'true' - shell: bash - run: vercel pull --yes --environment=${{ inputs.environment }} --token=$VERCEL_TOKEN - - # Preview deployment - - - name: Build Vercel Preview - shell: bash - if: (steps.ignore-build.outputs.exit_code == '1' || inputs.force == 'true') && inputs.environment == 'preview' - run: vercel build --yes --token=$VERCEL_TOKEN - - - name: Deploy Vercel Preview - shell: bash - if: (steps.ignore-build.outputs.exit_code == '1' || inputs.force == 'true') && inputs.environment == 'preview' && inputs.skip_deploy == 'false' - id: deploy-preview - run: | - DEPLOYMENT_URL="$(vercel deploy --yes --prebuilt --token=$VERCEL_TOKEN --archive=tgz)" - echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT - - # Production deployment - - - name: Build Vercel Production - shell: bash - if: (steps.ignore-build.outputs.exit_code == '1' || inputs.force == 'true') && inputs.environment == 'production' - run: vercel build --yes --prod --token=$VERCEL_TOKEN - - - name: Deploy Vercel Production - shell: bash - if: (steps.ignore-build.outputs.exit_code == '1' || inputs.force == 'true') && inputs.environment == 'production' && inputs.skip_deploy == 'false' - id: deploy-production - run: | - DEPLOYMENT_URL="$(vercel deploy --yes --prebuilt --prod --skip-domain --token=$VERCEL_TOKEN --archive=tgz)" - echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT - - # Print deployment information - - - name: Wait for Deployment - shell: bash - if: (steps.ignore-build.outputs.exit_code == '1' || inputs.force == 'true') && inputs.skip_deploy == 'false' - run: vercel inspect ${{ steps.deploy-preview.outputs.deployment_url || steps.deploy-production.outputs.deployment_url }} --token=$VERCEL_TOKEN --wait - - # Promote production deployment if promote=true - - - name: Promote Vercel Production - shell: bash - if: (steps.ignore-build.outputs.exit_code == '1' || inputs.force == 'true') && inputs.environment == 'production' && inputs.skip_deploy == 'false' && inputs.promote == 'true' - run: vercel promote ${{ steps.deploy-production.outputs.deployment_url }} --token=$VERCEL_TOKEN diff --git a/.github/workflows/deploy-docs-bundle-dev.yml b/.github/workflows/deploy-docs-bundle-dev.yml index a4a4ca3e0c..5240c20f07 100644 --- a/.github/workflows/deploy-docs-bundle-dev.yml +++ b/.github/workflows/deploy-docs-bundle-dev.yml @@ -1,10 +1,4 @@ name: Deploy @fern-ui/docs-bundle (dev) -env: - VERCEL_ORG_ID: team_6FKOM5nw037hv8g2mTk3gaH7 - VERCEL_PROJECT_ID: prj_QnsiTm1ijJP6cS23K5da6lL02wbp - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: "buildwithfern" on: push: @@ -14,11 +8,30 @@ on: concurrency: group: app-dev.buildwithfern.com - cancel-in-progress: ${{ github.event_name == 'push' }} + cancel-in-progress: true jobs: + ignore: + runs-on: ubuntu-latest + outputs: + continue: ${{ steps.ignore.outputs.continue }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 # used for turbo-ignore + + - name: Install + uses: ./.github/actions/install + with: + run_install: false + + - name: Ignore unchanged files + id: ignore + run: set +e; pnpx turbo-ignore @fern-ui/docs-bundle --fallback=HEAD^1; echo "continue=$?" >> $GITHUB_OUTPUT + deploy: - if: github.ref_name == 'main' + needs: ignore + if: needs.ignore.outputs.continue == 1 runs-on: ubuntu-latest environment: name: Production - app-dev.buildwithfern.com @@ -26,12 +39,16 @@ jobs: outputs: deployment_url: ${{ steps.deploy.outputs.deployment_url }} steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 - - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel - id: deploy with: ref: ${{ github.ref_name || github.ref }} - package: "@fern-ui/docs-bundle" - environment: production - promote: true + + - name: Install + uses: ./.github/actions/install + + - name: Build & Deploy to Vercel + id: deploy + run: | + pnpm vercel-scripts deploy app-dev.buildwithfern.com --token=${{ secrets.VERCEL_TOKEN }} --environment=production --force + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/deploy-docs-bundle-preview.yml b/.github/workflows/deploy-docs-bundle-preview.yml index bc850d2bad..649862f1a2 100644 --- a/.github/workflows/deploy-docs-bundle-preview.yml +++ b/.github/workflows/deploy-docs-bundle-preview.yml @@ -1,9 +1,4 @@ name: Preview @fern-ui/docs-bundle -env: - VERCEL_ORG_ID: team_6FKOM5nw037hv8g2mTk3gaH7 - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: "buildwithfern" on: pull_request: @@ -12,32 +7,60 @@ on: - main workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name || github.ref }} - cancel-in-progress: false - jobs: + ignore: + runs-on: ubuntu-latest + outputs: + continue: ${{ steps.ignore.outputs.continue }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 # used for turbo-ignore + + - name: Install + uses: ./.github/actions/install + with: + run_install: false + + - name: Ignore unchanged files + id: ignore + run: set +e; pnpx turbo-ignore @fern-ui/docs-bundle --fallback=HEAD^1; echo "continue=$?" >> $GITHUB_OUTPUT + deploy: + needs: ignore + if: needs.ignore.outputs.continue == 1 runs-on: ubuntu-latest environment: name: Preview - app.buildwithfern.com url: ${{ steps.deploy.outputs.deployment_url }} outputs: deployment_url: ${{ steps.deploy.outputs.deployment_url }} - permissions: write-all + permissions: write-all # required for the pr-preview comment steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 + with: + fetch-depth: 2 # used for turbo-ignore + ref: ${{ github.event.pull_request.head.ref || github.ref_name || github.ref }} + + - name: Install + uses: ./.github/actions/install + - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel id: deploy - env: - ANALYZE: 1 - VERCEL_PROJECT_ID: prj_QX3venU6jwRUmdt8ArfL8AU5r1d4 + run: | + pnpm vercel-scripts deploy app.buildwithfern.com --token=${{ secrets.VERCEL_TOKEN }} + if [ -f deployment-url.txt ]; then + pnpm vercel-scripts preview.txt $(cat deployment-url.txt) --token=${{ secrets.VERCEL_TOKEN }} + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT + fi + + - name: Comment PR Preview + uses: thollander/actions-comment-pull-request@v2 + if: github.event_name == 'pull_request' && steps.deploy.outputs.deployment_url with: - ref: ${{ github.event.pull_request.head.ref || github.ref_name || github.ref }} - package: "@fern-ui/docs-bundle" - environment: preview - force: ${{ github.event_name == 'workflow_dispatch' }} + filePath: preview.txt + comment_tag: pr_preview - name: Analyze bundle if: steps.deploy.outputs.deployment_url @@ -71,6 +94,8 @@ jobs: comment_tag: bundle_analysis deploy-dev: + needs: ignore + if: needs.ignore.outputs.continue == 1 runs-on: ubuntu-latest environment: name: Preview - app-dev.buildwithfern.com @@ -78,38 +103,33 @@ jobs: outputs: deployment_url: ${{ steps.deploy.outputs.deployment_url }} steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 - - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel - id: deploy - env: - VERCEL_PROJECT_ID: prj_QnsiTm1ijJP6cS23K5da6lL02wbp with: ref: ${{ github.event.pull_request.head.ref || github.ref_name || github.ref }} - package: "@fern-ui/docs-bundle" - environment: preview + + - name: Install + uses: ./.github/actions/install + + - name: Build & Deploy to Vercel + id: deploy + run: | + pnpm vercel-scripts deploy app-dev.buildwithfern.com --token=${{ secrets.VERCEL_TOKEN }} + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT ete: needs: deploy # only runs on fern-prod if: needs.deploy.outputs.deployment_url runs-on: ubuntu-latest - permissions: write-all + permissions: write-all # required for the playwright-report-summary action steps: - uses: actions/checkout@v4 - # this script generates domains.txt and preview.txt - - name: Generate preview URLs - env: - DEPLOYMENT_URL: ${{ needs.deploy.outputs.deployment_url }} - PR_PREVIEW: ${{ github.event_name == 'pull_request' }} - run: node scripts/fetch-domains.js + - name: Install + uses: ./.github/actions/install - - name: Comment PR Preview - uses: thollander/actions-comment-pull-request@v2 - if: github.event_name == 'pull_request' - with: - filePath: preview.txt - comment_tag: pr_preview + - name: Fetch domains + run: pnpm vercel-scripts domains.txt ${{ needs.deploy.outputs.deployment_url }} --token=${{ secrets.VERCEL_TOKEN }} - name: Run E2E tests uses: ./.github/actions/ete-docs-bundle diff --git a/.github/workflows/deploy-docs-bundle-prod.yml b/.github/workflows/deploy-docs-bundle-prod.yml index c4cbe7063a..eb8096c492 100644 --- a/.github/workflows/deploy-docs-bundle-prod.yml +++ b/.github/workflows/deploy-docs-bundle-prod.yml @@ -1,9 +1,4 @@ name: Deploy @fern-ui/docs-bundle -env: - VERCEL_ORG_ID: team_6FKOM5nw037hv8g2mTk3gaH7 - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: "buildwithfern" on: push: @@ -23,17 +18,18 @@ jobs: url: ${{ steps.deploy.outputs.deployment_url }} outputs: deployment_url: ${{ steps.deploy.outputs.deployment_url }} - env: - VERCEL_PROJECT_ID: prj_QX3venU6jwRUmdt8ArfL8AU5r1d4 steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 + + - name: Install + uses: ./.github/actions/install + - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel id: deploy - with: - package: "@fern-ui/docs-bundle" - environment: production - force: true + run: | + pnpm vercel-scripts deploy app.buildwithfern.com --token=${{ secrets.VERCEL_TOKEN }} --environment=production + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT deploy_app_ferndocs_com: runs-on: ubuntu-latest @@ -43,17 +39,18 @@ jobs: url: ${{ steps.deploy.outputs.deployment_url }} outputs: deployment_url: ${{ steps.deploy.outputs.deployment_url }} - env: - VERCEL_PROJECT_ID: prj_SfgTTzw7KefTMuVWsL5uhY8Y4BIt steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 + + - name: Install + uses: ./.github/actions/install + - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel id: deploy - with: - package: "@fern-ui/docs-bundle" - environment: production - force: true + run: | + pnpm vercel-scripts deploy app.ferndocs.com --token=${{ secrets.VERCEL_TOKEN }} --environment=production + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT deploy_app-slash_ferndocs_com: runs-on: ubuntu-latest @@ -63,17 +60,18 @@ jobs: url: ${{ steps.deploy.outputs.deployment_url }} outputs: deployment_url: ${{ steps.deploy.outputs.deployment_url }} - env: - VERCEL_PROJECT_ID: prj_GGc6CEzrWNyUK0hq1UK7KmbgUmZn steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 + + - name: Install + uses: ./.github/actions/install + - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel id: deploy - with: - package: "@fern-ui/docs-bundle" - environment: production - force: true + run: | + pnpm vercel-scripts deploy app-slash.ferndocs.com --token=${{ secrets.VERCEL_TOKEN }} --environment=production + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT ete: needs: deploy_app_buildwithfern_com @@ -82,9 +80,11 @@ jobs: steps: - uses: actions/checkout@v4 - # this script generates domains.txt and preview.txt + - name: Install + uses: ./.github/actions/install + - name: Fetch domains - run: node scripts/fetch-domains.js + run: pnpm vercel-scripts domains.txt app.buildwithfern.com --token ${{ secrets.VERCEL_TOKEN }} - uses: ./github/actions/ete-docs-bundle with: @@ -106,17 +106,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Vercel CLI - run: npm install -g vercel + - name: Install + uses: ./.github/actions/install - name: Promote Deployment - run: vercel promote ${{ matrix.deployment_url }} --token $VERCEL_TOKEN - - revalidate-all: - needs: promote # Ensure that the deployment is promoted before revalidating - uses: ./.github/workflows/revalidate-all.yml - with: - deployment_url: https://app.buildwithfern.com + run: pnpm vercel-scripts promote ${{ matrix.deployment_url }} --token ${{ secrets.VERCEL_TOKEN }} smoke-test: needs: promote # Ensure that the deployment is promoted before running smoke tests diff --git a/.github/workflows/deploy-fern-dashboard-dev.yml b/.github/workflows/deploy-fern-dashboard-dev.yml index 128fb6990e..0d8c8075c6 100644 --- a/.github/workflows/deploy-fern-dashboard-dev.yml +++ b/.github/workflows/deploy-fern-dashboard-dev.yml @@ -1,10 +1,5 @@ name: Deploy @fern-ui/dashboard (dev) env: - VERCEL_ORG_ID: team_6FKOM5nw037hv8g2mTk3gaH7 - VERCEL_PROJECT_ID: prj_pxVBPOeLKJLEUkQXXMHKWq8h6Z0f - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: "buildwithfern" # TODO: move to env vars in vercel AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }} AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }} @@ -28,12 +23,16 @@ jobs: name: Production - dashboard-dev.buildwithfern.com url: ${{ steps.deploy.outputs.deployment_url }} steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 + with: + ref: main + + - name: Install + uses: ./.github/actions/install + - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel id: deploy - with: - ref: ${{ github.ref_name || github.ref }} - package: "@fern-ui/dashboard" - environment: production - promote: true + run: | + pnpm vercel-scripts deploy dashboard-dev.buildwithfern.com --token=${{ secrets.VERCEL_TOKEN }} --environment=production + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/deploy-fern-dashboard-preview.yml b/.github/workflows/deploy-fern-dashboard-preview.yml index e5ac222dfb..34f43b5ff2 100644 --- a/.github/workflows/deploy-fern-dashboard-preview.yml +++ b/.github/workflows/deploy-fern-dashboard-preview.yml @@ -1,10 +1,5 @@ name: Preview @fern-ui/dashboard (dev) env: - VERCEL_ORG_ID: team_6FKOM5nw037hv8g2mTk3gaH7 - VERCEL_PROJECT_ID: prj_pxVBPOeLKJLEUkQXXMHKWq8h6Z0f - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: "buildwithfern" # TODO: move to env vars in vercel AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }} AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }} @@ -12,27 +7,49 @@ env: VENUS_AUDIENCE: ${{ secrets.VENUS_AUDIENCE }} on: + pull_request: push: branches: - main - -# Cancel previous workflows on previous push so we don't have deploys overwriting eachother here -concurrency: - group: dashboard-dev.buildwithfern.com - cancel-in-progress: false + workflow_dispatch: jobs: + ignore: + runs-on: ubuntu-latest + outputs: + continue: ${{ steps.ignore.outputs.continue }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 # used for turbo-ignore + + - name: Install + uses: ./.github/actions/install + with: + run_install: false + + - name: Ignore unchanged files + id: ignore + run: set +e; pnpx turbo-ignore @fern-ui/dashboard --fallback=HEAD^1; echo "continue=$?" >> $GITHUB_OUTPUT + deploy: + needs: ignore + if: needs.ignore.outputs.continue == 1 runs-on: ubuntu-latest environment: name: Preview - dashboard.buildwithfern.com url: ${{ steps.deploy.outputs.deployment_url }} steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref || github.ref_name || github.ref }} + + - name: Install + uses: ./.github/actions/install + - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel id: deploy - with: - ref: ${{ github.ref_name || github.ref }} - package: "@fern-ui/dashboard" - environment: preview + run: | + pnpm vercel-scripts deploy dashboard.buildwithfern.com --token=${{ secrets.VERCEL_TOKEN }} + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/deploy-fern-dashboard-prod.yml b/.github/workflows/deploy-fern-dashboard-prod.yml index c4167ab431..9ba4f7eeb8 100644 --- a/.github/workflows/deploy-fern-dashboard-prod.yml +++ b/.github/workflows/deploy-fern-dashboard-prod.yml @@ -1,10 +1,5 @@ name: Deploy @fern-ui/dashboard env: - VERCEL_ORG_ID: team_6FKOM5nw037hv8g2mTk3gaH7 - VERCEL_PROJECT_ID: prj_59B94Hus48Nbb0XrqFYZSALYmYTw - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: "buildwithfern" # TODO: move to env vars in vercel AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }} AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }} @@ -29,13 +24,16 @@ jobs: name: Production - dashboard.buildwithfern.com url: ${{ steps.deploy.outputs.deployment_url }} steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 + with: + ref: main + + - name: Install + uses: ./.github/actions/install + - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel id: deploy - with: - ref: ${{ github.ref_name || github.ref }} - package: "@fern-ui/dashboard" - environment: production - force: true - promote: true + run: | + pnpm vercel-scripts deploy dashboard.buildwithfern.com --token=${{ secrets.VERCEL_TOKEN }} --environment=production + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/deploy-fontawesome-cdn.yml b/.github/workflows/deploy-fontawesome-cdn.yml index 65519bc1d5..54044e1f8d 100644 --- a/.github/workflows/deploy-fontawesome-cdn.yml +++ b/.github/workflows/deploy-fontawesome-cdn.yml @@ -1,10 +1,4 @@ name: Deploy @fern-ui/fontawesome-cdn -env: - VERCEL_ORG_ID: team_6FKOM5nw037hv8g2mTk3gaH7 - VERCEL_PROJECT_ID: prj_MO0ayWSAeIoBYh8Cd13oigkd9knl - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: "buildwithfern" on: push: @@ -17,19 +11,43 @@ concurrency: cancel-in-progress: true jobs: + ignore: + runs-on: ubuntu-latest + outputs: + continue: ${{ steps.ignore.outputs.continue }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 # used for turbo-ignore + + - name: Install + uses: ./.github/actions/install + with: + run_install: false + + - name: Ignore unchanged files + id: ignore + run: set +e; pnpx turbo-ignore @fern-ui/fontawesome-cdn --fallback=HEAD^1; echo "continue=$?" >> $GITHUB_OUTPUT + deploy: + needs: ignore + if: needs.ignore.outputs.continue == 1 runs-on: ubuntu-latest environment: name: Production - icons.ferndocs.com url: ${{ steps.deploy.outputs.deployment_url }} steps: + # set the ref to a specific branch so that the deployment is scoped to that branch (instead of a headless ref) - uses: actions/checkout@v4 + with: + ref: main + + - name: Install + uses: ./.github/actions/install + - name: Build & Deploy to Vercel - uses: ./.github/actions/vercel id: deploy - with: - ref: ${{ github.ref_name || github.ref }} - package: "@fern-ui/fontawesome-cdn" - environment: production - force: true - promote: true + run: | + pnpm vercel-scripts deploy icons.ferndocs.com --token=${{ secrets.VERCEL_TOKEN }} --environment=production + echo "Deployed to $(cat deployment-url.txt) but NOT promoted. Please promote manually." + echo "deployment_url=$(cat deployment-url.txt)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 1c6ac51786..5416be60ca 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -13,6 +13,7 @@ jobs: test: timeout-minutes: 10 runs-on: ubuntu-latest + permissions: write-all # required for the playwright-report-summary action steps: - uses: actions/checkout@v4 @@ -27,7 +28,7 @@ jobs: WORKOS_CLIENT_ID: ${{ secrets.WORKOS_CLIENT_ID }} - name: Build next bundle - run: cd packages/ui/local-preview-bundle; pnpm next build + run: cd packages/ui/local-preview-bundle; pnpm turbo build - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps @@ -41,11 +42,9 @@ jobs: npm install -g @fern-api/fern-api-dev - name: Run Playwright tests - run: pnpm exec playwright test playwright/fixtures + run: PLAYWRIGHT_JSON_OUTPUT_NAME=results.json pnpm exec playwright test playwright/fixtures --reporter json - - uses: actions/upload-artifact@v4 + - uses: daun/playwright-report-summary@v3 if: always() with: - name: playwright-report - path: playwright-report/ - retention-days: 30 + report-file: results.json diff --git a/.github/workflows/revalidate-all.yml b/.github/workflows/revalidate-all.yml index ca10663d38..1b9653a0a9 100644 --- a/.github/workflows/revalidate-all.yml +++ b/.github/workflows/revalidate-all.yml @@ -25,11 +25,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - if: github.event_name == 'workflow_call' + + - name: Install + uses: ./.github/actions/install - name: Revalidate all - env: - DEPLOYMENT_URL: ${{ inputs.deployment_url || 'app.buildwithfern.com' }} - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - run: | - node scripts/revalidate-all.js + run: pnpm vercel-scripts revalidate-all ${{ inputs.deployment_url || 'app.buildwithfern.com' }} --token ${{ secrets.VERCEL_TOKEN }} diff --git a/clis/vercel-scripts/.gitignore b/clis/vercel-scripts/.gitignore new file mode 100644 index 0000000000..e985853ed8 --- /dev/null +++ b/clis/vercel-scripts/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/clis/vercel-scripts/package.json b/clis/vercel-scripts/package.json new file mode 100644 index 0000000000..39bbdf6e65 --- /dev/null +++ b/clis/vercel-scripts/package.json @@ -0,0 +1,31 @@ +{ + "name": "@fern-platform/vercel-scripts", + "version": "0.0.0", + "type": "module", + "scripts": { + "lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../.eslintignore", + "lint:eslint:fix": "pnpm lint:eslint --fix", + "lint:style": "stylelint 'src/**/*.scss' --allow-empty-input --max-warnings 0", + "lint:style:fix": "pnpm lint:style --fix", + "format": "prettier --write --ignore-unknown --ignore-path ../../shared/.prettierignore \"**\"", + "format:check": "prettier --check --ignore-unknown --ignore-path ../../shared/.prettierignore \"**\"", + "organize-imports": "organize-imports-cli tsconfig.json", + "depcheck": "depcheck" + }, + "devDependencies": { + "@fern-platform/configs": "workspace:*", + "@types/node": "^18.7.18", + "@types/yargs": "^17.0.32", + "depcheck": "^1.4.3", + "eslint": "^8.56.0", + "organize-imports-cli": "^0.10.0", + "prettier": "^3.3.2", + "typescript": "4.9.5", + "yargs": "^17.4.1" + }, + "dependencies": { + "@fern-fern/fern-docs-sdk": "0.0.5", + "@fern-fern/vercel": "0.0.4607", + "ts-essentials": "^10.0.1" + } +} diff --git a/clis/vercel-scripts/src/cli.ts b/clis/vercel-scripts/src/cli.ts new file mode 100644 index 0000000000..dd55215583 --- /dev/null +++ b/clis/vercel-scripts/src/cli.ts @@ -0,0 +1,191 @@ +import { VercelClient } from "@fern-fern/vercel"; +import { writeFileSync } from "fs"; +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { cwd } from "./cwd.js"; +import { VercelDeployer } from "./utils/deployer.js"; +import { DocsRevalidator } from "./utils/revalidator.js"; + +function isValidEnvironment(environment: string): environment is "preview" | "production" { + return environment === "preview" || environment === "production"; +} + +void yargs(hideBin(process.argv)) + .scriptName("vercel-scripts") + .strict() + .options("token", { + type: "string", + description: "The Vercel API token", + demandOption: true, + default: process.env.VERCEL_TOKEN, + }) + .options("teamId", { + type: "string", + description: "The Vercel team ID", + demandOption: false, + default: process.env.VERCEL_ORG_ID ?? "team_6FKOM5nw037hv8g2mTk3gaH7", + }) + .options("teamName", { + type: "string", + description: "The Vercel team name", + demandOption: false, + default: "buildwithfern", + }) + .command( + "deploy ", + "Deploy a project to Vercel", + (argv) => + argv + .positional("project", { + type: "string", + demandOption: true, + description: "The project ID or project name to deploy", + }) + .options("environment", { + type: "string", + description: "The environment to deploy to", + demandOption: true, + default: "preview", + choices: ["preview" as const, "production" as const], + }) + .option("skip-deploy", { type: "boolean", description: "Skip the deploy step" }) + .option("force", { type: "boolean", description: "Always deploy, even if the project is up-to-date" }) + .option("output", { + type: "string", + description: "The output file to write the preview URLs to", + default: "deployment-url.txt", + }), + async ({ project, environment, token, teamName, teamId, output, skipDeploy, force }) => { + if (!isValidEnvironment(environment)) { + throw new Error(`Invalid environment: ${environment}`); + } + + // eslint-disable-next-line no-console + console.log(`Deploying project ${project} to ${environment} environment`); + + const cli = new VercelDeployer({ + token, + teamName, + teamId, + environment, + cwd: cwd(), + }); + + const result = await cli.buildAndDeployToVercel(project, { skipDeploy, force }); + + if (result) { + // eslint-disable-next-line no-console + console.log("Deployed to:", result.deploymentUrl); + + writeFileSync(output, result.deploymentUrl); + } + + process.exit(0); + }, + ) + .command( + "promote ", + "Promote a deployment to production", + (argv) => + argv.positional("deploymentUrl", { type: "string", demandOption: true }).option("revalidate-all", { + type: "boolean", + description: "Revalidate the deployment (if it's fern docs)", + }), + async ({ deploymentUrl, token, teamId, revalidateAll }) => { + const deployment = await new VercelClient({ token }).deployments.getDeployment( + deploymentUrl.replace("https://", ""), + { teamId, withGitRepoInfo: "false" }, + ); + + if (deployment.target !== "production") { + // eslint-disable-next-line no-console + console.error("Deployment is not a production deployment"); + process.exit(1); + } else if (deployment.readySubstate !== "STAGED") { + // eslint-disable-next-line no-console + console.error("Deployment is not staged"); + process.exit(1); + } else if (!deployment.project) { + // eslint-disable-next-line no-console + console.error("Deployment does not have a project"); + process.exit(1); + } + + if (revalidateAll) { + const revalidator = new DocsRevalidator({ token, project: deployment.project.name, teamId }); + + await revalidator.revalidateAll(); + } + + process.exit(0); + }, + ) + .command( + "revalidate-all ", + "Revalidate all docs for a deployment", + (argv) => argv.positional("deploymentUrl", { type: "string", demandOption: true }), + async ({ deploymentUrl, token, teamId }) => { + const revalidator = new DocsRevalidator({ token, project: deploymentUrl, teamId }); + + await revalidator.revalidateAll(); + + process.exit(0); + }, + ) + .command( + "preview.txt ", + "Get preview URLs for a deployment", + (argv) => + argv.positional("deploymentUrl", { type: "string", demandOption: true }).option("output", { + type: "string", + description: "The output file to write the preview URLs to", + default: "preview.txt", + }), + async ({ deploymentUrl, token, teamId, output }) => { + const deployment = await new VercelClient({ token }).deployments.getDeployment( + deploymentUrl.replace("https://", ""), + { teamId, withGitRepoInfo: "false" }, + ); + + if (!deployment.project) { + throw new Error("Deployment does not have a project"); + } + + const revalidator = new DocsRevalidator({ token, project: deployment.project.name, teamId }); + + const urls = await revalidator.getPreviewUrls(deploymentUrl); + + writeFileSync(output, `## PR Preview\n\n${urls.map((d) => `- [ ] [${d.name}](${d.url})`).join("\n")}`); + + process.exit(0); + }, + ) + .command( + "domains.txt ", + "Get domains for a deployment", + (argv) => + argv.positional("deploymentUrl", { type: "string", demandOption: true }).option("output", { + type: "string", + description: "The output file to write the preview URLs to", + default: "domains.txt", + }), + async ({ deploymentUrl, token, teamId, output }) => { + const deployment = await new VercelClient({ token }).deployments.getDeployment( + deploymentUrl.replace("https://", ""), + { teamId, withGitRepoInfo: "false" }, + ); + + if (!deployment.project) { + throw new Error("Deployment does not have a project"); + } + + const revalidator = new DocsRevalidator({ token, project: deployment.project.name, teamId }); + + const urls = await revalidator.getDomains(); + + writeFileSync(output, urls.join("\n")); + + process.exit(0); + }, + ) + .parse(); diff --git a/clis/vercel-scripts/src/cwd.ts b/clis/vercel-scripts/src/cwd.ts new file mode 100644 index 0000000000..0ae88a1c28 --- /dev/null +++ b/clis/vercel-scripts/src/cwd.ts @@ -0,0 +1,16 @@ +import { exec } from "./utils/exec.js"; + +export function cwd(): string { + const cwd = exec("Get git project root", "git rev-parse --show-toplevel", { + stdio: "pipe", + }).trim(); + + if (!cwd.startsWith("/")) { + throw new Error("Could not detect git project root directory"); + } + + // eslint-disable-next-line no-console + console.log("Detected monorepo root directory:", cwd); + + return cwd; +} diff --git a/clis/vercel-scripts/src/utils/deployer.ts b/clis/vercel-scripts/src/utils/deployer.ts new file mode 100644 index 0000000000..6c966b805c --- /dev/null +++ b/clis/vercel-scripts/src/utils/deployer.ts @@ -0,0 +1,153 @@ +import { VercelClient } from "@fern-fern/vercel"; +import { readFileSync } from "fs"; +import { join } from "path"; +import { UnreachableCaseError } from "ts-essentials"; +import { exec } from "./exec.js"; + +export class VercelDeployer { + private token: string; + private teamName: string; + private teamId: string; + private environment: "preview" | "production"; + private cwd: string; + private vercel: VercelClient; + constructor({ + token, + teamName, + teamId, + environment, + cwd, + }: { + token: string; + teamName: string; + teamId: string; + environment: "preview" | "production"; + cwd: string; + }) { + this.token = token; + this.teamName = teamName; + this.teamId = teamId; + this.environment = environment; + this.cwd = cwd; + this.vercel = new VercelClient({ token }); + } + + get environmentName(): string { + if (this.environment === "preview") { + return "Preview"; + } else if (this.environment === "production") { + return "Production"; + } else if (this.environment === "production-dev2") { + return "Production Dev2"; + } else { + throw new UnreachableCaseError(this.environment); + } + } + + private env(projectId: string): Record { + return { + TURBO_TOKEN: this.token, + TURBO_TEAM: this.teamName, + VERCEL_ORG_ID: this.teamId, + VERCEL_PROJECT_ID: projectId, + }; + } + + private pull(project: { id: string; name: string }): void { + exec( + `[${this.environmentName}] Pull ${project.name} from Vercel (${project.id})`, + `pnpx vercel pull --yes --environment=${this.environment} --token=${this.token}`, + { env: this.env(project.id), cwd: this.cwd }, + ); + } + + private build(project: { id: string; name: string }): void { + let command = `pnpx vercel build --yes --token=${this.token} --debug`; + if (this.environment === "production") { + command += " --prod"; + } + exec(`[${this.environmentName}] Build bundle for ${project.name}`, command, { + env: this.env(project.id), + cwd: this.cwd, + }); + } + + private deploy(project: { id: string; name: string }): string { + let command = `pnpx vercel deploy --yes --prebuilt --token=${this.token} --archive=tgz`; + if (this.environment === "production") { + command += " --prod --skip-domain"; + } + return exec(`[${this.environmentName}] Deploy bundle for ${project.name} to Vercel`, command, { + stdio: "pipe", + env: this.env(project.id), + cwd: this.cwd, + }).trim(); + } + + public promote(deploymentUrl: string): void { + if (this.environment === "production") { + exec( + `[${this.environmentName}] Promote ${deploymentUrl}`, + `pnpx vercel promote ${deploymentUrl} --token=${this.token}`, + { cwd: this.cwd }, + ); + } + } + + public async buildAndDeployToVercel( + project: string, + { skipDeploy = false }: { skipDeploy?: boolean } = {}, + ): Promise< + | { + deploymentUrl: string; + canPromote: boolean; + } + | undefined + > { + const prj = await this.vercel.projects.getProject(project, { teamId: this.teamId }); + + this.pull(prj); + + this.build(prj); + + if (skipDeploy) { + return; + } + + const deploymentUrl = this.deploy(prj); + + if (!deploymentUrl) { + throw new Error("Deployment failed: no deployment URL returned"); + } + + let canPromote = this.environment === "production"; + + if (canPromote) { + /** + * If the deployment is to the dev2 registry, we should automatically promote it + * and not allow manual promotion. + */ + const isDev2 = this.loadEnvFile().includes("registry-dev2.buildwithfern.com"); + + if (isDev2) { + this.promote(deploymentUrl); + canPromote = false; + } + } + + return { + deploymentUrl, + canPromote, + }; + } + + private loadEnvFile(): string { + const dotvercel = join(this.cwd, ".vercel"); + const envfile = join(dotvercel, `.env.${this.environment}.local`); + + // eslint-disable-next-line no-console + console.log("Loading env file:", envfile); + + return readFileSync(envfile, "utf-8"); + } +} diff --git a/clis/vercel-scripts/src/utils/exec.ts b/clis/vercel-scripts/src/utils/exec.ts new file mode 100644 index 0000000000..2ece6e40ff --- /dev/null +++ b/clis/vercel-scripts/src/utils/exec.ts @@ -0,0 +1,56 @@ +import { execFileSync, ExecSyncOptions } from "child_process"; + +export function exec(title: string, command: string | string[], opts?: ExecSyncOptions): string { + const cmd = Array.isArray(command) ? command : command.split(" "); + if (!cmd[0]) { + throw new Error(`Empty command in ${title}`); + } + + logCommand(title, command); + return String( + execFileSync(cmd[0], cmd.slice(1), { + stdio: "inherit", + ...opts, + env: { ...process.env, ...opts?.env }, + }), + ); +} + +export function prettyCommand(command: string | string[]): string { + if (Array.isArray(command)) { + command = command.join(" "); + } + return command.replace(/ -- .*/, " -- …"); +} + +/** + * @param {string} title + * @param {string | string[]} [command] + */ +export function logCommand(title: string, command?: string | string[]): void { + if (command) { + const pretty = prettyCommand(command); + // eslint-disable-next-line no-console + console.log(`\n\x1b[1;4m${title}\x1b[0m\n> \x1b[1m${pretty}\x1b[0m\n`); + } else { + // eslint-disable-next-line no-console + console.log(`\n\x1b[1;4m${title}\x1b[0m\n`); + } +} + +export function booleanArg(args: string[], name: string): boolean { + const index = args.indexOf(name); + if (index === -1) { + return false; + } + args.splice(index, 1); + return true; +} + +export function namedValueArg(args: string[], name: string): string | undefined { + const index = args.indexOf(name); + if (index === -1) { + return undefined; + } + return args.splice(index, 2)[1]; +} diff --git a/clis/vercel-scripts/src/utils/revalidator.ts b/clis/vercel-scripts/src/utils/revalidator.ts new file mode 100644 index 0000000000..bb4f130772 --- /dev/null +++ b/clis/vercel-scripts/src/utils/revalidator.ts @@ -0,0 +1,88 @@ +import { FernDocsClient } from "@fern-fern/fern-docs-sdk"; +import { Vercel, VercelClient } from "@fern-fern/vercel"; +import { logCommand } from "./exec.js"; + +const BANNED_DOMAINS = ["vercel.app", "buildwithfern.com", "ferndocs.com"]; + +export class DocsRevalidator { + private vercel: VercelClient; + private project: string; + private teamId: string; + constructor({ token, project, teamId }: { token: string; project: string; teamId: string }) { + this.vercel = new VercelClient({ token }); + this.project = project; + this.teamId = teamId; + } + + private async *getProductionDomains(): AsyncGenerator { + let cursor: number | undefined = undefined; + do { + const res = await this.vercel.projects.getProjectDomains(this.project, { + teamId: this.teamId, + production: "true", + verified: "true", + limit: 50, + order: "ASC", + since: cursor ? cursor + 1 : undefined, + }); + + yield* res.domains.filter((domain) => !BANNED_DOMAINS.includes(domain.apexName)); + cursor = res.pagination.next; + } while (cursor); + } + + async getDomains(): Promise { + const domains: string[] = []; + for await (const domain of this.getProductionDomains()) { + domains.push(domain.name); + } + return domains.sort(); + } + + async getPreviewUrls(deploymentUrl: string): Promise<{ url: string; name: string }[]> { + const url = new URL("/api/fern-docs/preview", deploymentUrl); + + const urls: { url: string; name: string }[] = []; + + for await (const domain of this.getProductionDomains()) { + url.searchParams.set("host", domain.name); + urls.push({ url: url.toString(), name: domain.name }); + } + + return urls.sort((a, b) => a.name.localeCompare(b.name)); + } + + async revalidateAll(): Promise { + const summary: Record = {}; + + for await (const domain of this.getProductionDomains()) { + // eslint-disable-next-line no-console + console.log(`Revalidating ${domain.name}...`); + + const client = new FernDocsClient({ + // TODO: handle docs with basepath + environment: `https://${domain.name}`, + }); + + const revalidationSummary = { success: 0, failed: 0 }; + const results = await client.revalidation.revalidateAllV4({ limit: 50 }); + + for await (const result of results) { + if (!result.success) { + // eslint-disable-next-line no-console + console.warn(`[${domain.name}] Failed to revalidate ${result.url}: ${result.error}`); + revalidationSummary.failed++; + } else { + revalidationSummary.success++; + } + } + } + + // eslint-disable-next-line no-console + logCommand("Revalidation summary"); + Object.entries(summary).forEach(([domain, { success, failed }]) => { + // eslint-disable-next-line no-console + console.log(`- ${domain}: ${success} successful, ${failed} failed`); + }); + } +} diff --git a/clis/vercel-scripts/tsconfig.json b/clis/vercel-scripts/tsconfig.json new file mode 100644 index 0000000000..02b489cb75 --- /dev/null +++ b/clis/vercel-scripts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@fern-platform/configs/tsconfig/cli.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "composite": false, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/fern/apis/fern-docs/definition/api.yml b/fern/apis/fern-docs/definition/api.yml new file mode 100644 index 0000000000..9d73e0ef03 --- /dev/null +++ b/fern/apis/fern-docs/definition/api.yml @@ -0,0 +1 @@ +name: fern-docs diff --git a/fern/apis/fern-docs/definition/revalidation.yml b/fern/apis/fern-docs/definition/revalidation.yml new file mode 100644 index 0000000000..d92f529f65 --- /dev/null +++ b/fern/apis/fern-docs/definition/revalidation.yml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json + +service: + auth: false + base-path: /api/fern-docs + endpoints: + revalidatePath: + path: /revalidate-path + display-name: Revalidates a single path + method: GET + request: + name: RevalidatePathRequest + query-parameters: + path: string + response: RevalidationResult + + revalidateAllV3: + path: /revalidate-all/v3 + docs: Revalidates all pages in a docs website + display-name: Revalidate a docs instance + method: GET + response: RevalidateAllV3Response + + revalidateAllV4: + path: /revalidate-all/v4 + docs: Revalidates all pages a docs website with pagination + display-name: Revalidate a docs instance with pagination + method: GET + pagination: + offset: $request.offset + results: $response.results + request: + name: RevalidateAllV4Request + query-parameters: + offset: + type: optional + default: 0 + limit: + type: optional + default: 10 + response: RevalidateAllV4Response + +types: + RevalidateAllV3Response: + properties: + successfulRevalidations: list + failedRevalidations: list + + RevalidateAllV4Response: + properties: + total: integer + results: list + + RevalidationResult: + discriminated: false + union: + - SuccessfulRevalidation + - FailedRevalidation + + SuccessfulRevalidation: + properties: + success: literal + url: string + + FailedRevalidation: + properties: + success: literal + url: string + error: string diff --git a/fern/apis/revalidation/generators.yml b/fern/apis/fern-docs/generators.yml similarity index 55% rename from fern/apis/revalidation/generators.yml rename to fern/apis/fern-docs/generators.yml index 24a5befbea..eea2d6944d 100644 --- a/fern/apis/revalidation/generators.yml +++ b/fern/apis/fern-docs/generators.yml @@ -3,10 +3,12 @@ groups: sdk: generators: - name: fernapi/fern-typescript-node-sdk - version: 0.12.8-rc0 + version: 0.40.4 output: location: npm url: npm.buildwithfern.com - package-name: "@fern-fern/revalidation-sdk" - config: + package-name: "@fern-fern/fern-docs-sdk" + config: skipResponseValidation: true + includeUtilsOnUnionMembers: true + namespaceExport: FernDocs diff --git a/fern/apis/revalidation/definition/__package__.yml b/fern/apis/revalidation/definition/__package__.yml deleted file mode 100644 index c7e46a8f02..0000000000 --- a/fern/apis/revalidation/definition/__package__.yml +++ /dev/null @@ -1,63 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json - -service: - auth: false - base-path: /api/fern-docs - endpoints: - revalidateAllV2: - path: /revalidate-all/v2 - docs: Revalidates a docs website - display-name: Revalidate a docs instance - method: POST - request: - name: CreateRevalidateAllV2Request - headers: - x-fern-host: - type: string - docs: | - The host you want to revalidate the docs for. - query-parameters: - basePath: - type: string - docs: | - The base path of the docs you want to revalidate. - body: - properties: - host: - type: string - docs: | - The host you want to revalidate the docs for. - response: RevalidateAllV2Response - revalidateAllV3: - path: /revalidate-all/v3 - docs: Revalidates a docs website - display-name: Revalidate a docs instance - method: GET - request: - name: CreateRevalidateAllV3Request - headers: - x-fern-host: - type: string - docs: | - The host you want to revalidate the docs for. - query-parameters: - host: - type: string - docs: | - The host you want to revalidate the docs for. - basePath: - type: string - docs: | - The base path of the docs you want to revalidate. - response: RevalidateAllV2Response - -types: - RevalidateAllV2Response: - properties: - successfulRevalidations: list - failedRevalidations: list - - SuccessfulRevalidations: - properties: - success: boolean - url: string diff --git a/fern/apis/revalidation/definition/api.yml b/fern/apis/revalidation/definition/api.yml deleted file mode 100644 index 9d772657f1..0000000000 --- a/fern/apis/revalidation/definition/api.yml +++ /dev/null @@ -1 +0,0 @@ -name: revalidation diff --git a/fern/apis/vercel/generators.yml b/fern/apis/vercel/generators.yml index 010ac57e72..3d72d36b8f 100644 --- a/fern/apis/vercel/generators.yml +++ b/fern/apis/vercel/generators.yml @@ -8,6 +8,6 @@ groups: location: npm url: npm.buildwithfern.com package-name: "@fern-fern/vercel" - config: + config: skipResponseValidation: true - namespaceExport: Vercel \ No newline at end of file + namespaceExport: Vercel diff --git a/fern/docs.yml b/fern/docs.yml index b9c19b807f..50807dd9e6 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -11,13 +11,13 @@ navigation: path: ./docs/pages/enable-api-playground.mdx - page: Configure Custom Domain path: ./docs/pages/configure-custom-domain.mdx - - page: Configure Segment for a new Customer + - page: Configure Segment for a new Customer path: ./docs/pages/configure-segment.mdx - api: FDR API Reference api-name: fdr display-errors: true - - api: Revalidation API Reference - api-name: revalidation + - api: Fern Docs API Reference + api-name: fern-docs display-errors: true - api: Proxy API Reference api-name: proxy diff --git a/package.json b/package.json index 142e40fca5..064be86326 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "format": "prettier --write --ignore-unknown --ignore-path ./shared/.prettierignore \"**\"", "format:check": "prettier --check --ignore-unknown --ignore-path ./shared/.prettierignore \"**\"", "publish": "pnpm -r --filter=!private --parallel exec -- npm publish --access public", + "vercel-scripts": "pnpm tsx clis/vercel-scripts/src/cli.ts", "check-docs-release-blockers": "pnpm tsx packages/scripts/src/cli.ts -- fern-scripts check-docs-release-blockers", "root-package:check": "pnpm tsx packages/scripts/src/cli.ts -- fern-scripts check-root-package", "root-package:fix": "pnpm root-package:check --fix", diff --git a/packages/ui/docs-bundle/next.config.mjs b/packages/ui/docs-bundle/next.config.mjs index 89f94edb79..1b61cc7667 100644 --- a/packages/ui/docs-bundle/next.config.mjs +++ b/packages/ui/docs-bundle/next.config.mjs @@ -1,4 +1,4 @@ -import createWithBundleAnalyzer from "@next/bundle-analyzer"; +import NextBundleAnalyzer from "@next/bundle-analyzer"; import { withSentryConfig } from "@sentry/nextjs"; import process from "node:process"; @@ -160,8 +160,8 @@ const nextConfig = { }, }; -const withBundleAnalyzer = createWithBundleAnalyzer({ - enabled: isTruthy(process.env.ANALYZE), +const withBundleAnalyzer = NextBundleAnalyzer({ + enabled: true, }); // Injected content via Sentry wizard below diff --git a/packages/ui/docs-bundle/package.json b/packages/ui/docs-bundle/package.json index a7b580fd46..09c1894d73 100644 --- a/packages/ui/docs-bundle/package.json +++ b/packages/ui/docs-bundle/package.json @@ -31,6 +31,7 @@ "lint": "pnpm lint:eslint && pnpm lint:style" }, "dependencies": { + "@fern-fern/fern-docs-sdk": "0.0.5", "@algolia/requester-fetch": "^4.24.0", "@aws-sdk/client-s3": "^3.335.0", "@aws-sdk/s3-request-presigner": "^3.574.0", diff --git a/packages/ui/docs-bundle/projects.json b/packages/ui/docs-bundle/projects.json deleted file mode 100644 index f7f11af7be..0000000000 --- a/packages/ui/docs-bundle/projects.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "name": "app.buildwithfern.com", - "url": "https://app.buildwithfern.com", - "id": "prj_QX3venU6jwRUmdt8ArfL8AU5r1d4" - }, - { - "name": "app.ferndocs.com", - "url": "https://app.ferndocs.com", - "id": "prj_SfgTTzw7KefTMuVWsL5uhY8Y4BIt" - }, - { - "name": "app-slash.ferndocs.com", - "url": "https://app-slash.ferndocs.com", - "id": "prj_GGc6CEzrWNyUK0hq1UK7KmbgUmZn" - } -] diff --git a/packages/ui/docs-bundle/src/middleware.ts b/packages/ui/docs-bundle/src/middleware.ts index 76b9728529..33d9c8cff4 100644 --- a/packages/ui/docs-bundle/src/middleware.ts +++ b/packages/ui/docs-bundle/src/middleware.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-internal-modules import { FernUser, getAuthEdgeConfig, verifyFernJWTConfig } from "@fern-ui/ui/auth"; import { NextRequest, NextResponse, type NextMiddleware } from "next/server"; import urlJoin from "url-join"; diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts index 2e55663626..8057d86630 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/changelog.ts @@ -3,14 +3,13 @@ import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import { NodeCollector } from "@fern-api/fdr-sdk/navigation"; import { assertNever } from "@fern-ui/core-utils"; import { getFrontmatter } from "@fern-ui/ui"; +import { checkViewerAllowedNode } from "@fern-ui/ui/auth"; +import * as Sentry from "@sentry/nextjs"; import { Feed, Item } from "feed"; import { NextApiRequest, NextApiResponse } from "next"; import { buildUrlFromApiNode } from "../../../utils/buildUrlFromApi"; import { loadWithUrl } from "../../../utils/loadWithUrl"; import { getXFernHostNode } from "../../../utils/xFernHost"; -// eslint-disable-next-line import/no-internal-modules -import { checkViewerAllowedNode } from "@fern-ui/ui/auth"; -import * as Sentry from "@sentry/nextjs"; export const revalidate = 60 * 60 * 24; diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts index 060d359237..98eedd8005 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts @@ -7,14 +7,12 @@ import { setMdxBundler, type ResolvedRootPackage, } from "@fern-ui/ui"; +import { checkViewerAllowedNode } from "@fern-ui/ui/auth"; +import { getMdxBundler } from "@fern-ui/ui/bundlers"; import { NextApiHandler, NextApiResponse } from "next"; import { buildUrlFromApiNode } from "../../../utils/buildUrlFromApi"; import { getXFernHostNode } from "../../../utils/xFernHost"; import { getFeatureFlags } from "./feature-flags"; -// eslint-disable-next-line import/no-internal-modules -import { checkViewerAllowedNode } from "@fern-ui/ui/auth"; -// eslint-disable-next-line import/no-internal-modules -import { getMdxBundler } from "@fern-ui/ui/bundlers"; export const dynamic = "force-dynamic"; diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-all/v3.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-all/v3.ts index 5e8468e889..c78edcdcfc 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-all/v3.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-all/v3.ts @@ -1,54 +1,43 @@ import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import { NodeCollector } from "@fern-api/fdr-sdk/navigation"; -import { buildUrl } from "@fern-ui/fdr-utils"; -// eslint-disable-next-line import/no-internal-modules +import type { FernDocs } from "@fern-fern/fern-docs-sdk"; +import { provideRegistryService } from "@fern-ui/ui"; import { getAuthEdgeConfig } from "@fern-ui/ui/auth"; import { NextApiHandler, NextApiRequest, NextApiResponse } from "next"; -import urljoin from "url-join"; -import { loadWithUrl } from "../../../../utils/loadWithUrl"; -import { toValidPathname } from "../../../../utils/toValidPathname"; -import { conformTrailingSlash } from "../../../../utils/trailingSlash"; +import { Revalidator } from "../../../../utils/revalidator"; import { getXFernHostNode } from "../../../../utils/xFernHost"; export const config = { maxDuration: 300, }; -type RevalidatePathResult = RevalidatePathSuccessResult | RevalidatePathErrorResult; +// reduce concurrency per domain +const DEFAULT_BATCH_SIZE = 100; -interface RevalidatePathSuccessResult { - success: true; - url: string; -} - -function isSuccessResult(result: RevalidatePathResult): result is RevalidatePathSuccessResult { +function isSuccessResult(result: FernDocs.RevalidationResult): result is FernDocs.SuccessfulRevalidation { return result.success; } -interface RevalidatePathErrorResult { - success: false; - url: string; - message: string; -} - -function isFailureResult(result: RevalidatePathResult): result is RevalidatePathErrorResult { +function isFailureResult(result: FernDocs.RevalidationResult): result is FernDocs.FailedRevalidation { return !result.success; } -type RevalidatedPaths = { - successfulRevalidations: RevalidatePathSuccessResult[]; - failedRevalidations: RevalidatePathErrorResult[]; -}; +function chunk(arr: T[], size: number): T[][] { + return arr.reduce((acc, _, i) => (i % size ? acc : [...acc, arr.slice(i, i + size)]), [] as T[][]); +} const handler: NextApiHandler = async ( req: NextApiRequest, - res: NextApiResponse, + res: NextApiResponse, ): Promise => { - try { - // when we call res.revalidate() nextjs uses - // req.headers.host to make the network request - const xFernHost = getXFernHostNode(req, true); + // when we call res.revalidate() nextjs uses + // req.headers.host to make the network request + const xFernHost = getXFernHostNode(req, true); + req.headers.host = xFernHost; + + const revalidate = new Revalidator(res, xFernHost); + try { const authConfig = await getAuthEdgeConfig(xFernHost); /** @@ -61,53 +50,24 @@ const handler: NextApiHandler = async ( return res.status(200).json({ successfulRevalidations: [], failedRevalidations: [] }); } - const url = buildUrl({ - host: xFernHost, - pathname: toValidPathname(req.query.basePath), - }); - // eslint-disable-next-line no-console - console.log("[revalidate-all/v2] Loading docs for", url); - const docs = await loadWithUrl(url); + const docs = await provideRegistryService().docs.v2.read.getDocsForUrl({ url: xFernHost }); - if (docs == null) { - // return notFoundResponse(); - return res.status(404).json({ successfulRevalidations: [], failedRevalidations: [] }); + if (!docs.ok) { + /** + * If the error is UnauthorizedError, we don't need to revalidate, since all the routes require SSR. + */ + return res + .status(docs.error.error === "UnauthorizedError" ? 200 : 404) + .json({ successfulRevalidations: [], failedRevalidations: [] }); } - const node = FernNavigation.utils.convertLoadDocsForUrlResponse(docs); + const node = FernNavigation.utils.convertLoadDocsForUrlResponse(docs.body); const slugCollector = NodeCollector.collect(node); - const urls = slugCollector.getPageSlugs().map((slug) => conformTrailingSlash(urljoin(xFernHost, slug))); - - // when we call res.revalidate() nextjs uses - // req.headers.host to make the network request - if ( - docs.baseUrl.domain.includes(".docs.buildwithfern.com") || - docs.baseUrl.domain.includes(".docs.dev.buildwithfern.com") - ) { - req.headers.host = xFernHost; - } + const slugs = slugCollector.getPageSlugs(); - const results: RevalidatePathResult[] = []; - - const batchSize = 100; // reduce concurrency per domain - for (let i = 0; i < urls.length; i += batchSize) { - const batch = urls.slice(i, i + batchSize); - results.push( - ...(await Promise.all( - batch.map(async (url): Promise => { - // eslint-disable-next-line no-console - console.log(`Revalidating ${url}`); - try { - await res.revalidate(`/static/${encodeURI(url)}`); - return { success: true, url }; - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - return { success: false, url, message: e instanceof Error ? e.message : "Unknown error." }; - } - }), - )), - ); + const results: FernDocs.RevalidationResult[] = []; + for (const batch of chunk(slugs, DEFAULT_BATCH_SIZE)) { + results.push(...(await revalidate.batch(batch))); } const successfulRevalidations = results.filter(isSuccessResult); diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-all/v4.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-all/v4.ts new file mode 100644 index 0000000000..5fb12af538 --- /dev/null +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-all/v4.ts @@ -0,0 +1,85 @@ +import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; +import { NodeCollector } from "@fern-api/fdr-sdk/navigation"; +import type { FernDocs } from "@fern-fern/fern-docs-sdk"; +import { provideRegistryService } from "@fern-ui/ui"; +import { getAuthEdgeConfig } from "@fern-ui/ui/auth"; +import { NextApiHandler, NextApiRequest, NextApiResponse } from "next"; +import { Revalidator } from "../../../../utils/revalidator"; +import { getXFernHostNode } from "../../../../utils/xFernHost"; + +export const config = { + maxDuration: 300, +}; + +const MAX_BATCH_SIZE = 100; +const DEFAULT_BATCH_SIZE = 10; + +const handler: NextApiHandler = async ( + req: NextApiRequest, + res: NextApiResponse, +): Promise => { + // when we call res.revalidate() nextjs uses + // req.headers.host to make the network request + const xFernHost = getXFernHostNode(req, true); + req.headers.host = xFernHost; + + /** + * Limit the number of paths to revalidate to max of 100. + */ + let limit = req.query.limit == null ? DEFAULT_BATCH_SIZE : parseInt(req.query.limit as string, 10); + if (isNaN(limit) || limit < 0) { + // eslint-disable-next-line no-console + console.error("Invalid limit:", req.query.limit); + return res.status(400).json({ total: 0, results: [] }); + } + limit = Math.min(limit, MAX_BATCH_SIZE); + + /** + * Offset is the number of paths to skip before starting to revalidate. + */ + const offset = req.query.offset == null ? 0 : parseInt(req.query.offset as string, 10); + if (isNaN(offset) || offset < 0) { + // eslint-disable-next-line no-console + console.error("Invalid offset:", req.query.offset); + return res.status(400).json({ total: 0, results: [] }); + } + + try { + const authConfig = await getAuthEdgeConfig(xFernHost); + + /** + * If the auth config is basic_token_verification, we don't need to revalidate. + * + * This is because basic_token_verification is a special case where all the routes are protected by a fern_token that + * is generated by the customer, and so all routes use SSR and are not cached. + */ + if (authConfig?.type === "basic_token_verification") { + return res.status(200).json({ total: 0, results: [] }); + } + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + return res.status(500).json({ total: 0, results: [] }); + } + + const docs = await provideRegistryService().docs.v2.read.getDocsForUrl({ url: xFernHost }); + + if (!docs.ok) { + /** + * If the error is UnauthorizedError, we don't need to revalidate, since all the routes require SSR. + */ + return res.status(docs.error.error === "UnauthorizedError" ? 200 : 404).json({ total: 0, results: [] }); + } + + const node = FernNavigation.utils.convertLoadDocsForUrlResponse(docs.body); + const slugs = NodeCollector.collect(node).getPageSlugs(); + const total = slugs.length; + const batch = slugs.slice(offset, offset + limit); + + const revalidate = new Revalidator(res, xFernHost); + const results = await revalidate.batch(batch); + + return res.status(200).json({ total, results }); +}; + +export default handler; diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-path.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-path.ts index 7abf48ce6b..7df446d7a2 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-path.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/revalidate-path.ts @@ -1,42 +1,33 @@ -// eslint-disable-next-line import/no-internal-modules +import { FernDocs } from "@fern-fern/fern-docs-sdk"; import { NextApiHandler, NextApiRequest, NextApiResponse } from "next"; -import urljoin from "url-join"; +import { Revalidator } from "../../../utils/revalidator"; import { getXFernHostNode } from "../../../utils/xFernHost"; export const config = { maxDuration: 300, }; -type ValidPath = string; - -function isPath(path: string | undefined): path is ValidPath { - return typeof path === "string" && path.length > 0 && path[0] === "/"; -} - -const handler: NextApiHandler = async (req: NextApiRequest, res: NextApiResponse): Promise => { +const handler: NextApiHandler = async ( + req: NextApiRequest, + res: NextApiResponse, +): Promise => { const xFernHost = getXFernHostNode(req, true); + const revalidate = new Revalidator(res, xFernHost); - let path: string | undefined = undefined; - - if (typeof req.query.path === "string") { - path = req.query.path; - } else if (typeof req.query.slug === "string") { - path = `/${req.query.slug}`; - } + const path = req.query.path; - if (!isPath(path)) { - return res.status(400).json({ revalidated: false, message: "Invalid path", path }); + if (typeof path !== "string") { + return res + .status(400) + .json({ success: false, error: `Invalid path: ${path}`, url: revalidate.getUrl(String(path)) }); } - const ssgPath = urljoin("/static/", xFernHost, path); - try { - await res.revalidate(ssgPath); + const result = await revalidate.path(path); + return res.status(result.success ? 200 : 500).json(result); } catch (error) { - return res.status(500).json({ revalidated: false, message: String(error), path }); + return res.status(500).json({ success: false, error: String(error), url: revalidate.getUrl(path) }); } - - return res.status(200).json({ revalidated: true, path }); }; export default handler; diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/search.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/search.ts index 8a0b49d6e5..2a6f6b14d4 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/search.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/search.ts @@ -1,6 +1,5 @@ import { SearchConfig, getSearchConfig } from "@fern-ui/search-utils"; import { provideRegistryService } from "@fern-ui/ui"; -// eslint-disable-next-line import/no-internal-modules import { checkViewerAllowedEdge } from "@fern-ui/ui/auth"; import * as Sentry from "@sentry/nextjs"; import { NextRequest, NextResponse } from "next/server"; diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/sitemap.xml.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/sitemap.xml.ts index 92318ec2b2..f6b1ea42f3 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/sitemap.xml.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/sitemap.xml.ts @@ -1,13 +1,12 @@ import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import { NodeCollector } from "@fern-api/fdr-sdk/navigation"; +import { checkViewerAllowedEdge } from "@fern-ui/ui/auth"; import { NextRequest, NextResponse } from "next/server"; import urljoin from "url-join"; import { buildUrlFromApiEdge } from "../../../utils/buildUrlFromApi"; import { loadWithUrl } from "../../../utils/loadWithUrl"; -import { getXFernHostEdge } from "../../../utils/xFernHost"; -// eslint-disable-next-line import/no-internal-modules -import { checkViewerAllowedEdge } from "@fern-ui/ui/auth"; import { conformTrailingSlash } from "../../../utils/trailingSlash"; +import { getXFernHostEdge } from "../../../utils/xFernHost"; export const runtime = "edge"; export const revalidate = 60 * 60 * 24; diff --git a/packages/ui/docs-bundle/src/utils/revalidator.ts b/packages/ui/docs-bundle/src/utils/revalidator.ts new file mode 100644 index 0000000000..612a2f3d09 --- /dev/null +++ b/packages/ui/docs-bundle/src/utils/revalidator.ts @@ -0,0 +1,38 @@ +import type { FernDocs } from "@fern-fern/fern-docs-sdk"; +import type { NextApiResponse } from "next"; +import urljoin from "url-join"; +import { conformTrailingSlash } from "./trailingSlash"; + +export class Revalidator implements Revalidator { + constructor( + private res: NextApiResponse, + private domain: string, + ) {} + + getUrl(path: string): string { + return conformTrailingSlash(urljoin(this.domain, encodeURI(path))); + } + + async path(path: string): Promise { + const url = this.getUrl(path); + // eslint-disable-next-line no-console + console.log(`Revalidating ${url}`); + try { + await this.res.revalidate(`/static/${url}`); + return { success: true, url }; + } catch (error) { + // eslint-disable-next-line no-console + console.error(`Failed to revalidate ${url}:`, error); + return { success: false, url, error: String(error) }; + } + } + + async batch(paths: string[]): Promise { + const results: FernDocs.RevalidationResult[] = await Promise.all( + paths.map(async (path): Promise => { + return this.path(path); + }), + ); + return results; + } +} diff --git a/packages/ui/docs-bundle/turbo.json b/packages/ui/docs-bundle/turbo.json index c949dde57d..bb806a740f 100644 --- a/packages/ui/docs-bundle/turbo.json +++ b/packages/ui/docs-bundle/turbo.json @@ -3,6 +3,7 @@ "extends": ["//"], "tasks": { "docs:build": { + "outputs": [".next/**", "!.next/cache/**"], "dependsOn": ["^build", "^compile"] }, "docs:dev": { diff --git a/packages/ui/docs-bundle/vercel.json b/packages/ui/docs-bundle/vercel.json index 90508b873d..84416ca1b5 100644 --- a/packages/ui/docs-bundle/vercel.json +++ b/packages/ui/docs-bundle/vercel.json @@ -3,5 +3,8 @@ "installCommand": "pnpm install", "buildCommand": "pnpm turbo docs:build", "devCommand": "pnpm turbo docs:dev", + "git": { + "deploymentEnabled": false + }, "ignoreCommand": "exit 0" } diff --git a/packages/ui/fern-dashboard/turbo.json b/packages/ui/fern-dashboard/turbo.json new file mode 100644 index 0000000000..eadd743ddd --- /dev/null +++ b/packages/ui/fern-dashboard/turbo.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"], + "dependsOn": ["^build", "^compile"] + } + } +} diff --git a/packages/ui/fern-dashboard/vercel.json b/packages/ui/fern-dashboard/vercel.json index 79417e9bb3..7d3a2223d4 100644 --- a/packages/ui/fern-dashboard/vercel.json +++ b/packages/ui/fern-dashboard/vercel.json @@ -2,5 +2,11 @@ "$schema": "https://openapi.vercel.sh/vercel.json", "framework": "vite", "rewrites": [{ "source": "/(.*)", "destination": "/" }], + "installCommand": "pnpm --filter=@fern-ui/dashboard install", + "buildCommand": "pnpm turbo build", + "devCommand": "pnpm turbo dev", + "git": { + "deploymentEnabled": false + }, "ignoreCommand": "exit 0" } diff --git a/packages/ui/fontawesome-cdn/turbo.json b/packages/ui/fontawesome-cdn/turbo.json new file mode 100644 index 0000000000..ae6e65302a --- /dev/null +++ b/packages/ui/fontawesome-cdn/turbo.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": [".next/**", "!.next/cache/**"], + "dependsOn": ["^build", "^compile"] + } + } +} diff --git a/packages/ui/fontawesome-cdn/vercel.json b/packages/ui/fontawesome-cdn/vercel.json index 6cb7aedc7c..3f2fe84f57 100644 --- a/packages/ui/fontawesome-cdn/vercel.json +++ b/packages/ui/fontawesome-cdn/vercel.json @@ -1,4 +1,10 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", + "git": { + "deploymentEnabled": false + }, + "installCommand": "pnpm --filter=@fern-ui/fontawesome-cdn install", + "buildCommand": "pnpm turbo build", + "devCommand": "pnpm turbo dev", "ignoreCommand": "exit 0" } diff --git a/playwright/test-runner.ts b/playwright/test-runner.ts index b008a0ba69..9c3e342262 100644 --- a/playwright/test-runner.ts +++ b/playwright/test-runner.ts @@ -13,7 +13,8 @@ export function getPlaywrightTestUrls(type: string): string[] { const testUrls: string[] = fs .readFileSync("domains.txt", "utf-8") .split(/\r?\n/) - .filter((domain) => testInclusions.has(domain)); + .filter((domain) => testInclusions.has(domain)) + .map((domain) => `https://${domain}`); if (testUrls.length === 0) { throw new Error("No URLs found in domains.txt"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f98aba0c01..203a064661 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,7 +96,7 @@ importers: version: 3.15.1(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3))) eslint-plugin-vitest: specifier: ^0.3.26 - version: 0.3.26(@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3)(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 0.3.26(@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3)(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) execa: specifier: ^5.1.1 version: 5.1.1 @@ -168,7 +168,7 @@ importers: version: 5.1.0(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3))(typescript@5.4.3) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) dependenciesMeta: jsonc-parser@2.2.1: unplugged: true @@ -232,7 +232,7 @@ importers: version: 18.19.33 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) clis/generator-cli: devDependencies: @@ -289,7 +289,47 @@ importers: version: 4.9.5 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + yargs: + specifier: ^17.4.1 + version: 17.7.2 + + clis/vercel-scripts: + dependencies: + '@fern-fern/fern-docs-sdk': + specifier: 0.0.5 + version: 0.0.5 + '@fern-fern/vercel': + specifier: 0.0.4607 + version: 0.0.4607 + ts-essentials: + specifier: ^10.0.1 + version: 10.0.1(typescript@4.9.5) + devDependencies: + '@fern-platform/configs': + specifier: workspace:* + version: link:../../packages/configs + '@types/node': + specifier: ^18.7.18 + version: 18.19.33 + '@types/yargs': + specifier: ^17.0.32 + version: 17.0.32 + depcheck: + specifier: ^1.4.3 + version: 1.4.7 + eslint: + specifier: ^8.56.0 + version: 8.57.0 + organize-imports-cli: + specifier: ^0.10.0 + version: 0.10.0 + prettier: + specifier: ^3.3.2 + version: 3.3.2 + typescript: + specifier: 4.9.5 + version: 4.9.5 yargs: specifier: ^17.4.1 version: 17.7.2 @@ -378,7 +418,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/fdr-utils: dependencies: @@ -415,7 +455,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/github: dependencies: @@ -446,7 +486,7 @@ importers: version: 3.3.2 simple-git: specifier: ^3.24.0 - version: 3.24.0(supports-color@8.1.1) + version: 3.24.0 stylelint: specifier: ^16.1.0 version: 16.5.0(typescript@5.4.3) @@ -455,7 +495,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/loadable: dependencies: @@ -489,7 +529,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/next-seo: dependencies: @@ -532,7 +572,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/react/common-components: dependencies: @@ -575,7 +615,7 @@ importers: version: 5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/react/fonts: dependencies: @@ -615,7 +655,7 @@ importers: version: 5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/react/react-commons: dependencies: @@ -673,7 +713,7 @@ importers: version: 5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/react/react-query-utils: dependencies: @@ -719,7 +759,7 @@ importers: version: 5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/commons/search-utils: dependencies: @@ -768,7 +808,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/configs: {} @@ -806,7 +846,7 @@ importers: version: 3.15.1(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3))) eslint-plugin-vitest: specifier: ^0.3.26 - version: 0.3.26(@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3)(vitest@2.1.1(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 0.3.26(@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3)(vitest@2.1.1(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) devDependencies: prettier: specifier: ^3.3.2 @@ -886,7 +926,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/healthchecks: dependencies: @@ -947,7 +987,7 @@ importers: version: 4.9.5 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) yargs: specifier: ^17.4.1 version: 17.7.2 @@ -999,7 +1039,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/template-resolver: dependencies: @@ -1243,7 +1283,7 @@ importers: version: 7.8.0(algoliasearch@4.24.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-medium-image-zoom: specifier: ^5.1.10 - version: 5.2.0(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 5.2.0(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) react-virtuoso: specifier: ^4.7.7 version: 4.7.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1313,7 +1353,7 @@ importers: version: 8.1.0-alpha.6(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/addon-interactions': specifier: 8.1.0-alpha.6 - version: 8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@storybook/addon-links': specifier: 8.1.0-alpha.6 version: 8.1.0-alpha.6(react@18.3.1) @@ -1334,7 +1374,7 @@ importers: version: 8.1.0-alpha.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.3) '@storybook/test': specifier: 8.1.0-alpha.6 - version: 8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@tailwindcss/forms': specifier: ^0.5.7 version: 0.5.7(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3))) @@ -1343,7 +1383,7 @@ importers: version: 0.5.13(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3))) '@testing-library/jest-dom': specifier: ^6.4.2 - version: 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@testing-library/react': specifier: ^14.2.1 version: 14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1448,7 +1488,7 @@ importers: version: 5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/ui/chatbot: dependencies: @@ -1542,7 +1582,7 @@ importers: version: 5.4.3 vitest: specifier: ^2.1.1 - version: 2.1.1(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 2.1.1(@edge-runtime/vm@3.2.0)(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/ui/components: dependencies: @@ -1624,7 +1664,7 @@ importers: version: 8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(prettier@3.3.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/addon-interactions': specifier: ^8.1.1 - version: 8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@storybook/addon-links': specifier: ^8.1.1 version: 8.1.1(react@18.3.1) @@ -1648,7 +1688,7 @@ importers: version: 8.1.1(prettier@3.3.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.17.2)(typescript@5.4.3)(vite@5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@storybook/test': specifier: ^8.1.1 - version: 8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@tailwindcss/forms': specifier: ^0.5.7 version: 0.5.7(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3))) @@ -1657,7 +1697,7 @@ importers: version: 0.5.13(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3))) '@testing-library/jest-dom': specifier: ^6.4.2 - version: 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + version: 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@testing-library/react': specifier: ^14.2.1 version: 14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1726,7 +1766,7 @@ importers: version: 5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/ui/docs-bundle: dependencies: @@ -1745,6 +1785,9 @@ importers: '@fern-api/venus-api-sdk': specifier: ^0.1.0 version: 0.1.1 + '@fern-fern/fern-docs-sdk': + specifier: 0.0.5 + version: 0.0.5 '@fern-ui/chatbot': specifier: workspace:* version: link:../chatbot @@ -1899,7 +1942,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/ui/fern-dashboard: dependencies: @@ -2148,7 +2191,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages/ui/local-preview-bundle: dependencies: @@ -2276,7 +2319,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) ws: specifier: ^8.16.0 version: 8.17.0 @@ -2301,9 +2344,9 @@ importers: '@fern-api/venus-api-sdk': specifier: ^0.6.2-1-gbbd1ac5 version: 0.6.2-1-gbbd1ac5 - '@fern-fern/revalidation-sdk': - specifier: 0.0.9 - version: 0.0.9 + '@fern-fern/fern-docs-sdk': + specifier: 0.0.5 + version: 0.0.5 '@prisma/client': specifier: 5.13.0 version: 5.13.0(prisma@5.13.0) @@ -2463,7 +2506,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) servers/fdr-deploy: dependencies: @@ -2515,7 +2558,7 @@ importers: version: 5.4.3 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) servers/fern-bot: dependencies: @@ -2569,7 +2612,7 @@ importers: version: 3.21.0(serverless@3.38.0) simple-git: specifier: ^3.24.0 - version: 3.24.0(supports-color@8.1.1) + version: 3.24.0 tmp-promise: specifier: ^3.0.3 version: 3.0.3 @@ -2621,7 +2664,7 @@ importers: version: 4.9.5 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + version: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) packages: @@ -3723,6 +3766,14 @@ packages: '@dual-bundle/import-meta-resolve@4.1.0': resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==} + '@edge-runtime/primitives@4.1.0': + resolution: {integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==} + engines: {node: '>=16'} + + '@edge-runtime/vm@3.2.0': + resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} + engines: {node: '>=16'} + '@emnapi/runtime@1.1.1': resolution: {integrity: sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==} @@ -3949,11 +4000,14 @@ packages: '@fern-fern/fern-cloud-sdk@0.0.257': resolution: {integrity: sha512-JwlE6Qk84VAGrTYR1EMgwQlneKDIQ3ZRlHTu3O/L56bLtIbhdMTzLSGqSxf4/KFXfhH/RukhRuTsM4/DRmgJ/A==} + '@fern-fern/fern-docs-sdk@0.0.5': + resolution: {integrity: sha512-x6kfRboXiG1J3fpON2lXtD9qCub2UXvNuscvCsIY3ACYvSvLHHlHvc84rIDv+ZlP/anlEvtTyfNh72Hmi0nffw==} + '@fern-fern/generators-sdk@0.109.0-21be2e5be': resolution: {integrity: sha512-McFSFWNqfZBC7E0gUQfhj45tTl8Eu+6bahqD6FHf0fQm+TXI/oQf8mhPJw/TVqzAGxP/2kOzSYNqkDg1DwkIFQ==} - '@fern-fern/revalidation-sdk@0.0.9': - resolution: {integrity: sha512-iZhm1odMdgWqlJi8B6lUPkza8YFfPz6iAhLfTkTnG4cpgPLER4sdQ8tlO+sUcGJF07Z3KkCRKmD6k54wNhv6Gg==} + '@fern-fern/vercel@0.0.4607': + resolution: {integrity: sha512-cATse2PXRMTe6O+FHdVtN+/33UwT3YHjIanfnEwlJQCG/3r7a3/CQct5LvIa+81UOwsCEPReBfJjMt//9jlatw==} '@fern-fern/vercel@0.0.7': resolution: {integrity: sha512-k3ioiJifaX1WYJzbEHHdtg/aIfBVTz5eMOtHoyvl7+qY/IdmYczDtpa+NSYzdfxOM5uAAoVxTGWCvAlagVv+Fg==} @@ -17015,7 +17069,7 @@ snapshots: '@babel/traverse': 7.24.5 '@babel/types': 7.24.5 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -17814,7 +17868,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.5 '@babel/parser': 7.24.5 '@babel/types': 7.24.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -17829,7 +17883,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.5 '@babel/parser': 7.24.5 '@babel/types': 7.24.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -17917,6 +17971,14 @@ snapshots: '@dual-bundle/import-meta-resolve@4.1.0': {} + '@edge-runtime/primitives@4.1.0': + optional: true + + '@edge-runtime/vm@3.2.0': + dependencies: + '@edge-runtime/primitives': 4.1.0 + optional: true + '@emnapi/runtime@1.1.1': dependencies: tslib: 2.6.2 @@ -17943,7 +18005,7 @@ snapshots: '@esbuild-plugins/node-resolve@0.2.2(esbuild@0.20.2)': dependencies: '@types/resolve': 1.20.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) esbuild: 0.20.2 escape-string-regexp: 4.0.0 resolve: 1.22.8 @@ -18029,7 +18091,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -18122,6 +18184,17 @@ snapshots: transitivePeerDependencies: - debug + '@fern-fern/fern-docs-sdk@0.0.5': + dependencies: + form-data: 4.0.0 + formdata-node: 6.0.3 + node-fetch: 2.7.0 + qs: 6.11.2 + readable-stream: 4.5.2 + url-join: 4.0.1 + transitivePeerDependencies: + - encoding + '@fern-fern/generators-sdk@0.109.0-21be2e5be': dependencies: form-data: 4.0.0 @@ -18133,11 +18206,14 @@ snapshots: transitivePeerDependencies: - encoding - '@fern-fern/revalidation-sdk@0.0.9': + '@fern-fern/vercel@0.0.4607': dependencies: form-data: 4.0.0 + formdata-node: 6.0.3 + js-base64: 3.7.2 node-fetch: 2.7.0 qs: 6.11.2 + readable-stream: 4.5.2 url-join: 4.0.1 transitivePeerDependencies: - encoding @@ -18257,7 +18333,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -18690,6 +18766,12 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.3.4(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + '@kwsites/file-exists@1.1.1(supports-color@8.1.1)': dependencies: debug: 4.3.4(supports-color@8.1.1) @@ -20255,7 +20337,7 @@ snapshots: '@sentry/cli@1.77.3': dependencies: - https-proxy-agent: 5.0.1(supports-color@8.1.1) + https-proxy-agent: 5.0.1 mkdirp: 0.5.6 node-fetch: 2.7.0 progress: 2.0.3 @@ -20267,7 +20349,7 @@ snapshots: '@sentry/cli@2.31.2': dependencies: - https-proxy-agent: 5.0.1(supports-color@8.1.1) + https-proxy-agent: 5.0.1 node-fetch: 2.7.0 progress: 2.0.3 proxy-from-env: 1.1.0 @@ -21030,11 +21112,11 @@ snapshots: dependencies: '@storybook/global': 5.0.0 - '@storybook/addon-interactions@8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': + '@storybook/addon-interactions@8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': dependencies: '@storybook/global': 5.0.0 '@storybook/instrumenter': 8.1.0-alpha.6 - '@storybook/test': 8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + '@storybook/test': 8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@storybook/types': 8.1.0-alpha.6 polished: 4.3.1 ts-dedent: 2.2.0 @@ -21045,11 +21127,11 @@ snapshots: - jest - vitest - '@storybook/addon-interactions@8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': + '@storybook/addon-interactions@8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': dependencies: '@storybook/global': 5.0.0 '@storybook/instrumenter': 8.1.1 - '@storybook/test': 8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + '@storybook/test': 8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@storybook/types': 8.1.1 polished: 4.3.1 ts-dedent: 2.2.0 @@ -22231,14 +22313,14 @@ snapshots: - prettier - supports-color - '@storybook/test@8.0.10(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': + '@storybook/test@8.0.10(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': dependencies: '@storybook/client-logger': 8.0.10 '@storybook/core-events': 8.0.10 '@storybook/instrumenter': 8.0.10 '@storybook/preview-api': 8.0.10 '@testing-library/dom': 9.3.4 - '@testing-library/jest-dom': 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + '@testing-library/jest-dom': 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@testing-library/user-event': 14.5.2(@testing-library/dom@9.3.4) '@vitest/expect': 1.3.1 '@vitest/spy': 1.6.0 @@ -22250,14 +22332,14 @@ snapshots: - jest - vitest - '@storybook/test@8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': + '@storybook/test@8.1.0-alpha.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': dependencies: '@storybook/client-logger': 8.1.0-alpha.6 '@storybook/core-events': 8.1.0-alpha.6 '@storybook/instrumenter': 8.1.0-alpha.6 '@storybook/preview-api': 8.1.0-alpha.6 '@testing-library/dom': 9.3.4 - '@testing-library/jest-dom': 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + '@testing-library/jest-dom': 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@testing-library/user-event': 14.5.2(@testing-library/dom@9.3.4) '@vitest/expect': 1.3.1 '@vitest/spy': 1.6.0 @@ -22270,14 +22352,14 @@ snapshots: - jest - vitest - '@storybook/test@8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': + '@storybook/test@8.1.1(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': dependencies: '@storybook/client-logger': 8.1.1 '@storybook/core-events': 8.1.1 '@storybook/instrumenter': 8.1.1 '@storybook/preview-api': 8.1.1 '@testing-library/dom': 9.3.4 - '@testing-library/jest-dom': 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + '@testing-library/jest-dom': 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) '@testing-library/user-event': 14.5.2(@testing-library/dom@9.3.4) '@vitest/expect': 1.3.1 '@vitest/spy': 1.6.0 @@ -22516,7 +22598,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': + '@testing-library/jest-dom@6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0))': dependencies: '@adobe/css-tools': 4.3.3 '@babel/runtime': 7.24.5 @@ -22530,7 +22612,7 @@ snapshots: '@jest/globals': 29.7.0 '@types/jest': 29.5.12 jest: 29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)) - vitest: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + vitest: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) '@testing-library/react@14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -22951,7 +23033,7 @@ snapshots: '@typescript-eslint/type-utils': 7.3.1(eslint@8.57.0)(typescript@5.4.3) '@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.4.3) '@typescript-eslint/visitor-keys': 7.3.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -22969,7 +23051,7 @@ snapshots: '@typescript-eslint/types': 7.17.0 '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.4.3) '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 optionalDependencies: typescript: 5.4.3 @@ -22982,7 +23064,7 @@ snapshots: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.3) '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 optionalDependencies: typescript: 5.4.3 @@ -22995,7 +23077,7 @@ snapshots: '@typescript-eslint/types': 7.3.1 '@typescript-eslint/typescript-estree': 7.3.1(typescript@5.4.3) '@typescript-eslint/visitor-keys': 7.3.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 optionalDependencies: typescript: 5.4.3 @@ -23036,7 +23118,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.4.3) '@typescript-eslint/utils': 7.17.0(eslint@8.57.0)(typescript@5.4.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.3) optionalDependencies: @@ -23048,7 +23130,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.3.1(typescript@5.4.3) '@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.4.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.3) optionalDependencies: @@ -23101,7 +23183,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.17.0 '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 @@ -23131,7 +23213,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.3.1 '@typescript-eslint/visitor-keys': 7.3.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -26167,7 +26249,7 @@ snapshots: callsite: 1.0.0 camelcase: 6.3.0 cosmiconfig: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) deps-regex: 0.2.0 findup-sync: 5.0.0 ignore: 5.3.1 @@ -26657,7 +26739,7 @@ snapshots: eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) enhanced-resolve: 5.16.1 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) @@ -26824,24 +26906,24 @@ snapshots: postcss: 8.4.31 tailwindcss: 3.4.3(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)) - eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3)(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)): + eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3)(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)): dependencies: '@typescript-eslint/utils': 7.8.0(eslint@8.57.0)(typescript@5.4.3) eslint: 8.57.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3) - vitest: 1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + vitest: 1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3)(vitest@2.1.1(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)): + eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3)(vitest@2.1.1(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)): dependencies: '@typescript-eslint/utils': 7.8.0(eslint@8.57.0)(typescript@5.4.3) eslint: 8.57.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.3.1(@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.3))(eslint@8.57.0)(typescript@5.4.3) - vitest: 2.1.1(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) + vitest: 2.1.1(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) transitivePeerDependencies: - supports-color - typescript @@ -26871,7 +26953,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -28158,7 +28240,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -28169,6 +28251,13 @@ snapshots: https-browserify@1.0.0: {} + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + https-proxy-agent@5.0.1(supports-color@8.1.1): dependencies: agent-base: 6.0.2(supports-color@8.1.1) @@ -28179,7 +28268,7 @@ snapshots: https-proxy-agent@7.0.4: dependencies: agent-base: 7.1.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -28337,7 +28426,7 @@ snapshots: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -29280,7 +29369,7 @@ snapshots: dependencies: chalk: 5.3.0 commander: 11.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) execa: 7.2.0 lilconfig: 2.1.0 listr2: 6.6.1 @@ -30215,7 +30304,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.1 @@ -31660,9 +31749,9 @@ snapshots: transitivePeerDependencies: - supports-color - react-medium-image-zoom@5.2.0(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)): + react-medium-image-zoom@5.2.0(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)): dependencies: - '@storybook/test': 8.0.10(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) + '@storybook/test': 8.0.10(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@18.19.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.33)(typescript@5.4.3)))(vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -32619,6 +32708,14 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + simple-git@3.24.0: + dependencies: + '@kwsites/file-exists': 1.1.1 + '@kwsites/promise-deferred': 1.1.1 + debug: 4.3.4(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + simple-git@3.24.0(supports-color@8.1.1): dependencies: '@kwsites/file-exists': 1.1.1(supports-color@8.1.1) @@ -33056,7 +33153,7 @@ snapshots: cosmiconfig: 9.0.0(typescript@5.4.3) css-functions-list: 3.2.2 css-tree: 2.3.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) fast-glob: 3.3.2 fastest-levenshtein: 1.0.16 file-entry-cache: 8.0.0 @@ -33094,7 +33191,7 @@ snapshots: stylus@0.62.0: dependencies: '@adobe/css-tools': 4.3.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) glob: 7.2.3 sax: 1.3.0 source-map: 0.7.4 @@ -33496,6 +33593,10 @@ snapshots: ts-easing@0.2.0: {} + ts-essentials@10.0.1(typescript@4.9.5): + optionalDependencies: + typescript: 4.9.5 + ts-essentials@10.0.1(typescript@5.4.3): optionalDependencies: typescript: 5.4.3 @@ -33641,7 +33742,7 @@ snapshots: bundle-require: 4.1.0(esbuild@0.20.2) cac: 6.7.14 chokidar: 3.6.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) esbuild: 0.20.2 execa: 5.1.1 globby: 11.1.0 @@ -34158,7 +34259,7 @@ snapshots: vite-node@1.6.0(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) pathe: 1.1.2 picocolors: 1.0.0 vite: 5.2.11(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) @@ -34175,7 +34276,7 @@ snapshots: vite-node@1.6.0(@types/node@20.12.12)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) pathe: 1.1.2 picocolors: 1.0.0 vite: 5.2.11(@types/node@20.12.12)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) @@ -34275,7 +34376,7 @@ snapshots: stylus: 0.62.0 terser: 5.31.0 - vitest@1.6.0(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): + vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -34284,7 +34385,7 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.4.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.10 @@ -34298,6 +34399,7 @@ snapshots: vite-node: 1.6.0(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) why-is-node-running: 2.2.2 optionalDependencies: + '@edge-runtime/vm': 3.2.0 '@types/node': 18.19.33 jsdom: 24.0.0 transitivePeerDependencies: @@ -34309,7 +34411,7 @@ snapshots: - supports-color - terser - vitest@1.6.0(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): + vitest@1.6.0(@edge-runtime/vm@3.2.0)(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -34318,7 +34420,7 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.4.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.10 @@ -34332,6 +34434,7 @@ snapshots: vite-node: 1.6.0(@types/node@20.12.12)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) why-is-node-running: 2.2.2 optionalDependencies: + '@edge-runtime/vm': 3.2.0 '@types/node': 20.12.12 jsdom: 24.0.0 transitivePeerDependencies: @@ -34343,7 +34446,7 @@ snapshots: - supports-color - terser - vitest@2.1.1(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): + vitest@2.1.1(@edge-runtime/vm@3.2.0)(@types/node@18.19.33)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): dependencies: '@vitest/expect': 2.1.1 '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.3.5(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) @@ -34365,6 +34468,7 @@ snapshots: vite-node: 2.1.1(@types/node@18.19.33)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) why-is-node-running: 2.3.0 optionalDependencies: + '@edge-runtime/vm': 3.2.0 '@types/node': 18.19.33 jsdom: 24.0.0 transitivePeerDependencies: @@ -34378,7 +34482,7 @@ snapshots: - terser optional: true - vitest@2.1.1(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): + vitest@2.1.1(@edge-runtime/vm@3.2.0)(@types/node@20.12.12)(jsdom@24.0.0)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0): dependencies: '@vitest/expect': 2.1.1 '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.3.5(@types/node@20.12.12)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0)) @@ -34400,6 +34504,7 @@ snapshots: vite-node: 2.1.1(@types/node@20.12.12)(less@4.2.0)(sass@1.77.0)(stylus@0.62.0)(terser@5.31.0) why-is-node-running: 2.3.0 optionalDependencies: + '@edge-runtime/vm': 3.2.0 '@types/node': 20.12.12 jsdom: 24.0.0 transitivePeerDependencies: diff --git a/scripts/fetch-domains.js b/scripts/fetch-domains.js deleted file mode 100644 index 47c6677a7d..0000000000 --- a/scripts/fetch-domains.js +++ /dev/null @@ -1,83 +0,0 @@ -const fs = require("fs"); - -if (!process.env.VERCEL_TOKEN) { - console.error("VERCEL_TOKEN is required"); - process.exit(1); -} - -if (!process.env.VERCEL_ORG_ID) { - console.error("VERCEL_ORG_ID is required"); - process.exit(1); -} - -const BASE_URL = "https://api.vercel.com"; - -const PROJECTS = ["app.buildwithfern.com", "app.ferndocs.com", "app-slash.ferndocs.com"]; -const DENY_LIST = [...PROJECTS, "fdr-ete-test.buildwithfern.com"]; - -const ADDITIONAL_DOMAINS = [ - "buildwithfern.com", // buildwithfern.com/learn - "octo.ai", // octo.ai/docs/ -]; - -async function fetchDomainsPage(project, since) { - let uri = `${BASE_URL}/v9/projects/${project}/domains?limit=50&teamId=${process.env.VERCEL_ORG_ID}&withGitRepoInfo=false&production=true&redirects=false&order=ASC`; - uri = since ? `${uri}&since=${since + 1}` : uri; - - const res = await fetch(uri, { - headers: { Authorization: "Bearer " + process.env.VERCEL_TOKEN }, - }); - return res.json(); -} - -async function fetchDomains(project) { - let domains = []; - let next; - - do { - const body = await fetchDomainsPage(project, next); - - domains = domains.concat(body.domains); - next = body.pagination.next; - } while (next); - - return domains; -} - -async function main() { - let domains = new Set(ADDITIONAL_DOMAINS); - - for (const project of PROJECTS) { - const projectDomains = await fetchDomains(project); - - projectDomains.forEach((domain) => { - if (!DENY_LIST.includes(domain.name) && !domain.name.endsWith("vercel.app")) { - domains.add(domain.name); - } - }); - } - - domains = Array.from(domains).sort(); - - /** - * Write the domains to a file - */ - fs.writeFileSync("domains.txt", domains.join("\n")); - - /** - * Write the preview markdown to a file, if DEPLOYMENT_URL is set - */ - if (process.env.PR_PREVIEW && process.env.PR_PREVIEW !== "false") { - if (!process.env.DEPLOYMENT_URL) { - console.error("DEPLOYMENT_URL is required for PR preview"); - process.exit(1); - } - - fs.writeFileSync( - "preview.txt", - `## PR Preview\n\n${domains.map((d) => `- [ ] [${d}](${process.env.DEPLOYMENT_URL}/api/fern-docs/preview?host=${d})`).join("\n")}`, - ); - } -} - -main(); diff --git a/scripts/revalidate-all.js b/scripts/revalidate-all.js deleted file mode 100644 index a68873fd9a..0000000000 --- a/scripts/revalidate-all.js +++ /dev/null @@ -1,71 +0,0 @@ -const fs = require("fs"); - -if (!process.env.VERCEL_TOKEN) { - console.error("VERCEL_TOKEN is required"); - process.exit(1); -} - -if (!process.env.VERCEL_ORG_ID) { - console.error("VERCEL_ORG_ID is required"); - process.exit(1); -} - -const BASE_URL = "https://api.vercel.com"; - -const PROJECT = "app.buildwithfern.com"; -const DENY_LIST = [PROJECT, "fdr-ete-test.buildwithfern.com"]; - -async function fetchDomainsPage(since) { - let uri = `${BASE_URL}/v9/projects/${PROJECT}/domains?limit=50&teamId=${process.env.VERCEL_ORG_ID}&withGitRepoInfo=false&production=true&redirects=false&order=ASC`; - uri = since ? `${params}&since=${since + 1}` : uri; - - const res = await fetch(uri, { - headers: { Authorization: "Bearer " + process.env.VERCEL_TOKEN }, - }); - return res.json(); -} - -async function fetchDomains() { - let domains = []; - let next; - - do { - const body = await fetchDomainsPage(next); - - domains = domains.concat(body.domains); - next = body.pagination.next; - } while (next); - - return domains; -} - -async function main() { - let domains = new Set(); - - const projectDomains = await fetchDomains(); - - projectDomains.forEach((domain) => { - if (!DENY_LIST.includes(domain.name) && !domain.name.endsWith("vercel.app")) { - domains.add(domain.name); - } - }); - - domains = Array.from(domains).sort(); - - for (const domain of domains) { - const start = Date.now(); - const res = await fetch(`https://${domain}/api/fern-docs/revalidate-all/v3`); - const duration = Date.now() - start; - const body = await res.json(); - console.log( - `${res.status === 200 ? "✅" : "❌"} | ${domain} | Response: ${res.status} | Success = ${body.successfulRevalidations.length} | Failed = ${body.failedRevalidations.length} | Duration = ${duration}ms`, - ); - } - - /** - * Write the domains to a file - */ - fs.writeFileSync("domains.txt", domains.join("\n")); -} - -main(); diff --git a/servers/fdr/package.json b/servers/fdr/package.json index 8ac81700c4..66f9798ca2 100644 --- a/servers/fdr/package.json +++ b/servers/fdr/package.json @@ -16,7 +16,7 @@ "@fern-api/github": "workspace:*", "@fern-api/template-resolver": "workspace:*", "@fern-api/venus-api-sdk": "^0.6.2-1-gbbd1ac5", - "@fern-fern/revalidation-sdk": "0.0.9", + "@fern-fern/fern-docs-sdk": "0.0.5", "@prisma/client": "5.13.0", "@sentry/cli": "^2.31.0", "@sentry/node": "^7.112.2", diff --git a/servers/fdr/src/__test__/local/revalidate.test.ts b/servers/fdr/src/__test__/local/revalidate.test.ts index 069b825eb6..48b21cc8dc 100644 --- a/servers/fdr/src/__test__/local/revalidate.test.ts +++ b/servers/fdr/src/__test__/local/revalidate.test.ts @@ -10,7 +10,7 @@ it.skip("revalidates a custom docs domain", async () => { expect(revalidationResult.revalidationFailed).toEqual(false); - expect(revalidationResult.response?.failedRevalidations.length).toEqual(0); + expect(revalidationResult.failed.length).toEqual(0); - expect(revalidationResult.response?.successfulRevalidations.length).toBeGreaterThan(0); + expect(revalidationResult.successful.length).toBeGreaterThan(0); }); diff --git a/servers/fdr/src/__test__/mock.ts b/servers/fdr/src/__test__/mock.ts index d2dedf139b..1c579843b1 100644 --- a/servers/fdr/src/__test__/mock.ts +++ b/servers/fdr/src/__test__/mock.ts @@ -108,10 +108,8 @@ class MockSlackService implements SlackService { class MockRevalidatorService implements RevalidatorService { async revalidate(_params: { baseUrl: ParsedBaseUrl }): Promise { return { - response: { - successfulRevalidations: [], - failedRevalidations: [], - }, + successful: [], + failed: [], revalidationFailed: false, }; } diff --git a/servers/fdr/src/controllers/docs/v2/getDocsWriteV2Service.ts b/servers/fdr/src/controllers/docs/v2/getDocsWriteV2Service.ts index b015fdf50c..343c5c9ab1 100644 --- a/servers/fdr/src/controllers/docs/v2/getDocsWriteV2Service.ts +++ b/servers/fdr/src/controllers/docs/v2/getDocsWriteV2Service.ts @@ -168,24 +168,11 @@ export function getDocsWriteV2Service(app: FdrApplication): DocsV2WriteService { */ const urls = [docsRegistrationInfo.fernUrl, ...docsRegistrationInfo.customUrls]; - // const stagingUrl = createStagingUrl(docsRegistrationInfo.fernUrl); - // if (stagingUrl != null) { - // // revalidation needs to occur separately for staging - // urls.push(stagingUrl); - // } - - // revalidate all custom urls await Promise.all( urls.map(async (baseUrl) => { const results = await app.services.revalidator.revalidate({ baseUrl, app }); - if ( - results.response != null && - results.response.failedRevalidations.length === 0 && - !results.revalidationFailed - ) { - app.logger.info( - `Successfully revalidated ${results.response.successfulRevalidations.length} paths.`, - ); + if (results.failed.length === 0 && !results.revalidationFailed) { + app.logger.info(`Successfully revalidated ${results.successful.length} paths.`); } else { await app.services.slack.notifyFailedToRevalidatePaths({ domain: baseUrl.getFullUrl(), diff --git a/servers/fdr/src/services/revalidator/RevalidatorService.ts b/servers/fdr/src/services/revalidator/RevalidatorService.ts index f6f37328a8..0a834eb932 100644 --- a/servers/fdr/src/services/revalidator/RevalidatorService.ts +++ b/servers/fdr/src/services/revalidator/RevalidatorService.ts @@ -1,11 +1,12 @@ -import { FernRevalidation, FernRevalidationClient } from "@fern-fern/revalidation-sdk"; +import { FernDocs, FernDocsClient } from "@fern-fern/fern-docs-sdk"; import axios, { type AxiosInstance } from "axios"; import * as AxiosLogger from "axios-logger"; import { FdrApplication } from "../../app"; import { ParsedBaseUrl } from "../../util/ParsedBaseUrl"; export type RevalidatedPathsResponse = { - response?: FernRevalidation.RevalidateAllV2Response; + successful: FernDocs.SuccessfulRevalidation[]; + failed: FernDocs.FailedRevalidation[]; revalidationFailed: boolean; }; @@ -46,27 +47,34 @@ export class RevalidatorServiceImpl implements RevalidatorService { }): Promise { let revalidationFailed = false; try { - const client = new FernRevalidationClient({ + const client = new FernDocsClient({ environment: baseUrl.toURL().toString(), }); app?.logger.log("Revalidating paths at", baseUrl.toURL().toString()); - const response = await client.revalidateAllV3({ - host: baseUrl.hostname, - basePath: baseUrl.path != null ? baseUrl.path : "", - xFernHost: baseUrl.hostname, - }); + const page = await client.revalidation.revalidateAllV4(); + + const successful: FernDocs.SuccessfulRevalidation[] = []; + const failed: FernDocs.FailedRevalidation[] = []; + + for await (const result of page) { + if (!result.success) { + failed.push(result); + app?.logger.error(`Revalidation failed for ${result.url}`, result.error); + } else { + successful.push(result); + } + } + return { - response, + failed, + successful, revalidationFailed: false, }; } catch (e) { app?.logger.error("Failed to revalidate paths", e); revalidationFailed = true; console.log(e); - return { - response: undefined, - revalidationFailed: true, - }; + return { failed: [], successful: [], revalidationFailed: true }; } } } diff --git a/servers/fdr/src/services/slack/SlackService.ts b/servers/fdr/src/services/slack/SlackService.ts index 5b87ec424d..602f54dceb 100644 --- a/servers/fdr/src/services/slack/SlackService.ts +++ b/servers/fdr/src/services/slack/SlackService.ts @@ -70,8 +70,8 @@ export class SlackServiceImpl implements SlackService { async notifyFailedToRevalidatePaths(request: FailedToRevalidatePathsNotification): Promise { try { - const failedRevalidations = request.paths.response?.failedRevalidations; - if (failedRevalidations != null && failedRevalidations.length > 0) { + const failedRevalidations = request.paths.failed; + if (request.paths.failed.length > 0) { const { ts } = await this.client.chat.postMessage({ channel: "#engineering-notifs", text: `:rotating_light: \`${request.domain}\` encountered ${failedRevalidations.length} revalidation failurs. }`, diff --git a/turbo.json b/turbo.json index 17333eb93a..fe28dbcdb4 100644 --- a/turbo.json +++ b/turbo.json @@ -2,7 +2,7 @@ "$schema": "https://turbo.build/schema.json", "tasks": { "build": { - "outputs": ["dist/**", ".next/**", "!.next/cache/**"], + "outputs": ["dist/**"], "dependsOn": ["^build", "^compile", "codegen"] }, "codegen": {