Migrating from v4 to v5
API
DependenciesModule
Add the DependenciesModule to AppModule:
import {
...
+ DependenciesModule,
} from "@comet/cms-api";
...
@Module({})
export class AppModule {
static forRoot(config: Config): DynamicModule {
return {
module: AppModule,
imports: [
ConfigModule.forRoot(config),
DbModule,
...
+ DependenciesModule,
],
};
}
}
blocks-meta.json
The key (type) of OneOfBlocks is now included in the blocks-meta.json.
This is only a problem if you still have your own generate-block-types.ts in your project. In that case, you must switch to @comet/cli:
Install
@comet/clias a dev dependencyReplace the scripts in the package.json of your admin:
- "generate-block-types": "dotenv -c -- ts-node generate-block-types.ts",
+ "generate-block-types": "comet generate-block-types --inputs",Replace the scripts in the package.json of your site:
- "generate-block-types": "dotenv -c -- ts-node generate-block-types.ts",
+ "generate-block-types": "comet generate-block-types",
indexData()
Previously, BlockIndexData had one field: damFileIds. It could only represent dependencies to DAM files. Now, BlockIndexData has a generic dependencies field. It can represent dependencies to any entity.
The indexData() method of a block must be refactored
indexData(): BlockIndexData {
- return {
- damFileIds: this.damFileId ? [this.damFileId] : [],
- };
+ if (this.damFileId === undefined) {
+ return {};
+ }
+
+ return {
+ dependencies: [
+ {
+ targetEntityName: File.name,
+ id: this.damFileId,
+ },
+ ],
+ };
}
File and Folder Entities
File and Folder are no longer exported by @comet/cms-api. Instead, use the exported FileInterface and FolderInterface for typing.
If you need classes (e.g. as return type of a GraphQL field), you can create them using the createFileEntity() and createFolderEntity() factories.
You will then need to pass your classes to the DamModule during initialization:
DamModule.register({
// ...
+ File: DamFile,
+ Folder: DamFolder,
})
FilesService.upload()
The method signature changed. The second argument is now an options object. You may have to adjust this in your fixtures.
- await filesService.upload(file, folderId);
+ await filesService.upload(file, { folderId });
@IsOptional() -> @IsUndefinable() and @IsNullable()
class-validator offers the @IsOptional() decorator that allows undefined and null.
COMET now offers the more specific @IsUndefinable() and @IsNullable() decorators to avoid bugs.
Use these decorators from now on and (if possible) replace existing @IsOptional() with @IsUndefinable() or @IsNullable().
PartialType
COMET now has an own implementation of PartialType using @IsUndefinable() (instead of @IsOptional()).
Uses of PartialType from @nestjs/mapped-types should be replaced with PartialType from @comet/cms-api.
Admin
Admin Generator
Add the following command to the package.json of your admin app:
+ "admin-generator": "rimraf 'src/*/generated' && comet-admin-generator generate crud-generator-config.ts",
Documents (DocumentInterface)
The DocumentInterface now requires a dependencies() and a replaceDependenciesInOutput() method. However, you don't have to implement them yourself.
Instead, use the new createDocumentRootBlocksMethods() to generate the methods.
It further generates anchors() and inputToOutput().
export const Page: DocumentInterface<Pick<GQLPage, "content" | "seo">, GQLPageInput> = {
// ...
- inputToOutput: (input) => {},
- anchors: (input) => {},
+ ...createDocumentRootBlocksMethods({
+ content: PageContentBlock,
+ seo: SeoBlock,
+ }),
};
Blocks (BlockInterface)
The BlockInterface now has the methods dependencies() and replaceDependenciesInOutput().
However, you usually don't have to implement them. Only if your block has dependencies to some entity (e.g. a link to a PageTreeNode or uses a DamFile).
dependencies() returns the dependency information for the block. It could look like this:
dependencies: (state) => {
const dependencies: BlockDependency[] = [];
if (state.damFile?.id) {
dependencies.push({
targetGraphqlObjectType: "DamFile",
id: state.damFile.id,
data: {
damFile: state.damFile,
},
});
}
return dependencies;
};
replaceDependenciesInOutput() replaces the IDs of your dependencies when copying the block to another scope:
replaceDependenciesInOutput: (output, replacements) => {
const clonedOutput: PixelImageBlockInput = deepClone(output);
const replacement = replacements.find(
(replacement) =>
replacement.type === "DamFile" && replacement.originalId === output.damFileId,
);
if (replacement) {
clonedOutput.damFileId = replacement.replaceWithId;
}
return clonedOutput;
};
Dashboard
New components DashboardHeader, LatestBuildsDashboardWidget, and LatestContentUpdatesDashboardWidget have been added to replace existing components defined in application code.
See this PR for an example on how to migrate.
BlockPreview
The BlockPreview component was removed. Instead, use BlockPreviewContent:
- const state = linkBlock.input2State(params.value);
- return <BlockPreview title={linkBlock.dynamicDisplayName?.(state) ?? linkBlock.displayName} content={linkBlock.previewContent(state)} />;
+ return <BlockPreviewContent block={linkBlock} input={params.value} />;
@comet/admin
Loading
The Loading component now uses our BallTriangle instead of MUI's CircularProgress. If you use the size prop, you must now use sx instead:
- <Loading behavior="fillParent" size={20} color="inherit" />
+ <Loading behavior="fillParent" sx={{ fontSize: 20 }} color="inherit" />
FinalFormSelect
The getOptionSelected() prop was removed from FinalFormSelect and replaced with getOptionValue() in order to support multi-select.
If you had a custom implementation of getOptionSelected(), you may need to replace it with a custom getOptionValue(). However, for most cases the default implementation will be sufficient.
FilterBarMoreFilters
The textWrapper class of FilterBarMoreFilters was removed. Use the new button class instead.
Site
Restricted imports
The imports for Link, useRouter, and Image from next have been restricted.
Please use Link, useRouter and Image from @comet/cms-site instead.
- import Link from "next/link";
+ import { Link } from "@comet/cms-site";
- import { useRouter } from "next/router";
+ import { useRouter } from "@comet/cms-site";
- import Image from "next/image";
+ import { Image } from "@comet/cms-site";
ESLint
no-private-sibling-import
The new rule no-private-sibling-import bans imports of private sibling files.
Meaning, a file called Foo.gql.ts next to a Foo.ts can only be imported by Foo.ts and no other file.