From cfab440f66cb795d7643ec6ec18f0eb63eec51bb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 11 Dec 2025 03:53:47 +0000 Subject: [PATCH 1/8] feat: Add turboExtendsKeyword future flag and $TURBO_EXTENDS$ microsyntax Co-authored-by: anthony.shew --- .../content/docs/reference/configuration.mdx | 74 +++++++++++++++++++ .../docs/reference/package-configurations.mdx | 7 ++ 2 files changed, 81 insertions(+) diff --git a/docs/site/content/docs/reference/configuration.mdx b/docs/site/content/docs/reference/configuration.mdx index 3fabffde8b6fa..8a43f5facc180 100644 --- a/docs/site/content/docs/reference/configuration.mdx +++ b/docs/site/content/docs/reference/configuration.mdx @@ -187,6 +187,30 @@ Turborepo's Environment Modes allow you to control which environment variables a Read more about [Environment Modes](/docs/crafting-your-repository/using-environment-variables#environment-modes). +### `futureFlags` + +```jsonc title="./turbo.json" +{ + "futureFlags": { + "turboExtendsKeyword": true + } +} +``` + +Enable experimental features that will become the default behavior in future versions of Turborepo. + + + `futureFlags` can only be set in the root `turbo.json`. An error will be + thrown if set in a [Package + Configuration](/docs/reference/package-configurations). + + +#### `turboExtendsKeyword` + +Default: `false` + +When enabled, allows using the [`$TURBO_EXTENDS$`](#turbo_extends) microsyntax in array fields. This microsyntax changes the behavior of [Package Configurations](/docs/reference/package-configurations) from replacing array fields to appending to them. + ### `tags` Experimental ```jsonc title="./apps/web/turbo.json" @@ -476,6 +500,56 @@ Starting a file glob with `$TURBO_ROOT$` will change the glob to be relative to } ``` +#### `$TURBO_EXTENDS$` + + + This feature requires enabling + [`futureFlags.turboExtendsKeyword`](#turboextendskeyword) in your root + `turbo.json`. + + +When using [Package Configurations](/docs/reference/package-configurations), array fields normally completely replace the values from the root `turbo.json`. The `$TURBO_EXTENDS$` microsyntax changes this behavior to append instead of replace. + +This microsyntax can be used in the following array fields: + +- `dependsOn` +- `env` +- `inputs` +- `outputs` +- `passThroughEnv` +- `with` + +For example, if your root `turbo.json` defines: + +```jsonc title="./turbo.json" +{ + "futureFlags": { + "turboExtendsKeyword": true + }, + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} +``` + +A Package Configuration can add additional outputs while keeping the root outputs: + +```jsonc title="./apps/web/turbo.json" +{ + "extends": ["//"], + "tasks": { + "build": { + // Inherits "dist/**" from root, and adds ".next/**" + "outputs": ["$TURBO_EXTENDS$", ".next/**", "!.next/cache/**"] + } + } +} +``` + +Without `$TURBO_EXTENDS$`, the `outputs` array would be completely replaced with `[".next/**", "!.next/cache/**"]`, losing the `"dist/**"` from the root configuration. + ### `outputLogs` Default: `full` diff --git a/docs/site/content/docs/reference/package-configurations.mdx b/docs/site/content/docs/reference/package-configurations.mdx index bc33ed0928670..a6dabd4ab463d 100644 --- a/docs/site/content/docs/reference/package-configurations.mdx +++ b/docs/site/content/docs/reference/package-configurations.mdx @@ -38,6 +38,13 @@ Configuration in a package can override any of [the configurations for a task](/docs/reference/configuration#defining-tasks). Any keys that are not included are inherited from the extended `turbo.json`. + + By default, array fields like `outputs` and `env` completely replace the + values from the root configuration. To append to inherited values instead of + replacing them, use the [`$TURBO_EXTENDS$` + microsyntax](/docs/reference/configuration#turbo_extends). + + ## Examples ### Different frameworks in one Workspace From 370ae58a19c8734af1c80d2d8eca14924958e60b Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 12 Dec 2025 10:49:46 -0700 Subject: [PATCH 2/8] clarity --- .../content/docs/reference/configuration.mdx | 4 ++-- .../docs/reference/package-configurations.mdx | 23 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/site/content/docs/reference/configuration.mdx b/docs/site/content/docs/reference/configuration.mdx index 8a43f5facc180..057cf7935034b 100644 --- a/docs/site/content/docs/reference/configuration.mdx +++ b/docs/site/content/docs/reference/configuration.mdx @@ -508,7 +508,7 @@ Starting a file glob with `$TURBO_ROOT$` will change the glob to be relative to `turbo.json`. -When using [Package Configurations](/docs/reference/package-configurations), array fields normally completely replace the values from the root `turbo.json`. The `$TURBO_EXTENDS$` microsyntax changes this behavior to append instead of replace. +When using [Package Configurations](/docs/reference/package-configurations), array fields normally completely replace the values from the root `turbo.json`. The `$TURBO_EXTENDS$` microsyntax changes this behavior to **append** instead of **replace**. This microsyntax can be used in the following array fields: @@ -548,7 +548,7 @@ A Package Configuration can add additional outputs while keeping the root output } ``` -Without `$TURBO_EXTENDS$`, the `outputs` array would be completely replaced with `[".next/**", "!.next/cache/**"]`, losing the `"dist/**"` from the root configuration. +Without `$TURBO_EXTENDS$`, the `outputs` array would be completely replaced with `[".next/**", "!.next/cache/**"]`, dropping the `"dist/**"` from the root configuration. ### `outputLogs` diff --git a/docs/site/content/docs/reference/package-configurations.mdx b/docs/site/content/docs/reference/package-configurations.mdx index a6dabd4ab463d..5814f2b252d02 100644 --- a/docs/site/content/docs/reference/package-configurations.mdx +++ b/docs/site/content/docs/reference/package-configurations.mdx @@ -38,12 +38,23 @@ Configuration in a package can override any of [the configurations for a task](/docs/reference/configuration#defining-tasks). Any keys that are not included are inherited from the extended `turbo.json`. - - By default, array fields like `outputs` and `env` completely replace the - values from the root configuration. To append to inherited values instead of - replacing them, use the [`$TURBO_EXTENDS$` - microsyntax](/docs/reference/configuration#turbo_extends). - +## Extending array fields with `$TURBO_EXTENDS$` + +By default, array fields like `outputs` and `env` **completely replace** the values from the root configuration. To **add** to inherited values instead, use the [`$TURBO_EXTENDS$` microsyntax](/docs/reference/configuration#turbo_extends): + +```jsonc title="./apps/my-app/turbo.json" +{ + "extends": ["//"], + "tasks": { + "build": { + // Inherits `outputs` from root turbo.json AND adds ".cache/**" + "outputs": ["$TURBO_EXTENDS$", ".cache/**"] + } + } +} +``` + +Without `$TURBO_EXTENDS$`, the `outputs` array above would completely override the root configuration's `outputs`. With `$TURBO_EXTENDS$`, the values from the root are preserved and `.cache/**` is appended. ## Examples From 06dd7fabc6edb8d1566420a69cc776b7358c26fb Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 12 Dec 2025 11:04:35 -0700 Subject: [PATCH 3/8] touchups --- .../content/docs/reference/configuration.mdx | 4 +- .../docs/reference/package-configurations.mdx | 90 ++++++++++--------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/docs/site/content/docs/reference/configuration.mdx b/docs/site/content/docs/reference/configuration.mdx index 057cf7935034b..4f4c5bf479417 100644 --- a/docs/site/content/docs/reference/configuration.mdx +++ b/docs/site/content/docs/reference/configuration.mdx @@ -500,7 +500,7 @@ Starting a file glob with `$TURBO_ROOT$` will change the glob to be relative to } ``` -#### `$TURBO_EXTENDS$` +#### `$TURBO_EXTENDS$` Pre-release This feature requires enabling @@ -508,7 +508,7 @@ Starting a file glob with `$TURBO_ROOT$` will change the glob to be relative to `turbo.json`. -When using [Package Configurations](/docs/reference/package-configurations), array fields normally completely replace the values from the root `turbo.json`. The `$TURBO_EXTENDS$` microsyntax changes this behavior to **append** instead of **replace**. +When using [Package Configurations](/docs/reference/package-configurations), array fields completely replace the values from the root `turbo.json` by default. The `$TURBO_EXTENDS$` microsyntax changes this behavior to **append** instead of **replace**. This microsyntax can be used in the following array fields: diff --git a/docs/site/content/docs/reference/package-configurations.mdx b/docs/site/content/docs/reference/package-configurations.mdx index 5814f2b252d02..64d6689f73231 100644 --- a/docs/site/content/docs/reference/package-configurations.mdx +++ b/docs/site/content/docs/reference/package-configurations.mdx @@ -34,27 +34,60 @@ key: special name used to identify the root directory of the monorepo. -Configuration in a package can override any of [the configurations for a -task](/docs/reference/configuration#defining-tasks). Any keys that are not included are inherited -from the extended `turbo.json`. +## Inheritance behavior -## Extending array fields with `$TURBO_EXTENDS$` +When a Package Configuration extends the root `turbo.json`, task properties are inherited differently depending on their type. -By default, array fields like `outputs` and `env` **completely replace** the values from the root configuration. To **add** to inherited values instead, use the [`$TURBO_EXTENDS$` microsyntax](/docs/reference/configuration#turbo_extends): +### Scalar fields are inherited + +Scalar fields like `outputLogs`, `cache`, `persistent`, and `interactive` are **inherited** from the root configuration. You only need to specify them in a Package Configuration if you want to override them. + +For example, if your root `turbo.json` sets `"outputLogs": "hash-only"` for a task, all packages inherit that setting automatically. + +### Array fields replace by default + +Array fields like `outputs`, `env`, `inputs`, `dependsOn`, and `passThroughEnv` **completely replace** the root configuration's values by default. + +```jsonc title="./turbo.json" +{ + "tasks": { + "build": { + "outputs": ["dist/**"], + "env": ["NODE_ENV"] + } + } +} +``` ```jsonc title="./apps/my-app/turbo.json" { "extends": ["//"], "tasks": { "build": { - // Inherits `outputs` from root turbo.json AND adds ".cache/**" - "outputs": ["$TURBO_EXTENDS$", ".cache/**"] + // This REPLACES the root outputs - "dist/**" is NOT included + "outputs": [".next/**"] } } } ``` -Without `$TURBO_EXTENDS$`, the `outputs` array above would completely override the root configuration's `outputs`. With `$TURBO_EXTENDS$`, the values from the root are preserved and `.cache/**` is appended. +### Extending arrays with `$TURBO_EXTENDS$` Pre-release + +To **add** to inherited array values instead of replacing them, use the [`$TURBO_EXTENDS$` microsyntax](/docs/reference/configuration#turbo_extends): + +```jsonc title="./apps/my-app/turbo.json" +{ + "extends": ["//"], + "tasks": { + "build": { + // Inherits "dist/**" from root AND adds ".next/**" + "outputs": ["$TURBO_EXTENDS$", ".next/**"] + } + } +} +``` + +The `$TURBO_EXTENDS$` marker must be the first element in the array. It works with `outputs`, `env`, `inputs`, `dependsOn`, `passThroughEnv`, and `with`. ## Examples @@ -161,40 +194,9 @@ but continue to inherit any other tasks defined at the root. ## Comparison to package-specific tasks -At first glance, Package Configurations may sound a lot like the -[`package#task` syntax](/docs/crafting-your-repository/configuring-tasks#depending-on-a-specific-task-in-a-specific-package) in the root `turbo.json`. The features are -similar, but have one significant difference: when you declare a package-specific task -in the root `turbo.json`, it _completely_ overwrites the baseline task -configuration. With a Package Configuration, the task configuration is merged -instead. - -Consider the example of the monorepo with multiple Next.js apps and a Sveltekit -app again. With a package-specific task, you might configure your root -`turbo.json` like this: - -```jsonc title="./turbo.json" -{ - "tasks": { - "build": { - "outputLogs": "hash-only", - "inputs": ["src/**"], - "outputs": [".next/**", "!.next/cache/**"] - }, - "my-sveltekit-app#build": { - "outputLogs": "hash-only", // must duplicate this - "inputs": ["src/**"], // must duplicate this - "outputs": [".svelte-kit/**"] - } - } -} -``` - -In this example, `my-sveltekit-app#build` completely overwrites `build` for the -Sveltekit app, so `outputLogs` and `inputs` also need to be duplicated. +The [`package#task` syntax](/docs/crafting-your-repository/configuring-tasks#depending-on-a-specific-task-in-a-specific-package) in the root `turbo.json` **completely overwrites** all task configuration—nothing is inherited. -With Package Configurations, `outputLogs` and `inputs` are inherited, so -you don't need to duplicate them. You only need to override `outputs` in -`my-sveltekit-app` config. +With Package Configurations, scalar fields are inherited and only the fields you specify are overridden. This means less duplication when you only need to change one or two properties. Although there are no plans to remove package-specific task configurations, we @@ -236,18 +238,18 @@ Package Configuration for `my-nextjs-app`: { "tasks": { "my-nextjs-app#build": { - // ❌ This is not allowed. Even though it's + // This is not allowed. Even though it's // referencing the correct package, "my-nextjs-app" // is inferred, and we don't need to specify it again. // This syntax also has different behavior, so we do not want to allow it. // (see "Comparison to package-specific tasks" section) }, "my-sveltekit-app#build": { - // ❌ Changing configuration for the "my-sveltekit-app" package + // Changing configuration for the "my-sveltekit-app" package // from Package Configuration in "my-nextjs-app" is not allowed. }, "build": { - // ✅ just use the task name! + // Just use the task name! } } } From a166fd3d7aeab091b23763e8688fffb68ed9dc28 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 12 Dec 2025 11:09:29 -0700 Subject: [PATCH 4/8] WIP 386c4 --- docs/link-checker/src/markdown.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/link-checker/src/markdown.ts b/docs/link-checker/src/markdown.ts index 1d06562455190..bb35f12649f7e 100644 --- a/docs/link-checker/src/markdown.ts +++ b/docs/link-checker/src/markdown.ts @@ -203,7 +203,8 @@ const validateHashLink = (doc: Document, href: string) => { // Because we're parsing the raw document (not the rendered output), the JSX declaration is still present. hashLink.replace( "-experimental", - "-experimentalbadgeexperimentalexperimentalbadge" + "-experimentalbadgeexperimentalexperimentalbadge", + "-experimentalbadgepre-releaseexperimentalbadge" ) ) ) { From b81ec93720a5b28823ee41431050cdf68007ad77 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 12 Dec 2025 11:12:44 -0700 Subject: [PATCH 5/8] WIP 7a3e6 --- docs/link-checker/src/markdown.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/link-checker/src/markdown.ts b/docs/link-checker/src/markdown.ts index bb35f12649f7e..33027571cf6c2 100644 --- a/docs/link-checker/src/markdown.ts +++ b/docs/link-checker/src/markdown.ts @@ -197,19 +197,26 @@ const validateHashLink = (doc: Document, href: string) => { return []; } - if ( - doc.headings.includes( - // Handles when the link has the experimental badge in it. - // Because we're parsing the raw document (not the rendered output), the JSX declaration is still present. - hashLink.replace( - "-experimental", - "-experimentalbadgeexperimentalexperimentalbadge", - "-experimentalbadgepre-releaseexperimentalbadge" - ) - ) - ) { + // Handles when the link has the experimental badge in it. + // Because we're parsing the raw document (not the rendered output), the JSX declaration is still present. + const experimentalHeading = hashLink.replace( + "-experimental", + "-experimentalbadgeexperimentalexperimentalbadge" + ); + const preReleaseHeading = + hashLink + "-experimentalbadgepre-releaseexperimentalbadge"; + + if (doc.headings.includes(experimentalHeading)) { + console.warn( + `The hash link "${hashLink}" passed when including the Experimental JSX declaration.` + ); + console.log(); + return []; + } + + if (doc.headings.includes(preReleaseHeading)) { console.warn( - `The hash link "${hashLink}" passed when including the JSX declaration.` + `The hash link "${hashLink}" passed when including the Pre-release JSX declaration.` ); console.log(); return []; From a72c4827b827a2793b24f1b5190b5bac183f6242 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 12 Dec 2025 11:15:05 -0700 Subject: [PATCH 6/8] WIP e2881 --- docs/site/content/docs/reference/package-configurations.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/site/content/docs/reference/package-configurations.mdx b/docs/site/content/docs/reference/package-configurations.mdx index 64d6689f73231..2aec3d6bda726 100644 --- a/docs/site/content/docs/reference/package-configurations.mdx +++ b/docs/site/content/docs/reference/package-configurations.mdx @@ -73,7 +73,7 @@ Array fields like `outputs`, `env`, `inputs`, `dependsOn`, and `passThroughEnv` ### Extending arrays with `$TURBO_EXTENDS$` Pre-release -To **add** to inherited array values instead of replacing them, use the [`$TURBO_EXTENDS$` microsyntax](/docs/reference/configuration#turbo_extends): +To **add** to inherited array values instead of replacing them, use the [`$TURBO_EXTENDS$` microsyntax](/docs/reference/configuration#turbo_extends-pre-release): ```jsonc title="./apps/my-app/turbo.json" { From 8c726f4b6aaa2eb51c1371a7a384fe16a33a0d18 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 12 Dec 2025 11:20:52 -0700 Subject: [PATCH 7/8] WIP 138cc --- docs/link-checker/src/markdown.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/link-checker/src/markdown.ts b/docs/link-checker/src/markdown.ts index 33027571cf6c2..72310de62e183 100644 --- a/docs/link-checker/src/markdown.ts +++ b/docs/link-checker/src/markdown.ts @@ -146,6 +146,31 @@ const prepareDocumentMapEntry = async ( } }; +/** Checks if the hash exists in a document's headings, accounting for ExperimentalBadge suffixes */ +const hashExistsInHeadings = (headings: string[], hash: string): boolean => { + if (headings.includes(hash)) { + return true; + } + + // Handle experimental badge suffix + const experimentalHeading = hash.replace( + "-experimental", + "-experimentalbadgeexperimentalexperimentalbadge" + ); + if (headings.includes(experimentalHeading)) { + return true; + } + + // Handle pre-release badge suffix + const preReleaseHeading = + hash + "-experimentalbadgepre-releaseexperimentalbadge"; + if (headings.includes(preReleaseHeading)) { + return true; + } + + return false; +}; + /** Checks if the links point to existing documents */ const validateInternalLink = (documentMap: Map) => (doc: Document, href: string) => { @@ -172,7 +197,7 @@ const validateInternalLink = }); } else if (hash && !EXCLUDED_HASHES.includes(hash)) { // Check if the hash link points to an existing section within the document - const hashFound = foundPage.headings.includes(hash); + const hashFound = hashExistsInHeadings(foundPage.headings, hash); if (!hashFound) { errors.push({ From ef43b1377714e0fa1a78d9141f71c85758544215 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 12 Dec 2025 11:24:23 -0700 Subject: [PATCH 8/8] WIP 91355 --- docs/link-checker/src/markdown.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/link-checker/src/markdown.ts b/docs/link-checker/src/markdown.ts index 72310de62e183..efaade3bb7528 100644 --- a/docs/link-checker/src/markdown.ts +++ b/docs/link-checker/src/markdown.ts @@ -51,7 +51,9 @@ const slugger = new GitHubSlugger(); /** Collect the paths of all .mdx files we care about */ const getAllMdxFilePaths = async (): Promise => { const allFiles = await fs.readdir(DOCS_PATH, { recursive: true }); - return allFiles.filter((file) => file.endsWith(".mdx")); + return allFiles.filter( + (file) => file.endsWith(".mdx") && !file.startsWith("openapi/") + ); }; // Returns the slugs of all headings in a tree @@ -152,7 +154,7 @@ const hashExistsInHeadings = (headings: string[], hash: string): boolean => { return true; } - // Handle experimental badge suffix + // Handle experimental badge suffix: -experimental -> -experimentalbadgeexperimentalexperimentalbadge const experimentalHeading = hash.replace( "-experimental", "-experimentalbadgeexperimentalexperimentalbadge" @@ -161,9 +163,11 @@ const hashExistsInHeadings = (headings: string[], hash: string): boolean => { return true; } - // Handle pre-release badge suffix - const preReleaseHeading = - hash + "-experimentalbadgepre-releaseexperimentalbadge"; + // Handle pre-release badge suffix: -pre-release -> -experimentalbadgepre-releaseexperimentalbadge + const preReleaseHeading = hash.replace( + "-pre-release", + "-experimentalbadgepre-releaseexperimentalbadge" + ); if (headings.includes(preReleaseHeading)) { return true; }