Skip to content

Commit 1ad2f96

Browse files
docs(nx-dev): render migration docs from the documentation key (#36377)
> [!NOTE] > `@nx/react-native` declares a `documentation` file that its build never copies into the published package. That is a packaging bug rather than a rendering one, and it is fixed separately in #36378. The two PRs are independent and can merge in any order. ## Current Behavior The migrations reference pages inline `<implementation>.md` whenever a file with that name happens to sit next to a migration's implementation, instead of reading the `documentation` key the entry declares. Two things fall out of that guess: - The eslint `update-23-1-0-convert-to-flat-config` migration ships a `prompt` file whose basename matches its implementation, so its LLM runbook ("ESLint v9 Flat Config Migration Instructions for LLM") renders as public documentation on the eslint migrations page. - Prompt-only migrations never match the guess, since it keys off the implementation path. The docs declared by `update-23-1-0-create-ai-instructions-for-next-15` and `update-23-1-0-create-ai-instructions-for-react-19` therefore never render, even though both entries separate their agent `prompt` from a user-facing `documentation` file. ## Expected Behavior The pages read the `documentation` key and no longer guess from the implementation basename. The runbook is gone from the eslint page, and the two prompt-only migrations render the docs they declare. The two jest setup-file migrations relied on the guess to render their docs, so they now declare `documentation` explicitly. Every other migration that rendered through the guess declares the key, so the runbook is the only content any page loses. ## Related Issue(s) Fixes NXC-4712 <!-- polygraph-session-start --> --- [View session information ↗](https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/fix-migration-docs-3961141b) <!-- polygraph-session-end --> --------- Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
1 parent 9d9e64d commit 1ad2f96

6 files changed

Lines changed: 62 additions & 15 deletions

File tree

astro-docs/src/plugins/utils/generate-plugin-markdown.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,8 @@ export function generateMigrationItem(name: string, item: any): string {
2929
}
3030
}
3131

32-
if (config.fullPath) {
33-
const maybeExampleMdFile = config.fullPath + '.md';
34-
if (existsSync(maybeExampleMdFile)) {
35-
const rawContent = readFileSync(maybeExampleMdFile, 'utf-8');
36-
markdown += `${rawContent}\n\n`;
37-
}
32+
if (config.documentationPath && existsSync(config.documentationPath)) {
33+
markdown += `${readFileSync(config.documentationPath, 'utf-8')}\n\n`;
3834
}
3935

4036
return markdown;

astro-docs/src/plugins/utils/plugin-schema-parser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@ export function parseMigrations(pluginPath: string): Map<string, any> | null {
133133
for (const [name, config] of Object.entries(
134134
migrationsJson.generators || {}
135135
) as [string, any][]) {
136-
if (config.implementation || config.factory) {
137-
config['fullPath'] = resolvePath(
136+
if (config.documentation) {
137+
config['documentationPath'] = resolvePath(
138138
pluginPath,
139-
config.implementation || config.factory
139+
config.documentation
140140
);
141141
}
142142

packages/jest/migrations.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,14 @@
6565
"migrate-jest-executor-setup-file": {
6666
"version": "23.0.0-beta.22",
6767
"description": "Migrate the deprecated `setupFile` option of the `@nx/jest:jest` executor: push the file path into `setupFilesAfterEnv` in the project's Jest config and remove the option from `project.json` and `nx.json` target defaults.",
68-
"implementation": "./dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file"
68+
"implementation": "./dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file",
69+
"documentation": "./dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.md"
6970
},
7071
"migrate-jest-configuration-skip-setup-file": {
7172
"version": "23.0.0-beta.22",
7273
"description": "Migrate the deprecated `skipSetupFile` option of the `@nx/jest:configuration` generator stored as a default in `nx.json` or per-project `project.json` to `setupFile: 'none'` (when `true`) or remove it (when `false`).",
73-
"implementation": "./dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file"
74+
"implementation": "./dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file",
75+
"documentation": "./dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.md"
7476
},
7577
"update-23-0-0-migrate-create-nodes-v2-import": {
7678
"version": "23.0.0-beta.24",

packages/jest/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Migrates the previously deprecated `skipSetupFile` option of the `@nx/jest:confi
55
- `skipSetupFile: true` becomes `setupFile: 'none'` (preserving the original behavior of skipping the setup file). Existing `setupFile` values are left untouched.
66
- `skipSetupFile: false` is dropped (it was a no-op).
77

8-
Both flat (`@nx/jest:configuration`) and nested (`@nx/jest` `configuration`) forms are handled.
8+
Both flat (`@nx/jest:configuration`) and nested (`@nx/jest` -> `configuration`) forms are handled.
99

1010
#### Examples
1111

@@ -88,4 +88,4 @@ Rewrite a per-project generator default:
8888
}
8989
```
9090

91-
The nested form (`@nx/jest` `configuration`) is handled the same way.
91+
The nested form (`@nx/jest` -> `configuration`) is handled the same way.

packages/jest/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Migrates the previously deprecated `setupFile` option of the `@nx/jest:jest` executor. The setup file path is appended to the `setupFilesAfterEnv` array in the project's Jest configuration (using `<rootDir>/...` form), and the deprecated option is removed from `project.json` and `nx.json` target defaults.
44

5-
If the Jest configuration cannot be parsed automatically (e.g. it exports a factory function or assigns `setupFilesAfterEnv` to a non-array value), the deprecated option is still removed and a warning is logged listing the affected projects so the setup file path can be moved manually.
5+
If the setup file cannot be migrated automatically (e.g. the Jest configuration cannot be parsed because it exports a factory function or assigns `setupFilesAfterEnv` to a non-array value, it sets `rootDir` to a non-literal value, or the target shares a Jest configuration with another target using a different setup file), the deprecated option is still removed and a warning is logged listing the affected targets so the setup file path can be moved manually.
66

77
#### Examples
88

@@ -135,7 +135,7 @@ Remove the option from a target default using the `@nx/jest:jest` executor:
135135
}
136136
```
137137

138-
Per-project paths don't make sense as workspace defaults, so the option is removed without rewriting individual project Jest configs. A warning is logged so the setup file path can be added to each project's Jest config manually if needed.
138+
Per-project paths don't make sense as workspace defaults, so the option is removed from `nx.json`. Base targets that inherited it don't lose their setup file: the inherited path is expanded and added to `setupFilesAfterEnv` in each project's Jest config, the same as if the target had declared the option itself. A warning is logged noting the removal from `nx.json`.
139139

140140
Remove the option from a target default entry matching on the `@nx/jest:jest` executor:
141141

packages/nx/src/internal-testing-utils/assert-valid-migrations.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,28 @@ export function assertValidMigrationPaths(json: MigrationsJson, root: string) {
6161

6262
expect(orphans).toEqual([]);
6363
});
64+
65+
it('should not have orphaned .md files under ./src/migrations', () => {
66+
const migrationsPath = path.join(root, 'src/migrations');
67+
if (!fs.existsSync(migrationsPath)) return;
68+
69+
const referenced = new Set<string>();
70+
const entries = { ...json.generators, ...(json.schematics ?? {}) };
71+
for (const m of Object.values(entries)) {
72+
if (m.prompt) {
73+
referenced.add(toMigrationMarkdownSourcePath(m.prompt));
74+
}
75+
if (m.documentation) {
76+
referenced.add(toMigrationMarkdownSourcePath(m.documentation));
77+
}
78+
}
79+
80+
const orphans = collectMarkdownFiles(migrationsPath)
81+
.map((file) => path.relative(root, file).replace(/\\/g, '/'))
82+
.filter((rel) => !referenced.has(rel));
83+
84+
expect(orphans).toEqual([]);
85+
});
6486
}
6587

6688
function validateMigration(m: MigrationsJsonEntry, root: string) {
@@ -140,6 +162,33 @@ function collectMigrationEntryPointFiles(migrationsPath: string): string[] {
140162
return files;
141163
}
142164

165+
function collectMarkdownFiles(dir: string): string[] {
166+
const files: string[] = [];
167+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
168+
const entryPath = path.join(dir, entry.name);
169+
if (entry.isDirectory()) {
170+
files.push(...collectMarkdownFiles(entryPath));
171+
} else if (entry.name.endsWith('.md')) {
172+
files.push(entryPath);
173+
}
174+
}
175+
return files;
176+
}
177+
178+
/**
179+
* Maps a published markdown path from migrations.json back to its source
180+
* location. Most packages build with rootDir "." and publish
181+
* `./dist/src/migrations/foo.md`, but packages with rootDir "src" publish
182+
* `./dist/migrations/foo.md`, so the stripped `src/` segment is added back.
183+
*/
184+
function toMigrationMarkdownSourcePath(publishedPath: string): string {
185+
const sourcePath = publishedPath
186+
.replace(/\\/g, '/')
187+
.replace(/^\.\//, '')
188+
.replace(/^dist\//, '');
189+
return sourcePath.startsWith('src/') ? sourcePath : `src/${sourcePath}`;
190+
}
191+
143192
function normalizeMigrationImplPath(implPath: string): string {
144193
return implPath
145194
.split('#')[0]

0 commit comments

Comments
 (0)