Files
agentic-dev/docs/superpowers/plans/2026-04-06-plan-3-payload-cms.md
Danijel Martinek 0196de59a4 Add Plan 3: Payload CMS Integration implementation plan
10 tasks covering: cms-core dependencies, Users/Articles/Media collections,
slug auto-generation hook, SiteSettings global, Payload config, dual-mode
cms-client (local + HTTP), thin Next.js admin shell, build verification.
2026-04-06 14:38:33 +02:00

25 KiB

Plan 3: Payload CMS Integration — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Implement Payload CMS integration: @repo/cms-core (config, collections, hooks), @repo/cms-client (dual-mode local+HTTP client with generated types), and apps/cms (thin Next.js shell serving the Payload admin panel).

Architecture: Payload config and collections live in @repo/cms-core — standalone, testable, framework-agnostic. @repo/cms-client provides a dual-mode client (Local API primary, HTTP fallback) that receives a Payload instance via injection. apps/cms is a thin Next.js 15 shell that imports config from cms-core and serves the admin panel. Type generation runs payload generate:types against cms-core's config.

Tech Stack: Payload CMS 3.x, @payloadcms/db-postgres, @payloadcms/next, @payloadcms/richtext-lexical, Next.js 15, PostgreSQL 16, sharp


File Map

packages/cms-core

File Responsibility
packages/cms-core/package.json Dependencies: payload, @payloadcms/db-postgres, @payloadcms/richtext-lexical
packages/cms-core/src/payload.config.ts Root Payload config (db, editor, collections, globals)
packages/cms-core/src/collections/users/index.ts Users collection with auth enabled
packages/cms-core/src/collections/articles/index.ts Articles collection config
packages/cms-core/src/collections/articles/fields.ts Article field definitions
packages/cms-core/src/collections/articles/hooks/before-change.ts Slug auto-generation hook
packages/cms-core/src/collections/media/index.ts Media collection with uploads
packages/cms-core/src/globals/site-settings.ts Site settings global
packages/cms-core/src/index.ts Exports config + all collections

packages/cms-client

File Responsibility
packages/cms-client/package.json Dependencies: payload (types only)
packages/cms-client/src/client.ts createPayloadClient() factory
packages/cms-client/src/local-client.ts LocalPayloadClient — wraps Payload instance
packages/cms-client/src/http-client.ts HTTPPayloadClient — REST API fallback
packages/cms-client/src/types.ts Shared client types (PayloadClient interface)
packages/cms-client/src/index.ts Exports

apps/cms

File Responsibility
apps/cms/package.json Dependencies: next, payload, @payloadcms/next, @repo/cms-core
apps/cms/next.config.mjs Next.js config wrapped with withPayload
apps/cms/src/app/(payload)/admin/[[...segments]]/page.tsx Admin panel catch-all route
apps/cms/src/app/(payload)/admin/[[...segments]]/not-found.tsx Admin 404 page
apps/cms/src/app/(payload)/layout.tsx Payload layout with RootLayout
apps/cms/src/app/(payload)/custom.scss Empty custom styles
apps/cms/src/payload-types.ts Generated types (via payload generate:types)

Task 1: Install cms-core dependencies

Files:

  • Modify: packages/cms-core/package.json

  • Modify: packages/cms-core/tsconfig.json

  • Step 1: Update packages/cms-core/package.json

{
  "name": "@repo/cms-core",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "scripts": {
    "build": "tsc --noEmit",
    "lint": "eslint .",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "payload": "^3.14.0",
    "@payloadcms/db-postgres": "^3.14.0",
    "@payloadcms/richtext-lexical": "^3.14.0"
  },
  "devDependencies": {
    "@repo/eslint-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@types/node": "^22.0.0"
  }
}
  • Step 2: Update tsconfig.json
{
  "extends": "@repo/typescript-config/base.json",
  "compilerOptions": {
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules", "dist"]
}
  • Step 3: Run pnpm install

Run: pnpm install Expected: Payload and its dependencies install successfully.

  • Step 4: Commit
git add packages/cms-core/package.json packages/cms-core/tsconfig.json pnpm-lock.yaml
git commit -m "feat(cms-core): add Payload CMS dependencies"

Task 2: Users collection

Files:

  • Create: packages/cms-core/src/collections/users/index.ts

  • Step 1: Create Users collection

import type { CollectionConfig } from "payload";

export const Users: CollectionConfig = {
  slug: "users",
  auth: true,
  admin: {
    useAsTitle: "email",
  },
  fields: [
    {
      name: "displayName",
      type: "text",
    },
    {
      name: "role",
      type: "select",
      options: [
        { label: "Admin", value: "admin" },
        { label: "Editor", value: "editor" },
        { label: "Author", value: "author" },
      ],
      defaultValue: "author",
      required: true,
    },
  ],
};
  • Step 2: Commit
git add packages/cms-core/src/collections/users/
git commit -m "feat(cms-core): add Users collection with auth"

Task 3: Articles collection with fields and hooks

Files:

  • Create: packages/cms-core/src/collections/articles/fields.ts

  • Create: packages/cms-core/src/collections/articles/hooks/before-change.ts

  • Create: packages/cms-core/src/collections/articles/index.ts

  • Step 1: Create fields.ts

import type { Field } from "payload";

export const articleFields: Field[] = [
  {
    name: "title",
    type: "text",
    required: true,
    maxLength: 255,
  },
  {
    name: "slug",
    type: "text",
    unique: true,
    admin: {
      position: "sidebar",
      description: "Auto-generated from title if left empty",
    },
  },
  {
    name: "content",
    type: "richText",
  },
  {
    name: "status",
    type: "select",
    options: [
      { label: "Draft", value: "draft" },
      { label: "Published", value: "published" },
    ],
    defaultValue: "draft",
    required: true,
    admin: {
      position: "sidebar",
    },
  },
  {
    name: "author",
    type: "relationship",
    relationTo: "users",
    required: true,
    admin: {
      position: "sidebar",
    },
  },
  {
    name: "featuredImage",
    type: "upload",
    relationTo: "media",
  },
  {
    name: "publishedAt",
    type: "date",
    admin: {
      position: "sidebar",
      date: {
        pickerAppearance: "dayAndTime",
      },
    },
  },
];
  • Step 2: Create hooks/before-change.ts

This is a CMS-operational hook (slug auto-generation) — stays in cms-core per the design spec.

import type { CollectionBeforeChangeHook } from "payload";

function generateSlug(title: string): string {
  return title
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "");
}

export const autoGenerateSlug: CollectionBeforeChangeHook = ({
  data,
  operation,
}) => {
  if (operation === "create" || operation === "update") {
    if (data && data.title && !data.slug) {
      data.slug = generateSlug(data.title);
    }
  }
  return data;
};
  • Step 3: Create articles/index.ts
import type { CollectionConfig } from "payload";

import { articleFields } from "./fields.js";
import { autoGenerateSlug } from "./hooks/before-change.js";

export const Articles: CollectionConfig = {
  slug: "articles",
  admin: {
    useAsTitle: "title",
    defaultColumns: ["title", "status", "author", "updatedAt"],
  },
  hooks: {
    beforeChange: [autoGenerateSlug],
  },
  versions: {
    drafts: true,
  },
  fields: articleFields,
};
  • Step 4: Commit
git add packages/cms-core/src/collections/articles/
git commit -m "feat(cms-core): add Articles collection with slug auto-generation hook"

Task 4: Media collection

Files:

  • Create: packages/cms-core/src/collections/media/index.ts

  • Step 1: Create Media collection

import type { CollectionConfig } from "payload";

export const Media: CollectionConfig = {
  slug: "media",
  upload: {
    mimeTypes: ["image/*", "application/pdf"],
  },
  admin: {
    useAsTitle: "filename",
  },
  fields: [
    {
      name: "alt",
      type: "text",
      required: true,
    },
  ],
};
  • Step 2: Commit
git add packages/cms-core/src/collections/media/
git commit -m "feat(cms-core): add Media collection with uploads"

Task 5: Site settings global

Files:

  • Create: packages/cms-core/src/globals/site-settings.ts

  • Step 1: Create site-settings.ts

import type { GlobalConfig } from "payload";

export const SiteSettings: GlobalConfig = {
  slug: "site-settings",
  admin: {
    group: "Settings",
  },
  fields: [
    {
      name: "siteName",
      type: "text",
      required: true,
      defaultValue: "My App",
    },
    {
      name: "siteDescription",
      type: "textarea",
    },
  ],
};
  • Step 2: Commit
git add packages/cms-core/src/globals/
git commit -m "feat(cms-core): add SiteSettings global"

Task 6: Payload config + exports

Files:

  • Create: packages/cms-core/src/payload.config.ts

  • Modify: packages/cms-core/src/index.ts

  • Step 1: Create payload.config.ts

import { buildConfig } from "payload";
import { postgresAdapter } from "@payloadcms/db-postgres";
import { lexicalEditor } from "@payloadcms/richtext-lexical";
import path from "node:path";
import { fileURLToPath } from "node:url";

import { Users } from "./collections/users/index.js";
import { Articles } from "./collections/articles/index.js";
import { Media } from "./collections/media/index.js";
import { SiteSettings } from "./globals/site-settings.js";

const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);

export default buildConfig({
  editor: lexicalEditor(),
  collections: [Users, Articles, Media],
  globals: [SiteSettings],
  secret: process.env.PAYLOAD_SECRET || "default-secret-change-me",
  db: postgresAdapter({
    pool: {
      connectionString:
        process.env.DATABASE_URL ||
        "postgresql://postgres:postgres@localhost:5432/template",
    },
  }),
  typescript: {
    outputFile: path.resolve(dirname, "payload-types.ts"),
  },
});
  • Step 2: Update src/index.ts
export { Users } from "./collections/users/index.js";
export { Articles } from "./collections/articles/index.js";
export { Media } from "./collections/media/index.js";
export { SiteSettings } from "./globals/site-settings.js";
export { default as config } from "./payload.config.js";
  • Step 3: Commit
git add packages/cms-core/src/payload.config.ts packages/cms-core/src/index.ts
git commit -m "feat(cms-core): add Payload config with postgres adapter and lexical editor"

Task 7: cms-client — dual-mode Payload client

Files:

  • Modify: packages/cms-client/package.json

  • Create: packages/cms-client/src/types.ts

  • Create: packages/cms-client/src/local-client.ts

  • Create: packages/cms-client/src/http-client.ts

  • Create: packages/cms-client/src/client.ts

  • Modify: packages/cms-client/src/index.ts

  • Step 1: Update packages/cms-client/package.json

{
  "name": "@repo/cms-client",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "scripts": {
    "build": "tsc --noEmit",
    "lint": "eslint .",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "payload": "^3.14.0"
  },
  "devDependencies": {
    "@repo/eslint-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@types/node": "^22.0.0"
  }
}
  • Step 2: Create src/types.ts
export interface FindOptions {
  where?: Record<string, unknown>;
  sort?: string;
  limit?: number;
  page?: number;
  depth?: number;
  locale?: string;
}

export interface PayloadClientResult<T> {
  docs: T[];
  totalDocs: number;
  limit: number;
  totalPages: number;
  page: number;
  pagingCounter: number;
  hasPrevPage: boolean;
  hasNextPage: boolean;
  prevPage: number | null;
  nextPage: number | null;
}

export interface PayloadClient {
  find<T = Record<string, unknown>>(
    collection: string,
    options?: FindOptions
  ): Promise<PayloadClientResult<T>>;

  findByID<T = Record<string, unknown>>(
    collection: string,
    id: string,
    options?: { depth?: number }
  ): Promise<T>;

  create<T = Record<string, unknown>>(
    collection: string,
    data: Record<string, unknown>,
    options?: { depth?: number }
  ): Promise<T>;

  update<T = Record<string, unknown>>(
    collection: string,
    id: string,
    data: Record<string, unknown>,
    options?: { depth?: number }
  ): Promise<T>;

  delete<T = Record<string, unknown>>(
    collection: string,
    id: string
  ): Promise<T>;
}
  • Step 3: Create src/local-client.ts
import type { Payload } from "payload";
import type { FindOptions, PayloadClient, PayloadClientResult } from "./types.js";

export class LocalPayloadClient implements PayloadClient {
  constructor(private payload: Payload) {}

  async find<T = Record<string, unknown>>(
    collection: string,
    options?: FindOptions
  ): Promise<PayloadClientResult<T>> {
    const result = await this.payload.find({
      collection: collection as any,
      where: options?.where as any,
      sort: options?.sort,
      limit: options?.limit,
      page: options?.page,
      depth: options?.depth,
      locale: options?.locale as any,
    });
    return result as unknown as PayloadClientResult<T>;
  }

  async findByID<T = Record<string, unknown>>(
    collection: string,
    id: string,
    options?: { depth?: number }
  ): Promise<T> {
    const result = await this.payload.findByID({
      collection: collection as any,
      id,
      depth: options?.depth,
    });
    return result as unknown as T;
  }

  async create<T = Record<string, unknown>>(
    collection: string,
    data: Record<string, unknown>,
    options?: { depth?: number }
  ): Promise<T> {
    const result = await this.payload.create({
      collection: collection as any,
      data: data as any,
      depth: options?.depth,
    });
    return result as unknown as T;
  }

  async update<T = Record<string, unknown>>(
    collection: string,
    id: string,
    data: Record<string, unknown>,
    options?: { depth?: number }
  ): Promise<T> {
    const result = await this.payload.update({
      collection: collection as any,
      id,
      data: data as any,
      depth: options?.depth,
    });
    return result as unknown as T;
  }

  async delete<T = Record<string, unknown>>(
    collection: string,
    id: string
  ): Promise<T> {
    const result = await this.payload.delete({
      collection: collection as any,
      id,
    });
    return result as unknown as T;
  }
}
  • Step 4: Create src/http-client.ts
import type { FindOptions, PayloadClient, PayloadClientResult } from "./types.js";

export class HTTPPayloadClient implements PayloadClient {
  constructor(private baseURL: string) {}

  private async request<T>(path: string, options?: RequestInit): Promise<T> {
    const response = await fetch(`${this.baseURL}${path}`, {
      headers: { "Content-Type": "application/json" },
      ...options,
    });
    if (!response.ok) {
      throw new Error(`Payload API error: ${response.status} ${response.statusText}`);
    }
    return response.json() as Promise<T>;
  }

  async find<T = Record<string, unknown>>(
    collection: string,
    options?: FindOptions
  ): Promise<PayloadClientResult<T>> {
    const params = new URLSearchParams();
    if (options?.limit) params.set("limit", String(options.limit));
    if (options?.page) params.set("page", String(options.page));
    if (options?.sort) params.set("sort", options.sort);
    if (options?.depth) params.set("depth", String(options.depth));
    if (options?.where) params.set("where", JSON.stringify(options.where));
    const query = params.toString();
    return this.request<PayloadClientResult<T>>(
      `/api/${collection}${query ? `?${query}` : ""}`
    );
  }

  async findByID<T = Record<string, unknown>>(
    collection: string,
    id: string,
    options?: { depth?: number }
  ): Promise<T> {
    const params = new URLSearchParams();
    if (options?.depth) params.set("depth", String(options.depth));
    const query = params.toString();
    return this.request<T>(
      `/api/${collection}/${id}${query ? `?${query}` : ""}`
    );
  }

  async create<T = Record<string, unknown>>(
    collection: string,
    data: Record<string, unknown>,
    options?: { depth?: number }
  ): Promise<T> {
    const params = new URLSearchParams();
    if (options?.depth) params.set("depth", String(options.depth));
    const query = params.toString();
    return this.request<T>(
      `/api/${collection}${query ? `?${query}` : ""}`,
      { method: "POST", body: JSON.stringify(data) }
    );
  }

  async update<T = Record<string, unknown>>(
    collection: string,
    id: string,
    data: Record<string, unknown>,
    options?: { depth?: number }
  ): Promise<T> {
    const params = new URLSearchParams();
    if (options?.depth) params.set("depth", String(options.depth));
    const query = params.toString();
    return this.request<T>(
      `/api/${collection}/${id}${query ? `?${query}` : ""}`,
      { method: "PATCH", body: JSON.stringify(data) }
    );
  }

  async delete<T = Record<string, unknown>>(
    collection: string,
    id: string
  ): Promise<T> {
    return this.request<T>(`/api/${collection}/${id}`, { method: "DELETE" });
  }
}
  • Step 5: Create src/client.ts
import type { Payload } from "payload";
import type { PayloadClient } from "./types.js";
import { LocalPayloadClient } from "./local-client.js";
import { HTTPPayloadClient } from "./http-client.js";

type PayloadClientOptions =
  | { mode: "local"; payload: Payload }
  | { mode: "http"; baseURL: string };

export function createPayloadClient(options: PayloadClientOptions): PayloadClient {
  if (options.mode === "local") {
    return new LocalPayloadClient(options.payload);
  }
  return new HTTPPayloadClient(options.baseURL);
}
  • Step 6: Update src/index.ts
export { createPayloadClient } from "./client.js";
export { LocalPayloadClient } from "./local-client.js";
export { HTTPPayloadClient } from "./http-client.js";
export type {
  PayloadClient,
  PayloadClientResult,
  FindOptions,
} from "./types.js";
  • Step 7: Run pnpm install and commit

Run: pnpm install

git add packages/cms-client/ pnpm-lock.yaml
git commit -m "feat(cms-client): add dual-mode Payload client (local + HTTP)"

Task 8: apps/cms — thin Next.js shell

Files:

  • Modify: apps/cms/package.json

  • Create: apps/cms/next.config.mjs

  • Create: apps/cms/src/app/(payload)/admin/[[...segments]]/page.tsx

  • Create: apps/cms/src/app/(payload)/admin/[[...segments]]/not-found.tsx

  • Create: apps/cms/src/app/(payload)/layout.tsx

  • Create: apps/cms/src/app/(payload)/custom.scss

  • Modify: apps/cms/tsconfig.json

  • Step 1: Update apps/cms/package.json

{
  "name": "@repo/cms",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "build": "next build",
    "dev": "next dev --port 3001",
    "lint": "eslint .",
    "typecheck": "tsc --noEmit",
    "generate:types": "payload generate:types"
  },
  "dependencies": {
    "@payloadcms/next": "^3.14.0",
    "@payloadcms/ui": "^3.14.0",
    "@repo/cms-core": "workspace:*",
    "next": "^15.3.0",
    "payload": "^3.14.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "sharp": "^0.33.0"
  },
  "devDependencies": {
    "@repo/eslint-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@types/node": "^22.0.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0"
  }
}
  • Step 2: Update apps/cms/tsconfig.json
{
  "extends": "@repo/typescript-config/nextjs.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@payload-config": ["../../packages/cms-core/src/payload.config.ts"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "next-env.d.ts"],
  "exclude": ["node_modules"]
}
  • Step 3: Create next.config.mjs
import { withPayload } from "@payloadcms/next/withPayload";

/** @type {import('next').NextConfig} */
const nextConfig = {};

export default withPayload(nextConfig);
  • Step 4: Create admin catch-all page
// apps/cms/src/app/(payload)/admin/[[...segments]]/page.tsx
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from "next";

import config from "@payload-config";
import { RootPage, generatePageMetadata } from "@payloadcms/next/views";
import { importMap } from "../importMap.js";

type Args = {
  params: Promise<{ segments: string[] }>;
  searchParams: Promise<Record<string, string | string[]>>;
};

export const generateMetadata = ({
  params,
  searchParams,
}: Args): Promise<Metadata> =>
  generatePageMetadata({ config, params, searchParams });

const Page = ({ params, searchParams }: Args) =>
  RootPage({ config, importMap, params, searchParams });

export default Page;
  • Step 5: Create admin not-found page
// apps/cms/src/app/(payload)/admin/[[...segments]]/not-found.tsx
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from "next";

import config from "@payload-config";
import { NotFoundPage, generatePageMetadata } from "@payloadcms/next/views";
import { importMap } from "../importMap.js";

type Args = {
  params: Promise<{ segments: string[] }>;
  searchParams: Promise<Record<string, string | string[]>>;
};

export const generateMetadata = ({
  params,
  searchParams,
}: Args): Promise<Metadata> =>
  generatePageMetadata({ config, params, searchParams });

const NotFound = ({ params, searchParams }: Args) =>
  NotFoundPage({ config, importMap, params, searchParams });

export default NotFound;
  • Step 6: Create payload layout
// apps/cms/src/app/(payload)/layout.tsx
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { ServerFunctionClient } from "payload";

import config from "@payload-config";
import { RootLayout } from "@payloadcms/next/layouts";
import React from "react";

import { importMap } from "./importMap.js";
import "./custom.scss";

type Args = {
  children: React.ReactNode;
};

const serverFunction: ServerFunctionClient = async function (args) {
  "use server";
  const { default: payloadModule } = await import("payload");
  return payloadModule.handleServerFunctions({ ...args, config, importMap });
};

const Layout = ({ children }: Args) => (
  <RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
    {children}
  </RootLayout>
);

export default Layout;
  • Step 7: Create empty importMap and custom.scss
// apps/cms/src/app/(payload)/importMap.js
export const importMap = {};
// apps/cms/src/app/(payload)/custom.scss
// Custom admin panel styles
  • Step 8: Run pnpm install and commit

Run: pnpm install

git add apps/cms/ pnpm-lock.yaml
git commit -m "feat(cms): add thin Next.js shell for Payload admin panel"

Task 9: Update docker-compose with CMS service

Files:

  • Modify: docker-compose.yml

  • Step 1: Update docker-compose.yml

services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: template
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

Note: CMS app service will be added when Dockerfiles are created in a later plan. For now, local dev uses pnpm dev --filter @repo/cms.

  • Step 2: Commit (no change needed if docker-compose is already correct)

Only commit if docker-compose was modified.


Task 10: Verify build

  • Step 1: Run pnpm install from root

Run: pnpm install Expected: All dependencies resolve.

  • Step 2: Run turbo build

Run: pnpm build Expected: All workspaces build. The cms-core and cms-client packages should pass tsc --noEmit. The cms app should run next build (may need database for full build — if it fails due to no DB, that's expected and acceptable at this stage).

  • Step 3: Commit any remaining changes
git add -A && git status
# Only commit if there are changes
git commit -m "chore: update lockfile after CMS integration"