Skip to main content

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:

  1. Install @comet/cli as a dev dependency

  2. Replace 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",
  3. 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.