Generates list of migrations on the plugin overview page and a standalone `/migrations` page. To add sample code changes for a migration that has an implementation file, create a `.md` file with the same name as the implementation file in the same folder as the implementation file. i.e. `move-cache-directory.md` for `move-cache-directory.ts`. Migrations that have `packages` defined will have a table generated with the package updates listed. Separate PRs will be created to add sample code changes for each migration with an implementation. The migration list on the plugin overview page: [Angular migrations](https://nx-dev-git-docs-migration-details-nrwl.vercel.app/nx-api/angular#migrations) Standalone migration list page: [Angular migrations](https://nx-dev-git-docs-migration-details-nrwl.vercel.app/nx-api/angular/migrations) Sample migration with added markdown file details: [17.0.0-move-cache-directory](https://nx-dev-git-docs-migration-details-nrwl.vercel.app/nx-api/nx#1700movecachedirectory) Sample migration with only package updates: [Angular 20.4.0](https://nx-dev-git-docs-migration-details-nrwl.vercel.app/nx-api/angular#2040packageupdates) Sample migration without any markdown file details: [update-angular-cli-version-19-1-0](https://nx-dev-git-docs-migration-details-nrwl.vercel.app/nx-api/angular#updateangularcliversion1910) - This last sample is very bare-bones and the reason why we need these pages in the first place. People don't know what migrations are actually doing. Follow up PRs will address pages like this.
185 lines
5.3 KiB
TypeScript
185 lines
5.3 KiB
TypeScript
import { TagsApi } from '@nx/nx-dev/data-access-documents/node-only';
|
|
import { DocumentMetadata } from '@nx/nx-dev/models-document';
|
|
import {
|
|
FileMetadata,
|
|
IntrinsicPackageMetadata,
|
|
ProcessedPackageMetadata,
|
|
SchemaMetadata,
|
|
} from '@nx/nx-dev/models-package';
|
|
import { readFileSync, lstatSync, readdirSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
interface StaticDocumentPaths {
|
|
params: { segments: string[] };
|
|
}
|
|
|
|
export class PackagesApi {
|
|
private readonly manifest: Record<string, ProcessedPackageMetadata>;
|
|
|
|
constructor(
|
|
private readonly options: {
|
|
id: string;
|
|
manifest: Record<string, ProcessedPackageMetadata>;
|
|
prefix: string;
|
|
publicDocsRoot: string;
|
|
tagsApi: TagsApi;
|
|
}
|
|
) {
|
|
if (!options.id) {
|
|
throw new Error('id cannot be undefined');
|
|
}
|
|
if (!options.prefix) {
|
|
options.prefix = '';
|
|
}
|
|
if (!options.publicDocsRoot) {
|
|
throw new Error('public docs root cannot be undefined');
|
|
}
|
|
if (!options.manifest) {
|
|
throw new Error('public document sources cannot be undefined');
|
|
}
|
|
|
|
this.manifest = structuredClone(this.options.manifest);
|
|
}
|
|
|
|
getFilePath(path: string): string {
|
|
return join(this.options.publicDocsRoot, path);
|
|
}
|
|
|
|
/**
|
|
* Give a list of available segments/paths for the Nextjs app.
|
|
*/
|
|
getStaticDocumentPaths(): {
|
|
packages: StaticDocumentPaths[];
|
|
documents: StaticDocumentPaths[];
|
|
executors: StaticDocumentPaths[];
|
|
generators: StaticDocumentPaths[];
|
|
migrations: StaticDocumentPaths[];
|
|
} {
|
|
/**
|
|
* TODO: Extract this into utils, can be used by DocumentsAPI as well.
|
|
* Generate a Nextjs Segments Param from a path and prefix (optional)
|
|
* @param {string} path
|
|
* @param {string} prefix
|
|
* @returns {StaticDocumentPaths}
|
|
*/
|
|
function generateSegments(
|
|
path: string,
|
|
prefix: string = ''
|
|
): StaticDocumentPaths {
|
|
const segments = path.split('/').filter(Boolean).flat();
|
|
return {
|
|
params: {
|
|
segments: !!prefix ? [prefix].concat(segments) : segments,
|
|
},
|
|
};
|
|
}
|
|
|
|
const packages = Object.values(this.manifest);
|
|
const experiment: {
|
|
packages: StaticDocumentPaths[];
|
|
documents: StaticDocumentPaths[];
|
|
executors: StaticDocumentPaths[];
|
|
generators: StaticDocumentPaths[];
|
|
migrations: StaticDocumentPaths[];
|
|
} = {
|
|
packages: [],
|
|
documents: [],
|
|
executors: [],
|
|
generators: [],
|
|
migrations: [],
|
|
};
|
|
|
|
packages.forEach((p) => {
|
|
experiment.packages.push(generateSegments(p.path, this.options.prefix));
|
|
|
|
Object.keys(p.documents).map((path) =>
|
|
experiment.documents.push(generateSegments(path, this.options.prefix))
|
|
);
|
|
if (p.name === 'devkit') {
|
|
readdirSync('../../docs/generated/devkit').forEach((fileName) => {
|
|
if (fileName.endsWith('.md')) {
|
|
experiment.documents.push(
|
|
generateSegments(
|
|
`packages/devkit/documents/${fileName.replace('.md', '')}`,
|
|
this.options.prefix
|
|
)
|
|
);
|
|
} else {
|
|
readdirSync('../../docs/generated/devkit/' + fileName).forEach(
|
|
(subFileName) => {
|
|
experiment.documents.push(
|
|
generateSegments(
|
|
`packages/devkit/documents/${fileName}/${subFileName.replace(
|
|
'.md',
|
|
''
|
|
)}`,
|
|
this.options.prefix
|
|
)
|
|
);
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
Object.keys(p.executors).forEach((path) =>
|
|
experiment.executors.push(generateSegments(path, this.options.prefix))
|
|
);
|
|
|
|
Object.keys(p.generators).forEach((path) =>
|
|
experiment.generators.push(generateSegments(path, this.options.prefix))
|
|
);
|
|
|
|
Object.keys(p.migrations).forEach((path) =>
|
|
experiment.migrations.push(generateSegments(path, this.options.prefix))
|
|
);
|
|
});
|
|
|
|
return experiment;
|
|
}
|
|
|
|
getPackage(path: string[]): ProcessedPackageMetadata {
|
|
const pkg: ProcessedPackageMetadata | null =
|
|
this.manifest[path.join('/')] || null;
|
|
|
|
if (!pkg)
|
|
throw new Error(
|
|
`Package not found in manifest with: "${path.join('/')}"`
|
|
);
|
|
|
|
return {
|
|
...pkg,
|
|
description: pkg.documents['overview']
|
|
? readFileSync(pkg.documents['overview'].file, 'utf-8')
|
|
: pkg.description,
|
|
};
|
|
}
|
|
|
|
getPackageDocuments(name: string): Record<string, DocumentMetadata> {
|
|
return this.manifest[name]['documents'];
|
|
}
|
|
getPackageFileMetadatas(
|
|
name: string,
|
|
type: 'executors' | 'generators' | 'migrations'
|
|
): Record<string, FileMetadata> {
|
|
return this.manifest[name][type];
|
|
}
|
|
getSchemaMetadata(fileMetadata: FileMetadata): SchemaMetadata {
|
|
return JSON.parse(
|
|
readFileSync(this.getFilePath(fileMetadata.file), 'utf-8')
|
|
);
|
|
}
|
|
|
|
getRootPackageIndex(): IntrinsicPackageMetadata[] {
|
|
return Object.keys(this.manifest).map((k) => ({
|
|
description: this.manifest[k].description,
|
|
githubRoot: this.manifest[k].githubRoot,
|
|
name: this.manifest[k].name,
|
|
packageName: this.manifest[k].packageName,
|
|
path: this.manifest[k].path,
|
|
root: this.manifest[k].root,
|
|
source: this.manifest[k].source,
|
|
}));
|
|
}
|
|
}
|