diff --git a/.github/workflows/ci-dynamic-snippets.yml b/.github/workflows/ci-dynamic-snippets.yml index e29a4a9db34e..b452ff75c7b7 100644 --- a/.github/workflows/ci-dynamic-snippets.yml +++ b/.github/workflows/ci-dynamic-snippets.yml @@ -18,6 +18,7 @@ permissions: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: test-typescript: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89da09d5b6f3..9b975a7cf36a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ concurrency: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: compile: diff --git a/.github/workflows/definitions-validation.yml b/.github/workflows/definitions-validation.yml index c3a262c42e58..a332309b7a2d 100644 --- a/.github/workflows/definitions-validation.yml +++ b/.github/workflows/definitions-validation.yml @@ -10,6 +10,7 @@ on: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: run: diff --git a/.github/workflows/fix-lint.yml b/.github/workflows/fix-lint.yml index 488e00c90217..de84c6f94bf4 100644 --- a/.github/workflows/fix-lint.yml +++ b/.github/workflows/fix-lint.yml @@ -13,6 +13,7 @@ concurrency: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: fix-lint: diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 7405cb105f47..542c30fb16e8 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -26,6 +26,7 @@ permissions: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: detect-release-bump: diff --git a/.github/workflows/publish-generator-cli.yml b/.github/workflows/publish-generator-cli.yml index 21011622546d..1d32ac95d291 100644 --- a/.github/workflows/publish-generator-cli.yml +++ b/.github/workflows/publish-generator-cli.yml @@ -11,6 +11,7 @@ env: PACKAGE_NAME: "@fern-api/generator-cli" TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 FERN_TOKEN: ${{ secrets.FERN_TOKEN }} GITHUB_TOKEN: ${{ secrets.FERN_GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.FERN_NPM_TOKEN }} diff --git a/.github/workflows/publish-generator-migrations.yml b/.github/workflows/publish-generator-migrations.yml index df6f164ed01f..1221ea2bfb10 100644 --- a/.github/workflows/publish-generator-migrations.yml +++ b/.github/workflows/publish-generator-migrations.yml @@ -18,6 +18,7 @@ env: PACKAGE_NAME: "@fern-api/generator-migrations" TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 permissions: id-token: write # Required for OIDC diff --git a/.github/workflows/sdk-ete-tests.yml b/.github/workflows/sdk-ete-tests.yml index 096286743fc8..596aeb317913 100644 --- a/.github/workflows/sdk-ete-tests.yml +++ b/.github/workflows/sdk-ete-tests.yml @@ -146,7 +146,8 @@ on: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: "buildwithfern" + TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 permissions: contents: write diff --git a/.github/workflows/security-scanning-and-remediation.yml b/.github/workflows/security-scanning-and-remediation.yml index 8d918afec5ce..c8f51bf5b4ed 100644 --- a/.github/workflows/security-scanning-and-remediation.yml +++ b/.github/workflows/security-scanning-and-remediation.yml @@ -33,6 +33,7 @@ on: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: create-dependabot-prs: diff --git a/.github/workflows/seed.yml b/.github/workflows/seed.yml index 19cc1f2b4751..7bdd6a6815be 100644 --- a/.github/workflows/seed.yml +++ b/.github/workflows/seed.yml @@ -22,6 +22,7 @@ concurrency: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: setup: diff --git a/.github/workflows/test-definitions.yml b/.github/workflows/test-definitions.yml index d3e8fd821763..013e0b1bcefb 100644 --- a/.github/workflows/test-definitions.yml +++ b/.github/workflows/test-definitions.yml @@ -24,6 +24,7 @@ concurrency: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: run: diff --git a/.github/workflows/test-remote-vs-local-generation-parity.yml b/.github/workflows/test-remote-vs-local-generation-parity.yml index b52564db3a31..cfdf206d9b38 100644 --- a/.github/workflows/test-remote-vs-local-generation-parity.yml +++ b/.github/workflows/test-remote-vs-local-generation-parity.yml @@ -34,6 +34,7 @@ concurrency: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: # Determine which generator to test based on the triggering workflow diff --git a/.github/workflows/update-seed.yml b/.github/workflows/update-seed.yml index 825f49d142f9..76e253c15e2b 100644 --- a/.github/workflows/update-seed.yml +++ b/.github/workflows/update-seed.yml @@ -53,6 +53,7 @@ env: DOCKER_BUILDKIT: 1 TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: setup: diff --git a/.github/workflows/validate-changelog.yml b/.github/workflows/validate-changelog.yml index 71dfca4f7a5b..5d6b814a9728 100644 --- a/.github/workflows/validate-changelog.yml +++ b/.github/workflows/validate-changelog.yml @@ -25,6 +25,7 @@ concurrency: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: "buildwithfern" + TURBO_REMOTE_CACHE_TIMEOUT: 60 jobs: validate-changelogs: diff --git a/generators/python/src/fern_python/cli/abstract_generator.py b/generators/python/src/fern_python/cli/abstract_generator.py index 7cc27866f3e8..b6d38afd1900 100644 --- a/generators/python/src/fern_python/cli/abstract_generator.py +++ b/generators/python/src/fern_python/cli/abstract_generator.py @@ -371,8 +371,8 @@ def _get_github_workflow_legacy( """ if write_unit_tests: workflow_yaml += """ - - name: Install Fern - run: npm install -g fern-api + - name: Install Fern CLI + uses: fern-api/setup-fern-cli@v1 - name: Test run: fern test --command "poetry run pytest -rP -n auto ." """ @@ -458,8 +458,8 @@ def _get_github_workflow( """ if write_unit_tests: workflow_yaml += """ - - name: Install Fern - run: npm install -g fern-api + - name: Install Fern CLI + uses: fern-api/setup-fern-cli@v1 - name: Test run: fern test --command "poetry run pytest -rP -n auto ." """ diff --git a/package.json b/package.json index 65ffcbbb796b..cae659e2014a 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "test:debug": "turbo run test:debug --filter=!@fern-api/ete-tests", "test:update": "turbo run test:update --filter=!@fern-api/ete-tests", "test:update:dockerless": "turbo run test:update --filter=!@fern-api/ete-tests --filter=!@fern-api/docker-utils", - "test:ete": "pnpm fern-dev:build && pnpm fern-v2-dev:build && pnpm seed:build && pnpm --filter @fern-api/ete-tests test", - "test:ete:update": "pnpm fern-dev:build && pnpm fern-v2-dev:build && pnpm seed:build && pnpm --filter @fern-api/ete-tests test -- -u", + "test:ete": "turbo run dist:cli:dev --filter=@fern-api/cli --filter=@fern-api/cli-v2 && pnpm seed:build && pnpm --filter @fern-api/ete-tests test", + "test:ete:update": "turbo run dist:cli:dev --filter=@fern-api/cli --filter=@fern-api/cli-v2 && pnpm seed:build && pnpm --filter @fern-api/ete-tests test -- -u", "test:ete:v2": "pnpm fern-v2-dev:build && pnpm --filter @fern-api/ete-tests test -- v2", "test:ete:v2:update": "pnpm fern-v2-dev:build && pnpm --filter @fern-api/ete-tests test -- -u v2", "lint:biome": "biome lint --error-on-warnings", diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/__test__/convertSecurityScheme.test.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/__test__/convertSecurityScheme.test.ts index 2a8306d5b85b..1b98d7ec923e 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/__test__/convertSecurityScheme.test.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/__test__/convertSecurityScheme.test.ts @@ -59,12 +59,10 @@ describe("convertSecurityScheme", () => { const result = convertSecurityScheme(securitySchemeRef, source, mockTaskContext, context); // Verify the result - expect(result).toBeDefined(); - expect(result?.type).toBe("bearer"); - if (result?.type === "bearer") { - expect(result.tokenVariableName).toBeUndefined(); - expect(result.tokenEnvVar).toBeUndefined(); - } + expect.assert(result != null); + expect.assert(result.type === "bearer"); + expect(result.tokenVariableName).toBeUndefined(); + expect(result.tokenEnvVar).toBeUndefined(); }); it("should throw an error when resolving reference without context", () => { @@ -86,11 +84,9 @@ describe("convertSecurityScheme", () => { const result = convertSecurityScheme(securityScheme, source, mockTaskContext); - expect(result).toBeDefined(); - expect(result?.type).toBe("bearer"); - if (result?.type === "bearer") { - expect(result.tokenVariableName).toBeUndefined(); - expect(result.tokenEnvVar).toBeUndefined(); - } + expect.assert(result != null); + expect.assert(result.type === "bearer"); + expect(result.tokenVariableName).toBeUndefined(); + expect(result.tokenEnvVar).toBeUndefined(); }); }); diff --git a/packages/cli/configuration-loader/src/docs-yml/__test__/navigationUtils.test.ts b/packages/cli/configuration-loader/src/docs-yml/__test__/navigationUtils.test.ts index 830060fd21b1..e4f54e92dc93 100644 --- a/packages/cli/configuration-loader/src/docs-yml/__test__/navigationUtils.test.ts +++ b/packages/cli/configuration-loader/src/docs-yml/__test__/navigationUtils.test.ts @@ -183,14 +183,13 @@ describe("buildNavigationForDirectory", () => { slug: "advanced" }); const firstItem = result[0]; - if (firstItem && firstItem.type === "section") { - expect(firstItem.contents).toHaveLength(1); - expect(firstItem.contents[0]).toMatchObject({ - type: "page", - title: "Authentication", - slug: "authentication" - }); - } + expect.assert(firstItem?.type === "section"); + expect(firstItem.contents).toHaveLength(1); + expect(firstItem.contents[0]).toMatchObject({ + type: "page", + title: "Authentication", + slug: "authentication" + }); expect(result[1]).toMatchObject({ type: "page", title: "Getting Started", @@ -278,23 +277,21 @@ describe("buildNavigationForDirectory", () => { slug: "guides" }); const firstItem = result[0]; - if (firstItem && firstItem.type === "section") { - expect(firstItem.contents).toHaveLength(1); - expect(firstItem.contents[0]).toMatchObject({ - type: "section", - title: "Advanced", - slug: "advanced" - }); - const secondItem = firstItem.contents[0]; - if (secondItem && secondItem.type === "section") { - expect(secondItem.contents).toHaveLength(1); - expect(secondItem.contents[0]).toMatchObject({ - type: "page", - title: "Authentication", - slug: "authentication" - }); - } - } + expect.assert(firstItem?.type === "section"); + expect(firstItem.contents).toHaveLength(1); + expect(firstItem.contents[0]).toMatchObject({ + type: "section", + title: "Advanced", + slug: "advanced" + }); + const secondItem = firstItem.contents[0]; + expect.assert(secondItem?.type === "section"); + expect(secondItem.contents).toHaveLength(1); + expect(secondItem.contents[0]).toMatchObject({ + type: "page", + title: "Authentication", + slug: "authentication" + }); }); }); @@ -1097,14 +1094,13 @@ describe("buildNavigationForDirectory with index.mdx as section overview", () => slug: "guides", overviewAbsolutePath: "/test/guides/index.mdx" }); - if (section && section.type === "section") { - expect(section.contents).toHaveLength(1); - expect(section.contents[0]).toMatchObject({ - type: "page", - title: "Getting Started", - slug: "getting-started" - }); - } + expect.assert(section?.type === "section"); + expect(section.contents).toHaveLength(1); + expect(section.contents[0]).toMatchObject({ + type: "page", + title: "Getting Started", + slug: "getting-started" + }); }); it("should use index.md as section overview for subdirectories", async () => { @@ -1153,13 +1149,12 @@ describe("buildNavigationForDirectory with index.mdx as section overview", () => slug: "guides", overviewAbsolutePath: "/test/guides/index.md" }); - if (section && section.type === "section") { - expect(section.contents).toHaveLength(1); - expect(section.contents[0]).toMatchObject({ - type: "page", - title: "Authentication" - }); - } + expect.assert(section?.type === "section"); + expect(section.contents).toHaveLength(1); + expect(section.contents[0]).toMatchObject({ + type: "page", + title: "Authentication" + }); }); it("should exclude index.mdx from section contents", async () => { @@ -1208,13 +1203,12 @@ describe("buildNavigationForDirectory with index.mdx as section overview", () => expect(result).toHaveLength(1); const section = result[0]; - if (section && section.type === "section") { - expect(section.contents).toHaveLength(2); - const titles = section.contents.map((item) => (item.type === "page" ? item.title : "")); - expect(titles).not.toContain("Index"); - expect(titles).toContain("Endpoints"); - expect(titles).toContain("Errors"); - } + expect.assert(section?.type === "section"); + expect(section.contents).toHaveLength(2); + const titles = section.contents.map((item) => (item.type === "page" ? item.title : "")); + expect(titles).not.toContain("Index"); + expect(titles).toContain("Endpoints"); + expect(titles).toContain("Errors"); }); it("should handle nested subdirectories with index.mdx", async () => { @@ -1277,22 +1271,20 @@ describe("buildNavigationForDirectory with index.mdx as section overview", () => title: "Guides", overviewAbsolutePath: "/test/guides/index.mdx" }); - if (guidesSection && guidesSection.type === "section") { - expect(guidesSection.contents).toHaveLength(1); - const advancedSection = guidesSection.contents[0]; - expect(advancedSection).toMatchObject({ - type: "section", - title: "Advanced", - overviewAbsolutePath: "/test/guides/advanced/index.mdx" - }); - if (advancedSection && advancedSection.type === "section") { - expect(advancedSection.contents).toHaveLength(1); - expect(advancedSection.contents[0]).toMatchObject({ - type: "page", - title: "Auth" - }); - } - } + expect.assert(guidesSection?.type === "section"); + expect(guidesSection.contents).toHaveLength(1); + const advancedSection = guidesSection.contents[0]; + expect(advancedSection).toMatchObject({ + type: "section", + title: "Advanced", + overviewAbsolutePath: "/test/guides/advanced/index.mdx" + }); + expect.assert(advancedSection?.type === "section"); + expect(advancedSection.contents).toHaveLength(1); + expect(advancedSection.contents[0]).toMatchObject({ + type: "page", + title: "Auth" + }); }); it("should handle case-insensitive index file names", async () => { @@ -1339,13 +1331,12 @@ describe("buildNavigationForDirectory with index.mdx as section overview", () => type: "section", overviewAbsolutePath: "/test/guides/INDEX.MDX" }); - if (section && section.type === "section") { - expect(section.contents).toHaveLength(1); - expect(section.contents[0]).toMatchObject({ - type: "page", - title: "Page" - }); - } + expect.assert(section?.type === "section"); + expect(section.contents).toHaveLength(1); + expect(section.contents[0]).toMatchObject({ + type: "page", + title: "Page" + }); }); it("should not set overviewAbsolutePath when no index file exists", async () => { @@ -1387,9 +1378,8 @@ describe("buildNavigationForDirectory with index.mdx as section overview", () => title: "Guides", overviewAbsolutePath: undefined }); - if (section && section.type === "section") { - expect(section.contents).toHaveLength(1); - } + expect.assert(section?.type === "section"); + expect(section.contents).toHaveLength(1); }); }); diff --git a/packages/cli/configuration-loader/src/generators-yml/__test__/convertGeneratorsConfiguration.test.ts b/packages/cli/configuration-loader/src/generators-yml/__test__/convertGeneratorsConfiguration.test.ts index 8c7ce6bb5532..c70472ecbc3e 100644 --- a/packages/cli/configuration-loader/src/generators-yml/__test__/convertGeneratorsConfiguration.test.ts +++ b/packages/cli/configuration-loader/src/generators-yml/__test__/convertGeneratorsConfiguration.test.ts @@ -1,9 +1,7 @@ -/* eslint-disable jest/no-conditional-expect */ - import { AbsoluteFilePath } from "@fern-api/fs-utils"; import { Logger } from "@fern-api/logger"; import { createMockTaskContext } from "@fern-api/task-context"; -import { vi } from "vitest"; +import { expect, vi } from "vitest"; import { convertGeneratorsConfiguration } from "../convertGeneratorsConfiguration.js"; @@ -178,16 +176,10 @@ describe("convertGeneratorsConfiguration", () => { }); const output = converted.groups[0]?.generators[0]?.outputMode; - expect(output?.type).toEqual("githubV2"); - if (output?.type === "githubV2") { - const publishInfo = output.githubV2.publishInfo; - expect(publishInfo?.type).toEqual("maven"); - if (publishInfo?.type === "maven") { - expect(publishInfo.registryUrl).toEqual( - "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - ); - } - } + expect.assert(output?.type === "githubV2"); + const publishInfo = output.githubV2.publishInfo; + expect.assert(publishInfo?.type === "maven"); + expect(publishInfo.registryUrl).toEqual("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"); }); it("License Metadata", async () => { @@ -218,10 +210,8 @@ describe("convertGeneratorsConfiguration", () => { context }); const output = converted.groups[0]?.generators[0]?.outputMode; - expect(output?.type).toEqual("githubV2"); - if (output?.type === "githubV2") { - expect(output.githubV2.license?.type === "basic" && output.githubV2.license.id === "MIT").toEqual(true); - } + expect.assert(output?.type === "githubV2"); + expect(output.githubV2.license?.type === "basic" && output.githubV2.license.id === "MIT").toEqual(true); }); it("Reviewers", async () => { @@ -260,14 +250,13 @@ describe("convertGeneratorsConfiguration", () => { context }); const output = converted.groups[0]?.generators[0]?.outputMode; - expect(output?.type).toEqual("githubV2"); - if (output?.type === "githubV2" && output.githubV2.type === "pullRequest") { - expect(output.githubV2.reviewers != null).toBeTruthy(); - expect(output.githubV2.reviewers?.length).toEqual(3); - - const reviewerNames = output.githubV2.reviewers?.map((reviewer) => reviewer.name); - expect(reviewerNames).toEqual(["fern-eng", "armando", "deep"]); - } + expect.assert(output?.type === "githubV2"); + expect.assert(output.githubV2.type === "pullRequest"); + expect(output.githubV2.reviewers != null).toBeTruthy(); + expect(output.githubV2.reviewers?.length).toEqual(3); + + const reviewerNames = output.githubV2.reviewers?.map((reviewer) => reviewer.name); + expect(reviewerNames).toEqual(["fern-eng", "armando", "deep"]); }); it("Output Metadata", async () => { @@ -307,14 +296,12 @@ describe("convertGeneratorsConfiguration", () => { context }); const output = converted.groups[0]?.generators[0]?.outputMode; - expect(output?.type).toEqual("githubV2"); - if (output?.type === "githubV2") { - expect( - output.githubV2.publishInfo?.type === "pypi" && - output.githubV2.publishInfo.pypiMetadata?.documentationLink === "https://test.com" && - output.githubV2.publishInfo.pypiMetadata?.description === "test that's low level" - ).toEqual(true); - } + expect.assert(output?.type === "githubV2"); + expect( + output.githubV2.publishInfo?.type === "pypi" && + output.githubV2.publishInfo.pypiMetadata?.documentationLink === "https://test.com" && + output.githubV2.publishInfo.pypiMetadata?.description === "test that's low level" + ).toEqual(true); }); it("logs deprecation warnings for deprecated generators yml configuration", async () => { @@ -504,14 +491,12 @@ describe("convertGeneratorsConfiguration", () => { }); // Verify both specs inherit api-level settings - expect(converted.api?.type).toBe("singleNamespace"); - if (converted.api?.type === "singleNamespace") { - expect(converted.api.definitions).toHaveLength(2); - expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); - expect(converted.api.definitions[0]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); - expect(converted.api.definitions[1]?.settings?.shouldUseTitleAsName).toBe(true); - expect(converted.api.definitions[1]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); - } + expect.assert(converted.api?.type === "singleNamespace"); + expect(converted.api.definitions).toHaveLength(2); + expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); + expect(converted.api.definitions[0]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); + expect(converted.api.definitions[1]?.settings?.shouldUseTitleAsName).toBe(true); + expect(converted.api.definitions[1]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); }); it("spec settings override api-level settings", async () => { @@ -538,11 +523,9 @@ describe("convertGeneratorsConfiguration", () => { }); // Verify spec setting overrides api-level, but other api-level settings are preserved - expect(converted.api?.type).toBe("singleNamespace"); - if (converted.api?.type === "singleNamespace") { - expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(false); - expect(converted.api.definitions[0]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); - } + expect.assert(converted.api?.type === "singleNamespace"); + expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(false); + expect(converted.api.definitions[0]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); }); it("partial overrides preserve other api-level settings", async () => { @@ -571,12 +554,10 @@ describe("convertGeneratorsConfiguration", () => { }); // Verify only specified setting is overridden, others preserved from api-level - expect(converted.api?.type).toBe("singleNamespace"); - if (converted.api?.type === "singleNamespace") { - expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(false); - expect(converted.api.definitions[0]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); - expect(converted.api.definitions[0]?.settings?.coerceEnumsToLiterals).toBe(true); - } + expect.assert(converted.api?.type === "singleNamespace"); + expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(false); + expect(converted.api.definitions[0]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); + expect(converted.api.definitions[0]?.settings?.coerceEnumsToLiterals).toBe(true); }); it("empty spec settings preserve api-level settings", async () => { @@ -600,10 +581,8 @@ describe("convertGeneratorsConfiguration", () => { }); // Verify empty spec settings don't erase api-level settings - expect(converted.api?.type).toBe("singleNamespace"); - if (converted.api?.type === "singleNamespace") { - expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); - } + expect.assert(converted.api?.type === "singleNamespace"); + expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); }); it("multiple specs with different overrides", async () => { @@ -640,22 +619,20 @@ describe("convertGeneratorsConfiguration", () => { }); // Verify each spec has correct merged settings - expect(converted.api?.type).toBe("singleNamespace"); - if (converted.api?.type === "singleNamespace") { - expect(converted.api.definitions).toHaveLength(3); - - // Spec 1: overrides title-as-schema-name - expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(false); - expect(converted.api.definitions[0]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); - - // Spec 2: inherits all api-level settings - expect(converted.api.definitions[1]?.settings?.shouldUseTitleAsName).toBe(true); - expect(converted.api.definitions[1]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); - - // Spec 3: overrides idiomatic-request-names - expect(converted.api.definitions[2]?.settings?.shouldUseTitleAsName).toBe(true); - expect(converted.api.definitions[2]?.settings?.shouldUseIdiomaticRequestNames).toBe(true); - } + expect.assert(converted.api?.type === "singleNamespace"); + expect(converted.api.definitions).toHaveLength(3); + + // Spec 1: overrides title-as-schema-name + expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(false); + expect(converted.api.definitions[0]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); + + // Spec 2: inherits all api-level settings + expect(converted.api.definitions[1]?.settings?.shouldUseTitleAsName).toBe(true); + expect(converted.api.definitions[1]?.settings?.shouldUseIdiomaticRequestNames).toBe(false); + + // Spec 3: overrides idiomatic-request-names + expect(converted.api.definitions[2]?.settings?.shouldUseTitleAsName).toBe(true); + expect(converted.api.definitions[2]?.settings?.shouldUseIdiomaticRequestNames).toBe(true); }); it("OpenAPI-specific settings work alongside base settings", async () => { @@ -682,12 +659,10 @@ describe("convertGeneratorsConfiguration", () => { }); // Verify OpenAPI-specific settings merge with base settings - expect(converted.api?.type).toBe("singleNamespace"); - if (converted.api?.type === "singleNamespace") { - expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); - expect(converted.api.definitions[0]?.settings?.onlyIncludeReferencedSchemas).toBe(true); - expect(converted.api.definitions[0]?.settings?.inlinePathParameters).toBe(true); - } + expect.assert(converted.api?.type === "singleNamespace"); + expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); + expect(converted.api.definitions[0]?.settings?.onlyIncludeReferencedSchemas).toBe(true); + expect(converted.api.definitions[0]?.settings?.inlinePathParameters).toBe(true); }); it("AsyncAPI-specific settings work alongside base settings", async () => { @@ -713,11 +688,9 @@ describe("convertGeneratorsConfiguration", () => { }); // Verify AsyncAPI-specific settings merge with base settings - expect(converted.api?.type).toBe("singleNamespace"); - if (converted.api?.type === "singleNamespace") { - expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); - expect(converted.api.definitions[0]?.settings?.asyncApiMessageNaming).toBe("v2"); - } + expect.assert(converted.api?.type === "singleNamespace"); + expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); + expect(converted.api.definitions[0]?.settings?.asyncApiMessageNaming).toBe("v2"); }); it("no root settings maintains backward compatibility", async () => { @@ -740,10 +713,8 @@ describe("convertGeneratorsConfiguration", () => { }); // Verify existing configs without root settings still work - expect(converted.api?.type).toBe("singleNamespace"); - if (converted.api?.type === "singleNamespace") { - expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); - } + expect.assert(converted.api?.type === "singleNamespace"); + expect(converted.api.definitions[0]?.settings?.shouldUseTitleAsName).toBe(true); }); }); diff --git a/packages/cli/docs-resolver/src/__test__/availability-inheritance.test.ts b/packages/cli/docs-resolver/src/__test__/availability-inheritance.test.ts index 7edc6543fc4d..cc7b6d2a92ad 100644 --- a/packages/cli/docs-resolver/src/__test__/availability-inheritance.test.ts +++ b/packages/cli/docs-resolver/src/__test__/availability-inheritance.test.ts @@ -144,54 +144,49 @@ describe("availability inheritance", () => { expect(getAvailability(stableSection)).toBe("stable"); // Test specific endpoints within Beta Features section - if (betaSection?.type === "apiPackage") { - const userManagement = findNodeByTitle(betaSection.children, "User Management"); - expect(getAvailability(userManagement)).toBe("beta"); // Should inherit from beta section + expect.assert(betaSection?.type === "apiPackage"); + const userManagement = findNodeByTitle(betaSection.children, "User Management"); + expect(getAvailability(userManagement)).toBe("beta"); // Should inherit from beta section - if (userManagement?.type === "apiPackage") { - const getUser = findEndpointByTitle(userManagement.children, "getUser"); - const createUser = findEndpointByTitle(userManagement.children, "createUser"); - const deleteUser = findEndpointByTitle(userManagement.children, "deleteUser"); + expect.assert(userManagement?.type === "apiPackage"); + const getUser = findEndpointByTitle(userManagement.children, "getUser"); + const createUser = findEndpointByTitle(userManagement.children, "createUser"); + const deleteUser = findEndpointByTitle(userManagement.children, "deleteUser"); - // getUser should inherit beta from section - expect(getAvailability(getUser)).toBe("beta"); + // getUser should inherit beta from section + expect(getAvailability(getUser)).toBe("beta"); - // createUser has its own beta, but matches parent so should be beta - expect(getAvailability(createUser)).toBe("beta"); + // createUser has its own beta, but matches parent so should be beta + expect(getAvailability(createUser)).toBe("beta"); - // deleteUser has its own deprecated, should override parent beta - expect(getAvailability(deleteUser)).toBe("deprecated"); - } - } + // deleteUser has its own deprecated, should override parent beta + expect(getAvailability(deleteUser)).toBe("deprecated"); // Test endpoints within Admin package - if (adminPackage?.type === "apiPackage") { - const healthCheck = findEndpointByTitle(adminPackage.children, "healthCheck"); - const reboot = findEndpointByTitle(adminPackage.children, "reboot"); + expect.assert(adminPackage?.type === "apiPackage"); + const healthCheck = findEndpointByTitle(adminPackage.children, "healthCheck"); + const reboot = findEndpointByTitle(adminPackage.children, "reboot"); - // healthCheck should inherit deprecated from admin package - expect(getAvailability(healthCheck)).toBe("deprecated"); + // healthCheck should inherit deprecated from admin package + expect(getAvailability(healthCheck)).toBe("deprecated"); - // reboot has its own alpha, should override parent deprecated - expect(getAvailability(reboot)).toBe("alpha"); - } + // reboot has its own alpha, should override parent deprecated + expect(getAvailability(reboot)).toBe("alpha"); // Test endpoints within Stable section - if (stableSection?.type === "apiPackage") { - const stableUserPackage = findNodeByTitle(stableSection.children, "users"); - expect(getAvailability(stableUserPackage)).toBe("stable"); // Should inherit stable from section + expect.assert(stableSection?.type === "apiPackage"); + const stableUserPackage = findNodeByTitle(stableSection.children, "users"); + expect(getAvailability(stableUserPackage)).toBe("stable"); // Should inherit stable from section - if (stableUserPackage?.type === "apiPackage") { - const stableGetUser = findEndpointByTitle(stableUserPackage.children, "getUser"); - const stableCreateUser = findEndpointByTitle(stableUserPackage.children, "createUser"); + expect.assert(stableUserPackage?.type === "apiPackage"); + const stableGetUser = findEndpointByTitle(stableUserPackage.children, "getUser"); + const stableCreateUser = findEndpointByTitle(stableUserPackage.children, "createUser"); - // getUser should inherit stable from section - expect(getAvailability(stableGetUser)).toBe("stable"); + // getUser should inherit stable from section + expect(getAvailability(stableGetUser)).toBe("stable"); - // createUser has its own beta, should override parent stable - expect(getAvailability(stableCreateUser)).toBe("beta"); - } - } + // createUser has its own beta, should override parent stable + expect(getAvailability(stableCreateUser)).toBe("beta"); // Store the full snapshot for comprehensive testing expect(node).toMatchSnapshot(); diff --git a/packages/cli/docs-resolver/src/__test__/graphql-operation-layout.test.ts b/packages/cli/docs-resolver/src/__test__/graphql-operation-layout.test.ts index 12dc6d2f5772..f494978e9950 100644 --- a/packages/cli/docs-resolver/src/__test__/graphql-operation-layout.test.ts +++ b/packages/cli/docs-resolver/src/__test__/graphql-operation-layout.test.ts @@ -95,40 +95,35 @@ describe("GraphQL Operation Layout", () => { ); expect(userOperationsSection).toBeDefined(); - if (userOperationsSection && userOperationsSection.type === "apiPackage") { - // Should contain getUserProfile, createUser, updateUserProfile operations - expect(userOperationsSection.children).toHaveLength(3); - - const getUserProfileOp = userOperationsSection.children.find( - (child) => child.type === "graphql" && child.title === "Get User Profile" - ); - expect(getUserProfileOp).toBeDefined(); - - const createUserOp = userOperationsSection.children.find( - (child) => child.type === "graphql" && child.title === "Create New User" - ); - expect(createUserOp).toBeDefined(); - - if (createUserOp && createUserOp.type === "graphql") { - // Should use custom slug - expect(createUserOp.slug).toContain("create-user"); - } - } + expect.assert(userOperationsSection?.type === "apiPackage"); + // Should contain getUserProfile, createUser, updateUserProfile operations + expect(userOperationsSection.children).toHaveLength(3); + + const getUserProfileOp = userOperationsSection.children.find( + (child) => child.type === "graphql" && child.title === "Get User Profile" + ); + expect(getUserProfileOp).toBeDefined(); + + const createUserOp = userOperationsSection.children.find( + (child) => child.type === "graphql" && child.title === "Create New User" + ); + expect.assert(createUserOp?.type === "graphql"); + // Should use custom slug + expect(createUserOp.slug).toContain("create-user"); const adminOperationsSection = node.children.find( (child) => child.type === "apiPackage" && child.title === "Admin Operations" ); expect(adminOperationsSection).toBeDefined(); - if (adminOperationsSection && adminOperationsSection.type === "apiPackage") { - // Should contain getSystemInfo operation (resetSystem is hidden) - expect(adminOperationsSection.children.length).toBeGreaterThan(0); + expect.assert(adminOperationsSection?.type === "apiPackage"); + // Should contain getSystemInfo operation (resetSystem is hidden) + expect(adminOperationsSection.children.length).toBeGreaterThan(0); - const getSystemInfoOp = adminOperationsSection.children.find( - (child) => child.type === "graphql" && child.title === "System Information" - ); - expect(getSystemInfoOp).toBeDefined(); - } + const getSystemInfoOp = adminOperationsSection.children.find( + (child) => child.type === "graphql" && child.title === "System Information" + ); + expect(getSystemInfoOp).toBeDefined(); // Check for the subscription operation at root level const userUpdatesOp = node.children.find( @@ -136,9 +131,8 @@ describe("GraphQL Operation Layout", () => { ); expect(userUpdatesOp).toBeDefined(); - if (userUpdatesOp && userUpdatesOp.type === "graphql") { - expect(userUpdatesOp.availability).toBe("beta"); - } + expect.assert(userUpdatesOp?.type === "graphql"); + expect(userUpdatesOp.availability).toBe("beta"); }); it("should handle invalid operation format", async () => { diff --git a/packages/cli/ete-tests/src/tests/add/add.test.ts b/packages/cli/ete-tests/src/tests/add/add.test.ts index e0eb2c3df8ae..6685336f0c37 100644 --- a/packages/cli/ete-tests/src/tests/add/add.test.ts +++ b/packages/cli/ete-tests/src/tests/add/add.test.ts @@ -4,13 +4,11 @@ import { runFernCli } from "../../utils/runFernCli.js"; import { init } from "../init/init.js"; describe("fern add", () => { - it("fern add ", async () => { - const pathOfDirectory = await init(); + it("fern add ", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const add = async (generator: string) => { - await runFernCli(["add", generator], { - cwd: pathOfDirectory - }); + await runFernCli(["add", generator], { cwd: pathOfDirectory, signal }); }; await add("fernapi/fern-java-sdk"); @@ -19,13 +17,11 @@ describe("fern add", () => { expect(await getDirectoryContentsForSnapshot(pathOfDirectory)).not.toBeNull(); }, 60_000); - it("fern add --group sdk", async () => { - const pathOfDirectory = await init(); + it("fern add --group sdk", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const add = async (generator: string, groupName: string) => { - await runFernCli(["add", generator, "--group", groupName], { - cwd: pathOfDirectory - }); + await runFernCli(["add", generator, "--group", groupName], { cwd: pathOfDirectory, signal }); }; await add("fern-typescript", "typescript"); diff --git a/packages/cli/ete-tests/src/tests/api-option/api-option.test.ts b/packages/cli/ete-tests/src/tests/api-option/api-option.test.ts index 311e4b75b346..fdbb704d91c2 100644 --- a/packages/cli/ete-tests/src/tests/api-option/api-option.test.ts +++ b/packages/cli/ete-tests/src/tests/api-option/api-option.test.ts @@ -12,29 +12,32 @@ describe("--api", () => { function testFixture(fixtureName: string) { // eslint-disable-next-line jest/valid-title describe(fixtureName, () => { - it("Fails if API is not specified", async () => { + it("Fails if API is not specified", async ({ signal }) => { const fixturePath = path.join(FIXTURES_DIR, fixtureName); const { stdout, failed } = await runFernCli(["ir", (await tmp.file()).path], { cwd: fixturePath, - reject: false + reject: false, + signal }); expect(failed).toBe(true); expect(stdout).toContain("There are multiple workspaces. You must specify one with --api"); }, 90_000); - it("Succeeds if API is specified", async () => { + it("Succeeds if API is specified", async ({ signal }) => { const fixturePath = path.join(FIXTURES_DIR, fixtureName); const { failed } = await runFernCli(["--api", "api1", "ir", (await tmp.file()).path], { - cwd: fixturePath + cwd: fixturePath, + signal }); expect(failed).toBe(false); }, 90_000); - it("Fail if API does not exist", async () => { + it("Fail if API does not exist", async ({ signal }) => { const fixturePath = path.join(FIXTURES_DIR, fixtureName); const { failed } = await runFernCli(["--api", "api3", "ir", (await tmp.file()).path], { cwd: fixturePath, - reject: false + reject: false, + signal }); expect(failed).toBe(true); }, 90_000); diff --git a/packages/cli/ete-tests/src/tests/broken-links/broken-links.test.ts b/packages/cli/ete-tests/src/tests/broken-links/broken-links.test.ts index 58173c545314..4533ec51a4c8 100644 --- a/packages/cli/ete-tests/src/tests/broken-links/broken-links.test.ts +++ b/packages/cli/ete-tests/src/tests/broken-links/broken-links.test.ts @@ -5,10 +5,11 @@ import { runFernCli } from "../../utils/runFernCli.js"; const fixturesDir = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures")); describe("fern docs broken-links", () => { - it("simple broken links", async () => { + it("simple broken links", async ({ signal }) => { const { stdout } = await runFernCli(["docs", "broken-links"], { cwd: join(fixturesDir, RelativeFilePath.of("simple")), - reject: false + reject: false, + signal }); expect( stripAnsi(stdout) @@ -20,20 +21,22 @@ describe("fern docs broken-links", () => { ).toMatchSnapshot(); }, 20_000); - it("external link with docs domain in query param should not be flagged", async () => { + it("external link with docs domain in query param should not be flagged", async ({ signal }) => { const { stdout } = await runFernCli(["docs", "broken-links"], { cwd: join(fixturesDir, RelativeFilePath.of("external-link-query-param")), - reject: false + reject: false, + signal }); // This fixture should have no broken links - external URLs with the docs domain // in query parameters (e.g., ?source=docs.example.com) should NOT be flagged expect(stripAnsi(stdout)).toContain("All checks passed"); }, 20_000); - it("links to paths with external redirects should not be flagged as broken", async () => { + it("links to paths with external redirects should not be flagged as broken", async ({ signal }) => { const { stdout } = await runFernCli(["docs", "broken-links"], { cwd: join(fixturesDir, RelativeFilePath.of("external-redirect")), - reject: false + reject: false, + signal }); // This fixture tests that links to internal paths (e.g., /ui) that have // redirects configured to external URLs (e.g., https://auth-platform.example.com) @@ -41,18 +44,20 @@ describe("fern docs broken-links", () => { expect(stripAnsi(stdout)).toContain("All checks passed"); }, 20_000); - it("links with Markdown snippet references should be resolved", async () => { + it("links with Markdown snippet references should be resolved", async ({ signal }) => { const { stdout } = await runFernCli(["docs", "broken-links"], { cwd: join(fixturesDir, RelativeFilePath.of("snippet-link")), - reject: false + reject: false, + signal }); expect(stripAnsi(stdout)).toContain("All checks passed"); }, 20_000); - it("broken links in sections with path property should be detected", async () => { + it("broken links in sections with path property should be detected", async ({ signal }) => { const { stdout } = await runFernCli(["docs", "broken-links"], { cwd: join(fixturesDir, RelativeFilePath.of("section-with-path")), - reject: false + reject: false, + signal }); // This fixture tests that markdown files referenced by sections with a path // property are validated for broken links (not just pages) diff --git a/packages/cli/ete-tests/src/tests/dependencies/dependencies.test.ts b/packages/cli/ete-tests/src/tests/dependencies/dependencies.test.ts index 29d7562eeacf..bc4d898a3f7e 100644 --- a/packages/cli/ete-tests/src/tests/dependencies/dependencies.test.ts +++ b/packages/cli/ete-tests/src/tests/dependencies/dependencies.test.ts @@ -7,52 +7,58 @@ import { generateIrAsString } from "../ir/generateIrAsString.js"; const FIXTURES_DIR = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures")); describe("dependencies", () => { - it("correctly incorporates dependencies", async () => { + it("correctly incorporates dependencies", async ({ signal }) => { const ir = await generateIrAsString({ fixturePath: join(FIXTURES_DIR, RelativeFilePath.of("simple")), - apiName: "dependent" + apiName: "dependent", + signal }); expect(ir).toMatchSnapshot(); }, 90_000); - it("file dependencies", async () => { + it("file dependencies", async ({ signal }) => { const ir = await generateIrAsString({ fixturePath: join(FIXTURES_DIR, RelativeFilePath.of("file-dependencies")), - apiName: "api-docs" + apiName: "api-docs", + signal }); expect(ir.length).toMatchSnapshot(); }, 90_000); - it("fails when dependency does not exist", async () => { + it("fails when dependency does not exist", async ({ signal }) => { const { stdout } = await runFernCli(["check"], { cwd: join(FIXTURES_DIR, RelativeFilePath.of("non-existent-dependency")), - reject: false + reject: false, + signal }); expect(stdout).toContain("Failed to load dependency: @fern/non-existent-dependency"); }, 90_000); - it("fails when dependency is not listed in dependencies.yml", async () => { + it("fails when dependency is not listed in dependencies.yml", async ({ signal }) => { const { stdout } = await runFernCli(["check"], { cwd: join(FIXTURES_DIR, RelativeFilePath.of("unlisted-dependency")), - reject: false + reject: false, + signal }); expect(stripAnsi(stdout).trim()).toEqual( "[api]: Dependency is not listed in dependencies.yml: @fern/unlisted-dependency" ); }, 90_000); - it("fails when export package contains definitions", async () => { + it("fails when export package contains definitions", async ({ signal }) => { const { stdout } = await runFernCli(["check"], { cwd: join(FIXTURES_DIR, RelativeFilePath.of("other-definitions-specified")), - reject: false + reject: false, + signal }); expect(stripAnsi(stdout).trim()).toEqual("[api]: Exported package contains API definitions: package1"); }, 90_000); - it("fails when exporting package marker has non-export keys", async () => { + it("fails when exporting package marker has non-export keys", async ({ signal }) => { const { stdout } = await runFernCli(["check"], { cwd: join(FIXTURES_DIR, RelativeFilePath.of("invalid-package-marker")), - reject: false + reject: false, + signal }); expect(stdout).toContain("imported/__package__.yml has an export so it cannot define other keys."); }, 90_000); diff --git a/packages/cli/ete-tests/src/tests/diff/diff.test.ts b/packages/cli/ete-tests/src/tests/diff/diff.test.ts index 3fcc25de4116..be50fee137bd 100644 --- a/packages/cli/ete-tests/src/tests/diff/diff.test.ts +++ b/packages/cli/ete-tests/src/tests/diff/diff.test.ts @@ -16,7 +16,7 @@ const NON_BREAKING_FIXTURES_DIR = join( RelativeFilePath.of("non-breaking") ); -it("breaking", async () => { +it("breaking", async ({ signal }) => { const breakingChangeDirs = await readdir(BREAKING_FIXTURES_DIR, { withFileTypes: true }); for (const dir of breakingChangeDirs) { if (!dir.isDirectory()) { @@ -26,14 +26,15 @@ it("breaking", async () => { } const result = await diff({ fixturePath: AbsoluteFilePath.of(path.join(BREAKING_FIXTURES_DIR, dir.name)), - fromVersion: "1.1.0" + fromVersion: "1.1.0", + signal }); expect(result.stdout).toMatchSnapshot(); expect(result.exitCode).toBe(1); } }, 20_000); -it("non-breaking", async () => { +it("non-breaking", async ({ signal }) => { const nonBreakingChangeDirs = await readdir(NON_BREAKING_FIXTURES_DIR, { withFileTypes: true }); for (const dir of nonBreakingChangeDirs) { if (!dir.isDirectory()) { @@ -43,7 +44,8 @@ it("non-breaking", async () => { } const result = await diff({ fixturePath: AbsoluteFilePath.of(path.join(NON_BREAKING_FIXTURES_DIR, dir.name)), - fromVersion: "1.1.0" + fromVersion: "1.1.0", + signal }); expect(result.stdout).toMatchSnapshot(); expect(result.exitCode).toBe(0); diff --git a/packages/cli/ete-tests/src/tests/diff/diff.ts b/packages/cli/ete-tests/src/tests/diff/diff.ts index cd8fa6a82cf8..986a6e6971eb 100644 --- a/packages/cli/ete-tests/src/tests/diff/diff.ts +++ b/packages/cli/ete-tests/src/tests/diff/diff.ts @@ -11,24 +11,22 @@ export interface DiffResult { export async function diff({ fixturePath, - fromVersion + fromVersion, + signal }: { fixturePath: AbsoluteFilePath; fromVersion?: string; + signal?: AbortSignal; }): Promise { const fromIrFilename = await tmpName(); await rm(fromIrFilename, { force: true, recursive: true }); - await runFernCli(["ir", fromIrFilename], { - cwd: join(fixturePath, RelativeFilePath.of("from")) - }); + await runFernCli(["ir", fromIrFilename], { cwd: join(fixturePath, RelativeFilePath.of("from")), signal }); const toIrFilename = await tmpName(); await rm(toIrFilename, { force: true, recursive: true }); - await runFernCli(["ir", toIrFilename], { - cwd: join(fixturePath, RelativeFilePath.of("to")) - }); + await runFernCli(["ir", toIrFilename], { cwd: join(fixturePath, RelativeFilePath.of("to")), signal }); const command = ["diff", "--from", fromIrFilename, "--to", toIrFilename]; if (fromVersion != null) { @@ -37,7 +35,8 @@ export async function diff({ const result = await runFernCli(command, { cwd: join(fixturePath, RelativeFilePath.of("from")), - reject: false + reject: false, + signal }); return { diff --git a/packages/cli/ete-tests/src/tests/docs-dev/docsDev.test.ts b/packages/cli/ete-tests/src/tests/docs-dev/docsDev.test.ts index e3146a061710..bddcef13c6a9 100644 --- a/packages/cli/ete-tests/src/tests/docs-dev/docsDev.test.ts +++ b/packages/cli/ete-tests/src/tests/docs-dev/docsDev.test.ts @@ -6,21 +6,30 @@ import { captureFernCli, runFernCli } from "../../utils/runFernCli.js"; const fixturesDir = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures")); +// Use high-numbered ports unlikely to conflict with common services +const LEGACY_PORT = "47100"; +const BETA_BACKEND_PORT = "47101"; +const DEFAULT_BACKEND_PORT = "47102"; + describe.sequential("fern docs dev --legacy", () => { - it("basic docs --legacy", async () => { + it("basic docs --legacy", async ({ signal }) => { const check = await runFernCli(["check"], { - cwd: join(fixturesDir, RelativeFilePath.of("simple")) + cwd: join(fixturesDir, RelativeFilePath.of("simple")), + signal }); expect(check.exitCode).toBe(0); - const process = captureFernCli(["docs", "dev", "--legacy"], { - cwd: join(fixturesDir, RelativeFilePath.of("simple")) + const process = captureFernCli(["docs", "dev", "--legacy", "--port", LEGACY_PORT], { + cwd: join(fixturesDir, RelativeFilePath.of("simple")), + signal }); - const response = await waitForServer("http://localhost:3000/v2/registry/docs/load-with-url", { - method: "POST" - }); + const response = await waitForServer( + `http://localhost:${LEGACY_PORT}/v2/registry/docs/load-with-url`, + { method: "POST" }, + { signal } + ); expect(response.body != null).toEqual(true); const responseText = await response.text(); @@ -31,29 +40,32 @@ describe.sequential("fern docs dev --legacy", () => { // biome-ignore lint/suspicious/noExplicitAny: allow explicit any expect(Object.keys(responseBody as any)).toEqual(["baseUrl", "definition", "lightModeEnabled", "orgId"]); - // kill the process const finishProcess = process.kill(); expect(finishProcess).toBeTruthy(); }, 90_000); }); describe.sequential("fern docs dev --beta", () => { - it("basic docs --beta", async () => { + it("basic docs --beta", async ({ signal }) => { const check = await runFernCli(["check"], { cwd: join(fixturesDir, RelativeFilePath.of("simple")), - reject: true + reject: true, + signal }); expect(check.exitCode).toBe(0); - const process = captureFernCli(["docs", "dev", "--beta", "--backend-port", "3001"], { + const process = captureFernCli(["docs", "dev", "--beta", "--backend-port", BETA_BACKEND_PORT], { cwd: join(fixturesDir, RelativeFilePath.of("simple")), - reject: true + reject: true, + signal }); - const response = await waitForServer("http://localhost:3001/v2/registry/docs/load-with-url", { - method: "POST" - }); + const response = await waitForServer( + `http://localhost:${BETA_BACKEND_PORT}/v2/registry/docs/load-with-url`, + { method: "POST" }, + { signal } + ); expect(response.body != null).toEqual(true); const responseText = await response.text(); @@ -70,20 +82,24 @@ describe.sequential("fern docs dev --beta", () => { }); describe.sequential("fern docs dev", () => { - it("basic docs", async () => { + it("basic docs", async ({ signal }) => { const check = await runFernCli(["check"], { - cwd: join(fixturesDir, RelativeFilePath.of("simple")) + cwd: join(fixturesDir, RelativeFilePath.of("simple")), + signal }); expect(check.exitCode).toBe(0); - const process = captureFernCli(["docs", "dev", "--backend-port", "3002"], { - cwd: join(fixturesDir, RelativeFilePath.of("simple")) + const process = captureFernCli(["docs", "dev", "--backend-port", DEFAULT_BACKEND_PORT], { + cwd: join(fixturesDir, RelativeFilePath.of("simple")), + signal }); - const response = await waitForServer("http://localhost:3002/v2/registry/docs/load-with-url", { - method: "POST" - }); + const response = await waitForServer( + `http://localhost:${DEFAULT_BACKEND_PORT}/v2/registry/docs/load-with-url`, + { method: "POST" }, + { signal } + ); expect(response.body != null).toEqual(true); const responseText = await response.text(); @@ -93,22 +109,29 @@ describe.sequential("fern docs dev", () => { expect(typeof responseBody === "object").toEqual(true); // biome-ignore lint/suspicious/noExplicitAny: allow explicit any expect(Object.keys(responseBody as any)).toEqual(["baseUrl", "definition", "lightModeEnabled", "orgId"]); + + const finishProcess = process.kill(); + expect(finishProcess).toBeTruthy(); }, 90_000); }); async function waitForServer( url: string, init: Parameters[1], - { interval = 1_000, timeout = 60_000 }: { interval?: number; timeout?: number } = {} + { interval = 1_000, timeout = 60_000, signal }: { interval?: number; timeout?: number; signal?: AbortSignal } = {} ): Promise>> { const deadline = Date.now() + timeout; while (Date.now() < deadline) { + signal?.throwIfAborted(); try { - return await fetch(url, init); - } catch { + return await fetch(url, { ...init, signal }); + } catch (error) { + if (signal?.aborted) { + throw signal.reason; + } await new Promise((resolve) => setTimeout(resolve, interval)); } } // Final attempt – let it throw if the server is still unreachable - return await fetch(url, init); + return await fetch(url, { ...init, signal }); } diff --git a/packages/cli/ete-tests/src/tests/downgrade/downgrade.test.ts b/packages/cli/ete-tests/src/tests/downgrade/downgrade.test.ts index 386b5b8c9d8c..92d15f2e6381 100644 --- a/packages/cli/ete-tests/src/tests/downgrade/downgrade.test.ts +++ b/packages/cli/ete-tests/src/tests/downgrade/downgrade.test.ts @@ -6,8 +6,8 @@ import { runFernCli } from "../../utils/runFernCli.js"; import { init } from "../init/init.js"; describe("fern downgrade", () => { - it("downgrades CLI version in fern.config.json", async () => { - const directory = await init(); + it("downgrades CLI version in fern.config.json", async ({ signal }) => { + const directory = await init({ signal }); const fernConfigFilepath = join( directory, RelativeFilePath.of(FERN_DIRECTORY), @@ -17,26 +17,22 @@ describe("fern downgrade", () => { const initialConfig = JSON.parse((await readFile(fernConfigFilepath)).toString()); expect(initialConfig.version).toBeDefined(); - await runFernCli(["downgrade", "0.30.0"], { - cwd: directory - }); + await runFernCli(["downgrade", "0.30.0"], { cwd: directory, signal }); const updatedConfig = JSON.parse((await readFile(fernConfigFilepath)).toString()); expect(updatedConfig.version).toBe("0.30.0"); expect(updatedConfig.organization).toBe(initialConfig.organization); }, 60_000); - it("downgrades to a different version", async () => { - const directory = await init(); + it("downgrades to a different version", async ({ signal }) => { + const directory = await init({ signal }); const fernConfigFilepath = join( directory, RelativeFilePath.of(FERN_DIRECTORY), RelativeFilePath.of(PROJECT_CONFIG_FILENAME) ); - await runFernCli(["downgrade", "0.25.5"], { - cwd: directory - }); + await runFernCli(["downgrade", "0.25.5"], { cwd: directory, signal }); const updatedConfig = JSON.parse((await readFile(fernConfigFilepath)).toString()); expect(updatedConfig.version).toBe("0.25.5"); diff --git a/packages/cli/ete-tests/src/tests/dynamic/dynamic.test.ts b/packages/cli/ete-tests/src/tests/dynamic/dynamic.test.ts index 0e12d94548c3..3fa80c15e751 100644 --- a/packages/cli/ete-tests/src/tests/dynamic/dynamic.test.ts +++ b/packages/cli/ete-tests/src/tests/dynamic/dynamic.test.ts @@ -24,13 +24,14 @@ describe("fdr", () => { const { only = false } = fixture; (only ? it.only : it)( `${JSON.stringify(fixture)}`, - async () => { + async ({ signal }) => { const fixturePath = join(FIXTURES_DIR, RelativeFilePath.of(fixture.name)); const dynamicContents = await generateDynamicIrAsString({ fixturePath, language: fixture.language, audiences: fixture.audiences, - version: fixture.version + version: fixture.version, + signal }); // biome-ignore lint/suspicious/noMisplacedAssertion: allow expect(dynamicContents).toMatchSnapshot(); diff --git a/packages/cli/ete-tests/src/tests/dynamic/generateDynamicIrAsString.ts b/packages/cli/ete-tests/src/tests/dynamic/generateDynamicIrAsString.ts index 36cf46e548fd..a40e3a13a47b 100644 --- a/packages/cli/ete-tests/src/tests/dynamic/generateDynamicIrAsString.ts +++ b/packages/cli/ete-tests/src/tests/dynamic/generateDynamicIrAsString.ts @@ -9,13 +9,15 @@ export async function generateDynamicIrAsString({ language, audiences, apiName, - version + version, + signal }: { fixturePath: AbsoluteFilePath; language?: generatorsYml.GenerationLanguage; audiences?: string[]; apiName?: string; version?: string; + signal?: AbortSignal; }): Promise { const dynamicOutputPath = join(fixturePath, RelativeFilePath.of("dynamic.json")); await rm(dynamicOutputPath, { force: true, recursive: true }); @@ -37,9 +39,7 @@ export async function generateDynamicIrAsString({ command.push("--version", version); } - await runFernCli(command, { - cwd: fixturePath - }); + await runFernCli(command, { cwd: fixturePath, signal }); const dynamicContents = await readFile(dynamicOutputPath); return dynamicContents.toString(); diff --git a/packages/cli/ete-tests/src/tests/export/export.test.ts b/packages/cli/ete-tests/src/tests/export/export.test.ts index bf50f6da7e22..72aaa37dafc3 100644 --- a/packages/cli/ete-tests/src/tests/export/export.test.ts +++ b/packages/cli/ete-tests/src/tests/export/export.test.ts @@ -13,13 +13,11 @@ describe("overrides", () => { function itFixture(fixtureName: string) { it(// eslint-disable-next-line jest/valid-title - fixtureName, async () => { + fixtureName, async ({ signal }) => { const fixturePath = path.join(FIXTURES_DIR, fixtureName); for (const filename of ["openapi.yml", "openapi.json"]) { const outputPath = path.join(fixturePath, "output", filename); - await runFernCli(["export", outputPath], { - cwd: fixturePath - }); + await runFernCli(["export", outputPath], { cwd: fixturePath, signal }); expect((await readFile(AbsoluteFilePath.of(outputPath))).toString()).toMatchSnapshot(); } }, 90_000); @@ -27,13 +25,11 @@ function itFixture(fixtureName: string) { function itFixtureWithIndent(fixtureName: string, indent: number) { it(// eslint-disable-next-line jest/valid-title - `${fixtureName} --indent ${indent}`, async () => { + `${fixtureName} --indent ${indent}`, async ({ signal }) => { const fixturePath = path.join(FIXTURES_DIR, fixtureName); for (const filename of ["openapi.yml", "openapi.json"]) { const outputPath = path.join(fixturePath, "output", filename); - await runFernCli(["export", outputPath, "--indent", String(indent)], { - cwd: fixturePath - }); + await runFernCli(["export", outputPath, "--indent", String(indent)], { cwd: fixturePath, signal }); expect((await readFile(AbsoluteFilePath.of(outputPath))).toString()).toMatchSnapshot(); } }, 90_000); diff --git a/packages/cli/ete-tests/src/tests/fdr/fdr.test.ts b/packages/cli/ete-tests/src/tests/fdr/fdr.test.ts index 6c8d292e718e..9270500eae4d 100644 --- a/packages/cli/ete-tests/src/tests/fdr/fdr.test.ts +++ b/packages/cli/ete-tests/src/tests/fdr/fdr.test.ts @@ -27,13 +27,14 @@ describe("fdr", () => { const { only = false } = fixture; (only ? it.only : it)( `${JSON.stringify(fixture)}`, - async () => { + async ({ signal }) => { const fixturePath = join(FIXTURES_DIR, RelativeFilePath.of(fixture.name)); const fdrContents = await generateFdrApiDefinitionAsString({ fixturePath, language: fixture.language, audiences: fixture.audiences, - version: fixture.version + version: fixture.version, + signal }); // biome-ignore lint/suspicious/noMisplacedAssertion: allow expect(fdrContents).toMatchSnapshot(); diff --git a/packages/cli/ete-tests/src/tests/fdr/generateFdrApiDefinitionAsString.ts b/packages/cli/ete-tests/src/tests/fdr/generateFdrApiDefinitionAsString.ts index e79c4e74eb66..a74915802500 100644 --- a/packages/cli/ete-tests/src/tests/fdr/generateFdrApiDefinitionAsString.ts +++ b/packages/cli/ete-tests/src/tests/fdr/generateFdrApiDefinitionAsString.ts @@ -9,13 +9,15 @@ export async function generateFdrApiDefinitionAsString({ language, audiences, apiName, - version + version, + signal }: { fixturePath: AbsoluteFilePath; language?: generatorsYml.GenerationLanguage; audiences?: string[]; apiName?: string; version?: string; + signal?: AbortSignal; }): Promise { const fdrOutputPath = join(fixturePath, RelativeFilePath.of("fdr.json")); await rm(fdrOutputPath, { force: true, recursive: true }); @@ -37,9 +39,7 @@ export async function generateFdrApiDefinitionAsString({ command.push("--version", version); } - await runFernCli(command, { - cwd: fixturePath - }); + await runFernCli(command, { cwd: fixturePath, signal }); const fdrContents = await readFile(fdrOutputPath); return fdrContents.toString(); diff --git a/packages/cli/ete-tests/src/tests/generate/fernignore.test.ts b/packages/cli/ete-tests/src/tests/generate/fernignore.test.ts index 729f05eb1be9..f938801a539e 100644 --- a/packages/cli/ete-tests/src/tests/generate/fernignore.test.ts +++ b/packages/cli/ete-tests/src/tests/generate/fernignore.test.ts @@ -28,11 +28,9 @@ Practice schema-first API design with Fern describe("fern generate --local", () => { // eslint-disable-next-line jest/expect-expect - it.concurrent("Keep files listed in .fernignore from unmodified", async () => { - const pathOfDirectory = await init(); - await runFernCli(["generate", "--local", "--keepDocker"], { - cwd: pathOfDirectory - }); + it.concurrent("Keep files listed in .fernignore from unmodified", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); + await runFernCli(["generate", "--local", "--keepDocker"], { cwd: pathOfDirectory, signal }); // write custom files and override const absolutePathToLocalOutput = join(pathOfDirectory, RelativeFilePath.of("sdks/typescript")); @@ -46,23 +44,19 @@ describe("fern generate --local", () => { const absolutePathToDummyText = join(absolutePathToLocalOutput, RelativeFilePath.of(DUMMY_TXT_FILENAME)); await writeFile(absolutePathToDummyText, DUMMY_TXT_FILECONTENTS); - await runFernCli(["generate", "--local", "--keepDocker"], { - cwd: pathOfDirectory - }); + await runFernCli(["generate", "--local", "--keepDocker"], { cwd: pathOfDirectory, signal }); await expectPathExists(absolutePathToFernignore); await expectPathExists(absolutePathToFernJs); await expectPathDoesNotExist(absolutePathToDummyText); // rerun and make sure no issues if there are no changes - await runFernCli(["generate", "--local", "--keepDocker"], { - cwd: pathOfDirectory - }); + await runFernCli(["generate", "--local", "--keepDocker"], { cwd: pathOfDirectory, signal }); }, 360_000); // eslint-disable-next-line jest/expect-expect - it.concurrent("Prevent initial generation of files listed in .fernignore", async () => { - const pathOfDirectory = await init(); + it.concurrent("Prevent initial generation of files listed in .fernignore", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); // Create output directory with .fernignore BEFORE first generation const absolutePathToLocalOutput = join(pathOfDirectory, RelativeFilePath.of("sdks/typescript")); @@ -73,9 +67,7 @@ describe("fern generate --local", () => { await writeFile(absolutePathToFernignore, FERNIGNORE_PREVENT_INITIAL_GENERATION_FILECONTENTS); // Run first generation - excluded files should NOT be created - await runFernCli(["generate", "--local", "--keepDocker"], { - cwd: pathOfDirectory - }); + await runFernCli(["generate", "--local", "--keepDocker"], { cwd: pathOfDirectory, signal }); // Verify .fernignore still exists await expectPathExists(absolutePathToFernignore); @@ -89,9 +81,7 @@ describe("fern generate --local", () => { await expectPathDoesNotExist(absolutePathToCore); // Run generation again to ensure it's stable - await runFernCli(["generate", "--local", "--keepDocker"], { - cwd: pathOfDirectory - }); + await runFernCli(["generate", "--local", "--keepDocker"], { cwd: pathOfDirectory, signal }); // Files should still not exist await expectPathDoesNotExist(absolutePathToClientTs); diff --git a/packages/cli/ete-tests/src/tests/generate/generate-with-settings.test.ts b/packages/cli/ete-tests/src/tests/generate/generate-with-settings.test.ts index 06e5c68c009d..346f1091becd 100644 --- a/packages/cli/ete-tests/src/tests/generate/generate-with-settings.test.ts +++ b/packages/cli/ete-tests/src/tests/generate/generate-with-settings.test.ts @@ -5,7 +5,7 @@ import tmp from "tmp-promise"; import { runFernCli } from "../../utils/runFernCli.js"; describe("fern generate with settings", () => { - it.concurrent("single api", async ({ expect }) => { + it.concurrent("single api", async ({ expect, signal }) => { const fixturesDir = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures/api-settings")); const tmpDir = await tmp.dir(); @@ -13,25 +13,21 @@ describe("fern generate with settings", () => { await cp(fixturesDir, directory, { recursive: true }); - await runFernCli(["generate", "--local", "--keepDocker"], { - cwd: directory - }); + await runFernCli(["generate", "--local", "--keepDocker"], { cwd: directory, signal }); expect( await getDirectoryContentsForSnapshot(join(directory, RelativeFilePath.of("sdks/python"))) ).toMatchSnapshot(); }, 180_000); - it.concurrent("dependencies-based api", async ({ expect }) => { + it.concurrent("dependencies-based api", async ({ expect, signal }) => { const fixturesDir = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures/api-settings-unioned")); const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); await cp(fixturesDir, directory, { recursive: true }); - await runFernCli(["generate", "--local", "--keepDocker", "--api", "unioned"], { - cwd: directory - }); + await runFernCli(["generate", "--local", "--keepDocker", "--api", "unioned"], { cwd: directory, signal }); expect( await getDirectoryContentsForSnapshot(join(directory, RelativeFilePath.of("sdks/python"))) diff --git a/packages/cli/ete-tests/src/tests/generate/generate.test.ts b/packages/cli/ete-tests/src/tests/generate/generate.test.ts index 5e9fa542b7a9..c716305df171 100644 --- a/packages/cli/ete-tests/src/tests/generate/generate.test.ts +++ b/packages/cli/ete-tests/src/tests/generate/generate.test.ts @@ -2,26 +2,31 @@ import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-a import { exec } from "child_process"; import stripAnsi from "strip-ansi"; -import { runFernCli, runFernCliWithoutAuthToken } from "../../utils/runFernCli.js"; +import { runFernCli } from "../../utils/runFernCli.js"; import { init } from "../init/init.js"; const fixturesDir = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures")); describe("fern generate", () => { - it.concurrent("default api (fern init)", async () => { + it.concurrent("default api (fern init)", async ({ signal }) => { const pathOfDirectory = await init(); await runFernCli(["generate", "--local", "--keepDocker"], { - cwd: pathOfDirectory + cwd: pathOfDirectory, + signal }); expect(await doesPathExist(join(pathOfDirectory, RelativeFilePath.of("sdks/typescript")))).toBe(true); }, 180_000); - it.concurrent("ir contains fdr definitionid", async () => { + it.concurrent("ir contains fdr definitionid", async ({ signal }) => { + if (globalThis.process.env.FERN_ORG_TOKEN_DEV == null) { + throw new Error("FERN_ORG_TOKEN_DEV is not set"); + } const { stdout, stderr } = await runFernCli(["generate", "--log-level", "debug"], { cwd: join(fixturesDir, RelativeFilePath.of("basic")), - reject: false + reject: false, + signal }); console.log("stdout", stdout); @@ -32,31 +37,28 @@ describe("fern generate", () => { throw new Error(`Failed to get path to IR:\n${stdout}`); } - const process = exec( - `./ir-contains-fdr-definition-id.sh ${filepath}`, - { cwd: __dirname }, - (error, stdout, stderr) => { - if (error) { - console.error(`Error: ${error.message}`); - return; - } - if (stderr) { - console.error(`stderr: ${stderr}`); - return; - } - console.log(`stdout: ${stdout}`); + exec(`./ir-contains-fdr-definition-id.sh ${filepath}`, { cwd: __dirname }, (error, stdout, stderr) => { + if (error) { + console.error(`Error: ${error.message}`); + return; } - ); + if (stderr) { + console.error(`stderr: ${stderr}`); + return; + } + console.log(`stdout: ${stdout}`); + }); }, 180_000); // TODO: Re-enable this test if and when it doesn't require the user to be logged in. // It's otherwise flaky on developer machines that haven't logged in with the fern CLI. // // biome-ignore lint/suspicious/noSkippedTests: allow - it.skip("missing docs page", async () => { + it.skip("missing docs page", async ({ signal }) => { const { stdout } = await runFernCli(["generate", "--docs"], { cwd: join(fixturesDir, RelativeFilePath.of("docs-missing-page")), - reject: false + reject: false, + signal }); expect( @@ -66,13 +68,15 @@ describe("fern generate", () => { ).toMatchSnapshot(); }, 180_000); - it.concurrent("generate docs with no auth requires login", async () => { - const { stdout, stderr } = await runFernCliWithoutAuthToken(["generate", "--docs"], { + it.concurrent("generate docs with no auth requires login", async ({ signal }) => { + const { stdout, stderr } = await runFernCli(["generate", "--docs", "--no-prompt"], { cwd: join(fixturesDir, RelativeFilePath.of("docs")), reject: false, env: { FERN_TOKEN: "" - } + }, + includeAuthToken: false, + signal }); const output = stdout + stderr; expect(output).toContain( @@ -80,34 +84,39 @@ describe("fern generate", () => { ); }, 180_000); - it.concurrent("generate docs with auth bypass fails", async () => { - const { stdout } = await runFernCliWithoutAuthToken(["generate", "--docs"], { + it.concurrent("generate docs with auth bypass fails", async ({ signal }) => { + const { stdout } = await runFernCli(["generate", "--docs", "--no-prompt"], { cwd: join(fixturesDir, RelativeFilePath.of("docs")), reject: false, env: { FERN_SELF_HOSTED: "true", FERN_TOKEN: "" - } + }, + includeAuthToken: false, + signal }); expect(stdout).toContain("No token found. Please set the FERN_TOKEN environment variable."); }, 180_000); - it.concurrent("generate docs with auth bypass succeeds", async () => { - const { stdout } = await runFernCliWithoutAuthToken(["generate", "--docs"], { + it.concurrent("generate docs with auth bypass succeeds", async ({ signal }) => { + const { stdout } = await runFernCli(["generate", "--docs", "--no-prompt"], { cwd: join(fixturesDir, RelativeFilePath.of("docs")), reject: false, env: { FERN_SELF_HOSTED: "true", FERN_TOKEN: "dummy" - } + }, + includeAuthToken: false, + signal }); expect(stdout).toContain("ferndevtest.docs.dev.buildwithfern.com Started."); }, 180_000); - it.concurrent("generate docs with no docs.yml file fails", async () => { + it.concurrent("generate docs with no docs.yml file fails", async ({ signal }) => { const { stdout } = await runFernCli(["generate", "--docs"], { cwd: join(fixturesDir, RelativeFilePath.of("basic")), - reject: false + reject: false, + signal }); expect(stdout).toContain("No docs.yml file found. Please make sure your project has one."); }, 180_000); diff --git a/packages/cli/ete-tests/src/tests/generate/output-directory-prompts.test.ts b/packages/cli/ete-tests/src/tests/generate/output-directory-prompts.test.ts index eed36e240d95..ce91ec40516e 100644 --- a/packages/cli/ete-tests/src/tests/generate/output-directory-prompts.test.ts +++ b/packages/cli/ete-tests/src/tests/generate/output-directory-prompts.test.ts @@ -8,12 +8,13 @@ const envWithCI = { }; describe("output directory prompts", () => { - it("doesn't show prompts for CI environment", async () => { - const pathOfDirectory = await init(); + it("doesn't show prompts for CI environment", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const { stdout } = await runFernCli(["generate", "--local", "--keepDocker"], { cwd: pathOfDirectory, - env: envWithCI + env: envWithCI, + signal }); const cleanOutput = stripAnsi(stdout).trim(); diff --git a/packages/cli/ete-tests/src/tests/generators-get/generators-get.test.ts b/packages/cli/ete-tests/src/tests/generators-get/generators-get.test.ts index 279db08a362d..cbc06a97a6cd 100644 --- a/packages/cli/ete-tests/src/tests/generators-get/generators-get.test.ts +++ b/packages/cli/ete-tests/src/tests/generators-get/generators-get.test.ts @@ -6,34 +6,30 @@ import { init } from "../init/init.js"; // Ensure that the generators list command works and the format doesn't change, since fern-bot consumes this describe("fern generator get", () => { - it("fern generator get --version", async () => { - const pathOfDirectory = await init(); + it("fern generator get --version", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const out = await runFernCli( ["generator", "get", "--generator", "fernapi/fern-typescript-sdk", "--group", "local", "--version"], - { - cwd: pathOfDirectory - } + { cwd: pathOfDirectory, signal } ); expect(out.stdout).toMatchSnapshot(); }, 60_000); - it("fern generator get --language", async () => { - const pathOfDirectory = await init(); + it("fern generator get --language", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const out = await runFernCli( ["generator", "get", "--generator", "fernapi/fern-typescript-sdk", "--group", "local", "--language"], - { - cwd: pathOfDirectory - } + { cwd: pathOfDirectory, signal } ); expect(out.stdout).toMatchSnapshot(); }, 60_000); - it("fern generator get to file", async () => { - const pathOfDirectory = await init(); + it("fern generator get to file", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const tmpFile = await tmp.file(); await runFernCli( [ @@ -49,9 +45,7 @@ describe("fern generator get", () => { "-o", tmpFile.path ], - { - cwd: pathOfDirectory - } + { cwd: pathOfDirectory, signal } ); const out = await readFile(tmpFile.path, "utf-8"); diff --git a/packages/cli/ete-tests/src/tests/generators-list/generators-list.test.ts b/packages/cli/ete-tests/src/tests/generators-list/generators-list.test.ts index d661417dbffe..fa2fb9e558fb 100644 --- a/packages/cli/ete-tests/src/tests/generators-list/generators-list.test.ts +++ b/packages/cli/ete-tests/src/tests/generators-list/generators-list.test.ts @@ -3,31 +3,31 @@ import { init } from "../init/init.js"; // Ensure that the generators list command works and the format doesn't change, since fern-bot consumes this describe("fern generator list", () => { - it("fern generator list", async () => { - const pathOfDirectory = await init(); + it("fern generator list", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); - const out = await runFernCli(["generator", "list"], { - cwd: pathOfDirectory - }); + const out = await runFernCli(["generator", "list"], { cwd: pathOfDirectory, signal }); expect(out.stdout).toMatchSnapshot(); }, 60_000); - it("fern generator list with exclude", async () => { - const pathOfDirectory = await init(); + it("fern generator list with exclude", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const out = await runFernCli(["generator", "list", "--exclude-mode", "local-file-system"], { - cwd: pathOfDirectory + cwd: pathOfDirectory, + signal }); expect(out.stdout).toMatchSnapshot(); }, 60_000); - it("fern generator list with include", async () => { - const pathOfDirectory = await init(); + it("fern generator list with include", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const out = await runFernCli(["generator", "list", "--include-mode", "local-file-system"], { - cwd: pathOfDirectory + cwd: pathOfDirectory, + signal }); expect(out.stdout).toMatchSnapshot(); diff --git a/packages/cli/ete-tests/src/tests/init/init.test.ts b/packages/cli/ete-tests/src/tests/init/init.test.ts index 7f11fe79e92f..985f3c456298 100644 --- a/packages/cli/ete-tests/src/tests/init/init.test.ts +++ b/packages/cli/ete-tests/src/tests/init/init.test.ts @@ -15,35 +15,36 @@ import { init } from "./init.js"; const FIXTURES_DIR = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fixtures")); describe("fern init", () => { - it.concurrent("no existing fern directory", async ({ expect }) => { - const pathOfDirectory = await init(); + it.concurrent("no existing fern directory", async ({ expect, signal }) => { + const pathOfDirectory = await init({ signal }); expect( await getDirectoryContentsForSnapshot(join(pathOfDirectory, RelativeFilePath.of(FERN_DIRECTORY))) ).toMatchSnapshot(); }, 60_000); - it.concurrent("no existing fern directory with fern definition", async ({ expect }) => { + it.concurrent("no existing fern directory with fern definition", async ({ expect, signal }) => { const pathOfDirectory = await init({ - additionalArgs: [{ name: "--fern-definition" }] - }); - await runFernCli(["check"], { - cwd: pathOfDirectory + additionalArgs: [{ name: "--fern-definition" }], + signal }); + await runFernCli(["check"], { cwd: pathOfDirectory, signal }); expect( await getDirectoryContentsForSnapshot(join(pathOfDirectory, RelativeFilePath.of(FERN_DIRECTORY))) ).toMatchSnapshot(); }, 60_000); - it.concurrent("existing fern directory", async ({ expect }) => { + it.concurrent("existing fern directory", async ({ expect, signal }) => { // add existing directory const pathOfDirectory = await init({ - additionalArgs: [{ name: "--fern-definition" }] + additionalArgs: [{ name: "--fern-definition" }], + signal }); // add new api await init({ directory: pathOfDirectory, - additionalArgs: [{ name: "--fern-definition" }] + additionalArgs: [{ name: "--fern-definition" }], + signal }); expect( await doesPathExist( @@ -57,7 +58,7 @@ describe("fern init", () => { ).toBe(true); }, 60_000); - it.concurrent("init openapi", async ({ expect }) => { + it.concurrent("init openapi", async ({ expect, signal }) => { // Create a temporary directory for the OpenAPI test const tmpDir = await tmp.dir(); const sourceOpenAPI = join( @@ -73,16 +74,18 @@ describe("fern init", () => { { name: "--openapi", value: "petstore-openapi.yml" }, { name: "--log-level", value: "debug" } ], - directory: AbsoluteFilePath.of(tmpDir.path) + directory: AbsoluteFilePath.of(tmpDir.path), + signal }); expect(await getDirectoryContentsForSnapshot(pathOfDirectory)).toMatchSnapshot(); }, 60_000); - it.concurrent("existing openapi fern directory", async ({ expect }) => { - const pathOfDirectory = await init(); + it.concurrent("existing openapi fern directory", async ({ expect, signal }) => { + const pathOfDirectory = await init({ signal }); await init({ - directory: pathOfDirectory + directory: pathOfDirectory, + signal }); expect( await doesPathExist( @@ -128,12 +131,13 @@ describe("fern init", () => { ).toBe(true); }, 60_000); - it.concurrent("existing openapi then fern-definition", async ({ expect }) => { - const pathOfDirectory = await init(); + it.concurrent("existing openapi then fern-definition", async ({ expect, signal }) => { + const pathOfDirectory = await init({ signal }); await init({ directory: pathOfDirectory, - additionalArgs: [{ name: "--fern-definition" }] + additionalArgs: [{ name: "--fern-definition" }], + signal }); expect( await doesPathExist( @@ -159,7 +163,7 @@ describe("fern init", () => { ).toBe(true); }, 60_000); - it.concurrent("conflicting --openapi and --fern-definition flags", async ({ expect }) => { + it.concurrent("conflicting --openapi and --fern-definition flags", async ({ expect, signal }) => { const tmpDir = await tmp.dir(); const sourceOpenAPI = join( FIXTURES_DIR, @@ -173,29 +177,30 @@ describe("fern init", () => { ["init", "--organization", "fern", "--openapi", "petstore-openapi.yml", "--fern-definition"], { cwd: AbsoluteFilePath.of(tmpDir.path), - reject: false + reject: false, + signal } ); expect(result.exitCode).not.toBe(0); }, 60_000); - it.concurrent("init docs", async ({ expect }) => { + it.concurrent("init docs", async ({ expect, signal }) => { const pathOfDirectory = await init({ - additionalArgs: [{ name: "--fern-definition" }] + additionalArgs: [{ name: "--fern-definition" }], + signal }); - await runFernCli(["init", "--docs", "--organization", "fern"], { - cwd: pathOfDirectory - }); + await runFernCli(["init", "--docs", "--organization", "fern"], { cwd: pathOfDirectory, signal }); expect(await getDirectoryContentsForSnapshot(pathOfDirectory)).toMatchSnapshot(); }, 60_000); - it.concurrent("init mintlify", async ({ expect }) => { + it.concurrent("init mintlify", async ({ expect, signal }) => { const mintJsonPath = join(FIXTURES_DIR, RelativeFilePath.of("mintlify"), RelativeFilePath.of("mint.json")); const pathOfDirectory = await init({ - additionalArgs: [{ name: "--mintlify", value: mintJsonPath }] + additionalArgs: [{ name: "--mintlify", value: mintJsonPath }], + signal }); expect(await getDirectoryContentsForSnapshot(pathOfDirectory, { skipBinaryContents: true })).toMatchSnapshot(); diff --git a/packages/cli/ete-tests/src/tests/init/init.ts b/packages/cli/ete-tests/src/tests/init/init.ts index 1a30849f5055..79e68026c208 100644 --- a/packages/cli/ete-tests/src/tests/init/init.ts +++ b/packages/cli/ete-tests/src/tests/init/init.ts @@ -9,6 +9,7 @@ interface InitOptions { name: "--openapi" | "--mintlify" | "--log-level" | "--fern-definition"; value?: string; }[]; + signal?: AbortSignal; } export async function init(options: InitOptions = {}): Promise { @@ -27,8 +28,6 @@ export async function init(options: InitOptions = {}): Promise } } - await runFernCli(cliArgs, { - cwd: directory - }); + await runFernCli(cliArgs, { cwd: directory, signal: options.signal }); return directory; } diff --git a/packages/cli/ete-tests/src/tests/ir/generateIrAsString.ts b/packages/cli/ete-tests/src/tests/ir/generateIrAsString.ts index c421f9fe8380..43b59fbc21c1 100644 --- a/packages/cli/ete-tests/src/tests/ir/generateIrAsString.ts +++ b/packages/cli/ete-tests/src/tests/ir/generateIrAsString.ts @@ -10,13 +10,15 @@ export async function generateIrAsString({ language, audiences, apiName, - version + version, + signal }: { fixturePath: AbsoluteFilePath; language?: generatorsYml.GenerationLanguage; audiences?: string[]; apiName?: string; version?: string; + signal?: AbortSignal; }): Promise { const tmpFile = await tmp.file({ postfix: ".json" }); const irOutputPath = AbsoluteFilePath.of(tmpFile.path); @@ -38,9 +40,7 @@ export async function generateIrAsString({ command.push("--version", version); } - await runFernCli(command, { - cwd: fixturePath - }); + await runFernCli(command, { cwd: fixturePath, signal }); const irContents = await readFile(irOutputPath); await tmpFile.cleanup(); diff --git a/packages/cli/ete-tests/src/tests/ir/ir.test.ts b/packages/cli/ete-tests/src/tests/ir/ir.test.ts index ee67ddb3a365..2595368fcd91 100644 --- a/packages/cli/ete-tests/src/tests/ir/ir.test.ts +++ b/packages/cli/ete-tests/src/tests/ir/ir.test.ts @@ -72,49 +72,52 @@ describe("ir", () => { const { only = false } = fixture; (only ? it.only : it.concurrent)( `${JSON.stringify(fixture)}`, - async ({ expect }) => { + async ({ expect, signal }) => { const fixturePath = join(FIXTURES_DIR, RelativeFilePath.of(fixture.name)); const irContents = await generateIrAsString({ fixturePath, language: fixture.language, audiences: fixture.audiences, - version: fixture.version + version: fixture.version, + signal }); - // biome-ignore lint/suspicious/noMisplacedAssertion: allow expect(irContents).toMatchSnapshot(); }, 90_000 ); } - it.concurrent("works with latest version", async ({ expect }) => { + it.concurrent("works with latest version", async ({ expect, signal }) => { const tmpFile = await tmp.file({ postfix: ".json" }); const { stdout } = await runFernCli(["ir", tmpFile.path, "--version", "v27"], { cwd: join(FIXTURES_DIR, RelativeFilePath.of("migration")), - reject: false + reject: false, + signal }); await tmpFile.cleanup(); expect(stdout).toContain("Wrote IR to"); - }, 10_000); + }, 30_000); - it.concurrent("fails with invalid version", async ({ expect }) => { + it.concurrent("fails with invalid version", async ({ expect, signal }) => { const tmpFile = await tmp.file({ postfix: ".json" }); const { stdout } = await runFernCli(["ir", tmpFile.path, "--version", "v100"], { cwd: join(FIXTURES_DIR, RelativeFilePath.of("migration")), - reject: false + reject: false, + signal }); await tmpFile.cleanup(); expect(stdout).toContain("IR v100 does not exist"); - }, 10_000); + }, 30_000); }); describe("ir from proto", () => { // biome-ignore lint/suspicious/noSkippedTests: Allow test skip for now - it.skip("works with proto-ir", async () => { + it.skip("works with proto-ir", async ({ signal }) => { try { await runFernCli(["ir", "ir.json", "--from-openapi"], { cwd: join(FIXTURES_DIR, RelativeFilePath.of("proto-ir")), - reject: false + reject: false, + signal }); const contents = await readFile( path.join(FIXTURES_DIR, RelativeFilePath.of("proto-ir"), "ir.json"), @@ -127,11 +130,12 @@ describe("ir from proto", () => { } }, 10_000); // biome-ignore lint/suspicious/noSkippedTests: Allow test skip for now - it.skip("ir from proto through oas", async () => { + it.skip("ir from proto through oas", async ({ signal }) => { try { await runFernCli(["ir", "ir.json", "--from-openapi"], { cwd: join(FIXTURES_DIR, RelativeFilePath.of("proto-oas-ir")), - reject: false + reject: false, + signal }); const contents = await readFile( path.join(FIXTURES_DIR, RelativeFilePath.of("proto-oas-ir"), "ir.json"), diff --git a/packages/cli/ete-tests/src/tests/jsonschema/jsonschema.test.ts b/packages/cli/ete-tests/src/tests/jsonschema/jsonschema.test.ts index 407bf812a39f..dee02fd125d1 100644 --- a/packages/cli/ete-tests/src/tests/jsonschema/jsonschema.test.ts +++ b/packages/cli/ete-tests/src/tests/jsonschema/jsonschema.test.ts @@ -5,11 +5,12 @@ import { runFernCli } from "../../utils/runFernCli.js"; import { init } from "../init/init.js"; describe("jsonschema", () => { - it("works with latest version", async () => { - const pathOfDirectory = await init({ additionalArgs: [{ name: "--fern-definition" }] }); + it("works with latest version", async ({ signal }) => { + const pathOfDirectory = await init({ additionalArgs: [{ name: "--fern-definition" }], signal }); await runFernCli(["jsonschema", "schema.json", "--type", "imdb.Movie"], { cwd: pathOfDirectory, - reject: false + reject: false, + signal }); const jsonSchema = JSON.parse( diff --git a/packages/cli/ete-tests/src/tests/mock/mock.test.ts b/packages/cli/ete-tests/src/tests/mock/mock.test.ts index fb42c7a1a801..590ab2e23959 100644 --- a/packages/cli/ete-tests/src/tests/mock/mock.test.ts +++ b/packages/cli/ete-tests/src/tests/mock/mock.test.ts @@ -7,15 +7,17 @@ const fixturesDir = join(AbsoluteFilePath.of(__dirname), RelativeFilePath.of("fi describe("fern mock", () => { // biome-ignore lint/suspicious/noSkippedTests: allow - it.skip("mock request/response", async () => { + it.skip("mock request/response", async ({ signal }) => { void runFernCli(["mock", "--api", "simple", "--port", "3001"], { - cwd: join(fixturesDir, RelativeFilePath.of("simple")) + cwd: join(fixturesDir, RelativeFilePath.of("simple")), + signal }); await sleep(5000); const getResponse = await fetch("http://localhost:3001/test/root/movies/id-123?movieName=hello", { - method: "GET" + method: "GET", + signal }); expect(getResponse.body != null).toEqual(true); @@ -32,7 +34,8 @@ describe("fern mock", () => { }), headers: { "content-type": "application/json" - } + }, + signal }); expect(postResponse.body != null).toEqual(true); diff --git a/packages/cli/ete-tests/src/tests/organization/organization.test.ts b/packages/cli/ete-tests/src/tests/organization/organization.test.ts index 9f13fdb040ac..d9cf65ddda16 100644 --- a/packages/cli/ete-tests/src/tests/organization/organization.test.ts +++ b/packages/cli/ete-tests/src/tests/organization/organization.test.ts @@ -6,23 +6,19 @@ import { init } from "../init/init.js"; // Pretty trivial command, but adding tests in case this breaks down the line describe("fern organization", () => { - it("fern organization", async () => { - const pathOfDirectory = await init(); + it("fern organization", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); - const out = await runFernCli(["organization"], { - cwd: pathOfDirectory - }); + const out = await runFernCli(["organization"], { cwd: pathOfDirectory, signal }); expect(out.stdout).toEqual("fern"); }, 60_000); - it("fern organization -o ", async () => { - const pathOfDirectory = await init(); + it("fern organization -o ", async ({ signal }) => { + const pathOfDirectory = await init({ signal }); const tmpFile = await tmp.file(); - await runFernCli(["organization", "-o", tmpFile.path], { - cwd: pathOfDirectory - }); + await runFernCli(["organization", "-o", tmpFile.path], { cwd: pathOfDirectory, signal }); const out = await readFile(tmpFile.path, "utf-8"); expect(out).toEqual("fern"); diff --git a/packages/cli/ete-tests/src/tests/update-api/update-api.test.ts b/packages/cli/ete-tests/src/tests/update-api/update-api.test.ts index 57fa83bd67ba..c2a53e0a8245 100644 --- a/packages/cli/ete-tests/src/tests/update-api/update-api.test.ts +++ b/packages/cli/ete-tests/src/tests/update-api/update-api.test.ts @@ -9,7 +9,7 @@ import { setupOpenAPIServer } from "../../utils/setupOpenAPIServer.js"; const FIXTURES_DIR = path.join(__dirname, "fixtures"); describe("fern api update", () => { - it("fern api update", async () => { + it("fern api update", async ({ signal }) => { // Start express server that will respond with the OpenAPI spec. const { cleanup } = setupOpenAPIServer(); @@ -18,9 +18,7 @@ describe("fern api update", () => { const outputPath = AbsoluteFilePath.of(path.join(directory, "fern")); await cp(FIXTURES_DIR, directory, { recursive: true }); - await runFernCli(["api", "update"], { - cwd: directory - }); + await runFernCli(["api", "update"], { cwd: directory, signal }); expect(await getDirectoryContentsForSnapshot(outputPath)).toMatchSnapshot(); @@ -28,7 +26,7 @@ describe("fern api update", () => { await cleanup(); }, 60_000); - it("fern api update --indent 4", async () => { + it("fern api update --indent 4", async ({ signal }) => { // Start express server that will respond with the OpenAPI spec. const { cleanup } = setupOpenAPIServer(); @@ -37,9 +35,7 @@ describe("fern api update", () => { const outputPath = AbsoluteFilePath.of(path.join(directory, "fern")); await cp(FIXTURES_DIR, directory, { recursive: true }); - await runFernCli(["api", "update", "--indent", "4"], { - cwd: directory - }); + await runFernCli(["api", "update", "--indent", "4"], { cwd: directory, signal }); expect(await getDirectoryContentsForSnapshot(outputPath)).toMatchSnapshot(); diff --git a/packages/cli/ete-tests/src/tests/upgrade-generator/upgrade-generator.test.ts b/packages/cli/ete-tests/src/tests/upgrade-generator/upgrade-generator.test.ts index cd0f9579ab55..c5299099cde1 100644 --- a/packages/cli/ete-tests/src/tests/upgrade-generator/upgrade-generator.test.ts +++ b/packages/cli/ete-tests/src/tests/upgrade-generator/upgrade-generator.test.ts @@ -10,16 +10,14 @@ import { runFernCli } from "../../utils/runFernCli.js"; const FIXTURES_DIR = path.join(__dirname, "fixtures"); describe("fern generator upgrade", () => { - it.concurrent("fern generator upgrade", async () => { + it.concurrent("fern generator upgrade", async ({ signal }) => { // Create tmpdir and copy contents const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); await cp(FIXTURES_DIR, directory, { recursive: true }); - await runFernCli(["generator", "upgrade"], { - cwd: directory - }); + await runFernCli(["generator", "upgrade"], { cwd: directory, signal }); const outputFile = join(directory, RelativeFilePath.of("version.txt")); await runFernCli( @@ -34,15 +32,13 @@ describe("fern generator upgrade", () => { "-o", outputFile ], - { - cwd: directory - } + { cwd: directory, signal } ); expect(JSON.parse((await readFile(outputFile)).toString()).version).not.toEqual("3.0.0"); }, 60_000); - it.concurrent("fern generator upgrade with filters", async () => { + it.concurrent("fern generator upgrade with filters", async ({ signal }) => { // Create tmpdir and copy contents const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); @@ -59,9 +55,7 @@ describe("fern generator upgrade", () => { "fernapi/fern-python-sdk", "--include-major" ], - { - cwd: directory - } + { cwd: directory, signal } ); const outputFile = join(directory, RelativeFilePath.of("version.txt")); @@ -77,15 +71,13 @@ describe("fern generator upgrade", () => { "-o", outputFile ], - { - cwd: directory - } + { cwd: directory, signal } ); expect(JSON.parse((await readFile(outputFile)).toString()).version).not.toEqual("3.0.0"); }, 60_000); - it("fern generator help commands", async () => { + it("fern generator help commands", async ({ signal }) => { // Create tmpdir and copy contents const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); @@ -93,25 +85,15 @@ describe("fern generator upgrade", () => { await cp(FIXTURES_DIR, directory, { recursive: true }); expect( - ( - await runFernCli(["generator", "--help"], { - cwd: directory, - reject: false - }) - ).stdout + (await runFernCli(["generator", "--help"], { cwd: directory, reject: false, signal })).stdout ).toMatchSnapshot(); expect( - ( - await runFernCli(["generator", "upgrade", "--help"], { - cwd: directory, - reject: false - }) - ).stdout + (await runFernCli(["generator", "upgrade", "--help"], { cwd: directory, reject: false, signal })).stdout ).toMatchSnapshot(); }, 60_000); - it.concurrent("fern generator upgrade majors", async () => { + it.concurrent("fern generator upgrade majors", async ({ signal }) => { // Create tmpdir and copy contents const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); @@ -120,9 +102,7 @@ describe("fern generator upgrade", () => { await runFernCli( ["generator", "upgrade", "--group", "shouldnt-upgrade", "--generator", "fernapi/fern-python-sdk"], - { - cwd: directory - } + { cwd: directory, signal } ); const outputFile = join(directory, RelativeFilePath.of("version.txt")); @@ -138,9 +118,7 @@ describe("fern generator upgrade", () => { "-o", outputFile ], - { - cwd: directory - } + { cwd: directory, signal } ); expect(JSON.parse((await readFile(outputFile)).toString()).version).toEqual("2.16.0"); @@ -155,9 +133,7 @@ describe("fern generator upgrade", () => { "fernapi/fern-python-sdk", "--include-major" ], - { - cwd: directory - } + { cwd: directory, signal } ); const outputFileNewMajor = join(directory, RelativeFilePath.of("version-new.txt")); @@ -173,15 +149,13 @@ describe("fern generator upgrade", () => { "-o", outputFileNewMajor ], - { - cwd: directory - } + { cwd: directory, signal } ); expect(JSON.parse((await readFile(outputFileNewMajor)).toString()).version).not.toEqual("2.16.0"); }, 60_000); - it.skip("fern generator upgrade message", async () => { + it.skip("fern generator upgrade message", async ({ signal }) => { const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); @@ -203,15 +177,15 @@ describe("fern generator upgrade", () => { "-o", outputFileNewMajor ], - { - cwd: directory - } + { cwd: directory, signal } ) ).stderr ).toMatchSnapshot(); }, 60_000); - it.concurrent("fern generator upgrade --skip-autorelease-disabled skips autorelease false generators", async () => { + it.concurrent("fern generator upgrade --skip-autorelease-disabled skips autorelease false generators", async ({ + signal + }) => { const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); @@ -226,10 +200,7 @@ describe("fern generator upgrade", () => { "--include-major", "--skip-autorelease-disabled" ], - { - cwd: directory, - reject: false - } + { cwd: directory, reject: false, signal } ); expect(result.stdout).toContain("Skipped generators with autorelease disabled:"); @@ -249,9 +220,7 @@ describe("fern generator upgrade", () => { "-o", outputFile ], - { - cwd: directory - } + { cwd: directory, signal } ); expect(JSON.parse((await readFile(outputFile)).toString()).version).toEqual("3.0.0"); @@ -269,22 +238,23 @@ describe("fern generator upgrade", () => { "-o", outputFileJava ], - { - cwd: directory - } + { cwd: directory, signal } ); expect(JSON.parse((await readFile(outputFileJava)).toString()).version).not.toEqual("0.0.1"); }, 60_000); - it.concurrent("fern generator upgrade without --skip-autorelease-disabled upgrades all generators", async () => { + it.concurrent("fern generator upgrade without --skip-autorelease-disabled upgrades all generators", async ({ + signal + }) => { const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); await cp(FIXTURES_DIR, directory, { recursive: true }); await runFernCli(["generator", "upgrade", "--group", "autorelease-disabled", "--include-major"], { - cwd: directory + cwd: directory, + signal }); const outputFile = join(directory, RelativeFilePath.of("version-no-skip.txt")); @@ -300,15 +270,13 @@ describe("fern generator upgrade", () => { "-o", outputFile ], - { - cwd: directory - } + { cwd: directory, signal } ); expect(JSON.parse((await readFile(outputFile)).toString()).version).not.toEqual("3.0.0"); }, 60_000); - it.concurrent("fern generator upgrade shows major version message", async () => { + it.concurrent("fern generator upgrade shows major version message", async ({ signal }) => { const tmpDir = await tmp.dir(); const directory = AbsoluteFilePath.of(tmpDir.path); @@ -316,10 +284,7 @@ describe("fern generator upgrade", () => { const result = await runFernCli( ["generator", "upgrade", "--group", "shouldnt-upgrade", "--generator", "fernapi/fern-python-sdk"], - { - cwd: directory, - reject: false - } + { cwd: directory, reject: false, signal } ); expect(result.stdout).toContain("Major version upgrades available:"); diff --git a/packages/cli/ete-tests/src/tests/upgrade/upgrade.test.ts b/packages/cli/ete-tests/src/tests/upgrade/upgrade.test.ts index 7a9771278a86..24e10c493481 100644 --- a/packages/cli/ete-tests/src/tests/upgrade/upgrade.test.ts +++ b/packages/cli/ete-tests/src/tests/upgrade/upgrade.test.ts @@ -90,8 +90,8 @@ const GENERATORS_CONFIGURATION: generatorsYml.GeneratorsConfigurationSchema = { }; describe("fern upgrade", () => { - it("upgrades generators", async () => { - const directory = await init(); + it("upgrades generators", async ({ signal }) => { + const directory = await init({ signal }); const generatorsConfigurationFilepath = join( directory, RelativeFilePath.of(FERN_DIRECTORY), @@ -105,7 +105,8 @@ describe("fern upgrade", () => { env: { // this env var needs to be defined so the CLI thinks we're mid-upgrade FERN_PRE_UPGRADE_VERSION: "0.0.0" - } + }, + signal }); const generatorsConfiguration = (await readFile(generatorsConfigurationFilepath)).toString(); expect(generatorsConfiguration).toMatchSnapshot(); diff --git a/packages/cli/ete-tests/src/tests/validate/validate.test.ts b/packages/cli/ete-tests/src/tests/validate/validate.test.ts index c490cd81b2fa..5a8fcdf72125 100644 --- a/packages/cli/ete-tests/src/tests/validate/validate.test.ts +++ b/packages/cli/ete-tests/src/tests/validate/validate.test.ts @@ -15,14 +15,15 @@ describe("validate", () => { function itFixture(fixtureName: string) { it(// eslint-disable-next-line jest/valid-title - fixtureName, async () => { + fixtureName, async ({ signal }) => { const fixturePath = path.join(FIXTURES_DIR, fixtureName); const irOutputPath = path.join(fixturePath, "api", "ir.json"); await rm(irOutputPath, { force: true, recursive: true }); const { stdout } = await runFernCli(["check"], { cwd: fixturePath, - reject: false + reject: false, + signal }); if (fixtureName == "simple") { diff --git a/packages/cli/ete-tests/src/tests/validate/x-fern-examples.test.ts b/packages/cli/ete-tests/src/tests/validate/x-fern-examples.test.ts index b65e36d523eb..021bb97c22aa 100644 --- a/packages/cli/ete-tests/src/tests/validate/x-fern-examples.test.ts +++ b/packages/cli/ete-tests/src/tests/validate/x-fern-examples.test.ts @@ -6,12 +6,13 @@ import { runFernCli } from "../../utils/runFernCli.js"; const FIXTURES_DIR = path.join(__dirname, "fixtures"); describe("x-fern-examples file-linking", () => { - it("should resolve $ref to external code sample files", async () => { + it("should resolve $ref to external code sample files", async ({ signal }) => { const fixturePath = path.join(FIXTURES_DIR, "x-fern-examples-file-linking"); const { stdout, exitCode } = await runFernCli(["check"], { cwd: fixturePath, - reject: false + reject: false, + signal }); const strippedOutput = stripAnsi(stdout).trim(); diff --git a/packages/cli/ete-tests/src/tests/version/version.test.ts b/packages/cli/ete-tests/src/tests/version/version.test.ts index bd43adf932c8..9545d2b71a24 100644 --- a/packages/cli/ete-tests/src/tests/version/version.test.ts +++ b/packages/cli/ete-tests/src/tests/version/version.test.ts @@ -5,17 +5,13 @@ import { runFernCli } from "../../utils/runFernCli.js"; const DEFAULT_VERSION = "0.0.0"; describe("version", () => { - it("--version", async () => { - const { stdout } = await runFernCli(["--version"], { - cwd: (await tmp.dir()).path - }); + it("--version", async ({ signal }) => { + const { stdout } = await runFernCli(["--version"], { cwd: (await tmp.dir()).path, signal }); expect(stdout).toEqual(DEFAULT_VERSION); }, 60_000); - it("-v", async () => { - const { stdout } = await runFernCli(["-v"], { - cwd: (await tmp.dir()).path - }); + it("-v", async ({ signal }) => { + const { stdout } = await runFernCli(["-v"], { cwd: (await tmp.dir()).path, signal }); expect(stdout).toEqual(DEFAULT_VERSION); }, 60_000); }); diff --git a/packages/cli/ete-tests/src/tests/write-definition/writeDefinition.test.ts b/packages/cli/ete-tests/src/tests/write-definition/writeDefinition.test.ts index ae7470e9165e..4dd9a6534248 100644 --- a/packages/cli/ete-tests/src/tests/write-definition/writeDefinition.test.ts +++ b/packages/cli/ete-tests/src/tests/write-definition/writeDefinition.test.ts @@ -24,21 +24,17 @@ describe("write-definition", () => { function itFixture(fixtureName: string) { it.concurrent(// eslint-disable-next-line jest/valid-title - fixtureName, async ({ expect }) => { + fixtureName, async ({ expect, signal }) => { const fixturePath = path.join(FIXTURES_DIR, fixtureName); const definitionOutputPath = path.join(fixturePath, "fern", ".definition"); if (await doesPathExist(AbsoluteFilePath.of(definitionOutputPath))) { await rm(definitionOutputPath, { force: true, recursive: true }); } - await runFernCli(["write-definition", "--log-level", "debug"], { - cwd: fixturePath - }); + await runFernCli(["write-definition", "--log-level", "debug"], { cwd: fixturePath, signal }); expect(await getDirectoryContentsForSnapshot(AbsoluteFilePath.of(definitionOutputPath))).toMatchSnapshot(); - await runFernCli(["check", "--log-level", "debug"], { - cwd: fixturePath - }); + await runFernCli(["check", "--log-level", "debug"], { cwd: fixturePath, signal }); }, 90_000); } diff --git a/packages/cli/ete-tests/src/tests/write-docs-definition/writeDocsDefinition.test.ts b/packages/cli/ete-tests/src/tests/write-docs-definition/writeDocsDefinition.test.ts index f22e8c9fd7fb..eb565823bdfa 100644 --- a/packages/cli/ete-tests/src/tests/write-docs-definition/writeDocsDefinition.test.ts +++ b/packages/cli/ete-tests/src/tests/write-docs-definition/writeDocsDefinition.test.ts @@ -11,7 +11,7 @@ describe("write-docs-definition", () => { it.skip("products-with-versions", () => testFixture("products-with-versions"), 10_000); }); -async function testFixture(fixtureName: string) { +async function testFixture(fixtureName: string, signal?: AbortSignal) { const fixturePath = path.join(FIXTURES_DIR, fixtureName); const outputPath = path.join(fixturePath, "docs-definition.json"); @@ -22,7 +22,8 @@ async function testFixture(fixtureName: string) { // Generate docs definition await runFernCli(["write-docs-definition", "docs-definition.json", "--log-level", "debug"], { - cwd: fixturePath + cwd: fixturePath, + signal }); // Read and verify output diff --git a/packages/cli/ete-tests/src/tests/write-overrides/writeOverrides.test.ts b/packages/cli/ete-tests/src/tests/write-overrides/writeOverrides.test.ts index 923d2938b6f6..b2998ff733b6 100644 --- a/packages/cli/ete-tests/src/tests/write-overrides/writeOverrides.test.ts +++ b/packages/cli/ete-tests/src/tests/write-overrides/writeOverrides.test.ts @@ -12,13 +12,11 @@ describe("overrides", () => { function itFixture(fixtureName: string) { it(// eslint-disable-next-line jest/valid-title - fixtureName, async () => { + fixtureName, async ({ signal }) => { const fixturePath = path.join(FIXTURES_DIR, fixtureName); const outputPath = path.join(fixturePath, "fern", "openapi", "openapi-overrides.yml"); - await runFernCli(["write-overrides"], { - cwd: fixturePath - }); + await runFernCli(["write-overrides"], { cwd: fixturePath, signal }); await sleep(5000); diff --git a/packages/cli/ete-tests/src/utils/runCliV2.ts b/packages/cli/ete-tests/src/utils/runCliV2.ts index 0cfe655c4c43..8c6b444a2b7b 100644 --- a/packages/cli/ete-tests/src/utils/runCliV2.ts +++ b/packages/cli/ete-tests/src/utils/runCliV2.ts @@ -22,6 +22,8 @@ export declare namespace CliV2 { expectError?: boolean; /** Authentication token (set to null to explicitly remove FERN_TOKEN) */ authToken?: string | null; + /** AbortSignal from vitest test context for cleanup on timeout/bail/Ctrl+C */ + signal?: AbortSignal; } export interface Result { @@ -133,7 +135,8 @@ export async function runCliV2(options: CliV2.Options): Promise { const result = await loggingExeca(CONSOLE_LOGGER, "node", ["--enable-source-maps", cliPath, ...options.args], { ...execaOptions, - doNotPipeOutput: options.expectError ?? false + doNotPipeOutput: options.expectError ?? false, + signal: options.signal }); const duration = Date.now() - startTime; diff --git a/packages/cli/ete-tests/src/utils/runFernCli.ts b/packages/cli/ete-tests/src/utils/runFernCli.ts index 2ea974bd629c..960e81228649 100644 --- a/packages/cli/ete-tests/src/utils/runFernCli.ts +++ b/packages/cli/ete-tests/src/utils/runFernCli.ts @@ -1,15 +1,21 @@ -import { CONSOLE_LOGGER, Logger } from "@fern-api/logger"; +import { CONSOLE_LOGGER } from "@fern-api/logger"; import { loggingExeca, runExeca } from "@fern-api/logging-execa"; -import { Options } from "execa"; +import type { Options as ExecaOptions } from "execa"; import path from "path"; +interface RunFernCliOptions extends ExecaOptions { + /** Include the FERN_ORG_TOKEN_DEV auth token (default: true) */ + includeAuthToken?: boolean; + /** AbortSignal from vitest test context for cleanup on timeout/bail/Ctrl+C */ + signal?: AbortSignal; +} + export async function runFernCli( args: string[], - options?: Options, - includeAuthToken: boolean = true + { includeAuthToken = true, signal, ...execaOptions }: RunFernCliOptions = {} ): Promise { const env = { - ...options?.env, + ...execaOptions?.env, ...(includeAuthToken ? { FERN_TOKEN: process.env.FERN_ORG_TOKEN_DEV } : {}) }; @@ -18,33 +24,35 @@ export async function runFernCli( "node", ["--enable-source-maps", path.join(__dirname, "../../../cli/dist/dev/cli.cjs"), ...args], { - ...options, + ...execaOptions, env, - doNotPipeOutput: options?.reject === false + doNotPipeOutput: execaOptions?.reject === false, + signal } ); } -export async function runFernCliWithoutAuthToken( - args: string[], - options?: Options, - logger?: Logger -): Promise { - return runFernCli(args, options, false); +interface CaptureFernCliOptions extends ExecaOptions { + /** AbortSignal from vitest test context for cleanup on timeout/bail/Ctrl+C */ + signal?: AbortSignal; } -export function captureFernCli(args: string[], options?: Options): import("execa").ExecaChildProcess { +export function captureFernCli( + args: string[], + { signal, ...execaOptions }: CaptureFernCliOptions = {} +): ReturnType { return runExeca( CONSOLE_LOGGER, "node", ["--enable-source-maps", path.join(__dirname, "../../../cli/dist/dev/cli.cjs"), ...args], { - ...options, + ...execaOptions, env: { - ...options?.env, + ...execaOptions?.env, FERN_TOKEN: process.env.FERN_ORG_TOKEN_DEV }, - doNotPipeOutput: options?.reject === false + doNotPipeOutput: execaOptions?.reject === false, + signal } ); } diff --git a/packages/cli/fern-definition/validator/src/workerSetup.ts b/packages/cli/fern-definition/validator/src/workerSetup.ts new file mode 100644 index 000000000000..3669e6ecd8a0 --- /dev/null +++ b/packages/cli/fern-definition/validator/src/workerSetup.ts @@ -0,0 +1,10 @@ +// Worker setup file — pre-imports heavy modules so they are cached in the worker's +// module cache when isolate is disabled. With 47 test files sharing ~8 workers, +// each worker handles ~6 files. Without this, each file re-imports these modules. +// With isolate:false, they are loaded once per worker and shared across all files. + +import "@fern-api/configuration-loader"; +import "@fern-api/fs-utils"; +import "@fern-api/lazy-fern-workspace"; +import "@fern-api/logger"; +import "@fern-api/task-context"; diff --git a/packages/cli/fern-definition/validator/vitest.config.ts b/packages/cli/fern-definition/validator/vitest.config.ts index efbce2018779..65590050071c 100644 --- a/packages/cli/fern-definition/validator/vitest.config.ts +++ b/packages/cli/fern-definition/validator/vitest.config.ts @@ -1 +1,11 @@ -export { default } from "@fern-api/configs/vitest/base.mjs"; +import { defaultConfig, defineConfig, mergeConfig } from "@fern-api/configs/vitest/base.mjs"; + +export default mergeConfig( + defineConfig(defaultConfig), + defineConfig({ + test: { + setupFiles: ["./src/workerSetup.ts"], + isolate: false + } + }) +); diff --git a/packages/cli/generation/ir-migrations/src/migrations/v65-to-v63/__test__/migrateFromV65ToV63.test.ts b/packages/cli/generation/ir-migrations/src/migrations/v65-to-v63/__test__/migrateFromV65ToV63.test.ts index efc50913bcab..96c66fbce724 100644 --- a/packages/cli/generation/ir-migrations/src/migrations/v65-to-v63/__test__/migrateFromV65ToV63.test.ts +++ b/packages/cli/generation/ir-migrations/src/migrations/v65-to-v63/__test__/migrateFromV65ToV63.test.ts @@ -57,10 +57,8 @@ describe("migrateFromV65ToV63", () => { const migratedIR = V65_TO_V63_MIGRATION.migrateBackwards(v65IR, mockContext); const migratedEndpoint = migratedIR.services["service_test"]?.endpoints[0]; - expect(migratedEndpoint?.pagination?.type).toBe("cursor"); - if (migratedEndpoint?.pagination?.type === "cursor") { - expect(migratedEndpoint.pagination.page.property.type).toBe("query"); - } + expect.assert(migratedEndpoint?.pagination?.type === "cursor"); + expect(migratedEndpoint.pagination.page.property.type).toBe("query"); }); it("removes nextUri pagination", () => { diff --git a/packages/cli/generation/ir-migrations/src/workerSetup.ts b/packages/cli/generation/ir-migrations/src/workerSetup.ts new file mode 100644 index 000000000000..2f3821034a33 --- /dev/null +++ b/packages/cli/generation/ir-migrations/src/workerSetup.ts @@ -0,0 +1,11 @@ +// Worker setup file — pre-imports heavy modules so they are cached in the worker's +// module cache when isolate is disabled. With 56 test files sharing ~8 workers, +// each worker handles ~7 files. Without this, each file re-imports these modules. +// With isolate:false, they are loaded once per worker and shared across all files. + +import "@fern-api/cli-source-resolver"; +import "@fern-api/fs-utils"; +import "@fern-api/ir-generator"; +import "@fern-api/ir-sdk"; +import "@fern-api/task-context"; +import "@fern-api/workspace-loader"; diff --git a/packages/cli/generation/ir-migrations/vitest.config.ts b/packages/cli/generation/ir-migrations/vitest.config.ts index efbce2018779..65590050071c 100644 --- a/packages/cli/generation/ir-migrations/vitest.config.ts +++ b/packages/cli/generation/ir-migrations/vitest.config.ts @@ -1 +1,11 @@ -export { default } from "@fern-api/configs/vitest/base.mjs"; +import { defaultConfig, defineConfig, mergeConfig } from "@fern-api/configs/vitest/base.mjs"; + +export default mergeConfig( + defineConfig(defaultConfig), + defineConfig({ + test: { + setupFiles: ["./src/workerSetup.ts"], + isolate: false + } + }) +); diff --git a/packages/cli/generation/local-generation/docker-utils/src/__test__/runDocker.test.ts b/packages/cli/generation/local-generation/docker-utils/src/__test__/runDocker.test.ts index 48ac8a9877f2..0488bfbebec5 100644 --- a/packages/cli/generation/local-generation/docker-utils/src/__test__/runDocker.test.ts +++ b/packages/cli/generation/local-generation/docker-utils/src/__test__/runDocker.test.ts @@ -31,14 +31,15 @@ beforeAll(async () => { }, 60_000); describe("runContainer", () => { - it("basic-writer", async () => { + it("basic-writer", async ({ signal }) => { const expectedOutputFilePath = "my-file.txt"; await runContainer({ logger: CONSOLE_LOGGER, imageName: BASIC_WRITER_IMAGE_NAME, args: [expectedOutputFilePath], - binds: [`${HOST_OUTPUT_DIR}:${IMAGE_OUTPUT_DIR}`] + binds: [`${HOST_OUTPUT_DIR}:${IMAGE_OUTPUT_DIR}`], + signal }); const fileExists = await doesPathExist(join(HOST_OUTPUT_DIR, RelativeFilePath.of(expectedOutputFilePath))); diff --git a/packages/cli/generation/local-generation/docker-utils/src/runDocker.ts b/packages/cli/generation/local-generation/docker-utils/src/runDocker.ts index ae97cd0e9d70..3451646551f5 100644 --- a/packages/cli/generation/local-generation/docker-utils/src/runDocker.ts +++ b/packages/cli/generation/local-generation/docker-utils/src/runDocker.ts @@ -15,6 +15,8 @@ export declare namespace runContainer { writeLogsToFile?: boolean; removeAfterCompletion?: boolean; runner?: ContainerRunner; + /** AbortSignal to kill the container process on timeout/bail/Ctrl+C */ + signal?: AbortSignal; } export interface Result { @@ -31,7 +33,8 @@ export async function runContainer({ ports = {}, writeLogsToFile = true, removeAfterCompletion = false, - runner + runner, + signal }: runContainer.Args): Promise { const tryRun = () => tryRunContainer({ @@ -43,13 +46,14 @@ export async function runContainer({ ports, removeAfterCompletion, writeLogsToFile, - runner + runner, + signal }); try { await tryRun(); } catch (e) { if (e instanceof Error && e.message.includes("No such image")) { - await pullImage(imageName, runner); + await pullImage(imageName, runner, signal); await tryRun(); } else { throw e; @@ -71,6 +75,8 @@ export declare namespace runDocker { writeLogsToFile?: boolean; removeAfterCompletion?: boolean; runner?: ContainerRunner; + /** AbortSignal to kill the container process on timeout/bail/Ctrl+C */ + signal?: AbortSignal; } export interface Result { @@ -92,7 +98,8 @@ async function tryRunContainer({ ports = {}, removeAfterCompletion, writeLogsToFile, - runner + runner, + signal }: { logger: Logger; imageName: string; @@ -103,6 +110,7 @@ async function tryRunContainer({ removeAfterCompletion: boolean; writeLogsToFile: boolean; runner?: ContainerRunner; + signal?: AbortSignal; }): Promise { if (process.env["FERN_STACK_TRACK"]) { envVars["FERN_STACK_TRACK"] = process.env["FERN_STACK_TRACK"]; @@ -123,7 +131,8 @@ async function tryRunContainer({ const { stdout, stderr, exitCode } = await loggingExeca(logger, containerRunner, containerArgs, { reject: false, all: true, - doNotPipeOutput: true + doNotPipeOutput: true, + signal }); const logs = stdout + stderr; @@ -148,9 +157,10 @@ async function tryRunContainer({ } } -async function pullImage(imageName: string, runner?: ContainerRunner): Promise { +async function pullImage(imageName: string, runner?: ContainerRunner, signal?: AbortSignal): Promise { await loggingExeca(undefined, runner ?? "docker", ["pull", imageName], { all: true, - doNotPipeOutput: true + doNotPipeOutput: true, + signal }); } diff --git a/packages/cli/library-docs-generator/src/__test__/integration.test.ts b/packages/cli/library-docs-generator/src/__test__/integration.test.ts index 2bdae683f1b4..6d80c412efdf 100644 --- a/packages/cli/library-docs-generator/src/__test__/integration.test.ts +++ b/packages/cli/library-docs-generator/src/__test__/integration.test.ts @@ -268,13 +268,10 @@ describe("generate() — full pipeline integration", () => { } // data section has nested aime section - // biome-ignore lint/style/noNonNullAssertion: length asserted above - const dataSection = result.navigation[2]!; - expect(dataSection.type).toBe("section"); - if (dataSection.type === "section") { - expect(dataSection.children).toHaveLength(1); - expect(dataSection.children[0]?.title).toBe("aime"); - } + const dataSection = result.navigation[2]; + expect.assert(dataSection?.type === "section"); + expect(dataSection.children).toHaveLength(1); + expect(dataSection.children[0]?.title).toBe("aime"); }); it("navigation page nodes have correct slugs and pageIds", () => { @@ -288,10 +285,9 @@ describe("generate() — full pipeline integration", () => { expect(pages).toHaveLength(3); for (const page of pages) { - if (page.type === "page") { - expect(page.slug).toMatch(/^reference\/python\//); - expect(page.pageId).toBe(`${page.slug}.mdx`); - } + expect.assert(page.type === "page"); + expect(page.slug).toMatch(/^reference\/python\//); + expect(page.pageId).toBe(`${page.slug}.mdx`); } }); diff --git a/packages/cli/library-docs-generator/src/__test__/writers.test.ts b/packages/cli/library-docs-generator/src/__test__/writers.test.ts index 8e3c449a6a7e..8b80f8b87f25 100644 --- a/packages/cli/library-docs-generator/src/__test__/writers.test.ts +++ b/packages/cli/library-docs-generator/src/__test__/writers.test.ts @@ -179,8 +179,8 @@ describe("buildNavigation", () => { const nav = buildNavigation(root, "ref"); // Every submodule gets wrapped in a section by the parent expect(nav).toHaveLength(1); - expect(nav[0]?.type).toBe("section"); - const section = nav[0] as { type: "section"; title: string; slug: string; children: NavNode[] }; + const section = nav[0]; + expect.assert(section?.type === "section"); expect(section.title).toBe("utils"); expect(section.slug).toBe("ref/pkg/utils"); expect(section.children).toHaveLength(1); @@ -213,12 +213,12 @@ describe("buildNavigation", () => { const nav = buildNavigation(root, "ref"); expect(nav).toHaveLength(1); // sub is a section wrapping leaf's section - expect(nav[0]?.type).toBe("section"); - const sub = nav[0] as { type: "section"; children: NavNode[] }; + const sub = nav[0]; + expect.assert(sub?.type === "section"); expect(sub.children).toHaveLength(1); // leaf is also wrapped in a section by sub - expect(sub.children[0]?.type).toBe("section"); - const leafSection = sub.children[0] as { type: "section"; children: NavNode[] }; + const leafSection = sub.children[0]; + expect.assert(leafSection?.type === "section"); expect(leafSection.children).toHaveLength(1); expect(leafSection.children[0]?.type).toBe("page"); }); @@ -239,8 +239,9 @@ describe("buildNavigation", () => { const nav = buildNavigation(root, "ref"); // Only "filled" appears (wrapped in section); "empty" is skipped expect(nav).toHaveLength(1); - expect(nav[0]?.type).toBe("section"); - expect((nav[0] as { title: string }).title).toBe("filled"); + const filledSection = nav[0]; + expect.assert(filledSection?.type === "section"); + expect(filledSection.title).toBe("filled"); }); it("does not include root module as a node (root is section overview)", () => { @@ -276,8 +277,8 @@ describe("buildNavigation", () => { }); const nav = buildNavigation(root, "reference/python"); // "core" gets wrapped in a section - expect(nav[0]?.type).toBe("section"); - const section = nav[0] as { type: "section"; slug: string; children: NavNode[] }; + const section = nav[0]; + expect.assert(section?.type === "section"); expect(section.slug).toBe("reference/python/mylib/core"); // Inner page has same slug + pageId expect(section.children[0]).toEqual({ @@ -316,17 +317,14 @@ describe("buildNavigation", () => { // a -> section(b) -> section(c) -> section(d) -> page(d) expect(nav).toHaveLength(1); - // biome-ignore lint/style/noNonNullAssertion: length asserted above - const b = nav[0]! as { type: "section"; children: NavNode[] }; - expect(b.type).toBe("section"); + const b = nav[0]; + expect.assert(b?.type === "section"); expect(b.children).toHaveLength(1); - // biome-ignore lint/style/noNonNullAssertion: length asserted above - const c = b.children[0]! as { type: "section"; children: NavNode[] }; - expect(c.type).toBe("section"); + const c = b.children[0]; + expect.assert(c?.type === "section"); expect(c.children).toHaveLength(1); - // biome-ignore lint/style/noNonNullAssertion: length asserted above - const d = c.children[0]! as { type: "section"; title: string; children: NavNode[] }; - expect(d.type).toBe("section"); + const d = c.children[0]; + expect.assert(d?.type === "section"); expect(d.title).toBe("d"); expect(d.children).toHaveLength(1); expect(d.children[0]?.type).toBe("page"); @@ -356,8 +354,8 @@ describe("buildNavigation", () => { const nav = buildNavigation(root, "ref"); expect(nav).toHaveLength(1); // Wrapped in section, but inner page exists (docstring = content) - expect(nav[0]?.type).toBe("section"); - const section = nav[0] as { type: "section"; children: NavNode[] }; + const section = nav[0]; + expect.assert(section?.type === "section"); expect(section.children).toHaveLength(1); expect(section.children[0]?.type).toBe("page"); }); diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts index 3d666ca7cf29..07ba2f3bf5ca 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/openapi-from-flag.test.ts @@ -2883,19 +2883,17 @@ describe("OpenAPI v3 Parser Pipeline (--from-openapi flag)", () => { // Validate the basic auth scheme structure const basicAuthScheme = intermediateRepresentation.auth.schemes[0]; expect(basicAuthScheme).toBeDefined(); - expect(basicAuthScheme?.type).toBe("basic"); + expect.assert(basicAuthScheme?.type === "basic"); // Verify that x-fern-basic custom names are correctly flowing through to the IR // The OpenAPI spec has x-fern-basic with username.name="project_id" and password.name="api_token" - if (basicAuthScheme?.type === "basic") { - // Verify custom names from x-fern-basic are used - expect(basicAuthScheme.username.originalName).toBe("project_id"); - expect(basicAuthScheme.password.originalName).toBe("api_token"); - - // Verify env vars are also passed through - expect(basicAuthScheme.usernameEnvVar).toBe("PLANT_STORE_PROJECT_ID"); - expect(basicAuthScheme.passwordEnvVar).toBe("PLANT_STORE_API_TOKEN"); - } + // Verify custom names from x-fern-basic are used + expect(basicAuthScheme.username.originalName).toBe("project_id"); + expect(basicAuthScheme.password.originalName).toBe("api_token"); + + // Verify env vars are also passed through + expect(basicAuthScheme.usernameEnvVar).toBe("PLANT_STORE_PROJECT_ID"); + expect(basicAuthScheme.passwordEnvVar).toBe("PLANT_STORE_API_TOKEN"); // Validate FDR auth schemes expect(fdrApiDefinition.authSchemes).toBeDefined(); @@ -2903,12 +2901,10 @@ describe("OpenAPI v3 Parser Pipeline (--from-openapi flag)", () => { expect(fdrAuthSchemes).toBeDefined(); if (fdrAuthSchemes) { const basicAuth = Object.values(fdrAuthSchemes).find((scheme) => scheme.type === "basicAuth"); - expect(basicAuth).toBeDefined(); - if (basicAuth?.type === "basicAuth") { - // Verify custom names from x-fern-basic flow through to FDR - expect(basicAuth.usernameName).toBe("project_id"); - expect(basicAuth.passwordName).toBe("api_token"); - } + expect.assert(basicAuth?.type === "basicAuth"); + // Verify custom names from x-fern-basic flow through to FDR + expect(basicAuth.usernameName).toBe("project_id"); + expect(basicAuth.passwordName).toBe("api_token"); } // Snapshot the complete output for regression testing diff --git a/packages/commons/ir-utils/src/__test__/generateTypeReferenceExample.test.ts b/packages/commons/ir-utils/src/__test__/generateTypeReferenceExample.test.ts index 8dc21c84435e..835ad4d35fab 100644 --- a/packages/commons/ir-utils/src/__test__/generateTypeReferenceExample.test.ts +++ b/packages/commons/ir-utils/src/__test__/generateTypeReferenceExample.test.ts @@ -97,15 +97,13 @@ describe("v1 cycle detection in generateTypeReferenceExample", () => { const elapsed = Date.now() - start; expect(elapsed).toBeLessThan(1000); - expect(result.type).toBe("success"); - if (result.type === "success") { - const json = result.jsonExample as Record; - expect(json).toHaveProperty("name"); - for (const field of selfRefFields) { - const nested = json[field] as Record; - expect(nested).toBeDefined(); - expect(nested.name).toBe("name"); - } + expect.assert(result.type === "success"); + const json = result.jsonExample as Record; + expect(json).toHaveProperty("name"); + for (const field of selfRefFields) { + const nested = json[field] as Record; + expect(nested).toBeDefined(); + expect(nested.name).toBe("name"); } }); @@ -126,16 +124,14 @@ describe("v1 cycle detection in generateTypeReferenceExample", () => { // Now succeeds: first visit generates the object, inner "self" property // hits cycle limit and gets a stub empty object instead of failing. // The stub itself is an object with a "self" property that is also a stub. - expect(result.type).toBe("success"); - if (result.type === "success") { - const json = result.jsonExample as Record; - expect(json).toHaveProperty("self"); - const inner = json.self as Record; - // The inner stub also has "self" because it recursed one more level - // before hitting the limit. At the deepest level, "self" is an empty stub. - expect(inner).toHaveProperty("self"); - expect(inner.self).toEqual({}); - } + expect.assert(result.type === "success"); + const json = result.jsonExample as Record; + expect(json).toHaveProperty("self"); + const inner = json.self as Record; + // The inner stub also has "self" because it recursed one more level + // before hitting the limit. At the deepest level, "self" is an empty stub. + expect(inner).toHaveProperty("self"); + expect(inner.self).toEqual({}); }); it("should detect triangle cycle (A -> B -> C -> A) with optional back-edge", () => { @@ -154,38 +150,36 @@ describe("v1 cycle detection in generateTypeReferenceExample", () => { skipOptionalProperties: false }); - expect(result.type).toBe("success"); - if (result.type === "success") { - const json = result.jsonExample as Record; - expect(json).toHaveProperty("value"); - expect(json).toHaveProperty("toB"); - const toB = json.toB as Record; - expect(toB).toHaveProperty("value"); - expect(toB).toHaveProperty("toC"); - const toC = toB.toC as Record; - expect(toC).toHaveProperty("value"); - expect(toC).toHaveProperty("toA"); - const toA = toC.toA as Record; - expect(toA).toHaveProperty("value"); - expect(toA).toHaveProperty("toB"); - const toB2 = toA.toB as Record; - expect(toB2).toHaveProperty("value"); - expect(toB2).toHaveProperty("toC"); - const toC2 = toB2.toC as Record; - expect(toC2).toHaveProperty("value"); - // toA gets a stub with leaf properties and non-leaf named properties filled in recursively. - // The visited set prevents infinite recursion (A is visited, so toA inside C's minimal stub - // won't recurse back into A again). - expect(toC2.toA).toEqual({ + expect.assert(result.type === "success"); + const json = result.jsonExample as Record; + expect(json).toHaveProperty("value"); + expect(json).toHaveProperty("toB"); + const toB = json.toB as Record; + expect(toB).toHaveProperty("value"); + expect(toB).toHaveProperty("toC"); + const toC = toB.toC as Record; + expect(toC).toHaveProperty("value"); + expect(toC).toHaveProperty("toA"); + const toA = toC.toA as Record; + expect(toA).toHaveProperty("value"); + expect(toA).toHaveProperty("toB"); + const toB2 = toA.toB as Record; + expect(toB2).toHaveProperty("value"); + expect(toB2).toHaveProperty("toC"); + const toC2 = toB2.toC as Record; + expect(toC2).toHaveProperty("value"); + // toA gets a stub with leaf properties and non-leaf named properties filled in recursively. + // The visited set prevents infinite recursion (A is visited, so toA inside C's minimal stub + // won't recurse back into A again). + expect(toC2.toA).toEqual({ + value: "value", + toB: { value: "value", - toB: { - value: "value", - toC: { - value: "value" - } + toC: { + value: "value" } - }); - } + } + }); }); it("should allow same type in sibling branches (backtracking correctness)", () => { @@ -203,12 +197,10 @@ describe("v1 cycle detection in generateTypeReferenceExample", () => { skipOptionalProperties: false }); - expect(result.type).toBe("success"); - if (result.type === "success") { - const json = result.jsonExample as Record; - expect(json.child1).toEqual({ data: "data" }); - expect(json.child2).toEqual({ data: "data" }); - } + expect.assert(result.type === "success"); + const json = result.jsonExample as Record; + expect(json.child1).toEqual({ data: "data" }); + expect(json.child2).toEqual({ data: "data" }); }); it("should generate stubs for 3-way cycle with required fields (BulkSchedule-like)", () => { @@ -244,22 +236,20 @@ describe("v1 cycle detection in generateTypeReferenceExample", () => { skipOptionalProperties: false }); - expect(result.type).toBe("success"); - if (result.type === "success") { - const json = result.jsonExample as Record; - expect(json).toHaveProperty("frequency"); - expect(json).toHaveProperty("multi"); - const multi = json.multi as Record; - expect(multi).toHaveProperty("schedules"); - const schedules = multi.schedules as Array>; - // The list should have items (not be empty) because ItemSchedule.schedule - // now gets a stub with leaf properties instead of failing - expect(schedules.length).toBeGreaterThan(0); - expect(schedules[0]).toHaveProperty("item"); - expect(schedules[0]).toHaveProperty("schedule"); - // The stub for BulkSchedule at cycle limit includes its leaf property "frequency" - const stubSchedule = schedules[0]?.schedule as Record; - expect(stubSchedule).toHaveProperty("frequency"); - } + expect.assert(result.type === "success"); + const json = result.jsonExample as Record; + expect(json).toHaveProperty("frequency"); + expect(json).toHaveProperty("multi"); + const multi = json.multi as Record; + expect(multi).toHaveProperty("schedules"); + const schedules = multi.schedules as Array>; + // The list should have items (not be empty) because ItemSchedule.schedule + // now gets a stub with leaf properties instead of failing + expect(schedules.length).toBeGreaterThan(0); + expect(schedules[0]).toHaveProperty("item"); + expect(schedules[0]).toHaveProperty("schedule"); + // The stub for BulkSchedule at cycle limit includes its leaf property "frequency" + const stubSchedule = schedules[0]?.schedule as Record; + expect(stubSchedule).toHaveProperty("frequency"); }); }); diff --git a/packages/commons/logging-execa/src/loggingExeca.ts b/packages/commons/logging-execa/src/loggingExeca.ts index c7c991f03cc4..20c931d6ed5a 100644 --- a/packages/commons/logging-execa/src/loggingExeca.ts +++ b/packages/commons/logging-execa/src/loggingExeca.ts @@ -6,17 +6,46 @@ export declare namespace loggingExeca { doNotPipeOutput?: boolean; secrets?: string[]; substitutions?: Record; + /** AbortSignal to kill the child process on timeout/bail/Ctrl+C */ + signal?: AbortSignal; } export type ReturnValue = ExecaReturnValue; } +/** + * Wire an AbortSignal to kill an execa child process. + * When the signal aborts (e.g. on test timeout or Ctrl+C), the child + * process is terminated so it doesn't leak. + */ +function wireSignal(childProcess: import("execa").ExecaChildProcess, signal?: AbortSignal): void { + if (!signal) { + return; + } + // Proactively swallow any kill-related rejection so that neither + // signal-triggered kills nor manual `.kill()` calls surface as + // unhandled rejections in Vitest. + // biome-ignore lint/suspicious/noEmptyBlockStatements: intentionally swallow rejection + childProcess.catch(() => {}); + + if (signal.aborted) { + childProcess.kill(); + return; + } + const onAbort = (): void => { + childProcess.kill(); + }; + signal.addEventListener("abort", onAbort, { once: true }); + // biome-ignore lint/suspicious/noEmptyBlockStatements: swallow rejection from .finally() chain + void childProcess.finally(() => signal.removeEventListener("abort", onAbort)).catch(() => {}); +} + // returns the current command being run by execa export function runExeca( logger: Logger | undefined, executable: string, args: string[] = [], - { doNotPipeOutput = false, secrets = [], substitutions = {}, ...execaOptions }: loggingExeca.Options = {} + { doNotPipeOutput = false, secrets = [], substitutions = {}, signal, ...execaOptions }: loggingExeca.Options = {} ): import("execa").ExecaChildProcess { const allSubstitutions = secrets.reduce( (acc, secret) => ({ @@ -32,7 +61,9 @@ export function runExeca( } logger?.debug(`+ ${logLine}`); - return execa(executable, args, execaOptions); + const childProcess = execa(executable, args, execaOptions); + wireSignal(childProcess, signal); + return childProcess; } // finishes executing the command and returns the result @@ -40,9 +71,15 @@ export async function loggingExeca( logger: Logger | undefined, executable: string, args: string[] = [], - { doNotPipeOutput = false, secrets = [], substitutions = {}, ...execaOptions }: loggingExeca.Options = {} + { doNotPipeOutput = false, secrets = [], substitutions = {}, signal, ...execaOptions }: loggingExeca.Options = {} ): Promise { - const command = runExeca(logger, executable, args, { doNotPipeOutput, secrets, substitutions, ...execaOptions }); + const command = runExeca(logger, executable, args, { + doNotPipeOutput, + secrets, + substitutions, + signal, + ...execaOptions + }); if (!doNotPipeOutput) { command.stdout?.pipe(process.stdout); command.stderr?.pipe(process.stderr); diff --git a/packages/configs/vitest/base.mjs b/packages/configs/vitest/base.mjs index 9df455cad690..48edcfdffbcf 100644 --- a/packages/configs/vitest/base.mjs +++ b/packages/configs/vitest/base.mjs @@ -12,6 +12,7 @@ export const defaultConfig = { fallbackCJS: true } }, + reporters: process.env.CI ? [["default", { summary: false }], "github-actions"] : ["default"], maxConcurrency: 10, passWithNoTests: true } diff --git a/scripts/live-test.sh b/scripts/live-test.sh index dd55154e870f..0bbee79e9e0a 100755 --- a/scripts/live-test.sh +++ b/scripts/live-test.sh @@ -15,7 +15,7 @@ if [ "$test_tarball" = "true" ]; then echo "Creating tarball from $cli_dir..." cd "$cli_dir" - tarball_path="$(npm pack 2>&1 | tail -n 1)" + tarball_path="$(pnpm pack 2>&1 | tail -n 1)" echo "Created tarball: $tarball_path" install_dir="$(mktemp -d)" diff --git a/seed/postman/literal/collection.json b/seed/postman/literal/collection.json index 3ebda8a08cab..7356c241e1a9 100644 --- a/seed/postman/literal/collection.json +++ b/seed/postman/literal/collection.json @@ -448,7 +448,8 @@ { "key": "optional_prompt", "description": null, - "value": "You are a helpful assistant" + "value": "You are a helpful assistant", + "disabled": true }, { "key": "alias_prompt", @@ -458,7 +459,8 @@ { "key": "alias_optional_prompt", "description": null, - "value": "You are a helpful assistant" + "value": "You are a helpful assistant", + "disabled": true }, { "key": "stream", @@ -468,7 +470,8 @@ { "key": "optional_stream", "description": null, - "value": "false" + "value": "false", + "disabled": true }, { "key": "alias_stream", @@ -478,7 +481,8 @@ { "key": "alias_optional_stream", "description": null, - "value": "false" + "value": "false", + "disabled": true }, { "key": "query", @@ -523,7 +527,8 @@ { "key": "optional_prompt", "description": null, - "value": "You are a helpful assistant" + "value": "You are a helpful assistant", + "disabled": true }, { "key": "alias_prompt", @@ -533,7 +538,8 @@ { "key": "alias_optional_prompt", "description": null, - "value": "You are a helpful assistant" + "value": "You are a helpful assistant", + "disabled": true }, { "key": "stream", @@ -543,7 +549,8 @@ { "key": "optional_stream", "description": null, - "value": "false" + "value": "false", + "disabled": true }, { "key": "alias_stream", @@ -553,7 +560,8 @@ { "key": "alias_optional_stream", "description": null, - "value": "false" + "value": "false", + "disabled": true }, { "key": "query", @@ -601,7 +609,8 @@ { "key": "optional_prompt", "description": null, - "value": "You are a helpful assistant" + "value": "You are a helpful assistant", + "disabled": true }, { "key": "alias_prompt", @@ -611,7 +620,8 @@ { "key": "alias_optional_prompt", "description": null, - "value": "You are a helpful assistant" + "value": "You are a helpful assistant", + "disabled": true }, { "key": "query", @@ -626,7 +636,8 @@ { "key": "optional_stream", "description": null, - "value": "false" + "value": "false", + "disabled": true }, { "key": "alias_stream", @@ -636,7 +647,8 @@ { "key": "alias_optional_stream", "description": null, - "value": "false" + "value": "false", + "disabled": true } ], "variable": [] diff --git a/seed/postman/mixed-file-directory/collection.json b/seed/postman/mixed-file-directory/collection.json index b7674646a780..a5367928ff53 100644 --- a/seed/postman/mixed-file-directory/collection.json +++ b/seed/postman/mixed-file-directory/collection.json @@ -211,7 +211,8 @@ { "key": "limit", "description": "The maximum number of results to return.", - "value": "1" + "value": "1", + "disabled": true } ], "variable": [] @@ -247,7 +248,8 @@ { "key": "limit", "description": "The maximum number of results to return.", - "value": "1" + "value": "1", + "disabled": true } ], "variable": [] @@ -288,7 +290,8 @@ { "key": "limit", "description": "The maximum number of results to return.", - "value": "1" + "value": "1", + "disabled": true } ], "variable": [] @@ -323,7 +326,8 @@ { "key": "limit", "description": "The maximum number of results to return.", - "value": "1" + "value": "1", + "disabled": true } ], "variable": [] diff --git a/seed/python-sdk/examples/legacy-wire-tests/.github/workflows/ci.yml b/seed/python-sdk/examples/legacy-wire-tests/.github/workflows/ci.yml index c0ea1de49022..2f7568f21e03 100644 --- a/seed/python-sdk/examples/legacy-wire-tests/.github/workflows/ci.yml +++ b/seed/python-sdk/examples/legacy-wire-tests/.github/workflows/ci.yml @@ -32,8 +32,8 @@ jobs: - name: Install dependencies run: poetry install - - name: Install Fern - run: npm install -g fern-api + - name: Install Fern CLI + uses: fern-api/setup-fern-cli@v1 - name: Test run: fern test --command "poetry run pytest -rP -n auto ." diff --git a/turbo.jsonc b/turbo.jsonc index 1ed5d7e88c74..dea1e97b4e26 100644 --- a/turbo.jsonc +++ b/turbo.jsonc @@ -44,7 +44,7 @@ "$TURBO_ROOT$/test-definitions-openapi/**", "$TURBO_ROOT$/test-definitions/**", "$TURBO_ROOT$/tsconfig.eslint.json", - "$TURBO_ROOT$/vitest.workspace.ts", + "$TURBO_ROOT$/vitest.projects.config.ts", "src/**", "tests/**", "tsconfig.json", @@ -59,7 +59,7 @@ "$TURBO_ROOT$/test-definitions-openapi/**", "$TURBO_ROOT$/test-definitions/**", "$TURBO_ROOT$/tsconfig.eslint.json", - "$TURBO_ROOT$/vitest.workspace.ts", + "$TURBO_ROOT$/vitest.projects.config.ts", "src/**", "tests/**", "tsconfig.json", @@ -74,7 +74,7 @@ "$TURBO_ROOT$/test-definitions-openapi/**", "$TURBO_ROOT$/test-definitions/**", "$TURBO_ROOT$/tsconfig.eslint.json", - "$TURBO_ROOT$/vitest.workspace.ts", + "$TURBO_ROOT$/vitest.projects.config.ts", "src/**", "tests/**", "tsconfig.json", @@ -174,23 +174,12 @@ "with": ["dist", "dist:cli", "dist:cli:dev", "dist:cli:local", "dist:cli:prod"] }, "dockerTagLatest": { - "outputs": [], - "dependsOn": ["dist:cli"], - "inputs": ["dist/**", "Dockerfile", "Dockerfile.*"] + "cache": false, + "dependsOn": ["dist:cli"] }, "podmanTagLatest": { - "outputs": [], - "dependsOn": ["dist:cli"], - "inputs": [ - "Dockerfile", - "$TURBO_ROOT$/shared/.prettierignore", - "$TURBO_ROOT$/shared/stylelintrc.shared.json", - "$TURBO_ROOT$/packages/configs/**", - "src/**", - "tests/**", - "package.json", - "tsconfig.json" - ] + "cache": false, + "dependsOn": ["dist:cli"] } } } diff --git a/vitest.projects.config.ts b/vitest.projects.config.ts new file mode 100644 index 000000000000..d0114c06a01d --- /dev/null +++ b/vitest.projects.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + projects: ["./generators/**/vitest.config.ts", "./packages/**/vitest.config.ts"] + } +}); diff --git a/vitest.workspace.ts b/vitest.workspace.ts deleted file mode 100644 index e8156483e1ab..000000000000 --- a/vitest.workspace.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { defineWorkspace } from "vitest/config"; - -export default defineWorkspace(["./generators/**/vitest.config.ts", "./packages/**/vitest.config.ts"]);