Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 50 additions & 13 deletions docs/link-checker/src/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ const slugger = new GitHubSlugger();
/** Collect the paths of all .mdx files we care about */
const getAllMdxFilePaths = async (): Promise<string[]> => {
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
Expand Down Expand Up @@ -146,6 +148,33 @@ 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: -experimental -> -experimentalbadgeexperimentalexperimentalbadge
const experimentalHeading = hash.replace(
"-experimental",
"-experimentalbadgeexperimentalexperimentalbadge"
);
if (headings.includes(experimentalHeading)) {
return true;
}

// Handle pre-release badge suffix: -pre-release -> -experimentalbadgepre-releaseexperimentalbadge
const preReleaseHeading = hash.replace(
"-pre-release",
"-experimentalbadgepre-releaseexperimentalbadge"
);
if (headings.includes(preReleaseHeading)) {
return true;
}

return false;
};

/** Checks if the links point to existing documents */
const validateInternalLink =
(documentMap: Map<string, Document>) => (doc: Document, href: string) => {
Expand All @@ -172,7 +201,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({
Expand All @@ -197,18 +226,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"
)
)
) {
// 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 <ExperimentalBadge>Experimental</ExperimentalBadge> JSX declaration.`
);
console.log();
return [];
}

if (doc.headings.includes(preReleaseHeading)) {
console.warn(
`The hash link "${hashLink}" passed when including the <ExperimentalBadge /> JSX declaration.`
`The hash link "${hashLink}" passed when including the <ExperimentalBadge>Pre-release</ExperimentalBadge> JSX declaration.`
);
console.log();
return [];
Expand Down
74 changes: 74 additions & 0 deletions docs/site/content/docs/reference/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<Callout type="info">
`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).
</Callout>

#### `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` <ExperimentalBadge>Experimental</ExperimentalBadge>

```jsonc title="./apps/web/turbo.json"
Expand Down Expand Up @@ -476,6 +500,56 @@ Starting a file glob with `$TURBO_ROOT$` will change the glob to be relative to
}
```

#### `$TURBO_EXTENDS$` <ExperimentalBadge>Pre-release</ExperimentalBadge>

<Callout type="info">
This feature requires enabling
[`futureFlags.turboExtendsKeyword`](#turboextendskeyword) in your root
`turbo.json`.
</Callout>

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:

- `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/**"]`, dropping the `"dist/**"` from the root configuration.

### `outputLogs`

Default: `full`
Expand Down
98 changes: 59 additions & 39 deletions docs/site/content/docs/reference/package-configurations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,60 @@ key:
special name used to identify the root directory of the monorepo.
</Callout>

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

When a Package Configuration extends the root `turbo.json`, task properties are inherited differently depending on their type.

### 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": {
// This REPLACES the root outputs - "dist/**" is NOT included
"outputs": [".next/**"]
}
}
}
```

### Extending arrays with `$TURBO_EXTENDS$` <ExperimentalBadge>Pre-release</ExperimentalBadge>

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"
{
"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

Expand Down Expand Up @@ -143,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.

<Callout type="info">
Although there are no plans to remove package-specific task configurations, we
Expand Down Expand Up @@ -218,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!
}
}
}
Expand Down
Loading